diff --git a/lib/core/widgets/hint_card.dart b/lib/core/widgets/hint_card.dart index 44047a1..27b6ddb 100644 --- a/lib/core/widgets/hint_card.dart +++ b/lib/core/widgets/hint_card.dart @@ -6,12 +6,12 @@ import 'package:paperless_mobile/generated/l10n.dart'; class HintCard extends StatelessWidget { final String hintText; final double elevation; - final VoidCallback onHintAcknowledged; + final VoidCallback? onHintAcknowledged; final bool show; const HintCard({ super.key, required this.hintText, - required this.onHintAcknowledged, + this.onHintAcknowledged, this.elevation = 1, required this.show, }); @@ -43,13 +43,16 @@ class HintCard extends StatelessWidget { style: Theme.of(context).textTheme.bodySmall, ), ), - Align( - alignment: Alignment.bottomRight, - child: TextButton( - child: Text(S.of(context).genericAcknowledgeLabel), - onPressed: onHintAcknowledged, - ), - ), + if (onHintAcknowledged != null) + Align( + alignment: Alignment.bottomRight, + child: TextButton( + child: Text(S.of(context).genericAcknowledgeLabel), + onPressed: onHintAcknowledged, + ), + ) + else + Padding(padding: EdgeInsets.only(bottom: 24)), ], ).padded(), ).padded(), diff --git a/lib/extensions/date_time_extensions.dart b/lib/extensions/date_time_extensions.dart new file mode 100644 index 0000000..657c071 --- /dev/null +++ b/lib/extensions/date_time_extensions.dart @@ -0,0 +1,5 @@ +extension DateComparisons on DateTime { + bool isEqualToIgnoringDate(DateTime other) { + return day == other.day && month == other.month && year == other.year; + } +} diff --git a/lib/features/document_upload/view/document_upload_preparation_page.dart b/lib/features/document_upload/view/document_upload_preparation_page.dart index d7a22b1..f97d084 100644 --- a/lib/features/document_upload/view/document_upload_preparation_page.dart +++ b/lib/features/document_upload/view/document_upload_preparation_page.dart @@ -11,6 +11,7 @@ import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart'; import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart'; import 'package:paperless_mobile/core/type/types.dart'; +import 'package:paperless_mobile/core/widgets/hint_card.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart'; import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart'; @@ -148,11 +149,14 @@ class _DocumentUploadPreparationPageState } } }, - title: Text(S - .of(context) - .documentUploadPageSynchronizeTitleAndFilenameLabel), //TODO: INTL + title: Text( + S + .of(context) + .documentUploadPageSynchronizeTitleAndFilenameLabel, + ), //TODO: INTL ), FormBuilderDateTimePicker( + enabled: false, autovalidateMode: AutovalidateMode.always, format: DateFormat("dd. MMMM yyyy"), //TODO: INTL inputType: InputType.date, @@ -164,6 +168,11 @@ class _DocumentUploadPreparationPageState S.of(context).documentCreatedPropertyLabel + " *", ), ), + const HintCard( + hintText: + "Due to an apparent parsing bug with Paperless, setting the 'created at' date will cause the document consumption to fail! Therefore this field is disabled for now until this is fixed or I find a workaround!", + show: true, + ), LabelFormField( notAssignedSelectable: false, formBuilderState: _formKey.currentState, diff --git a/lib/features/inbox/bloc/inbox_cubit.dart b/lib/features/inbox/bloc/inbox_cubit.dart index 493c953..ab719ee 100644 --- a/lib/features/inbox/bloc/inbox_cubit.dart +++ b/lib/features/inbox/bloc/inbox_cubit.dart @@ -64,7 +64,6 @@ class InboxCubit extends HydratedCubit { /// Fetches inbox tag ids and loads the inbox items (documents). /// Future initializeInbox() async { - if (state.isLoaded) return; final inboxTags = await _tagsRepository.findAll().then( (tags) => tags.where((t) => t.isInboxTag ?? false).map((t) => t.id!), ); @@ -206,13 +205,14 @@ class InboxCubit extends HydratedCubit { } void loadSuggestions() { - Future.wait(state.inboxItems + state.inboxItems .whereNot((doc) => state.suggestions.containsKey(doc.id)) - .map((e) => _documentsApi.findSuggestions(e))).then((results) { - emit(state.copyWith(suggestions: { - ...state.suggestions, - for (var r in results) r.documentId!: r - })); + .map((e) => _documentsApi.findSuggestions(e)) + .forEach((suggestion) async { + final s = await suggestion; + emit(state.copyWith( + suggestions: {...state.suggestions, s.documentId!: s}, + )); }); } diff --git a/lib/features/inbox/view/pages/inbox_page.dart b/lib/features/inbox/view/pages/inbox_page.dart index a04dfac..3ff174c 100644 --- a/lib/features/inbox/view/pages/inbox_page.dart +++ b/lib/features/inbox/view/pages/inbox_page.dart @@ -115,9 +115,24 @@ class _InboxPageState extends State { SliverList( delegate: SliverChildBuilderDelegate( childCount: entry.value.length, - (context, index) => _buildListItem( - entry.value[index], - ), + (context, index) { + if (index < entry.value.length - 1) { + return Column( + children: [ + _buildListItem( + entry.value[index], + ), + const Divider( + indent: 16, + endIndent: 16, + ), + ], + ); + } + return _buildListItem( + entry.value[index], + ); + }, ), ), ], diff --git a/lib/features/inbox/view/widgets/inbox_item.dart b/lib/features/inbox/view/widgets/inbox_item.dart index e8f9172..f17baee 100644 --- a/lib/features/inbox/view/widgets/inbox_item.dart +++ b/lib/features/inbox/view/widgets/inbox_item.dart @@ -1,9 +1,14 @@ +import 'dart:developer'; + +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:intl/intl.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart'; import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart'; import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart'; +import 'package:paperless_mobile/extensions/date_time_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart'; @@ -14,6 +19,7 @@ import 'package:paperless_mobile/features/inbox/bloc/state/inbox_state.dart'; import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart'; import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart'; import 'package:paperless_mobile/generated/l10n.dart'; +import 'package:paperless_mobile/util.dart'; class InboxItem extends StatefulWidget { static const _a4AspectRatio = 1 / 1.4142; @@ -36,6 +42,7 @@ class _InboxItemState extends State { @override Widget build(BuildContext context) { return GestureDetector( + behavior: HitTestBehavior.translucent, onTap: () async { final returnedDocument = await Navigator.push( context, @@ -57,8 +64,7 @@ class _InboxItemState extends State { widget.onDocumentUpdated(returnedDocument); } }, - child: Container( - padding: const EdgeInsets.all(4), + child: SizedBox( height: 180, child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -74,29 +80,31 @@ class _InboxItemState extends State { alignment: Alignment.topCenter, enableHero: false, ), - ), + ).padded(), Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildTitle(), + _buildTitle().paddedOnly(left: 8, right: 8, top: 8), const Spacer(), - _buildCorrespondent(context), - _buildDocumentType(context), + _buildCorrespondent(context) + .paddedSymmetrically(horizontal: 8), + _buildDocumentType(context) + .paddedSymmetrically(horizontal: 8), const Spacer(), - _buildTags(), + _buildTags().paddedOnly(left: 8, bottom: 8), ], - ).padded(), + ), ), ], ), ), SizedBox( - height: 48, + height: 56, child: _buildActions(context), ), ], - ).padded(), + ).paddedOnly(left: 8, top: 8, bottom: 8), ), ); } @@ -127,25 +135,51 @@ class _InboxItemState extends State { ]; return BlocBuilder( builder: (context, state) { - return ListView( - scrollDirection: Axis.horizontal, + return Row( children: [ - ...actions, - if (state.suggestions[widget.document.id] != null) ...[ - SizedBox(width: 4), - ..._buildSuggestionChips( - chipShape, - state.suggestions[widget.document.id]!, - state, - ) - ] + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.bolt_outlined), + SizedBox( + width: 40, + child: Text( + S.of(context).inboxPageQuickActionsLabel, + textAlign: TextAlign.center, + maxLines: 2, + style: Theme.of(context).textTheme.labelSmall, + ), + ), + const VerticalDivider( + indent: 16, + endIndent: 16, + ), + ], + ), + const SizedBox(width: 4.0), + Expanded( + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + ...actions, + if (state.suggestions[widget.document.id] != null) ...[ + const SizedBox(width: 4), + ..._buildSuggestionChips( + chipShape, + state.suggestions[widget.document.id]!, + state, + ) + ] + ], + ), + ), ], ); }, ); } - ActionChip _buildAssignAsnAction( + Widget _buildAssignAsnAction( RoundedRectangleBorder chipShape, BuildContext context, ) { @@ -247,6 +281,7 @@ class _InboxItemState extends State { ) { return [ ...suggestions.correspondents + .whereNot((e) => widget.document.correspondent == e) .map( (e) => ActionChip( avatar: const Icon(Icons.person_outline), @@ -258,28 +293,78 @@ class _InboxItemState extends State { .updateDocument(widget.document.copyWith( correspondent: e, overwriteCorrespondent: true, - )); + )) + .then((value) => showSnackBar( + context, + S + .of(context) + .inboxPageSuggestionSuccessfullyAppliedMessage)); }, ), ) .toList(), ...suggestions.documentTypes + .whereNot((e) => widget.document.documentType == e) .map( (e) => ActionChip( avatar: const Icon(Icons.description_outlined), shape: chipShape, label: Text(state.availableDocumentTypes[e]?.name ?? ''), + onPressed: () => context + .read() + .updateDocument(widget.document + .copyWith(documentType: e, overwriteDocumentType: true)) + .then((value) => showSnackBar( + context, + S + .of(context) + .inboxPageSuggestionSuccessfullyAppliedMessage)), + ), + ) + .toList(), + ...suggestions.tags + .whereNot((e) => widget.document.tags.contains(e)) + .map( + (e) => ActionChip( + avatar: const Icon(Icons.label_outline), + shape: chipShape, + label: Text(state.availableTags[e]?.name ?? ''), onPressed: () { context .read() .updateDocument(widget.document.copyWith( - documentType: e, - overwriteDocumentType: true, - )); + tags: {...widget.document.tags, e}.toList(), + overwriteTags: true, + )) + .then((value) => showSnackBar( + context, + S + .of(context) + .inboxPageSuggestionSuccessfullyAppliedMessage)); }, ), ) .toList(), - ]; + ...suggestions.dates + .whereNot((e) => widget.document.created.isEqualToIgnoringDate(e)) + .map( + (e) => ActionChip( + avatar: const Icon(Icons.calendar_today_outlined), + shape: chipShape, + label: Text( + "${S.of(context).documentCreatedPropertyLabel}: ${DateFormat.yMd().format(e)}", + ), + onPressed: () => context + .read() + .updateDocument(widget.document.copyWith(created: e)) + .then((value) => showSnackBar( + context, + S + .of(context) + .inboxPageSuggestionSuccessfullyAppliedMessage)), + ), + ) + .toList(), + ].expand((element) => [element, const SizedBox(width: 4)]).toList(); } } diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index a736d15..721fc9f 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -364,6 +364,8 @@ "@genericActionUploadLabel": {}, "genericMessageOfflineText": "Jste offline.", "@genericMessageOfflineText": {}, + "inboxPageAssignAsnLabel": "", + "@inboxPageAssignAsnLabel": {}, "inboxPageDocumentRemovedMessageText": "Dokument odstraněn z inboxu.", "@inboxPageDocumentRemovedMessageText": {}, "inboxPageMarkAllAsSeenConfirmationDialogText": "Opravdu chcete označit všechny dokumenty jako shlédnuté? Toto provede hromadnou úpravu a odstraní inbox tag u všech dokumentů.\nToto je nevratná akce! Opravdu chcete pokračovat?", @@ -378,6 +380,10 @@ "@inboxPageNoNewDocumentsRefreshLabel": {}, "inboxPageNoNewDocumentsText": "Nemáte neshlédnuté dokumenty.", "@inboxPageNoNewDocumentsText": {}, + "inboxPageQuickActionsLabel": "", + "@inboxPageQuickActionsLabel": {}, + "inboxPageSuggestionSuccessfullyAppliedMessage": "", + "@inboxPageSuggestionSuccessfullyAppliedMessage": {}, "inboxPageTodayText": "Dnes", "@inboxPageTodayText": {}, "inboxPageUndoRemoveText": "VRÁTIT", @@ -563,6 +569,5 @@ "verifyIdentityPageTitle": "", "@verifyIdentityPageTitle": {}, "verifyIdentityPageVerifyIdentityButtonLabel": "", - "@verifyIdentityPageVerifyIdentityButtonLabel": {}, - "inboxPageAssignAsnLabel": "Assign ASN" + "@verifyIdentityPageVerifyIdentityButtonLabel": {} } \ No newline at end of file diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 6819885..d2afbc2 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -364,6 +364,8 @@ "@genericActionUploadLabel": {}, "genericMessageOfflineText": "Du bist offline.", "@genericMessageOfflineText": {}, + "inboxPageAssignAsnLabel": "", + "@inboxPageAssignAsnLabel": {}, "inboxPageDocumentRemovedMessageText": "Dokument aus Posteingang entfernt.", "@inboxPageDocumentRemovedMessageText": {}, "inboxPageMarkAllAsSeenConfirmationDialogText": "Bist Du sicher, dass Du alle Dokumente als gesehen markieren möchtest? Dadurch wird eine Massenbearbeitung durchgeführt, bei der alle Posteingangs-Tags von den Dokumenten entfernt werden. Diese Aktion kann nicht rückgängig gemacht werden! Möchtest Du trotzdem fortfahren?", @@ -378,6 +380,10 @@ "@inboxPageNoNewDocumentsRefreshLabel": {}, "inboxPageNoNewDocumentsText": "Du hast keine ungesehenen Dokumente.", "@inboxPageNoNewDocumentsText": {}, + "inboxPageQuickActionsLabel": "Schnell Aktion", + "@inboxPageQuickActionsLabel": {}, + "inboxPageSuggestionSuccessfullyAppliedMessage": "Vorschlag wurde erfolgreich angewendet.", + "@inboxPageSuggestionSuccessfullyAppliedMessage": {}, "inboxPageTodayText": "Heute", "@inboxPageTodayText": {}, "inboxPageUndoRemoveText": "Undo", @@ -563,6 +569,5 @@ "verifyIdentityPageTitle": "Verifiziere deine Identität", "@verifyIdentityPageTitle": {}, "verifyIdentityPageVerifyIdentityButtonLabel": "Identität verifizieren", - "@verifyIdentityPageVerifyIdentityButtonLabel": {}, - "inboxPageAssignAsnLabel": "Assign ASN" + "@verifyIdentityPageVerifyIdentityButtonLabel": {} } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index eb76a5a..8cdfccf 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -364,6 +364,8 @@ "@genericActionUploadLabel": {}, "genericMessageOfflineText": "You're offline.", "@genericMessageOfflineText": {}, + "inboxPageAssignAsnLabel": "Assign ASN", + "@inboxPageAssignAsnLabel": {}, "inboxPageDocumentRemovedMessageText": "Document removed from inbox.", "@inboxPageDocumentRemovedMessageText": {}, "inboxPageMarkAllAsSeenConfirmationDialogText": "Are you sure you want to mark all documents as seen? This will perform a bulk edit operation removing all inbox tags from the documents. This action is not reversible! Are you sure you want to continue?", @@ -378,6 +380,10 @@ "@inboxPageNoNewDocumentsRefreshLabel": {}, "inboxPageNoNewDocumentsText": "You do not have unseen documents.", "@inboxPageNoNewDocumentsText": {}, + "inboxPageQuickActionsLabel": "Quick Action", + "@inboxPageQuickActionsLabel": {}, + "inboxPageSuggestionSuccessfullyAppliedMessage": "Suggestion successfully applied.", + "@inboxPageSuggestionSuccessfullyAppliedMessage": {}, "inboxPageTodayText": "Today", "@inboxPageTodayText": {}, "inboxPageUndoRemoveText": "Undo", @@ -563,6 +569,5 @@ "verifyIdentityPageTitle": "Verify your identity", "@verifyIdentityPageTitle": {}, "verifyIdentityPageVerifyIdentityButtonLabel": "Verify Identity", - "@verifyIdentityPageVerifyIdentityButtonLabel": {}, - "inboxPageAssignAsnLabel": "Assign ASN" + "@verifyIdentityPageVerifyIdentityButtonLabel": {} } \ No newline at end of file diff --git a/packages/paperless_api/lib/src/models/document_model.dart b/packages/paperless_api/lib/src/models/document_model.dart index 1d9ec0a..3f1a18d 100644 --- a/packages/paperless_api/lib/src/models/document_model.dart +++ b/packages/paperless_api/lib/src/models/document_model.dart @@ -51,9 +51,9 @@ class DocumentModel extends Equatable { : id = json[idKey], title = json[titleKey], content = json[contentKey], - created = DateTime.parse(json[createdKey]).toLocal(), - modified = DateTime.parse(json[modifiedKey]).toLocal(), - added = DateTime.parse(json[addedKey]).toLocal(), + created = DateTime.parse(json[createdKey]), + modified = DateTime.parse(json[modifiedKey]), + added = DateTime.parse(json[addedKey]), archiveSerialNumber = json[asnKey], originalFileName = json[originalFileNameKey], archivedFileName = json[archivedFileNameKey], diff --git a/pubspec.yaml b/pubspec.yaml index f51ab43..dfcb1aa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.5.0+13 +version: 1.5.1+14 environment: sdk: '>=3.0.0-35.0.dev <4.0.0'