From 65d00c923ae26e805f38ab8e3668dc3106cce9a3 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 00:02:28 +0400 Subject: [PATCH 01/13] updated tag issue for subspace --- lib/common/edit_chip.dart | 39 +++ .../all_spaces/model/base_tag.dart | 26 ++ .../all_spaces/model/tag.dart | 35 +-- .../widgets/community_structure_widget.dart | 3 +- .../widgets/dialogs/create_space_dialog.dart | 228 +++++------------- .../views/assign_tag_models_dialog.dart | 12 +- .../create_subspace/bloc/subspace_bloc.dart | 60 ++++- .../create_subspace/bloc/subspace_state.dart | 5 +- .../views/create_subspace_model_dialog.dart | 113 +++++---- .../spaces_management/helper/tag_helper.dart | 3 +- .../bloc/create_space_model_bloc.dart | 2 +- .../space_model/models/tag_model.dart | 35 +-- .../space_model/view/space_model_page.dart | 4 +- .../widgets/button_content_widget.dart | 30 ++- .../dialog/create_space_model_dialog.dart | 8 +- .../widgets/subspace_model_create_widget.dart | 34 +-- .../widgets/subspace_name_label_widget.dart | 40 +++ .../widgets/tag_chips_display_widget.dart | 66 +++-- .../views/add_device_type_model_widget.dart | 14 +- 19 files changed, 408 insertions(+), 349 deletions(-) create mode 100644 lib/common/edit_chip.dart create mode 100644 lib/pages/spaces_management/all_spaces/model/base_tag.dart create mode 100644 lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart diff --git a/lib/common/edit_chip.dart b/lib/common/edit_chip.dart new file mode 100644 index 00000000..7607834d --- /dev/null +++ b/lib/common/edit_chip.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class EditChip extends StatelessWidget { + final String label; + final VoidCallback onTap; + final Color labelColor; + final Color backgroundColor; + final Color borderColor; + final double borderRadius; + + const EditChip({ + Key? key, + this.label = 'Edit', + required this.onTap, + this.labelColor = ColorsManager.spaceColor, + this.backgroundColor = ColorsManager.whiteColors, + this.borderColor = ColorsManager.spaceColor, + this.borderRadius = 16.0, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Chip( + label: Text( + label, + style: TextStyle(color: labelColor), + ), + backgroundColor: backgroundColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius), + side: BorderSide(color: borderColor), + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/all_spaces/model/base_tag.dart b/lib/pages/spaces_management/all_spaces/model/base_tag.dart new file mode 100644 index 00000000..57f223f4 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/model/base_tag.dart @@ -0,0 +1,26 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:uuid/uuid.dart'; + +abstract class BaseTag { + String? uuid; + String? tag; + final ProductModel? product; + String internalId; + String? location; + + BaseTag({ + this.uuid, + required this.tag, + this.product, + String? internalId, + this.location, + }) : internalId = internalId ?? const Uuid().v4(); + + Map toJson(); + BaseTag copyWith({ + String? tag, + ProductModel? product, + String? location, + String? internalId, + }); +} diff --git a/lib/pages/spaces_management/all_spaces/model/tag.dart b/lib/pages/spaces_management/all_spaces/model/tag.dart index 98494f7f..34bd08bb 100644 --- a/lib/pages/spaces_management/all_spaces/model/tag.dart +++ b/lib/pages/spaces_management/all_spaces/model/tag.dart @@ -1,22 +1,23 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; import 'package:uuid/uuid.dart'; -class Tag { - String? uuid; - String? tag; - final ProductModel? product; - String internalId; - String? location; - - Tag( - {this.uuid, - required this.tag, - this.product, - String? internalId, - this.location}) - : internalId = internalId ?? const Uuid().v4(); +class Tag extends BaseTag { + Tag({ + String? uuid, + required String? tag, + ProductModel? product, + String? internalId, + String? location, + }) : super( + uuid: uuid, + tag: tag, + product: product, + internalId: internalId, + location: location, + ); factory Tag.fromJson(Map json) { final String internalId = json['internalId'] ?? const Uuid().v4(); @@ -31,15 +32,19 @@ class Tag { ); } + @override Tag copyWith({ String? tag, ProductModel? product, String? location, + String? internalId, }) { return Tag( + uuid: uuid, tag: tag ?? this.tag, product: product ?? this.product, location: location ?? this.location, + internalId: internalId ?? this.internalId, ); } @@ -60,7 +65,7 @@ extension TagModelExtensions on Tag { ..productUuid = product?.uuid; } - CreateTagBodyModel toCreateTagBodyModel() { + CreateTagBodyModel toCreateTagBodyModel() { return CreateTagBodyModel() ..tag = tag ?? '' ..productUuid = product?.uuid; diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index f569d252..8c6e36f5 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -344,6 +344,7 @@ class _CommunityStructureAreaState extends State { builder: (BuildContext context) { return CreateSpaceDialog( products: widget.products, + spaceModels: widget.spaceModels, name: space.name, icon: space.icon, editSpace: space, @@ -463,7 +464,7 @@ class _CommunityStructureAreaState extends State { _markChildrenAsDeleted(space); } } - + _removeConnectionsForDeletedSpaces(); }); } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 6f3e9fcb..7844381d 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/common/edit_chip.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/add_device_type/views/add_device_type_widget.dart'; @@ -10,15 +11,23 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_mo import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.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/button_content_widget.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/constants/space_icon_const.dart'; class CreateSpaceDialog extends StatefulWidget { - final Function(String, String, List selectedProducts, - SpaceTemplateModel? spaceModel, List? subspaces, List? tags) onCreateSpace; + final Function( + String, + String, + List selectedProducts, + SpaceTemplateModel? spaceModel, + List? subspaces, + List? tags) onCreateSpace; final List? products; final String? name; final String? icon; @@ -211,42 +220,13 @@ class CreateSpaceDialogState extends State { ), const SizedBox(height: 10), selectedSpaceModel == null - ? DefaultButton( + ? TextButton( onPressed: () { _showLinkSpaceModelDialog(context); }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.link, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Link a space model', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), + child: const ButtonContentWidget( + svgAssets: Assets.link, + label: 'Link a space model', ), ) : Container( @@ -307,7 +287,7 @@ class CreateSpaceDialogState extends State { ), ), Padding( - padding: EdgeInsets.symmetric(horizontal: 8.0), + padding: EdgeInsets.symmetric(horizontal: 6.0), child: Text( 'OR', style: TextStyle( @@ -326,47 +306,21 @@ class CreateSpaceDialogState extends State { ), const SizedBox(height: 25), subspaces == null - ? DefaultButton( - onPressed: () { + ? TextButton( + style: TextButton.styleFrom( + overlayColor: ColorsManager.transparentColor, + ), + onPressed: () async { _showSubSpaceDialog(context, enteredName, [], false, widget.products, subspaces); }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Create sub space', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Create Sub Space', ), ) : SizedBox( - width: screenWidth * 0.35, + width: screenWidth * 0.25, child: Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( @@ -383,49 +337,15 @@ class CreateSpaceDialogState extends State { children: [ if (subspaces != null) ...subspaces!.map( - (subspace) => Chip( - label: Text( - subspace.subspaceName, - style: const TextStyle( - color: ColorsManager - .spaceColor), // Text color - ), - backgroundColor: ColorsManager - .whiteColors, // Chip background color - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - 16), // Rounded chip - side: const BorderSide( - color: ColorsManager - .spaceColor), // Border color - ), - ), - ), - GestureDetector( + (subspace) => SubspaceNameDisplayWidget( + text: subspace.subspaceName, + )), + EditChip( onTap: () async { - _showSubSpaceDialog( - context, - enteredName, - [], - false, - widget.products, - subspaces); + _showSubSpaceDialog(context, enteredName, + [], true, widget.products, subspaces); }, - child: Chip( - label: const Text( - 'Edit', - style: TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: - ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.spaceColor), - ), - ), - ), + ) ], ), ), @@ -452,7 +372,7 @@ class CreateSpaceDialogState extends State { runSpacing: 8.0, children: [ // Combine tags from spaceModel and subspaces - ..._groupTags([ + ...TagHelper.groupTags([ ...?tags, ...?subspaces?.expand( (subspace) => subspace.tags ?? []) @@ -484,70 +404,31 @@ class CreateSpaceDialogState extends State { ), ), ), - GestureDetector( - onTap: () async { - _showTagCreateDialog(context, enteredName, - widget.products); - // Edit action - }, - child: Chip( - label: const Text( - 'Edit', - style: TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: - ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide( - color: ColorsManager.spaceColor), - ), - ), - ), + + EditChip(onTap: () async { + _showTagCreateDialog( + context, + enteredName, + widget.products, + ); + // Edit action + }) ], ), ), ) - : DefaultButton( + : TextButton( onPressed: () { _showTagCreateDialog( context, enteredName, widget.products); }, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: Colors.black, - borderColor: ColorsManager.neutralGray, - borderRadius: 16.0, - padding: 10.0, // Reduced padding for smaller size - child: Align( - alignment: Alignment.centerLeft, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.only(left: 6.0), - child: SvgPicture.asset( - Assets.addIcon, - width: screenWidth * - 0.015, // Adjust icon size - height: screenWidth * 0.015, - ), - ), - const SizedBox(width: 3), - Flexible( - child: Text( - 'Add devices', - overflow: TextOverflow - .ellipsis, // Prevent overflow - style: Theme.of(context) - .textTheme - .bodyMedium, - ), - ), - ], - ), + style: TextButton.styleFrom( + padding: EdgeInsets.zero, ), - ) + child: const ButtonContentWidget( + icon: Icons.add, + label: 'Add Devices', + )) ], ), ), @@ -579,8 +460,13 @@ class CreateSpaceDialogState extends State { ? enteredName : (widget.name ?? ''); if (newName.isNotEmpty) { - widget.onCreateSpace(newName, selectedIcon, - selectedProducts, selectedSpaceModel,subspaces,tags); + widget.onCreateSpace( + newName, + selectedIcon, + selectedProducts, + selectedSpaceModel, + subspaces, + tags); Navigator.of(context).pop(); } } @@ -655,7 +541,7 @@ class CreateSpaceDialogState extends State { builder: (BuildContext context) { return CreateSubSpaceDialog( spaceName: name, - dialogTitle: 'Create Sub-space', + dialogTitle: isEdit ? 'Edit Sub-space' : 'Create Sub-space', spaceTags: spaceTags, isEdit: isEdit, products: products, diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index fc778436..5eef92f8 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -29,6 +29,7 @@ class AssignTagModelsDialog extends StatelessWidget { final String title; final BuildContext? pageContext; final List? otherSpaceModels; + final List? allSpaceModels; const AssignTagModelsDialog( {Key? key, @@ -42,7 +43,8 @@ class AssignTagModelsDialog extends StatelessWidget { required this.title, this.pageContext, this.otherSpaceModels, - this.spaceModel}) + this.spaceModel, + this.allSpaceModels}) : super(key: key); @override @@ -212,8 +214,8 @@ class AssignTagModelsDialog extends StatelessWidget { width: double.infinity, child: DialogDropdown( items: locations, - selectedValue: - tag.location ?? 'Main Space', + selectedValue: tag.location ?? + 'Main Space', onSelected: (value) { context .read< @@ -250,8 +252,7 @@ class AssignTagModelsDialog extends StatelessWidget { label: 'Add New Device', onPressed: () async { for (var tag in state.tags) { - if (tag.location == null || - subspaces == null) { + if (tag.location == null) { continue; } @@ -352,6 +353,7 @@ class AssignTagModelsDialog extends StatelessWidget { builder: (BuildContext dialogContext) { return CreateSpaceModelDialog( products: products, + allSpaceModels: allSpaceModels, allTags: allTags, pageContext: pageContext, otherSpaceModels: otherSpaceModels, diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart index 6a072e4a..5426f8f0 100644 --- a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart @@ -6,18 +6,23 @@ import 'subspace_event.dart'; import 'subspace_state.dart'; class SubSpaceBloc extends Bloc { - SubSpaceBloc() : super(SubSpaceState([], [], '')) { + SubSpaceBloc() : super(SubSpaceState([], [], '',{})) { on((event, emit) { - final existingNames = - state.subSpaces.map((e) => e.subspaceName).toSet(); + final existingNames = state.subSpaces.map((e) => e.subspaceName).toSet(); if (existingNames.contains(event.subSpace.subspaceName.toLowerCase())) { + final updatedDuplicates = Set.from(state.duplicates) + ..add(event.subSpace.subspaceName.toLowerCase()); + final updatedSubSpaces = List.from(state.subSpaces) + ..add(event.subSpace); emit(SubSpaceState( - state.subSpaces, + updatedSubSpaces, state.updatedSubSpaceModels, - 'Subspace name already exists.', + '*Duplicated sub-space name', + updatedDuplicates, )); } else { + // Add subspace if no duplicate exists final updatedSubSpaces = List.from(state.subSpaces) ..add(event.subSpace); @@ -25,6 +30,8 @@ class SubSpaceBloc extends Bloc { updatedSubSpaces, state.updatedSubSpaceModels, '', + state.duplicates, +// Clear error message )); } }); @@ -38,6 +45,16 @@ class SubSpaceBloc extends Bloc { state.updatedSubSpaceModels, ); + final nameOccurrences = {}; + for (final subSpace in updatedSubSpaces) { + final lowerName = subSpace.subspaceName.toLowerCase(); + nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1; + } + + final updatedDuplicates = nameOccurrences.entries + .where((entry) => entry.value > 1) + .map((entry) => entry.key) + .toSet(); if (event.subSpace.uuid?.isNotEmpty ?? false) { updatedSubspaceModels.add(UpdateSubspaceModel( action: Action.delete, @@ -45,13 +62,36 @@ class SubSpaceBloc extends Bloc { )); } - emit(SubSpaceState( - updatedSubSpaces, - updatedSubspaceModels, - '', // Clear error message - )); + emit(SubSpaceState(updatedSubSpaces, updatedSubspaceModels, '', + updatedDuplicates // Clear error message + )); }); // Handle UpdateSubSpace Event + + on((event, emit) { + final updatedSubSpaces = state.subSpaces.map((subSpace) { + if (subSpace.uuid == event.updatedSubSpace.uuid) { + return event.updatedSubSpace; + } + return subSpace; + }).toList(); + + final updatedSubspaceModels = List.from( + state.updatedSubSpaceModels, + ); + + updatedSubspaceModels.add(UpdateSubspaceModel( + action: Action.update, + uuid: event.updatedSubSpace.uuid!, + )); + + emit(SubSpaceState( + updatedSubSpaces, + updatedSubspaceModels, + '', + state.duplicates, + )); + }); } } diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart index d1374ea1..9521ff2b 100644 --- a/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_state.dart @@ -4,23 +4,26 @@ class SubSpaceState { final List subSpaces; final List updatedSubSpaceModels; final String errorMessage; + final Set duplicates; SubSpaceState( this.subSpaces, this.updatedSubSpaceModels, this.errorMessage, + this.duplicates, ); - SubSpaceState copyWith({ List? subSpaces, List? updatedSubSpaceModels, String? errorMessage, + Set? duplicates, }) { return SubSpaceState( subSpaces ?? this.subSpaces, updatedSubSpaceModels ?? this.updatedSubSpaceModels, errorMessage ?? this.errorMessage, + duplicates ?? this.duplicates, ); } } diff --git a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart index 6fd0b936..19babff9 100644 --- a/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart @@ -81,41 +81,64 @@ class CreateSubSpaceDialog extends StatelessWidget { spacing: 8.0, runSpacing: 8.0, children: [ - ...state.subSpaces.map( - (subSpace) => Chip( - label: Text( - subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: const BorderSide( - color: ColorsManager.transparentColor, - width: 0, - ), - ), - deleteIcon: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all( - color: ColorsManager.lightGrayColor, - width: 1.5, + ...state.subSpaces.asMap().entries.map( + (entry) { + final index = entry.key; + final subSpace = entry.value; + + final lowerName = + subSpace.subspaceName.toLowerCase(); + + final duplicateIndices = state.subSpaces + .asMap() + .entries + .where((e) => + e.value.subspaceName.toLowerCase() == + lowerName) + .map((e) => e.key) + .toList(); + final isDuplicate = + duplicateIndices.length > 1 && + duplicateIndices.indexOf(index) != 0; + + return Chip( + label: Text( + subSpace.subspaceName, + style: const TextStyle( + color: ColorsManager.spaceColor, ), ), - child: const Icon( - Icons.close, - size: 16, - color: ColorsManager.lightGrayColor, + backgroundColor: ColorsManager.whiteColors, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + side: BorderSide( + color: isDuplicate + ? ColorsManager.red + : ColorsManager.transparentColor, + width: 0, + ), ), - ), - onDeleted: () => context - .read() - .add(RemoveSubSpace(subSpace)), - ), + deleteIcon: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager.lightGrayColor, + width: 1.5, + ), + ), + child: const Icon( + Icons.close, + size: 16, + color: ColorsManager.lightGrayColor, + ), + ), + onDeleted: () => context + .read() + .add(RemoveSubSpace(subSpace)), + ); + }, ), SizedBox( width: 200, @@ -142,27 +165,29 @@ class CreateSubSpaceDialog extends StatelessWidget { color: ColorsManager.blackColor), ), ), - if (state.errorMessage.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - state.errorMessage, - style: const TextStyle( - color: ColorsManager.warningRed, - fontSize: 12, - ), - ), - ), ], ), ), + if (state.errorMessage.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + state.errorMessage, + style: const TextStyle( + color: ColorsManager.warningRed, + fontSize: 12, + ), + ), + ), const SizedBox(height: 16), Row( children: [ Expanded( child: CancelButton( label: 'Cancel', - onPressed: () async {}, + onPressed: () async { + Navigator.of(context).pop(); + }, ), ), const SizedBox(width: 10), diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index d4a0ea55..c0213622 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -1,3 +1,4 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; @@ -33,7 +34,7 @@ class TagHelper { return initialTags; } - static Map groupTags(List tags) { + static Map groupTags(List tags) { final Map groupedTags = {}; for (var tag in tags) { if (tag.product != null) { 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 index d8b39216..5553d8b0 100644 --- 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 @@ -301,7 +301,7 @@ class CreateSpaceModelBloc if (newTags != null || prevTags != null) { // Case 1: Tags deleted if (prevTags != null && newTags != null) { - for (var prevTag in prevTags!) { + for (var prevTag in prevTags) { final existsInNew = newTags!.any((newTag) => newTag.uuid == prevTag.uuid); if (!existsInNew) { diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 48f89167..c1ab4f40 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -1,22 +1,22 @@ +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/create_space_template_body_model.dart'; import 'package:uuid/uuid.dart'; -class TagModel { - String? uuid; - String? tag; - final ProductModel? product; - String internalId; - String? location; - - TagModel( - {this.uuid, - required this.tag, - this.product, - String? internalId, - this.location}) - : internalId = internalId ?? const Uuid().v4(); - +class TagModel extends BaseTag { + TagModel({ + String? uuid, + required String? tag, + ProductModel? product, + String? internalId, + String? location, + }) : super( + uuid: uuid, + tag: tag, + product: product, + internalId: internalId, + location: location, + ); factory TagModel.fromJson(Map json) { final String internalId = json['internalId'] ?? const Uuid().v4(); @@ -30,16 +30,19 @@ class TagModel { ); } + @override TagModel copyWith( {String? tag, ProductModel? product, + String? uuid, String? location, - String? internalId}) { + String? internalId}) { return TagModel( tag: tag ?? this.tag, product: product ?? this.product, location: location ?? this.location, internalId: internalId ?? this.internalId, + uuid:uuid?? this.uuid ); } 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 ae623e81..cb7bc0c9 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 @@ -63,7 +63,8 @@ class SpaceModelPage extends StatelessWidget { } // Render existing space model final model = spaceModels[index]; - final otherModel = List.from(allSpaceModelNames); + final otherModel = + List.from(allSpaceModelNames); otherModel.remove(model.modelName); return GestureDetector( onTap: () { @@ -76,6 +77,7 @@ class SpaceModelPage extends StatelessWidget { spaceModel: model, otherSpaceModels: otherModel, pageContext: context, + allSpaceModels: spaceModels, ); }, ); diff --git a/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart b/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart index 81ecb674..a3ccad7c 100644 --- a/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/button_content_widget.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class ButtonContentWidget extends StatelessWidget { - final IconData icon; + final IconData? icon; final String label; + final String? svgAssets; - const ButtonContentWidget({ - Key? key, - required this.icon, - required this.label, - }) : super(key: key); + const ButtonContentWidget( + {Key? key, this.icon, required this.label, this.svgAssets}) + : super(key: key); @override Widget build(BuildContext context) { @@ -30,10 +30,20 @@ class ButtonContentWidget extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), child: Row( children: [ - Icon( - icon, - color: ColorsManager.spaceColor, - ), + if (icon != null) + Icon( + icon, + color: ColorsManager.spaceColor, + ), + if (svgAssets != null) + Padding( + padding: const EdgeInsets.only(left: 6.0), + child: SvgPicture.asset( + svgAssets!, + width: screenWidth * 0.015, // Adjust icon size + height: screenWidth * 0.015, + ), + ), const SizedBox(width: 10), Expanded( child: Text( 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 c1bea0fd..c653d1b3 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 @@ -22,6 +22,7 @@ class CreateSpaceModelDialog extends StatelessWidget { final SpaceTemplateModel? spaceModel; final BuildContext? pageContext; final List? otherSpaceModels; + final List? allSpaceModels; const CreateSpaceModelDialog( {Key? key, @@ -29,7 +30,8 @@ class CreateSpaceModelDialog extends StatelessWidget { this.allTags, this.spaceModel, this.pageContext, - this.otherSpaceModels}) + this.otherSpaceModels, + this.allSpaceModels}) : super(key: key); @override @@ -138,6 +140,7 @@ class CreateSpaceModelDialog extends StatelessWidget { spaceNameController: spaceNameController, pageContext: pageContext, otherSpaceModels: otherSpaceModels, + allSpaceModels: allSpaceModels, ), const SizedBox(height: 20), SizedBox( @@ -147,7 +150,8 @@ class CreateSpaceModelDialog extends StatelessWidget { Expanded( child: CancelButton( label: 'Cancel', - onPressed: () => Navigator.of(context).pop(), + onPressed: (){ + Navigator.of(context).pop();}, ), ), const SizedBox(width: 10), diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart index 0dda53a6..8dc981da 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_model_create_widget.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/common/edit_chip.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/widgets/button_content_widget.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class SubspaceModelCreate extends StatelessWidget { @@ -46,39 +48,13 @@ class SubspaceModelCreate extends StatelessWidget { spacing: 8.0, runSpacing: 8.0, children: [ - ...subspaces.map((subspace) => Container( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 4.0), - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(10), - border: Border.all( - color: ColorsManager.transparentColor), - ), - child: Text( - subspace.subspaceName, - style: Theme.of(context) - .textTheme - .bodySmall - ?.copyWith(color: ColorsManager.spaceColor), - ), + ...subspaces.map((subspace) => SubspaceNameDisplayWidget( + text: subspace.subspaceName, )), - GestureDetector( + EditChip( onTap: () async { await _openDialog(context, 'Edit Sub-space'); }, - child: Chip( - label: const Text( - 'Edit', - style: TextStyle(color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: - const BorderSide(color: ColorsManager.spaceColor), - ), - ), ), ], ), diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart new file mode 100644 index 00000000..fd3c90b6 --- /dev/null +++ b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; + +class SubspaceNameDisplayWidget extends StatelessWidget { + final String text; + final TextStyle? textStyle; + final Color backgroundColor; + final Color borderColor; + final EdgeInsetsGeometry padding; + final BorderRadiusGeometry borderRadius; + + const SubspaceNameDisplayWidget({ + Key? key, + required this.text, + this.textStyle, + this.backgroundColor = Colors.white, + this.borderColor = Colors.transparent, + this.padding = const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), + this.borderRadius = const BorderRadius.all(Radius.circular(10)), + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: padding, + decoration: BoxDecoration( + color: backgroundColor, + borderRadius: borderRadius, + border: Border.all(color: borderColor), + ), + child: Text( + text, + style: textStyle ?? + Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: Colors.black), + ), + ); + } +} diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index d4111031..76c65805 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/common/edit_chip.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; @@ -18,6 +19,7 @@ class TagChipDisplay extends StatelessWidget { final TextEditingController spaceNameController; final BuildContext? pageContext; final List? otherSpaceModels; + final List? allSpaceModels; const TagChipDisplay(BuildContext context, {Key? key, @@ -28,7 +30,8 @@ class TagChipDisplay extends StatelessWidget { required this.allTags, required this.spaceNameController, this.pageContext, - this.otherSpaceModels}) + this.otherSpaceModels, + this.allSpaceModels}) : super(key: key); @override @@ -83,45 +86,31 @@ class TagChipDisplay extends StatelessWidget { ), ), ), - GestureDetector( - onTap: () async { - // Use the Navigator's context for showDialog - final navigatorContext = - Navigator.of(context).overlay?.context; + EditChip(onTap: () async { + // Use the Navigator's context for showDialog + Navigator.of(context).pop(); - if (navigatorContext != null) { - await showDialog( - barrierDismissible: false, - context: navigatorContext, - builder: (context) => AssignTagModelsDialog( - products: products, + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagModelsDialog( + products: products, + allSpaceModels: allSpaceModels, + subspaces: subspaces, + pageContext: pageContext, + allTags: allTags, + spaceModel: spaceModel, + otherSpaceModels: otherSpaceModels, + initialTags: TagHelper.generateInitialTags( subspaces: subspaces, - pageContext: pageContext, - allTags: allTags, - spaceModel: spaceModel, - initialTags: TagHelper.generateInitialTags( - subspaces: subspaces, - spaceTagModels: spaceModel?.tags ?? []), - title: 'Edit Device', - addedProducts: - TagHelper.createInitialSelectedProducts( - spaceModel?.tags ?? [], subspaces), - spaceName: spaceModel?.modelName ?? '', - )); - } - }, - child: Chip( - label: const Text( - 'Edit', - style: TextStyle(color: ColorsManager.spaceColor), - ), - backgroundColor: ColorsManager.whiteColors, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: const BorderSide(color: ColorsManager.spaceColor), - ), - ), - ), + spaceTagModels: spaceModel?.tags ?? []), + title: 'Edit Device', + addedProducts: + TagHelper.createInitialSelectedProducts( + spaceModel?.tags ?? [], subspaces), + spaceName: spaceModel?.modelName ?? '', + )); + }) ], ), ), @@ -141,6 +130,7 @@ class TagChipDisplay extends StatelessWidget { pageContext: pageContext, isCreate: true, spaceModel: spaceModel, + otherSpaceModels: otherSpaceModels, ), ); }, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index a9d40147..1ef3da9b 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -26,6 +26,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { final List? otherSpaceModels; final BuildContext? pageContext; final SpaceTemplateModel? spaceModel; + final List? allSpaceModels; const AddDeviceTypeModelWidget( {super.key, @@ -38,7 +39,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { required this.isCreate, this.pageContext, this.otherSpaceModels, - this.spaceModel}); + this.spaceModel, + this.allSpaceModels}); @override Widget build(BuildContext context) { @@ -106,6 +108,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { context: context, builder: (BuildContext dialogContext) { return CreateSpaceModelDialog( + allSpaceModels: allSpaceModels, products: products, allTags: allTags, pageContext: pageContext, @@ -175,11 +178,12 @@ class AddDeviceTypeModelWidget extends StatelessWidget { context: context, builder: (context) => AssignTagModelsDialog( products: products, + allSpaceModels: allSpaceModels, subspaces: subspaces, addedProducts: state.selectedProducts, allTags: allTags, spaceName: spaceName, - initialTags: state.initialTag, + initialTags: initialTags, otherSpaceModels: otherSpaceModels, title: dialogTitle, spaceModel: spaceModel, @@ -216,13 +220,15 @@ class AddDeviceTypeModelWidget extends StatelessWidget { if (subspace.tags != null) { initialTags.addAll( subspace.tags!.map( - (tag) => tag.copyWith(location: subspace.subspaceName), + (tag) => tag.copyWith( + location: subspace.subspaceName, + tag: tag.tag, + internalId: tag.internalId), ), ); } } } - return initialTags; } } From 24f7ab6af8677f2083719b168b683d94c7fef1ad Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 10:18:20 +0400 Subject: [PATCH 02/13] changed subspace label --- .../space_model/widgets/subspace_name_label_widget.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart index fd3c90b6..a2920b89 100644 --- a/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/subspace_name_label_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; class SubspaceNameDisplayWidget extends StatelessWidget { final String text; @@ -12,8 +13,8 @@ class SubspaceNameDisplayWidget extends StatelessWidget { Key? key, required this.text, this.textStyle, - this.backgroundColor = Colors.white, - this.borderColor = Colors.transparent, + this.backgroundColor = ColorsManager.whiteColors, + this.borderColor = ColorsManager.transparentColor, this.padding = const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), this.borderRadius = const BorderRadius.all(Radius.circular(10)), }) : super(key: key); @@ -33,7 +34,7 @@ class SubspaceNameDisplayWidget extends StatelessWidget { Theme.of(context) .textTheme .bodySmall - ?.copyWith(color: Colors.black), + ?.copyWith(color: ColorsManager.spaceColor), ), ); } From 7268253e35fd6ee9cb9ef252514a423bda3e9f3d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 11:45:11 +0400 Subject: [PATCH 03/13] fixed button validation --- .../widgets/dialog/create_space_model_dialog.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 c653d1b3..86a3938f 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 @@ -157,7 +157,7 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: state.errorMessage == null || + onPressed: state.errorMessage == null && isNameValid ? () { final updatedSpaceTemplate = @@ -236,7 +236,8 @@ class CreateSpaceModelDialog extends StatelessWidget { : null, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: isNameValid + foregroundColor: state.errorMessage == null && + isNameValid ? ColorsManager.whiteColors : ColorsManager.whiteColorsWithOpacity, child: const Text('OK'), From 5563197e9d77e4913db1277995e4a142cf30996c Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 15:20:36 +0400 Subject: [PATCH 04/13] add validation --- .../space_model/bloc/create_space_model_bloc.dart | 2 ++ .../space_model/models/subspace_template_model.dart | 2 +- lib/pages/spaces_management/space_model/models/tag_model.dart | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) 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 index 5553d8b0..3790991e 100644 --- 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 @@ -239,6 +239,7 @@ class CreateSpaceModelBloc for (var tag in newSubspace.tags!) { tagUpdates.add(TagModelUpdate( action: Action.add, + uuid: tag.uuid, tag: tag.tag, productUuid: tag.product?.uuid)); } @@ -325,6 +326,7 @@ class CreateSpaceModelBloc tagUpdates.add(TagModelUpdate( action: Action.add, tag: newTag.tag, + uuid: newTag.uuid, productUuid: newTag.product?.uuid)); processedTags.add(newTag.tag); } diff --git a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart index 6c73741b..9c69b4c8 100644 --- a/lib/pages/spaces_management/space_model/models/subspace_template_model.dart +++ b/lib/pages/spaces_management/space_model/models/subspace_template_model.dart @@ -20,7 +20,7 @@ class SubspaceTemplateModel { final String internalId = json['internalId'] ?? const Uuid().v4(); return SubspaceTemplateModel( - uuid: json['uuid'] ?? '', + uuid: json['uuid'], subspaceName: json['subspaceName'] ?? '', internalId: internalId, disabled: json['disabled'] ?? false, diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index c1ab4f40..4878ead7 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -21,7 +21,7 @@ class TagModel extends BaseTag { final String internalId = json['internalId'] ?? const Uuid().v4(); return TagModel( - uuid: json['uuid'] ?? '', + uuid: json['uuid'] , internalId: internalId, tag: json['tag'] ?? '', product: json['product'] != null From dac045146e9885cda74be2d5b017d261205b4be4 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 17:02:32 +0400 Subject: [PATCH 05/13] duplicate name validation --- .../bloc/create_space_model_bloc.dart | 13 +++++++++- .../bloc/create_space_model_event.dart | 3 ++- .../dialog/create_space_model_dialog.dart | 25 ++++++++++++------- 3 files changed, 30 insertions(+), 11 deletions(-) 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 index 3790991e..1d5ac2a1 100644 --- 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 @@ -68,8 +68,12 @@ class CreateSpaceModelBloc on((event, emit) { _space = event.spaceTemplate; - emit(CreateSpaceModelLoaded(_space!)); + final String? errorMessage = _checkDuplicateModelName( + event.allModels ?? [], event.spaceTemplate.modelName); + + emit(CreateSpaceModelLoaded(_space!,errorMessage: errorMessage ?? '')); }); + on((event, emit) { final currentState = state; @@ -352,4 +356,11 @@ class CreateSpaceModelBloc return tagUpdates; } + + String? _checkDuplicateModelName(List allModels, String name) { + if (allModels.contains(name)) { + return "Duplicate Model name"; + } + return null; + } } 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 index 22828941..d0cd245c 100644 --- 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 @@ -14,8 +14,9 @@ class LoadSpaceTemplate extends CreateSpaceModelEvent {} class UpdateSpaceTemplate extends CreateSpaceModelEvent { final SpaceTemplateModel spaceTemplate; + List? allModels; - UpdateSpaceTemplate(this.spaceTemplate); + UpdateSpaceTemplate(this.spaceTemplate,this.allModels); } class CreateSpaceTemplate extends CreateSpaceModelEvent { 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 86a3938f..5eedbfbb 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 @@ -52,12 +52,12 @@ class CreateSpaceModelDialog extends StatelessWidget { create: (_) { final bloc = CreateSpaceModelBloc(_spaceModelApi); if (spaceModel != null) { - bloc.add(UpdateSpaceTemplate(spaceModel!)); + bloc.add(UpdateSpaceTemplate(spaceModel!,otherSpaceModels)); } else { bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( modelName: '', subspaceModels: const [], - ))); + ),otherSpaceModels)); } spaceNameController.addListener(() { @@ -127,6 +127,11 @@ class CreateSpaceModelDialog extends StatelessWidget { context .read() .add(AddSubspacesToSpaceTemplate(updatedSubspaces)); + + context.read().add( + UpdateSpaceTemplateName( + name: spaceNameController.text, + allModels: otherSpaceModels ?? [])); }, ), const SizedBox(height: 10), @@ -150,16 +155,18 @@ class CreateSpaceModelDialog extends StatelessWidget { Expanded( child: CancelButton( label: 'Cancel', - onPressed: (){ - Navigator.of(context).pop();}, + onPressed: () { + Navigator.of(context).pop(); + }, ), ), const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: state.errorMessage == null && + onPressed: ( state.errorMessage == null) && isNameValid ? () { + final updatedSpaceTemplate = updatedSpaceModel.copyWith( modelName: @@ -236,10 +243,10 @@ class CreateSpaceModelDialog extends StatelessWidget { : null, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: state.errorMessage == null && - isNameValid - ? ColorsManager.whiteColors - : ColorsManager.whiteColorsWithOpacity, + foregroundColor: + state.errorMessage == null && isNameValid + ? ColorsManager.whiteColors + : ColorsManager.whiteColorsWithOpacity, child: const Text('OK'), ), ), From d4ed4efcd861b6d32c4f06633bf5380bb2033e02 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Thu, 23 Jan 2025 17:48:11 +0400 Subject: [PATCH 06/13] validation fix --- .../bloc/create_space_model_bloc.dart | 9 +++--- .../create_space_template_body_model.dart | 2 +- .../space_model/models/tag_model.dart | 2 +- .../dialog/create_space_model_dialog.dart | 29 ++++++++----------- 4 files changed, 18 insertions(+), 24 deletions(-) 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 index 1d5ac2a1..0ba36d3b 100644 --- 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 @@ -70,8 +70,7 @@ class CreateSpaceModelBloc _space = event.spaceTemplate; final String? errorMessage = _checkDuplicateModelName( event.allModels ?? [], event.spaceTemplate.modelName); - - emit(CreateSpaceModelLoaded(_space!,errorMessage: errorMessage ?? '')); + emit(CreateSpaceModelLoaded(_space!, errorMessage: errorMessage)); }); on((event, emit) { @@ -136,7 +135,7 @@ class CreateSpaceModelBloc final updatedSpace = currentState.space.copyWith(subspaceModels: updatedSubspaces); - emit(CreateSpaceModelLoaded(updatedSpace)); + emit(CreateSpaceModelLoaded(updatedSpace,errorMessage: currentState.errorMessage)); } else { emit(CreateSpaceModelError("Space template not initialized")); } @@ -243,7 +242,7 @@ class CreateSpaceModelBloc for (var tag in newSubspace.tags!) { tagUpdates.add(TagModelUpdate( action: Action.add, - uuid: tag.uuid, + uuid: tag.uuid == '' ? null : tag.uuid, tag: tag.tag, productUuid: tag.product?.uuid)); } @@ -330,7 +329,7 @@ class CreateSpaceModelBloc tagUpdates.add(TagModelUpdate( action: Action.add, tag: newTag.tag, - uuid: newTag.uuid, + uuid: newTag.uuid == '' ? null : newTag.uuid, productUuid: newTag.product?.uuid)); processedTags.add(newTag.tag); } diff --git a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart index 9b61f1b0..ad0770d5 100644 --- a/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart +++ b/lib/pages/spaces_management/space_model/models/create_space_template_body_model.dart @@ -1,5 +1,5 @@ class TagBodyModel { - late String uuid; + late String? uuid; late String tag; late final String? productUuid; diff --git a/lib/pages/spaces_management/space_model/models/tag_model.dart b/lib/pages/spaces_management/space_model/models/tag_model.dart index 4878ead7..20bd50e2 100644 --- a/lib/pages/spaces_management/space_model/models/tag_model.dart +++ b/lib/pages/spaces_management/space_model/models/tag_model.dart @@ -58,7 +58,7 @@ class TagModel extends BaseTag { extension TagModelExtensions on TagModel { TagBodyModel toTagBodyModel() { return TagBodyModel() - ..uuid = uuid ?? '' + ..uuid = uuid ..tag = tag ?? '' ..productUuid = product?.uuid; } 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 5eedbfbb..a825e868 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 @@ -52,12 +52,14 @@ class CreateSpaceModelDialog extends StatelessWidget { create: (_) { final bloc = CreateSpaceModelBloc(_spaceModelApi); if (spaceModel != null) { - bloc.add(UpdateSpaceTemplate(spaceModel!,otherSpaceModels)); + bloc.add(UpdateSpaceTemplate(spaceModel!, otherSpaceModels)); } else { - bloc.add(UpdateSpaceTemplate(SpaceTemplateModel( - modelName: '', - subspaceModels: const [], - ),otherSpaceModels)); + bloc.add(UpdateSpaceTemplate( + SpaceTemplateModel( + modelName: '', + subspaceModels: const [], + ), + otherSpaceModels)); } spaceNameController.addListener(() { @@ -127,11 +129,6 @@ class CreateSpaceModelDialog extends StatelessWidget { context .read() .add(AddSubspacesToSpaceTemplate(updatedSubspaces)); - - context.read().add( - UpdateSpaceTemplateName( - name: spaceNameController.text, - allModels: otherSpaceModels ?? [])); }, ), const SizedBox(height: 10), @@ -163,10 +160,8 @@ class CreateSpaceModelDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: ( state.errorMessage == null) && - isNameValid + onPressed: (state.errorMessage == null) ? () { - final updatedSpaceTemplate = updatedSpaceModel.copyWith( modelName: @@ -243,10 +238,10 @@ class CreateSpaceModelDialog extends StatelessWidget { : null, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: - state.errorMessage == null && isNameValid - ? ColorsManager.whiteColors - : ColorsManager.whiteColorsWithOpacity, + foregroundColor: (state.errorMessage != null && + state.errorMessage != '') + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, child: const Text('OK'), ), ), From cb71b5156593c41976527e56afc3536e32db4a8f Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 24 Jan 2025 20:41:31 +0400 Subject: [PATCH 07/13] community header --- assets/icons/edit_space.svg | 22 +++++++ lib/pages/common/buttons/default_button.dart | 11 +++- ...munity_structure_header_action_button.dart | 59 +++++++++++++++++ .../community_structure_header_button.dart | 65 +++++++++++++++++++ .../community_structure_header_widget.dart | 64 +----------------- .../all_spaces/widgets/sidebar_widget.dart | 4 +- .../bloc/create_space_model_bloc.dart | 22 +++++-- lib/utils/constants/assets.dart | 1 + 8 files changed, 178 insertions(+), 70 deletions(-) create mode 100644 assets/icons/edit_space.svg create mode 100644 lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart create mode 100644 lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart diff --git a/assets/icons/edit_space.svg b/assets/icons/edit_space.svg new file mode 100644 index 00000000..417cd5bd --- /dev/null +++ b/assets/icons/edit_space.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/pages/common/buttons/default_button.dart b/lib/pages/common/buttons/default_button.dart index 4aa748b7..ecca6138 100644 --- a/lib/pages/common/buttons/default_button.dart +++ b/lib/pages/common/buttons/default_button.dart @@ -19,12 +19,14 @@ class DefaultButton extends StatelessWidget { this.padding, this.borderColor, this.elevation, + this.borderWidth = 1.0, }); final void Function()? onPressed; final Widget child; final double? height; final bool isSecondary; final double? borderRadius; + final double borderWidth; final bool enabled; final double? padding; final bool isDone; @@ -66,13 +68,16 @@ class DefaultButton extends StatelessWidget { }), shape: WidgetStateProperty.all( RoundedRectangleBorder( - side: BorderSide(color: borderColor ?? Colors.transparent), + side: BorderSide( + color: borderColor ?? Colors.transparent, + width: borderWidth, + ), borderRadius: BorderRadius.circular(borderRadius ?? 20), ), ), fixedSize: height != null - ? WidgetStateProperty.all(Size.fromHeight(height!)) - : null, + ? WidgetStateProperty.all(Size.fromHeight(height!)) + : null, padding: WidgetStateProperty.all( EdgeInsets.all(padding ?? 10), ), diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart new file mode 100644 index 00000000..66bfe943 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class CommunityStructureHeaderActionButtons extends StatelessWidget { + const CommunityStructureHeaderActionButtons({ + super.key, + required this.theme, + required this.isSave, + required this.onSave, + required this.onDelete, + required this.selectedSpace, + }); + + final ThemeData theme; + final bool isSave; + final VoidCallback onSave; + final VoidCallback onDelete; + final SpaceModel? selectedSpace; + + @override + Widget build(BuildContext context) { + final canShowActions = selectedSpace != null && + selectedSpace?.status != SpaceStatus.deleted && + selectedSpace?.status != SpaceStatus.parentDeleted; + + return Wrap( + alignment: WrapAlignment.end, + spacing: 10, + children: [ + if (isSave) + CommunityStructureHeaderButton( + label: "Save", + icon: const Icon(Icons.save, + size: 18, color: ColorsManager.spaceColor), + onPressed: onSave, + theme: theme, + ), + if (canShowActions) ...[ + CommunityStructureHeaderButton( + label: "Edit", + svgAsset: Assets.editSpace, + onPressed: () => {}, + theme: theme, + ), + CommunityStructureHeaderButton( + label: "Delete", + icon: const Icon(Icons.delete, + size: 18, color: ColorsManager.warningRed), + onPressed: onDelete, + theme: theme, + ), + ], + ], + ); + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart new file mode 100644 index 00000000..4388c965 --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/common/buttons/default_button.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CommunityStructureHeaderButton extends StatelessWidget { + const CommunityStructureHeaderButton({ + super.key, + required this.label, + this.icon, + required this.onPressed, + this.svgAsset, + required this.theme, + }); + + final String label; + final Widget? icon; + final VoidCallback onPressed; + final String? svgAsset; + final ThemeData theme; + + @override + Widget build(BuildContext context) { + const double buttonHeight = 40; + return ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 100, + minHeight: buttonHeight, + ), + child: DefaultButton( + onPressed: onPressed, + borderWidth: 3, + backgroundColor: ColorsManager.textFieldGreyColor, + foregroundColor: ColorsManager.blackColor, + borderRadius: 12.0, + padding: 2.0, + height: buttonHeight, + elevation: 0, + borderColor: ColorsManager.lightGrayColor, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) icon!, + if (svgAsset != null) + SvgPicture.asset( + svgAsset!, + width: 30, + height: 30, + ), + const SizedBox(width: 10), + Flexible( + child: Text( + label, + style: theme.textTheme.bodySmall + ?.copyWith(color: ColorsManager.blackColor, fontSize: 14), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart index 02d3819a..18f7b340 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart'; import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -141,70 +142,11 @@ class _CommunityStructureHeaderState extends State { ), ), const SizedBox(width: 8), - _buildActionButtons(theme), + CommunityStructureHeaderActionButtons(theme), ], ), ], ); } - Widget _buildActionButtons(ThemeData theme) { - return Wrap( - alignment: WrapAlignment.end, - spacing: 10, - children: [ - if (widget.isSave) - _buildButton( - label: "Save", - icon: const Icon(Icons.save, - size: 18, color: ColorsManager.spaceColor), - onPressed: widget.onSave, - theme: theme), - if(widget.selectedSpace!= null) - _buildButton( - label: "Delete", - icon: const Icon(Icons.delete, - size: 18, color: ColorsManager.warningRed), - onPressed: widget.onDelete, - theme: theme), - ], - ); - } - - Widget _buildButton( - {required String label, - required Widget icon, - required VoidCallback onPressed, - required ThemeData theme}) { - const double buttonHeight = 30; - return ConstrainedBox( - constraints: BoxConstraints(maxWidth: 80, minHeight: buttonHeight), - child: DefaultButton( - onPressed: onPressed, - backgroundColor: ColorsManager.textFieldGreyColor, - foregroundColor: ColorsManager.blackColor, - borderRadius: 8.0, - padding: 2.0, - height: buttonHeight, - elevation: 0, - borderColor: ColorsManager.lightGrayColor, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - icon, - const SizedBox(width: 5), - Flexible( - child: Text( - label, - style: theme.textTheme.bodySmall - ?.copyWith(color: ColorsManager.blackColor), - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - ), - ], - ), - ), - ); - } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart index da67e6ed..7dc221a7 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/sidebar_widget.dart @@ -199,9 +199,11 @@ class _SidebarWidgetState extends State { }, children: hasChildren ? community.spaces + .where((space) => (space.status != SpaceStatus.deleted || + space.status != SpaceStatus.parentDeleted)) .map((space) => _buildSpaceTile(space, community)) .toList() - : null, // Render spaces within the community + : null, ); } 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 index 0ba36d3b..740ff832 100644 --- 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 @@ -135,7 +135,8 @@ class CreateSpaceModelBloc final updatedSpace = currentState.space.copyWith(subspaceModels: updatedSubspaces); - emit(CreateSpaceModelLoaded(updatedSpace,errorMessage: currentState.errorMessage)); + emit(CreateSpaceModelLoaded(updatedSpace, + errorMessage: currentState.errorMessage)); } else { emit(CreateSpaceModelError("Space template not initialized")); } @@ -217,8 +218,8 @@ class CreateSpaceModelBloc if (prevSubspaces != null || newSubspaces != null) { if (prevSubspaces != null && newSubspaces != null) { - for (var prevSubspace in prevSubspaces!) { - final existsInNew = newSubspaces! + for (var prevSubspace in prevSubspaces) { + final existsInNew = newSubspaces .any((newTag) => newTag.uuid == prevSubspace.uuid); if (!existsInNew) { subspaceUpdates.add(UpdateSubspaceTemplateModel( @@ -260,9 +261,20 @@ class CreateSpaceModelBloc for (var subspace in newSubspaces!) subspace.uuid: subspace }; - for (var prevSubspace in prevSubspaces!) { + for (var prevSubspace in prevSubspaces) { final newSubspace = newSubspaceMap[prevSubspace.uuid]; + if (newSubspace != null) { + if(prevSubspace.tags!=null){ + for(var t in prevSubspace.tags!){ + print("old tags are ${t.tag} ${t.uuid}"); + }} + + if(newSubspace.tags!=null){ + for(var t in newSubspace.tags!){ + print("new tags are ${t.tag} ${t.uuid}"); + }} + final List tagSubspaceUpdates = processTagUpdates(prevSubspace.tags, newSubspace.tags); subspaceUpdates.add(UpdateSubspaceTemplateModel( @@ -270,7 +282,7 @@ class CreateSpaceModelBloc uuid: newSubspace.uuid, subspaceName: newSubspace.subspaceName, tags: tagSubspaceUpdates)); - } else {} + } } } } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index a9deb3c7..b151890a 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -259,6 +259,7 @@ class Assets { static const String delete = 'assets/icons/delete.svg'; static const String edit = 'assets/icons/edit.svg'; + static const String editSpace = 'assets/icons/edit_space.svg'; //assets/icons/routine/tab_to_run.svg static const String tabToRun = 'assets/icons/routine/tab_to_run.svg'; From 4258ccdfbd31add6dae31668e04f459c3bb692df Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Fri, 24 Jan 2025 20:43:45 +0400 Subject: [PATCH 08/13] fix header issue --- .../widgets/community_structure_header_widget.dart | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart index 18f7b340..0419dc84 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart @@ -3,7 +3,6 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart'; -import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart'; import 'package:syncrow_web/pages/spaces_management/create_community/view/create_community_dialog.dart'; import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; @@ -142,11 +141,16 @@ class _CommunityStructureHeaderState extends State { ), ), const SizedBox(width: 8), - CommunityStructureHeaderActionButtons(theme), + CommunityStructureHeaderActionButtons( + theme: theme, + isSave: widget.isSave, + onSave: widget.onSave, + onDelete: widget.onDelete, + selectedSpace: widget.selectedSpace, + ), ], ), ], ); } - } From 9167c8da290cce929579044d5c40116c0e4a1b2e Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Sat, 25 Jan 2025 01:29:21 +0400 Subject: [PATCH 09/13] fixed space model updates --- .../views/assign_tag_models_dialog.dart | 184 ++++++++++-------- .../spaces_management/helper/tag_helper.dart | 18 +- .../widgets/tag_chips_display_widget.dart | 2 +- .../views/add_device_type_model_widget.dart | 33 +--- 4 files changed, 121 insertions(+), 116 deletions(-) diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 5eef92f8..8cff1b35 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -251,32 +251,15 @@ class AssignTagModelsDialog extends StatelessWidget { builder: (buttonContext) => CancelButton( label: 'Add New Device', onPressed: () async { - for (var tag in state.tags) { - if (tag.location == null) { - continue; - } + final updatedTags = + List.from(state.tags); + final result = + processTags(updatedTags, subspaces); - final previousTagSubspace = - checkTagExistInSubspace( - tag, subspaces ?? []); + final processedTags = + result['updatedTags'] as List; + final processedSubspaces = result['subspaces']; - if (tag.location == 'Main Space') { - removeTagFromSubspace( - tag, previousTagSubspace); - } else if (tag.location != - previousTagSubspace?.subspaceName) { - removeTagFromSubspace( - tag, previousTagSubspace); - moveToNewSubspace(tag, subspaces ?? []); - state.tags.removeWhere( - (t) => t.internalId == tag.internalId); - } else { - updateTagInSubspace( - tag, previousTagSubspace); - state.tags.removeWhere( - (t) => t.internalId == tag.internalId); - } - } if (context.mounted) { Navigator.of(context).pop(); @@ -293,15 +276,16 @@ class AssignTagModelsDialog extends StatelessWidget { allTags: allTags, spaceName: spaceName, otherSpaceModels: otherSpaceModels, - spaceTagModels: state.tags, + spaceTagModels: processedTags, pageContext: pageContext, spaceModel: SpaceTemplateModel( modelName: spaceName, - tags: state.tags, + tags: updatedTags, uuid: spaceModel?.uuid, internalId: spaceModel?.internalId, - subspaceModels: subspaces)), + subspaceModels: + processedSubspaces)), ); } }, @@ -318,33 +302,17 @@ class AssignTagModelsDialog extends StatelessWidget { foregroundColor: ColorsManager.whiteColors, onPressed: state.isSaveEnabled ? () async { - for (var tag in state.tags) { - if (tag.location == null || - subspaces == null) { - continue; - } + final updatedTags = + List.from(state.tags); + final result = + processTags(updatedTags, subspaces); - final previousTagSubspace = - checkTagExistInSubspace( - tag, subspaces ?? []); + final processedTags = + result['updatedTags'] as List; + final processedSubspaces = + result['subspaces'] + as List; - if (tag.location == 'Main Space') { - removeTagFromSubspace( - tag, previousTagSubspace); - } else if (tag.location != - previousTagSubspace?.subspaceName) { - removeTagFromSubspace( - tag, previousTagSubspace); - moveToNewSubspace(tag, subspaces ?? []); - state.tags.removeWhere((t) => - t.internalId == tag.internalId); - } else { - updateTagInSubspace( - tag, previousTagSubspace); - state.tags.removeWhere((t) => - t.internalId == tag.internalId); - } - } Navigator.of(context) .popUntil((route) => route.isFirst); @@ -359,11 +327,12 @@ class AssignTagModelsDialog extends StatelessWidget { otherSpaceModels: otherSpaceModels, spaceModel: SpaceTemplateModel( modelName: spaceName, - tags: state.tags, + tags: processedTags, uuid: spaceModel?.uuid, internalId: spaceModel?.internalId, - subspaceModels: subspaces), + subspaceModels: + processedSubspaces), ); }, ); @@ -397,39 +366,100 @@ class AssignTagModelsDialog extends StatelessWidget { .toList(); } - void removeTagFromSubspace(TagModel tag, SubspaceTemplateModel? subspace) { - subspace?.tags?.removeWhere((t) => t.internalId == tag.internalId); - } - - SubspaceTemplateModel? checkTagExistInSubspace( + int? checkTagExistInSubspace( TagModel tag, List? subspaces) { if (subspaces == null) return null; - for (var subspace in subspaces) { - if (subspace.tags == null) return null; + for (int i = 0; i < subspaces.length; i++) { + final subspace = subspaces[i]; + if (subspace.tags == null) continue; for (var t in subspace.tags!) { - if (tag.internalId == t.internalId) return subspace; + if (tag.internalId == t.internalId) { + return i; + } } } return null; } - void moveToNewSubspace(TagModel tag, List subspaces) { - final targetSubspace = subspaces - .firstWhere((subspace) => subspace.subspaceName == tag.location); + Map processTags( + List updatedTags, List? subspaces) { + final modifiedTags = List.from(updatedTags); + final modifiedSubspaces = List.from(subspaces ?? []); - targetSubspace.tags ??= []; - if (targetSubspace.tags?.any((t) => t.internalId == tag.internalId) != - true) { - targetSubspace.tags?.add(tag); - } - } + for (var tag in modifiedTags.toList()) { + if (modifiedSubspaces.isEmpty) continue; - void updateTagInSubspace(TagModel tag, SubspaceTemplateModel? subspace) { - final currentTag = subspace?.tags?.firstWhere( - (t) => t.internalId == tag.internalId, - ); - if (currentTag != null) { - currentTag.tag = tag.tag; + final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces); + + if ((tag.location == 'Main Space' || tag.location == null) && + (prevIndice == null || + modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) { + continue; + } + + if ((tag.location == 'Main Space' || tag.location == null) && + prevIndice != null) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + prevIndice == null) { + final newIndex = modifiedSubspaces + .indexWhere((subspace) => subspace.subspaceName == tag.location); + if (newIndex != -1) { + if (modifiedSubspaces[newIndex] + .tags + ?.any((t) => t.internalId == tag.internalId) != + true) { + tag.location = modifiedSubspaces[newIndex].subspaceName; + modifiedSubspaces[newIndex].tags?.add(tag); + } + } + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + tag.location != modifiedSubspaces[prevIndice!].subspaceName) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + final newIndex = modifiedSubspaces + .indexWhere((subspace) => subspace.subspaceName == tag.location); + if (newIndex != -1) { + if (modifiedSubspaces[newIndex] + .tags + ?.any((t) => t.internalId == tag.internalId) != + true) { + tag.location = modifiedSubspaces[newIndex].subspaceName; + modifiedSubspaces[newIndex].tags?.add(tag); + } + } + + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + tag.location == modifiedSubspaces[prevIndice!].subspaceName) { + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location == 'Main Space' || tag.location == null) && + prevIndice != null) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + } } + + return { + 'updatedTags': modifiedTags, + 'subspaces': modifiedSubspaces, + }; } } diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index c0213622..4fa86b88 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -18,19 +18,19 @@ class TagHelper { if (subspaces != null) { for (var subspace in subspaces) { if (subspace.tags != null) { - for (var existingTag in subspace.tags!) { - initialTags.addAll( - subspace.tags!.map( - (tag) => tag.copyWith( - location: subspace.subspaceName, - internalId: existingTag.internalId, - tag: existingTag.tag), + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith( + location: subspace.subspaceName, + internalId: tag.internalId, + tag: tag.tag, ), - ); - } + ), + ); } } } + return initialTags; } diff --git a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart index 76c65805..11c7ca5b 100644 --- a/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/tag_chips_display_widget.dart @@ -89,7 +89,7 @@ class TagChipDisplay extends StatelessWidget { EditChip(onTap: () async { // Use the Navigator's context for showDialog Navigator.of(context).pop(); - + await showDialog( barrierDismissible: false, context: context, diff --git a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart index 1ef3da9b..9d0eac96 100644 --- a/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart +++ b/lib/pages/spaces_management/tag_model/views/add_device_type_model_widget.dart @@ -5,6 +5,7 @@ import 'package:syncrow_web/pages/common/buttons/default_button.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; @@ -123,7 +124,7 @@ class AddDeviceTypeModelWidget extends StatelessWidget { }, ); } else { - final initialTags = generateInitialTags( + final initialTags = TagHelper.generateInitialTags( spaceTagModels: spaceTagModels, subspaces: subspaces, ); @@ -165,7 +166,8 @@ class AddDeviceTypeModelWidget extends StatelessWidget { : () async { if (state is AddDeviceModelLoaded && state.selectedProducts.isNotEmpty) { - final initialTags = generateInitialTags( + final initialTags = + TagHelper.generateInitialTags( spaceTagModels: spaceTagModels, subspaces: subspaces, ); @@ -204,31 +206,4 @@ class AddDeviceTypeModelWidget extends StatelessWidget { ), ); } - - List generateInitialTags({ - List? spaceTagModels, - List? subspaces, - }) { - final List initialTags = []; - - if (spaceTagModels != null) { - initialTags.addAll(spaceTagModels); - } - - if (subspaces != null) { - for (var subspace in subspaces) { - if (subspace.tags != null) { - initialTags.addAll( - subspace.tags!.map( - (tag) => tag.copyWith( - location: subspace.subspaceName, - tag: tag.tag, - internalId: tag.internalId), - ), - ); - } - } - } - return initialTags; - } } From 4907eebc42a46199b70e721b58b8b52aefbc0f8d Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 00:33:50 +0400 Subject: [PATCH 10/13] added duplicate --- assets/icons/duplicate.svg | 16 + assets/icons/space_delete.svg | 9 + .../bloc/add_device_model_bloc.dart | 86 +++-- .../bloc/add_device_state.dart | 36 ++ .../bloc/add_device_type_model_event.dart | 24 +- .../views/add_device_type_widget.dart | 141 ++++--- .../widgets/scrollable_grid_view_widget.dart | 8 +- ...munity_structure_header_action_button.dart | 31 +- .../community_structure_header_button.dart | 8 +- .../community_structure_header_widget.dart | 9 +- .../widgets/community_structure_widget.dart | 177 ++++++++- .../widgets/dialogs/create_space_dialog.dart | 136 +++---- .../dialogs/duplicate_process_dialog.dart | 86 +++++ .../assign_tag/bloc/assign_tag_bloc.dart | 12 +- .../assign_tag/views/assign_tag_dialog.dart | 352 ++++++++++-------- .../views/assign_tag_models_dialog.dart | 2 +- .../create_subspace/bloc/subspace_bloc.dart | 48 ++- .../bloc/subspace_model_bloc.dart | 54 ++- .../spaces_management/helper/tag_helper.dart | 68 +++- .../bloc/create_space_model_bloc.dart | 173 +++++---- lib/utils/color_manager.dart | 2 +- lib/utils/constants/assets.dart | 2 + 22 files changed, 1025 insertions(+), 455 deletions(-) create mode 100644 assets/icons/duplicate.svg create mode 100644 assets/icons/space_delete.svg create mode 100644 lib/pages/spaces_management/add_device_type/bloc/add_device_state.dart create mode 100644 lib/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart diff --git a/assets/icons/duplicate.svg b/assets/icons/duplicate.svg new file mode 100644 index 00000000..1faa1bab --- /dev/null +++ b/assets/icons/duplicate.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/assets/icons/space_delete.svg b/assets/icons/space_delete.svg new file mode 100644 index 00000000..90c3413e --- /dev/null +++ b/assets/icons/space_delete.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart b/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart index 10f3327e..e84851c5 100644 --- a/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart +++ b/lib/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart @@ -1,38 +1,74 @@ import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_state.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart'; -class AddDeviceTypeBloc - extends Bloc> { - AddDeviceTypeBloc(List initialProducts) - : super(initialProducts) { +class AddDeviceTypeBloc extends Bloc { + AddDeviceTypeBloc() : super(AddDeviceInitial()) { + on(_onInitializeTagModels); on(_onUpdateProductCount); } - void _onUpdateProductCount( - UpdateProductCountEvent event, Emitter> emit) { - final existingProduct = state.firstWhere( - (p) => p.productId == event.productId, - orElse: () => SelectedProduct(productId: event.productId, count: 0,productName: event.productName,product: event.product ), - ); + void _onInitializeTagModels( + InitializeDevice event, Emitter emit) { + emit(AddDeviceLoaded( + selectedProducts: event.addedProducts, + initialTag: event.initialTags, + )); + } - if (event.count > 0) { - if (!state.contains(existingProduct)) { - emit([ - ...state, - SelectedProduct(productId: event.productId, count: event.count, productName: event.productName, product: event.product) - ]); + void _onUpdateProductCount( + UpdateProductCountEvent event, Emitter emit) { + final currentState = state; + + if (currentState is AddDeviceLoaded) { + final existingProduct = currentState.selectedProducts.firstWhere( + (p) => p.productId == event.productId, + orElse: () => SelectedProduct( + productId: event.productId, + count: 0, + productName: event.productName, + product: event.product, + ), + ); + + List updatedProducts; + + if (event.count > 0) { + if (!currentState.selectedProducts.contains(existingProduct)) { + updatedProducts = [ + ...currentState.selectedProducts, + SelectedProduct( + productId: event.productId, + count: event.count, + productName: event.productName, + product: event.product, + ), + ]; + } else { + updatedProducts = currentState.selectedProducts.map((p) { + if (p.productId == event.productId) { + return SelectedProduct( + productId: p.productId, + count: event.count, + productName: p.productName, + product: p.product, + ); + } + return p; + }).toList(); + } } else { - final updatedList = state.map((p) { - if (p.productId == event.productId) { - return SelectedProduct(productId: p.productId, count: event.count, productName: p.productName,product: p.product); - } - return p; - }).toList(); - emit(updatedList); + // Remove the product if the count is 0 + updatedProducts = currentState.selectedProducts + .where((p) => p.productId != event.productId) + .toList(); } - } else { - emit(state.where((p) => p.productId != event.productId).toList()); + + // Emit the updated state + emit(AddDeviceLoaded( + selectedProducts: updatedProducts, + initialTag: currentState.initialTag)); } } } diff --git a/lib/pages/spaces_management/add_device_type/bloc/add_device_state.dart b/lib/pages/spaces_management/add_device_type/bloc/add_device_state.dart new file mode 100644 index 00000000..e1fa2593 --- /dev/null +++ b/lib/pages/spaces_management/add_device_type/bloc/add_device_state.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; + +abstract class AddDeviceState extends Equatable { + const AddDeviceState(); + + @override + List get props => []; +} + +class AddDeviceInitial extends AddDeviceState {} + +class AddDeviceLoading extends AddDeviceState {} + +class AddDeviceLoaded extends AddDeviceState { + final List selectedProducts; + final List initialTag; + + const AddDeviceLoaded({ + required this.selectedProducts, + required this.initialTag, + }); + + @override + List get props => [selectedProducts, initialTag]; +} + +class AddDeviceError extends AddDeviceState { + final String errorMessage; + + const AddDeviceError(this.errorMessage); + + @override + List get props => [errorMessage]; +} diff --git a/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart b/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart index addb6d67..254b78fd 100644 --- a/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart +++ b/lib/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart @@ -1,7 +1,11 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; abstract class AddDeviceTypeEvent extends Equatable { + const AddDeviceTypeEvent(); + @override List get props => []; } @@ -12,8 +16,26 @@ class UpdateProductCountEvent extends AddDeviceTypeEvent { final String productName; final ProductModel product; - UpdateProductCountEvent({required this.productId, required this.count, required this.productName, required this.product}); + UpdateProductCountEvent( + {required this.productId, + required this.count, + required this.productName, + required this.product}); @override List get props => [productId, count]; } + + +class InitializeDevice extends AddDeviceTypeEvent { + final List initialTags; + final List addedProducts; + + const InitializeDevice({ + this.initialTags = const [], + required this.addedProducts, + }); + + @override + List get props => [initialTags, addedProducts]; +} diff --git a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart index 9b9d6886..b26dbcc7 100644 --- a/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart +++ b/lib/pages/spaces_management/add_device_type/views/add_device_type_widget.dart @@ -1,17 +1,20 @@ import 'package:flutter/material.dart'; 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/add_device_type/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_state.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_type_model_event.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/tag_model/widgets/action_button_widget.dart'; import 'package:syncrow_web/utils/color_manager.dart'; - class AddDeviceTypeWidget extends StatelessWidget { final List? products; final ValueChanged>? onProductsSelected; @@ -20,8 +23,7 @@ class AddDeviceTypeWidget extends StatelessWidget { final List? spaceTags; final List? allTags; final String spaceName; - final Function(List,List?)? onSave; - + final Function(List, List?)? onSave; const AddDeviceTypeWidget( {super.key, @@ -44,30 +46,45 @@ class AddDeviceTypeWidget extends StatelessWidget { : 3; return BlocProvider( - create: (_) => AddDeviceTypeBloc(initialSelectedProducts ?? []), + create: (_) => AddDeviceTypeBloc() + ..add(InitializeDevice( + initialTags: spaceTags ?? [], + addedProducts: initialSelectedProducts ?? [], + )), child: Builder( builder: (context) => AlertDialog( title: const Text('Add Devices'), backgroundColor: ColorsManager.whiteColors, - content: SingleChildScrollView( - child: Container( - width: size.width * 0.9, - height: size.height * 0.65, - color: ColorsManager.textFieldGreyColor, - child: Column( - children: [ - const SizedBox(height: 16), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20.0), - child: ScrollableGridViewWidget( - products: products, crossAxisCount: crossAxisCount), - ), + content: BlocBuilder( + builder: (context, state) { + if (state is AddDeviceLoading) { + return const Center(child: CircularProgressIndicator()); + } + if (state is AddDeviceLoaded) { + return SingleChildScrollView( + child: Container( + width: size.width * 0.9, + height: size.height * 0.65, + color: ColorsManager.textFieldGreyColor, + child: Column( + children: [ + const SizedBox(height: 16), + Expanded( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 20.0), + child: ScrollableGridViewWidget( + products: products, + crossAxisCount: crossAxisCount), + ), + ), + ], ), - ], - ), - ), - ), + ), + ); + } + return const SizedBox(); + }), actions: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -78,43 +95,53 @@ class AddDeviceTypeWidget extends StatelessWidget { Navigator.of(context).pop(); }, ), - ActionButton( - label: 'Continue', - backgroundColor: ColorsManager.secondaryColor, - foregroundColor: ColorsManager.whiteColors, - onPressed: () async { - final currentState = - context.read().state; - Navigator.of(context).pop(); + SizedBox( + width: 140, + child: BlocBuilder( + builder: (context, state) { + final isDisabled = state is AddDeviceLoaded && + state.selectedProducts.isEmpty; + return DefaultButton( + borderRadius: 10, + backgroundColor: ColorsManager.secondaryColor, + foregroundColor: isDisabled + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, + onPressed: () async { + if (state is AddDeviceLoaded && + state.selectedProducts.isNotEmpty) { + final initialTags = + TagHelper.generateInitialForTags( + spaceTags: spaceTags, + subspaces: subspaces, + ); + Navigator.of(context).pop(); - if (currentState.isNotEmpty) { - final initialTags = generateInitialTags( - spaceTags: spaceTags, - subspaces: subspaces, - ); - - final dialogTitle = initialTags.isNotEmpty - ? 'Edit Device' - : 'Assign Tags'; - await showDialog( - barrierDismissible: false, - context: context, - builder: (context) => AssignTagDialog( - products: products, - subspaces: subspaces, - addedProducts: currentState, - allTags: allTags, - spaceName: spaceName, - initialTags: initialTags, - title: dialogTitle, - onSave: (tags,subspaces){ - onSave!(tags,subspaces); + final dialogTitle = initialTags.isNotEmpty + ? 'Edit Device' + : 'Assign Tags'; + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => AssignTagDialog( + products: products, + subspaces: subspaces, + addedProducts: state.selectedProducts, + allTags: allTags, + spaceName: spaceName, + initialTags: initialTags, + title: dialogTitle, + onSave: (tags, subspaces) { + onSave!(tags, subspaces); + }, + ), + ); + } }, - ), - ); - } - }, - ), + child: const Text('Next'), + ); + }, + )), ], ), ], diff --git a/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart b/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart index aeee6f1b..97bcf6d1 100644 --- a/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart +++ b/lib/pages/spaces_management/add_device_type/widgets/scrollable_grid_view_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_model_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/add_device_type/bloc/add_device_state.dart'; import 'package:syncrow_web/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; @@ -24,8 +25,11 @@ class ScrollableGridViewWidget extends StatelessWidget { return Scrollbar( controller: scrollController, thumbVisibility: true, - child: BlocBuilder>( - builder: (context, productCounts) { + child: BlocBuilder( + builder: (context, state) { + final productCounts = state is AddDeviceLoaded + ? state.selectedProducts + : []; return GridView.builder( controller: scrollController, shrinkWrap: true, diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart index 66bfe943..f5188c1e 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_action_button.dart @@ -5,19 +5,23 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; class CommunityStructureHeaderActionButtons extends StatelessWidget { - const CommunityStructureHeaderActionButtons({ - super.key, - required this.theme, - required this.isSave, - required this.onSave, - required this.onDelete, - required this.selectedSpace, - }); + const CommunityStructureHeaderActionButtons( + {super.key, + required this.theme, + required this.isSave, + required this.onSave, + required this.onDelete, + required this.selectedSpace, + required this.onDuplicate, + required this.onEdit}); final ThemeData theme; final bool isSave; final VoidCallback onSave; final VoidCallback onDelete; + final VoidCallback onDuplicate; + final VoidCallback onEdit; + final SpaceModel? selectedSpace; @override @@ -42,13 +46,18 @@ class CommunityStructureHeaderActionButtons extends StatelessWidget { CommunityStructureHeaderButton( label: "Edit", svgAsset: Assets.editSpace, - onPressed: () => {}, + onPressed: onEdit, + theme: theme, + ), + CommunityStructureHeaderButton( + label: "Duplicate", + svgAsset: Assets.duplicate, + onPressed: onDuplicate, theme: theme, ), CommunityStructureHeaderButton( label: "Delete", - icon: const Icon(Icons.delete, - size: 18, color: ColorsManager.warningRed), + svgAsset: Assets.spaceDelete, onPressed: onDelete, theme: theme, ), diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart index 4388c965..0369f7cd 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_button.dart @@ -24,12 +24,12 @@ class CommunityStructureHeaderButton extends StatelessWidget { const double buttonHeight = 40; return ConstrainedBox( constraints: const BoxConstraints( - maxWidth: 100, + maxWidth: 130, minHeight: buttonHeight, ), child: DefaultButton( onPressed: onPressed, - borderWidth: 3, + borderWidth: 2, backgroundColor: ColorsManager.textFieldGreyColor, foregroundColor: ColorsManager.blackColor, borderRadius: 12.0, @@ -44,8 +44,8 @@ class CommunityStructureHeaderButton extends StatelessWidget { if (svgAsset != null) SvgPicture.asset( svgAsset!, - width: 30, - height: 30, + width: 20, + height: 20, ), const SizedBox(width: 10), Flexible( diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart index 0419dc84..6bc35cca 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart @@ -14,6 +14,9 @@ class CommunityStructureHeader extends StatefulWidget { final TextEditingController nameController; final VoidCallback onSave; final VoidCallback onDelete; + final VoidCallback onEdit; + final VoidCallback onDuplicate; + final VoidCallback onEditName; final ValueChanged onNameSubmitted; final List communities; @@ -32,7 +35,9 @@ class CommunityStructureHeader extends StatefulWidget { required this.onNameSubmitted, this.community, required this.communities, - this.selectedSpace}); + this.selectedSpace, + required this.onDuplicate, + required this.onEdit}); @override State createState() => @@ -146,6 +151,8 @@ class _CommunityStructureHeaderState extends State { isSave: widget.isSave, onSave: widget.onSave, onDelete: widget.onDelete, + onDuplicate: widget.onDuplicate, + onEdit: widget.onEdit, selectedSpace: widget.selectedSpace, ), ], diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 8c6e36f5..b5f54708 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -4,6 +4,8 @@ import 'package:flutter_bloc/flutter_bloc.dart'; // Syncrow project imports import 'package:syncrow_web/pages/common/buttons/add_space_button.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/all_spaces/bloc/space_management_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; @@ -17,6 +19,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/blank_com import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/community_structure_header_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_line_painter.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_card_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/space_container_widget.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/space_template_model.dart'; @@ -133,6 +136,8 @@ class _CommunityStructureAreaState extends State { onSave: _saveSpaces, selectedSpace: widget.selectedSpace, onDelete: _onDelete, + onDuplicate: () => {_onDuplicate(context)}, + onEdit: () => {}, onEditName: () { setState(() { isEditingName = !isEditingName; @@ -328,7 +333,6 @@ class _CommunityStructureAreaState extends State { parentSpace.addOutgoingConnection(newConnection); parentSpace.children.add(newSpace); } - spaces.add(newSpace); _updateNodePosition(newSpace, newSpace.position); }); @@ -546,4 +550,175 @@ class _CommunityStructureAreaState extends State { space.status == SpaceStatus.modified || space.status == SpaceStatus.deleted); } + + void _onDuplicate(BuildContext parentContext) { + final screenWidth = MediaQuery.of(context).size.width; + + if (widget.selectedSpace != null) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: ColorsManager.whiteColors, + title: Text( + "Duplicate Space", + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .headlineLarge + ?.copyWith(color: ColorsManager.blackColor), + ), + content: SizedBox( + width: screenWidth * 0.4, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text("Are you sure you want to duplicate?", + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.headlineSmall), + const SizedBox(height: 15), + Text("All the child spaces will be duplicated.", + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(color: ColorsManager.lightGrayColor)), + const SizedBox(width: 15), + ], + ), + ), + actions: [ + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + SizedBox( + width: 200, + child: CancelButton( + onPressed: () { + Navigator.of(context).pop(); + }, + label: "Cancel", + ), + ), + const SizedBox(width: 10), + SizedBox( + width: 200, + child: DefaultButton( + onPressed: () { + Navigator.of(context).pop(); + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return DuplicateProcessDialog( + onDuplicate: () { + _duplicateSpace(widget.selectedSpace!); + _deselectSpace(parentContext); + }, + ); + }, + ); + }, + backgroundColor: ColorsManager.secondaryColor, + borderRadius: 10, + foregroundColor: ColorsManager.whiteColors, + child: const Text('OK'), + ), + ), + ]) + ], + ); + }, + ); + } + } + + void _duplicateSpace(SpaceModel space) { + final Map originalToDuplicate = {}; + const double horizontalGap = 150.0; + const double verticalGap = 100.0; + + SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition) { + // Find a new position for the duplicated space + Offset newPosition = parentPosition + Offset(horizontalGap, 0); + + // Avoid overlapping with existing spaces + while (spaces.any((s) => + (s.position - newPosition).distance < horizontalGap && + s.status != SpaceStatus.deleted)) { + newPosition += Offset(horizontalGap, 0); + } + + // Create the duplicated space + final duplicated = SpaceModel( + name: "${original.name} (Copy)", + icon: original.icon, + position: newPosition, + isPrivate: original.isPrivate, + children: [], + status: SpaceStatus.newSpace, + parent: original.parent, + spaceModel: original.spaceModel, + subspaces: original.subspaces, + tags: original.tags, + ); + + originalToDuplicate[original] = duplicated; + + // Copy the children of the original space to the duplicated space + Offset childStartPosition = newPosition + Offset(0, verticalGap); + for (final child in original.children) { + final duplicatedChild = duplicateRecursive(child, childStartPosition); + duplicated.children.add(duplicatedChild); + duplicatedChild.parent = + duplicated; // Set the parent for the duplicated child + childStartPosition += Offset(0, verticalGap); + } + + return duplicated; + } + + // Duplicate the selected space and its children + final duplicatedSpace = duplicateRecursive(space, space.position); + + // Ensure the duplicated space has the same parent as the original + if (space.parent != null) { + final parentSpace = space.parent!; + final duplicatedParent = originalToDuplicate[parentSpace] ?? parentSpace; + duplicatedSpace.parent = duplicatedParent; + duplicatedParent.children.add(duplicatedSpace); + } + + // Flatten the hierarchy of the duplicated spaces + List flattenHierarchy(SpaceModel root) { + final List result = []; + void traverse(SpaceModel node) { + result.add(node); + for (final child in node.children) { + traverse(child); + } + } + + traverse(root); + return result; + } + + final duplicatedSpacesList = flattenHierarchy(duplicatedSpace); + + setState(() { + spaces.addAll(duplicatedSpacesList); + + // Duplicate the connections + for (final connection in connections) { + if (originalToDuplicate.containsKey(connection.startSpace) && + originalToDuplicate.containsKey(connection.endSpace)) { + connections.add( + Connection( + startSpace: originalToDuplicate[connection.startSpace]!, + endSpace: originalToDuplicate[connection.endSpace]!, + direction: connection.direction, + ), + ); + } + } + }); + } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart index 7844381d..2b8d4aaf 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/create_space_dialog.dart @@ -10,6 +10,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/dialogs/icon_selection_dialog.dart'; +import 'package:syncrow_web/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/create_subspace/views/create_subspace_model_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/pages/spaces_management/link_space_model/view/link_space_model_dialog.dart'; @@ -409,7 +410,9 @@ class CreateSpaceDialogState extends State { _showTagCreateDialog( context, enteredName, + widget.isEdit, widget.products, + subspaces, ); // Edit action }) @@ -420,7 +423,12 @@ class CreateSpaceDialogState extends State { : TextButton( onPressed: () { _showTagCreateDialog( - context, enteredName, widget.products); + context, + enteredName, + widget.isEdit, + widget.products, + subspaces, + ); }, style: TextButton.styleFrom( padding: EdgeInsets.zero, @@ -558,85 +566,57 @@ class CreateSpaceDialogState extends State { ); } - void _showTagCreateDialog( - BuildContext context, String name, List? products) { - showDialog( - context: context, - builder: (BuildContext context) { - return AddDeviceTypeWidget( - spaceName: name, - products: products, - subspaces: subspaces, - spaceTags: tags, - allTags: [], - initialSelectedProducts: - createInitialSelectedProducts(tags, subspaces), - onSave: (selectedSpaceTags, selectedSubspaces) { - setState(() { - tags = selectedSpaceTags; - selectedSpaceModel = null; + void _showTagCreateDialog(BuildContext context, String name, bool isEdit, + List? products, List? subspaces) { + isEdit + ? showDialog( + context: context, + builder: (BuildContext context) { + return AssignTagDialog( + title: 'Edit Device', + addedProducts: TagHelper.createInitialSelectedProductsForTags( + tags, subspaces), + spaceName: name, + products: products, + subspaces: subspaces, + allTags: [], + onSave: (selectedSpaceTags, selectedSubspaces) {}, + ); + }, + ) + : showDialog( + context: context, + builder: (BuildContext context) { + return AddDeviceTypeWidget( + spaceName: name, + products: products, + subspaces: subspaces, + spaceTags: tags, + allTags: [], + initialSelectedProducts: + TagHelper.createInitialSelectedProductsForTags( + tags, subspaces), + onSave: (selectedSpaceTags, selectedSubspaces) { + setState(() { + tags = selectedSpaceTags; + selectedSpaceModel = null; - if (selectedSubspaces != null) { - if (subspaces != null) { - for (final subspace in subspaces!) { - for (final selectedSubspace in selectedSubspaces) { - if (subspace.subspaceName == - selectedSubspace.subspaceName) { - subspace.tags = selectedSubspace.tags; + if (selectedSubspaces != null) { + if (subspaces != null) { + for (final subspace in subspaces!) { + for (final selectedSubspace in selectedSubspaces) { + if (subspace.subspaceName == + selectedSubspace.subspaceName) { + subspace.tags = selectedSubspace.tags; + } + } + } } } - } - } - } - }); - }, - ); - }, - ); - } - - List createInitialSelectedProducts( - List? tags, List? subspaces) { - final Map productCounts = {}; - - if (tags != null) { - for (var tag in tags) { - if (tag.product != null) { - productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; - } - } - } - - if (subspaces != null) { - for (var subspace in subspaces) { - if (subspace.tags != null) { - for (var tag in subspace.tags!) { - if (tag.product != null) { - productCounts[tag.product!] = - (productCounts[tag.product!] ?? 0) + 1; - } - } - } - } - } - - return productCounts.entries - .map((entry) => SelectedProduct( - productId: entry.key.uuid, - count: entry.value, - productName: entry.key.name ?? 'Unnamed', - product: entry.key, - )) - .toList(); - } - - Map _groupTags(List tags) { - final Map groupedTags = {}; - for (var tag in tags) { - if (tag.product != null) { - groupedTags[tag.product!] = (groupedTags[tag.product!] ?? 0) + 1; - } - } - return groupedTags; + }); + }, + ); + }, + ); } } diff --git a/lib/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart b/lib/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart new file mode 100644 index 00000000..1f719b1a --- /dev/null +++ b/lib/pages/spaces_management/all_spaces/widgets/dialogs/duplicate_process_dialog.dart @@ -0,0 +1,86 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class DuplicateProcessDialog extends StatefulWidget { + final VoidCallback onDuplicate; + + const DuplicateProcessDialog({required this.onDuplicate, Key? key}) + : super(key: key); + + @override + _DuplicateProcessDialogState createState() => _DuplicateProcessDialogState(); +} + +class _DuplicateProcessDialogState extends State { + bool isDuplicating = true; + + @override + void initState() { + super.initState(); + _startDuplication(); + } + + void _startDuplication() async { + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.onDuplicate(); + }); + await Future.delayed(const Duration(seconds: 2)); + setState(() { + isDuplicating = false; + }); + + await Future.delayed(const Duration(seconds: 2)); + if (mounted) { + Navigator.of(context).pop(); + } + } + + @override + Widget build(BuildContext context) { + final screenWidth = MediaQuery.of(context).size.width; + + return AlertDialog( + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + content: SizedBox( + width: screenWidth * 0.4, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (isDuplicating) ...[ + const CircularProgressIndicator( + color: ColorsManager.vividBlue, + ), + const SizedBox(height: 15), + Text( + "Duplicating in progress", + style: Theme.of(context) + .textTheme + .headlineSmall + ?.copyWith(color: ColorsManager.primaryColor), + textAlign: TextAlign.center, + ), + ] else ...[ + const Icon( + Icons.check_circle, + color: ColorsManager.vividBlue, + size: 50, + ), + const SizedBox(height: 15), + Text( + "Duplicating successful", + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .headlineSmall + ?.copyWith(color: ColorsManager.primaryColor), + ), + ], + ], + ), + ), + ); + } +} diff --git a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart index 4a85348f..2d9222a6 100644 --- a/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart +++ b/lib/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart @@ -46,9 +46,9 @@ class AssignTagBloc extends Bloc { } emit(AssignTagLoaded( - tags: allTags, - isSaveEnabled: _validateTags(allTags), - )); + tags: allTags, + isSaveEnabled: _validateTags(allTags), + errorMessage: '')); }); on((event, emit) { @@ -117,12 +117,10 @@ class AssignTagBloc extends Bloc { } bool _validateTags(List tags) { - if (tags.isEmpty) { - return false; - } final uniqueTags = tags.map((tag) => tag.tag?.trim() ?? '').toSet(); final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty); - return uniqueTags.length == tags.length && !hasEmptyTag; + final isValid = uniqueTags.length == tags.length && !hasEmptyTag; + return isValid; } String? _getValidationError(List tags) { diff --git a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart index 959c83df..7ec19a45 100644 --- a/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart +++ b/lib/pages/spaces_management/assign_tag/views/assign_tag_dialog.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/common/dialog_dropdown.dart'; +import 'package:syncrow_web/common/dialog_textfield_dropdown.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/add_device_type/views/add_device_type_widget.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; @@ -79,6 +82,7 @@ class AssignTagDialog extends StatelessWidget { style: Theme.of(context).textTheme.bodyMedium)), DataColumn( + numeric: false, label: Text('Tag', style: Theme.of(context).textTheme.bodyMedium)), @@ -109,10 +113,11 @@ class AssignTagDialog extends StatelessWidget { : List.generate(state.tags.length, (index) { final tag = state.tags[index]; final controller = controllers[index]; - + final availableTags = getAvailableTags( + allTags ?? [], state.tags, tag); return DataRow( cells: [ - DataCell(Text(index.toString())), + DataCell(Text((index + 1).toString())), DataCell( Row( mainAxisAlignment: @@ -123,147 +128,80 @@ class AssignTagDialog extends StatelessWidget { tag.product?.name ?? 'Unknown', overflow: TextOverflow.ellipsis, )), - IconButton( - icon: const Icon(Icons.close, - color: ColorsManager.warningRed, - size: 16), - onPressed: () { - context.read().add( - DeleteTag( - tagToDelete: tag, - tags: state.tags)); - }, - tooltip: 'Delete Tag', - ) - ], - ), - ), - DataCell( - Row( - children: [ - Expanded( - child: TextFormField( - controller: controller, - onChanged: (value) { - context - .read() - .add(UpdateTagEvent( - index: index, - tag: value.trim(), - )); - }, - decoration: const InputDecoration( - hintText: 'Enter Tag', - border: InputBorder.none, - ), - style: const TextStyle( - fontSize: 14, - color: ColorsManager.blackColor, + const SizedBox(width: 10), + Container( + width: 20.0, + height: 20.0, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: ColorsManager + .lightGrayColor, + width: 1.0, ), ), - ), - SizedBox( - width: MediaQuery.of(context) - .size - .width * - 0.15, - child: PopupMenuButton( - color: ColorsManager.whiteColors, + child: IconButton( icon: const Icon( - Icons.arrow_drop_down, - color: - ColorsManager.blackColor), - onSelected: (value) { - controller.text = value; + Icons.close, + color: ColorsManager + .lightGreyColor, + size: 16, + ), + onPressed: () { context .read() - .add(UpdateTagEvent( - index: index, - tag: value, - )); - }, - itemBuilder: (context) { - return (allTags ?? []) - .where((tagValue) => !state - .tags - .map((e) => e.tag) - .contains(tagValue)) - .map((tagValue) { - return PopupMenuItem( - textStyle: const TextStyle( - color: ColorsManager - .textPrimaryColor), - value: tagValue, - child: ConstrainedBox( - constraints: - BoxConstraints( - minWidth: MediaQuery.of( - context) - .size - .width * - 0.15, - maxWidth: MediaQuery.of( - context) - .size - .width * - 0.15, - ), - child: Text( - tagValue, - overflow: TextOverflow - .ellipsis, - ), - )); - }).toList(); + .add(DeleteTag( + tagToDelete: tag, + tags: state.tags)); }, + tooltip: 'Delete Tag', + padding: EdgeInsets.zero, + constraints: + const BoxConstraints(), ), ), ], ), ), DataCell( - DropdownButtonHideUnderline( - child: DropdownButton( - value: tag.location ?? 'Main', - dropdownColor: ColorsManager - .whiteColors, // Dropdown background - style: const TextStyle( - color: Colors - .black), // Style for selected text - items: [ - const DropdownMenuItem( - value: 'Main Space', - child: Text( - 'Main Space', - style: TextStyle( - color: ColorsManager - .textPrimaryColor), - ), - ), - ...locations.map((location) { - return DropdownMenuItem( - value: location, - child: Text( - location, - style: const TextStyle( - color: ColorsManager - .textPrimaryColor), - ), - ); - }).toList(), - ], - onChanged: (value) { - if (value != null) { + Container( + alignment: Alignment + .centerLeft, // Align cell content to the left + child: SizedBox( + width: double + .infinity, // Ensure full width for dropdown + child: DialogTextfieldDropdown( + items: availableTags, + initialValue: tag.tag, + onSelected: (value) { + controller.text = value; + context + .read() + .add(UpdateTagEvent( + index: index, + tag: value.trim(), + )); + }, + ), + ), + ), + ), + DataCell( + SizedBox( + width: double.infinity, + child: DialogDropdown( + items: locations, + selectedValue: + tag.location ?? 'Main Space', + onSelected: (value) { context .read() .add(UpdateLocation( index: index, location: value, )); - } - }, - ), - ), + }, + )), ), ], ); @@ -284,11 +222,33 @@ class AssignTagDialog extends StatelessWidget { children: [ const SizedBox(width: 10), Expanded( - child: CancelButton( - label: 'Cancel', - onPressed: () async { - Navigator.of(context).pop(); - }, + child: Builder( + builder: (buttonContext) => CancelButton( + label: 'Add New Device', + onPressed: () async { + final updatedTags = List.from(state.tags); + final result = processTags(updatedTags, subspaces); + + final processedTags = + result['updatedTags'] as List; + final processedSubspaces = result['subspaces']; + + Navigator.of(context).pop(); + + await showDialog( + barrierDismissible: false, + context: context, + builder: (dialogContext) => AddDeviceTypeWidget( + products: products, + subspaces: processedSubspaces, + initialSelectedProducts: addedProducts, + allTags: allTags, + spaceName: spaceName, + spaceTags: processedTags, + ), + ); + }, + ), ), ), const SizedBox(width: 10), @@ -302,22 +262,16 @@ class AssignTagDialog extends StatelessWidget { onPressed: state.isSaveEnabled ? () async { Navigator.of(context).pop(); - final assignedTags = {}; - for (var tag in state.tags) { - if (tag.location == null || - subspaces == null) { - continue; - } - for (var subspace in subspaces!) { - if (tag.location == subspace.subspaceName) { - subspace.tags ??= []; - subspace.tags!.add(tag); - assignedTags.add(tag); - break; - } - } - } - onSave!(state.tags,subspaces); + final updatedTags = List.from(state.tags); + final result = + processTags(updatedTags, subspaces); + + final processedTags = + result['updatedTags'] as List; + final processedSubspaces = + result['subspaces'] as List; + + onSave!(processedTags, processedSubspaces); } : null, child: const Text('Save'), @@ -337,4 +291,110 @@ class AssignTagDialog extends StatelessWidget { ), ); } + + List getAvailableTags( + List allTags, List currentTags, Tag currentTag) { + return allTags + .where((tagValue) => !currentTags + .where((e) => e != currentTag) // Exclude the current row + .map((e) => e.tag) + .contains(tagValue)) + .toList(); + } + + Map processTags( + List updatedTags, List? subspaces) { + final modifiedTags = List.from(updatedTags); + final modifiedSubspaces = List.from(subspaces ?? []); + + for (var tag in modifiedTags.toList()) { + if (modifiedSubspaces.isEmpty) continue; + + final prevIndice = checkTagExistInSubspace(tag, modifiedSubspaces); + + if ((tag.location == 'Main Space' || tag.location == null) && + (prevIndice == null || + modifiedSubspaces[prevIndice].subspaceName == 'Main Space')) { + continue; + } + + if ((tag.location == 'Main Space' || tag.location == null) && + prevIndice != null) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + prevIndice == null) { + final newIndex = modifiedSubspaces + .indexWhere((subspace) => subspace.subspaceName == tag.location); + if (newIndex != -1) { + if (modifiedSubspaces[newIndex] + .tags + ?.any((t) => t.internalId == tag.internalId) != + true) { + tag.location = modifiedSubspaces[newIndex].subspaceName; + modifiedSubspaces[newIndex].tags?.add(tag); + } + } + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + tag.location != modifiedSubspaces[prevIndice!].subspaceName) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + final newIndex = modifiedSubspaces + .indexWhere((subspace) => subspace.subspaceName == tag.location); + if (newIndex != -1) { + if (modifiedSubspaces[newIndex] + .tags + ?.any((t) => t.internalId == tag.internalId) != + true) { + tag.location = modifiedSubspaces[newIndex].subspaceName; + modifiedSubspaces[newIndex].tags?.add(tag); + } + } + + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location != 'Main Space' && tag.location != null) && + tag.location == modifiedSubspaces[prevIndice!].subspaceName) { + modifiedTags.removeWhere((t) => t.internalId == tag.internalId); + continue; + } + + if ((tag.location == 'Main Space' || tag.location == null) && + prevIndice != null) { + modifiedSubspaces[prevIndice] + .tags + ?.removeWhere((t) => t.internalId == tag.internalId); + } + } + + return { + 'updatedTags': modifiedTags, + 'subspaces': modifiedSubspaces, + }; + } + + int? checkTagExistInSubspace(Tag tag, List? subspaces) { + if (subspaces == null) return null; + for (int i = 0; i < subspaces.length; i++) { + final subspace = subspaces[i]; + if (subspace.tags == null) continue; + for (var t in subspace.tags!) { + if (tag.internalId == t.internalId) { + return i; + } + } + } + return null; + } } diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 8cff1b35..8f9b51d7 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -99,7 +99,6 @@ class AssignTagModelsDialog extends StatelessWidget { .bodyMedium)), DataColumn( numeric: false, - headingRowAlignment: MainAxisAlignment.start, label: Text('Tag', style: Theme.of(context) .textTheme @@ -462,4 +461,5 @@ class AssignTagModelsDialog extends StatelessWidget { 'subspaces': modifiedSubspaces, }; } + } diff --git a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart index 5426f8f0..1a1884e2 100644 --- a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart @@ -6,7 +6,7 @@ import 'subspace_event.dart'; import 'subspace_state.dart'; class SubSpaceBloc extends Bloc { - SubSpaceBloc() : super(SubSpaceState([], [], '',{})) { + SubSpaceBloc() : super(SubSpaceState([], [], '', {})) { on((event, emit) { final existingNames = state.subSpaces.map((e) => e.subspaceName).toSet(); @@ -26,13 +26,22 @@ class SubSpaceBloc extends Bloc { final updatedSubSpaces = List.from(state.subSpaces) ..add(event.subSpace); - emit(SubSpaceState( - updatedSubSpaces, - state.updatedSubSpaceModels, - '', - state.duplicates, + if (state.duplicates.isNotEmpty) { + emit(SubSpaceState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '*Duplicated sub-space name', + state.duplicates, + )); + } else { + emit(SubSpaceState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '', + state.duplicates, // Clear error message - )); + )); + } } }); @@ -45,6 +54,13 @@ class SubSpaceBloc extends Bloc { state.updatedSubSpaceModels, ); + if (event.subSpace.uuid?.isNotEmpty ?? false) { + updatedSubspaceModels.add(UpdateSubspaceModel( + action: Action.delete, + uuid: event.subSpace.uuid!, + )); + } + final nameOccurrences = {}; for (final subSpace in updatedSubSpaces) { final lowerName = subSpace.subspaceName.toLowerCase(); @@ -55,19 +71,17 @@ class SubSpaceBloc extends Bloc { .where((entry) => entry.value > 1) .map((entry) => entry.key) .toSet(); - if (event.subSpace.uuid?.isNotEmpty ?? false) { - updatedSubspaceModels.add(UpdateSubspaceModel( - action: Action.delete, - uuid: event.subSpace.uuid!, - )); - } + final errorMessage = + updatedDuplicates.isNotEmpty ? '*Duplicated sub-space name' : ''; - emit(SubSpaceState(updatedSubSpaces, updatedSubspaceModels, '', - updatedDuplicates // Clear error message - )); + emit(SubSpaceState( + updatedSubSpaces, + updatedSubspaceModels, + errorMessage, + updatedDuplicates, + )); }); - // Handle UpdateSubSpace Event on((event, emit) { final updatedSubSpaces = state.subSpaces.map((subSpace) { diff --git a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart index 1e8d0ddc..a331aed2 100644 --- a/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart +++ b/lib/pages/spaces_management/create_subspace_model/bloc/subspace_model_bloc.dart @@ -25,22 +25,30 @@ class SubSpaceModelBloc extends Bloc { updatedDuplicates, )); } else { - // Add subspace if no duplicate exists final updatedSubSpaces = List.from(state.subSpaces) ..add(event.subSpace); - emit(SubSpaceModelState( - updatedSubSpaces, - state.updatedSubSpaceModels, - '', - state.duplicates, -// Clear error message - )); + if (state.duplicates.isNotEmpty) { + emit(SubSpaceModelState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '*Duplicated sub-space name', + state.duplicates, + )); + } else { + emit(SubSpaceModelState( + updatedSubSpaces, + state.updatedSubSpaceModels, + '', + state.duplicates, + )); + } } }); // Handle RemoveSubSpaceModel Event + on((event, emit) { final updatedSubSpaces = List.from(state.subSpaces) ..remove(event.subSpace); @@ -48,16 +56,6 @@ class SubSpaceModelBloc extends Bloc { final updatedSubspaceModels = List.from( state.updatedSubSpaceModels, ); - final nameOccurrences = {}; - for (final subSpace in updatedSubSpaces) { - final lowerName = subSpace.subspaceName.toLowerCase(); - nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1; - } - - final updatedDuplicates = nameOccurrences.entries - .where((entry) => entry.value > 1) - .map((entry) => entry.key) - .toSet(); if (event.subSpace.uuid?.isNotEmpty ?? false) { updatedSubspaceModels.add(UpdateSubspaceTemplateModel( @@ -66,12 +64,28 @@ class SubSpaceModelBloc extends Bloc { )); } + // Count occurrences of sub-space names to identify duplicates + final nameOccurrences = {}; + for (final subSpace in updatedSubSpaces) { + final lowerName = subSpace.subspaceName.toLowerCase(); + nameOccurrences[lowerName] = (nameOccurrences[lowerName] ?? 0) + 1; + } + + // Identify duplicate names + final updatedDuplicates = nameOccurrences.entries + .where((entry) => entry.value > 1) + .map((entry) => entry.key) + .toSet(); + + // Determine the error message + final errorMessage = + updatedDuplicates.isNotEmpty ? '*Duplicated sub-space name' : ''; + emit(SubSpaceModelState( updatedSubSpaces, updatedSubspaceModels, - '', + errorMessage, updatedDuplicates, -// Clear error message )); }); diff --git a/lib/pages/spaces_management/helper/tag_helper.dart b/lib/pages/spaces_management/helper/tag_helper.dart index 4fa86b88..041f005f 100644 --- a/lib/pages/spaces_management/helper/tag_helper.dart +++ b/lib/pages/spaces_management/helper/tag_helper.dart @@ -1,6 +1,8 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/selected_product_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/subspace_model.dart'; +import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/subspace_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_model.dart'; @@ -30,7 +32,36 @@ class TagHelper { } } } - + + return initialTags; + } + + static List generateInitialForTags({ + List? spaceTags, + List? subspaces, + }) { + final List initialTags = []; + + if (spaceTags != null) { + initialTags.addAll(spaceTags); + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + initialTags.addAll( + subspace.tags!.map( + (tag) => tag.copyWith( + location: subspace.subspaceName, + internalId: tag.internalId, + tag: tag.tag, + ), + ), + ); + } + } + } + return initialTags; } @@ -79,4 +110,39 @@ class TagHelper { )) .toList(); } + + static List createInitialSelectedProductsForTags( + List? tags, List? subspaces) { + final Map productCounts = {}; + + if (tags != null) { + for (var tag in tags) { + if (tag.product != null) { + productCounts[tag.product!] = (productCounts[tag.product!] ?? 0) + 1; + } + } + } + + if (subspaces != null) { + for (var subspace in subspaces) { + if (subspace.tags != null) { + for (var tag in subspace.tags!) { + if (tag.product != null) { + productCounts[tag.product!] = + (productCounts[tag.product!] ?? 0) + 1; + } + } + } + } + } + + return productCounts.entries + .map((entry) => SelectedProduct( + productId: entry.key.uuid, + count: entry.value, + productName: entry.key.name ?? 'Unnamed', + product: entry.key, + )) + .toList(); + } } 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 index 740ff832..36efaaa5 100644 --- 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 @@ -201,106 +201,101 @@ class CreateSpaceModelBloc on((event, emit) async { try { - final prevSpaceModel = event.spaceTemplate; - final newSpaceModel = event.updatedSpaceTemplate; - String? spaceModelName; - if (prevSpaceModel.modelName != newSpaceModel.modelName) { - spaceModelName = newSpaceModel.modelName; - } - List tagUpdates = []; - final List subspaceUpdates = []; - final List? prevSubspaces = - prevSpaceModel.subspaceModels; - final List? newSubspaces = - newSpaceModel.subspaceModels; + if (event.spaceTemplate.uuid != null) { + final prevSpaceModel = + await _api.getSpaceModel(event.spaceTemplate.uuid ?? ''); - tagUpdates = processTagUpdates(prevSpaceModel.tags, newSpaceModel.tags); + final newSpaceModel = event.updatedSpaceTemplate; + String? spaceModelName; + if (prevSpaceModel?.modelName != newSpaceModel.modelName) { + spaceModelName = newSpaceModel.modelName; + } + List tagUpdates = []; + final List subspaceUpdates = []; + final List? prevSubspaces = + prevSpaceModel?.subspaceModels; + final List? newSubspaces = + newSpaceModel.subspaceModels; - if (prevSubspaces != null || newSubspaces != null) { - if (prevSubspaces != null && newSubspaces != null) { - for (var prevSubspace in prevSubspaces) { - final existsInNew = newSubspaces - .any((newTag) => newTag.uuid == prevSubspace.uuid); - if (!existsInNew) { + tagUpdates = + processTagUpdates(prevSpaceModel?.tags, newSpaceModel.tags); + + if (prevSubspaces != null || newSubspaces != null) { + if (prevSubspaces != null && newSubspaces != null) { + for (var prevSubspace in prevSubspaces) { + final existsInNew = newSubspaces + .any((subspace) => subspace.uuid == prevSubspace.uuid); + if (!existsInNew) { + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.delete, uuid: prevSubspace.uuid)); + } + } + } else if (prevSubspaces != null && newSubspaces == null) { + for (var prevSubspace in prevSubspaces) { subspaceUpdates.add(UpdateSubspaceTemplateModel( action: Action.delete, uuid: prevSubspace.uuid)); } } - } else if (prevSubspaces != null && newSubspaces == null) { - for (var prevSubspace in prevSubspaces) { - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.delete, uuid: prevSubspace.uuid)); - } - } - if (newSubspaces != null) { - for (var newSubspace in newSubspaces!) { - // Tag without UUID - if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) { - final List tagUpdates = []; + if (newSubspaces != null) { + for (var newSubspace in newSubspaces!) { + // Tag without UUID + if ((newSubspace.uuid == null || newSubspace.uuid!.isEmpty)) { + final List tagUpdates = []; - if (newSubspace.tags != null) { - for (var tag in newSubspace.tags!) { - tagUpdates.add(TagModelUpdate( - action: Action.add, - uuid: tag.uuid == '' ? null : tag.uuid, - tag: tag.tag, - productUuid: tag.product?.uuid)); + if (newSubspace.tags != null) { + for (var tag in newSubspace.tags!) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + uuid: tag.uuid == '' ? null : tag.uuid, + tag: tag.tag, + productUuid: tag.product?.uuid)); + } } + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.add, + subspaceName: newSubspace.subspaceName, + tags: tagUpdates)); + } + } + } + + if (prevSubspaces != null && newSubspaces != null) { + final newSubspaceMap = { + for (var subspace in newSubspaces!) subspace.uuid: subspace + }; + + for (var prevSubspace in prevSubspaces) { + final newSubspace = newSubspaceMap[prevSubspace.uuid]; + + if (newSubspace != null) { + final List tagSubspaceUpdates = + processTagUpdates(prevSubspace.tags, newSubspace.tags); + subspaceUpdates.add(UpdateSubspaceTemplateModel( + action: Action.update, + uuid: newSubspace.uuid, + subspaceName: newSubspace.subspaceName, + tags: tagSubspaceUpdates)); } - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.add, - subspaceName: newSubspace.subspaceName, - tags: tagUpdates)); } } } - if (prevSubspaces != null && newSubspaces != null) { - final newSubspaceMap = { - for (var subspace in newSubspaces!) subspace.uuid: subspace - }; + final spaceModelBody = CreateSpaceTemplateBodyModel( + modelName: spaceModelName, + tags: tagUpdates, + subspaceModels: subspaceUpdates); - for (var prevSubspace in prevSubspaces) { - final newSubspace = newSubspaceMap[prevSubspace.uuid]; + final res = await _api.updateSpaceModel( + spaceModelBody, prevSpaceModel?.uuid ?? ''); - if (newSubspace != null) { - if(prevSubspace.tags!=null){ - for(var t in prevSubspace.tags!){ - print("old tags are ${t.tag} ${t.uuid}"); - }} - - if(newSubspace.tags!=null){ - for(var t in newSubspace.tags!){ - print("new tags are ${t.tag} ${t.uuid}"); - }} - - final List tagSubspaceUpdates = - processTagUpdates(prevSubspace.tags, newSubspace.tags); - subspaceUpdates.add(UpdateSubspaceTemplateModel( - action: Action.update, - uuid: newSubspace.uuid, - subspaceName: newSubspace.subspaceName, - tags: tagSubspaceUpdates)); - } + if (res != null) { + emit(CreateSpaceModelLoaded(newSpaceModel)); + if (event.onUpdate != null) { + event.onUpdate!(event.updatedSpaceTemplate); } } } - - final spaceModelBody = CreateSpaceTemplateBodyModel( - modelName: spaceModelName, - tags: tagUpdates, - subspaceModels: subspaceUpdates); - - final res = await _api.updateSpaceModel( - spaceModelBody, prevSpaceModel.uuid ?? ''); - - if (res != null) { - emit(CreateSpaceModelLoaded(newSpaceModel)); - if (event.onUpdate != null) { - event.onUpdate!(event.updatedSpaceTemplate); - } - } } catch (e) { emit(CreateSpaceModelError('Error creating space model')); } @@ -314,6 +309,18 @@ class CreateSpaceModelBloc final List tagUpdates = []; final processedTags = {}; + if (prevTags == null && newTags != null) { + for (var newTag in newTags) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + tag: newTag.tag, + uuid: newTag.uuid, + productUuid: newTag.product?.uuid, + )); + } + return tagUpdates; + } + if (newTags != null || prevTags != null) { // Case 1: Tags deleted if (prevTags != null && newTags != null) { @@ -334,9 +341,11 @@ class CreateSpaceModelBloc // Case 2: Tags added if (newTags != null) { + final prevTagUuids = prevTags?.map((t) => t.uuid).toSet() ?? {}; + for (var newTag in newTags!) { // Tag without UUID - if ((newTag.uuid == null || newTag.uuid!.isEmpty) && + if ((newTag.uuid == null || !prevTagUuids.contains(newTag.uuid)) && !processedTags.contains(newTag.tag)) { tagUpdates.add(TagModelUpdate( action: Action.add, diff --git a/lib/utils/color_manager.dart b/lib/utils/color_manager.dart index 301365ed..4d3dbb0c 100644 --- a/lib/utils/color_manager.dart +++ b/lib/utils/color_manager.dart @@ -70,5 +70,5 @@ abstract class ColorsManager { static const Color invitedOrangeText = Color(0xFFFFBF00); static const Color lightGrayBorderColor = Color(0xB2D5D5D5); //background: #F8F8F8; - + static const Color vividBlue = Color(0xFF023DFE); } diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index b151890a..d5d216c5 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -399,5 +399,7 @@ class Assets { static const String ZtoAIcon = 'assets/icons/ztoa_icon.png'; static const String AtoZIcon = 'assets/icons/atoz_icon.png'; static const String link = 'assets/icons/link.svg'; + static const String duplicate = 'assets/icons/duplicate.svg'; + static const String spaceDelete = 'assets/icons/space_delete.svg'; } //user_management.svg From 812dc4792bb37d3cfec2387addc4d1e16130d6cb Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 01:19:08 +0400 Subject: [PATCH 11/13] fixed duplicate --- .../widgets/community_structure_widget.dart | 109 +++++++++--------- 1 file changed, 55 insertions(+), 54 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index b5f54708..8e8cbe8f 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -633,29 +633,37 @@ class _CommunityStructureAreaState extends State { void _duplicateSpace(SpaceModel space) { final Map originalToDuplicate = {}; - const double horizontalGap = 150.0; + const double horizontalGap = 200.0; const double verticalGap = 100.0; - SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition) { - // Find a new position for the duplicated space + final Map nameCounters = {}; + + String _generateCopyName(String originalName) { + final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim(); + nameCounters[baseName] = (nameCounters[baseName] ?? 0) + 1; + return "$baseName(${nameCounters[baseName]})"; + } + + SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition, + SpaceModel? duplicatedParent) { Offset newPosition = parentPosition + Offset(horizontalGap, 0); - // Avoid overlapping with existing spaces while (spaces.any((s) => (s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) { newPosition += Offset(horizontalGap, 0); } - // Create the duplicated space + final duplicatedName = _generateCopyName(original.name); + final duplicated = SpaceModel( - name: "${original.name} (Copy)", + name: duplicatedName, icon: original.icon, position: newPosition, isPrivate: original.isPrivate, children: [], status: SpaceStatus.newSpace, - parent: original.parent, + parent: duplicatedParent, spaceModel: original.spaceModel, subspaces: original.subspaces, tags: original.tags, @@ -663,62 +671,55 @@ class _CommunityStructureAreaState extends State { originalToDuplicate[original] = duplicated; - // Copy the children of the original space to the duplicated space + setState(() { + spaces.add(duplicated); + _updateNodePosition(duplicated, duplicated.position); + + if (duplicatedParent != null) { + final newConnection = Connection( + startSpace: duplicatedParent, + endSpace: duplicated, + direction: "down", + ); + connections.add(newConnection); + duplicated.incomingConnection = newConnection; + duplicatedParent.addOutgoingConnection(newConnection); + } + + if (original.parent != null && duplicatedParent == null) { + final originalParent = original.parent!; + final duplicatedParent = + originalToDuplicate[originalParent] ?? originalParent; + + final parentConnection = Connection( + startSpace: duplicatedParent, + endSpace: duplicated, + direction: original.incomingConnection?.direction ?? "down", + ); + + connections.add(parentConnection); + duplicated.incomingConnection = parentConnection; + duplicatedParent.addOutgoingConnection(parentConnection); + } + }); + Offset childStartPosition = newPosition + Offset(0, verticalGap); for (final child in original.children) { - final duplicatedChild = duplicateRecursive(child, childStartPosition); + final duplicatedChild = + duplicateRecursive(child, childStartPosition, duplicated); duplicated.children.add(duplicatedChild); - duplicatedChild.parent = - duplicated; // Set the parent for the duplicated child childStartPosition += Offset(0, verticalGap); } return duplicated; } - // Duplicate the selected space and its children - final duplicatedSpace = duplicateRecursive(space, space.position); - - // Ensure the duplicated space has the same parent as the original - if (space.parent != null) { - final parentSpace = space.parent!; - final duplicatedParent = originalToDuplicate[parentSpace] ?? parentSpace; - duplicatedSpace.parent = duplicatedParent; - duplicatedParent.children.add(duplicatedSpace); + if (space.parent == null) { + duplicateRecursive(space, space.position, null); + } else { + final duplicatedParent = + originalToDuplicate[space.parent!] ?? space.parent!; + duplicateRecursive(space, space.position, duplicatedParent); } - - // Flatten the hierarchy of the duplicated spaces - List flattenHierarchy(SpaceModel root) { - final List result = []; - void traverse(SpaceModel node) { - result.add(node); - for (final child in node.children) { - traverse(child); - } - } - - traverse(root); - return result; - } - - final duplicatedSpacesList = flattenHierarchy(duplicatedSpace); - - setState(() { - spaces.addAll(duplicatedSpacesList); - - // Duplicate the connections - for (final connection in connections) { - if (originalToDuplicate.containsKey(connection.startSpace) && - originalToDuplicate.containsKey(connection.endSpace)) { - connections.add( - Connection( - startSpace: originalToDuplicate[connection.startSpace]!, - endSpace: originalToDuplicate[connection.endSpace]!, - direction: connection.direction, - ), - ); - } - } - }); } } From d0c6b130720783c6584f6d9b76c96047287b2e12 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 01:25:04 +0400 Subject: [PATCH 12/13] updated edit flow --- .../widgets/community_structure_widget.dart | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 8e8cbe8f..4d470c55 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -137,7 +137,7 @@ class _CommunityStructureAreaState extends State { selectedSpace: widget.selectedSpace, onDelete: _onDelete, onDuplicate: () => {_onDuplicate(context)}, - onEdit: () => {}, + onEdit: () => {_showEditSpaceDialog()}, onEditName: () { setState(() { isEditingName = !isEditingName; @@ -214,9 +214,6 @@ class _CommunityStructureAreaState extends State { opacity: isHighlighted ? 1.0 : 0.3, child: SpaceContainerWidget( index: index, - onDoubleTap: () { - _showEditSpaceDialog(spaces[index]); - }, onTap: () { _selectSpace(context, spaces[index]); }, @@ -342,40 +339,43 @@ class _CommunityStructureAreaState extends State { ); } - void _showEditSpaceDialog(SpaceModel space) { - showDialog( - context: context, - builder: (BuildContext context) { - return CreateSpaceDialog( - products: widget.products, - spaceModels: widget.spaceModels, - name: space.name, - icon: space.icon, - editSpace: space, - isEdit: true, - onCreateSpace: (String name, - String icon, - List selectedProducts, - SpaceTemplateModel? spaceModel, - List? subspaces, - List? tags) { - setState(() { - // Update the space's properties - space.name = name; - space.icon = icon; - space.spaceModel = spaceModel; - space.subspaces = subspaces; - space.tags = tags; + void _showEditSpaceDialog() { + if (widget.selectedSpace != null) { + showDialog( + context: context, + builder: (BuildContext context) { + return CreateSpaceDialog( + products: widget.products, + spaceModels: widget.spaceModels, + name: widget.selectedSpace!.name, + icon: widget.selectedSpace!.icon, + editSpace: widget.selectedSpace, + isEdit: true, + onCreateSpace: (String name, + String icon, + List selectedProducts, + SpaceTemplateModel? spaceModel, + List? subspaces, + List? tags) { + setState(() { + // Update the space's properties + widget.selectedSpace!.name = name; + widget.selectedSpace!.icon = icon; + widget.selectedSpace!.spaceModel = spaceModel; + widget.selectedSpace!.subspaces = subspaces; + widget.selectedSpace!.tags = tags; - if (space.status != SpaceStatus.newSpace) { - space.status = SpaceStatus.modified; // Mark as modified - } - }); - }, - key: Key(space.name), - ); - }, - ); + if (widget.selectedSpace!.status != SpaceStatus.newSpace) { + widget.selectedSpace!.status = + SpaceStatus.modified; // Mark as modified + } + }); + }, + key: Key(widget.selectedSpace!.name), + ); + }, + ); + } } void _handleHoverChanged(int index, bool isHovered) { From c72297e0c8b8da27aef907cb19c6da02e356b372 Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Mon, 27 Jan 2025 14:21:07 +0400 Subject: [PATCH 13/13] updated the duplicate --- .../widgets/community_structure_widget.dart | 22 ++++++++++++++++++- .../views/assign_tag_models_dialog.dart | 10 ++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart index 4d470c55..44122a06 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/community_structure_widget.dart @@ -703,8 +703,28 @@ class _CommunityStructureAreaState extends State { } }); - Offset childStartPosition = newPosition + Offset(0, verticalGap); + final childrenWithDownDirection = original.children + .where((child) => + child.incomingConnection?.direction == "down" && + child.status != SpaceStatus.deleted) + .toList(); + + Offset childStartPosition = childrenWithDownDirection.length == 1 + ? duplicated.position + : newPosition + Offset(0, verticalGap); + for (final child in original.children) { + final isDownDirection = + child.incomingConnection?.direction == "down" ?? false; + + if (isDownDirection && childrenWithDownDirection.length == 1) { + // Place the only "down" child vertically aligned with the parent + childStartPosition = duplicated.position + Offset(0, verticalGap); + } else if (!isDownDirection) { + // Position children with other directions horizontally + childStartPosition = duplicated.position + Offset(horizontalGap, 0); + } + final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated); duplicated.children.add(duplicatedChild); diff --git a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart index 8f9b51d7..31dba875 100644 --- a/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart +++ b/lib/pages/spaces_management/assign_tag_models/views/assign_tag_models_dialog.dart @@ -385,6 +385,15 @@ class AssignTagModelsDialog extends StatelessWidget { final modifiedTags = List.from(updatedTags); final modifiedSubspaces = List.from(subspaces ?? []); + if (subspaces != null) { + for (var subspace in subspaces) { + subspace.tags?.removeWhere( + (tag) => !modifiedTags + .any((updatedTag) => updatedTag.internalId == tag.internalId), + ); + } + } + for (var tag in modifiedTags.toList()) { if (modifiedSubspaces.isEmpty) continue; @@ -461,5 +470,4 @@ class AssignTagModelsDialog extends StatelessWidget { 'subspaces': modifiedSubspaces, }; } - }