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 b26dbcc7..1930963b 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 @@ -12,7 +12,6 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod 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 { @@ -23,10 +22,12 @@ class AddDeviceTypeWidget extends StatelessWidget { final List? spaceTags; final List? allTags; final String spaceName; + final bool isCreate; final Function(List, List?)? onSave; const AddDeviceTypeWidget( {super.key, + required this.isCreate, this.products, this.initialSelectedProducts, this.onProductsSelected, @@ -74,7 +75,9 @@ class AddDeviceTypeWidget extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 20.0), child: ScrollableGridViewWidget( + initialProductCounts: state.selectedProducts, products: products, + isCreate: isCreate, crossAxisCount: crossAxisCount), ), ), @@ -124,17 +127,14 @@ class AddDeviceTypeWidget extends StatelessWidget { 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); - }, - ), + products: products, + subspaces: subspaces, + addedProducts: state.selectedProducts, + allTags: allTags, + spaceName: spaceName, + initialTags: initialTags, + title: dialogTitle, + onSave: onSave), ); } }, @@ -148,29 +148,4 @@ class AddDeviceTypeWidget extends StatelessWidget { ), )); } - - List generateInitialTags({ - 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), - ), - ); - } - } - } - - return initialTags; - } } diff --git a/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart index 08ad79ac..db2d6014 100644 --- a/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart +++ b/lib/pages/spaces_management/add_device_type/widgets/device_type_tile_widget.dart @@ -13,12 +13,13 @@ import 'package:syncrow_web/utils/constants/assets.dart'; class DeviceTypeTileWidget extends StatelessWidget { final ProductModel product; final List productCounts; + final bool isCreate; - const DeviceTypeTileWidget({ - super.key, - required this.product, - required this.productCounts, - }); + const DeviceTypeTileWidget( + {super.key, + required this.product, + required this.productCounts, + required this.isCreate}); @override Widget build(BuildContext context) { @@ -48,7 +49,7 @@ class DeviceTypeTileWidget extends StatelessWidget { DeviceNameWidget(name: product.name), const SizedBox(height: 4), CounterWidget( - isCreate: false, + isCreate: isCreate, initialCount: selectedProduct.count, onCountChanged: (newCount) { context.read().add( 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 97bcf6d1..4056744e 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 @@ -10,13 +10,14 @@ class ScrollableGridViewWidget extends StatelessWidget { final List? products; final int crossAxisCount; final List? initialProductCounts; + final bool isCreate; - const ScrollableGridViewWidget({ - super.key, - required this.products, - required this.crossAxisCount, - this.initialProductCounts, - }); + const ScrollableGridViewWidget( + {super.key, + required this.products, + required this.crossAxisCount, + this.initialProductCounts, + required this.isCreate}); @override Widget build(BuildContext context) { @@ -46,6 +47,7 @@ class ScrollableGridViewWidget extends StatelessWidget { return DeviceTypeTileWidget( product: product, + isCreate: isCreate, productCounts: initialProductCount != null ? [...productCounts, initialProductCount] : productCounts, diff --git a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart index ff584f52..1b5692c6 100644 --- a/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/all_spaces/bloc/space_management_bloc.dart @@ -5,12 +5,15 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/product_mod import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/bloc/space_management_state.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/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/services/product_api.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/services/space_model_mang_api.dart'; +import 'package:syncrow_web/utils/constants/action_enum.dart'; class SpaceManagementBloc extends Bloc { @@ -341,7 +344,7 @@ class SpaceManagementBloc products: _cachedProducts ?? [], selectedCommunity: selectedCommunity, selectedSpace: selectedSpace, - spaceModels: spaceModels ?? [])); + spaceModels: spaceModels)); } } catch (e) { emit(SpaceManagementError('Error updating state: $e')); @@ -428,6 +431,76 @@ class SpaceManagementBloc for (var space in orderedSpaces) { try { if (space.uuid != null && space.uuid!.isNotEmpty) { + List tagUpdates = []; + + final prevSpace = await _api.getSpace(communityUuid, space.uuid!); + final List subspaceUpdates = []; + final List? prevSubspaces = prevSpace?.subspaces; + final List? newSubspaces = space.subspaces; + + tagUpdates = processTagUpdates(prevSpace?.tags, space.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)); + } + } + + 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)); + } + } + 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)); + } + } + } + } + final response = await _api.updateSpace( communityId: communityUuid, spaceId: space.uuid!, @@ -436,6 +509,8 @@ class SpaceManagementBloc isPrivate: space.isPrivate, position: space.position, icon: space.icon, + subspaces: subspaceUpdates, + tags: tagUpdates, direction: space.incomingConnection?.direction, ); } else { @@ -535,4 +610,79 @@ class SpaceManagementBloc emit(SpaceManagementError('Error loading communities and spaces: $e')); } } + + List processTagUpdates( + List? prevTags, + List? newTags, + ) { + 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) { + for (var prevTag in prevTags) { + final existsInNew = + newTags.any((newTag) => newTag.uuid == prevTag.uuid); + if (!existsInNew) { + tagUpdates + .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); + } + } + } else if (prevTags != null && newTags == null) { + for (var prevTag in prevTags) { + tagUpdates + .add(TagModelUpdate(action: Action.delete, uuid: prevTag.uuid)); + } + } + + // 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 || !prevTagUuids.contains(newTag.uuid)) && + !processedTags.contains(newTag.tag)) { + tagUpdates.add(TagModelUpdate( + action: Action.add, + tag: newTag.tag, + uuid: newTag.uuid == '' ? null : newTag.uuid, + productUuid: newTag.product?.uuid)); + processedTags.add(newTag.tag); + } + } + } + + // Case 3: Tags updated + if (prevTags != null && newTags != null) { + final newTagMap = {for (var tag in newTags) tag.uuid: tag}; + + for (var prevTag in prevTags) { + final newTag = newTagMap[prevTag.uuid]; + if (newTag != null) { + tagUpdates.add(TagModelUpdate( + action: Action.update, + uuid: newTag.uuid, + tag: newTag.tag, + )); + } else {} + } + } + } + + return tagUpdates; + } } diff --git a/lib/pages/spaces_management/all_spaces/model/space_model.dart b/lib/pages/spaces_management/all_spaces/model/space_model.dart index c8da9d9e..6ad91dad 100644 --- a/lib/pages/spaces_management/all_spaces/model/space_model.dart +++ b/lib/pages/spaces_management/all_spaces/model/space_model.dart @@ -66,7 +66,6 @@ class SpaceModel { final instance = SpaceModel( internalId: internalId, uuid: json['uuid'] ?? '', - spaceTuyaUuid: json['spaceTuyaUuid'], name: json['spaceName'], isPrivate: json['isPrivate'] ?? false, invitationCode: json['invitationCode'], 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 44122a06..3a208a0d 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 @@ -195,6 +195,7 @@ class _CommunityStructureAreaState extends State { screenSize, position: spaces[index].position + newPosition, + parentIndex: index, direction: direction, ); @@ -294,6 +295,7 @@ class _CommunityStructureAreaState extends State { return CreateSpaceDialog( products: widget.products, spaceModels: widget.spaceModels, + allTags: _getAllTagValues(spaces), parentSpace: parentIndex != null ? spaces[parentIndex] : null, onCreateSpace: (String name, String icon, @@ -350,7 +352,10 @@ class _CommunityStructureAreaState extends State { name: widget.selectedSpace!.name, icon: widget.selectedSpace!.icon, editSpace: widget.selectedSpace, + tags: widget.selectedSpace?.tags, + subspaces: widget.selectedSpace?.subspaces, isEdit: true, + allTags: _getAllTagValues(spaces), onCreateSpace: (String name, String icon, List selectedProducts, @@ -742,4 +747,14 @@ class _CommunityStructureAreaState extends State { duplicateRecursive(space, space.position, duplicatedParent); } } + + List _getAllTagValues(List spaces) { + final List allTags = []; + for (final space in spaces) { + if (space.tags != null) { + allTags.addAll(space.listAllTagValues()); + } + } + return allTags; + } } 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 50d73fff..405ccd92 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 @@ -39,6 +39,7 @@ class CreateSpaceDialog extends StatefulWidget { final List? spaceModels; final List? subspaces; final List? tags; + final List? allTags; const CreateSpaceDialog( {super.key, @@ -49,6 +50,7 @@ class CreateSpaceDialog extends StatefulWidget { this.icon, this.isEdit = false, this.editSpace, + this.allTags, this.selectedProducts = const [], this.spaceModels, this.subspaces, @@ -79,6 +81,8 @@ class CreateSpaceDialogState extends State { widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty; + tags = widget.tags ?? []; + subspaces = widget.subspaces ?? []; } @override @@ -171,14 +175,13 @@ class CreateSpaceDialogState extends State { } }); }, - style: const TextStyle(color: Colors.black), + style: Theme.of(context).textTheme.bodyMedium, decoration: InputDecoration( hintText: 'Please enter the name', - hintStyle: const TextStyle( - fontSize: 14, - color: ColorsManager.lightGrayColor, - fontWeight: FontWeight.w400, - ), + hintStyle: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith(color: ColorsManager.lightGrayColor), filled: true, fillColor: ColorsManager.boxColor, enabledBorder: OutlineInputBorder( @@ -237,7 +240,7 @@ class CreateSpaceDialogState extends State { ), ) : Container( - width: screenWidth * 0.35, + width: screenWidth * 0.25, padding: const EdgeInsets.symmetric( vertical: 10.0, horizontal: 16.0), decoration: BoxDecoration( @@ -251,8 +254,11 @@ class CreateSpaceDialogState extends State { Chip( label: Text( selectedSpaceModel?.modelName ?? '', - style: const TextStyle( - color: ColorsManager.spaceColor), + style: Theme.of(context) + .textTheme + .bodyMedium! + .copyWith( + color: ColorsManager.spaceColor), ), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( @@ -285,25 +291,25 @@ class CreateSpaceDialogState extends State { ), ), const SizedBox(height: 25), - const Row( + Row( children: [ - Expanded( + const Expanded( child: Divider( color: ColorsManager.neutralGray, thickness: 1.0, ), ), Padding( - padding: EdgeInsets.symmetric(horizontal: 6.0), + padding: const EdgeInsets.symmetric(horizontal: 6.0), child: Text( 'OR', - style: TextStyle( - color: Colors.black, - fontWeight: FontWeight.bold, - ), + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(fontWeight: FontWeight.bold), ), ), - Expanded( + const Expanded( child: Divider( color: ColorsManager.neutralGray, thickness: 1.0, @@ -312,7 +318,7 @@ class CreateSpaceDialogState extends State { ], ), const SizedBox(height: 25), - subspaces == null + subspaces == null || subspaces!.isEmpty ? TextButton( style: TextButton.styleFrom( padding: EdgeInsets.zero, @@ -344,21 +350,29 @@ class CreateSpaceDialogState extends State { runSpacing: 8.0, children: [ if (subspaces != null) - ...subspaces!.map((subspace) => - SubspaceNameDisplayWidget( - validateName: (updatedName) { - return !subspaces!.any((s) => - s != subspace && - s.subspaceName == updatedName); - }, - text: subspace.subspaceName, - onNameChanged: (updatedName) { - setState(() { - subspace.subspaceName = - updatedName; - }); - }, - )), + ...subspaces!.map((subspace) { + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + SubspaceNameDisplayWidget( + text: subspace.subspaceName, + validateName: (updatedName) { + return subspaces!.any((s) => + s != subspace && + s.subspaceName == + updatedName); + }, + onNameChanged: (updatedName) { + setState(() { + subspace.subspaceName = + updatedName; + }); + }, + ), + ], + ); + }), EditChip( onTap: () async { _showSubSpaceDialog(context, enteredName, @@ -408,9 +422,12 @@ class CreateSpaceDialogState extends State { ), label: Text( 'x${entry.value}', // Show count - style: const TextStyle( - color: ColorsManager.spaceColor, - ), + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorsManager + .spaceColor), ), backgroundColor: ColorsManager.whiteColors, @@ -425,14 +442,29 @@ class CreateSpaceDialogState extends State { ), EditChip(onTap: () async { - _showTagCreateDialog( - context, - enteredName, - widget.isEdit, - widget.products, - subspaces, + final result = await showDialog( + context: context, + builder: (context) => AssignTagDialog( + products: widget.products, + subspaces: widget.subspaces, + addedProducts: TagHelper + .createInitialSelectedProductsForTags( + tags ?? [], subspaces), + title: 'Edit Device', + initialTags: + TagHelper.generateInitialForTags( + spaceTags: tags, + subspaces: subspaces), + spaceName: widget.name ?? '', + onSave: + (updatedTags, updatedSubspaces) { + setState(() { + tags = updatedTags; + subspaces = updatedSubspaces; + }); + }, + ), ); - // Edit action }) ], ), @@ -547,6 +579,7 @@ class CreateSpaceDialogState extends State { setState(() { selectedSpaceModel = selectedModel; subspaces = null; + tags = null; }); } }, @@ -597,8 +630,26 @@ class CreateSpaceDialogState extends State { spaceName: name, products: products, subspaces: subspaces, - allTags: [], - onSave: (selectedSpaceTags, selectedSubspaces) {}, + allTags: widget.allTags, + 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; + } + } + } + } + } + }); + }, ); }, ) @@ -610,7 +661,8 @@ class CreateSpaceDialogState extends State { products: products, subspaces: subspaces, spaceTags: tags, - allTags: [], + isCreate: true, + allTags: widget.allTags, initialSelectedProducts: TagHelper.createInitialSelectedProductsForTags( tags, subspaces), diff --git a/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart b/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart index 6e1f50c1..62d8197c 100644 --- a/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart +++ b/lib/pages/spaces_management/all_spaces/widgets/space_widget.dart @@ -20,8 +20,7 @@ class SpaceWidget extends StatelessWidget { top: position.dy, child: GestureDetector( onTap: onTap, - child: - Container( + child: Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( color: ColorsManager.whiteColors, @@ -39,11 +38,10 @@ class SpaceWidget extends StatelessWidget { children: [ const Icon(Icons.location_on, color: ColorsManager.spaceColor), const SizedBox(width: 8), - Text(name, style: const TextStyle(fontSize: 16)), + Text(name, style: Theme.of(context).textTheme.bodyMedium), ], ), ), - ), ); } 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 7ec19a45..cb1f7b46 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 @@ -12,6 +12,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/tag.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_event.dart'; import 'package:syncrow_web/pages/spaces_management/assign_tag/bloc/assign_tag_state.dart'; +import 'package:syncrow_web/pages/spaces_management/helper/tag_helper.dart'; import 'package:syncrow_web/utils/color_manager.dart'; class AssignTagDialog extends StatelessWidget { @@ -40,8 +41,11 @@ class AssignTagDialog extends StatelessWidget { @override Widget build(BuildContext context) { - final List locations = - (subspaces ?? []).map((subspace) => subspace.subspaceName).toList(); + final List locations = (subspaces ?? []) + .map((subspace) => subspace.subspaceName) + .toList() + ..add('Main Space'); + return BlocProvider( create: (_) => AssignTagBloc() ..add(InitializeTags( @@ -93,21 +97,22 @@ class AssignTagDialog extends StatelessWidget { ], rows: state.tags.isEmpty ? [ - const DataRow(cells: [ + DataRow(cells: [ DataCell( Center( - child: Text( - 'No Data Available', - style: TextStyle( - fontSize: 14, - color: ColorsManager.lightGrayColor, - ), - ), + child: Text('No Data Available', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: ColorsManager + .lightGrayColor, + )), ), ), - DataCell(SizedBox()), - DataCell(SizedBox()), - DataCell(SizedBox()), + const DataCell(SizedBox()), + const DataCell(SizedBox()), + const DataCell(SizedBox()), ]) ] : List.generate(state.tags.length, (index) { @@ -209,10 +214,11 @@ class AssignTagDialog extends StatelessWidget { ), ), if (state.errorMessage != null) - Text( - state.errorMessage!, - style: const TextStyle(color: ColorsManager.warningRed), - ), + Text(state.errorMessage!, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.warningRed)), ], ), ), @@ -235,16 +241,18 @@ class AssignTagDialog extends StatelessWidget { Navigator.of(context).pop(); - await showDialog( - barrierDismissible: false, + await showDialog( context: context, - builder: (dialogContext) => AddDeviceTypeWidget( + builder: (context) => AddDeviceTypeWidget( products: products, subspaces: processedSubspaces, - initialSelectedProducts: addedProducts, - allTags: allTags, + initialSelectedProducts: TagHelper + .createInitialSelectedProductsForTags( + processedTags, processedSubspaces), spaceName: spaceName, spaceTags: processedTags, + isCreate: false, + onSave: onSave, ), ); }, @@ -261,7 +269,6 @@ class AssignTagDialog extends StatelessWidget { foregroundColor: ColorsManager.whiteColors, onPressed: state.isSaveEnabled ? () async { - Navigator.of(context).pop(); final updatedTags = List.from(state.tags); final result = processTags(updatedTags, subspaces); @@ -270,8 +277,8 @@ class AssignTagDialog extends StatelessWidget { result['updatedTags'] as List; final processedSubspaces = result['subspaces'] as List; - - onSave!(processedTags, processedSubspaces); + onSave?.call(processedTags, processedSubspaces); + Navigator.of(context).pop(); } : null, child: const Text('Save'), @@ -307,6 +314,14 @@ class AssignTagDialog 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; 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 90843aa2..9696723a 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 @@ -112,22 +112,22 @@ class AssignTagModelsDialog extends StatelessWidget { ], rows: state.tags.isEmpty ? [ - const DataRow(cells: [ + DataRow(cells: [ DataCell( Center( - child: Text( - 'No Data Available', - style: TextStyle( - fontSize: 14, - color: - ColorsManager.lightGrayColor, - ), - ), + child: Text('No Devices Available', + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: ColorsManager + .lightGrayColor, + )), ), ), - DataCell(SizedBox()), - DataCell(SizedBox()), - DataCell(SizedBox()), + const DataCell(SizedBox()), + const DataCell(SizedBox()), + const DataCell(SizedBox()), ]) ] : List.generate(state.tags.length, (index) { @@ -233,11 +233,11 @@ class AssignTagModelsDialog extends StatelessWidget { ), ), if (state.errorMessage != null) - Text( - state.errorMessage!, - style: const TextStyle( - color: ColorsManager.warningRed), - ), + Text(state.errorMessage!, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.warningRed)), ], ), ), @@ -268,7 +268,7 @@ class AssignTagModelsDialog extends StatelessWidget { builder: (dialogContext) => AddDeviceTypeModelWidget( products: products, - subspaces: subspaces, + subspaces: processedSubspaces, isCreate: false, initialSelectedProducts: TagHelper .createInitialSelectedProducts( diff --git a/lib/pages/spaces_management/create_community/view/create_community_dialog.dart b/lib/pages/spaces_management/create_community/view/create_community_dialog.dart index 1a5460d1..13e676b5 100644 --- a/lib/pages/spaces_management/create_community/view/create_community_dialog.dart +++ b/lib/pages/spaces_management/create_community/view/create_community_dialog.dart @@ -77,9 +77,7 @@ class CreateCommunityDialog extends StatelessWidget { .read() .add(ValidateCommunityNameEvent(value)); }, - style: const TextStyle( - color: ColorsManager.blackColor, - ), + style: Theme.of(context).textTheme.bodyMedium, decoration: InputDecoration( hintText: 'Please enter the community name', filled: true, 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 1a1884e2..b334a301 100644 --- a/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart +++ b/lib/pages/spaces_management/create_subspace/bloc/subspace_bloc.dart @@ -82,7 +82,6 @@ class SubSpaceBloc extends Bloc { )); }); - on((event, emit) { final updatedSubSpaces = state.subSpaces.map((subSpace) { if (subSpace.uuid == event.updatedSubSpace.uuid) { 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 19babff9..0a2a01e5 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 @@ -102,12 +102,13 @@ class CreateSubSpaceDialog extends StatelessWidget { duplicateIndices.indexOf(index) != 0; return Chip( - label: Text( - subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor, - ), - ), + label: Text(subSpace.subspaceName, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: + ColorsManager.spaceColor)), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), @@ -143,27 +144,29 @@ class CreateSubSpaceDialog extends StatelessWidget { SizedBox( width: 200, child: TextField( - controller: textController, - decoration: InputDecoration( - border: InputBorder.none, - hintText: state.subSpaces.isEmpty - ? 'Please enter the name' - : null, - hintStyle: const TextStyle( - color: ColorsManager.lightGrayColor), - ), - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - context.read().add( - AddSubSpace(SubspaceModel( - subspaceName: value.trim(), - disabled: false))); - textController.clear(); - } - }, - style: const TextStyle( - color: ColorsManager.blackColor), - ), + controller: textController, + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.subSpaces.isEmpty + ? 'Please enter the name' + : null, + hintStyle: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorsManager + .lightGrayColor)), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + context.read().add( + AddSubSpace(SubspaceModel( + subspaceName: value.trim(), + disabled: false))); + textController.clear(); + } + }, + style: + Theme.of(context).textTheme.bodyMedium), ), ], ), @@ -171,13 +174,13 @@ class CreateSubSpaceDialog extends StatelessWidget { if (state.errorMessage.isNotEmpty) Padding( padding: const EdgeInsets.only(top: 8.0), - child: Text( - state.errorMessage, - style: const TextStyle( - color: ColorsManager.warningRed, - fontSize: 12, - ), - ), + child: Text(state.errorMessage, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorsManager.warningRed, + )), ), const SizedBox(height: 16), Row( @@ -193,17 +196,21 @@ class CreateSubSpaceDialog extends StatelessWidget { const SizedBox(width: 10), Expanded( child: DefaultButton( - onPressed: () async { - final subSpaces = context - .read() - .state - .subSpaces; - onSave!(subSpaces); - Navigator.of(context).pop(); - }, + onPressed: (state.errorMessage.isNotEmpty) + ? null + : () async { + final subSpaces = context + .read() + .state + .subSpaces; + onSave!(subSpaces); + Navigator.of(context).pop(); + }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, - foregroundColor: ColorsManager.whiteColors, + foregroundColor: state.errorMessage.isNotEmpty + ? ColorsManager.whiteColorsWithOpacity + : ColorsManager.whiteColors, child: const Text('OK'), ), ), diff --git a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart index 4c0cb99f..7a39891b 100644 --- a/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart +++ b/lib/pages/spaces_management/create_subspace_model/views/create_subspace_model_dialog.dart @@ -94,12 +94,13 @@ class CreateSubSpaceModelDialog extends StatelessWidget { duplicateIndices.indexOf(index) != 0; return Chip( - label: Text( - subSpace.subspaceName, - style: const TextStyle( - color: ColorsManager.spaceColor, - ), - ), + label: Text(subSpace.subspaceName, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorsManager.spaceColor, + )), backgroundColor: ColorsManager.whiteColors, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), @@ -135,28 +136,33 @@ class CreateSubSpaceModelDialog extends StatelessWidget { SizedBox( width: 200, child: TextField( - controller: textController, - decoration: InputDecoration( - border: InputBorder.none, - hintText: state.subSpaces.isEmpty - ? 'Please enter the name' - : null, - hintStyle: const TextStyle( - color: ColorsManager.lightGrayColor), - ), - onSubmitted: (value) { - if (value.trim().isNotEmpty) { - context.read().add( - AddSubSpaceModel( - SubspaceTemplateModel( - subspaceName: value.trim(), - disabled: false))); - textController.clear(); - } - }, - style: const TextStyle( - color: ColorsManager.blackColor), - ), + controller: textController, + decoration: InputDecoration( + border: InputBorder.none, + hintText: state.subSpaces.isEmpty + ? 'Please enter the name' + : null, + hintStyle: Theme.of(context) + .textTheme + .bodySmall! + .copyWith( + color: ColorsManager + .lightGrayColor)), + onSubmitted: (value) { + if (value.trim().isNotEmpty) { + context.read().add( + AddSubSpaceModel( + SubspaceTemplateModel( + subspaceName: value.trim(), + disabled: false))); + textController.clear(); + } + }, + style: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith( + color: ColorsManager.blackColor)), ), ], ), @@ -164,13 +170,13 @@ class CreateSubSpaceModelDialog extends StatelessWidget { if (state.errorMessage.isNotEmpty) Padding( padding: const EdgeInsets.only(bottom: 16.0), - child: Text( - state.errorMessage, - style: const TextStyle( - color: ColorsManager.red, - fontSize: 12, - ), - ), + child: Text(state.errorMessage, + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith( + color: ColorsManager.red, + )), ), const SizedBox(height: 16), Row( 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 cb7bc0c9..b1fce7a1 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 @@ -97,7 +97,10 @@ class SpaceModelPage extends StatelessWidget { return Center( child: Text( 'Error: ${state.message}', - style: const TextStyle(color: ColorsManager.warningRed), + style: Theme.of(context) + .textTheme + .bodySmall + ?.copyWith(color: ColorsManager.warningRed), ), ); } @@ -109,14 +112,14 @@ class SpaceModelPage extends StatelessWidget { double _calculateChildAspectRatio(BuildContext context) { double screenWidth = MediaQuery.of(context).size.width; if (screenWidth > 1600) { - return 2; + return 1.5; // Decrease to make cards taller } if (screenWidth > 1200) { - return 3; + return 2.0; } else if (screenWidth > 800) { - return 3.5; + return 2.5; } else { - return 4.0; + return 3.0; } } 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 2fb2a5b0..212400b9 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 @@ -167,7 +167,8 @@ class CreateSpaceModelDialog extends StatelessWidget { onPressed: ((state.errorMessage != null && state.errorMessage != '') || !isNameValid) - ? () { + ? null + : () { final updatedSpaceTemplate = updatedSpaceModel.copyWith( modelName: @@ -240,8 +241,7 @@ class CreateSpaceModelDialog extends StatelessWidget { } } } - } - : null, + }, backgroundColor: ColorsManager.secondaryColor, borderRadius: 10, foregroundColor: ((state.errorMessage != null && diff --git a/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart b/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart index e24c7704..f3da4122 100644 --- a/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/dynamic_room_widget.dart @@ -29,7 +29,7 @@ class DynamicRoomWidget extends StatelessWidget { final TextPainter textPainter = TextPainter( text: TextSpan( text: subspace.subspaceName, - style: const TextStyle(fontSize: 16), + style: Theme.of(context).textTheme.bodyMedium ), textDirection: TextDirection.ltr, )..layout(); diff --git a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart index df0fba4f..0056c96f 100644 --- a/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart +++ b/lib/pages/spaces_management/space_model/widgets/space_model_card_widget.dart @@ -31,82 +31,90 @@ class SpaceModelCardWidget extends StatelessWidget { } } - return Container( - padding: const EdgeInsets.all(16.0), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10), - boxShadow: [ - BoxShadow( - color: Colors.grey.withOpacity(0.5), - spreadRadius: 2, - blurRadius: 5, - offset: const Offset(0, 3), + return LayoutBuilder( + builder: (context, constraints) { + bool showOnlyName = constraints.maxWidth < 250; + return Container( + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.5), + spreadRadius: 2, + blurRadius: 5, + offset: const Offset(0, 3), + ), + ], ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - model.modelName, - style: Theme.of(context).textTheme.headlineMedium?.copyWith( - color: Colors.black, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - const SizedBox(height: 10), - Expanded( - child: Row( - children: [ - // Left Container + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + model.modelName, + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (!showOnlyName) ...[ + const SizedBox(height: 10), Expanded( - flex: 1, // Distribute space proportionally - child: Container( - padding: const EdgeInsets.all(8.0), - child: LayoutBuilder( - builder: (context, constraints) { - return Align( - alignment: Alignment.topLeft, - child: DynamicRoomWidget( - subspaceModels: model.subspaceModels, - maxWidth: constraints.maxWidth, - maxHeight: constraints.maxHeight, + child: Row( + children: [ + // Left Container + Expanded( + flex: 1, // Distribute space proportionally + child: Container( + padding: const EdgeInsets.all(8.0), + child: LayoutBuilder( + builder: (context, constraints) { + return Align( + alignment: Alignment.topLeft, + child: DynamicRoomWidget( + subspaceModels: model.subspaceModels, + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight, + ), + ); + }, ), - ); - }, - ), + ), + ), + if (productTagCount.isNotEmpty && + model.subspaceModels != null) + Container( + width: 1.0, + color: ColorsManager.softGray, + margin: const EdgeInsets.symmetric(vertical: 6.0), + ), + Expanded( + flex: 1, // Distribute space proportionally + child: Container( + padding: const EdgeInsets.all(8.0), + child: LayoutBuilder( + builder: (context, constraints) { + return Align( + alignment: Alignment.topLeft, + child: DynamicProductWidget( + productTagCount: productTagCount, + maxWidth: constraints.maxWidth, + maxHeight: constraints.maxHeight)); + }, + ), + ), + ), + ], ), ), - if (productTagCount.isNotEmpty && model.subspaceModels != null) - Container( - width: 1.0, - color: ColorsManager.softGray, - margin: const EdgeInsets.symmetric(vertical: 6.0), - ), - Expanded( - flex: 1, // Distribute space proportionally - child: Container( - padding: const EdgeInsets.all(8.0), - child: LayoutBuilder( - builder: (context, constraints) { - return Align( - alignment: Alignment.topLeft, - child: DynamicProductWidget( - productTagCount: productTagCount, - maxWidth: constraints.maxWidth, - maxHeight: constraints.maxHeight)); - }, - ), - ), - ), - ], - ), + ] + ], ), - ], - ), + ); + }, ); } } 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 5fddfe6e..3e13f9c5 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 @@ -105,6 +105,7 @@ class _SubspaceModelCreateState extends State { isEdit: true, dialogTitle: dialogTitle, existingSubSpaces: _subspaces, + onUpdate: (subspaceModels) { setState(() { _subspaces = subspaceModels; diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 2a2d42ad..c4877c98 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -4,7 +4,9 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/create_subs import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_response_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/space_template_model.dart'; import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_body_model.dart'; +import 'package:syncrow_web/pages/spaces_management/space_model/models/tag_update_model.dart'; import 'package:syncrow_web/services/api/http_service.dart'; import 'package:syncrow_web/utils/constants/api_const.dart'; import 'package:syncrow_web/utils/constants/temp_const.dart'; @@ -154,7 +156,7 @@ class CommunitySpaceManagementApi { .replaceAll('{spaceId}', spaceId) .replaceAll('{projectId}', TempConst.projectId), expectedResponseModel: (json) { - return SpaceModel.fromJson(json); + return SpaceModel.fromJson(json['data']); }, ); return response; @@ -210,7 +212,7 @@ class CommunitySpaceManagementApi { } } - Future updateSpace({ + Future updateSpace({ required String communityId, required spaceId, required String name, @@ -219,6 +221,8 @@ class CommunitySpaceManagementApi { String? direction, bool isPrivate = false, required Offset position, + List? tags, + List? subspaces, }) async { try { final body = { @@ -228,6 +232,8 @@ class CommunitySpaceManagementApi { 'y': position.dy, 'direction': direction, 'icon': icon, + 'subspace': subspaces, + 'tags': tags, }; if (parentId != null) { body['parentUuid'] = parentId; @@ -240,13 +246,13 @@ class CommunitySpaceManagementApi { .replaceAll('{projectId}', TempConst.projectId), body: body, expectedResponseModel: (json) { - return SpaceModel.fromJson(json['data']); + return json['success'] ?? false; }, ); return response; } catch (e) { - debugPrint('Error creating space: $e'); - return null; + debugPrint('Error updating space: $e'); + return false; } }