matched community and space models with API.

This commit is contained in:
Faris Armoush
2025-06-22 12:21:46 +03:00
parent 65ed94eb08
commit 8494f0a8f1
8 changed files with 62 additions and 82 deletions

View File

@ -2,12 +2,12 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/pages/space_management_v2/main_module/widgets/space_management_body.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/communities_pagination_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/data/services/debounced_communities_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/data/services/remote_communities_service.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/services/communities_service.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/services/api/http_service.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
@ -20,7 +20,9 @@ class SpaceManagementPage extends StatelessWidget {
providers: [
BlocProvider(
create: (context) => CommunitiesBloc(
communitiesService: _FakeCommunitiesService(),
communitiesService: DebouncedCommunitiesService(
RemoteCommunitiesService(HTTPService()),
),
)..add(const LoadCommunities(LoadCommunitiesParam())),
),
BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()),
@ -43,23 +45,3 @@ class SpaceManagementPage extends StatelessWidget {
);
}
}
class _FakeCommunitiesService extends CommunitiesService {
@override
Future<CommunitiesPaginationModel> getCommunity(LoadCommunitiesParam param) {
return Future.value(const CommunitiesPaginationModel(
communities: [
CommunityModel(
uuid: '1',
name: 'Community 1',
spaces: [],
),
],
page: 1,
size: 10,
hasNext: false,
totalItems: 2,
totalPages: 1,
));
}
}

View File

