mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 15:15:50 -06:00
Added quick actions to inbox, disabled created at field in document upload for now and added hint
This commit is contained in:
@@ -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(),
|
||||
|
||||
5
lib/extensions/date_time_extensions.dart
Normal file
5
lib/extensions/date_time_extensions.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
extension DateComparisons on DateTime {
|
||||
bool isEqualToIgnoringDate(DateTime other) {
|
||||
return day == other.day && month == other.month && year == other.year;
|
||||
}
|
||||
}
|
||||
@@ -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<DocumentType>(
|
||||
notAssignedSelectable: false,
|
||||
formBuilderState: _formKey.currentState,
|
||||
|
||||
@@ -64,7 +64,6 @@ class InboxCubit extends HydratedCubit<InboxState> {
|
||||
/// Fetches inbox tag ids and loads the inbox items (documents).
|
||||
///
|
||||
Future<void> 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<InboxState> {
|
||||
}
|
||||
|
||||
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},
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -115,9 +115,24 @@ class _InboxPageState extends State<InboxPage> {
|
||||
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],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -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<InboxItem> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () async {
|
||||
final returnedDocument = await Navigator.push<DocumentModel?>(
|
||||
context,
|
||||
@@ -57,8 +64,7 @@ class _InboxItemState extends State<InboxItem> {
|
||||
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<InboxItem> {
|
||||
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<InboxItem> {
|
||||
];
|
||||
return BlocBuilder<InboxCubit, InboxState>(
|
||||
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<InboxItem> {
|
||||
) {
|
||||
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<InboxItem> {
|
||||
.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<InboxCubit>()
|
||||
.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<InboxCubit>()
|
||||
.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<InboxCubit>()
|
||||
.updateDocument(widget.document.copyWith(created: e))
|
||||
.then((value) => showSnackBar(
|
||||
context,
|
||||
S
|
||||
.of(context)
|
||||
.inboxPageSuggestionSuccessfullyAppliedMessage)),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
].expand((element) => [element, const SizedBox(width: 4)]).toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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": {}
|
||||
}
|
||||
@@ -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],
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user