Implemented inbox (still WIP)

This commit is contained in:
Anton Stubenbord
2022-11-24 13:37:25 +01:00
parent 8e7a5dddbf
commit eb5025e8ca
44 changed files with 674 additions and 316 deletions

View File

@@ -1,55 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/features/labels/model/label.model.dart';
import 'package:paperless_mobile/features/labels/repository/label_repository.dart';
abstract class LabelCubit<T extends Label> extends Cubit<Map<int, T>> {
final LabelRepository labelRepository;
LabelCubit(this.labelRepository) : super({});
@protected
void loadFrom(Iterable<T> items) =>
emit(Map.fromIterable(items, key: (e) => (e as T).id!));
Future<T> add(T item) async {
assert(item.id == null);
final addedItem = await save(item);
final newState = {...state};
newState.putIfAbsent(addedItem.id!, () => addedItem);
emit(newState);
return addedItem;
}
Future<T> replace(T item) async {
assert(item.id != null);
final updatedItem = await update(item);
final newState = {...state};
newState[item.id!] = updatedItem;
emit(newState);
return updatedItem;
}
Future<void> remove(T item) async {
assert(item.id != null);
if (state.containsKey(item.id)) {
final deletedId = await delete(item);
final newState = {...state};
newState.remove(deletedId);
emit(newState);
}
}
void reset() => emit({});
Future<void> initialize();
@protected
Future<T> save(T item);
@protected
Future<T> update(T item);
@protected
Future<int> delete(T item);
}

View File

@@ -11,7 +11,7 @@ class PaperlessServerInformationCubit
PaperlessServerInformationCubit(this.service) PaperlessServerInformationCubit(this.service)
: super(PaperlessServerInformation()); : super(PaperlessServerInformation());
Future<void> updateStatus() async { Future<void> updateInformtion() async {
emit(await service.getInformation()); emit(await service.getInformation());
} }
} }

View File

@@ -0,0 +1,62 @@
import 'dart:math';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:injectable/injectable.dart';
import 'package:paperless_mobile/core/model/paperless_statistics.dart';
import 'package:paperless_mobile/core/model/paperless_statistics_state.dart';
import 'package:paperless_mobile/core/service/paperless_statistics_service.dart';
@singleton
class PaperlessStatisticsCubit extends Cubit<PaperlessStatisticsState> {
final PaperlessStatisticsService statisticsService;
PaperlessStatisticsCubit(this.statisticsService)
: super(PaperlessStatisticsState(isLoaded: false));
Future<void> updateStatistics() async {
final stats = await statisticsService.getStatistics();
emit(PaperlessStatisticsState(isLoaded: true, statistics: stats));
}
void decrementInboxCount() {
if (state.isLoaded) {
emit(
PaperlessStatisticsState(
isLoaded: true,
statistics: PaperlessStatistics(
documentsInInbox: max(0, state.statistics!.documentsInInbox - 1),
documentsTotal: state.statistics!.documentsTotal,
),
),
);
}
}
void incrementInboxCount() {
if (state.isLoaded) {
emit(
PaperlessStatisticsState(
isLoaded: true,
statistics: PaperlessStatistics(
documentsInInbox: state.statistics!.documentsInInbox + 1,
documentsTotal: state.statistics!.documentsTotal,
),
),
);
}
}
void resetInboxCount() {
if (state.isLoaded) {
emit(
PaperlessStatisticsState(
isLoaded: true,
statistics: PaperlessStatistics(
documentsInInbox: 0,
documentsTotal: state.statistics!.documentsTotal,
),
),
);
}
}
}

View File

