import 'package:dio/dio.dart'; import 'package:fintracker_api/fintracker_api.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/api/api_client.dart'; class TransactionsFilter { const TransactionsFilter({ this.q, this.from, this.to, this.accountId, this.categoryId, this.flowType, }); final String? q; final DateTime? from; final DateTime? to; final int? accountId; final int? categoryId; final FlowType? flowType; bool get isEmpty => q == null && from == null && to == null && accountId == null && categoryId == null && flowType == null; TransactionsFilter copyWith({ String? Function()? q, DateTime? Function()? from, DateTime? Function()? to, int? Function()? accountId, int? Function()? categoryId, FlowType? Function()? flowType, }) { return TransactionsFilter( q: q != null ? q() : this.q, from: from != null ? from() : this.from, to: to != null ? to() : this.to, accountId: accountId != null ? accountId() : this.accountId, categoryId: categoryId != null ? categoryId() : this.categoryId, flowType: flowType != null ? flowType() : this.flowType, ); } } class TransactionsState { const TransactionsState({ this.items = const [], this.page = 1, this.pageSize = 50, this.total = 0, this.loading = false, this.loadingMore = false, this.error, }); final List items; final int page; final int pageSize; final int total; final bool loading; final bool loadingMore; final Object? error; bool get hasMore => items.length < total; TransactionsState copyWith({ List? items, int? page, int? pageSize, int? total, bool? loading, bool? loadingMore, Object? error, bool clearError = false, }) { return TransactionsState( items: items ?? this.items, page: page ?? this.page, pageSize: pageSize ?? this.pageSize, total: total ?? this.total, loading: loading ?? this.loading, loadingMore: loadingMore ?? this.loadingMore, error: clearError ? null : (error ?? this.error), ); } } /// Filters + paginated results for Операции. `refresh()` restarts from page 1 /// (called on build and on every filter change); `loadMore()` appends the /// next page for infinite scroll / the "ещё" button. class TransactionsController extends Notifier { TransactionsFilter filter = const TransactionsFilter(); @override TransactionsState build() { Future.microtask(refresh); return const TransactionsState(loading: true); } Future setFilter(TransactionsFilter Function(TransactionsFilter) update) { filter = update(filter); return refresh(); } Future refresh() async { state = state.copyWith(loading: true, clearError: true); try { final r = await ref.read(apiProvider).getTransactionsApi().transactionsList( from: filter.from, to: filter.to, accountId: filter.accountId, categoryId: filter.categoryId, flowType: filter.flowType, q: (filter.q == null || filter.q!.isEmpty) ? null : filter.q, page: 1, pageSize: state.pageSize, ); final page = r.data!; state = TransactionsState( items: page.items, page: page.page, pageSize: page.pageSize, total: page.total, ); } on DioException catch (e) { state = state.copyWith(loading: false, error: e); } } Future loadMore() async { if (state.loading || state.loadingMore || !state.hasMore) return; state = state.copyWith(loadingMore: true, clearError: true); try { final next = state.page + 1; final r = await ref.read(apiProvider).getTransactionsApi().transactionsList( from: filter.from, to: filter.to, accountId: filter.accountId, categoryId: filter.categoryId, flowType: filter.flowType, q: (filter.q == null || filter.q!.isEmpty) ? null : filter.q, page: next, pageSize: state.pageSize, ); final page = r.data!; state = state.copyWith( items: [...state.items, ...page.items], page: page.page, total: page.total, loadingMore: false, ); } on DioException catch (e) { state = state.copyWith(loadingMore: false, error: e); } } } final transactionsControllerProvider = NotifierProvider(TransactionsController.new);