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:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.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/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/data/services/debounced_communities_service.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/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/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/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/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/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart'; import 'package:syncrow_web/web_layout/web_scaffold.dart';
@ -20,7 +20,9 @@ class SpaceManagementPage extends StatelessWidget {
providers: [ providers: [
BlocProvider( BlocProvider(
create: (context) => CommunitiesBloc( create: (context) => CommunitiesBloc(
communitiesService: _FakeCommunitiesService(), communitiesService: DebouncedCommunitiesService(
RemoteCommunitiesService(HTTPService()),
),
)..add(const LoadCommunities(LoadCommunitiesParam())), )..add(const LoadCommunities(LoadCommunitiesParam())),
), ),
BlocProvider(create: (context) => CommunitiesTreeSelectionBloc()), 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/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/domain/services/communities_service.dart';
class DebouncedCommunitiesService implements CommunitiesService { final class DebouncedCommunitiesService implements CommunitiesService {
DebouncedCommunitiesService({ DebouncedCommunitiesService(
required CommunitiesService communitiesService, this._decoratee, {
this.debounceDuration = const Duration(milliseconds: 400), this.debounceDuration = const Duration(milliseconds: 500),
}) : _communitiesService = communitiesService; });
final CommunitiesService _communitiesService; final CommunitiesService _decoratee;
final Duration debounceDuration; final Duration debounceDuration;
Timer? _debounceTimer; Timer? _debounceTimer;
String _lastSearchQuery = ''; Completer<CommunitiesPaginationModel>? _completer;
@override @override
Future<CommunitiesPaginationModel> getCommunity( Future<CommunitiesPaginationModel> getCommunity(
LoadCommunitiesParam param, LoadCommunitiesParam param,
) async { ) async {
if (param.search.isNotEmpty) {
return _getDebouncedCommunity(param);
}
return _communitiesService.getCommunity(param);
}
Future<CommunitiesPaginationModel> _getDebouncedCommunity(
LoadCommunitiesParam param,
) async {
final completer = Completer<CommunitiesPaginationModel>();
_debounceTimer?.cancel(); _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 { _debounceTimer = Timer(debounceDuration, () async {
try { try {
if (_lastSearchQuery == param.search) { final result = await _decoratee.getCommunity(param);
final result = await _communitiesService.getCommunity(param); if (!currentCompleter.isCompleted) {
if (!completer.isCompleted) { currentCompleter.complete(result);
completer.complete(result);
}
} else {
if (!completer.isCompleted) {
completer.complete(const CommunitiesPaginationModel.empty());
}
} }
} catch (error) { } catch (error) {
if (!completer.isCompleted) { if (!currentCompleter.isCompleted) {
completer.completeError(error); 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'; static const _defaultErrorMessage = 'Failed to load communities';
@override @override
Future<CommunitiesPaginationModel> getCommunity(LoadCommunitiesParam param) async { Future<CommunitiesPaginationModel> getCommunity(
final projectUuid = await ProjectManager.getProjectUUID(); LoadCommunitiesParam param,
if (projectUuid == null) throw APIException('Project UUID is not set'); ) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
path: await _makeUrl(), path: await _makeUrl(),
@ -26,10 +25,12 @@ class RemoteCommunitiesService implements CommunitiesService {
'page': param.page, 'page': param.page,
'size': param.size, 'size': param.size,
'includeSpaces': param.includeSpaces, 'includeSpaces': param.includeSpaces,
if (param.search.isNotEmpty) 'search': param.search, if (param.search.isNotEmpty && param.search != 'null')
'search': param.search,
}, },
expectedResponseModel: (json) { 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 { Future<String> _makeUrl() async {
final projectUuid = await ProjectManager.getProjectUUID(); final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null) throw APIException('Project UUID is required'); 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, page: json['page'] as int? ?? 1,
size: json['size'] as int? ?? 25, size: json['size'] as int? ?? 25,
hasNext: json['hasNext'] as bool? ?? false, hasNext: json['hasNext'] as bool? ?? false,
totalItems: json['totalItems'] as int? ?? 0, totalItems: json['totalItem'] as int? ?? 0,
totalPages: json['totalPages'] 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 { class CommunityModel extends Equatable {
final String uuid; final String uuid;
final String name; final String name;
final DateTime createdAt;
final DateTime updatedAt;
final String description;
final String externalId;
final List<SpaceModel> spaces; final List<SpaceModel> spaces;
const CommunityModel({ const CommunityModel({
required this.uuid, required this.uuid,
required this.name, required this.name,
required this.createdAt,
required this.updatedAt,
required this.description,
required this.externalId,
required this.spaces, required this.spaces,
}); });
@ -16,6 +24,10 @@ class CommunityModel extends Equatable {
return CommunityModel( return CommunityModel(
uuid: json['uuid'] as String, uuid: json['uuid'] as String,
name: json['name'] 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>) spaces: (json['spaces'] as List<dynamic>)
.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>)) .map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),

View File

@ -1,43 +1,38 @@
import 'package:equatable/equatable.dart'; 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 { class SpaceModel extends Equatable {
final String uuid; final String uuid;
final DateTime createdAt;
final DateTime updatedAt;
final String spaceName; final String spaceName;
final String icon; final String icon;
final List<SpaceModel> children; final List<SpaceModel> children;
final SpaceStatus status; final SpaceModel? parent;
const SpaceModel({ const SpaceModel({
required this.uuid, required this.uuid,
required this.createdAt,
required this.updatedAt,
required this.spaceName, required this.spaceName,
required this.icon, required this.icon,
required this.children, required this.children,
required this.status, required this.parent,
}); });
factory SpaceModel.fromJson(Map<String, dynamic> json) { factory SpaceModel.fromJson(Map<String, dynamic> json) {
return SpaceModel( return SpaceModel(
uuid: json['uuid'] as String, uuid: json['uuid'] as String,
createdAt: DateTime.parse(json['createdAt'] as String),
updatedAt: DateTime.parse(json['updatedAt'] as String),
spaceName: json['spaceName'] as String, spaceName: json['spaceName'] as String,
icon: json['icon'] as String, icon: json['icon'] as String,
children: (json['children'] as List<dynamic>?) children: (json['children'] as List<dynamic>?)
?.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>)) ?.map((e) => SpaceModel.fromJson(e as Map<String, dynamic>))
.toList() ?? .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) { Widget _buildCommunityTile(BuildContext context, CommunityModel community) {
final spaces = community.spaces final spaces = community.spaces
.where((space) => space.status == SpaceStatus.active)
.map((space) => _buildSpaceTile( .map((space) => _buildSpaceTile(
space: space, space: space,
community: community, community: community,

View File

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