feat: extract snippets into widgets, code cleanup document details

This commit is contained in:
Anton Stubenbord
2023-02-11 16:37:51 +01:00
parent 45684010d6
commit a10c6efb00
29 changed files with 460 additions and 412 deletions

View File

@@ -198,6 +198,6 @@ SPEC CHECKSUMS:
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2 url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
WeScan: fed582f6c38014d529afb5aa9ffd1bad38fc72b7 WeScan: fed582f6c38014d529afb5aa9ffd1bad38fc72b7
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 PODFILE CHECKSUM: 7daa35cc908d9fba025075df27cb57a1ba1ebf13
COCOAPODS: 1.11.3 COCOAPODS: 1.11.3

View File

@@ -7,6 +7,8 @@ import 'package:open_filex/open_filex.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/core/service/file_service.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart';
part 'document_details_state.dart'; part 'document_details_state.dart';
@@ -73,6 +75,24 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
emit(state.copyWith(document: document)); emit(state.copyWith(document: document));
} }
Future<void> shareDocument() async {
final documentBytes = await _api.download(state.document);
final dir = await getTemporaryDirectory();
final String path = "${dir.path}/${state.document.originalFileName}";
await File(path).writeAsBytes(documentBytes);
Share.shareXFiles(
[
XFile(
path,
name: state.document.originalFileName,
mimeType: "application/pdf",
lastModified: state.document.modified,
),
],
subject: state.document.title,
);
}
@override @override
Future<void> close() { Future<void> close() {
for (final element in _subscriptions) { for (final element in _subscriptions) {

View File

@@ -4,31 +4,27 @@ import 'package:badges/badges.dart' as b;
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:intl/intl.dart';
import 'package:open_filex/open_filex.dart'; import 'package:open_filex/open_filex.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart'; import 'package:paperless_mobile/core/translation/error_code_localization_mapper.dart';
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
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_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_overview_widget.dart';
import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubit.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/document_edit/view/document_edit_page.dart';
import 'package:paperless_mobile/features/documents/view/pages/document_view.dart'; import 'package:paperless_mobile/features/documents/view/pages/document_view.dart';
import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.dart'; import 'package:paperless_mobile/features/documents/view/widgets/delete_document_confirmation_dialog.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/labels/storage_path/view/widgets/storage_path_widget.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/features/similar_documents/cubit/similar_documents_cubit.dart'; import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
import 'package:paperless_mobile/features/similar_documents/view/similar_documents_view.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/format_helpers.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:paperless_mobile/features/similar_documents/view/similar_documents_view.dart';
//TODO: Refactor this into several widgets //TODO: Refactor this into several widgets
class DocumentDetailsPage extends StatefulWidget { class DocumentDetailsPage extends StatefulWidget {
@@ -49,7 +45,7 @@ class DocumentDetailsPage extends StatefulWidget {
class _DocumentDetailsPageState extends State<DocumentDetailsPage> { class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
late Future<DocumentMetaData> _metaData; late Future<DocumentMetaData> _metaData;
static const double _itemPadding = 24;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -70,7 +66,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
length: 4, length: 4,
child: Scaffold( child: Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
floatingActionButton: widget.allowEdit ? _buildAppBar() : null, floatingActionButton: widget.allowEdit ? _buildEditButton() : null,
bottomNavigationBar: _buildBottomAppBar(), bottomNavigationBar: _buildBottomAppBar(),
body: NestedScrollView( body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [ headerSliverBuilder: (context, innerBoxIsScrolled) => [
@@ -96,27 +92,30 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
child: Text( child: Text(
S.of(context).documentDetailsPageTabOverviewLabel, S.of(context).documentDetailsPageTabOverviewLabel,
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onPrimaryContainer), .onPrimaryContainer,
),
), ),
), ),
Tab( Tab(
child: Text( child: Text(
S.of(context).documentDetailsPageTabContentLabel, S.of(context).documentDetailsPageTabContentLabel,
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onPrimaryContainer), .onPrimaryContainer,
),
), ),
), ),
Tab( Tab(
child: Text( child: Text(
S.of(context).documentDetailsPageTabMetaDataLabel, S.of(context).documentDetailsPageTabMetaDataLabel,
style: TextStyle( style: TextStyle(
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onPrimaryContainer), .onPrimaryContainer,
),
), ),
), ),
Tab( Tab(
@@ -146,20 +145,26 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
), ),
child: TabBarView( child: TabBarView(
children: [ children: [
_buildDocumentOverview( DocumentOverviewWidget(
state.document, document: state.document,
itemSpacing: _itemPadding,
queryString: widget.titleAndContentQueryString,
), ),
_buildDocumentContentView( DocumentContentWidget(
state.document, isFullContentLoaded: state.isFullContentLoaded,
state, document: state.document,
fullContent: state.fullContent,
queryString: widget.titleAndContentQueryString,
), ),
_buildDocumentMetaDataView( DocumentMetaDataWidget(
state.document, document: state.document,
itemSpacing: _itemPadding,
metaData: _metaData,
), ),
const SimilarDocumentsView(), const SimilarDocumentsView(),
], ],
), ),
).paddedSymmetrically(horizontal: 8); );
}, },
), ),
), ),
@@ -168,7 +173,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
); );
} }
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState> _buildAppBar() { Widget _buildEditButton() {
return BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>( return BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
builder: (context, state) { builder: (context, state) {
final _filteredSuggestions = final _filteredSuggestions =
@@ -176,7 +181,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
return BlocBuilder<ConnectivityCubit, ConnectivityState>( return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivityState) { builder: (context, connectivityState) {
if (!connectivityState.isConnected) { if (!connectivityState.isConnected) {
return Container(); return const SizedBox.shrink();
} }
return b.Badge( return b.Badge(
position: b.BadgePosition.topEnd(top: -12, end: -6), position: b.BadgePosition.topEnd(top: -12, end: -6),
@@ -244,8 +249,10 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
IconButton( IconButton(
tooltip: S.of(context).documentDetailsPageShareTooltip, tooltip: S.of(context).documentDetailsPageShareTooltip,
icon: const Icon(Icons.share), icon: const Icon(Icons.share),
onPressed: onPressed: isConnected
isConnected ? () => _onShare(state.document) : null, ? () =>
context.read<DocumentDetailsCubit>().shareDocument()
: null,
), ),
], ],
); );
@@ -317,203 +324,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
} }
} }
Widget _buildDocumentMetaDataView(DocumentModel document) {
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, state) {
if (!state.isConnected) {
return const Center(
child: OfflineWidget(),
);
}
return FutureBuilder<DocumentMetaData>(
future: _metaData,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final meta = snapshot.data!;
return ListView(
children: [
_DetailsItem.text(DateFormat().format(document.modified),
label: S.of(context).documentModifiedPropertyLabel,
context: context)
.paddedOnly(bottom: 16),
_DetailsItem.text(DateFormat().format(document.added),
label: S.of(context).documentAddedPropertyLabel,
context: context)
.paddedSymmetrically(vertical: 16),
_DetailsItem(
label: S
.of(context)
.documentArchiveSerialNumberPropertyLongLabel,
content: document.archiveSerialNumber != null
? Text(document.archiveSerialNumber.toString())
: TextButton.icon(
icon: const Icon(Icons.archive_outlined),
label: Text(S
.of(context)
.documentDetailsPageAssignAsnButtonLabel),
onPressed: widget.allowEdit
? () => _assignAsn(document)
: null,
),
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.mediaFilename,
context: context,
label:
S.of(context).documentMetaDataMediaFilenamePropertyLabel,
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.originalChecksum,
context: context,
label: S.of(context).documentMetaDataChecksumLabel,
).paddedSymmetrically(vertical: 16),
_DetailsItem.text(formatBytes(meta.originalSize, 2),
label:
S.of(context).documentMetaDataOriginalFileSizeLabel,
context: context)
.paddedSymmetrically(vertical: 16),
_DetailsItem.text(
meta.originalMimeType,
label: S.of(context).documentMetaDataOriginalMimeTypeLabel,
context: context,
).paddedSymmetrically(vertical: 16),
],
);
},
);
},
);
}
Future<void> _assignAsn(DocumentModel document) async {
try {
await context.read<DocumentDetailsCubit>().assignAsn(document);
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
Widget _buildDocumentContentView(
DocumentModel document,
DocumentDetailsState state,
) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
HighlightedText(
text: (state.isFullContentLoaded
? state.fullContent
: document.content) ??
"",
highlights: widget.titleAndContentQueryString != null
? widget.titleAndContentQueryString!.split(" ")
: [],
style: Theme.of(context).textTheme.bodyMedium,
caseSensitive: false,
),
if (!state.isFullContentLoaded && (document.content ?? '').isNotEmpty)
Align(
alignment: Alignment.bottomCenter,
child: TextButton(
child:
Text(S.of(context).documentDetailsPageLoadFullContentLabel),
onPressed: () {
context.read<DocumentDetailsCubit>().loadFullContent();
},
),
),
],
).padded(8).paddedOnly(top: 14),
);
}
Widget _buildDocumentOverview(DocumentModel document) {
return ListView(
children: [
_DetailsItem(
label: S.of(context).documentTitlePropertyLabel,
content: HighlightedText(
text: document.title,
highlights: widget.titleAndContentQueryString?.split(" ") ?? [],
style: Theme.of(context).textTheme.bodyLarge,
),
).paddedOnly(bottom: 16),
_DetailsItem.text(
DateFormat.yMMMMd().format(document.created),
context: context,
label: S.of(context).documentCreatedPropertyLabel,
).paddedSymmetrically(vertical: 16),
Visibility(
visible: document.documentType != null,
child: _DetailsItem(
label: S.of(context).documentDocumentTypePropertyLabel,
content: LabelText<DocumentType>(
style: Theme.of(context).textTheme.bodyLarge,
id: document.documentType,
),
).paddedSymmetrically(vertical: 16),
),
Visibility(
visible: document.correspondent != null,
child: _DetailsItem(
label: S.of(context).documentCorrespondentPropertyLabel,
content: LabelText<Correspondent>(
style: Theme.of(context).textTheme.bodyLarge,
id: document.correspondent,
),
).paddedSymmetrically(vertical: 16),
),
Visibility(
visible: document.storagePath != null,
child: _DetailsItem(
label: S.of(context).documentStoragePathPropertyLabel,
content: StoragePathWidget(
pathId: document.storagePath,
),
).paddedSymmetrically(vertical: 16),
),
Visibility(
visible: document.tags.isNotEmpty,
child: _DetailsItem(
label: S.of(context).documentTagsPropertyLabel,
content: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: TagsWidget(
isClickable: widget.isLabelClickable,
tagIds: document.tags,
),
),
).paddedSymmetrically(vertical: 16),
),
],
);
}
///
/// Downloads file to temporary directory, from which it can then be shared.
///
Future<void> _onShare(DocumentModel document) async {
Uint8List documentBytes =
await context.read<PaperlessDocumentsApi>().download(document);
final dir = await getTemporaryDirectory();
final String path = "${dir.path}/${document.originalFileName}";
await File(path).writeAsBytes(documentBytes);
Share.shareXFiles(
[
XFile(
path,
name: document.originalFileName,
mimeType: "application/pdf",
lastModified: document.modified,
)
],
subject: document.title,
);
}
void _onDelete(DocumentModel document) async { void _onDelete(DocumentModel document) async {
final delete = await showDialog( final delete = await showDialog(
context: context, context: context,
@@ -546,42 +356,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
} }
} }
class _DetailsItem extends StatelessWidget {
final String label;
final Widget content;
const _DetailsItem({
Key? key,
required this.label,
required this.content,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: Theme.of(context).textTheme.bodySmall,
),
content,
],
),
);
}
_DetailsItem.text(
String text, {
required this.label,
required BuildContext context,
}) : content = Text(
text,
style: Theme.of(context).textTheme.bodyLarge,
);
}
class ColoredTabBar extends Container implements PreferredSizeWidget { class ColoredTabBar extends Container implements PreferredSizeWidget {
ColoredTabBar({ ColoredTabBar({
super.key, super.key,

View File

@@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
class DetailsItem extends StatelessWidget {
final String label;
final Widget content;
const DetailsItem({
Key? key,
required this.label,
required this.content,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: Theme.of(context).textTheme.bodySmall,
),
content,
],
);
}
DetailsItem.text(
String text, {
required this.label,
required BuildContext context,
}) : content = Text(
text,
style: Theme.of(context).textTheme.bodyLarge,
);
}

View File

@@ -0,0 +1,52 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
import 'package:paperless_mobile/generated/l10n.dart';
class DocumentContentWidget extends StatelessWidget {
final bool isFullContentLoaded;
final String? fullContent;
final String? queryString;
final DocumentModel document;
const DocumentContentWidget({
super.key,
required this.isFullContentLoaded,
this.fullContent,
required this.document,
this.queryString,
});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
HighlightedText(
text: (isFullContentLoaded ? fullContent : document.content) ?? "",
highlights: queryString != null ? queryString!.split(" ") : [],
style: Theme.of(context).textTheme.bodyMedium,
caseSensitive: false,
),
if (!isFullContentLoaded && (document.content ?? '').isNotEmpty)
Align(
alignment: Alignment.bottomCenter,
child: TextButton(
child:
Text(S.of(context).documentDetailsPageLoadFullContentLabel),
onPressed: () {
context.read<DocumentDetailsCubit>().loadFullContent();
},
),
),
],
),
);
}
}

