diff --git a/lib/core/bloc/base_state.dart b/lib/core/bloc/base_state.dart new file mode 100644 index 0000000..6e16240 --- /dev/null +++ b/lib/core/bloc/base_state.dart @@ -0,0 +1,25 @@ +import 'package:paperless_mobile/core/bloc/loading_status.dart'; + +class BaseState { + final Object? error; + final T? value; + final LoadingStatus status; + + BaseState({ + required this.error, + required this.value, + required this.status, + }); + + BaseState copyWith({ + Object? error, + T? value, + LoadingStatus? status, + }) { + return BaseState( + error: error ?? this.error, + value: value ?? this.value, + status: status ?? this.status, + ); + } +} diff --git a/lib/features/document_details/view/pages/document_details_page.dart b/lib/features/document_details/view/pages/document_details_page.dart index 30ea68c..7d6ccd1 100644 --- a/lib/features/document_details/view/pages/document_details_page.dart +++ b/lib/features/document_details/view/pages/document_details_page.dart @@ -15,6 +15,7 @@ import 'package:paperless_mobile/features/document_details/cubit/document_detail import 'package:paperless_mobile/features/document_details/view/widgets/document_content_widget.dart'; import 'package:paperless_mobile/features/document_details/view/widgets/document_download_button.dart'; import 'package:paperless_mobile/features/document_details/view/widgets/document_meta_data_widget.dart'; +import 'package:paperless_mobile/features/document_details/view/widgets/document_notes_widget.dart'; import 'package:paperless_mobile/features/document_details/view/widgets/document_overview_widget.dart'; import 'package:paperless_mobile/features/document_details/view/widgets/document_permissions_widget.dart'; import 'package:paperless_mobile/features/document_details/view/widgets/document_share_button.dart'; @@ -67,7 +68,7 @@ class _DocumentDetailsPageState extends State { debugPrint(disableAnimations.toString()); final hasMultiUserSupport = context.watch().hasMultiUserSupport; - final tabLength = 4 + (hasMultiUserSupport ? 1 : 0); + final tabLength = 5 + (hasMultiUserSupport ? 1 : 0); return AnnotatedRegion( value: buildOverlayStyle( Theme.of(context), @@ -201,6 +202,16 @@ class _DocumentDetailsPageState extends State { ), ), ), + Tab( + child: Text( + "Notes", + style: TextStyle( + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, + ), + ), + ), if (hasMultiUserSupport) Tab( child: Text( @@ -303,6 +314,35 @@ class _DocumentDetailsPageState extends State { ), ], ), + CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView + .sliverOverlapAbsorberHandleFor(context), + ), + switch (state.status) { + LoadingStatus.loaded => DocumentNotesWidget( + document: state.document!, + ), + LoadingStatus.error => _buildErrorState(), + _ => _buildLoadingState(), + }, + if (state.status == LoadingStatus.loaded) + SliverToBoxAdapter( + child: Align( + alignment: Alignment.centerRight, + child: ElevatedButton.icon( + onPressed: () { + AddNoteRoute($extra: state.document!) + .push(context); + }, + icon: Icon(Icons.note_add_outlined), + label: Text('Add note'), + ), + ), + ), + ], + ), if (hasMultiUserSupport) CustomScrollView( controller: _pagingScrollController, diff --git a/lib/features/document_details/view/widgets/add_note_page.dart b/lib/features/document_details/view/widgets/add_note_page.dart new file mode 100644 index 0000000..0de1544 --- /dev/null +++ b/lib/features/document_details/view/widgets/add_note_page.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; + +class AddNotePage extends StatefulWidget { + final DocumentModel document; + + const AddNotePage({super.key, required this.document}); + + @override + State createState() => _AddNotePageState(); +} + +class _AddNotePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(S.of(context)!.addNote), + ), + body: Column( + children: [ + TextField( + decoration: InputDecoration( + labelText: S.of(context)!.content, + ), + ), + ElevatedButton( + onPressed: () {}, + child: Text(S.of(context)!.save), + ), + ], + ), + ); + } +} diff --git a/lib/features/document_details/view/widgets/document_meta_data_widget.dart b/lib/features/document_details/view/widgets/document_meta_data_widget.dart index 02d99fc..d11f402 100644 --- a/lib/features/document_details/view/widgets/document_meta_data_widget.dart +++ b/lib/features/document_details/view/widgets/document_meta_data_widget.dart @@ -25,54 +25,51 @@ class DocumentMetaDataWidget extends StatelessWidget { Widget build(BuildContext context) { final currentUser = context.watch().paperlessUser; - return SliverList( - delegate: SliverChildListDelegate( - [ - if (currentUser.canEditDocuments) - ArchiveSerialNumberField( - document: document, - ).paddedOnly(bottom: itemSpacing), - DetailsItem.text( - DateFormat.yMMMMd(Localizations.localeOf(context).toString()) - .format(document.modified), - context: context, - label: S.of(context)!.modifiedAt, + return SliverList.list( + children: [ + if (currentUser.canEditDocuments) + ArchiveSerialNumberField( + document: document, ).paddedOnly(bottom: itemSpacing), + DetailsItem.text( + DateFormat.yMMMMd(Localizations.localeOf(context).toString()) + .format(document.modified), + context: context, + label: S.of(context)!.modifiedAt, + ).paddedOnly(bottom: itemSpacing), + DetailsItem.text( + DateFormat.yMMMMd(Localizations.localeOf(context).toString()) + .format(document.added), + context: context, + label: S.of(context)!.addedAt, + ).paddedOnly(bottom: itemSpacing), + DetailsItem.text( + metaData.mediaFilename, + context: context, + label: S.of(context)!.mediaFilename, + ).paddedOnly(bottom: itemSpacing), + if (document.originalFileName != null) DetailsItem.text( - DateFormat.yMMMMd(Localizations.localeOf(context).toString()) - .format(document.added), - context: context, - label: S.of(context)!.addedAt, - ).paddedOnly(bottom: itemSpacing), - DetailsItem.text( - metaData.mediaFilename, - context: context, - label: S.of(context)!.mediaFilename, - ).paddedOnly(bottom: itemSpacing), - if (document.originalFileName != null) - DetailsItem.text( - document.originalFileName!, - context: context, - label: S.of(context)!.originalMD5Checksum, - ).paddedOnly(bottom: itemSpacing), - DetailsItem.text( - metaData.originalChecksum, + document.originalFileName!, context: context, label: S.of(context)!.originalMD5Checksum, ).paddedOnly(bottom: itemSpacing), - DetailsItem.text( - formatBytes(metaData.originalSize, 2), - context: context, - label: S.of(context)!.originalFileSize, - ).paddedOnly(bottom: itemSpacing), - DetailsItem.text( - metaData.originalMimeType, - context: context, - label: S.of(context)!.originalMIMEType, - ).paddedOnly(bottom: itemSpacing), - - ], - ), + DetailsItem.text( + metaData.originalChecksum, + context: context, + label: S.of(context)!.originalMD5Checksum, + ).paddedOnly(bottom: itemSpacing), + DetailsItem.text( + formatBytes(metaData.originalSize, 2), + context: context, + label: S.of(context)!.originalFileSize, + ).paddedOnly(bottom: itemSpacing), + DetailsItem.text( + metaData.originalMimeType, + context: context, + label: S.of(context)!.originalMIMEType, + ).paddedOnly(bottom: itemSpacing), + ], ); } } diff --git a/lib/features/document_details/view/widgets/document_notes_widget.dart b/lib/features/document_details/view/widgets/document_notes_widget.dart new file mode 100644 index 0000000..5ffdc77 --- /dev/null +++ b/lib/features/document_details/view/widgets/document_notes_widget.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:intl/intl.dart'; +import 'package:paperless_api/paperless_api.dart'; + +class DocumentNotesWidget extends StatelessWidget { + final DocumentModel document; + const DocumentNotesWidget({super.key, required this.document}); + + @override + Widget build(BuildContext context) { + return SliverList.builder( + itemBuilder: (context, index) { + final note = document.notes.elementAt(index); + return ListTile( + title: Text(note.note), + subtitle: Text( + DateFormat.yMMMd(Localizations.localeOf(context).toString()) + .format(note.created)), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () {}, + icon: Icon(Icons.edit), + ), + IconButton( + onPressed: () {}, + icon: Icon(Icons.delete), + ), + ], + ), + ); + }, + itemCount: document.notes.length, + ); + } +} diff --git a/lib/l10n/intl_ca.arb b/lib/l10n/intl_ca.arb index 1915311..bfb53d6 100644 --- a/lib/l10n/intl_ca.arb +++ b/lib/l10n/intl_ca.arb @@ -1024,5 +1024,7 @@ "@testingConnection": { "description": "Text shown while the app tries to establish a connection to the specified host." }, - "version": "Versió {versionCode}" + "version": "Versió {versionCode}", + "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}", + "addNote": "Add note" } \ No newline at end of file diff --git a/lib/l10n/intl_cs.arb b/lib/l10n/intl_cs.arb index 32c55a9..e7b6fbc 100644 --- a/lib/l10n/intl_cs.arb +++ b/lib/l10n/intl_cs.arb @@ -1024,5 +1024,7 @@ "@testingConnection": { "description": "Text shown while the app tries to establish a connection to the specified host." }, - "version": "Version {versionCode}" + "version": "Version {versionCode}", + "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}", + "addNote": "Add note" } \ No newline at end of file diff --git a/lib/l10n/intl_de.arb b/lib/l10n/intl_de.arb index 6118dd3..b314eed 100644 --- a/lib/l10n/intl_de.arb +++ b/lib/l10n/intl_de.arb @@ -1024,5 +1024,7 @@ "@testingConnection": { "description": "Text shown while the app tries to establish a connection to the specified host." }, - "version": "Version {versionCode}" + "version": "Version {versionCode}", + "notes": "{count, plural, zero{Notizen} one{Notiz} other{Notizen}}", + "addNote": "Notiz hinzufügen" } \ No newline at end of file diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 84674b1..5a9c1b8 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1024,5 +1024,7 @@ "@testingConnection": { "description": "Text shown while the app tries to establish a connection to the specified host." }, - "version": "Version {versionCode}" + "version": "Version {versionCode}", + "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}", + "addNote": "Add note" } \ No newline at end of file diff --git a/lib/l10n/intl_es.arb b/lib/l10n/intl_es.arb index f05b474..efe3a87 100644 --- a/lib/l10n/intl_es.arb +++ b/lib/l10n/intl_es.arb @@ -1024,5 +1024,7 @@ "@testingConnection": { "description": "Text shown while the app tries to establish a connection to the specified host." }, - "version": "Version {versionCode}" + "version": "Version {versionCode}", + "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}", + "addNote": "Add note" } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 471430d..8d02103 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -1024,5 +1024,7 @@ "@testingConnection": { "description": "Text shown while the app tries to establish a connection to the specified host." }, - "version": "Version {versionCode}" + "version": "Version {versionCode}", + "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}", + "addNote": "Add note" } \ No newline at end of file diff --git a/lib/l10n/intl_nl.arb b/lib/l10n/intl_nl.arb index 84674b1..5a9c1b8 100644 --- a/lib/l10n/intl_nl.arb +++ b/lib/l10n/intl_nl.arb @@ -1024,5 +1024,7 @@ "@testingConnection": { "description": "Text shown while the app tries to establish a connection to the specified host." }, - "version": "Version {versionCode}" + "version": "Version {versionCode}", + "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}", + "addNote": "Add note" } \ No newline at end of file diff --git a/lib/l10n/intl_pl.arb b/lib/l10n/intl_pl.arb index 4f15c72..fb7fc7d 100644 --- a/lib/l10n/intl_pl.arb +++ b/lib/l10n/intl_pl.arb @@ -1024,5 +1024,7 @@ "@testingConnection": { "description": "Text shown while the app tries to establish a connection to the specified host." }, - "version": "Version {versionCode}" + "version": "Version {versionCode}", + "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}", + "addNote": "Add note" } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 940d32a..5b9ea09 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -1024,5 +1024,7 @@ "@testingConnection": { "description": "Text shown while the app tries to establish a connection to the specified host." }, - "version": "Version {versionCode}" + "version": "Version {versionCode}", + "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}", + "addNote": "Add note" } \ No newline at end of file diff --git a/lib/l10n/intl_tr.arb b/lib/l10n/intl_tr.arb index 98e3675..b84fa77 100644 --- a/lib/l10n/intl_tr.arb +++ b/lib/l10n/intl_tr.arb @@ -1024,5 +1024,7 @@ "@testingConnection": { "description": "Text shown while the app tries to establish a connection to the specified host." }, - "version": "Version {versionCode}" + "version": "Version {versionCode}", + "notes": "{count, plural, zero{Notes} one{Note} other{Notes}}", + "addNote": "Add note" } \ No newline at end of file diff --git a/lib/routing/routes.dart b/lib/routing/routes.dart index 7f48599..3a44fa1 100644 --- a/lib/routing/routes.dart +++ b/lib/routing/routes.dart @@ -26,4 +26,5 @@ class R { static const loggingOut = "loggingOut"; static const restoringSession = "restoringSession"; static const addAccount = 'addAccount'; + static const addNote = 'addNote'; } diff --git a/lib/routing/routes/documents_route.dart b/lib/routing/routes/documents_route.dart index c26dde7..16ecc98 100644 --- a/lib/routing/routes/documents_route.dart +++ b/lib/routing/routes/documents_route.dart @@ -9,6 +9,7 @@ import 'package:paperless_mobile/features/document_bulk_action/view/widgets/full import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_tags_widget.dart'; import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart'; +import 'package:paperless_mobile/features/document_details/view/widgets/add_note_page.dart'; import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.dart'; import 'package:paperless_mobile/features/document_edit/view/document_edit_page.dart'; import 'package:paperless_mobile/features/documents/view/pages/document_view.dart'; @@ -210,3 +211,16 @@ class BulkEditDocumentsRoute extends GoRouteData { ); } } + +class AddNoteRoute extends GoRouteData { + final DocumentModel $extra; + + AddNoteRoute({required this.$extra}); + + @override + Widget build(BuildContext context, GoRouterState state) { + return AddNotePage( + document: $extra, + ); + } +} diff --git a/lib/routing/routes/shells/authenticated_route.dart b/lib/routing/routes/shells/authenticated_route.dart index feed381..92eac37 100644 --- a/lib/routing/routes/shells/authenticated_route.dart +++ b/lib/routing/routes/shells/authenticated_route.dart @@ -71,6 +71,7 @@ part 'authenticated_route.g.dart'; TypedGoRoute( path: "details/:id", name: R.documentDetails, + routes: [], ), TypedGoRoute( path: "edit", @@ -84,6 +85,10 @@ part 'authenticated_route.g.dart'; path: 'preview', name: R.documentPreview, ), + TypedGoRoute( + path: 'add-note', + name: R.addNote, + ), ], ) ], diff --git a/packages/paperless_api/lib/src/models/document_model.dart b/packages/paperless_api/lib/src/models/document_model.dart index 3c605ee..b43c58d 100644 --- a/packages/paperless_api/lib/src/models/document_model.dart +++ b/packages/paperless_api/lib/src/models/document_model.dart @@ -1,10 +1,9 @@ -// ignore_for_file: non_constant_identifier_names - import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/src/converters/local_date_time_json_converter.dart'; import 'package:paperless_api/src/models/custom_field_model.dart'; +import 'package:paperless_api/src/models/note_model.dart'; import 'package:paperless_api/src/models/search_hit.dart'; part 'document_model.g.dart'; @@ -48,10 +47,11 @@ class DocumentModel extends Equatable { final int? owner; final bool? userCanChange; + final Iterable notes; - // Only present if full_perms=true + /// Only present if full_perms=true final Permissions? permissions; - final Iterable? customFields; + final Iterable customFields; const DocumentModel({ required this.id, @@ -71,7 +71,8 @@ class DocumentModel extends Equatable { this.owner, this.userCanChange, this.permissions, - this.customFields, + this.customFields = const [], + this.notes = const [], }); factory DocumentModel.fromJson(Map json) => diff --git a/packages/paperless_api/lib/src/models/note_model.dart b/packages/paperless_api/lib/src/models/note_model.dart new file mode 100644 index 0000000..f23c025 --- /dev/null +++ b/packages/paperless_api/lib/src/models/note_model.dart @@ -0,0 +1,17 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +part 'note_model.freezed.dart'; +part 'note_model.g.dart'; + +@freezed +class NoteModel with _$NoteModel { + const factory NoteModel({ + required int id, + required String note, + required DateTime created, + required int document, + required int? user, + }) = _NoteModel; + + factory NoteModel.fromJson(Map json) => + _$NoteModelFromJson(json); +}