|
|
|
@@ -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<void> saveImageToLocal(String url) async {
|
|
|
|
|
// 1. 权限请求
|
|
|
|
|
if (Platform.isAndroid) {
|
|
|
|
|
dio.PermissionStatus status;
|
|
|
|
|
Future<void> 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<String>(
|
|
|
|
|
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<String> _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),
|
|
|
|
|