feat: Add notes

This commit is contained in:
Anton Stubenbord
2023-12-12 23:00:12 +01:00
parent a4fb72fdfc
commit aa05f9432a
20 changed files with 254 additions and 59 deletions

View File

@@ -0,0 +1,25 @@
import 'package:paperless_mobile/core/bloc/loading_status.dart';
class BaseState<T> {
final Object? error;
final T? value;
final LoadingStatus status;
BaseState({
required this.error,
required this.value,
required this.status,
});
BaseState<T> copyWith({
Object? error,
T? value,
LoadingStatus? status,
}) {
return BaseState(
error: error ?? this.error,
value: value ?? this.value,
status: status ?? this.status,
);
}
}

View File

@@ -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<DocumentDetailsPage> {
debugPrint(disableAnimations.toString());
final hasMultiUserSupport =
context.watch<LocalUserAccount>().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<DocumentDetailsPage> {
),
),
),
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<DocumentDetailsPage> {
),
],
),
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,

View File

@@ -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<AddNotePage> createState() => _AddNotePageState();
}
class _AddNotePageState extends State<AddNotePage> {
@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),
),
],
),
);
}
}

View File

@@ -25,54 +25,51 @@ class DocumentMetaDataWidget extends StatelessWidget {
Widget build(BuildContext context) {
final currentUser = context.watch<LocalUserAccount>().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),
],
);
}
}

View File

@@ -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,
);
}
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -26,4 +26,5 @@ class R {
static const loggingOut = "loggingOut";
static const restoringSession = "restoringSession";
static const addAccount = 'addAccount';
static const addNote = 'addNote';
}

View File

@@ -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,
);
}
}

View File

@@ -71,6 +71,7 @@ part 'authenticated_route.g.dart';
TypedGoRoute<DocumentDetailsRoute>(
path: "details/:id",
name: R.documentDetails,
routes: [],
),
TypedGoRoute<EditDocumentRoute>(
path: "edit",
@@ -84,6 +85,10 @@ part 'authenticated_route.g.dart';
path: 'preview',
name: R.documentPreview,
),
TypedGoRoute<AddNoteRoute>(
path: 'add-note',
name: R.addNote,
),
],
)
],

View File

@@ -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<NoteModel> notes;
// Only present if full_perms=true
/// Only present if full_perms=true
final Permissions? permissions;
final Iterable<CustomFieldModel>? customFields;
final Iterable<CustomFieldModel> 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<String, dynamic> json) =>

View File

@@ -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<String, dynamic> json) =>
_$NoteModelFromJson(json);
}