diff --git a/android/app/build.gradle b/android/app/build.gradle index ee2fcc1..a0e469f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -34,13 +34,6 @@ if (keystorePropertiesFile.exists()) { android { compileSdkVersion 33 - packagingOptions { - pickFirst '**/x86/libpdfium.so' - pickFirst '**/x86_64/libpdfium.so' - pickFirst '**/armeabi-v7a/libpdfium.so' - pickFirst '**/arm64-v8a/libpdfium.so' - } - compileOptions { // Required for flutter_local_notifications coreLibraryDesugaringEnabled true diff --git a/assets/example/sample.pdf b/assets/example/sample.pdf new file mode 100644 index 0000000..c0e31a0 --- /dev/null +++ b/assets/example/sample.pdf @@ -0,0 +1,198 @@ +%PDF-1.3 +%âãÏÓ + +1 0 obj +<< +/Type /Catalog +/Outlines 2 0 R +/Pages 3 0 R +>> +endobj + +2 0 obj +<< +/Type /Outlines +/Count 0 +>> +endobj + +3 0 obj +<< +/Type /Pages +/Count 2 +/Kids [ 4 0 R 6 0 R ] +>> +endobj + +4 0 obj +<< +/Type /Page +/Parent 3 0 R +/Resources << +/Font << +/F1 9 0 R +>> +/ProcSet 8 0 R +>> +/MediaBox [0 0 612.0000 792.0000] +/Contents 5 0 R +>> +endobj + +5 0 obj +<< /Length 1074 >> +stream +2 J +BT +0 0 0 rg +/F1 0027 Tf +57.3750 722.2800 Td +( A Simple PDF File ) Tj +ET +BT +/F1 0010 Tf +69.2500 688.6080 Td +( This is a small demonstration .pdf file - ) Tj +ET +BT +/F1 0010 Tf +69.2500 664.7040 Td +( just for use in the Virtual Mechanics tutorials. More text. And more ) Tj +ET +BT +/F1 0010 Tf +69.2500 652.7520 Td +( text. And more text. And more text. And more text. ) Tj +ET +BT +/F1 0010 Tf +69.2500 628.8480 Td +( And more text. And more text. And more text. And more text. And more ) Tj +ET +BT +/F1 0010 Tf +69.2500 616.8960 Td +( text. And more text. Boring, zzzzz. And more text. And more text. And ) Tj +ET +BT +/F1 0010 Tf +69.2500 604.9440 Td +( more text. And more text. And more text. And more text. And more text. ) Tj +ET +BT +/F1 0010 Tf +69.2500 592.9920 Td +( And more text. And more text. ) Tj +ET +BT +/F1 0010 Tf +69.2500 569.0880 Td +( And more text. And more text. And more text. And more text. And more ) Tj +ET +BT +/F1 0010 Tf +69.2500 557.1360 Td +( text. And more text. And more text. Even more. Continued on page 2 ...) Tj +ET +endstream +endobj + +6 0 obj +<< +/Type /Page +/Parent 3 0 R +/Resources << +/Font << +/F1 9 0 R +>> +/ProcSet 8 0 R +>> +/MediaBox [0 0 612.0000 792.0000] +/Contents 7 0 R +>> +endobj + +7 0 obj +<< /Length 676 >> +stream +2 J +BT +0 0 0 rg +/F1 0027 Tf +57.3750 722.2800 Td +( Simple PDF File 2 ) Tj +ET +BT +/F1 0010 Tf +69.2500 688.6080 Td +( ...continued from page 1. Yet more text. And more text. And more text. ) Tj +ET +BT +/F1 0010 Tf +69.2500 676.6560 Td +( And more text. And more text. And more text. And more text. And more ) Tj +ET +BT +/F1 0010 Tf +69.2500 664.7040 Td +( text. Oh, how boring typing this stuff. But not as boring as watching ) Tj +ET +BT +/F1 0010 Tf +69.2500 652.7520 Td +( paint dry. And more text. And more text. And more text. And more text. ) Tj +ET +BT +/F1 0010 Tf +69.2500 640.8000 Td +( Boring. More, a little more text. The end, and just as well. ) Tj +ET +endstream +endobj + +8 0 obj +[/PDF /Text] +endobj + +9 0 obj +<< +/Type /Font +/Subtype /Type1 +/Name /F1 +/BaseFont /Helvetica +/Encoding /WinAnsiEncoding +>> +endobj + +10 0 obj +<< +/Creator (Rave \(http://www.nevrona.com/rave\)) +/Producer (Nevrona Designs) +/CreationDate (D:20060301072826) +>> +endobj + +xref +0 11 +0000000000 65535 f +0000000019 00000 n +0000000093 00000 n +0000000147 00000 n +0000000222 00000 n +0000000390 00000 n +0000001522 00000 n +0000001690 00000 n +0000002423 00000 n +0000002456 00000 n +0000002574 00000 n + +trailer +<< +/Size 11 +/Root 1 0 R +/Info 10 0 R +>> + +startxref +2714 +%%EOF diff --git a/lib/core/service/file_service.dart b/lib/core/service/file_service.dart index a990e6e..1ede780 100644 --- a/lib/core/service/file_service.dart +++ b/lib/core/service/file_service.dart @@ -85,7 +85,7 @@ class FileService { bool create = false, }) async { final dir = getDirectory(type); - final filename = (fileName ?? const Uuid().v1()) + '.$extension'; + final filename = '${fileName ?? const Uuid().v1()}.$extension'; final file = File(p.join(dir.path, filename)); if (create) { await file.create(recursive: true); diff --git a/lib/features/document_details/cubit/document_details_cubit.dart b/lib/features/document_details/cubit/document_details_cubit.dart index 3c17cad..d893d70 100644 --- a/lib/features/document_details/cubit/document_details_cubit.dart +++ b/lib/features/document_details/cubit/document_details_cubit.dart @@ -168,7 +168,7 @@ class DocumentDetailsCubit extends Cubit { if (!file.existsSync()) { file.createSync(); await _api.downloadToFile( - state.document!, + state.document!.id, file.path, ); } @@ -219,7 +219,7 @@ class DocumentDetailsCubit extends Cubit { // ); await _api.downloadToFile( - state.document!, + state.document!.id, targetPath, original: downloadOriginal, onProgressChanged: (progress) { @@ -255,7 +255,7 @@ class DocumentDetailsCubit extends Cubit { FileService.instance.temporaryDirectory, ); await _api.downloadToFile( - state.document!, + state.document!.id, filePath, original: shareOriginal, ); @@ -282,7 +282,7 @@ class DocumentDetailsCubit extends Cubit { FileService.instance.temporaryDirectory, ); await _api.downloadToFile( - state.document!, + state.document!.id, filePath, original: false, ); diff --git a/lib/features/document_edit/view/document_edit_page.dart b/lib/features/document_edit/view/document_edit_page.dart index b65e4ba..59d7218 100644 --- a/lib/features/document_edit/view/document_edit_page.dart +++ b/lib/features/document_edit/view/document_edit_page.dart @@ -24,10 +24,9 @@ import 'package:paperless_mobile/routing/routes/labels_route.dart'; import 'package:paperless_mobile/routing/routes/shells/authenticated_route.dart'; typedef ItemBuilder = Widget Function(BuildContext context, T itemData); + class DocumentEditPage extends StatefulWidget { - const DocumentEditPage({ - super.key - }); + const DocumentEditPage({super.key}); @override State createState() => _DocumentEditPageState(); diff --git a/lib/features/documents/view/pages/document_view.dart b/lib/features/documents/view/pages/document_view.dart index 667ffcb..7116120 100644 --- a/lib/features/documents/view/pages/document_view.dart +++ b/lib/features/documents/view/pages/document_view.dart @@ -1,10 +1,160 @@ -import 'dart:math'; +// import 'dart:async'; +// import 'dart:developer'; + +// import 'package:flutter/foundation.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_animate/flutter_animate.dart'; +// import 'package:paperless_mobile/core/extensions/flutter_extensions.dart'; +// import 'package:pdfx/pdfx.dart'; + +// class DocumentView extends StatefulWidget { +// final String? filePath; +// final Future? bytes; +// final String? title; +// final bool showAppBar; +// final bool showControls; +// const DocumentView({ +// super.key, +// this.bytes, +// this.showAppBar = true, +// this.showControls = true, +// this.title, +// this.filePath, +// }) : assert(bytes != null || filePath != null); + +// @override +// State createState() => _DocumentViewState(); +// } + +// class _DocumentViewState extends State { +// late final PdfController _controller; +// int _currentPage = 1; +// int? _totalPages; +// @override +// void initState() { +// super.initState(); +// Future document; +// document = PdfDocument.openAsset("assets/example/sample.pdf"); +// // if (widget.bytes != null) { +// // document = widget.bytes!.then((value) => PdfDocument.openData(value)); +// // } else { +// // document = PdfDocument.openFile(widget.filePath!); +// // } +// _controller = PdfController(document: document); +// } + +// @override +// void didChangeDependencies() { +// super.didChangeDependencies(); +// log("Did change dependencies LOL"); +// } + +// @override +// void dispose() { +// _controller.dispose(); +// super.dispose(); +// } + +// @override +// Widget build(BuildContext context) { +// final pageTransitionDuration = MediaQuery.disableAnimationsOf(context) +// ? 0.milliseconds +// : 100.milliseconds; +// final canGoToNextPage = _totalPages != null && _currentPage < _totalPages!; +// final canGoToPreviousPage = +// _controller.pagesCount != null && _currentPage > 1; +// return Scaffold( +// appBar: widget.showAppBar +// ? AppBar( +// title: widget.title != null ? Text(widget.title!) : null, +// ) +// : null, +// bottomNavigationBar: widget.showControls +// ? BottomAppBar( +// child: Row( +// children: [ +// Flexible( +// child: Row( +// children: [ +// IconButton.filled( +// onPressed: canGoToPreviousPage +// ? () async { +// await _controller.previousPage( +// duration: pageTransitionDuration, +// curve: Curves.easeOut, +// ); +// } +// : null, +// icon: const Icon(Icons.arrow_left), +// ), +// const SizedBox(width: 16), +// IconButton.filled( +// onPressed: canGoToNextPage +// ? () async { +// await _controller.nextPage( +// duration: pageTransitionDuration, +// curve: Curves.easeOut, +// ); +// } +// : null, +// icon: const Icon(Icons.arrow_right), +// ), +// ], +// ), +// ), +// // PdfPageNumber( +// // controller: _controller, +// // builder: (context, loadingState, page, pagesCount) { +// // if (loadingState != PdfLoadingState.success) { +// // return const Text("-/-"); +// // } +// // return Text( +// // "$page/$pagesCount", +// // style: Theme.of(context).textTheme.titleMedium, +// // ).padded(); +// // }, +// // ), +// ], +// ), +// ) +// : null, +// body: PdfView( +// builders: PdfViewBuilders( +// options: const DefaultBuilderOptions(), +// documentLoaderBuilder: (_) => +// const Center(child: CircularProgressIndicator()), +// pageLoaderBuilder: (_) => +// const Center(child: CircularProgressIndicator()), +// errorBuilder: (p0, error) { +// return Center( +// child: Text(error.toString()), +// ); +// }, +// ), +// controller: _controller, +// ), +// // PdfView( +// // controller: _controller, +// // onDocumentLoaded: (document) { +// // setState(() { +// // _totalPages = document.pagesCount; +// // }); +// // }, +// // onPageChanged: (page) { +// // setState(() { +// // _currentPage = page; +// // }); +// // }, +// // ), +// ); +// } +// } import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:paperless_mobile/core/extensions/flutter_extensions.dart'; -import 'package:pdfrx/pdfrx.dart'; +import 'package:pdfx/pdfx.dart'; class DocumentView extends StatefulWidget { final Future documentBytes; @@ -12,37 +162,27 @@ class DocumentView extends StatefulWidget { final bool showAppBar; final bool showControls; const DocumentView({ - super.key, + Key? key, required this.documentBytes, this.showAppBar = true, this.showControls = true, this.title, - }); + }) : super(key: key); @override State createState() => _DocumentViewState(); } class _DocumentViewState extends State { - final PdfViewerController _controller = PdfViewerController(); + late final PdfController _controller; int _currentPage = 1; int? _totalPages; - @override void initState() { super.initState(); - _controller.addListener(() { - if (mounted) { - if (_controller.pageNumber != null) { - setState(() { - _currentPage = _controller.pageNumber!; - }); - } - setState(() { - _totalPages = _controller.pages.length; - }); - } - }); + final document = + widget.documentBytes.then((value) => PdfDocument.openData(value)); + _controller = PdfController(document: document); } @override @@ -57,7 +197,8 @@ class _DocumentViewState extends State { ? 0.milliseconds : 100.milliseconds; final canGoToNextPage = _totalPages != null && _currentPage < _totalPages!; - final canGoToPreviousPage = _totalPages != null && _currentPage > 1; + final canGoToPreviousPage = + _controller.pagesCount != null && _currentPage > 1; return Scaffold( appBar: widget.showAppBar ? AppBar( @@ -74,8 +215,9 @@ class _DocumentViewState extends State { IconButton.filled( onPressed: canGoToPreviousPage ? () async { - await _controller.goToPage( - pageNumber: _currentPage - 1, + await _controller.previousPage( + duration: pageTransitionDuration, + curve: Curves.easeOut, ); } : null, @@ -85,8 +227,9 @@ class _DocumentViewState extends State { IconButton.filled( onPressed: canGoToNextPage ? () async { - await _controller.goToPage( - pageNumber: _currentPage + 1, + await _controller.nextPage( + duration: pageTransitionDuration, + curve: Curves.easeOut, ); } : null, @@ -95,13 +238,14 @@ class _DocumentViewState extends State { ], ), ), - Builder( - builder: (context) { - if (_totalPages == null) { + PdfPageNumber( + controller: _controller, + builder: (context, loadingState, page, pagesCount) { + if (loadingState != PdfLoadingState.success) { return const Text("-/-"); } return Text( - "$_currentPage/$_totalPages", + "$page/$pagesCount", style: Theme.of(context).textTheme.titleMedium, ).padded(); }, @@ -110,61 +254,17 @@ class _DocumentViewState extends State { ), ) : null, - body: FutureBuilder( - future: widget.documentBytes, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return Center( - child: CircularProgressIndicator(), - ); - } - - return PdfViewer.data( - snapshot.data!, - controller: _controller, - anchor: PdfPageAnchor.all, - displayParams: PdfViewerParams( - backgroundColor: Theme.of(context).colorScheme.background, - margin: 0, - layoutPages: (pages, params) { - final height = - pages.fold(0.0, (prev, page) => max(prev, page.height)) + - params.margin * 2; - final pageLayouts = []; - double x = params.margin; - for (var page in pages) { - pageLayouts.add( - Rect.fromLTWH( - x, - (height - page.height) / 2, // center vertically - page.width, - page.height, - ), - ); - x += page.width + params.margin; - } - return PdfPageLayout( - pageLayouts: pageLayouts, - documentSize: Size(x, height), - ); - }, - ), - // controller: _controller, - // onDocumentLoaded: (document) { - // if (mounted) { - // setState(() { - // _totalPages = document.pagesCount; - // }); - // } - // }, - // onPageChanged: (page) { - // if (mounted) { - // setState(() { - // _currentPage = page; - // }); - // } - // }, - ); + body: PdfView( + controller: _controller, + onDocumentLoaded: (document) { + setState(() { + _totalPages = document.pagesCount; + }); + }, + onPageChanged: (page) { + setState(() { + _currentPage = page; + }); }, ), ); diff --git a/lib/features/documents/view/pages/pdfrx_document_view.dart b/lib/features/documents/view/pages/pdfrx_document_view.dart new file mode 100644 index 0000000..d6f5764 --- /dev/null +++ b/lib/features/documents/view/pages/pdfrx_document_view.dart @@ -0,0 +1,85 @@ +import 'dart:async'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:pdfrx/pdfrx.dart'; + +class PdfrxDocumentView extends StatefulWidget { + final Future bytes; + + const PdfrxDocumentView({super.key, required this.bytes}); + + @override + State createState() => _PdfrxDocumentViewState(); +} + +class _PdfrxDocumentViewState extends State { + @override + Widget build(BuildContext context) { + return PdfViewer.asset( + // snapshot.data!, + 'assets/example/sample.pdf', + displayParams: PdfViewerParams( + layoutPages: (pages, params) { + final height = + pages.fold(0.0, (prev, page) => max(prev, page.height)) + + params.margin * 2; + final pageLayouts = []; + double x = params.margin; + for (var page in pages) { + pageLayouts.add( + Rect.fromLTWH( + x, + (height - page.height) / 2, // center vertically + page.width, + page.height, + ), + ); + x += page.width + params.margin; + } + return PdfPageLayout( + pageLayouts: pageLayouts, + documentSize: Size(x, height), + ); + }, + ), + ); + return FutureBuilder( + future: widget.bytes, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator()); + } + return PdfViewer.asset( + // snapshot.data!, + 'assets/example/sample.pdf', + displayParams: PdfViewerParams( + layoutPages: (pages, params) { + final height = + pages.fold(0.0, (prev, page) => max(prev, page.height)) + + params.margin * 2; + final pageLayouts = []; + double x = params.margin; + for (var page in pages) { + pageLayouts.add( + Rect.fromLTWH( + x, + (height - page.height) / 2, // center vertically + page.width, + page.height, + ), + ); + x += page.width + params.margin; + } + return PdfPageLayout( + pageLayouts: pageLayouts, + documentSize: Size(x, height), + ); + }, + ), + ); + }, + ); + } +} diff --git a/lib/routing/routes/documents_route.dart b/lib/routing/routes/documents_route.dart index c26dde7..54fa80a 100644 --- a/lib/routing/routes/documents_route.dart +++ b/lib/routing/routes/documents_route.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; +import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bulk_action_cubit.dart'; import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_label_page.dart'; import 'package:paperless_mobile/features/document_bulk_action/view/widgets/fullscreen_bulk_edit_tags_widget.dart'; @@ -13,6 +14,7 @@ import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubi 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/documents_page.dart'; +import 'package:paperless_mobile/features/documents/view/pages/pdfrx_document_view.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/routing/navigation_keys.dart'; import 'package:paperless_mobile/theme.dart'; @@ -110,6 +112,8 @@ class DocumentPreviewRoute extends GoRouteData { @override Widget build(BuildContext context, GoRouterState state) { + return PdfrxDocumentView( + bytes: context.read().downloadDocument(id)); return DocumentView( documentBytes: context.read().downloadDocument(id), title: title, diff --git a/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api.dart b/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api.dart index 37a228d..bb3abb8 100644 --- a/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api.dart +++ b/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api.dart @@ -28,7 +28,7 @@ abstract class PaperlessDocumentsApi { String getThumbnailUrl(int docId); Future downloadDocument(int id, {bool original}); Future downloadToFile( - DocumentModel document, + int id, String localFilePath, { bool original = false, void Function(double progress)? onProgressChanged, diff --git a/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart b/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart index 5b746cc..5f51f39 100644 --- a/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart +++ b/packages/paperless_api/lib/src/modules/documents_api/paperless_documents_api_impl.dart @@ -223,14 +223,14 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi { @override Future downloadToFile( - DocumentModel document, + int id, String localFilePath, { bool original = false, void Function(double)? onProgressChanged, }) async { try { final response = await client.download( - "/api/documents/${document.id}/download/", + "/api/documents/$id/download/", localFilePath, onReceiveProgress: (count, total) => onProgressChanged?.call(count / total), diff --git a/pubspec.lock b/pubspec.lock index a787607..459487e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1191,10 +1191,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_android: dependency: transitive description: @@ -1247,10 +1247,10 @@ packages: dependency: "direct main" description: name: pdfrx - sha256: "001036f0b97214ec0a34bf02414f4f4a260b60ccc577d1bff97366b3a2e7b4f6" + sha256: "7b34f58ec0f8959cd99a6ff3de8fd1a54824823462c7a118fa55bcb42ca45422" url: "https://pub.dev" source: hosted - version: "0.4.3" + version: "0.4.6" pdfx: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 0e9f95a..c7a7604 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -87,7 +87,7 @@ dependencies: flutter_secure_storage: ^9.0.0 sliver_tools: ^0.2.10 webview_flutter: ^4.2.1 - printing: ^5.11.0 + printing: ^5.11.1 go_router: ^13.0.1 fl_chart: ^0.66.0 palette_generator: ^0.3.3+2 @@ -107,7 +107,7 @@ dependencies: ref: '4be9de9ffed5398fd7d5f44bbb07dcd3d3f1711b' path: packages/pdfx markdown: ^7.1.1 - pdfrx: ^0.4.3 + pdfrx: ^0.4.6 dependency_overrides: intl: ^0.18.1 @@ -158,6 +158,7 @@ flutter: - test/fixtures/preview/ - test/fixtures/document_types/ - assets/changelogs/ + - assets/example/ fonts: - family: RobotoMono