View File

@@ -6,7 +6,6 @@ import 'package:paperless_mobile/core/service/file_service.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/constants.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class DocumentDownloadButton extends StatefulWidget { class DocumentDownloadButton extends StatefulWidget {

View File

@@ -0,0 +1,105 @@
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/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/widgets/offline_widget.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/format_helpers.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
class DocumentMetaDataWidget extends StatelessWidget {
final Future<DocumentMetaData> metaData;
final DocumentModel document;
final double itemSpacing;
const DocumentMetaDataWidget({
super.key,
required this.metaData,
required this.document,
required this.itemSpacing,
});
@override
Widget build(BuildContext context) {
return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, state) {
if (!state.isConnected) {
return const Center(
child: OfflineWidget(),
);
}
return FutureBuilder<DocumentMetaData>(
future: metaData,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final meta = snapshot.data!;
return ListView(
padding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
children: [
DetailsItem(
label: S
.of(context)
.documentArchiveSerialNumberPropertyLongLabel,
content: document.archiveSerialNumber != null
? Text(document.archiveSerialNumber.toString())
: TextButton.icon(
icon: const Icon(Icons.archive_outlined),
label: Text(S
.of(context)
.documentDetailsPageAssignAsnButtonLabel),
onPressed: () => _assignAsn(context),
),
).paddedOnly(bottom: itemSpacing),
DetailsItem.text(DateFormat().format(document.modified),
label: S.of(context).documentModifiedPropertyLabel,
context: context)
.paddedOnly(bottom: itemSpacing),
DetailsItem.text(DateFormat().format(document.added),
label: S.of(context).documentAddedPropertyLabel,
context: context)
.paddedOnly(bottom: itemSpacing),
DetailsItem.text(
meta.mediaFilename,
context: context,
label:
S.of(context).documentMetaDataMediaFilenamePropertyLabel,
).paddedOnly(bottom: itemSpacing),
DetailsItem.text(
meta.originalChecksum,
context: context,
label: S.of(context).documentMetaDataChecksumLabel,
).paddedOnly(bottom: itemSpacing),
DetailsItem.text(formatBytes(meta.originalSize, 2),
label:
S.of(context).documentMetaDataOriginalFileSizeLabel,
context: context)
.paddedOnly(bottom: itemSpacing),
DetailsItem.text(
meta.originalMimeType,
label: S.of(context).documentMetaDataOriginalMimeTypeLabel,
context: context,
).paddedOnly(bottom: itemSpacing),
],
);
},
);
},
);
}
Future<void> _assignAsn(BuildContext context) async {
try {
await context.read<DocumentDetailsCubit>().assignAsn(document);
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}

View File

@@ -0,0 +1,89 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/highlighted_text.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_details/view/widgets/details_item.dart';
import 'package:paperless_mobile/features/labels/storage_path/view/widgets/storage_path_widget.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';
class DocumentOverviewWidget extends StatelessWidget {
final DocumentModel document;
final String? queryString;
final double itemSpacing;
const DocumentOverviewWidget({
super.key,
required this.document,
this.queryString,
required this.itemSpacing,
});
@override
Widget build(BuildContext context) {
return ListView(
padding: const EdgeInsets.symmetric(
vertical: 16,
horizontal: 16,
),
children: [
DetailsItem(
label: S.of(context).documentTitlePropertyLabel,
content: HighlightedText(
text: document.title,
highlights: queryString?.split(" ") ?? [],
style: Theme.of(context).textTheme.bodyLarge,
),
).paddedOnly(bottom: itemSpacing),
DetailsItem.text(
DateFormat.yMMMMd().format(document.created),
context: context,
label: S.of(context).documentCreatedPropertyLabel,
).paddedOnly(bottom: itemSpacing),
Visibility(
visible: document.documentType != null,
child: DetailsItem(
label: S.of(context).documentDocumentTypePropertyLabel,
content: LabelText<DocumentType>(
style: Theme.of(context).textTheme.bodyLarge,
id: document.documentType,
),
).paddedOnly(bottom: itemSpacing),
),
Visibility(
visible: document.correspondent != null,
child: DetailsItem(
label: S.of(context).documentCorrespondentPropertyLabel,
content: LabelText<Correspondent>(
style: Theme.of(context).textTheme.bodyLarge,
id: document.correspondent,
),
).paddedOnly(bottom: itemSpacing),
),
Visibility(
visible: document.storagePath != null,
child: DetailsItem(
label: S.of(context).documentStoragePathPropertyLabel,
content: StoragePathWidget(
pathId: document.storagePath,
),
).paddedOnly(bottom: itemSpacing),
),
Visibility(
visible: document.tags.isNotEmpty,
child: DetailsItem(
label: S.of(context).documentTagsPropertyLabel,
content: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: TagsWidget(
isClickable: false,
tagIds: document.tags,
),
),
).paddedOnly(bottom: itemSpacing),
),
],
);
}
}

