fix add subspace bugs and plusButton widget

This commit is contained in:
Rafeek Alkhoudare
2025-05-26 02:45:58 -05:00
parent e0951aa13d
commit 0b65c58947
5 changed files with 242 additions and 136 deletions

View File

@ -67,7 +67,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void initState() { void initState() {
super.initState(); super.initState();
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
_nameController = TextEditingController( _nameController = TextEditingController(
text: widget.selectedCommunity?.name ?? '', text: widget.selectedCommunity?.name ?? '',
@ -96,13 +97,15 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
if (oldWidget.spaces != widget.spaces) { if (oldWidget.spaces != widget.spaces) {
setState(() { setState(() {
spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : [];
connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; connections =
widget.spaces.isNotEmpty ? createConnections(widget.spaces) : [];
_adjustCanvasSizeForSpaces(); _adjustCanvasSizeForSpaces();
realignTree(); realignTree();
}); });
} }
if (widget.selectedSpace != oldWidget.selectedSpace && widget.selectedSpace != null) { if (widget.selectedSpace != oldWidget.selectedSpace &&
widget.selectedSpace != null) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_moveToSpace(widget.selectedSpace!); _moveToSpace(widget.selectedSpace!);
}); });
@ -185,7 +188,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
connection, widget.selectedSpace) connection, widget.selectedSpace)
? 1.0 ? 1.0
: 0.3, // Adjust opacity : 0.3, // Adjust opacity
child: CustomPaint(painter: CurvedLinePainter([connection])), child: CustomPaint(
painter: CurvedLinePainter([connection])),
), ),
for (var entry in spaces.asMap().entries) for (var entry in spaces.asMap().entries)
if (entry.value.status != SpaceStatus.deleted && if (entry.value.status != SpaceStatus.deleted &&
@ -195,9 +199,11 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
top: entry.value.position.dy, top: entry.value.position.dy,
child: SpaceCardWidget( child: SpaceCardWidget(
index: entry.key, index: entry.key,
onButtonTap: (int index, Offset newPosition, String direction) { onButtonTap: (int index, Offset newPosition,
String direction) {
_showCreateSpaceDialog(screenSize, _showCreateSpaceDialog(screenSize,
position: spaces[index].position + newPosition, position:
spaces[index].position + newPosition,
parentIndex: index, parentIndex: index,
direction: direction, direction: direction,
projectTags: widget.projectTags); projectTags: widget.projectTags);
@ -210,8 +216,9 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
_updateNodePosition(entry.value, newPosition); _updateNodePosition(entry.value, newPosition);
}, },
buildSpaceContainer: (int index) { buildSpaceContainer: (int index) {
final bool isHighlighted = SpaceHelper.isHighlightedSpace( final bool isHighlighted =
spaces[index], widget.selectedSpace); SpaceHelper.isHighlightedSpace(
spaces[index], widget.selectedSpace);
return Opacity( return Opacity(
opacity: isHighlighted ? 1.0 : 0.3, opacity: isHighlighted ? 1.0 : 0.3,
@ -299,19 +306,25 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
return CreateSpaceDialog( return CreateSpaceDialog(
products: widget.products, products: widget.products,
spaceModels: widget.spaceModels, spaceModels: widget.spaceModels,
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels), allTags:
TagHelper.getAllTagValues(widget.communities, widget.spaceModels),
parentSpace: parentIndex != null ? spaces[parentIndex] : null, parentSpace: parentIndex != null ? spaces[parentIndex] : null,
projectTags: projectTags, projectTags: projectTags,
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts, onCreateSpace: (String name,
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) { String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
setState(() { setState(() {
// Set the first space in the center or use passed position // Set the first space in the center or use passed position
Offset newPosition; Offset newPosition;
if (parentIndex != null) { if (parentIndex != null) {
newPosition = newPosition = getBalancedChildPosition(
getBalancedChildPosition(spaces[parentIndex]); // Ensure balanced position spaces[parentIndex]); // Ensure balanced position
} else { } else {
newPosition = position ?? ConnectionHelper.getCenterPosition(screenSize); newPosition =
position ?? ConnectionHelper.getCenterPosition(screenSize);
} }
SpaceModel newSpace = SpaceModel( SpaceModel newSpace = SpaceModel(
@ -360,16 +373,21 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
name: widget.selectedSpace!.name, name: widget.selectedSpace!.name,
icon: widget.selectedSpace!.icon, icon: widget.selectedSpace!.icon,
projectTags: widget.projectTags, projectTags: widget.projectTags,
parentSpace: parentSpace: SpaceHelper.findSpaceByInternalId(
SpaceHelper.findSpaceByInternalId(widget.selectedSpace?.parent?.internalId, spaces), widget.selectedSpace?.parent?.internalId, spaces),
editSpace: widget.selectedSpace, editSpace: widget.selectedSpace,
currentSpaceModel: widget.selectedSpace?.spaceModel, currentSpaceModel: widget.selectedSpace?.spaceModel,
tags: widget.selectedSpace?.tags, tags: widget.selectedSpace?.tags,
subspaces: widget.selectedSpace?.subspaces, subspaces: widget.selectedSpace?.subspaces,
isEdit: true, isEdit: true,
allTags: TagHelper.getAllTagValues(widget.communities, widget.spaceModels), allTags: TagHelper.getAllTagValues(
onCreateSpace: (String name, String icon, List<SelectedProduct> selectedProducts, widget.communities, widget.spaceModels),
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) { onCreateSpace: (String name,
String icon,
List<SelectedProduct> selectedProducts,
SpaceTemplateModel? spaceModel,
List<SubspaceModel>? subspaces,
List<Tag>? tags) {
setState(() { setState(() {
// Update the space's properties // Update the space's properties
widget.selectedSpace!.name = name; widget.selectedSpace!.name = name;
@ -379,7 +397,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
widget.selectedSpace!.tags = tags; widget.selectedSpace!.tags = tags;
if (widget.selectedSpace!.status != SpaceStatus.newSpace) { if (widget.selectedSpace!.status != SpaceStatus.newSpace) {
widget.selectedSpace!.status = SpaceStatus.modified; // Mark as modified widget.selectedSpace!.status =
SpaceStatus.modified; // Mark as modified
} }
for (var space in spaces) { for (var space in spaces) {
@ -410,7 +429,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
Map<String, SpaceModel> idToSpace = {}; Map<String, SpaceModel> idToSpace = {};
void flatten(SpaceModel space) { void flatten(SpaceModel space) {
if (space.status == SpaceStatus.deleted || space.status == SpaceStatus.parentDeleted) { if (space.status == SpaceStatus.deleted ||
space.status == SpaceStatus.parentDeleted) {
return; return;
} }
result.add(space); result.add(space);
@ -532,13 +552,16 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
void _selectSpace(BuildContext context, SpaceModel space) { void _selectSpace(BuildContext context, SpaceModel space) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: space), SelectSpaceEvent(
selectedCommunity: widget.selectedCommunity,
selectedSpace: space),
); );
} }
void _deselectSpace(BuildContext context) { void _deselectSpace(BuildContext context) {
context.read<SpaceManagementBloc>().add( context.read<SpaceManagementBloc>().add(
SelectSpaceEvent(selectedCommunity: widget.selectedCommunity, selectedSpace: null), SelectSpaceEvent(
selectedCommunity: widget.selectedCommunity, selectedSpace: null),
); );
} }
@ -708,7 +731,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent); SpaceModel duplicated = _deepCloneSpaceTree(space, parent: parent);
duplicated.position = Offset(space.position.dx + 300, space.position.dy + 100); duplicated.position =
Offset(space.position.dx + 300, space.position.dy + 100);
List<SpaceModel> duplicatedSubtree = []; List<SpaceModel> duplicatedSubtree = [];
void collectSubtree(SpaceModel node) { void collectSubtree(SpaceModel node) {
duplicatedSubtree.add(node); duplicatedSubtree.add(node);
@ -739,7 +763,8 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
} }
SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) { SpaceModel _deepCloneSpaceTree(SpaceModel original, {SpaceModel? parent}) {
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces); final duplicatedName =
SpaceHelper.generateUniqueSpaceName(original.name, spaces);
final newSpace = SpaceModel( final newSpace = SpaceModel(
name: duplicatedName, name: duplicatedName,

View File

@ -82,8 +82,10 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
super.initState(); super.initState();
selectedIcon = widget.icon ?? Assets.location; selectedIcon = widget.icon ?? Assets.location;
nameController = TextEditingController(text: widget.name ?? ''); nameController = TextEditingController(text: widget.name ?? '');
selectedProducts = widget.selectedProducts.isNotEmpty ? widget.selectedProducts : []; selectedProducts =
isOkButtonEnabled = enteredName.isNotEmpty || nameController.text.isNotEmpty; widget.selectedProducts.isNotEmpty ? widget.selectedProducts : [];
isOkButtonEnabled =
enteredName.isNotEmpty || nameController.text.isNotEmpty;
if (widget.currentSpaceModel != null) { if (widget.currentSpaceModel != null) {
subspaces = []; subspaces = [];
tags = []; tags = [];
@ -96,13 +98,15 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isSpaceModelDisabled = bool isSpaceModelDisabled = (tags != null && tags!.isNotEmpty ||
(tags != null && tags!.isNotEmpty || subspaces != null && subspaces!.isNotEmpty); subspaces != null && subspaces!.isNotEmpty);
bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null); bool isTagsAndSubspaceModelDisabled = (selectedSpaceModel != null);
final screenWidth = MediaQuery.of(context).size.width; final screenWidth = MediaQuery.of(context).size.width;
return AlertDialog( return AlertDialog(
title: widget.isEdit ? const Text('Edit Space') : const Text('Create New Space'), title: widget.isEdit
? const Text('Edit Space')
: const Text('Create New Space'),
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
content: SizedBox( content: SizedBox(
width: screenWidth * 0.5, width: screenWidth * 0.5,
@ -176,8 +180,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
isNameFieldInvalid = value.isEmpty; isNameFieldInvalid = value.isEmpty;
if (!isNameFieldInvalid) { if (!isNameFieldInvalid) {
if (SpaceHelper.isNameConflict( if (SpaceHelper.isNameConflict(value,
value, widget.parentSpace, widget.editSpace)) { widget.parentSpace, widget.editSpace)) {
isNameFieldExist = true; isNameFieldExist = true;
isOkButtonEnabled = false; isOkButtonEnabled = false;
} else { } else {
@ -244,7 +248,9 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
), ),
onPressed: () { onPressed: () {
isSpaceModelDisabled ? null : _showLinkSpaceModelDialog(context); isSpaceModelDisabled
? null
: _showLinkSpaceModelDialog(context);
}, },
child: ButtonContentWidget( child: ButtonContentWidget(
svgAssets: Assets.link, svgAssets: Assets.link,
@ -254,7 +260,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
) )
: Container( : Container(
width: screenWidth * 0.25, width: screenWidth * 0.25,
padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 16.0), padding: const EdgeInsets.symmetric(
vertical: 10.0, horizontal: 16.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: ColorsManager.boxColor, color: ColorsManager.boxColor,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
@ -269,7 +276,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyMedium! .bodyMedium!
.copyWith(color: ColorsManager.spaceColor), .copyWith(
color: ColorsManager.spaceColor),
), ),
backgroundColor: ColorsManager.whiteColors, backgroundColor: ColorsManager.whiteColors,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@ -340,12 +348,12 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
onPressed: () async { onPressed: () async {
isTagsAndSubspaceModelDisabled isTagsAndSubspaceModelDisabled
? null ? null
: _showSubSpaceDialog( : _showSubSpaceDialog(context, enteredName,
context, enteredName, [], false, widget.products, subspaces); [], false, widget.products, subspaces);
}, },
child: ButtonContentWidget( child: ButtonContentWidget(
icon: Icons.add, icon: Icons.add,
label: 'Create Sub Space', label: 'Create Sub Spaces',
disabled: isTagsAndSubspaceModelDisabled, disabled: isTagsAndSubspaceModelDisabled,
), ),
) )
@ -368,16 +376,22 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) if (subspaces != null)
...subspaces!.map((subspace) { ...subspaces!.map((subspace) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
SubspaceNameDisplayWidget( SubspaceNameDisplayWidget(
text: subspace.subspaceName, text: subspace.subspaceName,
validateName: (updatedName) { validateName: (updatedName) {
bool nameExists = subspaces!.any((s) { bool nameExists =
bool isSameId = s.internalId == subspace.internalId; subspaces!.any((s) {
bool isSameName = bool isSameId = s.internalId ==
s.subspaceName.trim().toLowerCase() == subspace.internalId;
updatedName.trim().toLowerCase(); bool isSameName = s.subspaceName
.trim()
.toLowerCase() ==
updatedName
.trim()
.toLowerCase();
return !isSameId && isSameName; return !isSameId && isSameName;
}); });
@ -386,7 +400,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}, },
onNameChanged: (updatedName) { onNameChanged: (updatedName) {
setState(() { setState(() {
subspace.subspaceName = updatedName; subspace.subspaceName =
updatedName;
}); });
}, },
), ),
@ -395,8 +410,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
}), }),
EditChip( EditChip(
onTap: () async { onTap: () async {
_showSubSpaceDialog(context, enteredName, [], true, _showSubSpaceDialog(context, enteredName,
widget.products, subspaces); [], true, widget.products, subspaces);
}, },
) )
], ],
@ -405,7 +420,9 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
(tags?.isNotEmpty == true || (tags?.isNotEmpty == true ||
subspaces?.any((subspace) => subspace.tags?.isNotEmpty == true) == true) subspaces?.any((subspace) =>
subspace.tags?.isNotEmpty == true) ==
true)
? SizedBox( ? SizedBox(
width: screenWidth * 0.25, width: screenWidth * 0.25,
child: Container( child: Container(
@ -425,14 +442,16 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
// Combine tags from spaceModel and subspaces // Combine tags from spaceModel and subspaces
...TagHelper.groupTags([ ...TagHelper.groupTags([
...?tags, ...?tags,
...?subspaces?.expand((subspace) => subspace.tags ?? []) ...?subspaces?.expand(
(subspace) => subspace.tags ?? [])
]).entries.map( ]).entries.map(
(entry) => Chip( (entry) => Chip(
avatar: SizedBox( avatar: SizedBox(
width: 24, width: 24,
height: 24, height: 24,
child: SvgPicture.asset( child: SvgPicture.asset(
entry.key.icon ?? 'assets/icons/gateway.svg', entry.key.icon ??
'assets/icons/gateway.svg',
fit: BoxFit.contain, fit: BoxFit.contain,
), ),
), ),
@ -441,11 +460,15 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodySmall .bodySmall
?.copyWith(color: ColorsManager.spaceColor), ?.copyWith(
color: ColorsManager
.spaceColor),
), ),
backgroundColor: ColorsManager.whiteColors, backgroundColor:
ColorsManager.whiteColors,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius:
BorderRadius.circular(16),
side: const BorderSide( side: const BorderSide(
color: ColorsManager.spaceColor, color: ColorsManager.spaceColor,
), ),
@ -460,15 +483,18 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
products: widget.products, products: widget.products,
subspaces: subspaces, subspaces: subspaces,
allTags: widget.allTags, allTags: widget.allTags,
addedProducts: addedProducts: TagHelper
TagHelper.createInitialSelectedProductsForTags( .createInitialSelectedProductsForTags(
tags ?? [], subspaces), tags ?? [], subspaces),
title: 'Edit Device', title: 'Edit Device',
initialTags: TagHelper.generateInitialForTags( initialTags:
spaceTags: tags, subspaces: subspaces), TagHelper.generateInitialForTags(
spaceTags: tags,
subspaces: subspaces),
spaceName: widget.name ?? '', spaceName: widget.name ?? '',
projectTags: widget.projectTags, projectTags: widget.projectTags,
onSave: (updatedTags, updatedSubspaces) { onSave:
(updatedTags, updatedSubspaces) {
setState(() { setState(() {
tags = updatedTags; tags = updatedTags;
subspaces = updatedSubspaces; subspaces = updatedSubspaces;
@ -529,17 +555,25 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
} else if (isNameFieldExist) { } else if (isNameFieldExist) {
return; return;
} else { } else {
String newName = enteredName.isNotEmpty ? enteredName : (widget.name ?? ''); String newName = enteredName.isNotEmpty
? enteredName
: (widget.name ?? '');
if (newName.isNotEmpty) { if (newName.isNotEmpty) {
widget.onCreateSpace(newName, selectedIcon, selectedProducts, widget.onCreateSpace(
selectedSpaceModel, subspaces, tags); newName,
selectedIcon,
selectedProducts,
selectedSpaceModel,
subspaces,
tags);
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
} }
}, },
borderRadius: 10, borderRadius: 10,
backgroundColor: backgroundColor: isOkButtonEnabled
isOkButtonEnabled ? ColorsManager.secondaryColor : ColorsManager.grayColor, ? ColorsManager.secondaryColor
: ColorsManager.grayColor,
foregroundColor: ColorsManager.whiteColors, foregroundColor: ColorsManager.whiteColors,
child: const Text('OK'), child: const Text('OK'),
), ),
@ -586,24 +620,31 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showSubSpaceDialog(BuildContext context, String name, final List<Tag>? spaceTags, void _showSubSpaceDialog(
bool isEdit, List<ProductModel>? products, final List<SubspaceModel>? existingSubSpaces) { BuildContext context,
String name,
final List<Tag>? spaceTags,
bool isEdit,
List<ProductModel>? products,
final List<SubspaceModel>? existingSubSpaces) {
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return CreateSubSpaceDialog( return CreateSubSpaceDialog(
spaceName: name, spaceName: name,
dialogTitle: isEdit ? 'Edit Sub-space' : 'Create Sub-space', dialogTitle: isEdit ? 'Edit Sub-spaces' : 'Create Sub-spaces',
products: products, products: products,
existingSubSpaces: existingSubSpaces, existingSubSpaces: existingSubSpaces,
onSave: (slectedSubspaces) { onSave: (slectedSubspaces) {
final List<Tag> tagsToAppendToSpace = []; final List<Tag> tagsToAppendToSpace = [];
if (slectedSubspaces != null) { if (slectedSubspaces != null && slectedSubspaces.isNotEmpty) {
final updatedIds = slectedSubspaces.map((s) => s.internalId).toSet(); final updatedIds =
slectedSubspaces.map((s) => s.internalId).toSet();
if (existingSubSpaces != null) { if (existingSubSpaces != null) {
final deletedSubspaces = final deletedSubspaces = existingSubSpaces
existingSubSpaces.where((s) => !updatedIds.contains(s.internalId)).toList(); .where((s) => !updatedIds.contains(s.internalId))
.toList();
for (var s in deletedSubspaces) { for (var s in deletedSubspaces) {
if (s.tags != null) { if (s.tags != null) {
tagsToAppendToSpace.addAll(s.tags!); tagsToAppendToSpace.addAll(s.tags!);
@ -623,15 +664,16 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
); );
} }
void _showTagCreateDialog( void _showTagCreateDialog(BuildContext context, String name, bool isEdit,
BuildContext context, String name, bool isEdit, List<ProductModel>? products) { List<ProductModel>? products) {
isEdit isEdit
? showDialog( ? showDialog(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AssignTagDialog( return AssignTagDialog(
title: 'Edit Device', title: 'Edit Device',
addedProducts: TagHelper.createInitialSelectedProductsForTags(tags, subspaces), addedProducts: TagHelper.createInitialSelectedProductsForTags(
tags, subspaces),
spaceName: name, spaceName: name,
products: products, products: products,
subspaces: subspaces, subspaces: subspaces,
@ -646,7 +688,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) { if (subspaces != null) {
for (final subspace in subspaces!) { for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) { for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName == selectedSubspace.subspaceName) { if (subspace.subspaceName ==
selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags; subspace.tags = selectedSubspace.tags;
} }
} }
@ -670,7 +713,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
allTags: widget.allTags, allTags: widget.allTags,
projectTags: widget.projectTags, projectTags: widget.projectTags,
initialSelectedProducts: initialSelectedProducts:
TagHelper.createInitialSelectedProductsForTags(tags, subspaces), TagHelper.createInitialSelectedProductsForTags(
tags, subspaces),
onSave: (selectedSpaceTags, selectedSubspaces) { onSave: (selectedSpaceTags, selectedSubspaces) {
setState(() { setState(() {
tags = selectedSpaceTags; tags = selectedSpaceTags;
@ -680,7 +724,8 @@ class CreateSpaceDialogState extends State<CreateSpaceDialog> {
if (subspaces != null) { if (subspaces != null) {
for (final subspace in subspaces!) { for (final subspace in subspaces!) {
for (final selectedSubspace in selectedSubspaces) { for (final selectedSubspace in selectedSubspaces) {
if (subspace.subspaceName == selectedSubspace.subspaceName) { if (subspace.subspaceName ==
selectedSubspace.subspaceName) {
subspace.tags = selectedSubspace.tags; subspace.tags = selectedSubspace.tags;
} }
} }

View File

@ -17,36 +17,33 @@ class PlusButtonWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Positioned( return GestureDetector(
left: offset.dx, onTap: () {
top: offset.dy, Offset newPosition;
child: GestureDetector( switch (direction) {
onTap: () { case 'left':
Offset newPosition; newPosition = const Offset(-200, 0);
switch (direction) { break;
case 'left': case 'right':
newPosition = const Offset(-200, 0); newPosition = const Offset(200, 0);
break; break;
case 'right': case 'down':
newPosition = const Offset(200, 0); newPosition = const Offset(0, 150);
break; break;
case 'down': default:
newPosition = const Offset(0, 150); newPosition = Offset.zero;
break; }
default: onButtonTap(index, newPosition, direction);
newPosition = Offset.zero; },
} child: Container(
onButtonTap(index, newPosition, direction); width: 30,
}, height: 30,
child: Container( decoration: const BoxDecoration(
width: 30, color: ColorsManager.spaceColor,
height: 30, shape: BoxShape.circle,
decoration: const BoxDecoration(
color: ColorsManager.spaceColor,
shape: BoxShape.circle,
),
child: const Icon(Icons.add, color: ColorsManager.whiteColors, size: 20),
), ),
child:
const Icon(Icons.add, color: ColorsManager.whiteColors, size: 20),
), ),
); );
} }

View File

@ -25,35 +25,34 @@ class SpaceCardWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return MouseRegion(
behavior: HitTestBehavior.opaque, onEnter: (_) => onHoverChanged(index, true),
onPanUpdate: (details) { onExit: (_) => onHoverChanged(index, false),
// Call the provided callback to update the position child: SizedBox(
final newPosition = position + details.delta; width: 140, // Make sure this covers both card and plus button
onPositionChanged(newPosition); height: 90,
},
child: MouseRegion(
onEnter: (_) {
// Call the provided callback to handle hover state
onHoverChanged(index, true);
},
onExit: (_) {
// Call the provided callback to handle hover state
onHoverChanged(index, false);
},
child: Stack( child: Stack(
clipBehavior: Clip clipBehavior: Clip.none,
.none, // Allow hovering elements to be displayed outside the boundary
children: [ children: [
buildSpaceContainer(index), // Build the space container // Main card
if (isHovered) ...[ Container(
PlusButtonWidget( width: 140,
index: index, height: 80,
direction: 'down', alignment: Alignment.center,
offset: const Offset(63, 50), color: Colors.transparent,
onButtonTap: onButtonTap, child: buildSpaceContainer(index),
),
// Plus button (NO inner Positioned!)
if (isHovered)
Align(
alignment: Alignment.bottomCenter,
child: PlusButtonWidget(
index: index,
direction: 'down',
offset: Offset.zero,
onButtonTap: onButtonTap,
),
), ),
],
], ],
), ),
), ),

View File

@ -79,6 +79,22 @@ class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
color: ColorsManager.blackColor, color: ColorsManager.blackColor,
), ),
), ),
Row(
children: [
Text(
'press Enter to Save',
style: context.textTheme.headlineLarge?.copyWith(
fontWeight: FontWeight.w500,
fontSize: 10,
color: ColorsManager.grayColor,
),
),
const SizedBox(
width: 5,
),
const Icon(Icons.save_as_sharp, size: 10),
],
),
const SizedBox(height: 16), const SizedBox(height: 16),
Container( Container(
width: context.screenWidth * 0.35, width: context.screenWidth * 0.35,
@ -101,13 +117,15 @@ class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
final index = entry.key; final index = entry.key;
final subSpace = entry.value; final subSpace = entry.value;
final lowerName = subSpace.subspaceName.toLowerCase(); final lowerName =
subSpace.subspaceName.toLowerCase();
final duplicateIndices = state.subSpaces final duplicateIndices = state.subSpaces
.asMap() .asMap()
.entries .entries
.where((e) => .where((e) =>
e.value.subspaceName.toLowerCase() == lowerName) e.value.subspaceName.toLowerCase() ==
lowerName)
.map((e) => e.key) .map((e) => e.key)
.toList(); .toList();
final isDuplicate = duplicateIndices.length > 1 && final isDuplicate = duplicateIndices.length > 1 &&
@ -182,10 +200,32 @@ class _CreateSubSpaceDialogState extends State<CreateSubSpaceDialog> {
Expanded( Expanded(
child: DefaultButton( child: DefaultButton(
onPressed: state.errorMessage.isEmpty onPressed: state.errorMessage.isEmpty
? () { ? () async {
final subSpacesBloc = context.read<SubSpaceBloc>(); final trimmedValue =
final subSpaces = subSpacesBloc.state.subSpaces; _subspaceNameController.text.trim();
final subSpacesBloc =
context.read<SubSpaceBloc>();
if (trimmedValue.isNotEmpty) {
subSpacesBloc.add(
AddSubSpace(
SubspaceModel(
subspaceName: trimmedValue,
disabled: false,
),
),
);
_subspaceNameController.clear();
}
await Future.delayed(
const Duration(milliseconds: 10));
final subSpaces =
subSpacesBloc.state.subSpaces;
// if (subSpaces.isNotEmpty) {
widget.onSave?.call(subSpaces); widget.onSave?.call(subSpaces);
// }
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
: null, : null,