fix:增加视频日志中的“已下载”功能和“云存”当中的下载功能

This commit is contained in:
liyi 2025-02-26 16:47:27 +08:00 committed by Liuyf
parent 98fadea001
commit 0e20fd57f2
8 changed files with 612 additions and 131 deletions

View File

@ -1,11 +1,15 @@
import 'dart:convert';
import 'dart:io';
import 'package:crypto/crypto.dart';
import 'package:dio/dio.dart';
import 'package:get/get.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:star_lock/app_settings/app_settings.dart';
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_logic.dart';
import 'package:star_lock/network/api_repository.dart';
import 'package:star_lock/tools/baseGetXController.dart';
import 'package:star_lock/versionUndate/versionUndate_entity.dart';
@ -14,6 +18,8 @@ import 'editVideoLog_state.dart';
class EditVideoLogLogic extends BaseGetXController {
EditVideoLogState state = EditVideoLogState();
final VideoLogLogic videoLogLogic = Get.find<VideoLogLogic>();
Future<void> deleteLockCloudStorageList() async {
final VersionUndateEntity entity =
await ApiRepository.to.deleteLockCloudStorageList(
@ -86,6 +92,146 @@ class EditVideoLogLogic extends BaseGetXController {
}
}
//
Future<bool> deleteDownloadsByPaths(List<String> filePaths) async {
try {
showEasyLoading();
//
Directory appDocDir = await getApplicationDocumentsDirectory();
final logFilePath = '${appDocDir.path}/download_log.json';
if (await File(logFilePath).exists()) {
final content = await File(logFilePath).readAsString();
Map<String, dynamic> logData =
Map<String, dynamic>.from(json.decode(content));
//
final filteredLogData = <String, dynamic>{};
for (final entry in logData.entries) {
final filePath = entry.key;
if (!filePaths.contains(filePath)) {
filteredLogData[filePath] = entry.value; //
} else {
//
final file = File(filePath);
if (await file.exists()) {
await file.delete();
print('已删除文件:$filePath');
}
}
}
//
await File(logFilePath).writeAsString(json.encode(filteredLogData));
showToast('删除成功'.tr);
//
await videoLogLogic.groupDownloadsByDay();
Get.back();
return true;
} else {
print('日志文件不存在');
return false;
}
} catch (e) {
print('批量删除失败:$e');
return false;
} finally {
dismissEasyLoading();
}
}
// URL生成唯一的文件名MD5哈希值
String getFileNameFromUrl(String url, String extension) {
final hash = md5.convert(utf8.encode(url)).toString(); // 使 md5
return '$hash.$extension';
}
Future<void> recordDownloadTime(String filePath) async {
final appDocDir = await getApplicationDocumentsDirectory();
final logFilePath = '${appDocDir.path}/download_log.json';
//
Map<String, int> logData = {};
if (await File(logFilePath).exists()) {
final content = await File(logFilePath).readAsString();
logData = Map<String, int>.from(json.decode(content));
}
//
logData[filePath] = DateTime.now().millisecondsSinceEpoch;
//
await File(logFilePath).writeAsString(json.encode(logData));
}
//
Future<String?> downloadFile(String? url) async {
if (url == null || url.isEmpty) {
print('URL不能为空');
return null;
}
//
if (await Permission.storage.request().isGranted) {
try {
//
Directory appDocDir = await getApplicationDocumentsDirectory();
// URL生成唯一文件名
String extension = _getFileTypeFromUrl(url); //
String fileName = getFileNameFromUrl(url, extension); // URL生成唯一文件名
String savePath = '${appDocDir.path}/downloads/$fileName'; //
//
final dir = Directory('${appDocDir.path}/downloads');
if (!await dir.exists()) {
await dir.create(recursive: true);
}
//
File file = File(savePath);
if (await file.exists()) {
print('文件已存在,无需重新下载:$savePath');
return savePath; //
}
//
await Dio().download(url, savePath,
onReceiveProgress: (received, total) {
if (total != -1) {
print('下载进度: ${(received / total * 100).toStringAsFixed(0)}%');
}
});
//
await recordDownloadTime(savePath);
print('文件已成功下载到:$savePath');
// 便使
return savePath;
} catch (e) {
print('下载失败:$e');
return null;
}
} else {
print('未获取存储权限');
return null;
}
}
// URL自动检测文件类型
String _getFileTypeFromUrl(String url) {
final uri = Uri.parse(url);
final path = uri.path;
final extension = path.split('.').last.toLowerCase();
return extension.isNotEmpty ? extension : 'unknown';
}
//
Future<bool> _requestPermission() async {
final status = await Permission.storage.request();

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -180,55 +182,70 @@ class _EditVideoLogPageState extends State<EditVideoLogPage> {
Widget bottomBottomBtnWidget() {
return SizedBox(
width: 1.sw,
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
bottomBtnItemWidget(
'images/main/icon_lockDetail_monitoringDownloadVideo.png',
'下载'.tr,
Colors.white,
_onDownLoadClick,
),
SizedBox(width: 100.w),
bottomBtnItemWidget(
'images/main/icon_lockDetail_monitoringDeletVideo.png',
'删除'.tr,
AppColors.mainColor,
_onDelClick,
)
]),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
state.isNavLocal.value == false
? bottomBtnItemWidget(
'images/main/icon_lockDetail_monitoringDownloadVideo.png',
'下载'.tr,
Colors.white,
_onDownLoadClick,
)
: SizedBox.shrink(),
state.isNavLocal.value == false
? SizedBox(width: 100.w)
: SizedBox.shrink(),
bottomBtnItemWidget(
'images/main/icon_lockDetail_monitoringDeletVideo.png',
'删除'.tr,
AppColors.mainColor,
_onDelClick,
)
],
),
);
}
Future<void> _onDownLoadClick() async {
if (state.selectVideoLogList.value.isNotEmpty) {
double _progress = 0.0;
state.selectVideoLogList.value.forEach((element) {
if (element.videoUrl != null && element.videoUrl != '') {
logic.downloadFile(element.videoUrl ?? '');
} else if (element.imagesUrl != null && element.imagesUrl != '') {
logic.downloadFile(element.imagesUrl ?? '');
}
});
// double _progress = 0.0;
//
//
EasyLoading.showProgress(_progress, status: '加载数据中'.tr);
//
for (int i = 0; i <= state.selectVideoLogList.length - 1; i++) {
final item = state.selectVideoLogList.value[i];
// imagesUrl
if (item.imagesUrl != null && item.imagesUrl!.isNotEmpty) {
await logic.downloadAndSaveToGallery(item.imagesUrl!, 'image_$i.jpg');
}
// videoUrl
if (item.videoUrl != null && item.videoUrl!.isNotEmpty) {
await logic.downloadAndSaveToGallery(item.videoUrl!, 'video_$i.mp4');
}
//
_progress = (i + 1) / state.selectVideoLogList.length;
EasyLoading.showProgress(_progress, status: '加载数据中'.tr);
}
//
EasyLoading.dismiss();
EasyLoading.showSuccess('下载完成,请到相册查看'.tr);
// EasyLoading.showProgress(_progress, status: '加载数据中'.tr);
//
// //
// for (int i = 0; i <= state.selectVideoLogList.length - 1; i++) {
// final item = state.selectVideoLogList.value[i];
//
// // imagesUrl
// if (item.imagesUrl != null && item.imagesUrl!.isNotEmpty) {
// await logic.downloadAndSaveToGallery(item.imagesUrl!, 'image_$i.jpg');
// }
//
// // videoUrl
// if (item.videoUrl != null && item.videoUrl!.isNotEmpty) {
// await logic.downloadAndSaveToGallery(item.videoUrl!, 'video_$i.mp4');
// }
//
// //
// _progress = (i + 1) / state.selectVideoLogList.length;
// EasyLoading.showProgress(_progress, status: '加载数据中'.tr);
// }
//
// //
// EasyLoading.dismiss();
// EasyLoading.showSuccess('下载完成,请到相册查看'.tr);
EasyLoading.showSuccess('下载完成'.tr);
state.selectVideoLogList.clear();
Get.back();
setState(() {});
// Get.toNamed(Routers.videoLogDownLoadPage,
// arguments: <String, List<RecordListData>>{
@ -240,11 +257,28 @@ class _EditVideoLogPageState extends State<EditVideoLogPage> {
}
Future<void> _onDelClick() async {
if (state.selectVideoLogList.value.isNotEmpty) {
await logic.deleteLockCloudStorageList();
setState(() {});
if (state.isNavLocal.isFalse) {
if (state.selectVideoLogList.value.isNotEmpty) {
await logic.deleteLockCloudStorageList();
setState(() {});
} else {
logic.showToast('请选择要删除的视频'.tr);
}
} else {
logic.showToast('请选择要删除的视频'.tr);
if (state.selectVideoLogList.value.isNotEmpty) {
List<String> deletePaths = [];
state.selectVideoLogList.value.forEach((element) {
if (element.imagesUrl != null && element.imagesUrl != '') {
deletePaths.add(element.imagesUrl!);
}
if (element.videoUrl != null && element.videoUrl != '') {
deletePaths.add(element.videoUrl!);
}
});
logic.deleteDownloadsByPaths(deletePaths);
} else {
logic.showToast('请选择要删除的视频'.tr);
}
}
}
@ -365,21 +399,56 @@ class _EditVideoLogPageState extends State<EditVideoLogPage> {
_buildImageItem(RecordListData recordData) {
return RotatedBox(
quarterTurns: -1,
child: Image.network(
recordData.imagesUrl!,
child: _buildImageWidget(recordData.imagesUrl),
);
}
// Image Widget
Widget _buildImageWidget(String? imageUrl) {
if (imageUrl == null || imageUrl.isEmpty) {
//
return Image.asset(
'images/icon_unHaveData.png', //
fit: BoxFit.cover,
);
}
//
if (_isNetworkUrl(imageUrl)) {
return Image.network(
imageUrl,
fit: BoxFit.cover,
errorBuilder:
(BuildContext context, Object error, StackTrace? stackTrace) {
//
return RotatedBox(
quarterTurns: -1,
child: Image.asset(
'images/icon_unHaveData.png', //
fit: BoxFit.cover,
),
return Image.asset(
'images/icon_unHaveData.png', //
fit: BoxFit.cover,
);
},
),
);
);
} else {
// 使 FileImage
return Image.file(
File(imageUrl),
fit: BoxFit.cover,
errorBuilder:
(BuildContext context, Object error, StackTrace? stackTrace) {
//
return Image.asset(
'images/icon_unHaveData.png', //
fit: BoxFit.cover,
);
},
);
}
}
//
bool _isNetworkUrl(String url) {
final uri = Uri.tryParse(url);
return uri != null &&
uri.scheme.isNotEmpty &&
uri.scheme.startsWith('http');
}
}

View File

@ -12,9 +12,13 @@ class EditVideoLogState {
if (map['lockId'] != null) {
getLockId.value = map['lockId'];
}
if (map['isNavLocal'] != null) {
isNavLocal.value = map['isNavLocal'];
}
}
RxList<RecordListData> selectVideoLogList = <RecordListData>[].obs; //
RxBool isSelectAll = false.obs;
RxList videoLogList = [].obs; //
RxList videoLogList = <CloudStorageData>[].obs; //
RxInt getLockId = 0.obs;
var isNavLocal = false.obs;
}

View File

@ -1,5 +1,8 @@
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:path_provider/path_provider.dart';
import 'package:star_lock/main/lockDetail/videoLog/videoLog/videoLog_entity.dart';
import 'package:star_lock/network/api_repository.dart';
import 'package:star_lock/tools/baseGetXController.dart';
@ -28,6 +31,117 @@ class VideoLogLogic extends BaseGetXController {
}
}
//
Future<void> listDownloadedVideos() async {
Directory appDocDir = await getApplicationDocumentsDirectory();
Directory downloadsDir = Directory('${appDocDir.path}/downloads');
if (await downloadsDir.exists()) {
List<FileSystemEntity> files = downloadsDir.listSync();
final list =
files.where((file) => file is File).map((file) => file.path).toList();
list.forEach((element) async {
final downloadTime = await getDownloadTime(element);
//
});
}
}
//
Future<List<CloudStorageData>> groupDownloadsByDay() async {
final appDocDir = await getApplicationDocumentsDirectory();
final logFilePath = '${appDocDir.path}/download_log.json';
//
Map<String, List<RecordListData>> groupedDownloads = {};
if (await File(logFilePath).exists()) {
final content = await File(logFilePath).readAsString();
final logData = Map<String, int>.from(json.decode(content));
//
logData.forEach((filePath, timestamp) {
final downloadDateTime = DateTime.fromMillisecondsSinceEpoch(timestamp);
final dateKey =
'${downloadDateTime.year}-${downloadDateTime.month.toString().padLeft(2, '0')}-${downloadDateTime.day.toString().padLeft(2, '0')}';
//
if (!groupedDownloads.containsKey(dateKey)) {
groupedDownloads[dateKey] = [];
}
//
if (filePath.endsWith('.jpg')) {
groupedDownloads[dateKey]?.add(
RecordListData(operateDate: timestamp, imagesUrl: filePath),
);
} else if (filePath.endsWith('.mp4')) {
groupedDownloads[dateKey]?.add(
RecordListData(operateDate: timestamp, videoUrl: filePath),
);
}
});
}
// CloudStorageData
List<CloudStorageData> cloudStorageData = [];
groupedDownloads.forEach((dateKey, recordList) {
cloudStorageData.add(
CloudStorageData(date: dateKey, recordList: recordList),
);
});
state.lockVideoList.value = cloudStorageData;
return cloudStorageData;
}
//
Future<int?> getDownloadTime(String filePath) async {
final appDocDir = await getApplicationDocumentsDirectory();
final logFilePath = '${appDocDir.path}/download_log.json';
if (await File(logFilePath).exists()) {
final content = await File(logFilePath).readAsString();
final logData = Map<String, int>.from(json.decode(content));
return logData[filePath];
}
return null;
}
//
Future<bool> clearDownloads() async {
try {
//
Directory appDocDir = await getApplicationDocumentsDirectory();
//
final downloadsDir = Directory('${appDocDir.path}/downloads');
if (await downloadsDir.exists()) {
await downloadsDir.list().forEach((entity) {
if (entity is File) {
entity.delete(recursive: true);
}
});
print('下载目录已清空');
} else {
print('下载目录不存在');
}
//
final logFilePath = '${appDocDir.path}/download_log.json';
if (await File(logFilePath).exists()) {
await File(logFilePath).delete();
print('日志文件已删除');
} else {
print('日志文件不存在');
}
return true;
} catch (e) {
print('清空失败:$e');
return false;
}
}
@override
onReady() {
super.onReady();

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
@ -30,6 +32,7 @@ class _VideoLogPageState extends State<VideoLogPage> {
void initState() {
// TODO: implement initState
super.initState();
logic.groupDownloadsByDay();
}
@override
@ -50,53 +53,94 @@ class _VideoLogPageState extends State<VideoLogPage> {
Visibility(visible: state.isNavLocal.value, child: localTip()),
// title加编辑按钮
editVideoTip(),
Obx(() => Visibility(
Obx(
() => Visibility(
visible: !state.isNavLocal.value,
child: Expanded(
child: ListView.builder(
itemCount: state.videoLogList.length,
itemBuilder: (BuildContext c, int index) {
final CloudStorageData item = state.videoLogList[index];
return Column(
children: <Widget>[
Container(
child: state.videoLogList.length > 0
? Expanded(
child: ListView.builder(
itemCount: state.videoLogList.length,
itemBuilder: (BuildContext c, int index) {
final CloudStorageData item =
state.videoLogList[index];
return Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(
left: 20.w, top: 15.w, bottom: 15.w),
child: Row(children: <Widget>[
Text(item.date ?? '',
style: TextStyle(fontSize: 20.sp)),
])),
mainListView(index, item)
],
);
},
),
)
: _buildNotData(),
),
),
//
Obx(
() => Visibility(
visible: state.isNavLocal.value,
child: state.lockVideoList.length > 0
? Expanded(
child: ListView.builder(
itemCount: state.lockVideoList.length,
itemBuilder: (BuildContext c, int index) {
final CloudStorageData item =
state.lockVideoList[index];
return Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(
left: 20.w, top: 15.w, bottom: 15.w),
child: Row(children: <Widget>[
Text(item.date ?? '',
style: TextStyle(fontSize: 20.sp)),
])),
mainListView(index, item)
],
);
})))),
//
Visibility(
visible: state.isNavLocal.value,
child: Expanded(
child: state.localList.isNotEmpty
? ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext c, int index) {
return Column(
children: <Widget>[
Container(
margin: EdgeInsets.only(
left: 20.w, top: 15.w, bottom: 15.w),
child: Row(children: <Widget>[
Text('2023.10.2$index',
style: TextStyle(fontSize: 20.sp)),
])),
mainListView(index, CloudStorageData()),
],
);
})
: NoData())),
child: Row(
children: <Widget>[
Text(item.date ?? '',
style: TextStyle(fontSize: 20.sp)),
],
),
),
lockMainListView(index, item)
],
);
},
),
)
: _buildNotData(),
),
),
],
),
);
}
Widget _buildNotData() {
return Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'images/icon_noData.png',
width: 160.w,
height: 180.h,
),
Text(
'暂无数据'.tr,
style: TextStyle(
color: AppColors.darkGrayTextColor, fontSize: 22.sp),
)
],
),
),
);
}
// nav按钮
Widget navBtn() {
return SizedBox(
@ -109,6 +153,8 @@ class _VideoLogPageState extends State<VideoLogPage> {
onPressed: () {
setState(() {
state.isNavLocal.value = false;
state.lockVideoList.clear();
// logic.clearDownloads();
});
},
child: Obx(() => Text('云存'.tr,
@ -122,21 +168,27 @@ class _VideoLogPageState extends State<VideoLogPage> {
fontSize: 28.sp,
fontWeight: FontWeight.w600)))),
TextButton(
onPressed: () {
setState(() {
state.isNavLocal.value = true;
});
},
child: Obx(() => Text('本地'.tr,
style: state.isNavLocal.value == true
? TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.w600)
: TextStyle(
color: Colors.grey,
fontSize: 26.sp,
fontWeight: FontWeight.w600)))),
onPressed: () {
setState(() {
state.isNavLocal.value = true;
logic.groupDownloadsByDay();
});
},
child: Obx(
() => Text(
'已下载'.tr,
style: state.isNavLocal.value == true
? TextStyle(
color: Colors.white,
fontSize: 28.sp,
fontWeight: FontWeight.w600)
: TextStyle(
color: Colors.grey,
fontSize: 26.sp,
fontWeight: FontWeight.w600),
),
),
),
],
),
);
@ -243,10 +295,21 @@ class _VideoLogPageState extends State<VideoLogPage> {
iconSize: 30,
color: Colors.black54,
onPressed: () {
Get.toNamed(Routers.editVideoLogPage, arguments: <String, Object>{
'videoDataList': state.videoLogList.value,
'lockId': state.getLockId.value
});
if (state.isNavLocal.value) {
Get.toNamed(Routers.editVideoLogPage,
arguments: <String, Object>{
'videoDataList': state.lockVideoList.value,
'lockId': state.getLockId.value,
'isNavLocal': state.isNavLocal.value
});
} else {
Get.toNamed(Routers.editVideoLogPage,
arguments: <String, Object>{
'videoDataList': state.videoLogList.value,
'lockId': state.getLockId.value,
'isNavLocal': state.isNavLocal.value
});
}
},
)
// TextButton(
@ -286,6 +349,28 @@ class _VideoLogPageState extends State<VideoLogPage> {
);
}
Widget lockMainListView(int index, CloudStorageData itemData) {
return GridView.builder(
padding: EdgeInsets.only(left: 15.w, right: 15.w),
itemCount: itemData.recordList!.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//
crossAxisCount: 3,
//
mainAxisSpacing: 15.w,
//
crossAxisSpacing: 15.w,
//
childAspectRatio: itemW / itemH),
itemBuilder: (BuildContext context, int index) {
final RecordListData recordData = itemData.recordList![index];
return videoItem(recordData);
},
);
}
Widget videoItem(RecordListData recordData) {
return GestureDetector(
onTap: () {
@ -323,9 +408,10 @@ class _VideoLogPageState extends State<VideoLogPage> {
),
SizedBox(height: 5.h),
Text(
DateTool().dateToYMDHNString(recordData.operateDate.toString()),
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.sp))
DateTool().dateToYMDHNString(recordData.operateDate.toString()),
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.sp),
)
],
),
),
@ -347,21 +433,56 @@ class _VideoLogPageState extends State<VideoLogPage> {
_buildImageItem(RecordListData recordData) {
return RotatedBox(
quarterTurns: -1,
child: Image.network(
recordData.imagesUrl!,
child: _buildImageWidget(recordData.imagesUrl),
);
}
// Image Widget
Widget _buildImageWidget(String? imageUrl) {
if (imageUrl == null || imageUrl.isEmpty) {
//
return Image.asset(
'images/icon_unHaveData.png', //
fit: BoxFit.cover,
);
}
//
if (_isNetworkUrl(imageUrl)) {
return Image.network(
imageUrl,
fit: BoxFit.cover,
errorBuilder:
(BuildContext context, Object error, StackTrace? stackTrace) {
//
return RotatedBox(
quarterTurns: -1,
child: Image.asset(
'images/icon_unHaveData.png', //
fit: BoxFit.cover,
),
return Image.asset(
'images/icon_unHaveData.png', //
fit: BoxFit.cover,
);
},
),
);
);
} else {
// 使 FileImage
return Image.file(
File(imageUrl),
fit: BoxFit.cover,
errorBuilder:
(BuildContext context, Object error, StackTrace? stackTrace) {
//
return Image.asset(
'images/icon_unHaveData.png', //
fit: BoxFit.cover,
);
},
);
}
}
//
bool _isNetworkUrl(String url) {
final uri = Uri.tryParse(url);
return uri != null &&
uri.scheme.isNotEmpty &&
uri.scheme.startsWith('http');
}
}

