Merge pull request #81 from SyncrowIOT/bugifx/tag-validation

Bugifx/tag-validation
This commit is contained in:
hannathkadher
2025-02-04 12:36:15 +04:00
committed by GitHub
9 changed files with 104 additions and 69 deletions

View File

@ -87,6 +87,7 @@ class SpaceManagementBloc
prevSpaceModels = List<SpaceTemplateModel>.from(
(previousState as dynamic).spaceModels ?? [],
);
allSpaces.addAll(prevSpaceModels);
}
if (prevSpaceModels.isEmpty) {
@ -314,7 +315,6 @@ class SpaceManagementBloc
SelectSpaceEvent event,
Emitter<SpaceManagementState> emit,
) {
_handleCommunitySpaceStateUpdate(
emit: emit,
selectedCommunity: event.selectedCommunity,

View File

@ -64,11 +64,11 @@ class SpaceManagementPageState extends State<SpaceManagementPage> {
);
} else if (state is SpaceModelLoaded) {
return LoadedSpaceView(
communities: state.communities,
products: state.products,
spaceModels: state.spaceModels,
shouldNavigateToSpaceModelPage: true,
);
communities: state.communities,
products: state.products,
spaceModels: state.spaceModels,
shouldNavigateToSpaceModelPage: true,
);
} else if (state is SpaceManagementError) {
return Center(child: Text('Error: ${state.errorMessage}'));
}

View File

