Compare commits

..

2 Commits

6 changed files with 138 additions and 87 deletions

View File

@ -5,13 +5,14 @@ import 'package:syncrow_web/utils/color_manager.dart';
class SpacesConnectionsArrowPainter extends CustomPainter {
final List<SpaceConnectionModel> connections;
final Map<String, Offset> positions;
final double cardWidth = 150.0;
final Map<String, double> cardWidths;
final double cardHeight = 90.0;
final Set<String> highlightedUuids;
SpacesConnectionsArrowPainter({
required this.connections,
required this.positions,
required this.cardWidths,
required this.highlightedUuids,
});
@ -29,19 +30,30 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
final from = positions[connection.from];
final to = positions[connection.to];
final fromWidth = cardWidths[connection.from] ?? 150.0;
final toWidth = cardWidths[connection.to] ?? 150.0;
if (from != null && to != null) {
final startPoint =
Offset(from.dx + cardWidth / 2, from.dy + cardHeight - 10);
final endPoint = Offset(to.dx + cardWidth / 2, to.dy);
Offset(from.dx + fromWidth / 2, from.dy + cardHeight - 10);
final endPoint = Offset(to.dx + toWidth / 2, to.dy);
final path = Path()..moveTo(startPoint.dx, startPoint.dy);
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 100);
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 100);
path.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx,
controlPoint2.dy, endPoint.dx, endPoint.dy);
if ((startPoint.dx - endPoint.dx).abs() < 1.0) {
path.lineTo(endPoint.dx, endPoint.dy);
} else {
final controlPoint1 = Offset(startPoint.dx, startPoint.dy + 100);
final controlPoint2 = Offset(endPoint.dx, endPoint.dy - 100);
path.cubicTo(
controlPoint1.dx,
controlPoint1.dy,
controlPoint2.dx,
controlPoint2.dy,
endPoint.dx,
endPoint.dy,
);
}
canvas.drawPath(path, paint);
@ -51,7 +63,7 @@ class SpacesConnectionsArrowPainter extends CustomPainter {
: ColorsManager.blackColor.withValues(alpha: 0.5)
..style = PaintingStyle.fill
..blendMode = BlendMode.srcIn;
canvas.drawCircle(endPoint, 4, circlePaint);
canvas.drawCircle(endPoint, 6, circlePaint);
}
}
}

View File