View File

@ -8,6 +8,7 @@ class VideoLogState {
var localList = [];
var getLockId = 0.obs;
var videoLogList = <CloudStorageData>[].obs;
var lockVideoList = <CloudStorageData>[].obs;
var videoCoverList = <Uint8List>[].obs;
VideoLogState() {

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
@ -11,18 +13,42 @@ class FullScreenImagePage extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
onTap: (){
onTap: () {
Navigator.pop(context); //
},
child: Container(
child: RotatedBox(
quarterTurns: -1,
child: PhotoView(
imageProvider: NetworkImage(imageUrl),
imageProvider:
_getImageProvider(imageUrl), // imageUrl
),
),
),
),
);
}
}
// imageUrl ImageProvider
ImageProvider<Object> _getImageProvider(String? imageUrl) {
if (imageUrl == null || imageUrl.isEmpty) {
//
return AssetImage('images/icon_unHaveData.png'); //
}
//
if (_isNetworkUrl(imageUrl)) {
return NetworkImage(imageUrl); //
} else {
return FileImage(File(imageUrl)); //
}
}
//
bool _isNetworkUrl(String url) {
final uri = Uri.tryParse(url);
return uri != null &&
uri.scheme.isNotEmpty &&
uri.scheme.startsWith('http');
}
}

View File

@ -186,7 +186,7 @@ dependencies:
lpinyin: ^2.0.3
#加密解密
# encrypt: ^5.0.1
# crypto: ^3.0.3
crypto: ^3.0.3
pointycastle: ^3.7.3 # 使用最新版本
date_format: ^2.0.7