diff --git a/lib/features/document_details/cubit/document_details_cubit.dart b/lib/features/document_details/cubit/document_details_cubit.dart index 89b850c..301a66b 100644 --- a/lib/features/document_details/cubit/document_details_cubit.dart +++ b/lib/features/document_details/cubit/document_details_cubit.dart @@ -11,6 +11,7 @@ import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/service/file_description.dart'; import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart'; +import 'package:printing/printing.dart'; import 'package:share_plus/share_plus.dart'; import 'package:cross_file/cross_file.dart'; @@ -98,6 +99,7 @@ class DocumentDetailsCubit extends Cubit { Future openDocumentInSystemViewer() async { final cacheDir = await FileService.temporaryDirectory; + //TODO: Why is this cleared here? await FileService.clearDirectoryContent(PaperlessDirectoryType.temporary); if (state.metaData == null) { await loadMetaData(); @@ -199,6 +201,26 @@ class DocumentDetailsCubit extends Cubit { ); } + Future printDocument() async { + if (state.metaData == null) { + await loadMetaData(); + } + final filePath = _buildDownloadFilePath(false, await FileService.temporaryDirectory); + await _api.downloadToFile( + state.document, + filePath, + original: false, + ); + final file = File(filePath); + if (!file.existsSync()) { + throw Exception("An error occurred while downloading the document."); + } + Printing.layoutPdf( + name: state.document.title, + onLayout: (format) => file.readAsBytesSync(), + ); + } + String _buildDownloadFilePath(bool original, Directory dir) { final description = FileDescription.fromPath( state.metaData!.mediaFilename.replaceAll("/", " "), // Flatten directory structure 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 0d7396c..afcdb71 100644 --- a/lib/features/document_details/view/pages/document_details_page.dart +++ b/lib/features/document_details/view/pages/document_details_page.dart @@ -1,5 +1,7 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:open_filex/open_filex.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; @@ -44,11 +46,6 @@ class _DocumentDetailsPageState extends State { final _pagingScrollController = ScrollController(); - @override - void didChangeDependencies() { - super.didChangeDependencies(); - } - @override Widget build(BuildContext context) { final apiVersion = context.watch(); @@ -87,22 +84,24 @@ class _DocumentDetailsPageState extends State { alignment: Alignment.topCenter, children: [ BlocBuilder( - builder: (context, state) => Positioned.fill( - child: DocumentPreview( - document: state.document, - fit: BoxFit.cover, - ), - ), + builder: (context, state) { + return Positioned.fill( + child: DocumentPreview( + document: state.document, + fit: BoxFit.cover, + ), + ); + }, ), Positioned.fill( top: 0, - child: Container( - height: 100, + child: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( colors: [ - Colors.black.withOpacity(0.7), - Colors.black.withOpacity(0.2), + Theme.of(context).colorScheme.background.withOpacity(0.8), + Theme.of(context).colorScheme.background.withOpacity(0.5), + Colors.transparent, Colors.transparent, Colors.transparent, ], @@ -310,7 +309,7 @@ class _DocumentDetailsPageState extends State { IconButton( tooltip: S.of(context)!.previewTooltip, icon: const Icon(Icons.visibility), - onPressed: (isConnected && false) ? () => _onOpen(state.document) : null, + onPressed: (isConnected) ? () => _onOpen(state.document) : null, ).paddedOnly(right: 4.0), IconButton( tooltip: S.of(context)!.openInSystemViewer, @@ -318,6 +317,11 @@ class _DocumentDetailsPageState extends State { onPressed: isConnected ? _onOpenFileInSystemViewer : null, ).paddedOnly(right: 4.0), DocumentShareButton(document: state.document), + IconButton( + tooltip: "Print", //TODO: INTL + onPressed: () => context.read().printDocument(), + icon: const Icon(Icons.print), + ), ], ); }, @@ -404,7 +408,8 @@ class _DocumentDetailsPageState extends State { Navigator.of(context).push( MaterialPageRoute( builder: (_) => DocumentView( - documentBytes: context.read().getPreview(document.id), + documentBytes: context.read().download(document), + title: document.title, ), ), ); diff --git a/lib/features/document_scan/view/scanner_page.dart b/lib/features/document_scan/view/scanner_page.dart index 1f2b8c6..0f5c705 100644 --- a/lib/features/document_scan/view/scanner_page.dart +++ b/lib/features/document_scan/view/scanner_page.dart @@ -120,6 +120,7 @@ class _ScannerPageState extends State with SingleTickerProviderStat ? () => Navigator.of(context).push( MaterialPageRoute( builder: (context) => DocumentView( + documentBytes: _assembleFileBytes( state, forcePdf: true, diff --git a/lib/features/documents/view/pages/document_view.dart b/lib/features/documents/view/pages/document_view.dart index 508fc0e..320aef0 100644 --- a/lib/features/documents/view/pages/document_view.dart +++ b/lib/features/documents/view/pages/document_view.dart @@ -1,12 +1,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_pdfview/flutter_pdfview.dart'; class DocumentView extends StatefulWidget { final Future documentBytes; - + final String? title; const DocumentView({ Key? key, required this.documentBytes, + this.title, }) : super(key: key); @override @@ -14,37 +16,90 @@ class DocumentView extends StatefulWidget { } class _DocumentViewState extends State { - // late PdfController _pdfController; - - @override - void initState() { - super.initState(); - // _pdfController = PdfController( - // document: PdfDocument.openData( - // widget.documentBytes, - // ), - // ); - } + int? _currentPage; + int? _totalPages; + PDFViewController? _controller; @override Widget build(BuildContext context) { - return Container(); - // return Scaffold( - // appBar: AppBar( - // title: Text(S.of(context)!.preview), - // ), - // body: PdfView( - // builders: PdfViewBuilders( - // options: const DefaultBuilderOptions( - // loaderSwitchDuration: Duration(milliseconds: 500), - // ), - // pageLoaderBuilder: (context) => const Center( - // child: CircularProgressIndicator(), - // ), - // ), - // scrollDirection: Axis.vertical, - // controller: _pdfController, - // ), - // ); + final isInitialized = _controller != null && _currentPage != null && _totalPages != null; + final canGoToNextPage = isInitialized && _currentPage! + 1 < _totalPages!; + final canGoToPreviousPage = isInitialized && _currentPage! > 0; + return Scaffold( + appBar: AppBar( + title: widget.title != null ? Text(widget.title!) : null, + ), + bottomNavigationBar: BottomAppBar( + child: Row( + children: [ + Flexible( + child: Row( + children: [ + IconButton.filled( + onPressed: canGoToPreviousPage + ? () { + _controller?.setPage(_currentPage! - 1); + } + : null, + icon: const Icon(Icons.arrow_left), + ), + const SizedBox(width: 16), + IconButton.filled( + onPressed: canGoToNextPage + ? () { + _controller?.setPage(_currentPage! + 1); + } + : null, + icon: const Icon(Icons.arrow_right), + ), + ], + ), + ), + if (_currentPage != null && _totalPages != null) + Text( + "${_currentPage! + 1}/$_totalPages", + style: Theme.of(context).textTheme.labelLarge, + ), + ], + ), + ), + body: FutureBuilder( + future: widget.documentBytes, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center( + child: CircularProgressIndicator(), + ); + } + return PDFView( + pdfData: snapshot.data, + defaultPage: 0, + enableSwipe: true, + fitPolicy: FitPolicy.BOTH, + swipeHorizontal: true, + onRender: (pages) { + setState(() { + _currentPage = 0; + _totalPages = pages ?? -1; + }); + }, + onPageChanged: (page, total) { + setState(() { + _currentPage = page; + _totalPages = total; + }); + }, + onViewCreated: (controller) { + _controller = controller; + }, + onError: (error) { + print(error.toString()); + }, + onPageError: (page, error) { + print('$page: ${error.toString()}'); + }, + ); + }), + ); } } diff --git a/lib/main.dart b/lib/main.dart index fa15e40..afcb42a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -74,6 +74,7 @@ Future _initHive() async { } void main() async { + Paint.enableDithering = true; if (kDebugMode) { // URL: http://localhost:3131 // Login: admin:test diff --git a/pubspec.lock b/pubspec.lock index 20ac4c4..d4e99dd 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -617,6 +617,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.18" + flutter_pdfview: + dependency: "direct main" + description: + name: flutter_pdfview + sha256: d9735fd8991609910742a25c63a5f87060849e57e60112c677b802ddb64bed72 + url: "https://pub.dev" + source: hosted + version: "1.3.1" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -1267,6 +1275,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + printing: + dependency: "direct main" + description: + name: printing + sha256: e7c383dca95ee7b88c02dc1c66638628d3dcdc2fb2cc47e7a595facd47e46b56 + url: "https://pub.dev" + source: hosted + version: "5.11.0" process: dependency: transitive description: @@ -1802,4 +1818,4 @@ packages: version: "3.1.1" sdks: dart: ">=3.0.0 <4.0.0" - flutter: ">=3.7.0" + flutter: ">=3.10.0" diff --git a/pubspec.yaml b/pubspec.yaml index d6e3e9d..bc8616f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -94,6 +94,8 @@ dependencies: flutter_secure_storage: ^8.0.0 sliver_tools: ^0.2.10 webview_flutter: ^4.2.1 + printing: ^5.11.0 + flutter_pdfview: ^1.3.1 dependency_overrides: intl: ^0.18.0