mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-10 00:07:54 -06:00
fix: Add custom fields, translations, add app logs to login routes
This commit is contained in:
@@ -3,30 +3,23 @@ import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/transient_error.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'document_bulk_action_state.dart';
|
||||
part 'document_bulk_action_cubit.freezed.dart';
|
||||
|
||||
class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
|
||||
final PaperlessDocumentsApi _documentsApi;
|
||||
final LabelRepository _labelRepository;
|
||||
final DocumentChangedNotifier _notifier;
|
||||
|
||||
DocumentBulkActionCubit(
|
||||
this._documentsApi,
|
||||
this._labelRepository,
|
||||
this._notifier, {
|
||||
required List<DocumentModel> selection,
|
||||
}) : super(
|
||||
DocumentBulkActionState(
|
||||
selection: selection,
|
||||
correspondents: _labelRepository.state.correspondents,
|
||||
documentTypes: _labelRepository.state.documentTypes,
|
||||
storagePaths: _labelRepository.state.storagePaths,
|
||||
tags: _labelRepository.state.tags,
|
||||
),
|
||||
) {
|
||||
_notifier.addListener(
|
||||
@@ -42,19 +35,6 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
|
||||
);
|
||||
},
|
||||
);
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
storagePaths: labels.storagePaths,
|
||||
tags: labels.tags,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> bulkDelete() async {
|
||||
@@ -69,47 +49,74 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
|
||||
}
|
||||
|
||||
Future<void> bulkModifyCorrespondent(int? correspondentId) async {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyLabelAction.correspondent(
|
||||
state.selectedIds,
|
||||
labelId: correspondentId,
|
||||
),
|
||||
);
|
||||
final updatedDocuments = state.selection
|
||||
.where((element) => modifiedDocumentIds.contains(element.id))
|
||||
.map((doc) => doc.copyWith(correspondent: () => correspondentId));
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
try {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyLabelAction.correspondent(
|
||||
state.selectedIds,
|
||||
labelId: correspondentId,
|
||||
),
|
||||
);
|
||||
final updatedDocuments = state.selection
|
||||
.where((element) => modifiedDocumentIds.contains(element.id))
|
||||
.map((doc) => doc.copyWith(correspondent: () => correspondentId));
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
}
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(
|
||||
code: e.code,
|
||||
details: e.details,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> bulkModifyDocumentType(int? documentTypeId) async {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyLabelAction.documentType(
|
||||
state.selectedIds,
|
||||
labelId: documentTypeId,
|
||||
),
|
||||
);
|
||||
final updatedDocuments = state.selection
|
||||
.where((element) => modifiedDocumentIds.contains(element.id))
|
||||
.map((doc) => doc.copyWith(documentType: () => documentTypeId));
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
try {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyLabelAction.documentType(
|
||||
state.selectedIds,
|
||||
labelId: documentTypeId,
|
||||
),
|
||||
);
|
||||
final updatedDocuments = state.selection
|
||||
.where((element) => modifiedDocumentIds.contains(element.id))
|
||||
.map((doc) => doc.copyWith(documentType: () => documentTypeId));
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
}
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(
|
||||
code: e.code,
|
||||
details: e.details,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> bulkModifyStoragePath(int? storagePathId) async {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyLabelAction.storagePath(
|
||||
state.selectedIds,
|
||||
labelId: storagePathId,
|
||||
),
|
||||
);
|
||||
final updatedDocuments = state.selection
|
||||
.where((element) => modifiedDocumentIds.contains(element.id))
|
||||
.map((doc) => doc.copyWith(storagePath: () => storagePathId));
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
try {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyLabelAction.storagePath(
|
||||
state.selectedIds,
|
||||
labelId: storagePathId,
|
||||
),
|
||||
);
|
||||
final updatedDocuments = state.selection
|
||||
.where((element) => modifiedDocumentIds.contains(element.id))
|
||||
.map((doc) => doc.copyWith(storagePath: () => storagePathId));
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
}
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(
|
||||
code: e.code,
|
||||
details: e.details,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,28 +124,36 @@ class DocumentBulkActionCubit extends Cubit<DocumentBulkActionState> {
|
||||
Iterable<int> addTagIds = const [],
|
||||
Iterable<int> removeTagIds = const [],
|
||||
}) async {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyTagsAction(
|
||||
state.selectedIds,
|
||||
addTags: addTagIds,
|
||||
removeTags: removeTagIds,
|
||||
),
|
||||
);
|
||||
final updatedDocuments = state.selection
|
||||
.where((element) => modifiedDocumentIds.contains(element.id))
|
||||
.map((doc) => doc.copyWith(tags: [
|
||||
...doc.tags.toSet().difference(removeTagIds.toSet()),
|
||||
...addTagIds
|
||||
]));
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
try {
|
||||
final modifiedDocumentIds = await _documentsApi.bulkAction(
|
||||
BulkModifyTagsAction(
|
||||
state.selectedIds,
|
||||
addTags: addTagIds,
|
||||
removeTags: removeTagIds,
|
||||
),
|
||||
);
|
||||
final updatedDocuments = state.selection
|
||||
.where((element) => modifiedDocumentIds.contains(element.id))
|
||||
.map((doc) => doc.copyWith(tags: [
|
||||
...doc.tags.toSet().difference(removeTagIds.toSet()),
|
||||
...addTagIds
|
||||
]));
|
||||
for (final doc in updatedDocuments) {
|
||||
_notifier.notifyUpdated(doc);
|
||||
}
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(
|
||||
code: e.code,
|
||||
details: e.details,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_notifier.removeListener(this);
|
||||
_labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
part of 'document_bulk_action_cubit.dart';
|
||||
|
||||
@freezed
|
||||
class DocumentBulkActionState with _$DocumentBulkActionState {
|
||||
const DocumentBulkActionState._();
|
||||
const factory DocumentBulkActionState({
|
||||
required List<DocumentModel> selection,
|
||||
required Map<int, Correspondent> correspondents,
|
||||
required Map<int, DocumentType> documentTypes,
|
||||
required Map<int, Tag> tags,
|
||||
required Map<int, StoragePath> storagePaths,
|
||||
}) = _DocumentBulkActionState;
|
||||
class DocumentBulkActionState {
|
||||
final List<DocumentModel> selection;
|
||||
|
||||
DocumentBulkActionState({
|
||||
required this.selection,
|
||||
});
|
||||
|
||||
Iterable<int> get selectedIds => selection.map((d) => d.id);
|
||||
DocumentBulkActionState copyWith({
|
||||
List<DocumentModel>? selection,
|
||||
}) {
|
||||
return DocumentBulkActionState(
|
||||
selection: selection ?? this.selection,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_fields/fullscreen_selection_form.dart';
|
||||
import 'package:paperless_mobile/core/extensions/dart_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bulk_action_cubit.dart';
|
||||
@@ -35,11 +36,12 @@ class _FullscreenBulkEditTagsWidgetState
|
||||
void initState() {
|
||||
super.initState();
|
||||
final state = context.read<DocumentBulkActionCubit>().state;
|
||||
final labels = context.read<LabelRepository>();
|
||||
_sharedTags = state.selection
|
||||
.map((e) => e.tags)
|
||||
.map((e) => e.toSet())
|
||||
.fold(
|
||||
state.tags.values.map((e) => e.id!).toSet(),
|
||||
labels.tags.values.map((e) => e.id!).toSet(),
|
||||
(previousValue, element) => previousValue.intersection(element),
|
||||
)
|
||||
.toList();
|
||||
@@ -49,14 +51,10 @@ class _FullscreenBulkEditTagsWidgetState
|
||||
.toSet()
|
||||
.difference(_sharedTags.toSet())
|
||||
.toList();
|
||||
_filteredTags = state.tags.keys.toList();
|
||||
_filteredTags = labels.tags.keys.toList();
|
||||
_controller.addListener(() {
|
||||
setState(() {
|
||||
_filteredTags = context
|
||||
.read<DocumentBulkActionCubit>()
|
||||
.state
|
||||
.tags
|
||||
.values
|
||||
_filteredTags = labels.tags.values
|
||||
.where((e) =>
|
||||
e.name.normalized().contains(_controller.text.normalized()))
|
||||
.map((e) => e.id!)
|
||||
@@ -69,6 +67,7 @@ class _FullscreenBulkEditTagsWidgetState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return BlocBuilder<DocumentBulkActionCubit, DocumentBulkActionState>(
|
||||
builder: (context, state) {
|
||||
return FullscreenSelectionForm(
|
||||
@@ -86,7 +85,7 @@ class _FullscreenBulkEditTagsWidgetState
|
||||
selectionBuilder: (context, index) {
|
||||
return _buildTagOption(
|
||||
_filteredTags[index],
|
||||
state.tags,
|
||||
labelRepository.tags,
|
||||
);
|
||||
},
|
||||
selectionCount: _filteredTags.length,
|
||||
@@ -155,11 +154,12 @@ class _FullscreenBulkEditTagsWidgetState
|
||||
void _submit() async {
|
||||
if (_addTags.isNotEmpty || _removeTags.isNotEmpty) {
|
||||
final bloc = context.read<DocumentBulkActionCubit>();
|
||||
final labelRepository = context.read<LabelRepository>();
|
||||
final addNames = _addTags
|
||||
.map((value) => "\"${bloc.state.tags[value]!.name}\"")
|
||||
.map((value) => "\"${labelRepository.tags[value]!.name}\"")
|
||||
.toList();
|
||||
final removeNames = _removeTags
|
||||
.map((value) => "\"${bloc.state.tags[value]!.name}\"")
|
||||
.map((value) => "\"${labelRepository.tags[value]!.name}\"")
|
||||
.toList();
|
||||
final shouldPerformAction = await showDialog<bool>(
|
||||
context: context,
|
||||
|
||||
@@ -4,8 +4,11 @@ import 'dart:io';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:cross_file/cross_file.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/loading_status.dart';
|
||||
import 'package:paperless_mobile/core/bloc/transient_error.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
@@ -15,6 +18,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:printing/printing.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
part 'document_details_cubit.freezed.dart';
|
||||
part 'document_details_state.dart';
|
||||
|
||||
class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
@@ -22,15 +26,13 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
final PaperlessDocumentsApi _api;
|
||||
final DocumentChangedNotifier _notifier;
|
||||
final LocalNotificationService _notificationService;
|
||||
final LabelRepository _labelRepository;
|
||||
|
||||
DocumentDetailsCubit(
|
||||
this._api,
|
||||
this._labelRepository,
|
||||
this._notifier,
|
||||
this._notificationService, {
|
||||
required this.id,
|
||||
}) : super(const DocumentDetailsInitial()) {
|
||||
}) : super(const DocumentDetailsState()) {
|
||||
_notifier.addListener(
|
||||
this,
|
||||
onUpdated: (document) {
|
||||
@@ -42,7 +44,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
|
||||
Future<void> initialize() async {
|
||||
debugPrint("Initialize called");
|
||||
emit(const DocumentDetailsLoading());
|
||||
emit(const DocumentDetailsState(status: LoadingStatus.loading));
|
||||
try {
|
||||
final (document, metaData) = await Future.wait([
|
||||
_api.find(id),
|
||||
@@ -54,11 +56,12 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
// final document = await _api.find(id);
|
||||
// final metaData = await _api.getMetaData(id);
|
||||
debugPrint("Document data loaded for $id");
|
||||
emit(DocumentDetailsLoaded(
|
||||
emit(DocumentDetailsState(
|
||||
status: LoadingStatus.loaded,
|
||||
document: document,
|
||||
metaData: metaData,
|
||||
));
|
||||
} catch (error, stackTrace) {
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
logger.fe(
|
||||
"An error occurred while loading data for document $id.",
|
||||
className: runtimeType.toString(),
|
||||
@@ -66,13 +69,22 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
emit(const DocumentDetailsError());
|
||||
emit(const DocumentDetailsState(status: LoadingStatus.error));
|
||||
addError(
|
||||
TransientPaperlessApiError(code: error.code, details: error.details),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> delete(DocumentModel document) async {
|
||||
await _api.delete(document);
|
||||
_notifier.notifyDeleted(document);
|
||||
try {
|
||||
await _api.delete(document);
|
||||
_notifier.notifyDeleted(document);
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(code: e.code, details: e.details),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> assignAsn(
|
||||
@@ -80,29 +92,34 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
int? asn,
|
||||
bool autoAssign = false,
|
||||
}) async {
|
||||
if (!autoAssign) {
|
||||
final updatedDocument = await _api.update(
|
||||
document.copyWith(archiveSerialNumber: () => asn),
|
||||
try {
|
||||
if (!autoAssign) {
|
||||
final updatedDocument = await _api.update(
|
||||
document.copyWith(archiveSerialNumber: () => asn),
|
||||
);
|
||||
_notifier.notifyUpdated(updatedDocument);
|
||||
} else {
|
||||
final int autoAsn = await _api.findNextAsn();
|
||||
final updatedDocument = await _api
|
||||
.update(document.copyWith(archiveSerialNumber: () => autoAsn));
|
||||
_notifier.notifyUpdated(updatedDocument);
|
||||
}
|
||||
} on PaperlessApiException catch (e) {
|
||||
addError(
|
||||
TransientPaperlessApiError(code: e.code, details: e.details),
|
||||
);
|
||||
_notifier.notifyUpdated(updatedDocument);
|
||||
} else {
|
||||
final int autoAsn = await _api.findNextAsn();
|
||||
final updatedDocument = await _api
|
||||
.update(document.copyWith(archiveSerialNumber: () => autoAsn));
|
||||
_notifier.notifyUpdated(updatedDocument);
|
||||
}
|
||||
}
|
||||
|
||||
Future<ResultType> openDocumentInSystemViewer() async {
|
||||
final s = state;
|
||||
if (s is! DocumentDetailsLoaded) {
|
||||
if (state.status != LoadingStatus.loaded) {
|
||||
throw Exception(
|
||||
"Document cannot be opened in system viewer "
|
||||
"if document information has not yet been loaded.",
|
||||
);
|
||||
}
|
||||
final cacheDir = FileService.instance.temporaryDirectory;
|
||||
final filePath = s.metaData.mediaFilename.replaceAll("/", " ");
|
||||
final filePath = state.metaData!.mediaFilename.replaceAll("/", " ");
|
||||
|
||||
final fileName = "${p.basenameWithoutExtension(filePath)}.pdf";
|
||||
final file = File("${cacheDir.path}/$fileName");
|
||||
@@ -110,7 +127,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
if (!file.existsSync()) {
|
||||
file.createSync();
|
||||
await _api.downloadToFile(
|
||||
s.document,
|
||||
state.document!,
|
||||
file.path,
|
||||
);
|
||||
}
|
||||
@@ -121,14 +138,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
}
|
||||
|
||||
void replace(DocumentModel document) {
|
||||
final s = state;
|
||||
if (s is! DocumentDetailsLoaded) {
|
||||
return;
|
||||
}
|
||||
emit(DocumentDetailsLoaded(
|
||||
document: document,
|
||||
metaData: s.metaData,
|
||||
));
|
||||
emit(state.copyWith(document: document));
|
||||
}
|
||||
|
||||
Future<void> downloadDocument({
|
||||
@@ -136,12 +146,11 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
required String locale,
|
||||
required String userId,
|
||||
}) async {
|
||||
final s = state;
|
||||
if (s is! DocumentDetailsLoaded) {
|
||||
if (state.status != LoadingStatus.loaded) {
|
||||
return;
|
||||
}
|
||||
String targetPath = _buildDownloadFilePath(
|
||||
s.metaData,
|
||||
state.metaData!,
|
||||
downloadOriginal,
|
||||
FileService.instance.downloadsDirectory,
|
||||
);
|
||||
@@ -150,7 +159,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
await File(targetPath).create();
|
||||
} else {
|
||||
await _notificationService.notifyDocumentDownload(
|
||||
document: s.document,
|
||||
document: state.document!,
|
||||
filename: p.basename(targetPath),
|
||||
filePath: targetPath,
|
||||
finished: true,
|
||||
@@ -169,12 +178,12 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
// );
|
||||
|
||||
await _api.downloadToFile(
|
||||
s.document,
|
||||
state.document!,
|
||||
targetPath,
|
||||
original: downloadOriginal,
|
||||
onProgressChanged: (progress) {
|
||||
_notificationService.notifyDocumentDownload(
|
||||
document: s.document,
|
||||
document: state.document!,
|
||||
filename: p.basename(targetPath),
|
||||
filePath: targetPath,
|
||||
finished: true,
|
||||
@@ -185,28 +194,27 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
},
|
||||
);
|
||||
await _notificationService.notifyDocumentDownload(
|
||||
document: s.document,
|
||||
document: state.document!,
|
||||
filename: p.basename(targetPath),
|
||||
filePath: targetPath,
|
||||
finished: true,
|
||||
locale: locale,
|
||||
userId: userId,
|
||||
);
|
||||
logger.fi("Document '${s.document.title}' saved to $targetPath.");
|
||||
logger.fi("Document '${state.document!.title}' saved to $targetPath.");
|
||||
}
|
||||
|
||||
Future<void> shareDocument({bool shareOriginal = false}) async {
|
||||
final s = state;
|
||||
if (s is! DocumentDetailsLoaded) {
|
||||
if (state.status != LoadingStatus.loaded) {
|
||||
return;
|
||||
}
|
||||
String filePath = _buildDownloadFilePath(
|
||||
s.metaData,
|
||||
state.metaData!,
|
||||
shareOriginal,
|
||||
FileService.instance.temporaryDirectory,
|
||||
);
|
||||
await _api.downloadToFile(
|
||||
s.document,
|
||||
state.document!,
|
||||
filePath,
|
||||
original: shareOriginal,
|
||||
);
|
||||
@@ -214,27 +222,26 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
[
|
||||
XFile(
|
||||
filePath,
|
||||
name: s.document.originalFileName,
|
||||
name: state.document!.originalFileName,
|
||||
mimeType: "application/pdf",
|
||||
lastModified: s.document.modified,
|
||||
lastModified: state.document!.modified,
|
||||
),
|
||||
],
|
||||
subject: s.document.title,
|
||||
subject: state.document!.title,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> printDocument() async {
|
||||
final s = state;
|
||||
if (s is! DocumentDetailsLoaded) {
|
||||
if (state.status != LoadingStatus.loaded) {
|
||||
return;
|
||||
}
|
||||
final filePath = _buildDownloadFilePath(
|
||||
s.metaData,
|
||||
state.metaData!,
|
||||
false,
|
||||
FileService.instance.temporaryDirectory,
|
||||
);
|
||||
await _api.downloadToFile(
|
||||
s.document,
|
||||
state.document!,
|
||||
filePath,
|
||||
original: false,
|
||||
);
|
||||
@@ -243,13 +250,16 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
throw Exception("An error occurred while downloading the document.");
|
||||
}
|
||||
Printing.layoutPdf(
|
||||
name: s.document.title,
|
||||
name: state.document!.title,
|
||||
onLayout: (format) => file.readAsBytesSync(),
|
||||
);
|
||||
}
|
||||
|
||||
String _buildDownloadFilePath(
|
||||
DocumentMetaData meta, bool original, Directory dir) {
|
||||
DocumentMetaData meta,
|
||||
bool original,
|
||||
Directory dir,
|
||||
) {
|
||||
final normalizedPath = meta.mediaFilename.replaceAll("/", " ");
|
||||
final extension = original ? p.extension(normalizedPath) : '.pdf';
|
||||
return "${dir.path}/${p.basenameWithoutExtension(normalizedPath)}$extension";
|
||||
@@ -257,7 +267,6 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
_labelRepository.removeListener(this);
|
||||
_notifier.removeListener(this);
|
||||
await super.close();
|
||||
}
|
||||
|
||||
@@ -1,41 +1,10 @@
|
||||
part of 'document_details_cubit.dart';
|
||||
|
||||
sealed class DocumentDetailsState {
|
||||
const DocumentDetailsState();
|
||||
@freezed
|
||||
class DocumentDetailsState with _$DocumentDetailsState {
|
||||
const factory DocumentDetailsState({
|
||||
@Default(LoadingStatus.initial) LoadingStatus status,
|
||||
DocumentModel? document,
|
||||
DocumentMetaData? metaData,
|
||||
}) = _DocumentDetailsState;
|
||||
}
|
||||
|
||||
class DocumentDetailsInitial extends DocumentDetailsState {
|
||||
const DocumentDetailsInitial();
|
||||
}
|
||||
|
||||
class DocumentDetailsLoading extends DocumentDetailsState {
|
||||
const DocumentDetailsLoading();
|
||||
}
|
||||
|
||||
class DocumentDetailsLoaded extends DocumentDetailsState {
|
||||
final DocumentModel document;
|
||||
final DocumentMetaData metaData;
|
||||
|
||||
const DocumentDetailsLoaded({
|
||||
required this.document,
|
||||
required this.metaData,
|
||||
});
|
||||
}
|
||||
|
||||
class DocumentDetailsError extends DocumentDetailsState {
|
||||
const DocumentDetailsError();
|
||||
}
|
||||
|
||||
|
||||
// @freezed
|
||||
// class DocumentDetailsState with _$DocumentDetailsState {
|
||||
// const factory DocumentDetailsState({
|
||||
// required DocumentModel document,
|
||||
// DocumentMetaData? metaData,
|
||||
// @Default(false) bool isFullContentLoaded,
|
||||
// @Default({}) Map<int, Correspondent> correspondents,
|
||||
// @Default({}) Map<int, DocumentType> documentTypes,
|
||||
// @Default({}) Map<int, Tag> tags,
|
||||
// @Default({}) Map<int, StoragePath> storagePaths,
|
||||
// }) = _DocumentDetailsState;
|
||||
// }
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:open_filex/open_filex.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/accessibility/accessibility_utils.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/loading_status.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||
@@ -15,6 +16,7 @@ import 'package:paperless_mobile/features/document_details/view/widgets/document
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_meta_data_widget.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_overview_widget.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_permissions_widget.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/document_share_button.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
@@ -65,7 +67,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
debugPrint(disableAnimations.toString());
|
||||
final hasMultiUserSupport =
|
||||
context.watch<LocalUserAccount>().hasMultiUserSupport;
|
||||
final tabLength = 4 + (hasMultiUserSupport && false ? 1 : 0);
|
||||
final tabLength = 4 + (hasMultiUserSupport ? 1 : 0);
|
||||
return AnnotatedRegion(
|
||||
value: buildOverlayStyle(
|
||||
Theme.of(context),
|
||||
@@ -79,9 +81,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
extendBodyBehindAppBar: false,
|
||||
floatingActionButtonLocation:
|
||||
FloatingActionButtonLocation.endDocked,
|
||||
floatingActionButton: switch (state) {
|
||||
DocumentDetailsLoaded(document: var document) =>
|
||||
_buildEditButton(document),
|
||||
floatingActionButton: switch (state.status) {
|
||||
LoadingStatus.loaded => _buildEditButton(state.document!),
|
||||
_ => null
|
||||
},
|
||||
bottomNavigationBar: _buildBottomAppBar(),
|
||||
@@ -93,9 +94,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
sliver:
|
||||
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
||||
builder: (context, state) {
|
||||
final title = switch (state) {
|
||||
DocumentDetailsLoaded(document: var document) =>
|
||||
document.title,
|
||||
final title = switch (state.status) {
|
||||
LoadingStatus.loaded => state.document!.title,
|
||||
_ => widget.title ?? '',
|
||||
};
|
||||
return SliverAppBar(
|
||||
@@ -201,17 +201,17 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
),
|
||||
),
|
||||
),
|
||||
// if (hasMultiUserSupport && false)
|
||||
// Tab(
|
||||
// child: Text(
|
||||
// "Permissions",
|
||||
// style: TextStyle(
|
||||
// color: Theme.of(context)
|
||||
// .colorScheme
|
||||
// .onPrimaryContainer,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
if (hasMultiUserSupport)
|
||||
Tab(
|
||||
child: Text(
|
||||
"Permissions",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -227,7 +227,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
documentId: widget.id,
|
||||
),
|
||||
child: Padding(
|
||||
@@ -243,17 +242,15 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
switch (state) {
|
||||
DocumentDetailsLoaded(
|
||||
document: var document
|
||||
) =>
|
||||
switch (state.status) {
|
||||
LoadingStatus.loaded =>
|
||||
DocumentOverviewWidget(
|
||||
document: document,
|
||||
document: state.document!,
|
||||
itemSpacing: _itemSpacing,
|
||||
queryString:
|
||||
widget.titleAndContentQueryString,
|
||||
),
|
||||
DocumentDetailsError() => _buildErrorState(),
|
||||
LoadingStatus.error => _buildErrorState(),
|
||||
_ => _buildLoadingState(),
|
||||
},
|
||||
],
|
||||
@@ -264,16 +261,13 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
switch (state) {
|
||||
DocumentDetailsLoaded(
|
||||
document: var document
|
||||
) =>
|
||||
DocumentContentWidget(
|
||||
document: document,
|
||||
switch (state.status) {
|
||||
LoadingStatus.loaded => DocumentContentWidget(
|
||||
document: state.document!,
|
||||
queryString:
|
||||
widget.titleAndContentQueryString,
|
||||
),
|
||||
DocumentDetailsError() => _buildErrorState(),
|
||||
LoadingStatus.error => _buildErrorState(),
|
||||
_ => _buildLoadingState(),
|
||||
}
|
||||
],
|
||||
@@ -284,17 +278,14 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
switch (state) {
|
||||
DocumentDetailsLoaded(
|
||||
document: var document,
|
||||
metaData: var metaData,
|
||||
) =>
|
||||
switch (state.status) {
|
||||
LoadingStatus.loaded =>
|
||||
DocumentMetaDataWidget(
|
||||
document: document,
|
||||
document: state.document!,
|
||||
itemSpacing: _itemSpacing,
|
||||
metaData: metaData,
|
||||
metaData: state.metaData!,
|
||||
),
|
||||
DocumentDetailsError() => _buildErrorState(),
|
||||
LoadingStatus.error => _buildErrorState(),
|
||||
_ => _buildLoadingState(),
|
||||
},
|
||||
],
|
||||
@@ -312,20 +303,25 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
// if (hasMultiUserSupport && false)
|
||||
// CustomScrollView(
|
||||
// controller: _pagingScrollController,
|
||||
// slivers: [
|
||||
// SliverOverlapInjector(
|
||||
// handle: NestedScrollView
|
||||
// .sliverOverlapAbsorberHandleFor(
|
||||
// context),
|
||||
// ),
|
||||
// DocumentPermissionsWidget(
|
||||
// document: state.document,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
if (hasMultiUserSupport)
|
||||
CustomScrollView(
|
||||
controller: _pagingScrollController,
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle: NestedScrollView
|
||||
.sliverOverlapAbsorberHandleFor(
|
||||
context),
|
||||
),
|
||||
switch (state.status) {
|
||||
LoadingStatus.loaded =>
|
||||
DocumentPermissionsWidget(
|
||||
document: state.document!,
|
||||
),
|
||||
LoadingStatus.error => _buildErrorState(),
|
||||
_ => _buildLoadingState(),
|
||||
}
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -383,8 +379,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
return BottomAppBar(
|
||||
child: Builder(
|
||||
builder: (context) {
|
||||
return switch (state) {
|
||||
DocumentDetailsLoaded(document: var document) => Row(
|
||||
return switch (state.status) {
|
||||
LoadingStatus.loaded => Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
ConnectivityAwareActionWrapper(
|
||||
@@ -398,7 +394,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
child: IconButton(
|
||||
tooltip: S.of(context)!.deleteDocumentTooltip,
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () => _onDelete(document),
|
||||
onPressed: () => _onDelete(state.document!),
|
||||
).paddedSymmetrically(horizontal: 4),
|
||||
),
|
||||
ConnectivityAwareActionWrapper(
|
||||
@@ -408,7 +404,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
enabled: false,
|
||||
),
|
||||
child: DocumentDownloadButton(
|
||||
document: document,
|
||||
document: state.document,
|
||||
),
|
||||
),
|
||||
ConnectivityAwareActionWrapper(
|
||||
@@ -422,7 +418,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
onPressed: _onOpenFileInSystemViewer,
|
||||
).paddedOnly(right: 4.0),
|
||||
),
|
||||
DocumentShareButton(document: document),
|
||||
DocumentShareButton(document: state.document),
|
||||
IconButton(
|
||||
tooltip: S.of(context)!.print,
|
||||
onPressed: () => context
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/bloc/loading_status.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
|
||||
@@ -50,16 +51,13 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
||||
context.watch<LocalUserAccount>().paperlessUser.canEditDocuments;
|
||||
return BlocListener<DocumentDetailsCubit, DocumentDetailsState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous is DocumentDetailsLoaded &&
|
||||
current is DocumentDetailsLoaded &&
|
||||
previous.document.archiveSerialNumber !=
|
||||
current.document.archiveSerialNumber,
|
||||
previous.status == LoadingStatus.loaded &&
|
||||
current.status == LoadingStatus.loaded &&
|
||||
previous.document!.archiveSerialNumber !=
|
||||
current.document!.archiveSerialNumber,
|
||||
listener: (context, state) {
|
||||
_asnEditingController.text = (state as DocumentDetailsLoaded)
|
||||
.document
|
||||
.archiveSerialNumber
|
||||
?.toString() ??
|
||||
'';
|
||||
_asnEditingController.text =
|
||||
state.document!.archiveSerialNumber?.toString() ?? '';
|
||||
setState(() {
|
||||
_canUpdate = false;
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/repository/user_repository.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/archive_serial_number_field.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
@@ -69,6 +70,7 @@ class DocumentMetaDataWidget extends StatelessWidget {
|
||||
context: context,
|
||||
label: S.of(context)!.originalMIMEType,
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -27,7 +27,7 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final user = context.watch<LocalUserAccount>().paperlessUser;
|
||||
final availableLabels = context.watch<LabelRepository>().state;
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
|
||||
return SliverList.list(
|
||||
children: [
|
||||
@@ -51,7 +51,7 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
label: S.of(context)!.documentType,
|
||||
content: LabelText<DocumentType>(
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
label: availableLabels.documentTypes[document.documentType],
|
||||
label: labelRepository.documentTypes[document.documentType],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.correspondent != null && user.canViewCorrespondents)
|
||||
@@ -59,14 +59,14 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
label: S.of(context)!.correspondent,
|
||||
content: LabelText<Correspondent>(
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
label: availableLabels.correspondents[document.correspondent],
|
||||
label: labelRepository.correspondents[document.correspondent],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.storagePath != null && user.canViewStoragePaths)
|
||||
DetailsItem(
|
||||
label: S.of(context)!.storagePath,
|
||||
content: LabelText<StoragePath>(
|
||||
label: availableLabels.storagePaths[document.storagePath],
|
||||
label: labelRepository.storagePaths[document.storagePath],
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
if (document.tags.isNotEmpty && user.canViewTags)
|
||||
@@ -77,7 +77,7 @@ class DocumentOverviewWidget extends StatelessWidget {
|
||||
child: TagsWidget(
|
||||
isClickable: false,
|
||||
tags:
|
||||
document.tags.map((e) => availableLabels.tags[e]!).toList(),
|
||||
document.tags.map((e) => labelRepository.tags[e]!).toList(),
|
||||
),
|
||||
),
|
||||
).paddedOnly(bottom: itemSpacing),
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/user_repository.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
|
||||
|
||||
class DocumentPermissionsWidget extends StatefulWidget {
|
||||
final DocumentModel document;
|
||||
@@ -13,8 +16,20 @@ class DocumentPermissionsWidget extends StatefulWidget {
|
||||
class _DocumentPermissionsWidgetState extends State<DocumentPermissionsWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const SliverToBoxAdapter(
|
||||
child: Placeholder(),
|
||||
return BlocBuilder<UserRepository, UserRepositoryState>(
|
||||
builder: (context, state) {
|
||||
final owner = state.users[widget.document.owner];
|
||||
return SliverList.list(
|
||||
children: [
|
||||
if (owner != null)
|
||||
DetailsItem.text(
|
||||
owner.username,
|
||||
label: 'Owner',
|
||||
context: context,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +185,8 @@ class _DocumentEditPageState extends State<DocumentEditPage>
|
||||
|
||||
Padding _buildEditForm(BuildContext context, DocumentEditState state,
|
||||
FieldSuggestions? filteredSuggestions, UserModel currentUser) {
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: TabBarView(
|
||||
@@ -211,8 +213,7 @@ class _DocumentEditPageState extends State<DocumentEditPage>
|
||||
).push<Correspondent>(context),
|
||||
addLabelText: S.of(context)!.addCorrespondent,
|
||||
labelText: S.of(context)!.correspondent,
|
||||
options:
|
||||
context.watch<LabelRepository>().state.correspondents,
|
||||
options: labelRepository.correspondents,
|
||||
initialValue: state.document.correspondent != null
|
||||
? SetIdQueryParameter(
|
||||
id: state.document.correspondent!)
|
||||
@@ -243,8 +244,7 @@ class _DocumentEditPageState extends State<DocumentEditPage>
|
||||
? SetIdQueryParameter(
|
||||
id: state.document.documentType!)
|
||||
: const UnsetIdQueryParameter(),
|
||||
options:
|
||||
context.watch<LabelRepository>().state.documentTypes,
|
||||
options: labelRepository.documentTypes,
|
||||
name: _DocumentEditPageState.fkDocumentType,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
@@ -266,8 +266,7 @@ class _DocumentEditPageState extends State<DocumentEditPage>
|
||||
canCreateNewLabel: currentUser.canCreateStoragePaths,
|
||||
addLabelText: S.of(context)!.addStoragePath,
|
||||
labelText: S.of(context)!.storagePath,
|
||||
options:
|
||||
context.watch<LabelRepository>().state.storagePaths,
|
||||
options: labelRepository.storagePaths,
|
||||
initialValue: state.document.storagePath != null
|
||||
? SetIdQueryParameter(id: state.document.storagePath!)
|
||||
: const UnsetIdQueryParameter(),
|
||||
@@ -280,7 +279,7 @@ class _DocumentEditPageState extends State<DocumentEditPage>
|
||||
// Tag form field
|
||||
if (currentUser.canViewTags)
|
||||
TagsFormField(
|
||||
options: context.watch<LabelRepository>().state.tags,
|
||||
options: labelRepository.tags,
|
||||
name: fkTags,
|
||||
allowOnlySelection: true,
|
||||
allowCreation: true,
|
||||
|
||||
@@ -3,20 +3,24 @@ import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/loading_status.dart';
|
||||
import 'package:paperless_mobile/core/bloc/transient_error.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
|
||||
part 'document_scanner_cubit.freezed.dart';
|
||||
part 'document_scanner_state.dart';
|
||||
|
||||
class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
final LocalNotificationService _notificationService;
|
||||
|
||||
DocumentScannerCubit(this._notificationService)
|
||||
: super(const InitialDocumentScannerState());
|
||||
: super(const DocumentScannerState());
|
||||
|
||||
Future<void> initialize() async {
|
||||
logger.fd(
|
||||
@@ -24,7 +28,7 @@ class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
className: runtimeType.toString(),
|
||||
methodName: "initialize",
|
||||
);
|
||||
emit(const RestoringDocumentScannerState());
|
||||
emit(const DocumentScannerState(status: LoadingStatus.loading));
|
||||
final tempDir = FileService.instance.temporaryScansDirectory;
|
||||
final allFiles = tempDir.list().whereType<File>();
|
||||
final scans =
|
||||
@@ -36,13 +40,14 @@ class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
);
|
||||
emit(
|
||||
scans.isEmpty
|
||||
? const InitialDocumentScannerState()
|
||||
: LoadedDocumentScannerState(scans: scans),
|
||||
? const DocumentScannerState()
|
||||
: DocumentScannerState(scans: scans, status: LoadingStatus.loaded),
|
||||
);
|
||||
}
|
||||
|
||||
void addScan(File file) async {
|
||||
emit(LoadedDocumentScannerState(
|
||||
emit(DocumentScannerState(
|
||||
status: LoadingStatus.loaded,
|
||||
scans: [...state.scans, file],
|
||||
));
|
||||
}
|
||||
@@ -60,21 +65,22 @@ class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
final scans = state.scans..remove(file);
|
||||
emit(
|
||||
scans.isEmpty
|
||||
? const InitialDocumentScannerState()
|
||||
: LoadedDocumentScannerState(scans: scans),
|
||||
? const DocumentScannerState()
|
||||
: DocumentScannerState(
|
||||
status: LoadingStatus.loaded,
|
||||
scans: scans,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reset() async {
|
||||
try {
|
||||
Future.wait([
|
||||
for (final file in state.scans) file.delete(),
|
||||
]);
|
||||
Future.wait([for (final file in state.scans) file.delete()]);
|
||||
imageCache.clear();
|
||||
} catch (_) {
|
||||
throw const PaperlessApiException(ErrorCode.scanRemoveFailed);
|
||||
addError(TransientPaperlessApiError(code: ErrorCode.scanRemoveFailed));
|
||||
} finally {
|
||||
emit(const InitialDocumentScannerState());
|
||||
emit(const DocumentScannerState());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,12 +89,16 @@ class DocumentScannerCubit extends Cubit<DocumentScannerState> {
|
||||
String fileName,
|
||||
String locale,
|
||||
) async {
|
||||
var file = await FileService.instance.saveToFile(bytes, fileName);
|
||||
_notificationService.notifyFileSaved(
|
||||
filename: fileName,
|
||||
filePath: file.path,
|
||||
finished: true,
|
||||
locale: locale,
|
||||
);
|
||||
try {
|
||||
var file = await FileService.instance.saveToFile(bytes, fileName);
|
||||
_notificationService.notifyFileSaved(
|
||||
filename: fileName,
|
||||
filePath: file.path,
|
||||
finished: true,
|
||||
locale: locale,
|
||||
);
|
||||
} on Exception catch (e) {
|
||||
addError(TransientMessageError(message: e.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,9 @@
|
||||
part of 'document_scanner_cubit.dart';
|
||||
|
||||
sealed class DocumentScannerState {
|
||||
final List<File> scans;
|
||||
|
||||
const DocumentScannerState({
|
||||
this.scans = const [],
|
||||
});
|
||||
}
|
||||
|
||||
class InitialDocumentScannerState extends DocumentScannerState {
|
||||
const InitialDocumentScannerState();
|
||||
}
|
||||
|
||||
class RestoringDocumentScannerState extends DocumentScannerState {
|
||||
const RestoringDocumentScannerState({super.scans});
|
||||
}
|
||||
|
||||
class LoadedDocumentScannerState extends DocumentScannerState {
|
||||
const LoadedDocumentScannerState({super.scans});
|
||||
}
|
||||
|
||||
class ErrorDocumentScannerState extends DocumentScannerState {
|
||||
final String message;
|
||||
|
||||
const ErrorDocumentScannerState({
|
||||
required this.message,
|
||||
super.scans,
|
||||
});
|
||||
@freezed
|
||||
class DocumentScannerState with _$DocumentScannerState {
|
||||
const factory DocumentScannerState({
|
||||
@Default(LoadingStatus.initial) LoadingStatus status,
|
||||
@Default([]) List<File> scans,
|
||||
}) = _DocumentScannerState;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/bloc/loading_status.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/global/constants.dart';
|
||||
@@ -78,13 +79,11 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
],
|
||||
body: BlocBuilder<DocumentScannerCubit, DocumentScannerState>(
|
||||
builder: (context, state) {
|
||||
return switch (state) {
|
||||
InitialDocumentScannerState() => _buildEmptyState(),
|
||||
RestoringDocumentScannerState() => Center(
|
||||
child: Text("Restoring..."),
|
||||
),
|
||||
LoadedDocumentScannerState() => _buildImageGrid(state.scans),
|
||||
ErrorDocumentScannerState() => Placeholder(),
|
||||
return switch (state.status) {
|
||||
LoadingStatus.initial => _buildEmptyState(),
|
||||
LoadingStatus.loading => Center(child: Text("Restoring...")),
|
||||
LoadingStatus.loaded => _buildImageGrid(state.scans),
|
||||
LoadingStatus.error => Placeholder(),
|
||||
};
|
||||
},
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/transient_error.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/features/tasks/model/pending_tasks_notifier.dart';
|
||||
@@ -33,24 +34,31 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
||||
DateTime? createdAt,
|
||||
int? asn,
|
||||
}) async {
|
||||
final taskId = await _documentApi.create(
|
||||
bytes,
|
||||
filename: filename,
|
||||
title: title,
|
||||
correspondent: correspondent,
|
||||
documentType: documentType,
|
||||
tags: tags,
|
||||
createdAt: createdAt,
|
||||
asn: asn,
|
||||
onProgressChanged: (progress) {
|
||||
if (!isClosed) {
|
||||
emit(state.copyWith(uploadProgress: progress));
|
||||
}
|
||||
},
|
||||
);
|
||||
if (taskId != null) {
|
||||
_tasksNotifier.listenToTaskChanges(taskId);
|
||||
try {
|
||||
final taskId = await _documentApi.create(
|
||||
bytes,
|
||||
filename: filename,
|
||||
title: title,
|
||||
correspondent: correspondent,
|
||||
documentType: documentType,
|
||||
tags: tags,
|
||||
createdAt: createdAt,
|
||||
asn: asn,
|
||||
onProgressChanged: (progress) {
|
||||
if (!isClosed) {
|
||||
emit(state.copyWith(uploadProgress: progress));
|
||||
}
|
||||
},
|
||||
);
|
||||
if (taskId != null) {
|
||||
_tasksNotifier.listenToTaskChanges(taskId);
|
||||
}
|
||||
return taskId;
|
||||
} on PaperlessApiException catch (error) {
|
||||
addError(TransientPaperlessApiError(
|
||||
code: error.code,
|
||||
details: error.details,
|
||||
));
|
||||
}
|
||||
return taskId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class _DocumentUploadPreparationPageState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labels = context.watch<LabelRepository>().state;
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return BlocBuilder<DocumentUploadCubit, DocumentUploadState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
@@ -242,7 +242,7 @@ class _DocumentUploadPreparationPageState
|
||||
addLabelText: S.of(context)!.addCorrespondent,
|
||||
labelText: S.of(context)!.correspondent + " *",
|
||||
name: DocumentModel.correspondentKey,
|
||||
options: labels.correspondents,
|
||||
options: labelRepository.correspondents,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
allowSelectUnassigned: true,
|
||||
canCreateNewLabel: context
|
||||
@@ -265,7 +265,7 @@ class _DocumentUploadPreparationPageState
|
||||
addLabelText: S.of(context)!.addDocumentType,
|
||||
labelText: S.of(context)!.documentType + " *",
|
||||
name: DocumentModel.documentTypeKey,
|
||||
options: labels.documentTypes,
|
||||
options: labelRepository.documentTypes,
|
||||
prefixIcon:
|
||||
const Icon(Icons.description_outlined),
|
||||
allowSelectUnassigned: true,
|
||||
@@ -283,7 +283,7 @@ class _DocumentUploadPreparationPageState
|
||||
allowCreation: true,
|
||||
allowExclude: false,
|
||||
allowOnlySelection: true,
|
||||
options: labels.tags,
|
||||
options: labelRepository.tags,
|
||||
),
|
||||
Text(
|
||||
"* " + S.of(context)!.uploadInferValuesHint,
|
||||
@@ -353,8 +353,6 @@ class _DocumentUploadPreparationPageState
|
||||
S.of(context)!.documentSuccessfullyUploadedProcessing,
|
||||
);
|
||||
context.pop(DocumentUploadResult(true, taskId));
|
||||
} on PaperlessApiException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} on PaperlessFormValidationException catch (exception) {
|
||||
setState(() => _errors = exception.validationMessages);
|
||||
} catch (error, stackTrace) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||
import 'package:paperless_mobile/core/extensions/document_extensions.dart';
|
||||
@@ -20,7 +18,6 @@ class DocumentsCubit extends Cubit<DocumentsState>
|
||||
@override
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
final LabelRepository _labelRepository;
|
||||
@override
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
|
||||
@@ -32,7 +29,6 @@ class DocumentsCubit extends Cubit<DocumentsState>
|
||||
DocumentsCubit(
|
||||
this.api,
|
||||
this.notifier,
|
||||
this._labelRepository,
|
||||
this._userState,
|
||||
this.connectivityStatusService,
|
||||
) : super(DocumentsState(
|
||||
@@ -58,17 +54,6 @@ class DocumentsCubit extends Cubit<DocumentsState>
|
||||
);
|
||||
},
|
||||
);
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) => emit(
|
||||
state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
storagePaths: labels.storagePaths,
|
||||
tags: labels.tags,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> bulkDelete(List<DocumentModel> documents) async {
|
||||
@@ -111,7 +96,6 @@ class DocumentsCubit extends Cubit<DocumentsState>
|
||||
@override
|
||||
Future<void> close() {
|
||||
notifier.removeListener(this);
|
||||
_labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,6 @@ part of 'documents_cubit.dart';
|
||||
|
||||
class DocumentsState extends DocumentPagingState {
|
||||
final List<DocumentModel> selection;
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
final Map<int, Tag> tags;
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
|
||||
final ViewType viewType;
|
||||
|
||||
@@ -16,10 +12,6 @@ class DocumentsState extends DocumentPagingState {
|
||||
super.filter = const DocumentFilter(),
|
||||
super.hasLoaded = false,
|
||||
super.isLoading = false,
|
||||
this.correspondents = const {},
|
||||
this.documentTypes = const {},
|
||||
this.tags = const {},
|
||||
this.storagePaths = const {},
|
||||
});
|
||||
|
||||
List<int> get selectedIds => selection.map((e) => e.id).toList();
|
||||
@@ -31,10 +23,6 @@ class DocumentsState extends DocumentPagingState {
|
||||
DocumentFilter? filter,
|
||||
List<DocumentModel>? selection,
|
||||
ViewType? viewType,
|
||||
Map<int, Correspondent>? correspondents,
|
||||
Map<int, DocumentType>? documentTypes,
|
||||
Map<int, Tag>? tags,
|
||||
Map<int, StoragePath>? storagePaths,
|
||||
}) {
|
||||
return DocumentsState(
|
||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||
@@ -43,10 +31,6 @@ class DocumentsState extends DocumentPagingState {
|
||||
filter: filter ?? this.filter,
|
||||
selection: selection ?? this.selection,
|
||||
viewType: viewType ?? this.viewType,
|
||||
correspondents: correspondents ?? this.correspondents,
|
||||
documentTypes: documentTypes ?? this.documentTypes,
|
||||
tags: tags ?? this.tags,
|
||||
storagePaths: storagePaths ?? this.storagePaths,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,10 +38,6 @@ class DocumentsState extends DocumentPagingState {
|
||||
List<Object?> get props => [
|
||||
selection,
|
||||
viewType,
|
||||
correspondents,
|
||||
documentTypes,
|
||||
tags,
|
||||
storagePaths,
|
||||
super.filter,
|
||||
super.hasLoaded,
|
||||
super.isLoading,
|
||||
|
||||
@@ -489,10 +489,6 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
initialFilter: context.read<DocumentsCubit>().state.filter,
|
||||
scrollController: controller,
|
||||
draggableSheetController: draggableSheetController,
|
||||
correspondents: state.correspondents,
|
||||
documentTypes: state.documentTypes,
|
||||
storagePaths: state.storagePaths,
|
||||
tags: state.tags,
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -18,6 +18,7 @@ class DateAndDocumentTypeLabelWidget extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final subtitleStyle =
|
||||
Theme.of(context).textTheme.labelMedium?.apply(color: Colors.grey);
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return RichText(
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -37,11 +38,8 @@ class DateAndDocumentTypeLabelWidget extends StatelessWidget {
|
||||
? () => onDocumentTypeSelected!(document.documentType)
|
||||
: null,
|
||||
child: Text(
|
||||
context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.documentTypes[document.documentType]!
|
||||
.name,
|
||||
labelRepository
|
||||
.documentTypes[document.documentType]!.name,
|
||||
style: subtitleStyle,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:hive_flutter/adapters.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/hive/hive_config.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/date_and_document_type_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
@@ -58,7 +55,7 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
final maxHeight = highlights != null
|
||||
? min(600.0, availableHeight)
|
||||
: min(500.0, availableHeight);
|
||||
final labels = context.watch<LabelRepository>().state;
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return Card(
|
||||
color: isSelected ? Theme.of(context).colorScheme.inversePrimary : null,
|
||||
child: InkWell(
|
||||
@@ -93,8 +90,9 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
Align(
|
||||
alignment: Alignment.bottomLeft,
|
||||
child: TagsWidget(
|
||||
tags:
|
||||
document.tags.map((e) => labels.tags[e]!).toList(),
|
||||
tags: document.tags
|
||||
.map((e) => labelRepository.tags[e]!)
|
||||
.toList(),
|
||||
onTagSelected: onTagSelected,
|
||||
).padded(),
|
||||
),
|
||||
@@ -107,7 +105,8 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
correspondent: labels.correspondents[document.correspondent],
|
||||
correspondent:
|
||||
labelRepository.correspondents[document.correspondent],
|
||||
).paddedLTRB(8, 8, 8, 0),
|
||||
Text(
|
||||
document.title.isEmpty ? '(-)' : document.title,
|
||||
|
||||
@@ -30,6 +30,7 @@ class DocumentGridItem extends DocumentItem {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var currentUser = context.watch<LocalUserAccount>().paperlessUser;
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return Stack(
|
||||
children: [
|
||||
Card(
|
||||
@@ -75,10 +76,7 @@ class DocumentGridItem extends DocumentItem {
|
||||
if (currentUser.canViewTags)
|
||||
TagsWidget.sliver(
|
||||
tags: document.tags
|
||||
.map((e) => context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.tags[e]!)
|
||||
.map((e) => labelRepository.tags[e]!)
|
||||
.toList(),
|
||||
onTagSelected: onTagSelected,
|
||||
),
|
||||
@@ -102,17 +100,13 @@ class DocumentGridItem extends DocumentItem {
|
||||
children: [
|
||||
if (currentUser.canViewCorrespondents)
|
||||
CorrespondentWidget(
|
||||
correspondent: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
correspondent: labelRepository
|
||||
.correspondents[document.correspondent],
|
||||
onSelected: onCorrespondentSelected,
|
||||
),
|
||||
if (currentUser.canViewDocumentTypes)
|
||||
DocumentTypeWidget(
|
||||
documentType: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
documentType: labelRepository
|
||||
.documentTypes[document.documentType],
|
||||
onSelected: onDocumentTypeSelected,
|
||||
),
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:paperless_api/src/models/document_model.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/date_and_document_type_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||
@@ -32,7 +29,7 @@ class DocumentListItem extends DocumentItem {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labels = context.watch<LabelRepository>().state;
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
|
||||
return ListTile(
|
||||
tileColor: backgroundColor,
|
||||
@@ -51,10 +48,8 @@ class DocumentListItem extends DocumentItem {
|
||||
absorbing: isSelectionActive,
|
||||
child: CorrespondentWidget(
|
||||
isClickable: isLabelClickable,
|
||||
correspondent: context
|
||||
.watch<LabelRepository>()
|
||||
.state
|
||||
.correspondents[document.correspondent],
|
||||
correspondent:
|
||||
labelRepository.correspondents[document.correspondent],
|
||||
onSelected: onCorrespondentSelected,
|
||||
),
|
||||
),
|
||||
@@ -70,8 +65,8 @@ class DocumentListItem extends DocumentItem {
|
||||
child: TagsWidget(
|
||||
isClickable: isLabelClickable,
|
||||
tags: document.tags
|
||||
.where((e) => labels.tags.containsKey(e))
|
||||
.map((e) => labels.tags[e]!)
|
||||
.where((e) => labelRepository.tags.containsKey(e))
|
||||
.map((e) => labelRepository.tags[e]!)
|
||||
.toList(),
|
||||
onTagSelected: (id) => onTagSelected?.call(id),
|
||||
),
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
@@ -47,10 +48,6 @@ class DocumentFilterForm extends StatefulWidget {
|
||||
final DocumentFilter initialFilter;
|
||||
final ScrollController? scrollController;
|
||||
final EdgeInsets padding;
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
final Map<int, Tag> tags;
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
|
||||
const DocumentFilterForm({
|
||||
super.key,
|
||||
@@ -59,10 +56,6 @@ class DocumentFilterForm extends StatefulWidget {
|
||||
required this.initialFilter,
|
||||
this.scrollController,
|
||||
this.padding = const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
required this.correspondents,
|
||||
required this.documentTypes,
|
||||
required this.tags,
|
||||
required this.storagePaths,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -80,13 +73,14 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return FormBuilder(
|
||||
key: widget.formKey,
|
||||
child: CustomScrollView(
|
||||
controller: widget.scrollController,
|
||||
slivers: [
|
||||
if (widget.header != null) widget.header!,
|
||||
..._buildFormFieldList(),
|
||||
..._buildFormFieldList(labelRepository),
|
||||
const SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: 32,
|
||||
@@ -97,7 +91,7 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildFormFieldList() {
|
||||
List<Widget> _buildFormFieldList(LabelRepository labelRepository) {
|
||||
return [
|
||||
_buildQueryFormField(),
|
||||
Align(
|
||||
@@ -123,10 +117,10 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
_checkQueryConstraints();
|
||||
},
|
||||
),
|
||||
_buildCorrespondentFormField(),
|
||||
_buildDocumentTypeFormField(),
|
||||
_buildStoragePathFormField(),
|
||||
_buildTagsFormField(),
|
||||
_buildCorrespondentFormField(labelRepository.correspondents),
|
||||
_buildDocumentTypeFormField(labelRepository.documentTypes),
|
||||
_buildStoragePathFormField(labelRepository.storagePaths),
|
||||
_buildTagsFormField(labelRepository.tags),
|
||||
]
|
||||
.map((w) => SliverPadding(
|
||||
padding: widget.padding,
|
||||
@@ -151,10 +145,10 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildDocumentTypeFormField() {
|
||||
Widget _buildDocumentTypeFormField(Map<int, DocumentType> documentTypes) {
|
||||
return LabelFormField<DocumentType>(
|
||||
name: DocumentFilterForm.fkDocumentType,
|
||||
options: widget.documentTypes,
|
||||
options: documentTypes,
|
||||
labelText: S.of(context)!.documentType,
|
||||
initialValue: widget.initialFilter.documentType,
|
||||
prefixIcon: const Icon(Icons.description_outlined),
|
||||
@@ -166,10 +160,10 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCorrespondentFormField() {
|
||||
Widget _buildCorrespondentFormField(Map<int, Correspondent> correspondents) {
|
||||
return LabelFormField<Correspondent>(
|
||||
name: DocumentFilterForm.fkCorrespondent,
|
||||
options: widget.correspondents,
|
||||
options: correspondents,
|
||||
labelText: S.of(context)!.correspondent,
|
||||
initialValue: widget.initialFilter.correspondent,
|
||||
prefixIcon: const Icon(Icons.person_outline),
|
||||
@@ -181,10 +175,10 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStoragePathFormField() {
|
||||
Widget _buildStoragePathFormField(Map<int, StoragePath> storagePaths) {
|
||||
return LabelFormField<StoragePath>(
|
||||
name: DocumentFilterForm.fkStoragePath,
|
||||
options: widget.storagePaths,
|
||||
options: storagePaths,
|
||||
labelText: S.of(context)!.storagePath,
|
||||
initialValue: widget.initialFilter.storagePath,
|
||||
prefixIcon: const Icon(Icons.folder_outlined),
|
||||
@@ -202,11 +196,11 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTagsFormField() {
|
||||
Widget _buildTagsFormField(Map<int, Tag> tags) {
|
||||
return TagsFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
initialValue: widget.initialFilter.tags,
|
||||
options: widget.tags,
|
||||
options: tags,
|
||||
allowExclude: false,
|
||||
allowOnlySelection: false,
|
||||
allowCreation: false,
|
||||
|
||||
@@ -13,20 +13,12 @@ class DocumentFilterPanel extends StatefulWidget {
|
||||
final DocumentFilter initialFilter;
|
||||
final ScrollController scrollController;
|
||||
final DraggableScrollableController draggableSheetController;
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
final Map<int, Tag> tags;
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
|
||||
const DocumentFilterPanel({
|
||||
Key? key,
|
||||
required this.initialFilter,
|
||||
required this.scrollController,
|
||||
required this.draggableSheetController,
|
||||
required this.correspondents,
|
||||
required this.documentTypes,
|
||||
required this.tags,
|
||||
required this.storagePaths,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -104,10 +96,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
|
||||
scrollController: widget.scrollController,
|
||||
initialFilter: widget.initialFilter,
|
||||
header: _buildPanelHeader(),
|
||||
correspondents: widget.correspondents,
|
||||
documentTypes: widget.documentTypes,
|
||||
storagePaths: widget.storagePaths,
|
||||
tags: widget.tags,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/translation/sort_field_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
@@ -8,10 +10,6 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
class SortFieldSelectionBottomSheet extends StatefulWidget {
|
||||
final SortOrder initialSortOrder;
|
||||
final SortField? initialSortField;
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
final Map<int, Tag> tags;
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
|
||||
final Future Function(SortField? field, SortOrder order) onSubmit;
|
||||
|
||||
@@ -20,10 +18,6 @@ class SortFieldSelectionBottomSheet extends StatefulWidget {
|
||||
required this.initialSortOrder,
|
||||
required this.initialSortField,
|
||||
required this.onSubmit,
|
||||
required this.correspondents,
|
||||
required this.documentTypes,
|
||||
required this.tags,
|
||||
required this.storagePaths,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -45,6 +39,7 @@ class _SortFieldSelectionBottomSheetState
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labelRepository = context.watch<LabelRepository>();
|
||||
return ClipRRect(
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
@@ -75,7 +70,7 @@ class _SortFieldSelectionBottomSheetState
|
||||
_buildSortOption(SortField.archiveSerialNumber),
|
||||
_buildSortOption(
|
||||
SortField.correspondentName,
|
||||
enabled: widget.correspondents.values.fold<bool>(
|
||||
enabled: labelRepository.correspondents.values.fold<bool>(
|
||||
false,
|
||||
(previousValue, element) =>
|
||||
previousValue || (element.documentCount ?? 0) > 0),
|
||||
@@ -83,7 +78,7 @@ class _SortFieldSelectionBottomSheetState
|
||||
_buildSortOption(SortField.title),
|
||||
_buildSortOption(
|
||||
SortField.documentType,
|
||||
enabled: widget.documentTypes.values.fold<bool>(
|
||||
enabled: labelRepository.documentTypes.values.fold<bool>(
|
||||
false,
|
||||
(previousValue, element) =>
|
||||
previousValue || (element.documentCount ?? 0) > 0),
|
||||
|
||||
@@ -69,10 +69,6 @@ class SortDocumentsButton extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
correspondents: state.correspondents,
|
||||
documentTypes: state.documentTypes,
|
||||
storagePaths: state.storagePaths,
|
||||
tags: state.tags,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'edit_label_state.dart';
|
||||
part 'edit_label_cubit.freezed.dart';
|
||||
|
||||
class EditLabelCubit extends Cubit<EditLabelState>
|
||||
with LabelCubitMixin<EditLabelState> {
|
||||
@override
|
||||
final LabelRepository labelRepository;
|
||||
|
||||
EditLabelCubit(this.labelRepository) : super(const EditLabelState()) {
|
||||
labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) => state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
tags: labels.tags,
|
||||
storagePaths: labels.storagePaths,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
part of 'edit_label_cubit.dart';
|
||||
|
||||
@freezed
|
||||
class EditLabelState with _$EditLabelState {
|
||||
const factory EditLabelState({
|
||||
@Default({}) Map<int, Correspondent> correspondents,
|
||||
@Default({}) Map<int, DocumentType> documentTypes,
|
||||
@Default({}) Map<int, Tag> tags,
|
||||
@Default({}) Map<int, StoragePath> storagePaths,
|
||||
}) = _EditLabelState;
|
||||
}
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class AddLabelPage<T extends Label> extends StatelessWidget {
|
||||
@@ -25,7 +25,7 @@ class AddLabelPage<T extends Label> extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read<LabelRepository>(),
|
||||
),
|
||||
child: AddLabelFormWidget(
|
||||
|
||||
@@ -9,8 +9,8 @@ import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart';
|
||||
import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart';
|
||||
import 'package:paperless_mobile/core/widgets/dialog_utils/pop_with_unsaved_changes.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
|
||||
@@ -35,7 +35,7 @@ class EditLabelPage<T extends Label> extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read<LabelRepository>(),
|
||||
),
|
||||
child: EditLabelForm(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class AddCorrespondentPage extends StatelessWidget {
|
||||
@@ -12,7 +12,7 @@ class AddCorrespondentPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: AddLabelPage<Correspondent>(
|
||||
@@ -20,7 +20,7 @@ class AddCorrespondentPage extends StatelessWidget {
|
||||
fromJsonT: Correspondent.fromJson,
|
||||
initialName: initialName,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().addCorrespondent(label),
|
||||
context.read<LabelCubit>().addCorrespondent(label),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class AddDocumentTypePage extends StatelessWidget {
|
||||
@@ -15,7 +15,7 @@ class AddDocumentTypePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: AddLabelPage<DocumentType>(
|
||||
@@ -23,7 +23,7 @@ class AddDocumentTypePage extends StatelessWidget {
|
||||
fromJsonT: DocumentType.fromJson,
|
||||
initialName: initialName,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().addDocumentType(label),
|
||||
context.read<LabelCubit>().addDocumentType(label),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
@@ -13,7 +13,7 @@ class AddStoragePathPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: AddLabelPage<StoragePath>(
|
||||
@@ -21,7 +21,7 @@ class AddStoragePathPage extends StatelessWidget {
|
||||
fromJsonT: StoragePath.fromJson,
|
||||
initialName: initialName,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().addStoragePath(label),
|
||||
context.read<LabelCubit>().addStoragePath(label),
|
||||
additionalFields: const [
|
||||
StoragePathAutofillFormBuilderField(name: StoragePath.pathKey),
|
||||
SizedBox(height: 120.0),
|
||||
|
||||
@@ -5,8 +5,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/add_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class AddTagPage extends StatelessWidget {
|
||||
@@ -16,15 +16,14 @@ class AddTagPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: AddLabelPage<Tag>(
|
||||
pageTitle: Text(S.of(context)!.addTag),
|
||||
fromJsonT: Tag.fromJson,
|
||||
initialName: initialName,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().addTag(label),
|
||||
onSubmit: (context, label) => context.read<LabelCubit>().addTag(label),
|
||||
additionalFields: [
|
||||
FormBuilderColorPickerField(
|
||||
name: Tag.colorKey,
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
|
||||
class EditCorrespondentPage extends StatelessWidget {
|
||||
final Correspondent correspondent;
|
||||
@@ -13,7 +13,7 @@ class EditCorrespondentPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
lazy: false,
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: Builder(builder: (context) {
|
||||
@@ -21,9 +21,9 @@ class EditCorrespondentPage extends StatelessWidget {
|
||||
label: correspondent,
|
||||
fromJsonT: Correspondent.fromJson,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().replaceCorrespondent(label),
|
||||
context.read<LabelCubit>().replaceCorrespondent(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeCorrespondent(label),
|
||||
context.read<LabelCubit>().removeCorrespondent(label),
|
||||
canDelete: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
|
||||
class EditDocumentTypePage extends StatelessWidget {
|
||||
final DocumentType documentType;
|
||||
@@ -12,16 +12,16 @@ class EditDocumentTypePage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: EditLabelPage<DocumentType>(
|
||||
label: documentType,
|
||||
fromJsonT: DocumentType.fromJson,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().replaceDocumentType(label),
|
||||
context.read<LabelCubit>().replaceDocumentType(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeDocumentType(label),
|
||||
context.read<LabelCubit>().removeDocumentType(label),
|
||||
canDelete: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_autofill_form_builder_field.dart';
|
||||
|
||||
class EditStoragePathPage extends StatelessWidget {
|
||||
@@ -13,16 +13,16 @@ class EditStoragePathPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: EditLabelPage<StoragePath>(
|
||||
label: storagePath,
|
||||
fromJsonT: StoragePath.fromJson,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().replaceStoragePath(label),
|
||||
context.read<LabelCubit>().replaceStoragePath(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeStoragePath(label),
|
||||
context.read<LabelCubit>().removeStoragePath(label),
|
||||
canDelete: context
|
||||
.watch<LocalUserAccount>()
|
||||
.paperlessUser
|
||||
|
||||
@@ -4,8 +4,8 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/widgets/form_builder_fields/form_builder_color_picker.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/edit_label_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class EditTagPage extends StatelessWidget {
|
||||
@@ -16,16 +16,16 @@ class EditTagPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => EditLabelCubit(
|
||||
create: (context) => LabelCubit(
|
||||
context.read(),
|
||||
),
|
||||
child: EditLabelPage<Tag>(
|
||||
label: tag,
|
||||
fromJsonT: Tag.fromJson,
|
||||
onSubmit: (context, label) =>
|
||||
context.read<EditLabelCubit>().replaceTag(label),
|
||||
context.read<LabelCubit>().replaceTag(label),
|
||||
onDelete: (context, label) =>
|
||||
context.read<EditLabelCubit>().removeTag(label),
|
||||
context.read<LabelCubit>().removeTag(label),
|
||||
canDelete:
|
||||
context.watch<LocalUserAccount>().paperlessUser.canDeleteTags,
|
||||
additionalFields: [
|
||||
|
||||
@@ -53,8 +53,8 @@ class HomeShellWidget extends StatelessWidget {
|
||||
builder: (context, box, _) {
|
||||
if (currentUserId == null) {
|
||||
//This only happens during logout...
|
||||
//TODO: Find way so this does not occur anymore
|
||||
return SizedBox.shrink();
|
||||
//FIXME: Find way so this does not occur anymore
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final currentLocalUser = box.get(currentUserId)!;
|
||||
return MultiProvider(
|
||||
@@ -107,36 +107,31 @@ class HomeShellWidget extends StatelessWidget {
|
||||
),
|
||||
if (currentLocalUser.hasMultiUserSupport)
|
||||
Provider(
|
||||
create: (context) => PaperlessUserApiV3Impl(
|
||||
create: (context) => paperlessProviderFactory.createUserApi(
|
||||
context.read<SessionManager>().client,
|
||||
apiVersion: paperlessApiVersion,
|
||||
),
|
||||
),
|
||||
],
|
||||
builder: (context, _) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) {
|
||||
final repo = LabelRepository(context.read());
|
||||
if (currentLocalUser
|
||||
.paperlessUser.canViewCorrespondents) {
|
||||
repo.findAllCorrespondents();
|
||||
}
|
||||
if (currentLocalUser
|
||||
.paperlessUser.canViewDocumentTypes) {
|
||||
repo.findAllDocumentTypes();
|
||||
}
|
||||
if (currentLocalUser.paperlessUser.canViewTags) {
|
||||
repo.findAllTags();
|
||||
}
|
||||
if (currentLocalUser
|
||||
.paperlessUser.canViewStoragePaths) {
|
||||
repo.findAllStoragePaths();
|
||||
}
|
||||
return repo;
|
||||
return LabelRepository(context.read())
|
||||
..initialize(
|
||||
loadCorrespondents: currentLocalUser
|
||||
.paperlessUser.canViewCorrespondents,
|
||||
loadDocumentTypes: currentLocalUser
|
||||
.paperlessUser.canViewDocumentTypes,
|
||||
loadStoragePaths: currentLocalUser
|
||||
.paperlessUser.canViewStoragePaths,
|
||||
loadTags:
|
||||
currentLocalUser.paperlessUser.canViewTags,
|
||||
);
|
||||
},
|
||||
),
|
||||
Provider(
|
||||
ChangeNotifierProvider(
|
||||
create: (context) {
|
||||
final repo = SavedViewRepository(context.read());
|
||||
if (currentLocalUser.paperlessUser.canViewSavedViews) {
|
||||
@@ -145,6 +140,12 @@ class HomeShellWidget extends StatelessWidget {
|
||||
return repo;
|
||||
},
|
||||
),
|
||||
if (currentLocalUser.hasMultiUserSupport)
|
||||
Provider(
|
||||
create: (context) => UserRepository(
|
||||
context.read(),
|
||||
)..initialize(),
|
||||
),
|
||||
],
|
||||
builder: (context, _) {
|
||||
return MultiProvider(
|
||||
@@ -152,7 +153,6 @@ class HomeShellWidget extends StatelessWidget {
|
||||
Provider(
|
||||
lazy: false,
|
||||
create: (context) => DocumentsCubit(
|
||||
context.read(),
|
||||
context.read(),
|
||||
context.read(),
|
||||
Hive.box<LocalUserAppState>(
|
||||
@@ -196,12 +196,6 @@ class HomeShellWidget extends StatelessWidget {
|
||||
context.read(),
|
||||
),
|
||||
),
|
||||
if (currentLocalUser.hasMultiUserSupport)
|
||||
Provider(
|
||||
create: (context) => UserRepository(
|
||||
context.read(),
|
||||
)..initialize(),
|
||||
),
|
||||
],
|
||||
child: child,
|
||||
);
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||
@@ -37,7 +36,7 @@ class InboxCubit extends HydratedCubit<InboxState>
|
||||
this._labelRepository,
|
||||
this.notifier,
|
||||
this.connectivityStatusService,
|
||||
) : super(InboxState(labels: _labelRepository.state)) {
|
||||
) : super(const InboxState()) {
|
||||
notifier.addListener(
|
||||
this,
|
||||
onDeleted: remove,
|
||||
@@ -62,12 +61,6 @@ class InboxCubit extends HydratedCubit<InboxState>
|
||||
}
|
||||
},
|
||||
);
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) {
|
||||
emit(state.copyWith(labels: labels));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -112,7 +105,7 @@ class InboxCubit extends HydratedCubit<InboxState>
|
||||
|
||||
if (inboxTags.isEmpty) {
|
||||
// no inbox tags = no inbox items.
|
||||
return emit(
|
||||
return emit(
|
||||
state.copyWith(
|
||||
hasLoaded: true,
|
||||
value: [],
|
||||
@@ -256,7 +249,6 @@ class InboxCubit extends HydratedCubit<InboxState>
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ part of 'inbox_cubit.dart';
|
||||
class InboxState extends DocumentPagingState {
|
||||
final Iterable<int> inboxTags;
|
||||
|
||||
final LabelRepositoryState labels;
|
||||
|
||||
final int itemsInInboxCount;
|
||||
|
||||
@JsonKey()
|
||||
@@ -19,7 +17,6 @@ class InboxState extends DocumentPagingState {
|
||||
this.inboxTags = const [],
|
||||
this.isHintAcknowledged = false,
|
||||
this.itemsInInboxCount = 0,
|
||||
this.labels = const LabelRepositoryState(),
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -32,7 +29,6 @@ class InboxState extends DocumentPagingState {
|
||||
documents,
|
||||
isHintAcknowledged,
|
||||
itemsInInboxCount,
|
||||
labels,
|
||||
];
|
||||
|
||||
InboxState copyWith({
|
||||
@@ -42,7 +38,6 @@ class InboxState extends DocumentPagingState {
|
||||
List<PagedSearchResult<DocumentModel>>? value,
|
||||
DocumentFilter? filter,
|
||||
bool? isHintAcknowledged,
|
||||
LabelRepositoryState? labels,
|
||||
Map<int, FieldSuggestions>? suggestions,
|
||||
int? itemsInInboxCount,
|
||||
}) {
|
||||
@@ -52,7 +47,6 @@ class InboxState extends DocumentPagingState {
|
||||
value: value ?? super.value,
|
||||
inboxTags: inboxTags ?? this.inboxTags,
|
||||
isHintAcknowledged: isHintAcknowledged ?? this.isHintAcknowledged,
|
||||
labels: labels ?? this.labels,
|
||||
filter: filter ?? super.filter,
|
||||
itemsInInboxCount: itemsInInboxCount ?? this.itemsInInboxCount,
|
||||
);
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/extensions/document_extensions.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/util/lambda_utils.dart';
|
||||
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
@@ -148,6 +150,7 @@ class _InboxItemState extends State<InboxItem> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final labelRepository = context.read<LabelRepository>();
|
||||
return BlocBuilder<InboxCubit, InboxState>(
|
||||
builder: (context, state) {
|
||||
return GestureDetector(
|
||||
@@ -193,7 +196,7 @@ class _InboxItemState extends State<InboxItem> {
|
||||
?.fontSize,
|
||||
),
|
||||
LabelText<Correspondent>(
|
||||
label: state.labels.correspondents[
|
||||
label: labelRepository.correspondents[
|
||||
widget.document.correspondent],
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
placeholder: "-",
|
||||
@@ -208,7 +211,7 @@ class _InboxItemState extends State<InboxItem> {
|
||||
?.fontSize,
|
||||
),
|
||||
LabelText<DocumentType>(
|
||||
label: state.labels.documentTypes[
|
||||
label: labelRepository.documentTypes[
|
||||
widget.document.documentType],
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
placeholder: "-",
|
||||
@@ -217,8 +220,8 @@ class _InboxItemState extends State<InboxItem> {
|
||||
const Spacer(),
|
||||
TagsWidget(
|
||||
tags: widget.document.tags
|
||||
.map((e) => state.labels.tags[e])
|
||||
.whereNot((e) => e == null)
|
||||
.map((e) => labelRepository.tags[e])
|
||||
.where(isNotNull)
|
||||
.toList()
|
||||
.cast<Tag>(),
|
||||
isClickable: false,
|
||||
|
||||
@@ -2,41 +2,134 @@ import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/features/labels/cubit/label_cubit_mixin.dart';
|
||||
|
||||
part 'label_cubit.freezed.dart';
|
||||
part 'label_state.dart';
|
||||
|
||||
class LabelCubit extends Cubit<LabelState> with LabelCubitMixin<LabelState> {
|
||||
@override
|
||||
class LabelCubit extends Cubit<LabelState> {
|
||||
final LabelRepository labelRepository;
|
||||
|
||||
LabelCubit(this.labelRepository) : super(const LabelState()) {
|
||||
labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) {
|
||||
() {
|
||||
emit(state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
storagePaths: labels.storagePaths,
|
||||
tags: labels.tags,
|
||||
correspondents: labelRepository.correspondents,
|
||||
documentTypes: labelRepository.documentTypes,
|
||||
storagePaths: labelRepository.storagePaths,
|
||||
tags: labelRepository.tags,
|
||||
));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reload() {
|
||||
return Future.wait([
|
||||
labelRepository.findAllCorrespondents(),
|
||||
labelRepository.findAllDocumentTypes(),
|
||||
labelRepository.findAllTags(),
|
||||
labelRepository.findAllStoragePaths(),
|
||||
]);
|
||||
Future<void> reload({
|
||||
required bool loadCorrespondents,
|
||||
required bool loadDocumentTypes,
|
||||
required bool loadStoragePaths,
|
||||
required bool loadTags,
|
||||
}) {
|
||||
return labelRepository.initialize(
|
||||
loadCorrespondents: loadCorrespondents,
|
||||
loadDocumentTypes: loadDocumentTypes,
|
||||
loadStoragePaths: loadStoragePaths,
|
||||
loadTags: loadTags,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Correspondent> addCorrespondent(Correspondent item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createCorrespondent(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadCorrespondents() {
|
||||
return labelRepository.findAllCorrespondents();
|
||||
}
|
||||
|
||||
Future<Correspondent> replaceCorrespondent(Correspondent item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateCorrespondent(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeCorrespondent(Correspondent item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.correspondents.containsKey(item.id)) {
|
||||
await labelRepository.deleteCorrespondent(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<DocumentType> addDocumentType(DocumentType item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createDocumentType(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadDocumentTypes() {
|
||||
return labelRepository.findAllDocumentTypes();
|
||||
}
|
||||
|
||||
Future<DocumentType> replaceDocumentType(DocumentType item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateDocumentType(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeDocumentType(DocumentType item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.documentTypes.containsKey(item.id)) {
|
||||
await labelRepository.deleteDocumentType(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<StoragePath> addStoragePath(StoragePath item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createStoragePath(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadStoragePaths() {
|
||||
return labelRepository.findAllStoragePaths();
|
||||
}
|
||||
|
||||
Future<StoragePath> replaceStoragePath(StoragePath item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateStoragePath(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeStoragePath(StoragePath item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.storagePaths.containsKey(item.id)) {
|
||||
await labelRepository.deleteStoragePath(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Tag> addTag(Tag item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createTag(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadTags() {
|
||||
return labelRepository.findAllTags();
|
||||
}
|
||||
|
||||
Future<Tag> replaceTag(Tag item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateTag(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeTag(Tag item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.tags.containsKey(item.id)) {
|
||||
await labelRepository.deleteTag(item);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
|
||||
///
|
||||
/// Mixin which adds functionality to manage labels to [Bloc]s.
|
||||
///
|
||||
mixin LabelCubitMixin<T> on BlocBase<T> {
|
||||
LabelRepository get labelRepository;
|
||||
|
||||
Future<Correspondent> addCorrespondent(Correspondent item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createCorrespondent(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadCorrespondents() {
|
||||
return labelRepository.findAllCorrespondents();
|
||||
}
|
||||
|
||||
Future<Correspondent> replaceCorrespondent(Correspondent item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateCorrespondent(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeCorrespondent(Correspondent item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.state.correspondents.containsKey(item.id)) {
|
||||
await labelRepository.deleteCorrespondent(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<DocumentType> addDocumentType(DocumentType item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createDocumentType(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadDocumentTypes() {
|
||||
return labelRepository.findAllDocumentTypes();
|
||||
}
|
||||
|
||||
Future<DocumentType> replaceDocumentType(DocumentType item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateDocumentType(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeDocumentType(DocumentType item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.state.documentTypes.containsKey(item.id)) {
|
||||
await labelRepository.deleteDocumentType(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<StoragePath> addStoragePath(StoragePath item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createStoragePath(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadStoragePaths() {
|
||||
return labelRepository.findAllStoragePaths();
|
||||
}
|
||||
|
||||
Future<StoragePath> replaceStoragePath(StoragePath item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateStoragePath(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeStoragePath(StoragePath item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.state.storagePaths.containsKey(item.id)) {
|
||||
await labelRepository.deleteStoragePath(item);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Tag> addTag(Tag item) async {
|
||||
assert(item.id == null);
|
||||
final addedItem = await labelRepository.createTag(item);
|
||||
return addedItem;
|
||||
}
|
||||
|
||||
Future<void> reloadTags() {
|
||||
return labelRepository.findAllTags();
|
||||
}
|
||||
|
||||
Future<Tag> replaceTag(Tag item) async {
|
||||
assert(item.id != null);
|
||||
final updatedItem = await labelRepository.updateTag(item);
|
||||
return updatedItem;
|
||||
}
|
||||
|
||||
Future<void> removeTag(Tag item) async {
|
||||
assert(item.id != null);
|
||||
if (labelRepository.state.tags.containsKey(item.id)) {
|
||||
await labelRepository.deleteTag(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,28 +19,13 @@ class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
|
||||
final ConnectivityStatusService connectivityStatusService;
|
||||
@override
|
||||
final DocumentChangedNotifier notifier;
|
||||
|
||||
final LabelRepository _labelRepository;
|
||||
|
||||
LinkedDocumentsCubit(
|
||||
DocumentFilter filter,
|
||||
this.api,
|
||||
this.notifier,
|
||||
this._labelRepository,
|
||||
this.connectivityStatusService,
|
||||
) : super(LinkedDocumentsState(filter: filter)) {
|
||||
updateFilter(filter: filter);
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) {
|
||||
emit(state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
tags: labels.tags,
|
||||
storagePaths: labels.storagePaths,
|
||||
));
|
||||
},
|
||||
);
|
||||
notifier.addListener(
|
||||
this,
|
||||
onUpdated: replace,
|
||||
|
||||
@@ -5,21 +5,12 @@ class LinkedDocumentsState extends DocumentPagingState {
|
||||
@JsonKey()
|
||||
final ViewType viewType;
|
||||
|
||||
final Map<int, Correspondent> correspondents;
|
||||
final Map<int, DocumentType> documentTypes;
|
||||
final Map<int, StoragePath> storagePaths;
|
||||
final Map<int, Tag> tags;
|
||||
|
||||
const LinkedDocumentsState({
|
||||
this.viewType = ViewType.list,
|
||||
super.filter = const DocumentFilter(),
|
||||
super.isLoading,
|
||||
super.hasLoaded,
|
||||
super.value,
|
||||
this.correspondents = const {},
|
||||
this.documentTypes = const {},
|
||||
this.storagePaths = const {},
|
||||
this.tags = const {},
|
||||
});
|
||||
|
||||
LinkedDocumentsState copyWith({
|
||||
@@ -39,10 +30,6 @@ class LinkedDocumentsState extends DocumentPagingState {
|
||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||
value: value ?? this.value,
|
||||
viewType: viewType ?? this.viewType,
|
||||
correspondents: correspondents ?? this.correspondents,
|
||||
documentTypes: documentTypes ?? this.documentTypes,
|
||||
storagePaths: storagePaths ?? this.storagePaths,
|
||||
tags: tags ?? this.tags,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,10 +51,6 @@ class LinkedDocumentsState extends DocumentPagingState {
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
viewType,
|
||||
correspondents,
|
||||
documentTypes,
|
||||
tags,
|
||||
storagePaths,
|
||||
...super.props,
|
||||
];
|
||||
|
||||
|
||||
@@ -112,6 +112,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
/// Switches to another account if it exists.
|
||||
Future<void> switchAccount(String localUserId) async {
|
||||
emit(const SwitchingAccountsState());
|
||||
await FileService.instance.initialize();
|
||||
|
||||
final redactedId = redactUserId(localUserId);
|
||||
logger.fd(
|
||||
'Trying to switch to user $redactedId...',
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
@@ -13,10 +17,13 @@ import 'package:paperless_mobile/features/login/model/client_certificate_form_mo
|
||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||
import 'package:paperless_mobile/features/login/model/reachability_status.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/login_settings_page.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/assets.gen.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routing/routes/app_logs_route.dart';
|
||||
|
||||
class AddAccountPage extends StatefulWidget {
|
||||
final FutureOr<void> Function(
|
||||
@@ -58,10 +65,172 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
bool _isCheckingConnection = false;
|
||||
ReachabilityStatus _reachabilityStatus = ReachabilityStatus.unknown;
|
||||
|
||||
bool _isFormSubmitted = false;
|
||||
|
||||
final _pageController = PageController();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
title: Text(widget.titleText),
|
||||
),
|
||||
body: FormBuilder(
|
||||
key: _formKey,
|
||||
child: AutofillGroup(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Assets.logos.paperlessLogoGreenPng.image(
|
||||
width: 150,
|
||||
height: 150,
|
||||
),
|
||||
Text(
|
||||
'Paperless Mobile',
|
||||
style: Theme.of(context).textTheme.displaySmall,
|
||||
).padded(),
|
||||
SizedBox(height: 24),
|
||||
Expanded(
|
||||
child: PageView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
controller: _pageController,
|
||||
allowImplicitScrolling: false,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
ServerAddressFormField(
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_reachabilityStatus = ReachabilityStatus.unknown;
|
||||
});
|
||||
},
|
||||
).paddedSymmetrically(
|
||||
horizontal: 12,
|
||||
vertical: 12,
|
||||
),
|
||||
ClientCertificateFormField(
|
||||
initialBytes: widget.initialClientCertificate?.bytes,
|
||||
initialPassphrase:
|
||||
widget.initialClientCertificate?.passphrase,
|
||||
).padded(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
//TODO: Move additional headers and client cert to separate page
|
||||
// IconButton.filledTonal(
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).push(
|
||||
// MaterialPageRoute(builder: (context) {
|
||||
// return LoginSettingsPage();
|
||||
// }),
|
||||
// );
|
||||
// },
|
||||
// icon: Icon(Icons.settings),
|
||||
// ),
|
||||
SizedBox(width: 8),
|
||||
FilledButton.icon(
|
||||
onPressed: () async {
|
||||
final status = await _updateReachability();
|
||||
if (status == ReachabilityStatus.reachable) {
|
||||
Future.delayed(1.seconds, () {
|
||||
_pageController.nextPage(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: _isCheckingConnection
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondary,
|
||||
),
|
||||
)
|
||||
: _reachabilityStatus ==
|
||||
ReachabilityStatus.reachable
|
||||
? Icon(Icons.done)
|
||||
: Icon(Icons.arrow_forward),
|
||||
label: Text(S.of(context)!.continueLabel),
|
||||
),
|
||||
],
|
||||
).paddedSymmetrically(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
_buildStatusIndicator().padded(),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
UserCredentialsFormField(
|
||||
formKey: _formKey,
|
||||
initialUsername: widget.initialUsername,
|
||||
initialPassword: widget.initialPassword,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
_pageController.previousPage(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
},
|
||||
icon: Icon(Icons.arrow_back),
|
||||
label: Text(S.of(context)!.edit),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
_onSubmit();
|
||||
},
|
||||
child: Text(S.of(context)!.signIn),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
Text(
|
||||
S.of(context)!.loginRequiredPermissionsHint,
|
||||
style: Theme.of(context).textTheme.bodySmall?.apply(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
).padded(16),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
children: [
|
||||
TextSpan(text: S.of(context)!.version(packageInfo.version)),
|
||||
WidgetSpan(child: SizedBox(width: 24)),
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
text: S.of(context)!.appLogs(''),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
AppLogsRoute().push(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
).padded(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.titleText),
|
||||
@@ -91,7 +260,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
children: [
|
||||
ServerAddressFormField(
|
||||
initialValue: widget.initialServerUrl,
|
||||
onSubmit: (address) {
|
||||
onChanged: (address) {
|
||||
_updateReachability(address);
|
||||
},
|
||||
).padded(),
|
||||
@@ -117,7 +286,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
).padded(16),
|
||||
]
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -125,7 +294,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateReachability([String? address]) async {
|
||||
Future<ReachabilityStatus> _updateReachability([String? address]) async {
|
||||
setState(() {
|
||||
_isCheckingConnection = true;
|
||||
});
|
||||
@@ -150,13 +319,10 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
_isCheckingConnection = false;
|
||||
_reachabilityStatus = status;
|
||||
});
|
||||
return status;
|
||||
}
|
||||
|
||||
Widget _buildStatusIndicator() {
|
||||
if (_isCheckingConnection) {
|
||||
return const ListTile();
|
||||
}
|
||||
|
||||
Widget _buildIconText(
|
||||
IconData icon,
|
||||
String text, [
|
||||
@@ -176,14 +342,6 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
|
||||
Color errorColor = Theme.of(context).colorScheme.error;
|
||||
switch (_reachabilityStatus) {
|
||||
case ReachabilityStatus.unknown:
|
||||
return Container();
|
||||
case ReachabilityStatus.reachable:
|
||||
return _buildIconText(
|
||||
Icons.done,
|
||||
S.of(context)!.connectionSuccessfulylEstablished,
|
||||
Colors.green,
|
||||
);
|
||||
case ReachabilityStatus.notReachable:
|
||||
return _buildIconText(
|
||||
Icons.close,
|
||||
@@ -214,6 +372,8 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
S.of(context)!.connectionTimedOut,
|
||||
errorColor,
|
||||
);
|
||||
default:
|
||||
return const ListTile();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'obscured_input_text_form_field.dart';
|
||||
|
||||
class ClientCertificateFormField extends StatefulWidget {
|
||||
@@ -16,10 +17,10 @@ class ClientCertificateFormField extends StatefulWidget {
|
||||
final String? initialPassphrase;
|
||||
final Uint8List? initialBytes;
|
||||
|
||||
final void Function(ClientCertificateFormModel? cert) onChanged;
|
||||
final ValueChanged<ClientCertificateFormModel?>? onChanged;
|
||||
const ClientCertificateFormField({
|
||||
super.key,
|
||||
required this.onChanged,
|
||||
this.onChanged,
|
||||
this.initialPassphrase,
|
||||
this.initialBytes,
|
||||
});
|
||||
@@ -29,13 +30,15 @@ class ClientCertificateFormField extends StatefulWidget {
|
||||
_ClientCertificateFormFieldState();
|
||||
}
|
||||
|
||||
class _ClientCertificateFormFieldState
|
||||
extends State<ClientCertificateFormField> {
|
||||
class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
File? _selectedFile;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FormBuilderField<ClientCertificateFormModel?>(
|
||||
key: const ValueKey('login-client-cert'),
|
||||
name: ClientCertificateFormField.fkClientCertificate,
|
||||
onChanged: widget.onChanged,
|
||||
initialValue: widget.initialBytes != null
|
||||
? ClientCertificateFormModel(
|
||||
@@ -43,16 +46,6 @@ class _ClientCertificateFormFieldState
|
||||
passphrase: widget.initialPassphrase,
|
||||
)
|
||||
: null,
|
||||
validator: (value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
assert(_selectedFile != null);
|
||||
if (_selectedFile?.path.split(".").last != 'pfx') {
|
||||
return S.of(context)!.invalidCertificateFormat;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
builder: (field) {
|
||||
final theme =
|
||||
Theme.of(context).copyWith(dividerColor: Colors.transparent); //new
|
||||
@@ -127,7 +120,6 @@ class _ClientCertificateFormFieldState
|
||||
),
|
||||
);
|
||||
},
|
||||
name: ClientCertificateFormField.fkClientCertificate,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,6 +132,11 @@ class _ClientCertificateFormFieldState
|
||||
if (result == null || result.files.single.path == null) {
|
||||
return;
|
||||
}
|
||||
final path = result.files.single.path!;
|
||||
if (p.extension(path) != '.pfx') {
|
||||
showSnackBar(context, S.of(context)!.invalidCertificateFormat);
|
||||
return;
|
||||
}
|
||||
File file = File(result.files.single.path!);
|
||||
setState(() {
|
||||
_selectedFile = file;
|
||||
@@ -171,4 +168,7 @@ class _ClientCertificateFormFieldState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class LoginSettingsPage extends StatelessWidget {
|
||||
const LoginSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context)!.settings),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
ClientCertificateFormField(onChanged: (certificate) {}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,11 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
class ServerAddressFormField extends StatefulWidget {
|
||||
static const String fkServerAddress = "serverAddress";
|
||||
final String? initialValue;
|
||||
final void Function(String? address) onSubmit;
|
||||
final ValueChanged<String?>? onChanged;
|
||||
|
||||
const ServerAddressFormField({
|
||||
Key? key,
|
||||
required this.onSubmit,
|
||||
this.onChanged,
|
||||
this.initialValue,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -20,8 +21,10 @@ class ServerAddressFormField extends StatefulWidget {
|
||||
State<ServerAddressFormField> createState() => _ServerAddressFormFieldState();
|
||||
}
|
||||
|
||||
class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
class _ServerAddressFormFieldState extends State<ServerAddressFormField>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
bool _canClear = false;
|
||||
final _textFieldKey = GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -38,10 +41,12 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FormBuilderField<String>(
|
||||
initialValue: widget.initialValue,
|
||||
name: ServerAddressFormField.fkServerAddress,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
onChanged: widget.onChanged,
|
||||
builder: (field) {
|
||||
return RawAutocomplete<String>(
|
||||
focusNode: _focusNode,
|
||||
@@ -51,6 +56,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
onSelected: onSelected,
|
||||
options: options,
|
||||
maxOptionsHeight: 200.0,
|
||||
maxWidth: MediaQuery.sizeOf(context).width - 40,
|
||||
);
|
||||
},
|
||||
key: const ValueKey('login-server-address'),
|
||||
@@ -60,12 +66,12 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
.where((element) => element.contains(textEditingValue.text));
|
||||
},
|
||||
onSelected: (option) {
|
||||
_formatInput();
|
||||
field.didChange(_textEditingController.text);
|
||||
_formatInput(field);
|
||||
},
|
||||
fieldViewBuilder:
|
||||
(context, textEditingController, focusNode, onFieldSubmitted) {
|
||||
return TextFormField(
|
||||
key: _textFieldKey,
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
decoration: InputDecoration(
|
||||
@@ -78,15 +84,22 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
onPressed: () {
|
||||
textEditingController.clear();
|
||||
field.didChange(textEditingController.text);
|
||||
widget.onSubmit(textEditingController.text);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
),
|
||||
autofocus: false,
|
||||
onFieldSubmitted: (_) {
|
||||
_formatInput(field);
|
||||
onFieldSubmitted();
|
||||
_formatInput();
|
||||
},
|
||||
onTapOutside: (event) {
|
||||
if (!FocusScope.of(context).hasFocus) {
|
||||
return;
|
||||
}
|
||||
_formatInput(field);
|
||||
onFieldSubmitted();
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (value) {
|
||||
@@ -113,7 +126,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
);
|
||||
}
|
||||
|
||||
void _formatInput() {
|
||||
void _formatInput(FormFieldState<String> field) {
|
||||
String address = _textEditingController.text.trim();
|
||||
address = address.replaceAll(RegExp(r'^\/+|\/+$'), '');
|
||||
_textEditingController.text = address;
|
||||
@@ -121,8 +134,11 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
baseOffset: address.length,
|
||||
extentOffset: address.length,
|
||||
);
|
||||
widget.onSubmit(address);
|
||||
field.didChange(_textEditingController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
/// Taken from [Autocomplete]
|
||||
@@ -131,12 +147,14 @@ class _AutocompleteOptions extends StatelessWidget {
|
||||
required this.onSelected,
|
||||
required this.options,
|
||||
required this.maxOptionsHeight,
|
||||
required this.maxWidth,
|
||||
});
|
||||
|
||||
final AutocompleteOnSelected<String> onSelected;
|
||||
|
||||
final Iterable<String> options;
|
||||
final double maxOptionsHeight;
|
||||
final double maxWidth;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -145,7 +163,10 @@ class _AutocompleteOptions extends StatelessWidget {
|
||||
child: Material(
|
||||
elevation: 4.0,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: maxOptionsHeight),
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: maxOptionsHeight,
|
||||
maxWidth: maxWidth,
|
||||
),
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
|
||||
@@ -12,13 +12,13 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
class UserCredentialsFormField extends StatefulWidget {
|
||||
static const fkCredentials = 'credentials';
|
||||
|
||||
final void Function() onFieldsSubmitted;
|
||||
final VoidCallback? onFieldsSubmitted;
|
||||
final String? initialUsername;
|
||||
final String? initialPassword;
|
||||
final GlobalKey<FormBuilderState> formKey;
|
||||
const UserCredentialsFormField({
|
||||
Key? key,
|
||||
required this.onFieldsSubmitted,
|
||||
this.onFieldsSubmitted,
|
||||
this.initialUsername,
|
||||
this.initialPassword,
|
||||
required this.formKey,
|
||||
@@ -29,12 +29,14 @@ class UserCredentialsFormField extends StatefulWidget {
|
||||
_UserCredentialsFormFieldState();
|
||||
}
|
||||
|
||||
class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
class _UserCredentialsFormFieldState extends State<UserCredentialsFormField>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final _usernameFocusNode = FocusNode();
|
||||
final _passwordFocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FormBuilderField<LoginFormCredentials?>(
|
||||
initialValue: LoginFormCredentials(
|
||||
password: widget.initialPassword,
|
||||
@@ -87,7 +89,7 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
LoginFormCredentials(password: password),
|
||||
),
|
||||
onFieldSubmitted: (_) {
|
||||
widget.onFieldsSubmitted();
|
||||
widget.onFieldsSubmitted?.call();
|
||||
},
|
||||
validator: (value) {
|
||||
if (value?.trim().isEmpty ?? true) {
|
||||
@@ -100,6 +102,9 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/routing/routes/app_logs_route.dart';
|
||||
import 'package:paperless_mobile/theme.dart';
|
||||
|
||||
class LoginTransitionPage extends StatelessWidget {
|
||||
@@ -20,10 +22,25 @@ class LoginTransitionPage extends StatelessWidget {
|
||||
body: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 16),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Text(text).paddedOnly(bottom: 24),
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Text(text).paddedOnly(bottom: 24),
|
||||
child: TextButton(
|
||||
child: Text(S.of(context)!.appLogs('')),
|
||||
onPressed: () {
|
||||
AppLogsRoute().push(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded(16),
|
||||
|
||||
@@ -13,17 +13,14 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
|
||||
SavedViewCubit(this._savedViewRepository)
|
||||
: super(const SavedViewState.initial()) {
|
||||
_savedViewRepository.addListener(
|
||||
this,
|
||||
onChanged: (views) {
|
||||
views.when(
|
||||
initial: (savedViews) => emit(const SavedViewState.initial()),
|
||||
loading: (savedViews) => emit(const SavedViewState.loading()),
|
||||
loaded: (savedViews) =>
|
||||
emit(SavedViewState.loaded(savedViews: savedViews)),
|
||||
error: (savedViews) => emit(const SavedViewState.error()),
|
||||
);
|
||||
},
|
||||
_savedViewRepository.addListener(_onSavedViewsChanged);
|
||||
}
|
||||
|
||||
void _onSavedViewsChanged() {
|
||||
emit(
|
||||
SavedViewState.loaded(
|
||||
savedViews: _savedViewRepository.savedViews,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -53,7 +50,7 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
_savedViewRepository.removeListener(this);
|
||||
_savedViewRepository.removeListener(_onSavedViewsChanged);
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,32 +34,13 @@ class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState>
|
||||
required this.savedView,
|
||||
int initialCount = 25,
|
||||
}) : super(
|
||||
SavedViewDetailsState(
|
||||
correspondents: _labelRepository.state.correspondents,
|
||||
documentTypes: _labelRepository.state.documentTypes,
|
||||
tags: _labelRepository.state.tags,
|
||||
storagePaths: _labelRepository.state.storagePaths,
|
||||
viewType: _userState.savedViewsViewType,
|
||||
),
|
||||
SavedViewDetailsState(viewType: _userState.savedViewsViewType),
|
||||
) {
|
||||
notifier.addListener(
|
||||
this,
|
||||
onDeleted: remove,
|
||||
onUpdated: replace,
|
||||
);
|
||||
_labelRepository.addListener(
|
||||
this,
|
||||
onChanged: (labels) {
|
||||
if (!isClosed) {
|
||||
emit(state.copyWith(
|
||||
correspondents: labels.correspondents,
|
||||
documentTypes: labels.documentTypes,
|
||||
tags: labels.tags,
|
||||
storagePaths: labels.storagePaths,
|
||||
));
|
||||
}
|
||||
},
|
||||
);
|
||||
updateFilter(
|
||||
filter: savedView.toDocumentFilter().copyWith(
|
||||
page: 1,
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:paperless_mobile/core/database/hive/hive_extensions.dart';
|
||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/features/logging/data/logger.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||
|
||||
@@ -17,15 +18,12 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
||||
@override
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
final LabelRepository _labelRepository;
|
||||
|
||||
@override
|
||||
final DocumentChangedNotifier notifier;
|
||||
|
||||
SimilarDocumentsCubit(
|
||||
this.api,
|
||||
this.notifier,
|
||||
this._labelRepository,
|
||||
this.connectivityStatusService, {
|
||||
required this.documentId,
|
||||
}) : super(const SimilarDocumentsState(filter: DocumentFilter())) {
|
||||
@@ -39,19 +37,30 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
||||
@override
|
||||
Future<void> initialize() async {
|
||||
if (!state.hasLoaded) {
|
||||
await updateFilter(
|
||||
filter: state.filter.copyWith(
|
||||
moreLike: () => documentId,
|
||||
sortField: SortField.score,
|
||||
),
|
||||
);
|
||||
try {
|
||||
await updateFilter(
|
||||
filter: state.filter.copyWith(
|
||||
moreLike: () => documentId,
|
||||
sortField: SortField.score,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(error: null));
|
||||
} on PaperlessApiException catch (e, stackTrace) {
|
||||
logger.fe(
|
||||
"An error occurred while loading similar documents for document $documentId",
|
||||
className: "SimilarDocumentsCubit",
|
||||
methodName: "initialize",
|
||||
error: e.details,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
emit(state.copyWith(error: e.code));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() {
|
||||
notifier.removeListener(this);
|
||||
_labelRepository.removeListener(this);
|
||||
return super.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
part of 'similar_documents_cubit.dart';
|
||||
|
||||
class SimilarDocumentsState extends DocumentPagingState {
|
||||
final ErrorCode? error;
|
||||
const SimilarDocumentsState({
|
||||
required super.filter,
|
||||
super.hasLoaded,
|
||||
super.isLoading,
|
||||
super.value,
|
||||
this.error,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
List<Object?> get props => [
|
||||
filter,
|
||||
hasLoaded,
|
||||
isLoading,
|
||||
value,
|
||||
error,
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -36,12 +39,14 @@ class SimilarDocumentsState extends DocumentPagingState {
|
||||
bool? isLoading,
|
||||
List<PagedSearchResult<DocumentModel>>? value,
|
||||
DocumentFilter? filter,
|
||||
ErrorCode? error,
|
||||
}) {
|
||||
return SimilarDocumentsState(
|
||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
value: value ?? this.value,
|
||||
filter: filter ?? this.filter,
|
||||
error: error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
import 'package:paperless_mobile/core/extensions/document_extensions.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||
@@ -49,6 +51,16 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
||||
child: OfflineWidget(),
|
||||
);
|
||||
}
|
||||
if (state.error != null) {
|
||||
return SliverFillRemaining(
|
||||
child: Center(
|
||||
child: Text(
|
||||
translateError(context, state.error!),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
).padded(),
|
||||
);
|
||||
}
|
||||
if (state.hasLoaded &&
|
||||
!state.isLoading &&
|
||||
state.documents.isEmpty) {
|
||||
|
||||
Reference in New Issue
Block a user