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'; /// What the События screen asks the ledger for. `instrumentId` has no control of its own — /// the instrument card already lists an instrument's own events — but it is carried here /// so navigating in with a preset instrument stays a one-line change. class EventsFilter { const EventsFilter({ this.q, this.from, this.to, this.accountId, this.instrumentId, this.kind, this.status, this.externalFlow, }); final String? q; final DateTime? from; final DateTime? to; final int? accountId; final int? instrumentId; final EventKind? kind; final EventStatus? status; final bool? externalFlow; EventsFilter copyWith({ String? Function()? q, DateTime? Function()? from, DateTime? Function()? to, int? Function()? accountId, int? Function()? instrumentId, EventKind? Function()? kind, EventStatus? Function()? status, bool? Function()? externalFlow, }) { return EventsFilter( q: q != null ? q() : this.q, from: from != null ? from() : this.from, to: to != null ? to() : this.to, accountId: accountId != null ? accountId() : this.accountId, instrumentId: instrumentId != null ? instrumentId() : this.instrumentId, kind: kind != null ? kind() : this.kind, status: status != null ? status() : this.status, externalFlow: externalFlow != null ? externalFlow() : this.externalFlow, ); } } class EventsState { const EventsState({ 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; EventsState copyWith({ List? items, int? page, int? pageSize, int? total, bool? loading, bool? loadingMore, Object? error, bool clearError = false, }) { return EventsState( 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 События, the same shape Операции uses: `refresh()` /// restarts from page 1 (on build and on every filter change), `loadMore()` appends the /// next page for infinite scroll. class EventsController extends Notifier { EventsFilter filter = const EventsFilter(); @override EventsState build() { Future.microtask(refresh); return const EventsState(loading: true); } Future setFilter(EventsFilter Function(EventsFilter) update) { filter = update(filter); return refresh(); } Future _fetch(int page) async { final r = await ref .read(apiProvider) .getEventsApi() .eventsList( from: filter.from, to: filter.to, accountId: filter.accountId, instrumentId: filter.instrumentId, kind: filter.kind, status: filter.status, externalFlow: filter.externalFlow, q: (filter.q == null || filter.q!.isEmpty) ? null : filter.q, page: page, pageSize: state.pageSize, ); return r.data!; } Future refresh() async { state = state.copyWith(loading: true, clearError: true); try { final page = await _fetch(1); state = EventsState( 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 page = await _fetch(state.page + 1); 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 eventsControllerProvider = NotifierProvider(EventsController.new);