200 lines
6.0 KiB
Dart
200 lines
6.0 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:real_estate_mobile/features/agents/data/agents_repository.dart';
|
|
import 'package:real_estate_mobile/features/agents/data/models/agent_profile.dart';
|
|
import 'package:real_estate_mobile/features/agents/data/models/agent_type.dart';
|
|
import 'package:real_estate_mobile/features/agents/data/models/filterable_field.dart';
|
|
import 'package:real_estate_mobile/features/agents/presentation/providers/agents_provider.dart';
|
|
|
|
class SearchAgentsState {
|
|
final List<AgentProfile> agents;
|
|
final List<AgentType> agentTypes;
|
|
final List<FilterableField> filterFields;
|
|
final bool isLoading;
|
|
final bool isLoadingMore;
|
|
final String? error;
|
|
final String searchQuery;
|
|
final String? selectedAgentTypeId;
|
|
final int currentPage;
|
|
final int totalPages;
|
|
final Map<String, List<String>> activeFilters;
|
|
|
|
const SearchAgentsState({
|
|
this.agents = const [],
|
|
this.agentTypes = const [],
|
|
this.filterFields = const [],
|
|
this.isLoading = false,
|
|
this.isLoadingMore = false,
|
|
this.error,
|
|
this.searchQuery = '',
|
|
this.selectedAgentTypeId,
|
|
this.currentPage = 1,
|
|
this.totalPages = 1,
|
|
this.activeFilters = const {},
|
|
});
|
|
|
|
bool get hasMore => currentPage < totalPages;
|
|
|
|
SearchAgentsState copyWith({
|
|
List<AgentProfile>? agents,
|
|
List<AgentType>? agentTypes,
|
|
List<FilterableField>? filterFields,
|
|
bool? isLoading,
|
|
bool? isLoadingMore,
|
|
String? error,
|
|
String? searchQuery,
|
|
String? selectedAgentTypeId,
|
|
bool clearAgentTypeId = false,
|
|
int? currentPage,
|
|
int? totalPages,
|
|
Map<String, List<String>>? activeFilters,
|
|
}) {
|
|
return SearchAgentsState(
|
|
agents: agents ?? this.agents,
|
|
agentTypes: agentTypes ?? this.agentTypes,
|
|
filterFields: filterFields ?? this.filterFields,
|
|
isLoading: isLoading ?? this.isLoading,
|
|
isLoadingMore: isLoadingMore ?? this.isLoadingMore,
|
|
error: error,
|
|
searchQuery: searchQuery ?? this.searchQuery,
|
|
selectedAgentTypeId: clearAgentTypeId
|
|
? null
|
|
: (selectedAgentTypeId ?? this.selectedAgentTypeId),
|
|
currentPage: currentPage ?? this.currentPage,
|
|
totalPages: totalPages ?? this.totalPages,
|
|
activeFilters: activeFilters ?? this.activeFilters,
|
|
);
|
|
}
|
|
}
|
|
|
|
class SearchAgentsNotifier extends StateNotifier<SearchAgentsState> {
|
|
final AgentsRepository _repository;
|
|
|
|
SearchAgentsNotifier(this._repository)
|
|
: super(const SearchAgentsState(isLoading: true)) {
|
|
_init();
|
|
}
|
|
|
|
Future<void> _init() async {
|
|
try {
|
|
final results = await Future.wait([
|
|
_repository.getAgentTypes(),
|
|
_repository.getFilterableFields(agentTypeId: state.selectedAgentTypeId),
|
|
]);
|
|
if (!mounted) return;
|
|
state = state.copyWith(
|
|
agentTypes: results[0] as List<AgentType>,
|
|
filterFields: results[1] as List<FilterableField>,
|
|
);
|
|
await _fetchAgents(page: 1);
|
|
} catch (e) {
|
|
if (!mounted) return;
|
|
state = state.copyWith(isLoading: false, error: e.toString());
|
|
}
|
|
}
|
|
|
|
/// Re-fetch filterable fields scoped to the currently selected agent type.
|
|
/// Called whenever the user switches agent types so irrelevant filters disappear.
|
|
Future<void> _refreshFilterFields(String? agentTypeId) async {
|
|
try {
|
|
final fields =
|
|
await _repository.getFilterableFields(agentTypeId: agentTypeId);
|
|
if (!mounted) return;
|
|
// Drop any active filter slugs that no longer exist in the new list.
|
|
final validSlugs = fields.map((f) => f.slug).toSet();
|
|
final culled = <String, List<String>>{};
|
|
state.activeFilters.forEach((k, v) {
|
|
if (validSlugs.contains(k)) culled[k] = v;
|
|
});
|
|
state = state.copyWith(
|
|
filterFields: fields,
|
|
activeFilters: culled,
|
|
);
|
|
} catch (_) {
|
|
// Keep existing fields if the refresh fails; user can retry.
|
|
}
|
|
}
|
|
|
|
Future<void> _fetchAgents({required int page, bool append = false}) async {
|
|
try {
|
|
final response = await _repository.searchAgents(
|
|
search: state.searchQuery.isNotEmpty ? state.searchQuery : null,
|
|
agentTypeId: state.selectedAgentTypeId,
|
|
page: page,
|
|
limit: 10,
|
|
filters: state.activeFilters.isNotEmpty ? state.activeFilters : null,
|
|
);
|
|
if (!mounted) return;
|
|
state = state.copyWith(
|
|
agents: append ? [...state.agents, ...response.data] : response.data,
|
|
currentPage: response.page,
|
|
totalPages: response.totalPages,
|
|
isLoading: false,
|
|
isLoadingMore: false,
|
|
);
|
|
} catch (e) {
|
|
if (!mounted) return;
|
|
state = state.copyWith(
|
|
isLoading: false,
|
|
isLoadingMore: false,
|
|
error: e.toString(),
|
|
);
|
|
}
|
|
}
|
|
|
|
void search(String query) {
|
|
state = state.copyWith(
|
|
searchQuery: query,
|
|
isLoading: true,
|
|
currentPage: 1,
|
|
);
|
|
_fetchAgents(page: 1);
|
|
}
|
|
|
|
void filterByAgentType(String? agentTypeId) {
|
|
state = state.copyWith(
|
|
selectedAgentTypeId: agentTypeId,
|
|
clearAgentTypeId: agentTypeId == null,
|
|
isLoading: true,
|
|
currentPage: 1,
|
|
);
|
|
_refreshFilterFields(agentTypeId);
|
|
_fetchAgents(page: 1);
|
|
}
|
|
|
|
void applyFilters(Map<String, List<String>> filters) {
|
|
state = state.copyWith(
|
|
activeFilters: filters,
|
|
isLoading: true,
|
|
currentPage: 1,
|
|
);
|
|
_fetchAgents(page: 1);
|
|
}
|
|
|
|
void clearFilters() {
|
|
state = state.copyWith(
|
|
activeFilters: const {},
|
|
isLoading: true,
|
|
currentPage: 1,
|
|
);
|
|
_fetchAgents(page: 1);
|
|
}
|
|
|
|
void loadMore() {
|
|
if (state.isLoadingMore || !state.hasMore) return;
|
|
state = state.copyWith(isLoadingMore: true);
|
|
_fetchAgents(page: state.currentPage + 1, append: true);
|
|
}
|
|
|
|
Future<void> refresh() async {
|
|
state = state.copyWith(isLoading: true, currentPage: 1);
|
|
await _fetchAgents(page: 1);
|
|
}
|
|
}
|
|
|
|
final searchAgentsProvider =
|
|
StateNotifierProvider.autoDispose<SearchAgentsNotifier, SearchAgentsState>(
|
|
(ref) {
|
|
final repository = ref.watch(agentsRepositoryProvider);
|
|
return SearchAgentsNotifier(repository);
|
|
});
|