mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 17:15:50 -06:00
Started removing tight coupling
This commit is contained in:
@@ -1,15 +1,13 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.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/bloc/documents_state.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/bulk_edit.model.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/document.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/document_filter.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/paged_search_result.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:paperless_mobile/features/documents/repository/document_repository.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
|
|
||||||
@singleton
|
@singleton
|
||||||
class DocumentsCubit extends Cubit<DocumentsState> {
|
class DocumentsCubit extends Cubit<DocumentsState> {
|
||||||
@@ -17,45 +15,20 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
|||||||
|
|
||||||
DocumentsCubit(this.documentRepository) : super(DocumentsState.initial);
|
DocumentsCubit(this.documentRepository) : super(DocumentsState.initial);
|
||||||
|
|
||||||
Future<void> addDocument(
|
Future<void> remove(DocumentModel document) async {
|
||||||
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 {
|
|
||||||
await documentRepository.delete(document);
|
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(
|
await documentRepository.bulkAction(
|
||||||
BulkDeleteAction(documents.map((doc) => doc.id)),
|
BulkDeleteAction(documents.map((doc) => doc.id)),
|
||||||
);
|
);
|
||||||
await reloadDocuments();
|
await reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> bulkEditTags(
|
Future<void> bulkEditTags(
|
||||||
List<DocumentModel> documents, {
|
Iterable<DocumentModel> documents, {
|
||||||
Iterable<int> addTags = const [],
|
Iterable<int> addTags = const [],
|
||||||
Iterable<int> removeTags = const [],
|
Iterable<int> removeTags = const [],
|
||||||
}) async {
|
}) async {
|
||||||
@@ -64,15 +37,15 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
|||||||
addTags: addTags,
|
addTags: addTags,
|
||||||
removeTags: removeTags,
|
removeTags: removeTags,
|
||||||
));
|
));
|
||||||
await reloadDocuments();
|
await reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateDocument(DocumentModel document) async {
|
Future<void> update(DocumentModel document) async {
|
||||||
await documentRepository.update(document);
|
await documentRepository.update(document);
|
||||||
await reloadDocuments();
|
await reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadDocuments() async {
|
Future<void> load() async {
|
||||||
final result = await documentRepository.find(state.filter);
|
final result = await documentRepository.find(state.filter);
|
||||||
emit(DocumentsState(
|
emit(DocumentsState(
|
||||||
isLoaded: true,
|
isLoaded: true,
|
||||||
@@ -81,7 +54,7 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reloadDocuments() async {
|
Future<void> reload() async {
|
||||||
if (state.currentPageNumber >= 5) {
|
if (state.currentPageNumber >= 5) {
|
||||||
return _bulkReloadDocuments();
|
return _bulkReloadDocuments();
|
||||||
}
|
}
|
||||||
@@ -113,7 +86,7 @@ class DocumentsCubit extends Cubit<DocumentsState> {
|
|||||||
Future<void> assignAsn(DocumentModel document) async {
|
Future<void> assignAsn(DocumentModel document) async {
|
||||||
if (document.archiveSerialNumber == null) {
|
if (document.archiveSerialNumber == null) {
|
||||||
final int asn = await documentRepository.findNextAsn();
|
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() {
|
void resetSelection() {
|
||||||
emit(state.copyWith(selection: []));
|
emit(state.copyWith(selection: []));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_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/logic/error_code_localization_mapper.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/model/error_message.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/highlighted_text.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");
|
DateFormat("MMM d, yyyy HH:mm:ss");
|
||||||
|
|
||||||
bool _isDownloadPending = false;
|
bool _isDownloadPending = false;
|
||||||
bool _isAssignAsnPending = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -350,7 +349,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
final wasUpdated = await Navigator.push<bool>(
|
final wasUpdated = await Navigator.push<bool>(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => LabelBlocProvider(
|
builder: (_) => GlobalStateBlocProvider(
|
||||||
child: DocumentEditPage(document: document),
|
child: DocumentEditPage(document: document),
|
||||||
),
|
),
|
||||||
maintainState: true,
|
maintainState: true,
|
||||||
@@ -412,7 +411,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
false;
|
false;
|
||||||
if (delete) {
|
if (delete) {
|
||||||
try {
|
try {
|
||||||
await BlocProvider.of<DocumentsCubit>(context).removeDocument(document);
|
await BlocProvider.of<DocumentsCubit>(context).remove(document);
|
||||||
showSnackBar(context, S.of(context).documentDeleteSuccessMessage);
|
showSnackBar(context, S.of(context).documentDeleteSuccessMessage);
|
||||||
} on ErrorMessage catch (error, stackTrace) {
|
} on ErrorMessage catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
});
|
});
|
||||||
bool wasUpdated = false;
|
bool wasUpdated = false;
|
||||||
try {
|
try {
|
||||||
await getIt<DocumentsCubit>().updateDocument(updatedDocument);
|
await getIt<DocumentsCubit>().update(updatedDocument);
|
||||||
showSnackBar(context, S.of(context).documentUpdateErrorMessage);
|
showSnackBar(context, S.of(context).documentUpdateErrorMessage);
|
||||||
wasUpdated = true;
|
wasUpdated = true;
|
||||||
} on ErrorMessage catch (error, stackTrace) {
|
} on ErrorMessage catch (error, stackTrace) {
|
||||||
|
|||||||
@@ -44,15 +44,13 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
if (!BlocProvider.of<DocumentsCubit>(context).state.isLoaded) {
|
_initDocuments();
|
||||||
_initDocuments();
|
|
||||||
}
|
|
||||||
_pagingController.addPageRequestListener(_loadNewPage);
|
_pagingController.addPageRequestListener(_loadNewPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _initDocuments() async {
|
Future<void> _initDocuments() async {
|
||||||
try {
|
try {
|
||||||
BlocProvider.of<DocumentsCubit>(context).loadDocuments();
|
BlocProvider.of<DocumentsCubit>(context).load();
|
||||||
} on ErrorMessage catch (error, stackTrace) {
|
} on ErrorMessage catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
}
|
}
|
||||||
@@ -113,7 +111,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
previous != ConnectivityState.connected &&
|
previous != ConnectivityState.connected &&
|
||||||
current == ConnectivityState.connected,
|
current == ConnectivityState.connected,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
BlocProvider.of<DocumentsCubit>(context).loadDocuments();
|
BlocProvider.of<DocumentsCubit>(context).load();
|
||||||
},
|
},
|
||||||
builder: (context, connectivityState) {
|
builder: (context, connectivityState) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@@ -241,9 +239,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
|||||||
BlocProvider.value(
|
BlocProvider.value(
|
||||||
value: BlocProvider.of<PaperlessStatisticsCubit>(context)),
|
value: BlocProvider.of<PaperlessStatisticsCubit>(context)),
|
||||||
],
|
],
|
||||||
child: DocumentDetailsPage(
|
child: DocumentDetailsPage(documentId: model.id),
|
||||||
documentId: model.id,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ class _DocumentsPageAppBarState extends State<DocumentsPageAppBar> {
|
|||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
try {
|
try {
|
||||||
await BlocProvider.of<DocumentsCubit>(context)
|
await BlocProvider.of<DocumentsCubit>(context)
|
||||||
.bulkRemoveDocuments(documentsState.selection);
|
.bulkRemove(documentsState.selection);
|
||||||
showSnackBar(
|
showSnackBar(
|
||||||
context,
|
context,
|
||||||
S.of(context).documentsPageBulkDeleteSuccessfulText,
|
S.of(context).documentsPageBulkDeleteSuccessfulText,
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import 'dart:developer';
|
|
||||||
import 'dart:isolate';
|
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_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/di_initializer.dart';
|
||||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.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/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/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/bottom_navigation_bar.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/widget/info_drawer.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/inbox/bloc/inbox_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.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/correspondent/bloc/correspondents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_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';
|
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/labels/view/pages/labels_page.dart';
|
||||||
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.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/scan/view/scanner_page.dart';
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
|
||||||
import 'package:paperless_mobile/util.dart';
|
import 'package:paperless_mobile/util.dart';
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
@@ -42,30 +37,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_initializeData(context).then(
|
_initializeData(context);
|
||||||
(_) 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();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -89,15 +61,22 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
drawer: const InfoDrawer(),
|
drawer: const InfoDrawer(),
|
||||||
body: [
|
body: [
|
||||||
BlocProvider.value(
|
MultiBlocProvider(
|
||||||
value: getIt<DocumentsCubit>(),
|
providers: [
|
||||||
|
BlocProvider.value(
|
||||||
|
value: getIt<DocumentsCubit>(),
|
||||||
|
),
|
||||||
|
],
|
||||||
child: const DocumentsPage(),
|
child: const DocumentsPage(),
|
||||||
),
|
),
|
||||||
BlocProvider.value(
|
BlocProvider.value(
|
||||||
value: getIt<DocumentScannerCubit>(),
|
value: getIt<DocumentScannerCubit>(),
|
||||||
child: const ScannerPage(),
|
child: const ScannerPage(),
|
||||||
),
|
),
|
||||||
const LabelsPage(),
|
BlocProvider.value(
|
||||||
|
value: getIt<DocumentsCubit>(),
|
||||||
|
child: const LabelsPage(),
|
||||||
|
),
|
||||||
][_currentIndex],
|
][_currentIndex],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -109,12 +88,12 @@ class _HomePageState extends State<HomePage> {
|
|||||||
return Future.wait([
|
return Future.wait([
|
||||||
BlocProvider.of<PaperlessServerInformationCubit>(context)
|
BlocProvider.of<PaperlessServerInformationCubit>(context)
|
||||||
.updateInformtion(),
|
.updateInformtion(),
|
||||||
BlocProvider.of<PaperlessStatisticsCubit>(context).updateStatistics(),
|
getIt<PaperlessStatisticsCubit>().updateStatistics(),
|
||||||
BlocProvider.of<DocumentTypeCubit>(context).initialize(),
|
getIt<DocumentTypeCubit>().initialize(),
|
||||||
BlocProvider.of<CorrespondentCubit>(context).initialize(),
|
getIt<CorrespondentCubit>().initialize(),
|
||||||
BlocProvider.of<TagCubit>(context).initialize(),
|
getIt<TagCubit>().initialize(),
|
||||||
BlocProvider.of<StoragePathCubit>(context).initialize(),
|
getIt<StoragePathCubit>().initialize(),
|
||||||
BlocProvider.of<SavedViewCubit>(context).initialize(),
|
getIt<SavedViewCubit>().initialize(),
|
||||||
]);
|
]);
|
||||||
} on ErrorMessage catch (error, stackTrace) {
|
} on ErrorMessage catch (error, stackTrace) {
|
||||||
showErrorMessage(context, error, stackTrace);
|
showErrorMessage(context, error, stackTrace);
|
||||||
|
|||||||
@@ -3,14 +3,15 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.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/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/bloc/paperless_server_information_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/model/error_message.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_server_information.dart';
|
||||||
import 'package:paperless_mobile/core/model/paperless_statistics.dart';
|
import 'package:paperless_mobile/core/model/paperless_statistics.dart';
|
||||||
import 'package:paperless_mobile/core/service/paperless_statistics_service.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/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/features/settings/bloc/application_settings_cubit.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.dart';
|
import 'package:paperless_mobile/di_initializer.dart';
|
||||||
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
|
||||||
@@ -127,24 +128,7 @@ class InfoDrawer extends StatelessWidget {
|
|||||||
trailing: state.isLoaded
|
trailing: state.isLoaded
|
||||||
? Text(state.statistics!.documentsInInbox.toString())
|
? Text(state.statistics!.documentsInInbox.toString())
|
||||||
: null,
|
: null,
|
||||||
onTap: () async {
|
onTap: () => _onOpenInbox(context),
|
||||||
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();
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -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() {
|
Link _buildOnboardingImageCredits() {
|
||||||
return Link(
|
return Link(
|
||||||
uri: Uri.parse(
|
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/storage_path/bloc/storage_path_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/bloc/tags_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;
|
final Widget child;
|
||||||
const LabelBlocProvider({super.key, required this.child});
|
const GlobalStateBlocProvider({
|
||||||
|
super.key,
|
||||||
|
this.additionalProviders = const [],
|
||||||
|
required this.child,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -20,6 +25,7 @@ class LabelBlocProvider extends StatelessWidget {
|
|||||||
BlocProvider.value(value: getIt<TagCubit>()),
|
BlocProvider.value(value: getIt<TagCubit>()),
|
||||||
BlocProvider.value(value: getIt<StoragePathCubit>()),
|
BlocProvider.value(value: getIt<StoragePathCubit>()),
|
||||||
BlocProvider.value(value: getIt<SavedViewCubit>()),
|
BlocProvider.value(value: getIt<SavedViewCubit>()),
|
||||||
|
...additionalProviders,
|
||||||
],
|
],
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
@@ -24,7 +24,7 @@ class EditTagPage extends StatelessWidget {
|
|||||||
label: tag,
|
label: tag,
|
||||||
onSubmit: (tag) async {
|
onSubmit: (tag) async {
|
||||||
await BlocProvider.of<TagCubit>(context).replace(tag);
|
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();
|
BlocProvider.of<PaperlessStatisticsCubit>(context).updateStatistics();
|
||||||
},
|
},
|
||||||
onDelete: (tag) => _onDelete(tag, context),
|
onDelete: (tag) => _onDelete(tag, context),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_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/di_initializer.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/documents_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
import 'package:paperless_mobile/features/documents/model/document_filter.dart';
|
||||||
@@ -54,135 +54,130 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider.value(
|
return DefaultTabController(
|
||||||
value: getIt<DocumentsCubit>(),
|
length: 3,
|
||||||
child: DefaultTabController(
|
child: Scaffold(
|
||||||
length: 3,
|
drawer: const InfoDrawer(),
|
||||||
child: Scaffold(
|
appBar: AppBar(
|
||||||
drawer: const InfoDrawer(),
|
title: Text(
|
||||||
appBar: AppBar(
|
[
|
||||||
title: Text(
|
S.of(context).labelsPageCorrespondentsTitleText,
|
||||||
[
|
S.of(context).labelsPageDocumentTypesTitleText,
|
||||||
S.of(context).labelsPageCorrespondentsTitleText,
|
S.of(context).labelsPageTagsTitleText,
|
||||||
S.of(context).labelsPageDocumentTypesTitleText,
|
S.of(context).labelsPageStoragePathTitleText
|
||||||
S.of(context).labelsPageTagsTitleText,
|
][_currentIndex],
|
||||||
S.of(context).labelsPageStoragePathTitleText
|
),
|
||||||
][_currentIndex],
|
actions: [
|
||||||
),
|
IconButton(
|
||||||
actions: [
|
onPressed: _onAddPressed,
|
||||||
IconButton(
|
icon: const Icon(Icons.add),
|
||||||
onPressed: _onAddPressed,
|
)
|
||||||
icon: const Icon(Icons.add),
|
],
|
||||||
)
|
bottom: PreferredSize(
|
||||||
],
|
preferredSize: const Size.fromHeight(kToolbarHeight),
|
||||||
bottom: PreferredSize(
|
child: ColoredBox(
|
||||||
preferredSize: const Size.fromHeight(kToolbarHeight),
|
color: Theme.of(context).bottomAppBarColor,
|
||||||
child: ColoredBox(
|
child: TabBar(
|
||||||
color: Theme.of(context).bottomAppBarColor,
|
indicatorColor: Theme.of(context).colorScheme.primary,
|
||||||
child: TabBar(
|
controller: _tabController,
|
||||||
indicatorColor: Theme.of(context).colorScheme.primary,
|
tabs: [
|
||||||
controller: _tabController,
|
Tab(
|
||||||
tabs: [
|
icon: Icon(
|
||||||
Tab(
|
Icons.person_outline,
|
||||||
icon: Icon(
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
Icons.person_outline,
|
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Tab(
|
),
|
||||||
icon: Icon(
|
Tab(
|
||||||
Icons.description_outlined,
|
icon: Icon(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
Icons.description_outlined,
|
||||||
),
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
Tab(
|
),
|
||||||
icon: Icon(
|
Tab(
|
||||||
Icons.label_outline,
|
icon: Icon(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
Icons.label_outline,
|
||||||
),
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
Tab(
|
),
|
||||||
icon: Icon(
|
Tab(
|
||||||
Icons.folder_open,
|
icon: Icon(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
Icons.folder_open,
|
||||||
),
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
)
|
),
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: TabBarView(
|
),
|
||||||
controller: _tabController,
|
body: TabBarView(
|
||||||
children: [
|
controller: _tabController,
|
||||||
LabelTabView<Correspondent>(
|
children: [
|
||||||
cubit: BlocProvider.of<CorrespondentCubit>(context),
|
LabelTabView<Correspondent>(
|
||||||
filterBuilder: (label) => DocumentFilter(
|
cubit: BlocProvider.of<CorrespondentCubit>(context),
|
||||||
correspondent: CorrespondentQuery.fromId(label.id),
|
filterBuilder: (label) => DocumentFilter(
|
||||||
pageSize: label.documentCount ?? 0,
|
correspondent: CorrespondentQuery.fromId(label.id),
|
||||||
),
|
pageSize: label.documentCount ?? 0,
|
||||||
onOpenEditPage: _openEditCorrespondentPage,
|
|
||||||
emptyStateActionButtonLabel:
|
|
||||||
S.of(context).labelsPageCorrespondentEmptyStateAddNewLabel,
|
|
||||||
emptyStateDescription: S
|
|
||||||
.of(context)
|
|
||||||
.labelsPageCorrespondentEmptyStateDescriptionText,
|
|
||||||
onOpenAddNewPage: _onAddPressed,
|
|
||||||
),
|
),
|
||||||
LabelTabView<DocumentType>(
|
onOpenEditPage: _openEditCorrespondentPage,
|
||||||
cubit: BlocProvider.of<DocumentTypeCubit>(context),
|
emptyStateActionButtonLabel:
|
||||||
filterBuilder: (label) => DocumentFilter(
|
S.of(context).labelsPageCorrespondentEmptyStateAddNewLabel,
|
||||||
documentType: DocumentTypeQuery.fromId(label.id),
|
emptyStateDescription: S
|
||||||
pageSize: label.documentCount ?? 0,
|
.of(context)
|
||||||
),
|
.labelsPageCorrespondentEmptyStateDescriptionText,
|
||||||
onOpenEditPage: _openEditDocumentTypePage,
|
onOpenAddNewPage: _onAddPressed,
|
||||||
emptyStateActionButtonLabel:
|
),
|
||||||
S.of(context).labelsPageDocumentTypeEmptyStateAddNewLabel,
|
LabelTabView<DocumentType>(
|
||||||
emptyStateDescription: S
|
cubit: BlocProvider.of<DocumentTypeCubit>(context),
|
||||||
.of(context)
|
filterBuilder: (label) => DocumentFilter(
|
||||||
.labelsPageDocumentTypeEmptyStateDescriptionText,
|
documentType: DocumentTypeQuery.fromId(label.id),
|
||||||
onOpenAddNewPage: _onAddPressed,
|
pageSize: label.documentCount ?? 0,
|
||||||
),
|
),
|
||||||
LabelTabView<Tag>(
|
onOpenEditPage: _openEditDocumentTypePage,
|
||||||
cubit: BlocProvider.of<TagCubit>(context),
|
emptyStateActionButtonLabel:
|
||||||
filterBuilder: (label) => DocumentFilter(
|
S.of(context).labelsPageDocumentTypeEmptyStateAddNewLabel,
|
||||||
tags: IdsTagsQuery.fromIds([label.id!]),
|
emptyStateDescription:
|
||||||
pageSize: label.documentCount ?? 0,
|
S.of(context).labelsPageDocumentTypeEmptyStateDescriptionText,
|
||||||
),
|
onOpenAddNewPage: _onAddPressed,
|
||||||
onOpenEditPage: _openEditTagPage,
|
),
|
||||||
leadingBuilder: (t) => CircleAvatar(
|
LabelTabView<Tag>(
|
||||||
backgroundColor: t.color,
|
cubit: BlocProvider.of<TagCubit>(context),
|
||||||
child: t.isInboxTag ?? false
|
filterBuilder: (label) => DocumentFilter(
|
||||||
? Icon(
|
tags: IdsTagsQuery.fromIds([label.id!]),
|
||||||
Icons.inbox,
|
pageSize: label.documentCount ?? 0,
|
||||||
color: t.textColor,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
contentBuilder: (t) => Text(t.match ?? ''),
|
|
||||||
emptyStateActionButtonLabel:
|
|
||||||
S.of(context).labelsPageTagsEmptyStateAddNewLabel,
|
|
||||||
emptyStateDescription:
|
|
||||||
S.of(context).labelsPageTagsEmptyStateDescriptionText,
|
|
||||||
onOpenAddNewPage: _onAddPressed,
|
|
||||||
),
|
),
|
||||||
LabelTabView<StoragePath>(
|
onOpenEditPage: _openEditTagPage,
|
||||||
cubit: BlocProvider.of<StoragePathCubit>(context),
|
leadingBuilder: (t) => CircleAvatar(
|
||||||
onOpenEditPage: _openEditStoragePathPage,
|
backgroundColor: t.color,
|
||||||
filterBuilder: (label) => DocumentFilter(
|
child: t.isInboxTag ?? false
|
||||||
storagePath: StoragePathQuery.fromId(label.id),
|
? Icon(
|
||||||
pageSize: label.documentCount ?? 0,
|
Icons.inbox,
|
||||||
),
|
color: t.textColor,
|
||||||
contentBuilder: (path) => Text(path.path ?? ""),
|
)
|
||||||
emptyStateActionButtonLabel:
|
: null,
|
||||||
S.of(context).labelsPageStoragePathEmptyStateAddNewLabel,
|
|
||||||
emptyStateDescription: S
|
|
||||||
.of(context)
|
|
||||||
.labelsPageStoragePathEmptyStateDescriptionText,
|
|
||||||
onOpenAddNewPage: _onAddPressed,
|
|
||||||
),
|
),
|
||||||
],
|
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(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => MultiBlocProvider(
|
builder: (_) => GlobalStateBlocProvider(
|
||||||
providers: [
|
additionalProviders: [
|
||||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
BlocProvider.value(value: BlocProvider.of<DocumentsCubit>(context)),
|
||||||
BlocProvider.value(
|
|
||||||
value: BlocProvider.of<CorrespondentCubit>(context)),
|
|
||||||
],
|
],
|
||||||
child: EditCorrespondentPage(correspondent: correspondent),
|
child: EditCorrespondentPage(correspondent: correspondent),
|
||||||
),
|
),
|
||||||
@@ -208,11 +201,9 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => MultiBlocProvider(
|
builder: (_) => GlobalStateBlocProvider(
|
||||||
providers: [
|
additionalProviders: [
|
||||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
BlocProvider.value(value: BlocProvider.of<DocumentsCubit>(context)),
|
||||||
BlocProvider.value(
|
|
||||||
value: BlocProvider.of<DocumentTypeCubit>(context)),
|
|
||||||
],
|
],
|
||||||
child: EditDocumentTypePage(documentType: docType),
|
child: EditDocumentTypePage(documentType: docType),
|
||||||
),
|
),
|
||||||
@@ -224,10 +215,9 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => MultiBlocProvider(
|
builder: (_) => GlobalStateBlocProvider(
|
||||||
providers: [
|
additionalProviders: [
|
||||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
BlocProvider.value(value: BlocProvider.of<DocumentsCubit>(context)),
|
||||||
BlocProvider.value(value: BlocProvider.of<TagCubit>(context)),
|
|
||||||
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
|
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
|
||||||
],
|
],
|
||||||
child: EditTagPage(tag: tag),
|
child: EditTagPage(tag: tag),
|
||||||
@@ -240,11 +230,9 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => MultiBlocProvider(
|
builder: (_) => GlobalStateBlocProvider(
|
||||||
providers: [
|
additionalProviders: [
|
||||||
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||||
BlocProvider.value(
|
|
||||||
value: BlocProvider.of<StoragePathCubit>(context)),
|
|
||||||
],
|
],
|
||||||
child: EditStoragePathPage(storagePath: path),
|
child: EditStoragePathPage(storagePath: path),
|
||||||
),
|
),
|
||||||
@@ -269,7 +257,7 @@ class _LabelsPageState extends State<LabelsPage>
|
|||||||
case 3:
|
case 3:
|
||||||
page = const AddStoragePathPage();
|
page = const AddStoragePathPage();
|
||||||
}
|
}
|
||||||
return LabelBlocProvider(child: page);
|
return GlobalStateBlocProvider(child: page);
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.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/logic/error_code_localization_mapper.dart';
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
import 'package:paperless_mobile/di_initializer.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/model/document_filter.dart';
|
||||||
import 'package:paperless_mobile/features/documents/repository/document_repository.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/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 {
|
class LabelItem<T extends Label> extends StatelessWidget {
|
||||||
final T label;
|
final T label;
|
||||||
@@ -50,13 +51,13 @@ class LabelItem<T extends Label> extends StatelessWidget {
|
|||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => LabelBlocProvider(
|
builder: (context) => GlobalStateBlocProvider(
|
||||||
child: BlocProvider(
|
additionalProviders: [
|
||||||
create: (context) =>
|
BlocProvider.value(
|
||||||
DocumentsCubit(getIt<DocumentRepository>())
|
value: getIt<LinkedDocumentsCubit>()
|
||||||
..updateFilter(filter: filter),
|
..initialize(filter)),
|
||||||
child: LinkedDocumentsPreview(filter: 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:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
import 'package:injectable/injectable.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>> {
|
class DocumentScannerCubit extends Cubit<List<File>> {
|
||||||
|
final DocumentRepository documentRepository;
|
||||||
|
|
||||||
static List<File> initialState = [];
|
static List<File> initialState = [];
|
||||||
|
|
||||||
DocumentScannerCubit() : super(initialState);
|
DocumentScannerCubit(this.documentRepository) : super(initialState);
|
||||||
|
|
||||||
void addScan(File file) => emit([...state, file]);
|
void addScan(File file) => emit([...state, file]);
|
||||||
|
|
||||||
@@ -40,4 +44,30 @@ class DocumentScannerCubit extends Cubit<List<File>> {
|
|||||||
throw const ErrorMessage(ErrorCode.scanRemoveFailed);
|
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? title;
|
||||||
final String? filename;
|
final String? filename;
|
||||||
final void Function()? afterUpload;
|
final void Function()? afterUpload;
|
||||||
|
final void Function(DocumentModel)? onSuccessfullyConsumed;
|
||||||
|
|
||||||
const DocumentUploadPage({
|
const DocumentUploadPage({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -42,6 +43,7 @@ class DocumentUploadPage extends StatefulWidget {
|
|||||||
this.afterUpload,
|
this.afterUpload,
|
||||||
this.title,
|
this.title,
|
||||||
this.filename,
|
this.filename,
|
||||||
|
this.onSuccessfullyConsumed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -229,6 +231,7 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
|
|||||||
|
|
||||||
void _onSubmit() async {
|
void _onSubmit() async {
|
||||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
||||||
|
final cubit = BlocProvider.of<DocumentScannerCubit>(context);
|
||||||
try {
|
try {
|
||||||
setState(() => _isUploadLoading = true);
|
setState(() => _isUploadLoading = true);
|
||||||
|
|
||||||
@@ -240,17 +243,19 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
|
|||||||
final tags = fv[DocumentModel.tagsKey] as IdsTagsQuery;
|
final tags = fv[DocumentModel.tagsKey] as IdsTagsQuery;
|
||||||
final correspondent =
|
final correspondent =
|
||||||
fv[DocumentModel.correspondentKey] as IdQueryParameter;
|
fv[DocumentModel.correspondentKey] as IdQueryParameter;
|
||||||
await BlocProvider.of<DocumentsCubit>(context).addDocument(
|
|
||||||
|
await cubit.uploadDocument(
|
||||||
widget.fileBytes,
|
widget.fileBytes,
|
||||||
_padWithPdfExtension(_formKey.currentState?.value[fkFileName]),
|
_padWithPdfExtension(_formKey.currentState?.value[fkFileName]),
|
||||||
onConsumptionFinished: _onConsumptionFinished,
|
onConsumptionFinished: widget.onSuccessfullyConsumed,
|
||||||
title: title,
|
title: title,
|
||||||
documentType: docType.id,
|
documentType: docType.id,
|
||||||
correspondent: correspondent.id,
|
correspondent: correspondent.id,
|
||||||
tags: tags.ids,
|
tags: tags.ids,
|
||||||
createdAt: createdAt,
|
createdAt: createdAt,
|
||||||
);
|
);
|
||||||
getIt<DocumentScannerCubit>().reset(); //TODO: Access via provider
|
|
||||||
|
cubit.reset(); //TODO: Access via provider
|
||||||
showSnackBar(context, S.of(context).documentUploadSuccessText);
|
showSnackBar(context, S.of(context).documentUploadSuccessText);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
widget.afterUpload?.call();
|
widget.afterUpload?.call();
|
||||||
@@ -275,24 +280,4 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
|
|||||||
String _formatFilename(String source) {
|
String _formatFilename(String source) {
|
||||||
return source.replaceAll(RegExp(r"[\W_]"), "_");
|
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/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:mime/mime.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/global/constants.dart';
|
||||||
import 'package:paperless_mobile/core/model/error_message.dart';
|
import 'package:paperless_mobile/core/model/error_message.dart';
|
||||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||||
@@ -127,12 +128,17 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
final bytes = await doc.save();
|
final bytes = await doc.save();
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BlocProvider.value(
|
builder: (_) => GlobalStateBlocProvider(
|
||||||
value: getIt<DocumentsCubit>(),
|
additionalProviders: [
|
||||||
child: LabelBlocProvider(
|
BlocProvider<DocumentScannerCubit>.value(
|
||||||
child: DocumentUploadPage(
|
value: BlocProvider.of<DocumentScannerCubit>(context),
|
||||||
fileBytes: bytes,
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
child: DocumentUploadPage(
|
||||||
|
fileBytes: bytes,
|
||||||
|
onSuccessfullyConsumed: (_) =>
|
||||||
|
BlocProvider.of<PaperlessStatisticsCubit>(context)
|
||||||
|
.updateStatistics(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -242,17 +248,20 @@ class _ScannerPageState extends State<ScannerPage>
|
|||||||
// pdf
|
// pdf
|
||||||
fileBytes = file.readAsBytesSync();
|
fileBytes = file.readAsBytesSync();
|
||||||
}
|
}
|
||||||
|
Navigator.of(context).push(
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BlocProvider.value(
|
builder: (_) => GlobalStateBlocProvider(
|
||||||
value: getIt<DocumentsCubit>(),
|
additionalProviders: [
|
||||||
child: LabelBlocProvider(
|
BlocProvider<DocumentScannerCubit>.value(
|
||||||
child: DocumentUploadPage(
|
value: BlocProvider.of<DocumentScannerCubit>(context),
|
||||||
filename: filename,
|
|
||||||
fileBytes: fileBytes,
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
child: DocumentUploadPage(
|
||||||
|
filename: filename,
|
||||||
|
fileBytes: fileBytes,
|
||||||
|
onSuccessfullyConsumed: (_) =>
|
||||||
|
BlocProvider.of<PaperlessStatisticsCubit>(context)
|
||||||
|
.updateStatistics(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -183,7 +183,7 @@
|
|||||||
"labelsPageTagsEmptyStateDescriptionText": "Es wurden noch keine Tags angelegt.",
|
"labelsPageTagsEmptyStateDescriptionText": "Es wurden noch keine Tags angelegt.",
|
||||||
"labelsPageStoragePathEmptyStateAddNewLabel": "Erstelle neuen Speicherpfad",
|
"labelsPageStoragePathEmptyStateAddNewLabel": "Erstelle neuen Speicherpfad",
|
||||||
"labelsPageStoragePathEmptyStateDescriptionText": "Es wurden noch keine Speicherpfade angelegt.",
|
"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",
|
"editLabelPageConfirmDeletionDialogTitle": "Löschen bestätigen",
|
||||||
"editLabelPageDeletionDialogText": "Dieser Kennzeichner wird von Dokumenten referenziert. Durch das Löschen dieses Kennzeichners werden alle Referenzen entfernt. Fortfahren?",
|
"editLabelPageDeletionDialogText": "Dieser Kennzeichner wird von Dokumenten referenziert. Durch das Löschen dieses Kennzeichners werden alle Referenzen entfernt. Fortfahren?",
|
||||||
"settingsPageStorageSettingsLabel": "Speicher",
|
"settingsPageStorageSettingsLabel": "Speicher",
|
||||||
|
|||||||
@@ -184,7 +184,7 @@
|
|||||||
"labelsPageTagsEmptyStateDescriptionText": "You don't seem to have any tags set up.",
|
"labelsPageTagsEmptyStateDescriptionText": "You don't seem to have any tags set up.",
|
||||||
"labelsPageStoragePathEmptyStateAddNewLabel": "Add new storage path",
|
"labelsPageStoragePathEmptyStateAddNewLabel": "Add new storage path",
|
||||||
"labelsPageStoragePathEmptyStateDescriptionText": "You don't seem to have any storage paths set up.",
|
"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",
|
"editLabelPageConfirmDeletionDialogTitle": "Confirm deletion",
|
||||||
"editLabelPageDeletionDialogText": "This label contains references to other documents. By deleting this label, all references will be removed. Continue?",
|
"editLabelPageDeletionDialogText": "This label contains references to other documents. By deleting this label, all references will be removed. Continue?",
|
||||||
"settingsPageStorageSettingsLabel": "Storage",
|
"settingsPageStorageSettingsLabel": "Storage",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import 'package:intl/intl_standalone.dart';
|
|||||||
import 'package:package_info_plus/package_info_plus.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/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/paperless_statistics_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/bloc/paperless_server_information_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/global/asset_images.dart';
|
import 'package:paperless_mobile/core/global/asset_images.dart';
|
||||||
import 'package:paperless_mobile/core/global/constants.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/home/view/home_page.dart';
|
||||||
import 'package:paperless_mobile/features/login/bloc/authentication_cubit.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/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/scan/view/document_upload_page.dart';
|
||||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.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<ApplicationSettingsCubit>().initialize();
|
||||||
await getIt<AuthenticationCubit>().initialize();
|
await getIt<AuthenticationCubit>().initialize();
|
||||||
|
|
||||||
runApp(const MyApp());
|
runApp(const PaperlessMobileEntrypoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatefulWidget {
|
class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||||
const MyApp({Key? key}) : super(key: key);
|
const PaperlessMobileEntrypoint({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MyApp> createState() => _MyAppState();
|
State<PaperlessMobileEntrypoint> createState() =>
|
||||||
|
_PaperlessMobileEntrypointState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyAppState extends State<MyApp> {
|
class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
@@ -68,6 +70,7 @@ class _MyAppState extends State<MyApp> {
|
|||||||
BlocProvider.value(value: getIt<ConnectivityCubit>()),
|
BlocProvider.value(value: getIt<ConnectivityCubit>()),
|
||||||
BlocProvider.value(value: getIt<AuthenticationCubit>()),
|
BlocProvider.value(value: getIt<AuthenticationCubit>()),
|
||||||
BlocProvider.value(value: getIt<PaperlessServerInformationCubit>()),
|
BlocProvider.value(value: getIt<PaperlessServerInformationCubit>()),
|
||||||
|
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
|
||||||
BlocProvider.value(value: getIt<ApplicationSettingsCubit>()),
|
BlocProvider.value(value: getIt<ApplicationSettingsCubit>()),
|
||||||
],
|
],
|
||||||
child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||||
@@ -172,20 +175,26 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => BlocProvider.value(
|
builder: (context) => GlobalStateBlocProvider(
|
||||||
value: getIt<DocumentsCubit>(),
|
additionalProviders: [
|
||||||
child: LabelBlocProvider(
|
BlocProvider.value(value: getIt<DocumentScannerCubit>()),
|
||||||
child: DocumentUploadPage(
|
],
|
||||||
fileBytes: bytes,
|
child: DocumentUploadPage(
|
||||||
afterUpload: () => SystemNavigator.pop(),
|
fileBytes: bytes,
|
||||||
filename: filename,
|
afterUpload: SystemNavigator.pop,
|
||||||
),
|
filename: filename,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
FlutterNativeSplash.remove();
|
||||||
|
super.didChangeDependencies();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -218,14 +227,14 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
},
|
},
|
||||||
builder: (context, authentication) {
|
builder: (context, authentication) {
|
||||||
if (authentication.isAuthenticated) {
|
if (authentication.isAuthenticated) {
|
||||||
return BlocProvider.value(
|
return GlobalStateBlocProvider(
|
||||||
value: getIt<PaperlessStatisticsCubit>(),
|
additionalProviders: [
|
||||||
child: const LabelBlocProvider(
|
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
|
||||||
child: HomePage(),
|
BlocProvider.value(value: getIt<DocumentsCubit>()),
|
||||||
),
|
],
|
||||||
|
child: const HomePage(),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
FlutterNativeSplash.remove();
|
|
||||||
return const LoginPage();
|
return const LoginPage();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ void main() async {
|
|||||||
),
|
),
|
||||||
build: () => DocumentsCubit(documentRepository),
|
build: () => DocumentsCubit(documentRepository),
|
||||||
seed: () => DocumentsState.initial,
|
seed: () => DocumentsState.initial,
|
||||||
act: (bloc) => bloc.loadDocuments(),
|
act: (bloc) => bloc.load(),
|
||||||
expect: () => [
|
expect: () => [
|
||||||
DocumentsState(
|
DocumentsState(
|
||||||
isLoaded: true,
|
isLoaded: true,
|
||||||
@@ -83,7 +83,7 @@ void main() async {
|
|||||||
),
|
),
|
||||||
build: () => DocumentsCubit(documentRepository),
|
build: () => DocumentsCubit(documentRepository),
|
||||||
seed: () => DocumentsState.initial,
|
seed: () => DocumentsState.initial,
|
||||||
act: (bloc) => bloc.loadDocuments(),
|
act: (bloc) => bloc.load(),
|
||||||
expect: () => [
|
expect: () => [
|
||||||
DocumentsState(
|
DocumentsState(
|
||||||
isLoaded: true,
|
isLoaded: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user