mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-08-24 20:12:27 +00:00
Feature/reorder spaces api integration (#362)
<!-- Thanks for contributing! Provide a description of your changes below and a general summary in the title Please look at the following checklist to ensure that your PR can be accepted quickly: --> ## Description Integrated reordering spaces with the API. Fixed drop target bug, where the canvas wouldn't show the first drop target in the tree. ## Type of Change <!--- Put an `x` in all the boxes that apply: --> - [ x ] ✨ New feature (non-breaking change which adds functionality) - [ x ] 🛠️ Bug fix (non-breaking change which fixes an issue) - [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) - [ ] 🧹 Code refactor - [ ] ✅ Build configuration change - [ ] 📝 Documentation - [ ] 🗑️ Chore
This commit is contained in:
@ -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),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
};
|
||||
}
|
@ -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);
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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];
|
||||
}
|
@ -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];
|
||||
}
|
@ -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';
|
||||
|
Reference in New Issue
Block a user