import 'dart:async'; import 'dart:developer'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_state.dart'; class DocumentsCubit extends Cubit with HydratedMixin { final PaperlessDocumentsApi _api; final SavedViewRepository _savedViewRepository; DocumentsCubit(this._api, this._savedViewRepository) : super(const DocumentsState()) { hydrate(); } Future bulkRemove(List documents) async { log("[DocumentsCubit] bulkRemove"); await _api.bulkAction( BulkDeleteAction(documents.map((doc) => doc.id)), ); await reload(); } Future bulkEditTags( Iterable documents, { Iterable addTags = const [], Iterable removeTags = const [], }) async { log("[DocumentsCubit] bulkEditTags"); await _api.bulkAction(BulkModifyTagsAction( documents.map((doc) => doc.id), addTags: addTags, removeTags: removeTags, )); await reload(); } Future update( DocumentModel document, [ bool updateRemote = true, ]) async { log("[DocumentsCubit] update"); if (updateRemote) { await _api.update(document); } await reload(); } Future load() async { log("[DocumentsCubit] load"); emit(state.copyWith(isLoading: true)); try { final result = await _api.findAll(state.filter); emit(state.copyWith( isLoading: false, hasLoaded: true, value: [...state.value, result], )); } finally { emit(state.copyWith(isLoading: false)); } } Future reload() async { log("[DocumentsCubit] reload"); emit(state.copyWith(isLoading: true)); try { final filter = state.filter.copyWith(page: 1); final result = await _api.findAll(filter); emit(state.copyWith( hasLoaded: true, value: [result], isLoading: false, filter: filter, )); } finally { emit(state.copyWith(isLoading: false)); } } Future _bulkReloadDocuments() async { emit(state.copyWith(isLoading: true)); try { final result = await _api.findAll( state.filter.copyWith( page: 1, pageSize: state.documents.length, ), ); emit(DocumentsState( hasLoaded: true, value: [result], filter: state.filter, isLoading: false, )); } finally { emit(state.copyWith(isLoading: false)); } } Future loadMore() async { log("[DocumentsCubit] loadMore"); if (state.isLastPageLoaded) { return; } emit(state.copyWith(isLoading: true)); final newFilter = state.filter.copyWith(page: state.filter.page + 1); try { final result = await _api.findAll(newFilter); emit( DocumentsState( hasLoaded: true, value: [...state.value, result], filter: newFilter, isLoading: false, ), ); } finally { emit(state.copyWith(isLoading: false)); } } /// /// Updates document filter and automatically reloads documents. Always resets page to 1. /// Use [loadMore] to load more data. Future updateFilter({ final DocumentFilter filter = DocumentFilter.initial, }) async { log("[DocumentsCubit] updateFilter"); try { emit(state.copyWith(isLoading: true)); final result = await _api.findAll(filter.copyWith(page: 1)); emit( DocumentsState( filter: filter, value: [result], hasLoaded: true, isLoading: false, ), ); } finally { emit(state.copyWith(isLoading: false)); } } Future resetFilter() { log("[DocumentsCubit] resetFilter"); final filter = DocumentFilter.initial.copyWith( sortField: state.filter.sortField, sortOrder: state.filter.sortOrder, ); return updateFilter(filter: filter); } /// /// Convenience method which allows to directly use [DocumentFilter.copyWith] on the current filter. /// Future updateCurrentFilter( final DocumentFilter Function(DocumentFilter) transformFn, ) async => updateFilter(filter: transformFn(state.filter)); void toggleDocumentSelection(DocumentModel model) { log("[DocumentsCubit] toggleSelection"); if (state.selectedIds.contains(model.id)) { emit( state.copyWith( selection: state.selection .where((element) => element.id != model.id) .toList(), ), ); } else { emit( state.copyWith(selection: [...state.selection, model]), ); } } void resetSelection() { log("[DocumentsCubit] resetSelection"); emit(state.copyWith(selection: [])); } @override void onChange(Change change) { super.onChange(change); log("[DocumentsCubit] state changed: ${change.currentState.selection.map((e) => e.id).join(",")} to ${change.nextState.selection.map((e) => e.id).join(",")}"); } void reset() { log("[DocumentsCubit] reset"); emit(const DocumentsState()); } Future selectView(int id) async { emit(state.copyWith(isLoading: true)); try { final filter = _savedViewRepository.current?.values[id]?.toDocumentFilter(); if (filter == null) { return; } final results = await _api.findAll(filter.copyWith(page: 1)); emit( DocumentsState( filter: filter, hasLoaded: true, isLoading: false, selectedSavedViewId: id, value: [results], ), ); } finally { emit(state.copyWith(isLoading: false)); } } Future> autocomplete(String query) async { final res = await _api.autocomplete(query); return res; } void unselectView() { emit(state.copyWith(selectedSavedViewId: null)); } @override DocumentsState? fromJson(Map json) { return DocumentsState.fromJson(json); } @override Map? toJson(DocumentsState state) { return state.toJson(); } }