mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 23:15:43 -06:00
Started removing tight coupling
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/bulk_edit.model.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/paged_search_result.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
|
||||
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
|
||||
@singleton
|
||||
class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
@@ -17,45 +15,20 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
|
||||
DocumentsCubit(this.documentRepository) : super(DocumentsState.initial);
|
||||
|
||||
Future<void> addDocument(
|
||||
Uint8List bytes,
|
||||
String fileName, {
|
||||
required String title,
|
||||
required void Function(DocumentModel document) onConsumptionFinished,
|
||||
int? documentType,
|
||||
int? correspondent,
|
||||
Iterable<int> tags = const [],
|
||||
DateTime? createdAt,
|
||||
}) async {
|
||||
await documentRepository.create(
|
||||
bytes,
|
||||
fileName,
|
||||
title: title,
|
||||
documentType: documentType,
|
||||
correspondent: correspondent,
|
||||
tags: tags,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
|
||||
documentRepository
|
||||
.waitForConsumptionFinished(fileName, title)
|
||||
.then((value) => onConsumptionFinished(value));
|
||||
}
|
||||
|
||||
Future<void> removeDocument(DocumentModel document) async {
|
||||
Future<void> remove(DocumentModel document) async {
|
||||
await documentRepository.delete(document);
|
||||
return await reloadDocuments();
|
||||
await reload();
|
||||
}
|
||||
|
||||
Future<void> bulkRemoveDocuments(List<DocumentModel> documents) async {
|
||||
Future<void> bulkRemove(List<DocumentModel> documents) async {
|
||||
await documentRepository.bulkAction(
|
||||
BulkDeleteAction(documents.map((doc) => doc.id)),
|
||||
);
|
||||
await reloadDocuments();
|
||||
await reload();
|
||||
}
|
||||
|
||||
Future<void> bulkEditTags(
|
||||
List<DocumentModel> documents, {
|
||||
Iterable<DocumentModel> documents, {
|
||||
Iterable<int> addTags = const [],
|
||||
Iterable<int> removeTags = const [],
|
||||
}) async {
|
||||
@@ -64,15 +37,15 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
addTags: addTags,
|
||||
removeTags: removeTags,
|
||||
));
|
||||
await reloadDocuments();
|
||||
await reload();
|
||||
}
|
||||
|
||||
Future<void> updateDocument(DocumentModel document) async {
|
||||
Future<void> update(DocumentModel document) async {
|
||||
await documentRepository.update(document);
|
||||
await reloadDocuments();
|
||||
await reload();
|
||||
}
|
||||
|
||||
Future<void> loadDocuments() async {
|
||||
Future<void> load() async {
|
||||
final result = await documentRepository.find(state.filter);
|
||||
emit(DocumentsState(
|
||||
isLoaded: true,
|
||||
@@ -81,7 +54,7 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
));
|
||||
}
|
||||
|
||||
Future<void> reloadDocuments() async {
|
||||
Future<void> reload() async {
|
||||
if (state.currentPageNumber >= 5) {
|
||||
return _bulkReloadDocuments();
|
||||
}
|
||||
@@ -113,7 +86,7 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
Future<void> assignAsn(DocumentModel document) async {
|
||||
if (document.archiveSerialNumber == null) {
|
||||
final int asn = await documentRepository.findNextAsn();
|
||||
updateDocument(document.copyWith(archiveSerialNumber: asn));
|
||||
update(document.copyWith(archiveSerialNumber: asn));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,22 +124,6 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Updates the given document with the inbox tags removed and returns the remoed inbox tags.
|
||||
///
|
||||
Future<Iterable<int>> removeInboxTags(
|
||||
DocumentModel document, final Iterable<int> inboxTags) async {
|
||||
final tagsToRemove = document.tags.toSet().intersection(inboxTags.toSet());
|
||||
final updatedTags = {...document.tags}..removeAll(tagsToRemove);
|
||||
await updateDocument(
|
||||
document.copyWith(
|
||||
tags: updatedTags,
|
||||
overwriteTags: true,
|
||||
),
|
||||
);
|
||||
return tagsToRemove;
|
||||
}
|
||||
|
||||
void resetSelection() {
|
||||
emit(state.copyWith(selection: []));
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
|
||||
@@ -52,7 +52,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
DateFormat("MMM d, yyyy HH:mm:ss");
|
||||
|
||||
bool _isDownloadPending = false;
|
||||
bool _isAssignAsnPending = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -350,7 +349,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
final wasUpdated = await Navigator.push<bool>(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => LabelBlocProvider(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
child: DocumentEditPage(document: document),
|
||||
),
|
||||
maintainState: true,
|
||||
@@ -412,7 +411,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
false;
|
||||
if (delete) {
|
||||
try {
|
||||
await BlocProvider.of<DocumentsCubit>(context).removeDocument(document);
|
||||
await BlocProvider.of<DocumentsCubit>(context).remove(document);
|
||||
showSnackBar(context, S.of(context).documentDeleteSuccessMessage);
|
||||
} on ErrorMessage catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
|
||||
@@ -83,7 +83,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
});
|
||||
bool wasUpdated = false;
|
||||
try {
|
||||
await getIt<DocumentsCubit>().updateDocument(updatedDocument);
|
||||
await getIt<DocumentsCubit>().update(updatedDocument);
|
||||
showSnackBar(context, S.of(context).documentUpdateErrorMessage);
|
||||
wasUpdated = true;
|
||||
} on ErrorMessage catch (error, stackTrace) {
|
||||
|
||||
@@ -44,15 +44,13 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (!BlocProvider.of<DocumentsCubit>(context).state.isLoaded) {
|
||||
_initDocuments();
|
||||
}
|
||||
_initDocuments();
|
||||
_pagingController.addPageRequestListener(_loadNewPage);
|
||||
}
|
||||
|
||||
Future<void> _initDocuments() async {
|
||||
try {
|
||||
BlocProvider.of<DocumentsCubit>(context).loadDocuments();
|
||||
BlocProvider.of<DocumentsCubit>(context).load();
|
||||
} on ErrorMessage catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
@@ -113,7 +111,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
previous != ConnectivityState.connected &&
|
||||
current == ConnectivityState.connected,
|
||||
listener: (context, state) {
|
||||
BlocProvider.of<DocumentsCubit>(context).loadDocuments();
|
||||
BlocProvider.of<DocumentsCubit>(context).load();
|
||||
},
|
||||
builder: (context, connectivityState) {
|
||||
return Scaffold(
|
||||
@@ -241,9 +239,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<PaperlessStatisticsCubit>(context)),
|
||||
],
|
||||
child: DocumentDetailsPage(
|
||||
documentId: model.id,
|
||||
),
|
||||
child: DocumentDetailsPage(documentId: model.id),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -99,7 +99,7 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
||||
if (shouldDelete) {
|
||||
try {
|
||||
await BlocProvider.of<DocumentsCubit>(context)
|
||||
.bulkRemoveDocuments(documentsState.selection);
|
||||
.bulkRemove(documentsState.selection);
|
||||
showSnackBar(
|
||||
context,
|
||||
S.of(context).documentsPageBulkDeleteSuccessfulText,
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
||||
@@ -13,12 +9,12 @@ import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/bottom_navigation_bar.dart';
|
||||
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/inbox_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
@@ -26,7 +22,6 @@ import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
|
||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
@@ -42,30 +37,7 @@ class _HomePageState extends State<HomePage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeData(context).then(
|
||||
(_) async {
|
||||
FlutterNativeSplash.remove();
|
||||
if (BlocProvider.of<ApplicationSettingsCubit>(context)
|
||||
.state
|
||||
.showInboxOnStartup) {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: getIt<PaperlessStatisticsCubit>(),
|
||||
child: LabelBlocProvider(
|
||||
child: BlocProvider.value(
|
||||
value: DocumentsCubit(getIt<DocumentRepository>()),
|
||||
child: const InboxPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
getIt<DocumentsCubit>().reloadDocuments();
|
||||
}
|
||||
},
|
||||
);
|
||||
_initializeData(context);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -89,15 +61,22 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
drawer: const InfoDrawer(),
|
||||
body: [
|
||||
BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
),
|
||||
],
|
||||
child: const DocumentsPage(),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: getIt<DocumentScannerCubit>(),
|
||||
child: const ScannerPage(),
|
||||
),
|
||||
const LabelsPage(),
|
||||
BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
child: const LabelsPage(),
|
||||
),
|
||||
][_currentIndex],
|
||||
);
|
||||
},
|
||||
@@ -109,12 +88,12 @@ class _HomePageState extends State<HomePage> {
|
||||
return Future.wait([
|
||||
BlocProvider.of<PaperlessServerInformationCubit>(context)
|
||||
.updateInformtion(),
|
||||
BlocProvider.of<PaperlessStatisticsCubit>(context).updateStatistics(),
|
||||
BlocProvider.of<DocumentTypeCubit>(context).initialize(),
|
||||
BlocProvider.of<CorrespondentCubit>(context).initialize(),
|
||||
BlocProvider.of<TagCubit>(context).initialize(),
|
||||
BlocProvider.of<StoragePathCubit>(context).initialize(),
|
||||
BlocProvider.of<SavedViewCubit>(context).initialize(),
|
||||
getIt<PaperlessStatisticsCubit>().updateStatistics(),
|
||||
getIt<DocumentTypeCubit>().initialize(),
|
||||
getIt<CorrespondentCubit>().initialize(),
|
||||
getIt<TagCubit>().initialize(),
|
||||
getIt<StoragePathCubit>().initialize(),
|
||||
getIt<SavedViewCubit>().initialize(),
|
||||
]);
|
||||
} on ErrorMessage catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
|
||||
@@ -3,14 +3,15 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/paperless_statistics_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/model/paperless_server_information.dart';
|
||||
import 'package:paperless_mobile/core/model/paperless_statistics.dart';
|
||||
import 'package:paperless_mobile/core/service/paperless_statistics_service.dart';
|
||||
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/inbox_page.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||
@@ -127,24 +128,7 @@ class InfoDrawer extends StatelessWidget {
|
||||
trailing: state.isLoaded
|
||||
? Text(state.statistics!.documentsInInbox.toString())
|
||||
: null,
|
||||
onTap: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: getIt<PaperlessStatisticsCubit>(),
|
||||
child: LabelBlocProvider(
|
||||
child: BlocProvider.value(
|
||||
value:
|
||||
DocumentsCubit(getIt<DocumentRepository>()),
|
||||
child: const InboxPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
getIt<DocumentsCubit>().reloadDocuments();
|
||||
},
|
||||
onTap: () => _onOpenInbox(context),
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -228,6 +212,27 @@ class InfoDrawer extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<dynamic> _onOpenInbox(BuildContext context) {
|
||||
return Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<PaperlessStatisticsCubit>.value(
|
||||
value: BlocProvider.of<PaperlessStatisticsCubit>(context),
|
||||
),
|
||||
BlocProvider<InboxCubit>.value(
|
||||
value: getIt<InboxCubit>()..initialize(),
|
||||
),
|
||||
BlocProvider<DocumentsCubit>.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
),
|
||||
],
|
||||
child: const InboxPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Link _buildOnboardingImageCredits() {
|
||||
return Link(
|
||||
uri: Uri.parse(
|
||||
|
||||
115
lib/features/inbox/bloc/inbox_cubit.dart
Normal file
115
lib/features/inbox/bloc/inbox_cubit.dart
Normal file
@@ -0,0 +1,115 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/bulk_edit.model.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/query_parameters/sort_field.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
|
||||
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
||||
import 'package:paperless_mobile/features/labels/repository/label_repository.dart';
|
||||
|
||||
@injectable
|
||||
class InboxCubit extends Cubit<InboxState> {
|
||||
final LabelRepository _labelRepository;
|
||||
final DocumentRepository _documentRepository;
|
||||
|
||||
InboxCubit(this._labelRepository, this._documentRepository)
|
||||
: super(const InboxState());
|
||||
|
||||
///
|
||||
/// Fetches inbox tag ids and loads the inbox items (documents).
|
||||
///
|
||||
Future<void> initialize() async {
|
||||
final inboxTags = await _labelRepository.getTags().then(
|
||||
(value) => value.where((t) => t.isInboxTag ?? false).map((t) => t.id!));
|
||||
final inboxDocuments = await _documentRepository
|
||||
.find(DocumentFilter(
|
||||
tags: AnyAssignedTagsQuery(tagIds: inboxTags),
|
||||
sortField: SortField.added,
|
||||
))
|
||||
.then((psr) => psr.results);
|
||||
final newState = InboxState(
|
||||
isLoaded: true,
|
||||
inboxItems: inboxDocuments,
|
||||
inboxTags: inboxTags,
|
||||
);
|
||||
emit(newState);
|
||||
}
|
||||
|
||||
Future<void> reloadInbox() async {
|
||||
if (!state.isLoaded) {
|
||||
throw "State has not yet loaded. Ensure the state is loaded when calling this method!";
|
||||
}
|
||||
final inboxDocuments = await _documentRepository
|
||||
.find(DocumentFilter(
|
||||
tags: AnyAssignedTagsQuery(tagIds: state.inboxTags),
|
||||
sortField: SortField.added,
|
||||
))
|
||||
.then((psr) => psr.results);
|
||||
emit(InboxState(
|
||||
isLoaded: true,
|
||||
inboxItems: inboxDocuments,
|
||||
inboxTags: state.inboxTags,
|
||||
));
|
||||
}
|
||||
|
||||
///
|
||||
/// Updates the document with all inbox tags removed and removes the document
|
||||
/// from the currently loaded inbox documents.
|
||||
///
|
||||
Future<Iterable<int>> remove(DocumentModel document) async {
|
||||
if (!state.isLoaded) {
|
||||
throw "State has not yet loaded. Ensure the state is loaded when calling this method!";
|
||||
}
|
||||
final tagsToRemove =
|
||||
document.tags.toSet().intersection(state.inboxTags.toSet());
|
||||
|
||||
final updatedTags = {...document.tags}..removeAll(tagsToRemove);
|
||||
await _documentRepository.update(
|
||||
document.copyWith(
|
||||
tags: updatedTags,
|
||||
overwriteTags: true,
|
||||
),
|
||||
);
|
||||
emit(
|
||||
InboxState(
|
||||
isLoaded: true,
|
||||
inboxTags: state.inboxTags,
|
||||
inboxItems: state.inboxItems.where((doc) => doc.id != document.id),
|
||||
),
|
||||
);
|
||||
|
||||
return tagsToRemove;
|
||||
}
|
||||
|
||||
Future<void> undoRemove(
|
||||
DocumentModel document, Iterable<int> removedTags) async {
|
||||
final updatedDoc = document.copyWith(
|
||||
tags: {...document.tags, ...removedTags},
|
||||
overwriteTags: true,
|
||||
);
|
||||
await _documentRepository.update(updatedDoc);
|
||||
emit(InboxState(
|
||||
isLoaded: true,
|
||||
inboxItems: [...state.inboxItems, updatedDoc]
|
||||
..sort((d1, d2) => d1.added.compareTo(d2.added)),
|
||||
inboxTags: state.inboxTags,
|
||||
));
|
||||
}
|
||||
|
||||
///
|
||||
/// Removes inbox tags from all documents in the inbox.
|
||||
///
|
||||
Future<void> clearInbox() async {
|
||||
await _documentRepository.bulkAction(BulkModifyTagsAction.removeTags(
|
||||
state.inboxItems.map((e) => e.id), state.inboxTags));
|
||||
emit(
|
||||
InboxState(
|
||||
isLoaded: true,
|
||||
inboxTags: state.inboxTags,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
17
lib/features/inbox/bloc/state/inbox_state.dart
Normal file
17
lib/features/inbox/bloc/state/inbox_state.dart
Normal file
@@ -0,0 +1,17 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
|
||||
class InboxState with EquatableMixin {
|
||||
final bool isLoaded;
|
||||
final Iterable<int> inboxTags;
|
||||
final Iterable<DocumentModel> inboxItems;
|
||||
|
||||
const InboxState({
|
||||
this.isLoaded = false,
|
||||
this.inboxTags = const [],
|
||||
this.inboxItems = const [],
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [isLoaded, inboxTags, inboxItems];
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/model/paperless_statistics_state.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class InboxPage extends StatefulWidget {
|
||||
const InboxPage({super.key});
|
||||
|
||||
@override
|
||||
State<InboxPage> createState() => _InboxPageState();
|
||||
}
|
||||
|
||||
class _InboxPageState extends State<InboxPage> {
|
||||
static const _a4AspectRatio = 1 / 1.4142;
|
||||
|
||||
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
|
||||
Iterable<int> _inboxTags = [];
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initializeDateFormatting();
|
||||
_initInbox();
|
||||
}
|
||||
|
||||
Future<void> _initInbox() async {
|
||||
final tags = BlocProvider.of<TagCubit>(context).state.labels;
|
||||
log("Loading documents with tags...${tags.values.join(",")}");
|
||||
_inboxTags =
|
||||
tags.values.where((t) => t.isInboxTag ?? false).map((t) => t.id!);
|
||||
final filter =
|
||||
DocumentFilter(tags: AnyAssignedTagsQuery(tagIds: _inboxTags));
|
||||
return BlocProvider.of<DocumentsCubit>(context).updateFilter(
|
||||
filter: filter,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, documentState) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title:
|
||||
BlocBuilder<PaperlessStatisticsCubit, PaperlessStatisticsState>(
|
||||
builder: (context, state) {
|
||||
return Text(
|
||||
S.of(context).bottomNavInboxPageLabel +
|
||||
(state.isLoaded
|
||||
? ' (${state.statistics!.documentsInInbox})'
|
||||
: ''),
|
||||
);
|
||||
},
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
floatingActionButton: documentState.documents.isNotEmpty
|
||||
? FloatingActionButton.extended(
|
||||
label: Text("Mark all as seen"),
|
||||
icon: const Icon(Icons.done_all),
|
||||
onPressed: () =>
|
||||
_onMarkAllAsSeen(documentState.documents, _inboxTags),
|
||||
)
|
||||
: null,
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
if (!documentState.isLoaded) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (documentState.documents.isEmpty) {
|
||||
return Text(
|
||||
"You do not have new documents in your inbox.",
|
||||
textAlign: TextAlign.center,
|
||||
) // TODO: INTL
|
||||
.padded();
|
||||
}
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
'Hint: Swipe left to mark a document as read. This will remove all inbox tags from the document.', //TODO: INTL
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
).padded(
|
||||
const EdgeInsets.only(
|
||||
top: 4.0,
|
||||
left: 8.0,
|
||||
right: 8.0,
|
||||
bottom: 8.0,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: AnimatedList(
|
||||
key: _listKey,
|
||||
initialItemCount: documentState.documents.length,
|
||||
itemBuilder: (context, index, animation) {
|
||||
final doc = documentState.documents[index];
|
||||
return _buildListItem(context, doc);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildListItem(BuildContext context, DocumentModel doc) {
|
||||
return Dismissible(
|
||||
direction: DismissDirection.endToStart,
|
||||
background: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.done,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
).padded(),
|
||||
Text(
|
||||
'Mark as read', //TODO: INTL
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
confirmDismiss: (_) => _onItemDismissed(doc),
|
||||
key: ObjectKey(doc.id),
|
||||
child: ListTile(
|
||||
title: Text(doc.title),
|
||||
isThreeLine: true,
|
||||
leading: AspectRatio(
|
||||
aspectRatio: _a4AspectRatio,
|
||||
child: DocumentPreview(
|
||||
id: doc.id,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(DateFormat().format(doc.added)),
|
||||
TagsWidget(tagIds: doc.tags.where((id) => _inboxTags.contains(id)))
|
||||
],
|
||||
),
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => LabelBlocProvider(
|
||||
child: BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentsCubit>(context),
|
||||
child: DocumentDetailsPage(
|
||||
documentId: doc.id,
|
||||
allowEdit: false,
|
||||
isLabelClickable: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSlideAnimation(
|
||||
BuildContext context,
|
||||
animation,
|
||||
Widget child,
|
||||
) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(-1, 0),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onMarkAllAsSeen(
|
||||
List<DocumentModel> documents,
|
||||
Iterable<int> inboxTags,
|
||||
) async {
|
||||
for (int i = documents.length - 1; i >= 0; i--) {
|
||||
final doc = documents[i];
|
||||
_listKey.currentState?.removeItem(
|
||||
0,
|
||||
(context, animation) => _buildSlideAnimation(
|
||||
context,
|
||||
animation,
|
||||
_buildListItem(context, doc),
|
||||
),
|
||||
);
|
||||
await Future.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
await BlocProvider.of<DocumentsCubit>(context)
|
||||
.bulkEditTags(documents, removeTags: inboxTags);
|
||||
BlocProvider.of<PaperlessStatisticsCubit>(context).resetInboxCount();
|
||||
}
|
||||
|
||||
Future<bool> _onItemDismissed(DocumentModel doc) async {
|
||||
try {
|
||||
final removedTags = await BlocProvider.of<DocumentsCubit>(context)
|
||||
.removeInboxTags(doc, _inboxTags);
|
||||
BlocProvider.of<PaperlessStatisticsCubit>(context).decrementInboxCount();
|
||||
showSnackBar(
|
||||
context,
|
||||
'Document removed from inbox.', //TODO: INTL
|
||||
action: SnackBarAction(
|
||||
label: 'UNDO', //TODO: INTL
|
||||
textColor: Theme.of(context).colorScheme.primary,
|
||||
onPressed: () => _onUndoMarkAsSeen(doc, removedTags),
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} on ErrorMessage catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
return false;
|
||||
} catch (error) {
|
||||
showErrorMessage(
|
||||
context,
|
||||
const ErrorMessage.unknown(),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onUndoMarkAsSeen(
|
||||
DocumentModel doc, Iterable<int> removedTags) async {
|
||||
try {
|
||||
await BlocProvider.of<DocumentsCubit>(context).updateDocument(
|
||||
doc.copyWith(
|
||||
tags: {...doc.tags, ...removedTags},
|
||||
overwriteTags: true,
|
||||
),
|
||||
);
|
||||
BlocProvider.of<PaperlessStatisticsCubit>(context).incrementInboxCount();
|
||||
BlocProvider.of<DocumentsCubit>(context).reloadDocuments();
|
||||
} on ErrorMessage catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
202
lib/features/inbox/view/pages/inbox_page.dart
Normal file
202
lib/features/inbox/view/pages/inbox_page.dart
Normal file
@@ -0,0 +1,202 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/widgets/document_inbox_item.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
|
||||
class InboxPage extends StatefulWidget {
|
||||
const InboxPage({super.key});
|
||||
|
||||
@override
|
||||
State<InboxPage> createState() => _InboxPageState();
|
||||
}
|
||||
|
||||
class _InboxPageState extends State<InboxPage> {
|
||||
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initializeDateFormatting();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bloc = BlocProvider.of<InboxCubit>(context);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).bottomNavInboxPageLabel),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.close),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
),
|
||||
),
|
||||
floatingActionButton: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
return FloatingActionButton.extended(
|
||||
label: Text("Mark all as seen"),
|
||||
icon: const Icon(Icons.done_all),
|
||||
onPressed: state.isLoaded && state.inboxItems.isNotEmpty
|
||||
? () => _onMarkAllAsSeen(
|
||||
bloc.state.inboxItems,
|
||||
bloc.state.inboxTags,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
),
|
||||
body: BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
if (!state.isLoaded) {
|
||||
return const DocumentsListLoadingWidget();
|
||||
}
|
||||
|
||||
if (state.inboxItems.isEmpty) {
|
||||
return Text(
|
||||
"You do not have new documents in your inbox.",
|
||||
textAlign: TextAlign.center,
|
||||
).padded();
|
||||
}
|
||||
return RefreshIndicator(
|
||||
onRefresh: () => BlocProvider.of<InboxCubit>(context).reloadInbox(),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'Hint: Swipe left to mark a document as seen. This will remove all inbox tags from the document.', //TODO: INTL
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
).padded(
|
||||
const EdgeInsets.only(
|
||||
top: 4.0,
|
||||
left: 8.0,
|
||||
right: 8.0,
|
||||
bottom: 8.0,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: AnimatedList(
|
||||
key: _listKey,
|
||||
initialItemCount: state.inboxItems.length,
|
||||
itemBuilder: (context, index, animation) {
|
||||
final doc = state.inboxItems.elementAt(index);
|
||||
return _buildListItem(context, doc);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildListItem(BuildContext context, DocumentModel doc) {
|
||||
return Dismissible(
|
||||
direction: DismissDirection.endToStart,
|
||||
background: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.done,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
).padded(),
|
||||
Text(
|
||||
'Mark as read', //TODO: INTL
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
confirmDismiss: (_) => _onItemDismissed(doc),
|
||||
key: ObjectKey(doc.id),
|
||||
child: DocumentInboxItem(document: doc),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSlideAnimation(
|
||||
BuildContext context,
|
||||
animation,
|
||||
Widget child,
|
||||
) {
|
||||
return SlideTransition(
|
||||
position: Tween<Offset>(
|
||||
begin: const Offset(-1, 0),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onMarkAllAsSeen(
|
||||
Iterable<DocumentModel> documents,
|
||||
Iterable<int> inboxTags,
|
||||
) async {
|
||||
for (int i = documents.length - 1; i >= 0; i--) {
|
||||
final doc = documents.elementAt(i);
|
||||
_listKey.currentState?.removeItem(
|
||||
0,
|
||||
(context, animation) => _buildSlideAnimation(
|
||||
context,
|
||||
animation,
|
||||
_buildListItem(context, doc),
|
||||
),
|
||||
);
|
||||
await Future.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
await BlocProvider.of<DocumentsCubit>(context)
|
||||
.bulkEditTags(documents, removeTags: inboxTags);
|
||||
BlocProvider.of<PaperlessStatisticsCubit>(context).resetInboxCount();
|
||||
}
|
||||
|
||||
Future<bool> _onItemDismissed(DocumentModel doc) async {
|
||||
try {
|
||||
final removedTags =
|
||||
await BlocProvider.of<InboxCubit>(context).remove(doc);
|
||||
BlocProvider.of<PaperlessStatisticsCubit>(context).decrementInboxCount();
|
||||
showSnackBar(
|
||||
context,
|
||||
'Document removed from inbox.', //TODO: INTL
|
||||
action: SnackBarAction(
|
||||
label: 'UNDO', //TODO: INTL
|
||||
textColor: Theme.of(context).colorScheme.primary,
|
||||
onPressed: () => _onUndoMarkAsSeen(doc, removedTags),
|
||||
),
|
||||
);
|
||||
return true;
|
||||
} on ErrorMessage catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
return false;
|
||||
} catch (error) {
|
||||
showErrorMessage(
|
||||
context,
|
||||
const ErrorMessage.unknown(),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _onUndoMarkAsSeen(
|
||||
DocumentModel document,
|
||||
Iterable<int> removedTags,
|
||||
) async {
|
||||
try {
|
||||
await BlocProvider.of<InboxCubit>(context)
|
||||
.undoRemove(document, removedTags);
|
||||
BlocProvider.of<PaperlessStatisticsCubit>(context).incrementInboxCount();
|
||||
} on ErrorMessage catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
lib/features/inbox/view/widgets/document_inbox_item.dart
Normal file
61
lib/features/inbox/view/widgets/document_inbox_item.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||
|
||||
class DocumentInboxItem extends StatelessWidget {
|
||||
final DocumentModel document;
|
||||
|
||||
const DocumentInboxItem({
|
||||
super.key,
|
||||
required this.document,
|
||||
});
|
||||
static const _a4AspectRatio = 1 / 1.4142;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text(document.title),
|
||||
isThreeLine: true,
|
||||
leading: AspectRatio(
|
||||
aspectRatio: _a4AspectRatio,
|
||||
child: DocumentPreview(
|
||||
id: document.id,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
),
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(DateFormat().format(document.added)),
|
||||
TagsWidget(
|
||||
tagIds: document.tags,
|
||||
isMultiLine: false,
|
||||
isClickable: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentsCubit>(context)),
|
||||
],
|
||||
child: DocumentDetailsPage(
|
||||
documentId: document.id,
|
||||
allowEdit: false,
|
||||
isLabelClickable: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,14 @@ import 'package:paperless_mobile/features/labels/document_type/bloc/document_typ
|
||||
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
|
||||
|
||||
class LabelBlocProvider extends StatelessWidget {
|
||||
class GlobalStateBlocProvider extends StatelessWidget {
|
||||
final List<BlocProvider> additionalProviders;
|
||||
final Widget child;
|
||||
const LabelBlocProvider({super.key, required this.child});
|
||||
const GlobalStateBlocProvider({
|
||||
super.key,
|
||||
this.additionalProviders = const [],
|
||||
required this.child,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -20,6 +25,7 @@ class LabelBlocProvider extends StatelessWidget {
|
||||
BlocProvider.value(value: getIt<TagCubit>()),
|
||||
BlocProvider.value(value: getIt<StoragePathCubit>()),
|
||||
BlocProvider.value(value: getIt<SavedViewCubit>()),
|
||||
...additionalProviders,
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
@@ -24,7 +24,7 @@ class EditTagPage extends StatelessWidget {
|
||||
label: tag,
|
||||
onSubmit: (tag) async {
|
||||
await BlocProvider.of<TagCubit>(context).replace(tag);
|
||||
//If inbox property was added/removed from tag, the number of documetns in inbox may increase/decrease.
|
||||
//If inbox property was added/removed from tag, the number of documents in inbox may increase/decrease.
|
||||
BlocProvider.of<PaperlessStatisticsCubit>(context).updateStatistics();
|
||||
},
|
||||
onDelete: (tag) => _onDelete(tag, context),
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||
@@ -54,135 +54,130 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
child: DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
drawer: const InfoDrawer(),
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
[
|
||||
S.of(context).labelsPageCorrespondentsTitleText,
|
||||
S.of(context).labelsPageDocumentTypesTitleText,
|
||||
S.of(context).labelsPageTagsTitleText,
|
||||
S.of(context).labelsPageStoragePathTitleText
|
||||
][_currentIndex],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _onAddPressed,
|
||||
icon: const Icon(Icons.add),
|
||||
)
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||
child: ColoredBox(
|
||||
color: Theme.of(context).bottomAppBarColor,
|
||||
child: TabBar(
|
||||
indicatorColor: Theme.of(context).colorScheme.primary,
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.person_outline,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
return DefaultTabController(
|
||||
length: 3,
|
||||
child: Scaffold(
|
||||
drawer: const InfoDrawer(),
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
[
|
||||
S.of(context).labelsPageCorrespondentsTitleText,
|
||||
S.of(context).labelsPageDocumentTypesTitleText,
|
||||
S.of(context).labelsPageTagsTitleText,
|
||||
S.of(context).labelsPageStoragePathTitleText
|
||||
][_currentIndex],
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: _onAddPressed,
|
||||
icon: const Icon(Icons.add),
|
||||
)
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||
child: ColoredBox(
|
||||
color: Theme.of(context).bottomAppBarColor,
|
||||
child: TabBar(
|
||||
indicatorColor: Theme.of(context).colorScheme.primary,
|
||||
controller: _tabController,
|
||||
tabs: [
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.person_outline,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.description_outlined,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.description_outlined,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.label_outline,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.label_outline,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.folder_open,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Tab(
|
||||
icon: Icon(
|
||||
Icons.folder_open,
|
||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
LabelTabView<Correspondent>(
|
||||
cubit: BlocProvider.of<CorrespondentCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
correspondent: CorrespondentQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onOpenEditPage: _openEditCorrespondentPage,
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageCorrespondentEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageCorrespondentEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
),
|
||||
body: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
LabelTabView<Correspondent>(
|
||||
cubit: BlocProvider.of<CorrespondentCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
correspondent: CorrespondentQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
LabelTabView<DocumentType>(
|
||||
cubit: BlocProvider.of<DocumentTypeCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
documentType: DocumentTypeQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onOpenEditPage: _openEditDocumentTypePage,
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageDocumentTypeEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageDocumentTypeEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
onOpenEditPage: _openEditCorrespondentPage,
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageCorrespondentEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageCorrespondentEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
),
|
||||
LabelTabView<DocumentType>(
|
||||
cubit: BlocProvider.of<DocumentTypeCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
documentType: DocumentTypeQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
LabelTabView<Tag>(
|
||||
cubit: BlocProvider.of<TagCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
tags: IdsTagsQuery.fromIds([label.id!]),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
onOpenEditPage: _openEditTagPage,
|
||||
leadingBuilder: (t) => CircleAvatar(
|
||||
backgroundColor: t.color,
|
||||
child: t.isInboxTag ?? false
|
||||
? Icon(
|
||||
Icons.inbox,
|
||||
color: t.textColor,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
contentBuilder: (t) => Text(t.match ?? ''),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageTagsEmptyStateAddNewLabel,
|
||||
emptyStateDescription:
|
||||
S.of(context).labelsPageTagsEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
onOpenEditPage: _openEditDocumentTypePage,
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageDocumentTypeEmptyStateAddNewLabel,
|
||||
emptyStateDescription:
|
||||
S.of(context).labelsPageDocumentTypeEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
),
|
||||
LabelTabView<Tag>(
|
||||
cubit: BlocProvider.of<TagCubit>(context),
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
tags: IdsTagsQuery.fromIds([label.id!]),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
LabelTabView<StoragePath>(
|
||||
cubit: BlocProvider.of<StoragePathCubit>(context),
|
||||
onOpenEditPage: _openEditStoragePathPage,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
storagePath: StoragePathQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
contentBuilder: (path) => Text(path.path ?? ""),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageStoragePathEmptyStateAddNewLabel,
|
||||
emptyStateDescription: S
|
||||
.of(context)
|
||||
.labelsPageStoragePathEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
onOpenEditPage: _openEditTagPage,
|
||||
leadingBuilder: (t) => CircleAvatar(
|
||||
backgroundColor: t.color,
|
||||
child: t.isInboxTag ?? false
|
||||
? Icon(
|
||||
Icons.inbox,
|
||||
color: t.textColor,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
contentBuilder: (t) => Text(t.match ?? ''),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageTagsEmptyStateAddNewLabel,
|
||||
emptyStateDescription:
|
||||
S.of(context).labelsPageTagsEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
),
|
||||
LabelTabView<StoragePath>(
|
||||
cubit: BlocProvider.of<StoragePathCubit>(context),
|
||||
onOpenEditPage: _openEditStoragePathPage,
|
||||
filterBuilder: (label) => DocumentFilter(
|
||||
storagePath: StoragePathQuery.fromId(label.id),
|
||||
pageSize: label.documentCount ?? 0,
|
||||
),
|
||||
contentBuilder: (path) => Text(path.path ?? ""),
|
||||
emptyStateActionButtonLabel:
|
||||
S.of(context).labelsPageStoragePathEmptyStateAddNewLabel,
|
||||
emptyStateDescription:
|
||||
S.of(context).labelsPageStoragePathEmptyStateDescriptionText,
|
||||
onOpenAddNewPage: _onAddPressed,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -192,11 +187,9 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<CorrespondentCubit>(context)),
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider.value(value: BlocProvider.of<DocumentsCubit>(context)),
|
||||
],
|
||||
child: EditCorrespondentPage(correspondent: correspondent),
|
||||
),
|
||||
@@ -208,11 +201,9 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentTypeCubit>(context)),
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider.value(value: BlocProvider.of<DocumentsCubit>(context)),
|
||||
],
|
||||
child: EditDocumentTypePage(documentType: docType),
|
||||
),
|
||||
@@ -224,10 +215,9 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||
BlocProvider.value(value: BlocProvider.of<TagCubit>(context)),
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider.value(value: BlocProvider.of<DocumentsCubit>(context)),
|
||||
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
|
||||
],
|
||||
child: EditTagPage(tag: tag),
|
||||
@@ -240,11 +230,9 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => MultiBlocProvider(
|
||||
providers: [
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<StoragePathCubit>(context)),
|
||||
],
|
||||
child: EditStoragePathPage(storagePath: path),
|
||||
),
|
||||
@@ -269,7 +257,7 @@ class _LabelsPageState extends State<LabelsPage>
|
||||
case 3:
|
||||
page = const AddStoragePathPage();
|
||||
}
|
||||
return LabelBlocProvider(child: page);
|
||||
return GlobalStateBlocProvider(child: page);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/di_initializer.dart';
|
||||
@@ -8,7 +8,8 @@ import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/model/label.model.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/linked_documents_preview.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/view/pages/linked_documents_page.dart';
|
||||
|
||||
class LabelItem<T extends Label> extends StatelessWidget {
|
||||
final T label;
|
||||
@@ -50,13 +51,13 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => LabelBlocProvider(
|
||||
child: BlocProvider(
|
||||
create: (context) =>
|
||||
DocumentsCubit(getIt<DocumentRepository>())
|
||||
..updateFilter(filter: filter),
|
||||
child: LinkedDocumentsPreview(filter: filter),
|
||||
),
|
||||
builder: (context) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider.value(
|
||||
value: getIt<LinkedDocumentsCubit>()
|
||||
..initialize(filter)),
|
||||
],
|
||||
child: const LinkedDocumentsPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
|
||||
class LinkedDocumentsPreview extends StatefulWidget {
|
||||
final DocumentFilter filter;
|
||||
|
||||
const LinkedDocumentsPreview({super.key, required this.filter});
|
||||
|
||||
@override
|
||||
State<LinkedDocumentsPreview> createState() => _LinkedDocumentsPreviewState();
|
||||
}
|
||||
|
||||
class _LinkedDocumentsPreviewState extends State<LinkedDocumentsPreview> {
|
||||
final _pagingController =
|
||||
PagingController<int, DocumentModel>(firstPageKey: 1);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pagingController.nextPageKey = null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).linkedDocumentsPageTitle),
|
||||
),
|
||||
body: BlocBuilder<DocumentsCubit, DocumentsState>(
|
||||
builder: (context, state) {
|
||||
_pagingController.itemList = state.documents;
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).referencedDocumentsReadOnlyHintText,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
DocumentListView(
|
||||
isLabelClickable: false,
|
||||
onTap: (doc) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (ctxt) => LabelBlocProvider(
|
||||
child: BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentsCubit>(context),
|
||||
child: DocumentDetailsPage(
|
||||
documentId: doc.id,
|
||||
allowEdit: false,
|
||||
isLabelClickable: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
pagingController: _pagingController,
|
||||
state: state,
|
||||
onSelected: BlocProvider.of<DocumentsCubit>(context)
|
||||
.toggleDocumentSelection,
|
||||
hasInternetConnection: true,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/state/linked_documents_state.dart';
|
||||
|
||||
@injectable
|
||||
class LinkedDocumentsCubit extends Cubit<LinkedDocumentsState> {
|
||||
final DocumentRepository _documentRepository;
|
||||
|
||||
LinkedDocumentsCubit(this._documentRepository)
|
||||
: super(LinkedDocumentsState());
|
||||
|
||||
Future<void> initialize(DocumentFilter filter) async {
|
||||
final documents = await _documentRepository.find(
|
||||
filter.copyWith(
|
||||
pageSize: 100,
|
||||
),
|
||||
);
|
||||
emit(LinkedDocumentsState(
|
||||
isLoaded: true,
|
||||
documents: documents,
|
||||
filter: filter,
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/paged_search_result.dart';
|
||||
|
||||
class LinkedDocumentsState {
|
||||
final bool isLoaded;
|
||||
final PagedSearchResult<DocumentModel>? documents;
|
||||
final DocumentFilter? filter;
|
||||
|
||||
LinkedDocumentsState({
|
||||
this.filter,
|
||||
this.isLoaded = false,
|
||||
this.documents,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
|
||||
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/linked_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/linked_documents_preview/bloc/state/linked_documents_state.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class LinkedDocumentsPage extends StatefulWidget {
|
||||
const LinkedDocumentsPage({super.key});
|
||||
|
||||
@override
|
||||
State<LinkedDocumentsPage> createState() => _LinkedDocumentsPageState();
|
||||
}
|
||||
|
||||
class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
|
||||
final _pagingController =
|
||||
PagingController<int, DocumentModel>(firstPageKey: 1);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_pagingController.nextPageKey = null;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context).linkedDocumentsPageTitle),
|
||||
),
|
||||
body: BlocBuilder<LinkedDocumentsCubit, LinkedDocumentsState>(
|
||||
builder: (context, state) {
|
||||
if (!state.isLoaded) {
|
||||
return const DocumentsListLoadingWidget();
|
||||
}
|
||||
|
||||
_pagingController.itemList = state.documents!.results;
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
S.of(context).referencedDocumentsReadOnlyHintText,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.caption,
|
||||
),
|
||||
Expanded(
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
PagedSliverList<int, DocumentModel>(
|
||||
pagingController: _pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate(
|
||||
animateTransitions: true,
|
||||
itemBuilder: (context, document, index) {
|
||||
return DocumentListItem(
|
||||
isLabelClickable: false,
|
||||
document: document,
|
||||
onTap: (doc) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (ctxt) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider.value(
|
||||
value: BlocProvider.of<DocumentsCubit>(
|
||||
context,
|
||||
),
|
||||
),
|
||||
],
|
||||
child: DocumentDetailsPage(
|
||||
documentId: doc.id,
|
||||
allowEdit: false,
|
||||
isLabelClickable: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
isSelected: false,
|
||||
isAtLeastOneSelected: false,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,16 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:paperless_mobile/features/documents/model/document.model.dart';
|
||||
import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||
|
||||
@singleton
|
||||
@injectable
|
||||
class DocumentScannerCubit extends Cubit<List<File>> {
|
||||
final DocumentRepository documentRepository;
|
||||
|
||||
static List<File> initialState = [];
|
||||
|
||||
DocumentScannerCubit() : super(initialState);
|
||||
DocumentScannerCubit(this.documentRepository) : super(initialState);
|
||||
|
||||
void addScan(File file) => emit([...state, file]);
|
||||
|
||||
@@ -40,4 +44,30 @@ class DocumentScannerCubit extends Cubit<List<File>> {
|
||||
throw const ErrorMessage(ErrorCode.scanRemoveFailed);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> uploadDocument(
|
||||
Uint8List bytes,
|
||||
String fileName, {
|
||||
required String title,
|
||||
required void Function(DocumentModel document)? onConsumptionFinished,
|
||||
int? documentType,
|
||||
int? correspondent,
|
||||
Iterable<int> tags = const [],
|
||||
DateTime? createdAt,
|
||||
}) async {
|
||||
await documentRepository.create(
|
||||
bytes,
|
||||
fileName,
|
||||
title: title,
|
||||
documentType: documentType,
|
||||
correspondent: correspondent,
|
||||
tags: tags,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
if (onConsumptionFinished != null) {
|
||||
documentRepository
|
||||
.waitForConsumptionFinished(fileName, title)
|
||||
.then((value) => onConsumptionFinished(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ class DocumentUploadPage extends StatefulWidget {
|
||||
final String? title;
|
||||
final String? filename;
|
||||
final void Function()? afterUpload;
|
||||
final void Function(DocumentModel)? onSuccessfullyConsumed;
|
||||
|
||||
const DocumentUploadPage({
|
||||
Key? key,
|
||||
@@ -42,6 +43,7 @@ class DocumentUploadPage extends StatefulWidget {
|
||||
this.afterUpload,
|
||||
this.title,
|
||||
this.filename,
|
||||
this.onSuccessfullyConsumed,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -229,6 +231,7 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
|
||||
|
||||
void _onSubmit() async {
|
||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||
final cubit = BlocProvider.of<DocumentScannerCubit>(context);
|
||||
try {
|
||||
setState(() => _isUploadLoading = true);
|
||||
|
||||
@@ -240,17 +243,19 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
|
||||
final tags = fv[DocumentModel.tagsKey] as IdsTagsQuery;
|
||||
final correspondent =
|
||||
fv[DocumentModel.correspondentKey] as IdQueryParameter;
|
||||
await BlocProvider.of<DocumentsCubit>(context).addDocument(
|
||||
|
||||
await cubit.uploadDocument(
|
||||
widget.fileBytes,
|
||||
_padWithPdfExtension(_formKey.currentState?.value[fkFileName]),
|
||||
onConsumptionFinished: _onConsumptionFinished,
|
||||
onConsumptionFinished: widget.onSuccessfullyConsumed,
|
||||
title: title,
|
||||
documentType: docType.id,
|
||||
correspondent: correspondent.id,
|
||||
tags: tags.ids,
|
||||
createdAt: createdAt,
|
||||
);
|
||||
getIt<DocumentScannerCubit>().reset(); //TODO: Access via provider
|
||||
|
||||
cubit.reset(); //TODO: Access via provider
|
||||
showSnackBar(context, S.of(context).documentUploadSuccessText);
|
||||
Navigator.pop(context);
|
||||
widget.afterUpload?.call();
|
||||
@@ -275,24 +280,4 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
|
||||
String _formatFilename(String source) {
|
||||
return source.replaceAll(RegExp(r"[\W_]"), "_");
|
||||
}
|
||||
|
||||
void _onConsumptionFinished(DocumentModel document) {
|
||||
// ScaffoldMessenger.of(rootScaffoldKey.currentContext!).showSnackBar(
|
||||
// SnackBar(
|
||||
// action: SnackBarAction(
|
||||
// onPressed: () async {
|
||||
// try {
|
||||
// getIt<DocumentsCubit>().reloadDocuments();
|
||||
// } on ErrorMessage catch (error, stackTrace) {
|
||||
// showErrorMessage(context, error, stackTrace);
|
||||
// }
|
||||
// },
|
||||
// label:
|
||||
// S.of(context).documentUploadProcessingSuccessfulReloadActionText,
|
||||
// ),
|
||||
// content: Text(S.of(context).documentUploadProcessingSuccessfulText),
|
||||
// ),
|
||||
// );
|
||||
getIt<PaperlessStatisticsCubit>().incrementInboxCount();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
@@ -127,12 +128,17 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
final bytes = await doc.save();
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
child: LabelBlocProvider(
|
||||
child: DocumentUploadPage(
|
||||
fileBytes: bytes,
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<DocumentScannerCubit>.value(
|
||||
value: BlocProvider.of<DocumentScannerCubit>(context),
|
||||
),
|
||||
],
|
||||
child: DocumentUploadPage(
|
||||
fileBytes: bytes,
|
||||
onSuccessfullyConsumed: (_) =>
|
||||
BlocProvider.of<PaperlessStatisticsCubit>(context)
|
||||
.updateStatistics(),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -242,17 +248,20 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
// pdf
|
||||
fileBytes = file.readAsBytesSync();
|
||||
}
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
child: LabelBlocProvider(
|
||||
child: DocumentUploadPage(
|
||||
filename: filename,
|
||||
fileBytes: fileBytes,
|
||||
builder: (_) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider<DocumentScannerCubit>.value(
|
||||
value: BlocProvider.of<DocumentScannerCubit>(context),
|
||||
),
|
||||
],
|
||||
child: DocumentUploadPage(
|
||||
filename: filename,
|
||||
fileBytes: fileBytes,
|
||||
onSuccessfullyConsumed: (_) =>
|
||||
BlocProvider.of<PaperlessStatisticsCubit>(context)
|
||||
.updateStatistics(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
"labelsPageTagsEmptyStateDescriptionText": "Es wurden noch keine Tags angelegt.",
|
||||
"labelsPageStoragePathEmptyStateAddNewLabel": "Erstelle neuen Speicherpfad",
|
||||
"labelsPageStoragePathEmptyStateDescriptionText": "Es wurden noch keine Speicherpfade angelegt.",
|
||||
"referencedDocumentsReadOnlyHintText": "Dies ist eine schreibgeschützte Ansicht! Dokumente können nicht bearbeitet oder entfernt werden.",
|
||||
"referencedDocumentsReadOnlyHintText": "Dies ist eine schreibgeschützte Ansicht! Dokumente können nicht bearbeitet oder entfernt werden. Es werden maximal 100 referenzierte Dokumente geladen.",
|
||||
"editLabelPageConfirmDeletionDialogTitle": "Löschen bestätigen",
|
||||
"editLabelPageDeletionDialogText": "Dieser Kennzeichner wird von Dokumenten referenziert. Durch das Löschen dieses Kennzeichners werden alle Referenzen entfernt. Fortfahren?",
|
||||
"settingsPageStorageSettingsLabel": "Speicher",
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
"labelsPageTagsEmptyStateDescriptionText": "You don't seem to have any tags set up.",
|
||||
"labelsPageStoragePathEmptyStateAddNewLabel": "Add new storage path",
|
||||
"labelsPageStoragePathEmptyStateDescriptionText": "You don't seem to have any storage paths set up.",
|
||||
"referencedDocumentsReadOnlyHintText": "This is a read-only view! You cannot edit or remove documents.",
|
||||
"referencedDocumentsReadOnlyHintText": "This is a read-only view! You cannot edit or remove documents. A maximum of 100 referenced documents will be loaded.",
|
||||
"editLabelPageConfirmDeletionDialogTitle": "Confirm deletion",
|
||||
"editLabelPageDeletionDialogText": "This label contains references to other documents. By deleting this label, all references will be removed. Continue?",
|
||||
"settingsPageStorageSettingsLabel": "Storage",
|
||||
|
||||
@@ -12,7 +12,7 @@ import 'package:intl/intl_standalone.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/features/labels/bloc/global_state_bloc_provider.dart';
|
||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||
import 'package:paperless_mobile/core/global/asset_images.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
@@ -26,6 +26,7 @@ import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/home/view/home_page.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
|
||||
import 'package:paperless_mobile/features/scan/view/document_upload_page.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
@@ -50,17 +51,18 @@ void main() async {
|
||||
await getIt<ApplicationSettingsCubit>().initialize();
|
||||
await getIt<AuthenticationCubit>().initialize();
|
||||
|
||||
runApp(const MyApp());
|
||||
runApp(const PaperlessMobileEntrypoint());
|
||||
}
|
||||
|
||||
class MyApp extends StatefulWidget {
|
||||
const MyApp({Key? key}) : super(key: key);
|
||||
class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||
const PaperlessMobileEntrypoint({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
State<PaperlessMobileEntrypoint> createState() =>
|
||||
_PaperlessMobileEntrypointState();
|
||||
}
|
||||
|
||||
class _MyAppState extends State<MyApp> {
|
||||
class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
@@ -68,6 +70,7 @@ class _MyAppState extends State<MyApp> {
|
||||
BlocProvider.value(value: getIt<ConnectivityCubit>()),
|
||||
BlocProvider.value(value: getIt<AuthenticationCubit>()),
|
||||
BlocProvider.value(value: getIt<PaperlessServerInformationCubit>()),
|
||||
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
|
||||
BlocProvider.value(value: getIt<ApplicationSettingsCubit>()),
|
||||
],
|
||||
child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||
@@ -172,20 +175,26 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: getIt<DocumentsCubit>(),
|
||||
child: LabelBlocProvider(
|
||||
child: DocumentUploadPage(
|
||||
fileBytes: bytes,
|
||||
afterUpload: () => SystemNavigator.pop(),
|
||||
filename: filename,
|
||||
),
|
||||
builder: (context) => GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider.value(value: getIt<DocumentScannerCubit>()),
|
||||
],
|
||||
child: DocumentUploadPage(
|
||||
fileBytes: bytes,
|
||||
afterUpload: SystemNavigator.pop,
|
||||
filename: filename,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
FlutterNativeSplash.remove();
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -218,14 +227,14 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||
},
|
||||
builder: (context, authentication) {
|
||||
if (authentication.isAuthenticated) {
|
||||
return BlocProvider.value(
|
||||
value: getIt<PaperlessStatisticsCubit>(),
|
||||
child: const LabelBlocProvider(
|
||||
child: HomePage(),
|
||||
),
|
||||
return GlobalStateBlocProvider(
|
||||
additionalProviders: [
|
||||
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
|
||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||
],
|
||||
child: const HomePage(),
|
||||
);
|
||||
} else {
|
||||
FlutterNativeSplash.remove();
|
||||
return const LoginPage();
|
||||
}
|
||||
},
|
||||
|
||||
@@ -54,7 +54,7 @@ void main() async {
|
||||
),
|
||||
build: () => DocumentsCubit(documentRepository),
|
||||
seed: () => DocumentsState.initial,
|
||||
act: (bloc) => bloc.loadDocuments(),
|
||||
act: (bloc) => bloc.load(),
|
||||
expect: () => [
|
||||
DocumentsState(
|
||||
isLoaded: true,
|
||||
@@ -83,7 +83,7 @@ void main() async {
|
||||
),
|
||||
build: () => DocumentsCubit(documentRepository),
|
||||
seed: () => DocumentsState.initial,
|
||||
act: (bloc) => bloc.loadDocuments(),
|
||||
act: (bloc) => bloc.load(),
|
||||
expect: () => [
|
||||
DocumentsState(
|
||||
isLoaded: true,
|
||||
|
||||
Reference in New Issue
Block a user