@ -22,7 +22,9 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/widgets/curved_li
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/helper/connection_helper.dart';
import 'package:syncrow_web/pages/spaces_management/helper/space_helper.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/utils/color_manager.dart';
@ -131,7 +133,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
communities: widget.communities,
communityName: widget.selectedCommunity?.name,
community: widget.selectedCommunity,
isSave: isSave(spaces),
isSave: SpaceHelper.isSave(spaces),
isEditingName: isEditingName,
nameController: _nameController,
onSave: _saveSpaces,
@ -176,7 +178,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
children: [
for (var connection in connections)
Opacity(
opacity: _isHighlightedConnection(connection)
opacity: ConnectionHelper.isHighlightedConnection(
connection, widget.selectedSpace)
? 1.0
: 0.3, // Adjust opacity
child: CustomPaint(
@ -209,7 +212,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
},
buildSpaceContainer: (int index) {
final bool isHighlighted =
_isHighlightedSpace(spaces[index]);
SpaceHelper.isHighlightedSpace(
spaces[index], widget.selectedSpace);
return Opacity(
opacity: isHighlighted ? 1.0 : 0.3,
@ -295,7 +299,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return CreateSpaceDialog(
products: widget.products,
spaceModels: widget.spaceModels,
allTags: _getAllTagValues(spaces),
allTags:
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
parentSpace: parentIndex != null ? spaces[parentIndex] : null,
onCreateSpace: (String name,
String icon,
@ -306,7 +311,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
setState(() {
// Set the first space in the center or use passed position
Offset centerPosition =
position ?? _getCenterPosition(screenSize);
position ?? ConnectionHelper.getCenterPosition(screenSize);
SpaceModel newSpace = SpaceModel(
name: name,
icon: icon,
@ -358,7 +363,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces,
isEdit: true,
allTags: _getAllTagValues(spaces),
allTags: TagHelper.getAllTagValues(
widget.communities, widget.spaceModels),
onCreateSpace: (String name,
String icon,
List<SelectedProduct> selectedProducts,
@ -527,17 +533,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
);
}
bool _isHighlightedSpace(SpaceModel space) {
final selectedSpace = widget.selectedSpace;
if (selectedSpace == null) return true;
return space == selectedSpace ||
selectedSpace.parent?.internalId == space.internalId ||
selectedSpace.children
?.any((child) => child.internalId == space.internalId) ==
true;
}
void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(
@ -545,28 +540,6 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
);
}
bool _isHighlightedConnection(Connection connection) {
if (widget.selectedSpace == null) return true;
return connection.startSpace == widget.selectedSpace ||
connection.endSpace == widget.selectedSpace;
}
Offset _getCenterPosition(Size screenSize) {
return Offset(
screenSize.width / 2 - 260,
screenSize.height / 2 - 200,
);
}
bool isSave(List<SpaceModel> spaces) {
return spaces.isNotEmpty &&
spaces.any((space) =>
space.status == SpaceStatus.newSpace ||
space.status == SpaceStatus.modified ||
space.status == SpaceStatus.deleted);
}
void _onDuplicate(BuildContext parentContext) {
final screenWidth = MediaQuery.of(context).size.width;
@ -652,11 +625,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
const double horizontalGap = 200.0;
const double verticalGap = 100.0;
final Map<String, int> nameCounters = {};
SpaceModel duplicateRecursive(SpaceModel original, Offset parentPosition,
SpaceModel? duplicatedParent) {
Offset newPosition = parentPosition + Offset(horizontalGap, 0);
Offset newPosition =
Offset(parentPosition.dx + horizontalGap, original.position.dy);
while (spaces.any((s) =>
(s.position - newPosition).distance < horizontalGap &&
@ -690,7 +662,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
final newConnection = Connection(
startSpace: duplicatedParent,
endSpace: duplicated,
direction: "down",
direction: original.incomingConnection?.direction ?? 'down',
);
connections.add(newConnection);
duplicated.incomingConnection = newConnection;
@ -729,10 +701,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
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);
}
@ -753,14 +723,4 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
duplicateRecursive(space, space.position, duplicatedParent);
}
}
List<String> _getAllTagValues(List<SpaceModel> spaces) {
final List<String> allTags = [];
for (final space in spaces) {
if (space.tags != null) {
allTags.addAll(space.listAllTagValues());
}
}
return allTags;
}
}

View File

@ -83,8 +83,13 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
tags = widget.tags ?? [];
subspaces = widget.subspaces ?? [];
if (widget.currentSpaceModel != null) {
subspaces = [];
tags = [];
} else {
tags = widget.tags ?? [];
subspaces = widget.subspaces ?? [];
}
selectedSpaceModel = widget.currentSpaceModel;
}
@ -461,6 +466,7 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
builder: (context) => AssignTagDialog(
products: widget.products,
subspaces: subspaces,
allTags: widget.allTags,
addedProducts: TagHelper
.createInitialSelectedProductsForTags(
tags ?? [], subspaces),

View File

@ -57,6 +57,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
if (currentState is AssignTagLoaded && currentState.tags.isNotEmpty) {
final tags = List<Tag>.from(currentState.tags);
tags[event.index].tag = event.tag;
emit(AssignTagLoaded(
tags: tags,
isSaveEnabled: _validateTags(tags),
@ -78,6 +79,7 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded(
tags: tags,
isSaveEnabled: _validateTags(tags),
errorMessage: _getValidationError(tags),
));
}
});
@ -106,12 +108,13 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
emit(AssignTagLoaded(
tags: updatedTags,
isSaveEnabled: _validateTags(updatedTags),
errorMessage: _getValidationError(updatedTags),
));
} else {
emit(const AssignTagLoaded(
tags: [],
isSaveEnabled: false,
));
tags: [],
isSaveEnabled: false,
errorMessage: 'Failed to delete tag'));
}
});
}
@ -125,7 +128,10 @@ class AssignTagBloc extends Bloc<AssignTagEvent, AssignTagState> {
String? _getValidationError(List<Tag> tags) {
final hasEmptyTag = tags.any((tag) => (tag.tag?.trim() ?? '').isEmpty);
if (hasEmptyTag) return 'Tags cannot be empty.';
if (hasEmptyTag) {
return 'Tags cannot be empty.';
}
final duplicateTags = tags
.map((tag) => tag.tag?.trim() ?? '')
.fold<Map<String, int>>({}, (map, tag) {

View File

@ -21,11 +21,11 @@ class AssignTagLoaded extends AssignTagState {
const AssignTagLoaded({
required this.tags,
required this.isSaveEnabled,
this.errorMessage,
required this.errorMessage,
});
@override
List<Object> get props => [tags, isSaveEnabled];
List<Object> get props => [tags, isSaveEnabled, errorMessage ?? ''];
}
class AssignTagError extends AssignTagState {

View File

@ -0,0 +1,21 @@
import 'dart:ui';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/connection_model.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
class ConnectionHelper {
static Offset getCenterPosition(Size screenSize) {
return Offset(
screenSize.width / 2 - 260,
screenSize.height / 2 - 200,
);
}
static bool isHighlightedConnection(
Connection connection, SpaceModel? selectedSpace) {
if (selectedSpace == null) return true;
return connection.startSpace == selectedSpace ||
connection.endSpace == selectedSpace;
}
}

View File

@ -40,4 +40,22 @@ class SpaceHelper {
return "$baseName(${maxNumber + 1})";
}
static bool isSave(List<SpaceModel> spaces) {
return spaces.isNotEmpty &&
spaces.any((space) =>
space.status == SpaceStatus.newSpace ||
space.status == SpaceStatus.modified ||
space.status == SpaceStatus.deleted);
}
static bool isHighlightedSpace(SpaceModel space, SpaceModel? selectedSpace) {
if (selectedSpace == null) return true;
return space == selectedSpace ||
selectedSpace.parent?.internalId == space.internalId ||
selectedSpace.children
?.any((child) => child.internalId == space.internalId) ==
true;
}
}

View File

@ -1,8 +1,11 @@
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/base_tag.dart';
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_model.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/space_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/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';
@ -337,4 +340,25 @@ class TagHelper {
checkTagExistInSubspace: checkTagExistInSubspace,
);
}
static List<String> getAllTagValues(
List<CommunityModel> communities, List<SpaceTemplateModel>? spaceModels) {
final Set<String> allTags = {};
if (spaceModels != null) {
for (var model in spaceModels) {
allTags.addAll(model.listAllTagValues());
}
}
for (final community in communities) {
for (final space in community.spaces) {
if (space.tags != null) {
allTags.addAll(space.listAllTagValues());
}
}
}
return allTags.toList();
}
}