diff --git a/ln_jq_app/ios/Runner/Info.plist b/ln_jq_app/ios/Runner/Info.plist index 0126237..629759d 100644 --- a/ln_jq_app/ios/Runner/Info.plist +++ b/ln_jq_app/ios/Runner/Info.plist @@ -76,5 +76,11 @@ en +UIFileSharingEnabled + + +LSSupportsOpeningDocumentsInPlace + + diff --git a/ln_jq_app/lib/pages/b_page/site/controller.dart b/ln_jq_app/lib/pages/b_page/site/controller.dart index 3324f68..30dc70b 100644 --- a/ln_jq_app/lib/pages/b_page/site/controller.dart +++ b/ln_jq_app/lib/pages/b_page/site/controller.dart @@ -3,12 +3,14 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_pdfview/flutter_pdfview.dart'; import 'package:getx_scaffold/getx_scaffold.dart' as dio; import 'package:getx_scaffold/getx_scaffold.dart'; import 'package:image_picker/image_picker.dart'; import 'package:ln_jq_app/common/model/base_model.dart'; import 'package:ln_jq_app/common/styles/theme.dart'; import 'package:ln_jq_app/storage_service.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view_gallery.dart'; import 'package:pull_to_refresh/pull_to_refresh.dart'; @@ -794,54 +796,83 @@ class SiteController extends GetxController with BaseControllerMixin { } /// 保存图片到相册 - Future saveImageToLocal(String url) async { - // 1. 权限请求 - if (Platform.isAndroid) { - dio.PermissionStatus status; + Future saveFileToLocal(String url) async { + try { + // 权限请求 + if (Platform.isAndroid) { + dio.PermissionStatus status; - final deviceInfo = await DeviceInfoPlugin().androidInfo; - final sdkInt = deviceInfo.version.sdkInt; + final deviceInfo = await DeviceInfoPlugin().androidInfo; + final sdkInt = deviceInfo.version.sdkInt; - if (sdkInt <= 32) { - status = await Permission.storage.request(); + if (sdkInt <= 32) { + status = await Permission.storage.request(); + } else { + status = await Permission.photos.request(); + } + + if (!status.isGranted) { + showErrorToast("请在系统设置中开启存储权限"); + return; + } } else { - status = await Permission.photos.request(); + var status = await Permission.photos.request(); + if (!status.isGranted) { + showErrorToast("请在系统设置中开启相册权限"); + return; + } } - if (!status.isGranted) { - showErrorToast("请在系统设置中开启存储权限"); - return; + showLoading("正在保存..."); + + // 下载文件 + var response = await Dio().get( + url, + options: Options(responseType: ResponseType.bytes), + ); + + final Uint8List bytes = Uint8List.fromList(response.data); + + if (url.toLowerCase().endsWith('.pdf')) { + String? savePath; + + if (Platform.isAndroid) { + final directory = Directory('/storage/emulated/0/Download'); + if (!await directory.exists()) { + await directory.create(recursive: true); + } + final String fileName = "certificate_${DateTime.now().millisecondsSinceEpoch}.pdf"; + savePath = "${directory.path}/$fileName"; + } else { + // iOS: 保存到文档目录 + final directory = await getApplicationDocumentsDirectory(); + final String fileName = "certificate_${DateTime.now().millisecondsSinceEpoch}.pdf"; + savePath = "${directory.path}/$fileName"; + } + + final File file = File(savePath); + await file.writeAsBytes(bytes); + + dismissLoading(); + showSuccessToast(Platform.isAndroid ? "PDF已保存至系统下载目录" : "PDF已保存,请在'文件'App中查看"); + } else { + // 保存图片到相册 + final result = await SaverGallery.saveImage( + bytes, + quality: 100, + fileName: "certificate_${DateTime.now().millisecondsSinceEpoch}", + skipIfExists: false, + ); + dismissLoading(); + if (result.isSuccess) { + showSuccessToast("图片已保存至相册"); + } else { + showErrorToast("保存失败"); + } } - } else { - var status = await Permission.photos.request(); - if (!status.isGranted) { - showErrorToast("请在系统设置中开启相册权限"); - return; - } - } - - showLoading("正在保存..."); - - // 2. 下载图片 - var response = await Dio().get( - url, - options: Options(responseType: ResponseType.bytes), - ); - - // 3. 保存到相册 - final result = await SaverGallery.saveImage( - Uint8List.fromList(response.data), - quality: 100, - fileName: "certificate_${DateTime.now().millisecondsSinceEpoch}", - skipIfExists: false, - ); - - dismissLoading(); - - if (result.isSuccess) { - showSuccessToast("图片已保存至相册"); - } else { - showErrorToast("保存失败"); + } catch (e) { + dismissLoading(); + showErrorToast("保存异常"); } } @@ -888,12 +919,30 @@ class SiteController extends GetxController with BaseControllerMixin { child: PhotoViewGallery.builder( scrollPhysics: const BouncingScrollPhysics(), builder: (BuildContext context, int index) { + final String url = images[index]; + final bool isPdf = url.toLowerCase().endsWith('.pdf'); + + if (isPdf) { + return PhotoViewGalleryPageOptions.customChild( + child: GestureDetector( + onTap: (){ + _showSaveMenu(url); + }, + child: _buildPdfPreview(url),), + initialScale: PhotoViewComputedScale.contained, + heroAttributes: PhotoViewHeroAttributes(tag: url), + onTapDown: (context, details, controllerValue) { + _showSaveMenu(url); + }, + ); + } + return PhotoViewGalleryPageOptions( - imageProvider: NetworkImage(images[index]), + imageProvider: NetworkImage(url), initialScale: PhotoViewComputedScale.contained, - heroAttributes: PhotoViewHeroAttributes(tag: images[index]), + heroAttributes: PhotoViewHeroAttributes(tag: url), onTapDown: (context, details, controllerValue) { - _showSaveImageMenu(images[index]); + _showSaveMenu(url); }, ); }, @@ -939,7 +988,38 @@ class SiteController extends GetxController with BaseControllerMixin { ); } - void _showSaveImageMenu(String url) { + /// PDF 预览小部件 + Widget _buildPdfPreview(String url) { + return FutureBuilder( + future: _downloadPdf(url), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator(color: Colors.white)); + } + if (snapshot.hasError || snapshot.data == null) { + return const Center(child: Text("PDF 加载失败", style: TextStyle(color: Colors.white))); + } + return PDFView( + filePath: snapshot.data!, + enableSwipe: false, + swipeHorizontal: false, + autoSpacing: false, + pageFling: false, + ); + }, + ); + } + + Future _downloadPdf(String url) async { + final file = File('${(await getTemporaryDirectory()).path}/${url.hashCode}.pdf'); + if (await file.exists()) return file.path; + var response = await Dio().get(url, options: Options(responseType: ResponseType.bytes)); + await file.writeAsBytes(response.data); + return file.path; + } + + void _showSaveMenu(String url) { + final bool isPdf = url.toLowerCase().endsWith('.pdf'); Get.bottomSheet( Container( color: Colors.white, @@ -949,10 +1029,10 @@ class SiteController extends GetxController with BaseControllerMixin { children: [ ListTile( leading: const Icon(Icons.download), - title: const Text('保存图片到相册'), + title: Text(isPdf ? '保存 PDF 文件' : '保存图片到相册'), onTap: () { Get.back(); - saveImageToLocal(url); + saveFileToLocal(url); }, ), const Divider(height: 1), diff --git a/ln_jq_app/pubspec.lock b/ln_jq_app/pubspec.lock index ed02b5f..558172d 100644 --- a/ln_jq_app/pubspec.lock +++ b/ln_jq_app/pubspec.lock @@ -798,7 +798,7 @@ packages: source: hosted version: "1.1.0" path_provider: - dependency: transitive + dependency: "direct main" description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" diff --git a/ln_jq_app/pubspec.yaml b/ln_jq_app/pubspec.yaml index 6c575cf..6ef4730 100644 --- a/ln_jq_app/pubspec.yaml +++ b/ln_jq_app/pubspec.yaml @@ -54,7 +54,7 @@ dependencies: pull_to_refresh: ^2.0.0 flutter_app_update: ^3.2.2 saver_gallery: ^4.0.0 - + path_provider: ^2.1.5 dev_dependencies: flutter_test: