From 20f94e290de9ce27d711c466bf1ce797814972be Mon Sep 17 00:00:00 2001 From: hannathkadher Date: Tue, 19 Nov 2024 01:21:32 +0400 Subject: [PATCH] fixed page load for space --- .../bloc/space_management_bloc.dart | 131 ++++++++++++------ .../bloc/space_management_event.dart | 10 ++ .../bloc/space_management_state.dart | 10 +- .../spaces_management/model/space_model.dart | 70 +++++++--- .../view/spaces_management_page.dart | 3 - .../widgets/community_structure_widget.dart | 117 ++++++++++++++-- .../widgets/loaded_space_widget.dart | 6 +- lib/services/space_mana_api.dart | 64 +++++---- 8 files changed, 303 insertions(+), 108 deletions(-) diff --git a/lib/pages/spaces_management/bloc/space_management_bloc.dart b/lib/pages/spaces_management/bloc/space_management_bloc.dart index 1b4aa221..0f5b2ad7 100644 --- a/lib/pages/spaces_management/bloc/space_management_bloc.dart +++ b/lib/pages/spaces_management/bloc/space_management_bloc.dart @@ -1,5 +1,3 @@ - -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; @@ -7,15 +5,14 @@ import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event. import 'package:syncrow_web/pages/spaces_management/bloc/space_management_state.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; -class SpaceManagementBloc - extends Bloc { +class SpaceManagementBloc extends Bloc { final CommunitySpaceManagementApi _api; SpaceManagementBloc(this._api) : super(SpaceManagementInitial()) { on(_onLoadCommunityAndSpaces); - on(_onCreateSpace); on(_onUpdateSpacePosition); on(_onCreateCommunity); + on(_onSaveSpaces); } void _onLoadCommunityAndSpaces( @@ -30,8 +27,7 @@ class SpaceManagementBloc // Use Future.wait to handle async calls within map List updatedCommunities = await Future.wait( communities.map((community) async { - List spaces = - await _api.getSpaceHierarchy(community.uuid); + List spaces = await _api.getSpaceHierarchy(community.uuid); return CommunityModel( uuid: community.uuid, createdAt: community.createdAt, @@ -50,15 +46,6 @@ class SpaceManagementBloc } } - void _onCreateSpace( - CreateSpaceEvent event, - Emitter emit, - ) { - // Handle space creation logic - // You can emit a new state here based on your needs - emit(SpaceCreationSuccess()); - } - void _onUpdateSpacePosition( UpdateSpacePositionEvent event, Emitter emit, @@ -82,9 +69,8 @@ class SpaceManagementBloc if (newCommunity != null) { if (previousState is SpaceManagementLoaded) { - final updatedCommunities = - List.from(previousState.communities) - ..add(newCommunity); + final updatedCommunities = List.from(previousState.communities) + ..add(newCommunity); emit(SpaceManagementLoaded(communities: updatedCommunities)); } } else { @@ -95,34 +81,91 @@ class SpaceManagementBloc } } -void _onSaveSpaces( - SaveSpacesEvent event, - Emitter emit, -) async { - final previousState = state; - emit(SpaceManagementLoading()); + void _onSaveSpaces( + SaveSpacesEvent event, + Emitter emit, + ) async { + final previousState = state; + emit(SpaceManagementLoading()); - try { - // Save spaces one by one - for (var space in event.spaces) { - await _api.createSpace( - communityId: event.communityUuid, - name: space.name, - parentId: space.parent?.uuid, - isPrivate: space.isPrivate, - position: space.position, - ); - } + try { + final updatedSpaces = await saveSpacesHierarchically(event.spaces, event.communityUuid); - emit(SpaceCreationSuccess()); - } catch (e) { - emit(SpaceManagementError('Error saving spaces: $e')); - - // Revert back to the previous state if an error occurs - if (previousState is SpaceManagementLoaded) { - emit(previousState); + emit(SpaceCreationSuccess(spaces: updatedSpaces)); + add(LoadCommunityAndSpacesEvent()); + } catch (e) { + emit(SpaceManagementError('Error saving spaces: $e')); + if (previousState is SpaceManagementLoaded) { + emit(previousState); + } } } -} + Future> saveSpacesHierarchically( + List spaces, String communityUuid) async { + final orderedSpaces = flattenHierarchy(spaces); + + for (var space in orderedSpaces) { + try { + if (space.uuid != null && space.uuid!.isNotEmpty) { + // Call update if the space already has a UUID + final response = await _api.updateSpace( + communityId: communityUuid, + spaceId: space.uuid!, + name: space.name, + parentId: space.parent?.uuid, + isPrivate: space.isPrivate, + position: space.position, + icon: space.icon, + direction: space.incomingConnection?.direction, + ); + } else { + // Call create if the space does not have a UUID + final response = await _api.createSpace( + communityId: communityUuid, + name: space.name, + parentId: space.parent?.uuid, + isPrivate: space.isPrivate, + position: space.position, + icon: space.icon, + direction: space.incomingConnection?.direction, + ); + space.uuid = response?.uuid; + } + } catch (e) { + print('Error creating space ${space.name}: $e'); + rethrow; // Stop further execution on failure + } + } + return spaces; + } + + List flattenHierarchy(List spaces) { + final result = {}; // Use a Set to avoid duplicates + + // Collect all top-level spaces (those without a parent) + final topLevelSpaces = spaces.where((space) => space.parent == null); + + void visit(SpaceModel space) { + if (!result.contains(space)) { + result.add(space); // Add the space + for (var child in spaces.where((s) => s.parent == space)) { + visit(child); // Recursively add children based on parent + } + } + } + + // Start with top-level spaces + for (var space in topLevelSpaces) { + visit(space); + } + + // Add any spaces that were not part of the hierarchy + for (var space in spaces) { + if (!result.contains(space)) { + result.add(space); // Add standalone or orphan spaces + } + } + return result.toList(); // Convert back to a list + } } diff --git a/lib/pages/spaces_management/bloc/space_management_event.dart b/lib/pages/spaces_management/bloc/space_management_event.dart index 3bea27e8..cc0ba9b2 100644 --- a/lib/pages/spaces_management/bloc/space_management_event.dart +++ b/lib/pages/spaces_management/bloc/space_management_event.dart @@ -73,3 +73,13 @@ class CreateCommunityEvent extends SpaceManagementEvent { @override List get props => [name, description, regionId]; } + + +class LoadSpaceHierarchyEvent extends SpaceManagementEvent { + final String communityId; + + const LoadSpaceHierarchyEvent({required this.communityId}); + + @override + List get props => [communityId]; +} \ No newline at end of file diff --git a/lib/pages/spaces_management/bloc/space_management_state.dart b/lib/pages/spaces_management/bloc/space_management_state.dart index 87a98ef2..3d859547 100644 --- a/lib/pages/spaces_management/bloc/space_management_state.dart +++ b/lib/pages/spaces_management/bloc/space_management_state.dart @@ -1,5 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; +import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; abstract class SpaceManagementState extends Equatable { const SpaceManagementState(); @@ -21,7 +22,14 @@ class SpaceManagementLoaded extends SpaceManagementState { List get props => [communities]; } -class SpaceCreationSuccess extends SpaceManagementState {} +class SpaceCreationSuccess extends SpaceManagementState { + final List spaces; + + const SpaceCreationSuccess({required this.spaces}); + + @override + List get props => [spaces]; +} class SpaceManagementError extends SpaceManagementState { final String errorMessage; diff --git a/lib/pages/spaces_management/model/space_model.dart b/lib/pages/spaces_management/model/space_model.dart index 6363a331..f6c3620c 100644 --- a/lib/pages/spaces_management/model/space_model.dart +++ b/lib/pages/spaces_management/model/space_model.dart @@ -3,7 +3,7 @@ import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart'; class SpaceModel { - final String? uuid; + String? uuid; final String? icon; final String? spaceTuyaUuid; final String name; @@ -16,24 +16,25 @@ class SpaceModel { bool isHovered; List outgoingConnections = []; // Connections from this space - List incomingConnections = []; // Connections to this space + Connection? incomingConnection; // Connections to this space - SpaceModel({ - this.uuid, - this.spaceTuyaUuid, - required this.icon, - required this.name, - required this.isPrivate, - this.invitationCode, - this.parent, - this.community, - required this.children, - required this.position, - this.isHovered = false, - }); + SpaceModel( + {this.uuid, + this.spaceTuyaUuid, + required this.icon, + required this.name, + required this.isPrivate, + this.invitationCode, + this.parent, + this.community, + required this.children, + required this.position, + this.isHovered = false, + this.incomingConnection}); factory SpaceModel.fromJson(Map json) { - return SpaceModel( + // Create SpaceModel instance first + final instance = SpaceModel( uuid: json['uuid'] ?? '', spaceTuyaUuid: json['spaceTuyaUuid'], name: json['spaceName'], @@ -45,11 +46,38 @@ class SpaceModel { ? (json['children'] as List).map((child) => SpaceModel.fromJson(child)).toList() : [], icon: json['icon'] as String?, - position: json['position'] != null - ? Offset(json['position']['dx'], json['position']['dy']) + position: json['x'] != null && json['y'] != null + ? Offset(json['x'], json['y']) : const Offset(0, 0), isHovered: false, ); + + // Add incomingConnection to the instance after creation + if (json['incomingConnections'] != null && + json['incomingConnections'] is List && + (json['incomingConnections'] as List).isNotEmpty && instance.parent != null ) { + final conn = json['incomingConnections'][0]; + instance.incomingConnection = Connection( + startSpace: instance.parent ?? instance, // Parent space + endSpace: instance, // This space instance + direction: conn['direction'], + ); + } + + return instance; + } + @override + String toString() { + return ''' +SpaceModel { + uuid: $uuid, + name: $name, + isPrivate: $isPrivate, + position: {dx: ${position.dx}, dy: ${position.dy}}, + parentUuid: ${parent?.uuid}, + children: ${children.map((child) => child.name).toList()}, + isHovered: $isHovered +}'''; } Map toMap() { @@ -66,15 +94,11 @@ class SpaceModel { 'position': {'dx': position.dx, 'dy': position.dy}, 'isHovered': isHovered, 'outgoingConnections': outgoingConnections.map((c) => c.toMap()).toList(), - 'incomingConnections': incomingConnections.map((c) => c.toMap()).toList(), + 'incomingConnection': incomingConnection?.toMap(), }; } void addOutgoingConnection(Connection connection) { outgoingConnections.add(connection); } - - void addIncomingConnection(Connection connection) { - incomingConnections.add(connection); - } } diff --git a/lib/pages/spaces_management/view/spaces_management_page.dart b/lib/pages/spaces_management/view/spaces_management_page.dart index ac076b84..79d57072 100644 --- a/lib/pages/spaces_management/view/spaces_management_page.dart +++ b/lib/pages/spaces_management/view/spaces_management_page.dart @@ -7,10 +7,7 @@ import 'package:syncrow_web/pages/spaces_management/model/community_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/connection_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/space_data_model.dart'; import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/community_structure_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/gradient_canvas_border_widget.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/loaded_space_widget.dart'; -import 'package:syncrow_web/pages/spaces_management/widgets/sidebar_widget.dart'; import 'package:syncrow_web/services/space_mana_api.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart'; diff --git a/lib/pages/spaces_management/widgets/community_structure_widget.dart b/lib/pages/spaces_management/widgets/community_structure_widget.dart index f162b49f..ce5007e0 100644 --- a/lib/pages/spaces_management/widgets/community_structure_widget.dart +++ b/lib/pages/spaces_management/widgets/community_structure_widget.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/common/buttons/add_space_button.dart'; +import 'package:syncrow_web/pages/spaces_management/bloc/space_management_bloc.dart'; +import 'package:syncrow_web/pages/spaces_management/bloc/space_management_event.dart'; import 'package:syncrow_web/pages/spaces_management/model/space_model.dart'; import 'package:syncrow_web/pages/spaces_management/view/dialogs/create_space_dialog.dart'; import 'package:syncrow_web/pages/spaces_management/widgets/curved_line_painter.dart'; @@ -11,8 +14,14 @@ import 'package:syncrow_web/utils/color_manager.dart'; class CommunityStructureArea extends StatefulWidget { final CommunityModel? selectedCommunity; + final List spaces; + final List connections; - CommunityStructureArea({this.selectedCommunity}); + CommunityStructureArea({ + this.selectedCommunity, + required this.spaces, + required this.connections, + }); @override _CommunityStructureAreaState createState() => _CommunityStructureAreaState(); @@ -24,6 +33,28 @@ class _CommunityStructureAreaState extends State { List spaces = []; List connections = []; + @override + void initState() { + super.initState(); + spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; + connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; + } + + @override + void didUpdateWidget(covariant CommunityStructureArea oldWidget) { + super.didUpdateWidget(oldWidget); + + if (oldWidget.spaces != widget.spaces || oldWidget.connections != widget.connections) { + setState(() { + spaces = widget.spaces.isNotEmpty ? flattenSpaces(widget.spaces) : []; + connections = widget.spaces.isNotEmpty ? createConnections(widget.spaces) : []; + }); + for (var space in spaces) { + print('InitState - Space Position ${space.name}: ${space.incomingConnection?.direction}'); + } + } + } + @override Widget build(BuildContext context) { Size screenSize = MediaQuery.of(context).size; @@ -196,7 +227,6 @@ class _CommunityStructureAreaState extends State { screenSize.width / 2 - 75, // Center horizontally screenSize.height / 2 - 50, // Slightly above the center vertically ); - SpaceModel newSpace = SpaceModel( name: name, icon: icon, @@ -208,15 +238,15 @@ class _CommunityStructureAreaState extends State { if (parentIndex != null && direction != null) { SpaceModel parentSpace = spaces[parentIndex]; newSpace.parent = parentSpace; - parentSpace.children.add(newSpace); final newConnection = Connection( startSpace: parentSpace, endSpace: newSpace, direction: direction, ); connections.add(newConnection); - newSpace.addIncomingConnection(newConnection); + newSpace.incomingConnection = newConnection; parentSpace.addOutgoingConnection(newConnection); + parentSpace.children.add(newSpace); } spaces.add(newSpace); @@ -234,14 +264,77 @@ class _CommunityStructureAreaState extends State { }); } - void _saveSpaces() { - List> spaceData = spaces.map((space) => space.toMap()).toList(); - List> connectionData = - connections.map((connection) => connection.toMap()).toList(); + List flattenSpaces(List spaces) { + List result = []; - // Save to local storage, a file, or send to a backend - print('Spaces: ${spaceData}'); - print('Connections: ${connectionData}'); - + void flatten(SpaceModel space) { + // Add the current space to the result + result.add(space); + + // Recursively flatten child spaces + for (var child in space.children) { + flatten(child); + } + } + + // Process each top-level space + for (var space in spaces) { + flatten(space); + } + + return result; + } + + List createConnections(List spaces) { + List connections = []; + + void addConnections(SpaceModel parent, String direction) { + for (var child in parent.children) { + // Create a connection object + connections.add( + Connection( + startSpace: parent, + endSpace: child, + direction: child.incomingConnection?.direction ?? + "down", // Assuming "down" for all direct children + ), + ); + + // Recursively process the child's children + addConnections(child, direction); + } + } + + // Process each top-level space + for (var space in spaces) { + addConnections(space, "down"); + } + + return connections; + } + + void _saveSpaces() { + if (widget.selectedCommunity == null) { + print("No community selected for saving spaces."); + return; + } + + // Prepare spaces and connections data + List spaceList = spaces; + String communityUuid = widget.selectedCommunity!.uuid; + + for (var space in spaceList) { + print("Processing space: ${space.name}, UUID: ${space.uuid}"); + // Example: Log connections + print("Parent UUID: ${space.parent?.uuid}"); + print("Incoming connection: ${space.incomingConnection?.direction}"); + print("Outgoing connections: ${space.outgoingConnections.map((c) => c.direction).toList()}"); + } + + // Dispatch the save event + context.read().add(SaveSpacesEvent( + spaces: spaceList, + communityUuid: communityUuid, + )); } } diff --git a/lib/pages/spaces_management/widgets/loaded_space_widget.dart b/lib/pages/spaces_management/widgets/loaded_space_widget.dart index 25e41e1a..3b3ebeab 100644 --- a/lib/pages/spaces_management/widgets/loaded_space_widget.dart +++ b/lib/pages/spaces_management/widgets/loaded_space_widget.dart @@ -32,7 +32,11 @@ class _LoadedStateViewState extends State { communities: widget.communities, onCommunitySelected: widget.onCommunitySelected, ), - CommunityStructureArea(selectedCommunity: widget.selectedCommunity), + CommunityStructureArea( + selectedCommunity: widget.selectedCommunity, + spaces: widget.selectedCommunity?.spaces ?? [], + connections: [], + ), ], ), const GradientCanvasBorderWidget(), diff --git a/lib/services/space_mana_api.dart b/lib/services/space_mana_api.dart index 12372326..2461d5c8 100644 --- a/lib/services/space_mana_api.dart +++ b/lib/services/space_mana_api.dart @@ -32,8 +32,7 @@ class CommunitySpaceManagementApi { Future getCommunityById(String communityId) async { try { final response = await HTTPService().get( - path: ApiEndpoints.getCommunityById - .replaceAll('{communityId}', communityId), + path: ApiEndpoints.getCommunityById.replaceAll('{communityId}', communityId), expectedResponseModel: (json) { return CommunityModel.fromJson(json['data']); }, @@ -45,8 +44,7 @@ class CommunitySpaceManagementApi { } } - Future createCommunity( - String name, String description, String regionId) async { + Future createCommunity(String name, String description, String regionId) async { try { final response = await HTTPService().post( path: ApiEndpoints.createCommunity, @@ -66,12 +64,10 @@ class CommunitySpaceManagementApi { } } - Future updateCommunity( - String communityId, CommunityModel community) async { + Future updateCommunity(String communityId, CommunityModel community) async { try { final response = await HTTPService().put( - path: ApiEndpoints.updateCommunity - .replaceAll('{communityId}', communityId), + path: ApiEndpoints.updateCommunity.replaceAll('{communityId}', communityId), body: community.toMap(), expectedResponseModel: (json) { return json['success'] ?? false; @@ -87,8 +83,7 @@ class CommunitySpaceManagementApi { Future deleteCommunity(String communityId) async { try { final response = await HTTPService().delete( - path: ApiEndpoints.deleteCommunity - .replaceAll('{communityId}', communityId), + path: ApiEndpoints.deleteCommunity.replaceAll('{communityId}', communityId), expectedResponseModel: (json) { return json['success'] ?? false; }, @@ -145,20 +140,24 @@ class CommunitySpaceManagementApi { required String communityId, required String name, String? parentId, + String? direction, bool isPrivate = false, required Offset position, + String? icon, }) async { try { final body = { - 'name': name, + 'spaceName': name, 'isPrivate': isPrivate, 'x': position.dx, 'y': position.dy, + 'direction': direction, + 'icon': icon }; if (parentId != null) { - body['parentId'] = parentId; + body['parentUuid'] = parentId; } - + print(ApiEndpoints.createSpace.replaceAll('{communityId}', communityId)); final response = await HTTPService().post( path: ApiEndpoints.createSpace.replaceAll('{communityId}', communityId), body: body, @@ -173,22 +172,42 @@ class CommunitySpaceManagementApi { } } - Future updateSpace( - String communityId, String spaceId, SpaceModel space) async { + Future updateSpace({ + required String communityId, + required spaceId, + required String name, + String? parentId, + String? icon, + String? direction, + bool isPrivate = false, + required Offset position, + }) async { try { + final body = { + 'spaceName': name, + 'isPrivate': isPrivate, + 'x': position.dx, + 'y': position.dy, + 'direction': direction, + 'icon': icon + }; + if (parentId != null) { + body['parentUuid'] = parentId; + } + final response = await HTTPService().put( path: ApiEndpoints.updateSpace .replaceAll('{communityId}', communityId) .replaceAll('{spaceId}', spaceId), - body: space.toMap(), + body: body, expectedResponseModel: (json) { - return json['success'] ?? false; + return SpaceModel.fromJson(json['data']); }, ); return response; } catch (e) { - debugPrint('Error updating space: $e'); - return false; + debugPrint('Error creating space: $e'); + return null; } } @@ -212,12 +231,9 @@ class CommunitySpaceManagementApi { Future> getSpaceHierarchy(String communityId) async { try { final response = await HTTPService().get( - path: ApiEndpoints.getSpaceHierarchy - .replaceAll('{communityId}', communityId), + path: ApiEndpoints.getSpaceHierarchy.replaceAll('{communityId}', communityId), expectedResponseModel: (json) { - return (json['data'] as List) - .map((spaceJson) => SpaceModel.fromJson(spaceJson)) - .toList(); + return (json['data'] as List).map((spaceJson) => SpaceModel.fromJson(spaceJson)).toList(); }, ); return response;