@ -30,10 +30,11 @@ class CommunityStructureCanvas extends StatefulWidget {
class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
with SingleTickerProviderStateMixin {
final Map<String, Offset> _positions = {};
final double _cardWidth = 150.0;
final Map<String, double> _cardWidths = {};
final double _cardHeight = 90.0;
final double _horizontalSpacing = 150.0;
final double _verticalSpacing = 120.0;
static const double _minCardWidth = 150.0;
late final TransformationController _transformationController;
late final AnimationController _animationController;
@ -51,6 +52,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
@override
void didUpdateWidget(covariant CommunityStructureCanvas oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.selectedSpace?.uuid != oldWidget.selectedSpace?.uuid) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
@ -58,7 +60,6 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
}
});
}
super.didUpdateWidget(oldWidget);
}
@override
@ -68,6 +69,34 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
super.dispose();
}
double _calculateCardWidth(String text) {
final style = context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
);
final textPainter = TextPainter(
text: TextSpan(text: text, style: style),
maxLines: 1,
textDirection: TextDirection.ltr,
)..layout();
const iconWidth = 40.0;
const horizontalPadding = 10.0;
const contentPadding = 10.0;
final calculatedWidth =
iconWidth + horizontalPadding + textPainter.width + contentPadding;
return calculatedWidth.clamp(_minCardWidth, double.infinity);
}
void _calculateAllCardWidths(List<SpaceModel> spaces) {
for (final space in spaces) {
_cardWidths[space.uuid] = _calculateCardWidth(space.spaceName);
if (space.children.isNotEmpty) {
_calculateAllCardWidths(space.children);
}
}
}
Set<String> _getAllDescendantUuids(SpaceModel space) {
final uuids = <String>{};
for (final child in space.children) {
@ -102,11 +131,12 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
final position = _positions[space.uuid];
if (position == null) return;
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
const scale = 1;
final viewSize = context.size;
if (viewSize == null) return;
final x = -position.dx * scale + (viewSize.width / 2) - (_cardWidth * scale / 2);
final x = -position.dx * scale + (viewSize.width / 2) - (cardWidth * scale / 2);
final y =
-position.dy * scale + (viewSize.height / 2) - (_cardHeight * scale / 2);
@ -155,13 +185,16 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
Map<int, double> levelXOffset,
) {
for (final space in spaces) {
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
double childSubtreeWidth = 0;
if (space.children.isNotEmpty) {
_calculateLayout(space.children, depth + 1, levelXOffset);
final firstChildPos = _positions[space.children.first.uuid];
final lastChildPos = _positions[space.children.last.uuid];
if (firstChildPos != null && lastChildPos != null) {
childSubtreeWidth = (lastChildPos.dx + _cardWidth) - firstChildPos.dx;
final lastChildWidth =
_cardWidths[space.children.last.uuid] ?? _minCardWidth;
childSubtreeWidth = (lastChildPos.dx + lastChildWidth) - firstChildPos.dx;
}
}
@ -170,7 +203,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
if (space.children.isNotEmpty) {
final firstChildPos = _positions[space.children.first.uuid]!;
x = firstChildPos.dx + (childSubtreeWidth - _cardWidth) / 2;
x = firstChildPos.dx + (childSubtreeWidth - cardWidth) / 2;
} else {
x = currentX;
}
@ -187,7 +220,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
final y = depth * (_verticalSpacing + _cardHeight);
_positions[space.uuid] = Offset(x, y);
levelXOffset[depth] = x + _cardWidth + _horizontalSpacing;
levelXOffset[depth] = x + cardWidth + _horizontalSpacing;
}
}
@ -202,8 +235,11 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
List<Widget> _buildTreeWidgets() {
_positions.clear();
_cardWidths.clear();
final community = widget.community;
_calculateAllCardWidths(community.spaces);
final levelXOffset = <int, double>{};
_calculateLayout(community.spaces, 0, levelXOffset);
@ -240,6 +276,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
painter: SpacesConnectionsArrowPainter(
connections: connections,
positions: _positions,
cardWidths: _cardWidths,
highlightedUuids: highlightedUuids,
),
child: Stack(alignment: AlignmentDirectional.center, children: widgets),
@ -271,6 +308,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
continue;
}
final cardWidth = _cardWidths[space.uuid] ?? _minCardWidth;
final isHighlighted = highlightedUuids.contains(space.uuid);
final hasNoSelectedSpace = widget.selectedSpace == null;
@ -278,14 +316,10 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
buildSpaceContainer: () {
return Opacity(
opacity: hasNoSelectedSpace || isHighlighted ? 1.0 : 0.5,
child: Tooltip(
message: space.spaceName,
preferBelow: false,
child: SpaceCell(
onTap: () => _onSpaceTapped(space),
icon: space.icon,
name: space.spaceName,
),
child: SpaceCell(
onTap: () => _onSpaceTapped(space),
icon: space.icon,
name: space.spaceName,
),
);
},
@ -305,7 +339,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
Positioned(
left: position.dx,
top: position.dy,
width: _cardWidth,
width: cardWidth,
height: _cardHeight,
child: Draggable<SpaceReorderDataModel>(
data: reorderData,
@ -314,7 +348,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
child: Opacity(
opacity: 0.2,
child: SizedBox(
width: _cardWidth,
width: cardWidth,
height: _cardHeight,
child: spaceCard,
),
@ -330,7 +364,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
);
final targetPos = Offset(
position.dx + _cardWidth + (_horizontalSpacing / 4) - 20,
position.dx + cardWidth + (_horizontalSpacing / 4) - 20,
position.dy,
);
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));

View File

@ -20,21 +20,19 @@ class SpaceCell extends StatelessWidget {
return InkWell(
onTap: onTap,
child: Container(
width: 150,
padding: const EdgeInsetsDirectional.only(end: 10),
height: 70,
decoration: _containerDecoration(),
child: Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
children: [
_buildIconContainer(),
const SizedBox(width: 10),
Expanded(
child: Text(
name,
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
color: ColorsManager.blackColor,
),
overflow: TextOverflow.ellipsis,
Text(
name,
style: context.textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.bold,
color: ColorsManager.blackColor,
),
),
],
@ -74,8 +72,9 @@ class SpaceCell extends StatelessWidget {
borderRadius: BorderRadius.circular(15),
boxShadow: [
BoxShadow(
color: ColorsManager.lightGrayColor.withValues(alpha: 0.25),
blurRadius: 7,
color: ColorsManager.lightGrayColor.withValues(alpha: 0.5),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 3),
),
],

View File

@ -5,6 +5,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/data/remote_delete_space_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/bloc/delete_space_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_dialog_form.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_loading_widget.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/delete_space/presentation/widgets/delete_space_status_widget.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -45,8 +47,8 @@ class DeleteSpaceDialog extends StatelessWidget {
communityUuid: community.uuid,
),
DeleteSpaceLoading() => const DeleteSpaceLoadingWidget(),
DeleteSpaceSuccess(:final successMessage) => DeleteSpaceStatusWidget(
message: successMessage,
DeleteSpaceSuccess() => DeleteSpaceStatusWidget(
message: state.successMessage,
icon: const Icon(
Icons.check_circle,
size: 92,
@ -69,50 +71,3 @@ class DeleteSpaceDialog extends StatelessWidget {
);
}
}
class DeleteSpaceLoadingWidget extends StatelessWidget {
const DeleteSpaceLoadingWidget({super.key});
@override
Widget build(BuildContext context) {
return const SizedBox.square(
dimension: 32,
child: Center(child: CircularProgressIndicator()),
);
}
}
class DeleteSpaceStatusWidget extends StatelessWidget {
const DeleteSpaceStatusWidget({
required this.message,
required this.icon,
super.key,
});
final String message;
final Widget icon;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 16,
children: [
icon,
SelectableText(
message,
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.blackColor,
fontSize: 22,
),
textAlign: TextAlign.center,
),
FilledButton(
onPressed: Navigator.of(context).pop,
child: const Text('Close'),
),
],
);
}
}

View File

@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
class DeleteSpaceLoadingWidget extends StatelessWidget {
const DeleteSpaceLoadingWidget({super.key});
@override
Widget build(BuildContext context) {
return const SizedBox.square(
dimension: 32,
child: Center(child: CircularProgressIndicator()),
);
}
}

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class DeleteSpaceStatusWidget extends StatelessWidget {
const DeleteSpaceStatusWidget({
required this.message,
required this.icon,
super.key,
});
final String message;
final Widget icon;
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
spacing: 16,
children: [
icon,
SelectableText(
message,
style: context.textTheme.bodyMedium?.copyWith(
color: ColorsManager.blackColor,
fontSize: 22,
),
textAlign: TextAlign.center,
),
FilledButton(
onPressed: Navigator.of(context).pop,
child: const Text('Close'),
),
],
);
}
}