Compare commits

...

13 Commits

Author SHA1 Message Date
94f9c1beea Adjust layout in CommunityStructureCanvas by adding horizontal padding to positions and refining target position calculations for improved spacing and alignment. Enhance Stack widget behavior by allowing overflow clipping. 2025-07-22 10:04:38 +03:00
dfd8c5fa31 Replace Container with AnimatedContainer in CommunityStructureCanvas to enhance visual feedback during state changes. Adjust alpha value for improved visibility based on candidate data presence. 2025-07-22 09:46:56 +03:00
60b8ee8b50 Enhance DragTarget logic in CommunityStructureCanvas by refining conditions for rendering and improving readability. Ensure proper handling of dragged data and its parent/community relationships. 2025-07-22 09:37:31 +03:00
9d60f913eb Refactor CommunityStructureCanvas to simplify DragTarget logic by replacing SizedBox with SizedBox.shrink() for better performance and readability. 2025-07-22 09:32:57 +03:00
40251b846b Integrate ReorderSpaces functionality into CommunityStructureCanvas and enhance RemoteReorderSpacesService with dynamic URL generation. Update ReorderSpacesParam to require parentSpaceUuid and spaces for improved validation and serialization. 2025-07-21 16:43:26 +03:00
1323bceca1 Update ReorderSpacesParam to make parentSpaceUuid optional and add toJson method for serialization. 2025-07-21 16:39:31 +03:00
35c8a73156 Refactor SpaceManagementPage's initState to ensure HTTPService is initialized before use in CommunitiesBloc. 2025-07-21 16:24:11 +03:00
ce65b068ff Merge branch 'dev' of https://github.com/SyncrowIOT/web into feature/reorder_spaces_api_integration 2025-07-21 16:22:22 +03:00
96f107f972 Refactor SpaceManagementPage to utilize a shared HTTPService instance for API calls in Communities, SpaceDetails, Products, and ReorderSpaces blocs, and injected ReorderSpacesBloc into it. 2025-07-21 16:15:26 +03:00
a3a7937021 Implemented ReorderSpacesBloc. 2025-07-21 16:14:03 +03:00
9bf715501b Implement ReorderSpacesService. 2025-07-21 15:57:31 +03:00
c65f4a7fab Add ReorderSpacesParam and ReorderSpacesService for managing space reordering functionality. 2025-07-21 15:57:20 +03:00
7af8887d4f Add new API endpoint for reordering spaces in the community module. 2025-07-21 15:57:10 +03:00
9 changed files with 218 additions and 17 deletions

View File

@ -9,6 +9,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/presen
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/products/data/services/remote_products_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/products/presentation/bloc/products_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/data/services/remote_reorder_spaces_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/presentation/bloc/reorder_spaces_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/remote_space_details_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/data/services/unique_space_details_spaces_decorator_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/bloc/space_details_bloc.dart';
@ -25,15 +27,16 @@ class SpaceManagementPage extends StatefulWidget {
class _SpaceManagementPageState extends State<SpaceManagementPage> {
late final CommunitiesBloc communitiesBloc;
late final HTTPService _httpService;
@override
void initState() {
_httpService = HTTPService();
communitiesBloc = CommunitiesBloc(
communitiesService: DebouncedCommunitiesService(
RemoteCommunitiesService(HTTPService()),
RemoteCommunitiesService(_httpService),
),
)..add(const LoadCommunities(LoadCommunitiesParam()));
super.initState();
}
@ -50,13 +53,18 @@ class _SpaceManagementPageState extends State<SpaceManagementPage> {
BlocProvider(
create: (context) => SpaceDetailsBloc(
UniqueSpaceDetailsSpacesDecoratorService(
RemoteSpaceDetailsService(httpService: HTTPService()),
RemoteSpaceDetailsService(httpService: _httpService),
),
),
),
BlocProvider(
create: (context) => ProductsBloc(
RemoteProductsService(HTTPService()),
RemoteProductsService(_httpService),
),
),
BlocProvider(
create: (context) => ReorderSpacesBloc(
RemoteReorderSpacesService(_httpService),
),
),
],

View File

@ -11,6 +11,8 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/bloc/communities_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/presentation/communities_tree_selection_bloc/communities_tree_selection_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/presentation/bloc/reorder_spaces_bloc.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/space_details/presentation/helpers/space_details_dialog_helper.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
@ -164,6 +166,16 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
context.read<CommunitiesBloc>().add(
CommunitiesUpdateCommunity(newCommunity),
);
context.read<ReorderSpacesBloc>().add(
ReorderSpacesEvent(
ReorderSpacesParam(
communityUuid: widget.community.uuid,
parentSpaceUuid: data.parent?.uuid ?? '',
spaces: children,
),
),
);
}
void _onSpaceTapped(SpaceModel? space) {
@ -245,6 +257,13 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
final levelXOffset = <int, double>{};
_calculateLayout(community.spaces, 0, levelXOffset);
const horizontalCanvasPadding = 100.0;
final originalPositions = Map.of(_positions);
_positions.clear();
for (final entry in originalPositions.entries) {
_positions[entry.key] = entry.value.translate(horizontalCanvasPadding, 0);
}
final selectedSpace = widget.selectedSpace;
final highlightedUuids = <String>{};
if (selectedSpace != null) {
@ -262,7 +281,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
community: widget.community,
);
final createButtonX = levelXOffset[0] ?? 0.0;
final createButtonX = (levelXOffset[0] ?? 0.0) + horizontalCanvasPadding;
const createButtonY = 0.0;
widgets.add(
@ -294,10 +313,12 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
CommunityModel? community,
SpaceModel? parent,
}) {
const targetWidth = 40.0;
final padding = (_horizontalSpacing - targetWidth) / 2;
if (spaces.isNotEmpty) {
final firstChildPos = _positions[spaces.first.uuid]!;
final targetPos = Offset(
firstChildPos.dx - (_horizontalSpacing / 4),
firstChildPos.dx - padding - targetWidth,
firstChildPos.dy,
);
widgets.add(_buildDropTarget(parent, community, 0, targetPos));
@ -379,7 +400,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
);
final targetPos = Offset(
position.dx + cardWidth + (_horizontalSpacing / 4) - 20,
position.dx + cardWidth + padding,
position.dy,
);
widgets.add(_buildDropTarget(parent, community, i + 1, targetPos));
@ -414,24 +435,33 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
child: DragTarget<SpaceReorderDataModel>(
builder: (context, candidateData, rejectedData) {
if (_draggedData == null) {
return const SizedBox();
return const SizedBox.shrink();
}
final isTargetForDragged = (_draggedData?.parent?.uuid == parent?.uuid &&
_draggedData?.community == null) ||
(_draggedData?.community?.uuid == community?.uuid &&
_draggedData?.parent == null);
final children = parent?.children ?? community?.spaces ?? [];
final isSameParent = (_draggedData!.parent?.uuid == parent?.uuid &&
_draggedData!.community == null) ||
(_draggedData!.community?.uuid == community?.uuid &&
_draggedData!.parent == null);
if (!isTargetForDragged) {
return const SizedBox();
if (!isSameParent) {
return const SizedBox.shrink();
}
return Container(
final oldIndex =
children.indexWhere((s) => s.uuid == _draggedData!.space.uuid);
if (oldIndex != -1 && (oldIndex == index || oldIndex == index - 1)) {
return const SizedBox.shrink();
}
return AnimatedContainer(
duration: const Duration(milliseconds: 150),
width: 40,
alignment: Alignment.center,
height: _cardHeight,
decoration: BoxDecoration(
color: context.theme.colorScheme.primary.withValues(
alpha: candidateData.isNotEmpty ? 0.7 : 0.3,
alpha: candidateData.isNotEmpty ? 0.9 : 0.3,
),
borderRadius: BorderRadius.circular(8),
),
@ -454,6 +484,9 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
final oldIndex =
children.indexWhere((s) => s.uuid == data.data.space.uuid);
if (oldIndex == -1) {
return true;
}
if (oldIndex == index || oldIndex == index - 1) {
return false;
}
@ -481,7 +514,7 @@ class _CommunityStructureCanvasState extends State<CommunityStructureCanvas>
child: SizedBox(
width: context.screenWidth * 5,
height: context.screenHeight * 5,
child: Stack(children: treeWidgets),
child: Stack(clipBehavior: Clip.none, children: treeWidgets),
),
),
);

View File

@ -0,0 +1,58 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/services/reorder_spaces_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/constants/api_const.dart';
final class RemoteReorderSpacesService implements ReorderSpacesService {
RemoteReorderSpacesService(this._httpClient);
final HTTPService _httpClient;
@override
Future<void> reorderSpaces(ReorderSpacesParam param) async {
try {
await _httpClient.post(
path: await _makeUrl(param),
body: param.toJson(),
expectedResponseModel: (json) => json,
);
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
throw APIException(_getErrorMessageFromBody(message));
} catch (e) {
throw APIException(e.toString());
}
}
String _getErrorMessageFromBody(Map<String, dynamic>? body) {
if (body == null) return 'Failed to delete space';
final error = body['error'] as Map<String, dynamic>?;
final errorMessage = error?['message'] as String? ?? '';
return errorMessage;
}
Future<String> _makeUrl(ReorderSpacesParam param) async {
final projectUuid = await ProjectManager.getProjectUUID();
final communityUuid = param.communityUuid;
if (projectUuid == null || projectUuid.isEmpty) {
throw APIException('Project UUID is not set');
}
if (communityUuid.isEmpty) {
throw APIException('Community UUID is not set');
}
if (param.parentSpaceUuid.isEmpty) {
throw APIException('Parent Space UUID is not set');
}
return ApiEndpoints.reorderSpaces
.replaceAll('{projectUuid}', projectUuid)
.replaceAll('{communityUuid}', communityUuid)
.replaceAll('{parentSpaceUuid}', param.parentSpaceUuid);
}
}

View File

@ -0,0 +1,21 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
class ReorderSpacesParam extends Equatable {
const ReorderSpacesParam({
required this.communityUuid,
required this.parentSpaceUuid,
required this.spaces,
});
final String communityUuid;
final String parentSpaceUuid;
final List<SpaceModel> spaces;
@override
List<Object?> get props => [spaces, communityUuid, parentSpaceUuid];
Map<String, dynamic> toJson() => {
'spacesUuids': spaces.map((space) => space.uuid).toList(),
};
}

View File

@ -0,0 +1,5 @@
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
abstract interface class ReorderSpacesService {
Future<void> reorderSpaces(ReorderSpacesParam param);
}

View File

@ -0,0 +1,35 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/params/reorder_spaces_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/reorder_spaces/domain/services/reorder_spaces_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'reorder_spaces_event.dart';
part 'reorder_spaces_state.dart';
class ReorderSpacesBloc extends Bloc<ReorderSpacesEvent, ReorderSpacesState> {
ReorderSpacesBloc(
this._reorderSpacesService,
) : super(const ReorderSpacesInitial()) {
on<ReorderSpacesEvent>(_onReorderSpacesEvent);
}
final ReorderSpacesService _reorderSpacesService;
Future<void> _onReorderSpacesEvent(
ReorderSpacesEvent event,
Emitter<ReorderSpacesState> emit,
) async {
emit(const ReorderSpacesLoading());
try {
await _reorderSpacesService.reorderSpaces(event.param);
emit(const ReorderSpacesSuccess());
} on APIException catch (e) {
emit(ReorderSpacesFailure(e.message));
} catch (e) {
emit(ReorderSpacesFailure(e.toString()));
} finally {
emit(const ReorderSpacesInitial());
}
}
}

View File

@ -0,0 +1,10 @@
part of 'reorder_spaces_bloc.dart';
final class ReorderSpacesEvent extends Equatable {
const ReorderSpacesEvent(this.param);
final ReorderSpacesParam param;
@override
List<Object> get props => [param];
}

View File

@ -0,0 +1,29 @@
part of 'reorder_spaces_bloc.dart';
sealed class ReorderSpacesState extends Equatable {
const ReorderSpacesState();
@override
List<Object> get props => [];
}
final class ReorderSpacesInitial extends ReorderSpacesState {
const ReorderSpacesInitial();
}
final class ReorderSpacesLoading extends ReorderSpacesState {
const ReorderSpacesLoading();
}
final class ReorderSpacesSuccess extends ReorderSpacesState {
const ReorderSpacesSuccess();
}
final class ReorderSpacesFailure extends ReorderSpacesState {
const ReorderSpacesFailure(this.errorMessage);
final String errorMessage;
@override
List<Object> get props => [errorMessage];
}

View File

@ -41,6 +41,8 @@ abstract class ApiEndpoints {
'/projects/{projectId}/communities/{communityId}/spaces/{spaceId}';
static const String getSpaceHierarchy =
'/projects/{projectId}/communities/{communityId}/spaces';
static const String reorderSpaces =
'/projects/{projectUuid}/communities/{communityUuid}/spaces/{parentSpaceUuid}/spaces/order';
// Community Module
static const String createCommunity = '/projects/{projectId}/communities';