@ -4,57 +4,44 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/params/load_communities_param.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/services/communities_service.dart';
class DebouncedCommunitiesService implements CommunitiesService {
DebouncedCommunitiesService({
required CommunitiesService communitiesService,
this.debounceDuration = const Duration(milliseconds: 400),
}) : _communitiesService = communitiesService;
final class DebouncedCommunitiesService implements CommunitiesService {
DebouncedCommunitiesService(
this._decoratee, {
this.debounceDuration = const Duration(milliseconds: 500),
});
final CommunitiesService _communitiesService;
final CommunitiesService _decoratee;
final Duration debounceDuration;
Timer? _debounceTimer;
String _lastSearchQuery = '';
Completer<CommunitiesPaginationModel>? _completer;
@override
Future<CommunitiesPaginationModel> getCommunity(
LoadCommunitiesParam param,
) async {
if (param.search.isNotEmpty) {
return _getDebouncedCommunity(param);
}
return _communitiesService.getCommunity(param);
}
Future<CommunitiesPaginationModel> _getDebouncedCommunity(
LoadCommunitiesParam param,
) async {
final completer = Completer<CommunitiesPaginationModel>();
_debounceTimer?.cancel();
_lastSearchQuery = param.search;
if (_completer != null && !_completer!.isCompleted) {
_completer!.completeError(Exception('Request cancelled by newer request'));
}
_completer = Completer<CommunitiesPaginationModel>();
final currentCompleter = _completer!;
_debounceTimer = Timer(debounceDuration, () async {
try {
if (_lastSearchQuery == param.search) {
final result = await _communitiesService.getCommunity(param);
if (!completer.isCompleted) {
completer.complete(result);
}
} else {
if (!completer.isCompleted) {
completer.complete(const CommunitiesPaginationModel.empty());
}
final result = await _decoratee.getCommunity(param);
if (!currentCompleter.isCompleted) {
currentCompleter.complete(result);
}
} catch (error) {
if (!completer.isCompleted) {
completer.completeError(error);
if (!currentCompleter.isCompleted) {
currentCompleter.completeError(error);
}
}
});
return completer.future;
return currentCompleter.future;
}
}

View File

@ -15,10 +15,9 @@ class RemoteCommunitiesService implements CommunitiesService {
static const _defaultErrorMessage = 'Failed to load communities';
@override
Future<CommunitiesPaginationModel> getCommunity(LoadCommunitiesParam param) async {
final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null) throw APIException('Project UUID is not set');
Future<CommunitiesPaginationModel> getCommunity(
LoadCommunitiesParam param,
) async {
try {
final response = await _httpService.get(
path: await _makeUrl(),
@ -26,10 +25,12 @@ class RemoteCommunitiesService implements CommunitiesService {
'page': param.page,
'size': param.size,
'includeSpaces': param.includeSpaces,
if (param.search.isNotEmpty) 'search': param.search,
if (param.search.isNotEmpty && param.search != 'null')
'search': param.search,
},
expectedResponseModel: (json) {
return CommunitiesPaginationModel.fromJson(json as Map<String, dynamic>);
final data = json as Map<String, dynamic>;
return CommunitiesPaginationModel.fromJson(data);
},
);
@ -48,6 +49,9 @@ class RemoteCommunitiesService implements CommunitiesService {
Future<String> _makeUrl() async {
final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null) throw APIException('Project UUID is required');
return ApiEndpoints.getCommunityList.replaceAll('{projectId}', projectUuid);
return ApiEndpoints.getCommunityListv2.replaceAll(
'{projectId}',
projectUuid,
);
}
}

View File

@ -34,8 +34,8 @@ class CommunitiesPaginationModel extends Equatable {
page: json['page'] as int? ?? 1,
size: json['size'] as int? ?? 25,
hasNext: json['hasNext'] as bool? ?? false,
totalItems: json['totalItems'] as int? ?? 0,
totalPages: json['totalPages'] as int? ?? 0,
totalItems: json['totalItem'] as int? ?? 0,
totalPages: json['totalPage'] as int? ?? 0,
);
}

View File

@ -4,11 +4,19 @@ import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain
class CommunityModel extends Equatable {
final String uuid;
final String name;
final DateTime createdAt;
final DateTime updatedAt;
final String description;
final String externalId;
final List<SpaceModel> spaces;
const CommunityModel({
required this.uuid,
required this.name,
required this.createdAt,
required this.updatedAt,
required this.description,
required this.externalId,
required this.spaces,
});
@ -16,6 +24,10 @@ class CommunityModel extends Equatable {
return CommunityModel(
uuid: json['uuid'] as String,
name: json['name'] as String,
createdAt: DateTime.parse(json['createdAt'] as String),
updatedAt: DateTime.parse(json['updatedAt'] as String),
description: json['description'] as String,
externalId: json['externalId'] as String,
spaces: (json['spaces'] as List<dynamic>)
.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
.toList(),

View File

@ -1,43 +1,38 @@
import 'package:equatable/equatable.dart';
enum SpaceStatus {
active,
deleted,
parentDeleted;
static SpaceStatus getValueFromString(String value) => switch (value) {
'active' => active,
'deleted' => deleted,
'parentDeleted' => parentDeleted,
_ => active,
};
}
class SpaceModel extends Equatable {
final String uuid;
final DateTime createdAt;
final DateTime updatedAt;
final String spaceName;
final String icon;
final List<SpaceModel> children;
final SpaceStatus status;
final SpaceModel? parent;
const SpaceModel({
required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.spaceName,
required this.icon,
required this.children,
required this.status,
required this.parent,
});
factory SpaceModel.fromJson(Map<String, dynamic> json) {
return SpaceModel(
uuid: json['uuid'] as String,
createdAt: DateTime.parse(json['createdAt'] as String),
updatedAt: DateTime.parse(json['updatedAt'] as String),
spaceName: json['spaceName'] as String,
icon: json['icon'] as String,
children: (json['children'] as List<dynamic>?)
?.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
.toList() ??
[],
status: SpaceStatus.getValueFromString(json['status'] as String),
parent: json['parent'] != null
? SpaceModel.fromJson(json['parent'] as Map<String, dynamic>)
: null,
);
}

View File

@ -146,7 +146,6 @@ class _SpaceManagementCommunitiesTreeState
Widget _buildCommunityTile(BuildContext context, CommunityModel community) {
final spaces = community.spaces
.where((space) => space.status == SpaceStatus.active)
.map((space) => _buildSpaceTile(
space: space,
community: community,

View File

@ -46,6 +46,7 @@ abstract class ApiEndpoints {
// Community Module
static const String createCommunity = '/projects/{projectId}/communities';
static const String getCommunityList = '/projects/{projectId}/communities';
static const String getCommunityListv2 = '/projects/{projectId}/communities/v2';
static const String getCommunityById =
'/projects/{projectId}/communities/{communityId}';
static const String updateCommunity =