View File

@@ -2,17 +2,17 @@ import 'package:collection/collection.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
part 'document_search_state.dart'; part 'document_search_state.dart';
part 'document_search_cubit.g.dart'; part 'document_search_cubit.g.dart';
class DocumentSearchCubit extends HydratedCubit<DocumentSearchState> class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
with PagedDocumentsMixin { with DocumentPagingBlocMixin {
@override @override
final PaperlessDocumentsApi api; final PaperlessDocumentsApi api;
@override @override

View File

@@ -6,7 +6,7 @@ enum SearchView {
} }
@JsonSerializable(ignoreUnannotated: true) @JsonSerializable(ignoreUnannotated: true)
class DocumentSearchState extends PagedDocumentsState { class DocumentSearchState extends DocumentPagingState {
@JsonKey() @JsonKey()
final List<String> searchHistory; final List<String> searchHistory;
final SearchView view; final SearchView view;

View File

@@ -5,16 +5,16 @@ import 'package:flutter/foundation.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
part 'documents_state.dart'; part 'documents_state.dart';
part 'documents_cubit.g.dart'; part 'documents_cubit.g.dart';
class DocumentsCubit extends HydratedCubit<DocumentsState> class DocumentsCubit extends HydratedCubit<DocumentsState>
with PagedDocumentsMixin { with DocumentPagingBlocMixin {
@override @override
final PaperlessDocumentsApi api; final PaperlessDocumentsApi api;

View File

@@ -1,7 +1,7 @@
part of 'documents_cubit.dart'; part of 'documents_cubit.dart';
@JsonSerializable() @JsonSerializable()
class DocumentsState extends PagedDocumentsState { class DocumentsState extends DocumentPagingState {
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
final List<DocumentModel> selection; final List<DocumentModel> selection;

View File

@@ -2,11 +2,11 @@ import 'package:flutter/material.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/widgets/empty_state.dart'; import 'package:paperless_mobile/core/widgets/empty_state.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
class DocumentsEmptyState extends StatelessWidget { class DocumentsEmptyState extends StatelessWidget {
final PagedDocumentsState state; final DocumentPagingState state;
final VoidCallback? onReset; final VoidCallback? onReset;
const DocumentsEmptyState({ const DocumentsEmptyState({
Key? key, Key? key,

View File

@@ -25,6 +25,7 @@ import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; import 'package:paperless_mobile/features/labels/cubit/label_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/notifications/services/local_notification_service.dart'; import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart'; import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart'; import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart'; import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
@@ -271,8 +272,18 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
], ],
child: const LabelsPage(), child: const LabelsPage(),
), ),
BlocProvider.value( MultiBlocProvider(
value: _inboxCubit, providers: [
// We need to manually downcast the inboxcubit to the
// mixed-in DocumentPagingBlocMixin to use the
// DocumentPagingViewMixin in the inbox.
BlocProvider<DocumentPagingBlocMixin>.value(
value: _inboxCubit,
),
BlocProvider<InboxCubit>.value(
value: _inboxCubit,
),
],
child: const InboxPage(), child: const InboxPage(),
), ),
// const SettingsPage(), // const SettingsPage(),

View File

@@ -5,13 +5,14 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
part 'inbox_cubit.g.dart'; part 'inbox_cubit.g.dart';
part 'inbox_state.dart'; part 'inbox_state.dart';
class InboxCubit extends HydratedCubit<InboxState> with PagedDocumentsMixin { class InboxCubit extends HydratedCubit<InboxState>
with DocumentPagingBlocMixin {
final LabelRepository<Tag> _tagsRepository; final LabelRepository<Tag> _tagsRepository;
final LabelRepository<Correspondent> _correspondentRepository; final LabelRepository<Correspondent> _correspondentRepository;
final LabelRepository<DocumentType> _documentTypeRepository; final LabelRepository<DocumentType> _documentTypeRepository;

View File

@@ -1,7 +1,7 @@
part of 'inbox_cubit.dart'; part of 'inbox_cubit.dart';
@JsonSerializable(ignoreUnannotated: true) @JsonSerializable(ignoreUnannotated: true)
class InboxState extends PagedDocumentsState { class InboxState extends DocumentPagingState {
final Iterable<int> inboxTags; final Iterable<int> inboxTags;
final Map<int, Tag> availableTags; final Map<int, Tag> availableTags;

View File

@@ -12,6 +12,7 @@ import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart'; import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_empty_widget.dart'; import 'package:paperless_mobile/features/inbox/view/widgets/inbox_empty_widget.dart';
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_item.dart'; import 'package:paperless_mobile/features/inbox/view/widgets/inbox_item.dart';
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart'; import 'package:paperless_mobile/features/search_app_bar/view/search_app_bar.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';
@@ -23,35 +24,15 @@ class InboxPage extends StatefulWidget {
State<InboxPage> createState() => _InboxPageState(); State<InboxPage> createState() => _InboxPageState();
} }
class _InboxPageState extends State<InboxPage> { class _InboxPageState extends State<InboxPage> with DocumentPagingViewMixin {
final ScrollController _scrollController = ScrollController(); @override
final pagingScrollController = ScrollController();
final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>(); final _emptyStateRefreshIndicatorKey = GlobalKey<RefreshIndicatorState>();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
context.read<InboxCubit>().loadInbox(); context.read<InboxCubit>().loadInbox();
_scrollController.addListener(_listenForLoadNewData);
}
@override
void dispose() {
_scrollController.removeListener(_listenForLoadNewData);
super.dispose();
}
void _listenForLoadNewData() {
final currState = context.read<InboxCubit>().state;
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent * 0.75 &&
!currState.isLoading &&
!currState.isLastPageLoaded) {
try {
context.read<InboxCubit>().loadMore();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
} }
@override @override
@@ -143,7 +124,7 @@ class _InboxPageState extends State<InboxPage> {
physics: state.documents.isEmpty physics: state.documents.isEmpty
? const NeverScrollableScrollPhysics() ? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(), : const AlwaysScrollableScrollPhysics(),
controller: _scrollController, controller: pagingScrollController,
slivers: [ slivers: [
SearchAppBar( SearchAppBar(
hintText: S.of(context).documentSearchSearchDocuments, hintText: S.of(context).documentSearchSearchDocuments,

View File

@@ -2,15 +2,15 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'linked_documents_state.dart'; part 'linked_documents_state.dart';
part 'linked_documents_cubit.g.dart'; part 'linked_documents_cubit.g.dart';
class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState> class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
with PagedDocumentsMixin { with DocumentPagingBlocMixin {
@override @override
final PaperlessDocumentsApi api; final PaperlessDocumentsApi api;

View File

@@ -1,7 +1,7 @@
part of 'linked_documents_cubit.dart'; part of 'linked_documents_cubit.dart';
@JsonSerializable(ignoreUnannotated: true) @JsonSerializable(ignoreUnannotated: true)
class LinkedDocumentsState extends PagedDocumentsState { class LinkedDocumentsState extends DocumentPagingState {
@JsonKey() @JsonKey()
final ViewType viewType; final ViewType viewType;
const LinkedDocumentsState({ const LinkedDocumentsState({

View File

@@ -1,12 +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_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart'; import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart'; import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart'; import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart';
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
import 'package:paperless_mobile/generated/l10n.dart'; import 'package:paperless_mobile/generated/l10n.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/routes/document_details_route.dart'; import 'package:paperless_mobile/routes/document_details_route.dart';
class LinkedDocumentsPage extends StatefulWidget { class LinkedDocumentsPage extends StatefulWidget {
@@ -16,28 +15,10 @@ class LinkedDocumentsPage extends StatefulWidget {
State<LinkedDocumentsPage> createState() => _LinkedDocumentsPageState(); State<LinkedDocumentsPage> createState() => _LinkedDocumentsPageState();
} }
class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> { class _LinkedDocumentsPageState extends State<LinkedDocumentsPage>
final _scrollController = ScrollController(); with DocumentPagingViewMixin {
@override @override
void initState() { final pagingScrollController = ScrollController();
super.initState();
_scrollController.addListener(_listenForLoadNewData);
}
void _listenForLoadNewData() async {
final currState = context.read<LinkedDocumentsCubit>().state;
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent * 0.75 &&
!currState.isLoading &&
!currState.isLastPageLoaded) {
try {
await context.read<LinkedDocumentsCubit>().loadMore();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -60,7 +41,7 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage> {
return BlocBuilder<ConnectivityCubit, ConnectivityState>( return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivity) { builder: (context, connectivity) {
return CustomScrollView( return CustomScrollView(
controller: _scrollController, controller: pagingScrollController,
slivers: [ slivers: [
SliverAdaptiveDocumentsView( SliverAdaptiveDocumentsView(
viewType: state.viewType, viewType: state.viewType,

View File

@@ -1,17 +1,15 @@
import 'dart:developer';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'model/paged_documents_state.dart'; import 'paged_documents_state.dart';
/// ///
/// Mixin which can be used on cubits that handle documents. /// Mixin which can be used on cubits that handle documents.
/// This implements all paging and filtering logic. /// This implements all paging and filtering logic.
/// ///
mixin PagedDocumentsMixin<State extends PagedDocumentsState> mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
on BlocBase<State> { on BlocBase<State> {
PaperlessDocumentsApi get api; PaperlessDocumentsApi get api;
DocumentChangedNotifier get notifier; DocumentChangedNotifier get notifier;

View File

@@ -5,13 +5,13 @@ import 'package:paperless_api/paperless_api.dart';
/// Base state for all blocs/cubits using a paged view of documents. /// Base state for all blocs/cubits using a paged view of documents.
/// [T] is the return type of the API call. /// [T] is the return type of the API call.
/// ///
abstract class PagedDocumentsState extends Equatable { abstract class DocumentPagingState extends Equatable {
final bool hasLoaded; final bool hasLoaded;
final bool isLoading; final bool isLoading;
final List<PagedSearchResult<DocumentModel>> value; final List<PagedSearchResult<DocumentModel>> value;
final DocumentFilter filter; final DocumentFilter filter;
const PagedDocumentsState({ const DocumentPagingState({
this.value = const [], this.value = const [],
this.hasLoaded = false, this.hasLoaded = false,
this.isLoading = false, this.isLoading = false,

View File

@@ -0,0 +1,43 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart';
mixin DocumentPagingViewMixin<T extends StatefulWidget> on State<T> {
ScrollController get pagingScrollController;
@override
void initState() {
super.initState();
pagingScrollController.addListener(shouldLoadMoreDocumentsListener);
}
@override
void dispose() {
pagingScrollController.removeListener(shouldLoadMoreDocumentsListener);
super.dispose();
}
DocumentPagingBlocMixin get _bloc => context.read<DocumentPagingBlocMixin>();
void shouldLoadMoreDocumentsListener() async {
if (shouldLoadMoreDocuments) {
try {
await _bloc.loadMore();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}
bool get shouldLoadMoreDocuments {
final currState = _bloc.state;
return pagingScrollController.position.maxScrollExtent != 0 &&
!currState.isLoading &&
!currState.isLastPageLoaded &&
pagingScrollController.offset >=
pagingScrollController.position.maxScrollExtent * 0.75;
}
}

View File

@@ -2,15 +2,15 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'saved_view_details_cubit.g.dart'; part 'saved_view_details_cubit.g.dart';
part 'saved_view_details_state.dart'; part 'saved_view_details_state.dart';
class SavedViewDetailsCubit extends HydratedCubit<SavedViewDetailsState> class SavedViewDetailsCubit extends HydratedCubit<SavedViewDetailsState>
with PagedDocumentsMixin { with DocumentPagingBlocMixin {
@override @override
final PaperlessDocumentsApi api; final PaperlessDocumentsApi api;

View File

@@ -1,7 +1,7 @@
part of 'saved_view_details_cubit.dart'; part of 'saved_view_details_cubit.dart';
@JsonSerializable(ignoreUnannotated: true) @JsonSerializable(ignoreUnannotated: true)
class SavedViewDetailsState extends PagedDocumentsState { class SavedViewDetailsState extends DocumentPagingState {
@JsonKey() @JsonKey()
final ViewType viewType; final ViewType viewType;

View File

@@ -6,8 +6,8 @@ import 'package:paperless_mobile/features/documents/view/widgets/adaptive_docume
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart'; import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart'; import 'package:paperless_mobile/features/documents/view/widgets/selection/confirm_delete_saved_view_dialog.dart';
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart'; import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart'; import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/routes/document_details_route.dart'; import 'package:paperless_mobile/routes/document_details_route.dart';
class SavedViewDetailsPage extends StatefulWidget { class SavedViewDetailsPage extends StatefulWidget {
@@ -21,33 +21,14 @@ class SavedViewDetailsPage extends StatefulWidget {
State<SavedViewDetailsPage> createState() => _SavedViewDetailsPageState(); State<SavedViewDetailsPage> createState() => _SavedViewDetailsPageState();
} }
class _SavedViewDetailsPageState extends State<SavedViewDetailsPage> { class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
final _scrollController = ScrollController(); with DocumentPagingViewMixin {
@override @override
void initState() { final pagingScrollController = ScrollController();
super.initState();
_scrollController.addListener(_listenForLoadNewData);
}
void _listenForLoadNewData() async {
final currState = context.read<SavedViewDetailsCubit>().state;
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent * 0.7 &&
!currState.isLoading &&
!currState.isLastPageLoaded) {
try {
await context.read<SavedViewDetailsCubit>().loadMore();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final cubit = context.read<SavedViewDetailsCubit>(); final cubit = context.read<SavedViewDetailsCubit>();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(cubit.savedView.name), title: Text(cubit.savedView.name),
@@ -57,8 +38,9 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage> {
onPressed: () async { onPressed: () async {
final shouldDelete = await showDialog<bool>( final shouldDelete = await showDialog<bool>(
context: context, context: context,
builder: (context) => builder: (context) => ConfirmDeleteSavedViewDialog(
ConfirmDeleteSavedViewDialog(view: cubit.savedView), view: cubit.savedView,
),
) ?? ) ??
false; false;
if (shouldDelete) { if (shouldDelete) {
@@ -85,7 +67,7 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage> {
return BlocBuilder<ConnectivityCubit, ConnectivityState>( return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivity) { builder: (context, connectivity) {
return CustomScrollView( return CustomScrollView(
controller: _scrollController, controller: pagingScrollController,
slivers: [ slivers: [
SliverAdaptiveDocumentsView( SliverAdaptiveDocumentsView(
documents: state.documents, documents: state.documents,
@@ -93,7 +75,16 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage> {
isLabelClickable: false, isLabelClickable: false,
isLoading: state.isLoading, isLoading: state.isLoading,
hasLoaded: state.hasLoaded, hasLoaded: state.hasLoaded,
onTap: _onOpenDocumentDetails, onTap: (document) {
Navigator.pushNamed(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
isLabelClickable: false,
),
);
},
viewType: state.viewType, viewType: state.viewType,
), ),
if (state.hasLoaded && state.isLoading) if (state.hasLoaded && state.isLoading)
@@ -110,15 +101,4 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage> {
), ),
); );
} }
void _onOpenDocumentDetails(DocumentModel document) {
Navigator.pushNamed(
context,
DocumentDetailsRoute.routeName,
arguments: DocumentDetailsRouteArguments(
document: document,
isLabelClickable: false,
),
);
}
} }

View File

@@ -1,13 +1,13 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/features/paged_document_view/paged_documents_mixin.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/paged_document_view/model/paged_documents_state.dart'; import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
part 'similar_documents_state.dart'; part 'similar_documents_state.dart';
class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState> class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
with PagedDocumentsMixin { with DocumentPagingBlocMixin {
final int documentId; final int documentId;
@override @override

View File

@@ -1,6 +1,6 @@
part of 'similar_documents_cubit.dart'; part of 'similar_documents_cubit.dart';
class SimilarDocumentsState extends PagedDocumentsState { class SimilarDocumentsState extends DocumentPagingState {
const SimilarDocumentsState({ const SimilarDocumentsState({
super.filter, super.filter,
super.hasLoaded, super.hasLoaded,

View File

@@ -2,10 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/widgets/hint_card.dart';
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart'; import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart'; import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart'; import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart'; import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:paperless_mobile/routes/document_details_route.dart'; import 'package:paperless_mobile/routes/document_details_route.dart';
@@ -17,13 +16,14 @@ class SimilarDocumentsView extends StatefulWidget {
State<SimilarDocumentsView> createState() => _SimilarDocumentsViewState(); State<SimilarDocumentsView> createState() => _SimilarDocumentsViewState();
} }
class _SimilarDocumentsViewState extends State<SimilarDocumentsView> { class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
final _scrollController = ScrollController(); with DocumentPagingViewMixin {
@override
final pagingScrollController = ScrollController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_scrollController.addListener(_listenForLoadNewData);
try { try {
context.read<SimilarDocumentsCubit>().initialize(); context.read<SimilarDocumentsCubit>().initialize();
} on PaperlessServerException catch (error, stackTrace) { } on PaperlessServerException catch (error, stackTrace) {
@@ -31,26 +31,6 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView> {
} }
} }
@override
void dispose() {
_scrollController.removeListener(_listenForLoadNewData);
super.dispose();
}
void _listenForLoadNewData() async {
final currState = context.read<SimilarDocumentsCubit>().state;
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent * 0.75 &&
!currState.isLoading &&
!currState.isLastPageLoaded) {
try {
await context.read<SimilarDocumentsCubit>().loadMore();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<SimilarDocumentsCubit, SimilarDocumentsState>( return BlocBuilder<SimilarDocumentsCubit, SimilarDocumentsState>(
@@ -70,7 +50,7 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView> {
return BlocBuilder<ConnectivityCubit, ConnectivityState>( return BlocBuilder<ConnectivityCubit, ConnectivityState>(
builder: (context, connectivity) { builder: (context, connectivity) {
return CustomScrollView( return CustomScrollView(
controller: _scrollController, controller: pagingScrollController,
slivers: [ slivers: [
SliverAdaptiveDocumentsView( SliverAdaptiveDocumentsView(
documents: state.documents, documents: state.documents,