made communities paginatable.

This commit is contained in:
Faris Armoush
2025-06-22 11:11:25 +03:00
parent 2f233db332
commit 51c088d998
8 changed files with 272 additions and 51 deletions

View File

@ -2,8 +2,8 @@ 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/domain/models/community_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/domain/models/space_model.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/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';
@ -46,37 +46,20 @@ class SpaceManagementPage extends StatelessWidget {
class _FakeCommunitiesService extends CommunitiesService { class _FakeCommunitiesService extends CommunitiesService {
@override @override
Future<List<CommunityModel>> getCommunity(LoadCommunitiesParam param) async { Future<CommunitiesPaginationModel> getCommunity(LoadCommunitiesParam param) {
return Future.delayed( return Future.value(const CommunitiesPaginationModel(
const Duration(seconds: 1), communities: [
() => [ CommunityModel(
const CommunityModel(
uuid: '1', uuid: '1',
name: 'Community 1', name: 'Community 1',
spaces: [
SpaceModel(
uuid: '3',
spaceName: 'Space 1',
icon: 'assets/icons/space.png',
children: [
SpaceModel(
uuid: '4',
spaceName: 'Space 2',
icon: 'assets/icons/space.png',
children: [],
status: SpaceStatus.active,
),
],
status: SpaceStatus.active,
),
],
),
const CommunityModel(
uuid: '2',
name: 'Community 1',
spaces: [], spaces: [],
), ),
], ],
); page: 1,
size: 10,
hasNext: false,
totalItems: 2,
totalPages: 1,
));
} }
} }

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart'; import 'package:syncrow_web/pages/common/bloc/project_manager.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/domain/models/communities_pagination_model.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/domain/services/communities_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart'; import 'package:syncrow_web/services/api/api_exception.dart';
@ -15,28 +15,25 @@ class RemoteCommunitiesService implements CommunitiesService {
static const _defaultErrorMessage = 'Failed to load communities'; static const _defaultErrorMessage = 'Failed to load communities';
@override @override
Future<List<CommunityModel>> getCommunity(LoadCommunitiesParam param) async { Future<CommunitiesPaginationModel> getCommunity(LoadCommunitiesParam param) async {
final projectUuid = await ProjectManager.getProjectUUID(); final projectUuid = await ProjectManager.getProjectUUID();
if (projectUuid == null) throw APIException('Project UUID is not set'); if (projectUuid == null) throw APIException('Project UUID is not set');
try { try {
final allCommunities = <CommunityModel>[]; final response = await _httpService.get(
await _httpService.get(
path: await _makeUrl(), path: await _makeUrl(),
queryParameters: {
'page': param.page,
'size': param.size,
'includeSpaces': param.includeSpaces,
if (param.search.isNotEmpty) 'search': param.search,
},
expectedResponseModel: (json) { expectedResponseModel: (json) {
final response = json as Map<String, dynamic>; return CommunitiesPaginationModel.fromJson(json as Map<String, dynamic>);
final jsonData = response['data'] as List<dynamic>? ?? [];
return jsonData
.map(
(jsonItem) => CommunityModel.fromJson(
jsonItem as Map<String, dynamic>,
),
)
.toList();
}, },
); );
return allCommunities; return response;
} on DioException catch (e) { } on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?; final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?; final error = message?['error'] as Map<String, dynamic>?;

View File

@ -0,0 +1,69 @@
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
class CommunitiesPaginationModel extends Equatable {
const CommunitiesPaginationModel({
required this.communities,
required this.page,
required this.size,
required this.hasNext,
required this.totalItems,
required this.totalPages,
});
final List<CommunityModel> communities;
final int page;
final int size;
final bool hasNext;
final int totalItems;
final int totalPages;
const CommunitiesPaginationModel.empty()
: communities = const [],
page = 1,
size = 25,
hasNext = false,
totalItems = 0,
totalPages = 0;
factory CommunitiesPaginationModel.fromJson(Map<String, dynamic> json) {
return CommunitiesPaginationModel(
communities: (json['data'] as List<dynamic>? ?? [])
.map((e) => CommunityModel.fromJson(e as Map<String, dynamic>))
.toList(),
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,
);
}
CommunitiesPaginationModel copyWith({
List<CommunityModel>? communities,
int? page,
int? size,
bool? hasNext,
int? totalItems,
int? totalPages,
}) {
return CommunitiesPaginationModel(
communities: communities ?? this.communities,
page: page ?? this.page,
size: size ?? this.size,
hasNext: hasNext ?? this.hasNext,
totalItems: totalItems ?? this.totalItems,
totalPages: totalPages ?? this.totalPages,
);
}
@override
List<Object?> get props => [
communities,
page,
size,
hasNext,
totalItems,
totalPages,
];
}

View File

@ -1,3 +1,32 @@
class LoadCommunitiesParam { import 'package:equatable/equatable.dart';
const LoadCommunitiesParam();
class LoadCommunitiesParam extends Equatable {
const LoadCommunitiesParam({
this.page = 1,
this.size = 25,
this.search = '',
this.includeSpaces = true,
});
final int page;
final int size;
final String search;
final bool includeSpaces;
LoadCommunitiesParam copyWith({
int? page,
int? size,
String? search,
bool? includeSpaces,
}) {
return LoadCommunitiesParam(
page: page ?? this.page,
size: size ?? this.size,
search: search ?? this.search,
includeSpaces: includeSpaces ?? this.includeSpaces,
);
}
@override
List<Object?> get props => [page, size, search, includeSpaces];
} }

View File

@ -1,6 +1,6 @@
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/domain/models/communities_pagination_model.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';
abstract class CommunitiesService { abstract class CommunitiesService {
Future<List<CommunityModel>> getCommunity(LoadCommunitiesParam param); Future<CommunitiesPaginationModel> getCommunity(LoadCommunitiesParam param);
} }

View File

@ -14,6 +14,8 @@ class CommunitiesBloc extends Bloc<CommunitiesEvent, CommunitiesState> {
}) : _communitiesService = communitiesService, }) : _communitiesService = communitiesService,
super(const CommunitiesState()) { super(const CommunitiesState()) {
on<LoadCommunities>(_onLoadCommunities); on<LoadCommunities>(_onLoadCommunities);
on<LoadMoreCommunities>(_onLoadMoreCommunities);
on<SearchCommunities>(_onSearchCommunities);
} }
final CommunitiesService _communitiesService; final CommunitiesService _communitiesService;
@ -23,24 +25,113 @@ class CommunitiesBloc extends Bloc<CommunitiesEvent, CommunitiesState> {
Emitter<CommunitiesState> emit, Emitter<CommunitiesState> emit,
) async { ) async {
try { try {
emit(const CommunitiesState(status: CommunitiesStatus.loading)); emit(state.copyWith(status: CommunitiesStatus.loading));
final communities = await _communitiesService.getCommunity(event.param);
final paginationResponse = await _communitiesService.getCommunity(event.param);
emit( emit(
CommunitiesState( CommunitiesState(
status: CommunitiesStatus.success, status: CommunitiesStatus.success,
communities: communities, communities: paginationResponse.communities,
hasNext: paginationResponse.hasNext,
currentPage: paginationResponse.page,
searchQuery: event.param.search,
), ),
); );
} on APIException catch (e) { } on APIException catch (e) {
emit( emit(
CommunitiesState( state.copyWith(
status: CommunitiesStatus.failure, status: CommunitiesStatus.failure,
errorMessage: e.message, errorMessage: e.message,
), ),
); );
} catch (e) { } catch (e) {
emit(
state.copyWith(
status: CommunitiesStatus.failure,
errorMessage: e.toString(),
),
);
}
}
Future<void> _onLoadMoreCommunities(
LoadMoreCommunities event,
Emitter<CommunitiesState> emit,
) async {
if (!state.hasNext || state.isLoadingMore) return;
try {
emit(state.copyWith(isLoadingMore: true));
final param = LoadCommunitiesParam(
page: state.currentPage + 1,
search: state.searchQuery,
);
final paginationResponse = await _communitiesService.getCommunity(param);
final updatedCommunities = List<CommunityModel>.from(state.communities)
..addAll(paginationResponse.communities);
emit(
state.copyWith(
communities: updatedCommunities,
hasNext: paginationResponse.hasNext,
currentPage: paginationResponse.page,
isLoadingMore: false,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
isLoadingMore: false,
errorMessage: e.message,
),
);
} catch (e) {
emit(
state.copyWith(
isLoadingMore: false,
errorMessage: e.toString(),
),
);
}
}
Future<void> _onSearchCommunities(
SearchCommunities event,
Emitter<CommunitiesState> emit,
) async {
try {
emit(state.copyWith(status: CommunitiesStatus.loading));
final param = LoadCommunitiesParam(
page: 1,
search: event.searchQuery,
);
final paginationResponse = await _communitiesService.getCommunity(param);
emit( emit(
CommunitiesState( CommunitiesState(
status: CommunitiesStatus.success,
communities: paginationResponse.communities,
hasNext: paginationResponse.hasNext,
currentPage: paginationResponse.page,
searchQuery: event.searchQuery,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
status: CommunitiesStatus.failure,
errorMessage: e.message,
),
);
} catch (e) {
emit(
state.copyWith(
status: CommunitiesStatus.failure, status: CommunitiesStatus.failure,
errorMessage: e.toString(), errorMessage: e.toString(),
), ),

View File

@ -15,3 +15,19 @@ class LoadCommunities extends CommunitiesEvent {
@override @override
List<Object?> get props => [param]; List<Object?> get props => [param];
} }
class LoadMoreCommunities extends CommunitiesEvent {
const LoadMoreCommunities();
@override
List<Object?> get props => [];
}
class SearchCommunities extends CommunitiesEvent {
const SearchCommunities(this.searchQuery);
final String searchQuery;
@override
List<Object?> get props => [searchQuery];
}

View File

@ -7,12 +7,48 @@ final class CommunitiesState extends Equatable {
this.status = CommunitiesStatus.initial, this.status = CommunitiesStatus.initial,
this.communities = const [], this.communities = const [],
this.errorMessage, this.errorMessage,
this.isLoadingMore = false,
this.hasNext = false,
this.currentPage = 1,
this.searchQuery = '',
}); });
final CommunitiesStatus status; final CommunitiesStatus status;
final List<CommunityModel> communities; final List<CommunityModel> communities;
final String? errorMessage; final String? errorMessage;
final bool isLoadingMore;
final bool hasNext;
final int currentPage;
final String searchQuery;
CommunitiesState copyWith({
CommunitiesStatus? status,
List<CommunityModel>? communities,
String? errorMessage,
bool? isLoadingMore,
bool? hasNext,
int? currentPage,
String? searchQuery,
}) {
return CommunitiesState(
status: status ?? this.status,
communities: communities ?? this.communities,
errorMessage: errorMessage ?? this.errorMessage,
isLoadingMore: isLoadingMore ?? this.isLoadingMore,
hasNext: hasNext ?? this.hasNext,
currentPage: currentPage ?? this.currentPage,
searchQuery: searchQuery ?? this.searchQuery,
);
}
@override @override
List<Object?> get props => [status, communities, errorMessage]; List<Object?> get props => [
status,
communities,
errorMessage,
isLoadingMore,
hasNext,
currentPage,
searchQuery,
];
} }