@@ -38,8 +38,8 @@ String translateError(BuildContext context, ErrorCode code) {
return S.of(context).errorMessageScanRemoveFailed; return S.of(context).errorMessageScanRemoveFailed;
case ErrorCode.invalidClientCertificateConfiguration: case ErrorCode.invalidClientCertificateConfiguration:
return S.of(context).errorMessageInvalidClientCertificateConfiguration; return S.of(context).errorMessageInvalidClientCertificateConfiguration;
case ErrorCode.documentBulkDeleteFailed: case ErrorCode.documentBulkActionFailed:
return S.of(context).errorMessageBulkDeleteDocumentsFailed; return S.of(context).errorMessageBulkActionFailed;
case ErrorCode.biometricsNotSupported: case ErrorCode.biometricsNotSupported:
return S.of(context).errorMessageBiotmetricsNotSupported; return S.of(context).errorMessageBiotmetricsNotSupported;
case ErrorCode.biometricAuthenticationFailed: case ErrorCode.biometricAuthenticationFailed:

View File

@@ -27,7 +27,7 @@ enum ErrorCode {
documentUpdateFailed, documentUpdateFailed,
documentLoadFailed, documentLoadFailed,
documentDeleteFailed, documentDeleteFailed,
documentBulkDeleteFailed, documentBulkActionFailed,
documentPreviewFailed, documentPreviewFailed,
documentAsnQueryFailed, documentAsnQueryFailed,
tagCreateFailed, tagCreateFailed,

View File

@@ -0,0 +1,11 @@
import 'package:paperless_mobile/core/model/paperless_statistics.dart';
class PaperlessStatisticsState {
final bool isLoaded;
final PaperlessStatistics? statistics;
PaperlessStatisticsState({
required this.isLoaded,
this.statistics,
});
}

View File

@@ -3,6 +3,7 @@ import 'dart:typed_data';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/model/bulk_edit.model.dart';
import 'package:paperless_mobile/features/documents/model/document.model.dart'; import 'package:paperless_mobile/features/documents/model/document.model.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart'; import 'package:paperless_mobile/features/documents/model/document_filter.dart';
import 'package:paperless_mobile/features/documents/model/paged_search_result.dart'; import 'package:paperless_mobile/features/documents/model/paged_search_result.dart';
@@ -36,9 +37,9 @@ class DocumentsCubit extends Cubit<DocumentsState> {
createdAt: createdAt, createdAt: createdAt,
); );
// documentRepository documentRepository
// .waitForConsumptionFinished(fileName, title) .waitForConsumptionFinished(fileName, title)
// .then((value) => onConsumptionFinished(value)); .then((value) => onConsumptionFinished(value));
} }
Future<void> removeDocument(DocumentModel document) async { Future<void> removeDocument(DocumentModel document) async {
@@ -47,8 +48,23 @@ class DocumentsCubit extends Cubit<DocumentsState> {
} }
Future<void> bulkRemoveDocuments(List<DocumentModel> documents) async { Future<void> bulkRemoveDocuments(List<DocumentModel> documents) async {
await documentRepository.bulkDelete(documents); await documentRepository.bulkAction(
return await reloadDocuments(); BulkDeleteAction(documents.map((doc) => doc.id)),
);
await reloadDocuments();
}
Future<void> bulkEditTags(
List<DocumentModel> documents, {
Iterable<int> addTags = const [],
Iterable<int> removeTags = const [],
}) async {
await documentRepository.bulkAction(BulkModifyTagsAction(
documents.map((doc) => doc.id),
addTags: addTags,
removeTags: removeTags,
));
await reloadDocuments();
} }
Future<void> updateDocument(DocumentModel document) async { Future<void> updateDocument(DocumentModel document) async {
@@ -135,15 +151,20 @@ class DocumentsCubit extends Cubit<DocumentsState> {
} }
} }
Future<void> removeInboxTags( ///
/// Updates the given document with the inbox tags removed and returns the remoed inbox tags.
///
Future<Iterable<int>> removeInboxTags(
DocumentModel document, final Iterable<int> inboxTags) async { DocumentModel document, final Iterable<int> inboxTags) async {
final updatedTags = document.tags.where((id) => !inboxTags.contains(id)); final tagsToRemove = document.tags.toSet().intersection(inboxTags.toSet());
return updateDocument( final updatedTags = {...document.tags}..removeAll(tagsToRemove);
await updateDocument(
document.copyWith( document.copyWith(
tags: updatedTags, tags: updatedTags,
overwriteTags: true, overwriteTags: true,
), ),
); );
return tagsToRemove;
} }
void resetSelection() { void resetSelection() {

View File

@@ -1,24 +1,50 @@
import 'package:paperless_mobile/core/type/types.dart'; import 'package:paperless_mobile/core/type/types.dart';
class BulkEditAction { abstract class BulkAction {
final List<int> documents; final Iterable<int> documentIds;
final BulkEditActionMethod _method;
final Map<String, dynamic> parameters;
BulkEditAction.delete(this.documents) BulkAction(this.documentIds);
: _method = BulkEditActionMethod.delete,
parameters = {};
JSON toJson();
}
class BulkDeleteAction extends BulkAction {
BulkDeleteAction(super.documents);
@override
JSON toJson() { JSON toJson() {
return { return {
'documents': documents, 'documents': documentIds.toList(),
'method': _method.name, 'method': 'delete',
'parameters': parameters,
}; };
} }
} }
enum BulkEditActionMethod { class BulkModifyTagsAction extends BulkAction {
delete, final Iterable<int> removeTags;
edit; final Iterable<int> addTags;
BulkModifyTagsAction(
super.documents, {
this.removeTags = const [],
this.addTags = const [],
});
BulkModifyTagsAction.addTags(super.documents, this.addTags)
: removeTags = const [];
BulkModifyTagsAction.removeTags(super.documents, this.removeTags)
: addTags = const [];
@override
JSON toJson() {
return {
'documents': documentIds.toList(),
'method': 'modify_tags',
'parameters': {
'add_tags': addTags.toList(),
'remove_tags': removeTags.toList(),
}
};
}
} }

View File

@@ -17,13 +17,19 @@ class OnlyNotAssignedTagsQuery extends TagsQuery {
} }
class AnyAssignedTagsQuery extends TagsQuery { class AnyAssignedTagsQuery extends TagsQuery {
const AnyAssignedTagsQuery(); final Iterable<int> tagIds;
const AnyAssignedTagsQuery({
this.tagIds = const [],
});
@override @override
List<Object?> get props => []; List<Object?> get props => [];
@override @override
String toQueryParameter() { String toQueryParameter() {
return '&is_tagged=1'; if (tagIds.isEmpty) {
return '&is_tagged=1';
}
return '&tags__id__in=${tagIds.join(',')}';
} }
} }

View File

@@ -1,5 +1,6 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:paperless_mobile/features/documents/model/bulk_edit.model.dart';
import 'package:paperless_mobile/features/documents/model/document.model.dart'; import 'package:paperless_mobile/features/documents/model/document.model.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart'; import 'package:paperless_mobile/features/documents/model/document_filter.dart';
import 'package:paperless_mobile/features/documents/model/document_meta_data.model.dart'; import 'package:paperless_mobile/features/documents/model/document_meta_data.model.dart';
@@ -23,7 +24,7 @@ abstract class DocumentRepository {
Future<List<SimilarDocumentModel>> findSimilar(int docId); Future<List<SimilarDocumentModel>> findSimilar(int docId);
Future<int> delete(DocumentModel doc); Future<int> delete(DocumentModel doc);
Future<DocumentMetaData> getMetaData(DocumentModel document); Future<DocumentMetaData> getMetaData(DocumentModel document);
Future<List<int>> bulkDelete(List<DocumentModel> models); Future<Iterable<int>> bulkAction(BulkAction action);
Future<Uint8List> getPreview(int docId); Future<Uint8List> getPreview(int docId);
String getThumbnailUrl(int docId); String getThumbnailUrl(int docId);
Future<DocumentModel> waitForConsumptionFinished( Future<DocumentModel> waitForConsumptionFinished(

View File

@@ -216,18 +216,16 @@ class DocumentRepositoryImpl implements DocumentRepository {
} }
@override @override
Future<List<int>> bulkDelete(List<DocumentModel> documentModels) async { Future<Iterable<int>> bulkAction(BulkAction action) async {
final List<int> ids = documentModels.map((e) => e.id).toList();
final action = BulkEditAction.delete(ids);
final response = await httpClient.post( final response = await httpClient.post(
Uri.parse("/api/documents/bulk_edit/"), Uri.parse("/api/documents/bulk_edit/"),
body: json.encode(action.toJson()), body: json.encode(action.toJson()),
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
return ids; return action.documentIds;
} else { } else {
throw const ErrorMessage(ErrorCode.documentBulkDeleteFailed); throw const ErrorMessage(ErrorCode.documentBulkActionFailed);
} }
} }

View File

@@ -5,7 +5,8 @@ import 'dart:math';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart'; import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart'; import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/core/widgets/highlighted_text.dart'; import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
@@ -345,16 +346,20 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
return const SizedBox(height: 32.0); return const SizedBox(height: 32.0);
} }
void _onEdit(DocumentModel document) { void _onEdit(DocumentModel document) async {
Navigator.push( final wasUpdated = await Navigator.push<bool>(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => LabelBlocProvider( builder: (_) => LabelBlocProvider(
child: DocumentEditPage(document: document), child: DocumentEditPage(document: document),
), ),
maintainState: true, maintainState: true,
), ),
); ) ??
false;
if (wasUpdated) {
BlocProvider.of<PaperlessStatisticsCubit>(context).updateStatistics();
}
} }
Future<void> _onDownload(DocumentModel document) async { Future<void> _onDownload(DocumentModel document) async {

View File

@@ -3,6 +3,8 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:intl/intl.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
@@ -20,6 +22,7 @@ import 'package:paperless_mobile/features/labels/correspondent/view/pages/add_co
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart'; import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart';
import 'package:paperless_mobile/features/labels/document_type/view/pages/add_document_type_page.dart'; import 'package:paperless_mobile/features/labels/document_type/view/pages/add_document_type_page.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart';
import 'package:paperless_mobile/features/labels/storage_path/view/pages/add_storage_path_page.dart'; import 'package:paperless_mobile/features/labels/storage_path/view/pages/add_storage_path_page.dart';
@@ -27,8 +30,6 @@ import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_fie
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart'; import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart'; import 'package:paperless_mobile/util.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:intl/intl.dart';
class DocumentEditPage extends StatefulWidget { class DocumentEditPage extends StatefulWidget {
final DocumentModel document; final DocumentModel document;
@@ -80,13 +81,15 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
setState(() { setState(() {
_isSubmitLoading = true; _isSubmitLoading = true;
}); });
bool wasUpdated = false;
try { try {
await getIt<DocumentsCubit>().updateDocument(updatedDocument); await getIt<DocumentsCubit>().updateDocument(updatedDocument);
showSnackBar(context, S.of(context).documentUpdateErrorMessage); showSnackBar(context, S.of(context).documentUpdateErrorMessage);
wasUpdated = true;
} on ErrorMessage catch (error, stackTrace) { } on ErrorMessage catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
} finally { } finally {
Navigator.pop(context); Navigator.pop(context, wasUpdated);
} }
} }
}, },
@@ -115,7 +118,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
child: ListView(children: [ child: ListView(children: [
_buildTitleFormField().padded(), _buildTitleFormField().padded(),
_buildCreatedAtFormField().padded(), _buildCreatedAtFormField().padded(),
BlocBuilder<DocumentTypeCubit, Map<int, DocumentType>>( BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
builder: (context, state) { builder: (context, state) {
return LabelFormField<DocumentType, DocumentTypeQuery>( return LabelFormField<DocumentType, DocumentTypeQuery>(
notAssignedSelectable: false, notAssignedSelectable: false,
@@ -130,7 +133,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
label: S.of(context).documentDocumentTypePropertyLabel, label: S.of(context).documentDocumentTypePropertyLabel,
initialValue: initialValue:
DocumentTypeQuery.fromId(widget.document.documentType), DocumentTypeQuery.fromId(widget.document.documentType),
state: state, state: state.labels,
name: fkDocumentType, name: fkDocumentType,
queryParameterIdBuilder: DocumentTypeQuery.fromId, queryParameterIdBuilder: DocumentTypeQuery.fromId,
queryParameterNotAssignedBuilder: queryParameterNotAssignedBuilder:
@@ -139,7 +142,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
); );
}, },
).padded(), ).padded(),
BlocBuilder<CorrespondentCubit, Map<int, Correspondent>>( BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
builder: (context, state) { builder: (context, state) {
return LabelFormField<Correspondent, CorrespondentQuery>( return LabelFormField<Correspondent, CorrespondentQuery>(
notAssignedSelectable: false, notAssignedSelectable: false,
@@ -150,7 +153,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
child: AddCorrespondentPage(initalValue: initialValue), child: AddCorrespondentPage(initalValue: initialValue),
), ),
label: S.of(context).documentCorrespondentPropertyLabel, label: S.of(context).documentCorrespondentPropertyLabel,
state: state, state: state.labels,
initialValue: initialValue:
CorrespondentQuery.fromId(widget.document.correspondent), CorrespondentQuery.fromId(widget.document.correspondent),
name: fkCorrespondent, name: fkCorrespondent,
@@ -161,7 +164,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
); );
}, },
).padded(), ).padded(),
BlocBuilder<StoragePathCubit, Map<int, StoragePath>>( BlocBuilder<StoragePathCubit, LabelState<StoragePath>>(
builder: (context, state) { builder: (context, state) {
return LabelFormField<StoragePath, StoragePathQuery>( return LabelFormField<StoragePath, StoragePathQuery>(
notAssignedSelectable: false, notAssignedSelectable: false,
@@ -172,7 +175,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
child: AddStoragePathPage(initalValue: initialValue), child: AddStoragePathPage(initalValue: initialValue),
), ),
label: S.of(context).documentStoragePathPropertyLabel, label: S.of(context).documentStoragePathPropertyLabel,
state: state, state: state.labels,
initialValue: initialValue:
StoragePathQuery.fromId(widget.document.storagePath), StoragePathQuery.fromId(widget.document.storagePath),
name: fkStoragePath, name: fkStoragePath,

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
@@ -237,6 +238,8 @@ class _DocumentsPageState extends State<DocumentsPage> {
BlocProvider.value(value: BlocProvider.of<TagCubit>(context)), BlocProvider.value(value: BlocProvider.of<TagCubit>(context)),
BlocProvider.value( BlocProvider.value(
value: BlocProvider.of<StoragePathCubit>(context)), value: BlocProvider.of<StoragePathCubit>(context)),
BlocProvider.value(
value: BlocProvider.of<PaperlessStatisticsCubit>(context)),
], ],
child: DocumentDetailsPage( child: DocumentDetailsPage(
documentId: model.id, documentId: model.id,

View File

@@ -43,7 +43,6 @@ class DocumentListItem extends StatelessWidget {
child: CorrespondentWidget( child: CorrespondentWidget(
isClickable: isLabelClickable, isClickable: isLabelClickable,
correspondentId: document.correspondent, correspondentId: document.correspondent,
afterSelected: () {},
), ),
), ),
], ],

View File

@@ -18,6 +18,7 @@ import 'package:paperless_mobile/features/labels/correspondent/bloc/corresponden
import 'package:paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; import 'package:paperless_mobile/features/labels/correspondent/model/correspondent.model.dart';
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart'; import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart'; import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
@@ -53,14 +54,6 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
final _formKey = GlobalKey<FormBuilderState>(); final _formKey = GlobalKey<FormBuilderState>();
late final DocumentsCubit _documentsCubit;
@override
void initState() {
super.initState();
_documentsCubit = BlocProvider.of<DocumentsCubit>(context);
}
DateTimeRange? _dateTimeRangeOfNullable(DateTime? start, DateTime? end) { DateTimeRange? _dateTimeRangeOfNullable(DateTime? start, DateTime? end) {
if (start == null && end == null) { if (start == null && end == null) {
return null; return null;
@@ -181,12 +174,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
} }
Widget _buildDocumentTypeFormField(DocumentsState docState) { Widget _buildDocumentTypeFormField(DocumentsState docState) {
return BlocBuilder<DocumentTypeCubit, Map<int, DocumentType>>( return BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
builder: (context, state) { builder: (context, state) {
return LabelFormField<DocumentType, DocumentTypeQuery>( return LabelFormField<DocumentType, DocumentTypeQuery>(
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
name: fkDocumentType, name: fkDocumentType,
state: state, state: state.labels,
label: S.of(context).documentDocumentTypePropertyLabel, label: S.of(context).documentDocumentTypePropertyLabel,
initialValue: docState.filter.documentType, initialValue: docState.filter.documentType,
queryParameterIdBuilder: DocumentTypeQuery.fromId, queryParameterIdBuilder: DocumentTypeQuery.fromId,
@@ -198,12 +191,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
} }
Widget _buildCorrespondentFormField(DocumentsState docState) { Widget _buildCorrespondentFormField(DocumentsState docState) {
return BlocBuilder<CorrespondentCubit, Map<int, Correspondent>>( return BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
builder: (context, state) { builder: (context, state) {
return LabelFormField<Correspondent, CorrespondentQuery>( return LabelFormField<Correspondent, CorrespondentQuery>(
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
name: fkCorrespondent, name: fkCorrespondent,
state: state, state: state.labels,
label: S.of(context).documentCorrespondentPropertyLabel, label: S.of(context).documentCorrespondentPropertyLabel,
initialValue: docState.filter.correspondent, initialValue: docState.filter.correspondent,
queryParameterIdBuilder: CorrespondentQuery.fromId, queryParameterIdBuilder: CorrespondentQuery.fromId,
@@ -215,12 +208,12 @@ class _DocumentFilterPanelState extends State<DocumentFilterPanel> {
} }
Widget _buildStoragePathFormField(DocumentsState docState) { Widget _buildStoragePathFormField(DocumentsState docState) {
return BlocBuilder<StoragePathCubit, Map<int, StoragePath>>( return BlocBuilder<StoragePathCubit, LabelState<StoragePath>>(
builder: (context, state) { builder: (context, state) {
return LabelFormField<StoragePath, StoragePathQuery>( return LabelFormField<StoragePath, StoragePathQuery>(
formBuilderState: _formKey.currentState, formBuilderState: _formKey.currentState,
name: fkStoragePath, name: fkStoragePath,
state: state, state: state.labels,
label: S.of(context).documentStoragePathPropertyLabel, label: S.of(context).documentStoragePathPropertyLabel,
initialValue: docState.filter.storagePath, initialValue: docState.filter.storagePath,
queryParameterIdBuilder: StoragePathQuery.fromId, queryParameterIdBuilder: StoragePathQuery.fromId,

View File

@@ -1,18 +1,24 @@
import 'dart:developer';
import 'dart:isolate';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart'; import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/core/widgets/offline_banner.dart'; import 'package:paperless_mobile/core/widgets/offline_banner.dart';
import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart'; import 'package:paperless_mobile/features/documents/bloc/saved_view_cubit.dart';
import 'package:paperless_mobile/features/documents/repository/document_repository.dart'; import 'package:paperless_mobile/features/documents/repository/document_repository.dart';
import 'package:paperless_mobile/features/documents/repository/document_repository_impl.dart';
import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart'; import 'package:paperless_mobile/features/documents/view/pages/documents_page.dart';
import 'package:paperless_mobile/features/home/view/widget/bottom_navigation_bar.dart'; import 'package:paperless_mobile/features/home/view/widget/bottom_navigation_bar.dart';
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart'; import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart';
import 'package:paperless_mobile/features/inbox/view/inbox_page.dart'; import 'package:paperless_mobile/features/inbox/view/inbox_page.dart';
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
@@ -20,6 +26,7 @@ import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart'; import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart'; import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
import 'package:paperless_mobile/features/scan/view/scanner_page.dart'; import 'package:paperless_mobile/features/scan/view/scanner_page.dart';
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
import 'package:paperless_mobile/util.dart'; import 'package:paperless_mobile/util.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
@@ -35,7 +42,30 @@ class _HomePageState extends State<HomePage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_initializeData(context); _initializeData(context).then(
(_) async {
FlutterNativeSplash.remove();
if (BlocProvider.of<ApplicationSettingsCubit>(context)
.state
.showInboxOnStartup) {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BlocProvider.value(
value: getIt<PaperlessStatisticsCubit>(),
child: LabelBlocProvider(
child: BlocProvider.value(
value: DocumentsCubit(getIt<DocumentRepository>()),
child: const InboxPage(),
),
),
),
),
);
getIt<DocumentsCubit>().reloadDocuments();
}
},
);
} }
@override @override
@@ -59,10 +89,6 @@ class _HomePageState extends State<HomePage> {
), ),
drawer: const InfoDrawer(), drawer: const InfoDrawer(),
body: [ body: [
BlocProvider.value(
value: DocumentsCubit(getIt<DocumentRepository>()),
child: const InboxPage(),
),
BlocProvider.value( BlocProvider.value(
value: getIt<DocumentsCubit>(), value: getIt<DocumentsCubit>(),
child: const DocumentsPage(), child: const DocumentsPage(),
@@ -78,17 +104,21 @@ class _HomePageState extends State<HomePage> {
); );
} }
_initializeData(BuildContext context) async { Future<void> _initializeData(BuildContext context) {
try { try {
await BlocProvider.of<PaperlessServerInformationCubit>(context) return Future.wait([
.updateStatus(); BlocProvider.of<PaperlessServerInformationCubit>(context)
BlocProvider.of<DocumentTypeCubit>(context).initialize(); .updateInformtion(),
BlocProvider.of<CorrespondentCubit>(context).initialize(); BlocProvider.of<PaperlessStatisticsCubit>(context).updateStatistics(),
BlocProvider.of<TagCubit>(context).initialize(); BlocProvider.of<DocumentTypeCubit>(context).initialize(),
BlocProvider.of<StoragePathCubit>(context).initialize(); BlocProvider.of<CorrespondentCubit>(context).initialize(),
BlocProvider.of<SavedViewCubit>(context).initialize(); BlocProvider.of<TagCubit>(context).initialize(),
BlocProvider.of<StoragePathCubit>(context).initialize(),
BlocProvider.of<SavedViewCubit>(context).initialize(),
]);
} on ErrorMessage catch (error, stackTrace) { } on ErrorMessage catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); showErrorMessage(context, error, stackTrace);
return Future.error(error);
} }
} }
} }

View File

@@ -18,14 +18,6 @@ class BottomNavBar extends StatelessWidget {
onDestinationSelected: onNavigationChanged, onDestinationSelected: onNavigationChanged,
selectedIndex: selectedIndex, selectedIndex: selectedIndex,
destinations: [ destinations: [
NavigationDestination(
icon: const Icon(Icons.inbox_outlined),
selectedIcon: Icon(
Icons.inbox,
color: Theme.of(context).colorScheme.primary,
),
label: S.of(context).bottomNavInboxPageLabel,
),
NavigationDestination( NavigationDestination(
icon: const Icon(Icons.description_outlined), icon: const Icon(Icons.description_outlined),
selectedIcon: Icon( selectedIcon: Icon(

View File

@@ -1,7 +1,9 @@
import 'package:badges/badges.dart'; import 'package:badges/badges.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart'; import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
import 'package:paperless_mobile/core/model/paperless_statistics_state.dart';
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart'; import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/core/model/paperless_server_information.dart'; import 'package:paperless_mobile/core/model/paperless_server_information.dart';
@@ -57,7 +59,7 @@ class InfoDrawer extends StatelessWidget {
).padded(const EdgeInsets.only(right: 8.0)), ).padded(const EdgeInsets.only(right: 8.0)),
Text( Text(
S.of(context).appTitleText, S.of(context).appTitleText,
style: Theme.of(context).textTheme.headline5!.copyWith( style: Theme.of(context).textTheme.headline5?.copyWith(
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onPrimaryContainer, .onPrimaryContainer,
@@ -104,18 +106,6 @@ class InfoDrawer extends StatelessWidget {
), ),
], ],
), ),
// title: RichText(
// text: TextSpan(
// children: [
// TextSpan(
// text:
// style:
// Theme.of(context).textTheme.bodyText2,
// ),
// ],
// ),
// ),
isThreeLine: true, isThreeLine: true,
), ),
], ],
@@ -129,27 +119,32 @@ class InfoDrawer extends StatelessWidget {
color: Theme.of(context).colorScheme.primaryContainer, color: Theme.of(context).colorScheme.primaryContainer,
), ),
), ),
FutureBuilder<PaperlessStatistics>( BlocBuilder<PaperlessStatisticsCubit, PaperlessStatisticsState>(
future: getIt<PaperlessStatisticsService>().getStatistics(), builder: (context, state) {
builder: (context, snapshot) {
return ListTile( return ListTile(
title: Text("Inbox"), title: Text(S.of(context).bottomNavInboxPageLabel),
leading: const Icon(Icons.inbox), leading: const Icon(Icons.inbox),
trailing: snapshot.hasData trailing: state.isLoaded
? Text( ? Text(state.statistics!.documentsInInbox.toString())
snapshot.data!.documentsInInbox.toString(),
)
: null, : null,
onTap: () => Navigator.push( onTap: () async {
await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => LabelBlocProvider( builder: (context) => BlocProvider.value(
child: BlocProvider.value( value: getIt<PaperlessStatisticsCubit>(),
value: DocumentsCubit(getIt<DocumentRepository>()), child: LabelBlocProvider(
child: const InboxPage(), child: BlocProvider.value(
value:
DocumentsCubit(getIt<DocumentRepository>()),
child: const InboxPage(),
),
), ),
), ),
)), ),
);
getIt<DocumentsCubit>().reloadDocuments();
},
); );
}, },
), ),

View File

@@ -1,18 +1,25 @@
import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart'; import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/core/model/paperless_statistics_state.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/model/document.model.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart'; import 'package:paperless_mobile/features/documents/model/document_filter.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
import 'package:paperless_mobile/features/documents/view/pages/document_details_page.dart'; import 'package:paperless_mobile/features/documents/view/pages/document_details_page.dart';
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart'; import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart'; import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/util.dart';
class InboxPage extends StatefulWidget { class InboxPage extends StatefulWidget {
const InboxPage({super.key}); const InboxPage({super.key});
@@ -22,6 +29,9 @@ class InboxPage extends StatefulWidget {
} }
class _InboxPageState extends State<InboxPage> { class _InboxPageState extends State<InboxPage> {
static const _a4AspectRatio = 1 / 1.4142;
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
Iterable<int> _inboxTags = []; Iterable<int> _inboxTags = [];
@override @override
void initState() { void initState() {
@@ -31,9 +41,12 @@ class _InboxPageState extends State<InboxPage> {
} }
Future<void> _initInbox() async { Future<void> _initInbox() async {
final tags = BlocProvider.of<TagCubit>(context).state.values; final tags = BlocProvider.of<TagCubit>(context).state.labels;
_inboxTags = tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!); log("Loading documents with tags...${tags.values.join(",")}");
final filter = DocumentFilter(tags: IdsTagsQuery.included(_inboxTags)); _inboxTags =
tags.values.where((t) => t.isInboxTag ?? false).map((t) => t.id!);
final filter =
DocumentFilter(tags: AnyAssignedTagsQuery(tagIds: _inboxTags));
return BlocProvider.of<DocumentsCubit>(context).updateFilter( return BlocProvider.of<DocumentsCubit>(context).updateFilter(
filter: filter, filter: filter,
); );
@@ -41,71 +54,210 @@ class _InboxPageState extends State<InboxPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return BlocBuilder<DocumentsCubit, DocumentsState>(
appBar: AppBar( builder: (context, documentState) {
title: Text("Inbox"), return Scaffold(
), appBar: AppBar(
drawer: const InfoDrawer(), title:
floatingActionButton: FloatingActionButton.extended( BlocBuilder<PaperlessStatisticsCubit, PaperlessStatisticsState>(
label: Text("Mark all as read"), builder: (context, state) {
icon: const Icon(FontAwesomeIcons.checkDouble), return Text(
onPressed: () {}, S.of(context).bottomNavInboxPageLabel +
), (state.isLoaded
body: BlocBuilder<DocumentsCubit, DocumentsState>( ? ' (${state.statistics!.documentsInInbox})'
builder: (context, state) { : ''),
if (!state.isLoaded) { );
return const Center(child: CircularProgressIndicator()); },
} ),
if (state.documents.isEmpty) { leading: IconButton(
return Text("You do not have new documents in your inbox.") icon: const Icon(Icons.close),
.padded(); onPressed: () => Navigator.pop(context),
} ),
return Column( ),
children: [ floatingActionButton: documentState.documents.isNotEmpty
Text( ? FloatingActionButton.extended(
"You have ${state.documents.length} documents in your inbox.", label: Text("Mark all as seen"),
icon: const Icon(Icons.done_all),
onPressed: () =>
_onMarkAllAsSeen(documentState.documents, _inboxTags),
)
: null,
body: Builder(
builder: (context) {
if (!documentState.isLoaded) {
return const Center(child: CircularProgressIndicator());
}
if (documentState.documents.isEmpty) {
return Text(
"You do not have new documents in your inbox.",
textAlign: TextAlign.center,
) // TODO: INTL
.padded();
}
return Column(
children: [
Text(
'Hint: Swipe left to mark a document as read. This will remove all inbox tags from the document.', //TODO: INTL
style: Theme.of(context).textTheme.caption,
).padded(
const EdgeInsets.only(
top: 4.0,
left: 8.0,
right: 8.0,
bottom: 8.0,
),
),
Expanded(
child: AnimatedList(
key: _listKey,
initialItemCount: documentState.documents.length,
itemBuilder: (context, index, animation) {
final doc = documentState.documents[index];
return _buildListItem(context, doc);
},
),
),
],
);
},
),
);
},
);
}
Widget _buildListItem(BuildContext context, DocumentModel doc) {
return Dismissible(
direction: DismissDirection.endToStart,
background: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Icon(
Icons.done,
color: Theme.of(context).colorScheme.primary,
).padded(),
Text(
'Mark as read', //TODO: INTL
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
),
),
],
).padded(),
confirmDismiss: (_) => _onItemDismissed(doc),
key: ObjectKey(doc.id),
child: ListTile(
title: Text(doc.title),
isThreeLine: true,
leading: AspectRatio(
aspectRatio: _a4AspectRatio,
child: DocumentPreview(
id: doc.id,
fit: BoxFit.cover,
alignment: Alignment.topCenter,
),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(DateFormat().format(doc.added)),
TagsWidget(tagIds: doc.tags.where((id) => _inboxTags.contains(id)))
],
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => LabelBlocProvider(
child: BlocProvider.value(
value: BlocProvider.of<DocumentsCubit>(context),
child: DocumentDetailsPage(
documentId: doc.id,
allowEdit: false,
isLabelClickable: false,
),
), ),
Expanded( ),
child: ListView( ),
children: state.documents ),
.map(
(doc) => Dismissible(
direction: DismissDirection.endToStart,
onDismissed: (_) {
BlocProvider.of<DocumentsCubit>(context)
.removeInboxTags(doc, _inboxTags);
},
key: ObjectKey(doc.id),
child: ListTile(
title: Text(doc.title),
isThreeLine: true,
leading: DocumentPreview(id: doc.id),
subtitle: Text(DateFormat().format(doc.added)),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => LabelBlocProvider(
child: BlocProvider.value(
value:
BlocProvider.of<DocumentsCubit>(context),
child: DocumentDetailsPage(
documentId: doc.id,
allowEdit: false,
isLabelClickable: false,
),
),
),
),
),
),
),
)
.toList(),
)),
],
);
},
), ),
); );
} }
Widget _buildSlideAnimation(
BuildContext context,
animation,
Widget child,
) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
Future<void> _onMarkAllAsSeen(
List<DocumentModel> documents,
Iterable<int> inboxTags,
) async {
for (int i = documents.length - 1; i >= 0; i--) {
final doc = documents[i];
_listKey.currentState?.removeItem(
0,
(context, animation) => _buildSlideAnimation(
context,
animation,
_buildListItem(context, doc),
),
);
await Future.delayed(const Duration(milliseconds: 75));
}
await BlocProvider.of<DocumentsCubit>(context)
.bulkEditTags(documents, removeTags: inboxTags);
BlocProvider.of<PaperlessStatisticsCubit>(context).resetInboxCount();
}
Future<bool> _onItemDismissed(DocumentModel doc) async {
try {
final removedTags = await BlocProvider.of<DocumentsCubit>(context)
.removeInboxTags(doc, _inboxTags);
BlocProvider.of<PaperlessStatisticsCubit>(context).decrementInboxCount();
showSnackBar(
context,
'Document removed from inbox.', //TODO: INTL
action: SnackBarAction(
label: 'UNDO', //TODO: INTL
textColor: Theme.of(context).colorScheme.primary,
onPressed: () => _onUndoMarkAsSeen(doc, removedTags),
),
);
return true;
} on ErrorMessage catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
return false;
} catch (error) {
showErrorMessage(
context,
const ErrorMessage.unknown(),
);
return false;
}
}
Future<void> _onUndoMarkAsSeen(
DocumentModel doc, Iterable<int> removedTags) async {
try {
await BlocProvider.of<DocumentsCubit>(context).updateDocument(
doc.copyWith(
tags: {...doc.tags, ...removedTags},
overwriteTags: true,
),
);
BlocProvider.of<PaperlessStatisticsCubit>(context).incrementInboxCount();
BlocProvider.of<DocumentsCubit>(context).reloadDocuments();
} on ErrorMessage catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
} }

View File

@@ -0,0 +1,75 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/features/labels/model/label.model.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/repository/label_repository.dart';
abstract class LabelCubit<T extends Label> extends Cubit<LabelState<T>> {
final LabelRepository labelRepository;
LabelCubit(this.labelRepository) : super(LabelState.initial());
@protected
void loadFrom(Iterable<T> items) {
emit(
LabelState(
isLoaded: true,
labels: Map.fromIterable(items, key: (e) => (e as T).id!),
),
);
}
Future<T> add(T item) async {
assert(item.id == null);
final addedItem = await save(item);
final newValues = {...state.labels};
newValues.putIfAbsent(addedItem.id!, () => addedItem);
emit(
LabelState(
isLoaded: true,
labels: newValues,
),
);
return addedItem;
}
Future<T> replace(T item) async {
assert(item.id != null);
final updatedItem = await update(item);
final updatedValues = {...state.labels};
updatedValues[item.id!] = updatedItem;
emit(
LabelState(
isLoaded: state.isLoaded,
labels: updatedValues,
),
);
return updatedItem;
}
Future<void> remove(T item) async {
assert(item.id != null);
if (state.labels.containsKey(item.id)) {
final deletedId = await delete(item);
final updatedValues = {...state.labels}..remove(deletedId);
emit(
LabelState(isLoaded: true, labels: updatedValues),
);
}
}
void reset() {
emit(LabelState(isLoaded: false, labels: {}));
}
Future<void> initialize();
@protected
Future<T> save(T item);
@protected
Future<T> update(T item);
@protected
Future<int> delete(T item);
}

View File

@@ -1,4 +1,4 @@
import 'package:paperless_mobile/core/bloc/label_cubit.dart'; import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; import 'package:paperless_mobile/features/labels/correspondent/model/correspondent.model.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';

View File

@@ -6,6 +6,7 @@ import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/correspondent_query.dart';
import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart';
import 'package:paperless_mobile/features/labels/correspondent/model/correspondent.model.dart'; import 'package:paperless_mobile/features/labels/correspondent/model/correspondent.model.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/util.dart'; import 'package:paperless_mobile/util.dart';
class CorrespondentWidget extends StatelessWidget { class CorrespondentWidget extends StatelessWidget {
@@ -16,7 +17,7 @@ class CorrespondentWidget extends StatelessWidget {
const CorrespondentWidget({ const CorrespondentWidget({
Key? key, Key? key,
required this.correspondentId, this.correspondentId,
this.afterSelected, this.afterSelected,
this.textColor, this.textColor,
this.isClickable = true, this.isClickable = true,
@@ -26,12 +27,12 @@ class CorrespondentWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AbsorbPointer( return AbsorbPointer(
absorbing: !isClickable, absorbing: !isClickable,
child: BlocBuilder<CorrespondentCubit, Map<int, Correspondent>>( child: BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
builder: (context, state) { builder: (context, state) {
return GestureDetector( return GestureDetector(
onTap: () => _addCorrespondentToFilter(context), onTap: () => _addCorrespondentToFilter(context),
child: Text( child: Text(
(state[correspondentId]?.name) ?? "-", (state.getLabel(correspondentId)?.name) ?? "-",
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyText2?.copyWith( style: Theme.of(context).textTheme.bodyText2?.copyWith(

View File

@@ -1,5 +1,5 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:paperless_mobile/core/bloc/label_cubit.dart'; import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart'; import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';

View File

@@ -5,6 +5,7 @@ import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/document_type_query.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/document_type_query.dart';
import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart';
import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart'; import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/util.dart'; import 'package:paperless_mobile/util.dart';
class DocumentTypeWidget extends StatelessWidget { class DocumentTypeWidget extends StatelessWidget {
@@ -24,10 +25,10 @@ class DocumentTypeWidget extends StatelessWidget {
absorbing: !isClickable, absorbing: !isClickable,
child: GestureDetector( child: GestureDetector(
onTap: () => _addDocumentTypeToFilter(context), onTap: () => _addDocumentTypeToFilter(context),
child: BlocBuilder<DocumentTypeCubit, Map<int, DocumentType>>( child: BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
builder: (context, state) { builder: (context, state) {
return Text( return Text(
state[documentTypeId]?.toString() ?? "-", state.labels[documentTypeId]?.toString() ?? "-",
style: Theme.of(context) style: Theme.of(context)
.textTheme .textTheme
.bodyText2! .bodyText2!

View File

@@ -0,0 +1,19 @@
import 'package:paperless_mobile/features/labels/model/label.model.dart';
class LabelState<T extends Label> {
LabelState.initial() : this(isLoaded: false, labels: {});
final bool isLoaded;
final Map<int, T> labels;
LabelState({
required this.isLoaded,
required this.labels,
});
T? getLabel(int? key) {
if (isLoaded) {
return labels[key];
}
return null;
}
}

View File

@@ -1,5 +1,5 @@
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:paperless_mobile/core/bloc/label_cubit.dart'; import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart';
@singleton @singleton

View File

@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/storage_path_query.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart';
import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart'; import 'package:paperless_mobile/features/labels/storage_path/model/storage_path.model.dart';
import 'package:paperless_mobile/util.dart'; import 'package:paperless_mobile/util.dart';
@@ -15,7 +16,7 @@ class StoragePathWidget extends StatelessWidget {
const StoragePathWidget({ const StoragePathWidget({
Key? key, Key? key,
required this.pathId, this.pathId,
this.afterSelected, this.afterSelected,
this.textColor, this.textColor,
this.isClickable = true, this.isClickable = true,
@@ -25,12 +26,12 @@ class StoragePathWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AbsorbPointer( return AbsorbPointer(
absorbing: !isClickable, absorbing: !isClickable,
child: BlocBuilder<StoragePathCubit, Map<int, StoragePath>>( child: BlocBuilder<StoragePathCubit, LabelState<StoragePath>>(
builder: (context, state) { builder: (context, state) {
return GestureDetector( return GestureDetector(
onTap: () => _addStoragePathToFilter(context), onTap: () => _addStoragePathToFilter(context),
child: Text( child: Text(
(state[pathId]?.name) ?? "-", state.getLabel(pathId)?.name ?? "-",
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodyText2?.copyWith( style: Theme.of(context).textTheme.bodyText2?.copyWith(

View File

@@ -1,4 +1,4 @@
import 'package:paperless_mobile/core/bloc/label_cubit.dart'; import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart'; import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart'; import 'package:paperless_mobile/features/documents/model/document_filter.dart';
@@ -21,7 +22,11 @@ class EditTagPage extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return EditLabelPage<Tag>( return EditLabelPage<Tag>(
label: tag, label: tag,
onSubmit: BlocProvider.of<TagCubit>(context).replace, onSubmit: (tag) async {
await BlocProvider.of<TagCubit>(context).replace(tag);
//If inbox property was added/removed from tag, the number of documetns in inbox may increase/decrease.
BlocProvider.of<PaperlessStatisticsCubit>(context).updateStatistics();
},
onDelete: (tag) => _onDelete(tag, context), onDelete: (tag) => _onDelete(tag, context),
fromJson: Tag.fromJson, fromJson: Tag.fromJson,
additionalFields: [ additionalFields: [

View File

@@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart'; import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart';
import 'package:paperless_mobile/features/labels/tags/view/pages/add_tag_page.dart'; import 'package:paperless_mobile/features/labels/tags/view/pages/add_tag_page.dart';
@@ -45,7 +46,7 @@ class _TagFormFieldState extends State<TagFormField> {
_textEditingController = TextEditingController() _textEditingController = TextEditingController()
..addListener(() { ..addListener(() {
setState(() { setState(() {
_showCreationSuffixIcon = state.values _showCreationSuffixIcon = state.labels.values
.where( .where(
(item) => item.name.toLowerCase().startsWith( (item) => item.name.toLowerCase().startsWith(
_textEditingController.text.toLowerCase(), _textEditingController.text.toLowerCase(),
@@ -61,7 +62,7 @@ class _TagFormFieldState extends State<TagFormField> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<TagCubit, Map<int, Tag>>( return BlocBuilder<TagCubit, LabelState<Tag>>(
builder: (context, tagState) { builder: (context, tagState) {
return FormBuilderField<TagsQuery>( return FormBuilderField<TagsQuery>(
builder: (field) { builder: (field) {
@@ -81,7 +82,7 @@ class _TagFormFieldState extends State<TagFormField> {
controller: _textEditingController, controller: _textEditingController,
), ),
suggestionsCallback: (query) { suggestionsCallback: (query) {
final suggestions = tagState.values final suggestions = tagState.labels.values
.where((element) => element.name .where((element) => element.name
.toLowerCase() .toLowerCase()
.startsWith(query.toLowerCase())) .startsWith(query.toLowerCase()))
@@ -113,7 +114,7 @@ class _TagFormFieldState extends State<TagFormField> {
title: Text(S.of(context).labelAnyAssignedText), title: Text(S.of(context).labelAnyAssignedText),
); );
} }
final tag = tagState[data]!; final tag = tagState.getLabel(data)!;
return ListTile( return ListTile(
leading: Icon( leading: Icon(
Icons.circle, Icons.circle,
@@ -159,7 +160,7 @@ class _TagFormFieldState extends State<TagFormField> {
(query) => _buildTag( (query) => _buildTag(
field, field,
query, query,
tagState[query.id]!, tagState.getLabel(query.id)!,
), ),
) )
.toList(), .toList(),

View File

@@ -2,6 +2,7 @@ import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart';
import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart'; import 'package:paperless_mobile/features/labels/tags/model/tag.model.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart'; import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart';
@@ -27,13 +28,13 @@ class TagsWidget extends StatefulWidget {
class _TagsWidgetState extends State<TagsWidget> { class _TagsWidgetState extends State<TagsWidget> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<TagCubit, Map<int, Tag>>( return BlocBuilder<TagCubit, LabelState<Tag>>(
builder: (context, state) { builder: (context, state) {
final children = widget.tagIds final children = widget.tagIds
.where((id) => state.containsKey(id)) .where((id) => state.labels.containsKey(id))
.map( .map(
(id) => TagWidget( (id) => TagWidget(
tag: state[id]!, tag: state.getLabel(id)!,
afterTagTapped: widget.afterTagTapped, afterTagTapped: widget.afterTagTapped,
isClickable: widget.isClickable, isClickable: widget.isClickable,
), ),

View File

@@ -2,7 +2,7 @@ import 'dart:developer';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_mobile/core/bloc/label_cubit.dart'; import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart'; import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/core/type/types.dart'; import 'package:paperless_mobile/core/type/types.dart';

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart'; import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/di_initializer.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart'; import 'package:paperless_mobile/features/documents/model/document_filter.dart';
@@ -227,6 +228,7 @@ class _LabelsPageState extends State<LabelsPage>
providers: [ providers: [
BlocProvider.value(value: getIt<DocumentsCubit>()), BlocProvider.value(value: getIt<DocumentsCubit>()),
BlocProvider.value(value: BlocProvider.of<TagCubit>(context)), BlocProvider.value(value: BlocProvider.of<TagCubit>(context)),
BlocProvider.value(value: getIt<PaperlessStatisticsCubit>()),
], ],
child: EditTagPage(tag: tag), child: EditTagPage(tag: tag),
), ),

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart'; import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart'; import 'package:paperless_mobile/core/logic/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/di_initializer.dart';

View File

@@ -1,10 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/label_cubit.dart'; import 'package:paperless_mobile/features/labels/bloc/label_cubit.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart'; import 'package:paperless_mobile/core/widgets/offline_widget.dart';
import 'package:paperless_mobile/features/documents/model/document_filter.dart'; import 'package:paperless_mobile/features/documents/model/document_filter.dart';
import 'package:paperless_mobile/features/labels/model/label.model.dart'; import 'package:paperless_mobile/features/labels/model/label.model.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart'; import 'package:paperless_mobile/features/labels/view/widgets/label_item.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
@@ -45,10 +46,10 @@ class LabelTabView<T extends Label> extends StatelessWidget {
} }
return RefreshIndicator( return RefreshIndicator(
onRefresh: cubit.initialize, onRefresh: cubit.initialize,
child: BlocBuilder<Cubit<Map<int, T>>, Map<int, T>>( child: BlocBuilder<Cubit<LabelState<T>>, LabelState<T>>(
bloc: cubit, bloc: cubit,
builder: (context, state) { builder: (context, state) {
final labels = state.values.toList()..sort(); final labels = state.labels.values.toList()..sort();
if (labels.isEmpty) { if (labels.isEmpty) {
return Center( return Center(
child: Column( child: Column(

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart'; import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
import 'package:paperless_mobile/features/documents/model/document.model.dart'; import 'package:paperless_mobile/features/documents/model/document.model.dart';

View File

@@ -3,6 +3,7 @@ import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/core/type/types.dart'; import 'package:paperless_mobile/core/type/types.dart';
import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/di_initializer.dart';
@@ -19,6 +20,7 @@ import 'package:paperless_mobile/features/labels/correspondent/model/corresponde
import 'package:paperless_mobile/features/labels/correspondent/view/pages/add_correspondent_page.dart'; import 'package:paperless_mobile/features/labels/correspondent/view/pages/add_correspondent_page.dart';
import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart'; import 'package:paperless_mobile/features/labels/document_type/model/document_type.model.dart';
import 'package:paperless_mobile/features/labels/document_type/view/pages/add_document_type_page.dart'; import 'package:paperless_mobile/features/labels/document_type/view/pages/add_document_type_page.dart';
import 'package:paperless_mobile/features/labels/model/label_state.dart';
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.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'; import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart'; import 'package:paperless_mobile/features/scan/bloc/document_scanner_cubit.dart';
@@ -165,7 +167,7 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
labelText: S.of(context).documentCreatedPropertyLabel + " *", labelText: S.of(context).documentCreatedPropertyLabel + " *",
), ),
), ),
BlocBuilder<DocumentTypeCubit, Map<int, DocumentType>>( BlocBuilder<DocumentTypeCubit, LabelState<DocumentType>>(
bloc: getIt<DocumentTypeCubit>(), //TODO: Use provider bloc: getIt<DocumentTypeCubit>(), //TODO: Use provider
builder: (context, state) { builder: (context, state) {
return LabelFormField<DocumentType, DocumentTypeQuery>( return LabelFormField<DocumentType, DocumentTypeQuery>(
@@ -178,7 +180,7 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
), ),
label: S.of(context).documentDocumentTypePropertyLabel + " *", label: S.of(context).documentDocumentTypePropertyLabel + " *",
name: DocumentModel.documentTypeKey, name: DocumentModel.documentTypeKey,
state: state, state: state.labels,
queryParameterIdBuilder: DocumentTypeQuery.fromId, queryParameterIdBuilder: DocumentTypeQuery.fromId,
queryParameterNotAssignedBuilder: queryParameterNotAssignedBuilder:
DocumentTypeQuery.notAssigned, DocumentTypeQuery.notAssigned,
@@ -186,7 +188,7 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
); );
}, },
), ),
BlocBuilder<CorrespondentCubit, Map<int, Correspondent>>( BlocBuilder<CorrespondentCubit, LabelState<Correspondent>>(
bloc: getIt<CorrespondentCubit>(), //TODO: Use provider bloc: getIt<CorrespondentCubit>(), //TODO: Use provider
builder: (context, state) { builder: (context, state) {
return LabelFormField<Correspondent, CorrespondentQuery>( return LabelFormField<Correspondent, CorrespondentQuery>(
@@ -200,7 +202,7 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
label: label:
S.of(context).documentCorrespondentPropertyLabel + " *", S.of(context).documentCorrespondentPropertyLabel + " *",
name: DocumentModel.correspondentKey, name: DocumentModel.correspondentKey,
state: state, state: state.labels,
queryParameterIdBuilder: CorrespondentQuery.fromId, queryParameterIdBuilder: CorrespondentQuery.fromId,
queryParameterNotAssignedBuilder: queryParameterNotAssignedBuilder:
CorrespondentQuery.notAssigned, CorrespondentQuery.notAssigned,
@@ -257,7 +259,7 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
} on PaperlessValidationErrors catch (errorMessages) { } on PaperlessValidationErrors catch (errorMessages) {
setState(() => _errors = errorMessages); setState(() => _errors = errorMessages);
} catch (unknownError, stackTrace) { } catch (unknownError, stackTrace) {
showErrorMessage(context, ErrorMessage.unknown(), stackTrace); showErrorMessage(context, const ErrorMessage.unknown(), stackTrace);
} finally { } finally {
setState(() { setState(() {
_isUploadLoading = false; _isUploadLoading = false;
@@ -274,22 +276,23 @@ class _DocumentUploadPageState extends State<DocumentUploadPage> {
return source.replaceAll(RegExp(r"[\W_]"), "_"); return source.replaceAll(RegExp(r"[\W_]"), "_");
} }
void _onConsumptionFinished(document) { void _onConsumptionFinished(DocumentModel document) {
ScaffoldMessenger.of(rootScaffoldKey.currentContext!).showSnackBar( // ScaffoldMessenger.of(rootScaffoldKey.currentContext!).showSnackBar(
SnackBar( // SnackBar(
action: SnackBarAction( // action: SnackBarAction(
onPressed: () async { // onPressed: () async {
try { // try {
getIt<DocumentsCubit>().reloadDocuments(); // getIt<DocumentsCubit>().reloadDocuments();
} on ErrorMessage catch (error, stackTrace) { // } on ErrorMessage catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace); // showErrorMessage(context, error, stackTrace);
} // }
}, // },
label: // label:
S.of(context).documentUploadProcessingSuccessfulReloadActionText, // S.of(context).documentUploadProcessingSuccessfulReloadActionText,
), // ),
content: Text(S.of(context).documentUploadProcessingSuccessfulText), // content: Text(S.of(context).documentUploadProcessingSuccessfulText),
), // ),
); // );
getIt<PaperlessStatisticsCubit>().incrementInboxCount();
} }
} }

View File

@@ -8,7 +8,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:mime/mime.dart'; import 'package:mime/mime.dart';
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart'; import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
import 'package:paperless_mobile/core/global/constants.dart'; import 'package:paperless_mobile/core/global/constants.dart';
import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/core/model/error_message.dart';
import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/core/service/file_service.dart';

View File

@@ -14,23 +14,27 @@ class ApplicationSettingsState {
preferredLocaleSubtag: Platform.localeName.split('_').first, preferredLocaleSubtag: Platform.localeName.split('_').first,
preferredThemeMode: ThemeMode.system, preferredThemeMode: ThemeMode.system,
preferredViewType: ViewType.list, preferredViewType: ViewType.list,
showInboxOnStartup: true,
); );
static const isLocalAuthenticationEnabledKey = "isLocalAuthenticationEnabled"; static const isLocalAuthenticationEnabledKey = "isLocalAuthenticationEnabled";
static const preferredLocaleSubtagKey = "localeSubtag"; static const preferredLocaleSubtagKey = "localeSubtag";
static const preferredThemeModeKey = "preferredThemeModeKey"; static const preferredThemeModeKey = "preferredThemeModeKey";
static const preferredViewTypeKey = 'preferredViewType'; static const preferredViewTypeKey = 'preferredViewType';
static const showInboxOnStartupKey = 'showinboxOnStartup';
final bool isLocalAuthenticationEnabled; final bool isLocalAuthenticationEnabled;
final String preferredLocaleSubtag; final String preferredLocaleSubtag;
final ThemeMode preferredThemeMode; final ThemeMode preferredThemeMode;
final ViewType preferredViewType; final ViewType preferredViewType;
final bool showInboxOnStartup;
ApplicationSettingsState({ ApplicationSettingsState({
required this.preferredLocaleSubtag, required this.preferredLocaleSubtag,
required this.preferredThemeMode, required this.preferredThemeMode,
required this.isLocalAuthenticationEnabled, required this.isLocalAuthenticationEnabled,
required this.preferredViewType, required this.preferredViewType,
required this.showInboxOnStartup,
}); });
JSON toJson() { JSON toJson() {
@@ -43,17 +47,25 @@ class ApplicationSettingsState {
} }
ApplicationSettingsState.fromJson(JSON json) ApplicationSettingsState.fromJson(JSON json)
: isLocalAuthenticationEnabled = json[isLocalAuthenticationEnabledKey], : isLocalAuthenticationEnabled = json[isLocalAuthenticationEnabledKey] ??
preferredLocaleSubtag = json[preferredLocaleSubtagKey], defaultSettings.isLocalAuthenticationEnabled,
preferredThemeMode = preferredLocaleSubtag = json[preferredLocaleSubtagKey] ??
ThemeMode.values.byName(json[preferredThemeModeKey]), defaultSettings.preferredLocaleSubtag,
preferredViewType = ViewType.values.byName(json[preferredViewTypeKey]); preferredThemeMode = json.containsKey(preferredThemeModeKey)
? ThemeMode.values.byName(json[preferredThemeModeKey])
: defaultSettings.preferredThemeMode,
preferredViewType = json.containsKey(preferredViewTypeKey)
? ViewType.values.byName(json[preferredViewTypeKey])
: defaultSettings.preferredViewType,
showInboxOnStartup =
json[showInboxOnStartupKey] ?? defaultSettings.showInboxOnStartup;
ApplicationSettingsState copyWith({ ApplicationSettingsState copyWith({
bool? isLocalAuthenticationEnabled, bool? isLocalAuthenticationEnabled,
String? preferredLocaleSubtag, String? preferredLocaleSubtag,
ThemeMode? preferredThemeMode, ThemeMode? preferredThemeMode,
ViewType? preferredViewType, ViewType? preferredViewType,
bool? showInboxOnStartup,
}) { }) {
return ApplicationSettingsState( return ApplicationSettingsState(
isLocalAuthenticationEnabled: isLocalAuthenticationEnabled:
@@ -62,6 +74,7 @@ class ApplicationSettingsState {
preferredLocaleSubtag ?? this.preferredLocaleSubtag, preferredLocaleSubtag ?? this.preferredLocaleSubtag,
preferredThemeMode: preferredThemeMode ?? this.preferredThemeMode, preferredThemeMode: preferredThemeMode ?? this.preferredThemeMode,
preferredViewType: preferredViewType ?? this.preferredViewType, preferredViewType: preferredViewType ?? this.preferredViewType,
showInboxOnStartup: showInboxOnStartup ?? this.showInboxOnStartup,
); );
} }
} }

View File

@@ -84,7 +84,7 @@
"appSettingsBiometricAuthenticationLabel": "Biometrische Authentifizierung aktivieren", "appSettingsBiometricAuthenticationLabel": "Biometrische Authentifizierung aktivieren",
"appSettingsEnableBiometricAuthenticationReasonText": "Authentifizieren, um die biometrische Authentifizierung zu aktivieren.", "appSettingsEnableBiometricAuthenticationReasonText": "Authentifizieren, um die biometrische Authentifizierung zu aktivieren.",
"appSettingsDisableBiometricAuthenticationReasonText": "Authentifizieren, um die biometrische Authentifizierung zu deaktivieren.", "appSettingsDisableBiometricAuthenticationReasonText": "Authentifizieren, um die biometrische Authentifizierung zu deaktivieren.",
"errorMessageBulkDeleteDocumentsFailed": "Es ist ein Fehler beim massenhaften Löschen der Dokumente aufgetreten.", "errorMessageBulkActionFailed": "Es ist ein Fehler beim massenhaften bearbeiten der Dokumente aufgetreten.",
"errorMessageBiotmetricsNotSupported": "Biometrische Authentifizierung wird von diesem Gerät nicht unterstützt.", "errorMessageBiotmetricsNotSupported": "Biometrische Authentifizierung wird von diesem Gerät nicht unterstützt.",
"errorMessageBiometricAuthenticationFailed": "Biometrische Authentifizierung fehlgeschlagen.", "errorMessageBiometricAuthenticationFailed": "Biometrische Authentifizierung fehlgeschlagen.",
"errorMessageDeviceOffline": "Daten konnten nicht geladen werden: Eine Verbindung zum Internet konnte nicht hergestellt werden.", "errorMessageDeviceOffline": "Daten konnten nicht geladen werden: Eine Verbindung zum Internet konnte nicht hergestellt werden.",

View File

@@ -83,7 +83,7 @@
"documentPreviewPageTitle": "Preview", "documentPreviewPageTitle": "Preview",
"appSettingsEnableBiometricAuthenticationReasonText": "Authenticate to enable biometric authentication", "appSettingsEnableBiometricAuthenticationReasonText": "Authenticate to enable biometric authentication",
"appSettingsDisableBiometricAuthenticationReasonText": "Authenticate to disable biometric authentication", "appSettingsDisableBiometricAuthenticationReasonText": "Authenticate to disable biometric authentication",
"errorMessageBulkDeleteDocumentsFailed": "Could not bulk delete documents.", "errorMessageBulkActionFailed": "Could not bulk edit documents.",
"errorMessageBiotmetricsNotSupported": "Biometric authentication not supported on this device.", "errorMessageBiotmetricsNotSupported": "Biometric authentication not supported on this device.",
"errorMessageBiometricAuthenticationFailed": "Biometric authentication failed.", "errorMessageBiometricAuthenticationFailed": "Biometric authentication failed.",
"errorMessageDeviceOffline": "Could not fetch data: You are not connected to the internet.", "errorMessageDeviceOffline": "Could not fetch data: You are not connected to the internet.",

View File

@@ -11,7 +11,8 @@ import 'package:intl/intl.dart';
import 'package:intl/intl_standalone.dart'; import 'package:intl/intl_standalone.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/label_bloc_provider.dart'; import 'package:paperless_mobile/core/bloc/paperless_statistics_cubit.dart';
import 'package:paperless_mobile/features/labels/bloc/label_bloc_provider.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart'; import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/global/asset_images.dart'; import 'package:paperless_mobile/core/global/asset_images.dart';
import 'package:paperless_mobile/core/global/constants.dart'; import 'package:paperless_mobile/core/global/constants.dart';
@@ -194,15 +195,6 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
ReceiveSharingIntent.getInitialMedia().then(handleReceivedFiles); ReceiveSharingIntent.getInitialMedia().then(handleReceivedFiles);
} }
@override
void didChangeDependencies() {
FlutterNativeSplash.remove();
for (var element in AssetImages.values) {
element.load(context);
}
super.didChangeDependencies();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SafeArea( return SafeArea(
@@ -215,9 +207,6 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
final bool showIntroSlider = final bool showIntroSlider =
authState.isAuthenticated && !authState.wasLoginStored; authState.isAuthenticated && !authState.wasLoginStored;
if (showIntroSlider) { if (showIntroSlider) {
for (final img in AssetImages.values) {
img.load(context);
}
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
@@ -229,10 +218,14 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
}, },
builder: (context, authentication) { builder: (context, authentication) {
if (authentication.isAuthenticated) { if (authentication.isAuthenticated) {
return const LabelBlocProvider( return BlocProvider.value(
child: HomePage(), value: getIt<PaperlessStatisticsCubit>(),
child: const LabelBlocProvider(
child: HomePage(),
),
); );
} else { } else {
FlutterNativeSplash.remove();
return const LoginPage(); return const LoginPage();
} }
}, },