From 8ff338452ea45053b3fce05a9c7d216c03ca99d4 Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 26 Feb 2025 09:48:57 +0800 Subject: [PATCH 1/5] =?UTF-8?q?fix=EF=BC=9A=E7=8C=AB=E7=9C=BC=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=E8=B0=83=E6=95=B4=E4=B8=BA=E8=93=9D=E7=89=99=E5=8F=91?= =?UTF-8?q?=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lockSet/catEyeSet/catEyeWorkMode/catEyeWorkMode_logic.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/main/lockDetail/lockSet/catEyeSet/catEyeWorkMode/catEyeWorkMode_logic.dart b/lib/main/lockDetail/lockSet/catEyeSet/catEyeWorkMode/catEyeWorkMode_logic.dart index 1839363b..26e8893f 100755 --- a/lib/main/lockDetail/lockSet/catEyeSet/catEyeWorkMode/catEyeWorkMode_logic.dart +++ b/lib/main/lockDetail/lockSet/catEyeSet/catEyeWorkMode/catEyeWorkMode_logic.dart @@ -97,6 +97,7 @@ class CatEyeWorkModeLogic extends BaseGetXController { ); if (entity.errorCode!.codeIsSuccessful) { showToast('设置成功'.tr); + getLockSettingInfoData(); } } From 5145bae22911d2f2c29c76dfd2a48fd429ec4432 Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 26 Feb 2025 11:35:29 +0800 Subject: [PATCH 2/5] =?UTF-8?q?fix=EF=BC=9A=E5=A4=84=E7=90=86=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E9=94=81=E5=90=8Ewifi=E9=85=8D=E7=BD=91loading?= =?UTF-8?q?=E4=B8=AD=E6=96=AD=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../configuringWifi_logic.dart | 50 ++++++++++++++++--- .../configuringWifi_state.dart | 3 ++ .../wifiList/wifiList_page.dart | 8 +-- lib/mine/addLock/saveLock/saveLock_logic.dart | 15 +----- lib/network/api_provider.dart | 3 ++ lib/network/api_repository.dart | 8 +++ 6 files changed, 62 insertions(+), 25 deletions(-) diff --git a/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart b/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart index c350b336..1ed6f736 100755 --- a/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart +++ b/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_logic.dart @@ -75,6 +75,11 @@ class ConfiguringWifiLogic extends BaseGetXController { } else { Get.offAllNamed(Routers.starLockMain); } + dismissEasyLoading(); + if (state.loadingTimer != null) { + state.loadingTimer!.cancel(); + state.loadingTimer = null; + } }); } } @@ -85,6 +90,7 @@ class ConfiguringWifiLogic extends BaseGetXController { void _initReplySubscription() { _replySubscription = EventBusManager().eventBus!.on().listen((Reply reply) async { + AppLog.log('收到蓝牙回调${EasyLoading.isShow}'); // WIFI配网结果 if (reply is GatewayConfiguringWifiResultReply) { _replySenderConfiguringWifiResult(reply); @@ -95,6 +101,7 @@ class ConfiguringWifiLogic extends BaseGetXController { if (reply is GatewayGetStatusReply) { _replyStatusInfo(reply); } + AppLog.log('蓝牙回调处理完毕${EasyLoading.isShow}'); }); } @@ -106,9 +113,6 @@ class ConfiguringWifiLogic extends BaseGetXController { switch (status) { case 0x00: - //成功 - cancelBlueConnetctToastTimer(); - final int secretKeyJsonLength = (reply.data[4] << 8) + reply.data[3]; final List secretKeyList = @@ -126,7 +130,7 @@ class ConfiguringWifiLogic extends BaseGetXController { /// 配网成功后,赋值锁的peerId StartChartManage().lockPeerId = peerId ?? ''; - dismissEasyLoading(); + state.isLoading.value = false; // 保存到缓存 await Storage.saveLockNetWorkInfo(jsonMap); @@ -141,6 +145,10 @@ class ConfiguringWifiLogic extends BaseGetXController { default: //失败 dismissEasyLoading(); + if (state.loadingTimer != null) { + state.loadingTimer!.cancel(); + state.loadingTimer = null; + } cancelBlueConnetctToastTimer(); showToast('配网失败'.tr); state.isLoading.value = false; @@ -156,6 +164,8 @@ class ConfiguringWifiLogic extends BaseGetXController { // 点击配置wifi Future senderConfiguringWifiAction() async { + AppLog.log('开始配网${EasyLoading.isShow}'); + EasyLoading.show(); if (state.isLoading.isTrue) { AppLog.log('正在配网中请勿重复点击'); return; @@ -175,7 +185,7 @@ class ConfiguringWifiLogic extends BaseGetXController { state.sureBtnState.value = 1; final GetGatewayConfigurationEntity entity = - await ApiRepository.to.getGatewayConfiguration(timeout: 60); + await ApiRepository.to.getGatewayConfigurationNotLoading(timeout: 60); if (entity.errorCode!.codeIsSuccessful) { state.getGatewayConfigurationStr = entity.data ?? ''; } @@ -210,6 +220,8 @@ class ConfiguringWifiLogic extends BaseGetXController { state.getGatewayConfigurationStr = "{\"userPeerld\": \"$appPeerId\"}"; } + AppLog.log('获取到配网信息===开始发送蓝牙指令${EasyLoading.isShow}'); + BlueManage().blueSendData( BlueManage().connectDeviceName, (BluetoothConnectionState connectionState) async { @@ -223,8 +235,12 @@ class ConfiguringWifiLogic extends BaseGetXController { }, isAddEquipment: true, ); - // 显示加载指示器 - showEasyLoading(); + state.loadingTimer ??= Timer.periodic(Duration(milliseconds: 100), (timer) { + if (!EasyLoading.isShow) { + EasyLoading.show(); + } + }); + state.isLoading.value = true; // 添加15秒超时检查 Future.delayed(const Duration(seconds: 15), () { @@ -232,9 +248,14 @@ class ConfiguringWifiLogic extends BaseGetXController { EasyLoading.dismiss(); state.isLoading.value = false; state.sureBtnState.value = 0; + if (state.loadingTimer != null) { + state.loadingTimer!.cancel(); + state.loadingTimer = null; + } showToast('配网失败'.tr); } }); + AppLog.log('发送方法执行完毕${EasyLoading.isShow}'); } // 获取设备状态 @@ -358,9 +379,24 @@ class ConfiguringWifiLogic extends BaseGetXController { //失败 dismissEasyLoading(); showToast('配网失败'.tr); + if (state.loadingTimer != null) { + state.loadingTimer!.cancel(); + state.loadingTimer = null; + } break; } } void _replyStatusInfo(reply) {} + + @override + void dispose() { + // TODO: implement dispose + super.dispose(); + state.isLoading.value = false; + if (state.loadingTimer != null) { + state.loadingTimer!.cancel(); + state.loadingTimer = null; + } + } } diff --git a/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_state.dart b/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_state.dart index 062ebf51..1a090780 100755 --- a/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_state.dart +++ b/lib/main/lockDetail/lockSet/configuringWifi/configuringWifi/configuringWifi_state.dart @@ -1,4 +1,6 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -30,4 +32,5 @@ class ConfiguringWifiState{ String getGatewayConfigurationStr = ''; RxBool isLoading=false.obs; + Timer? loadingTimer; } \ No newline at end of file diff --git a/lib/main/lockDetail/lockSet/configuringWifi/wifiList/wifiList_page.dart b/lib/main/lockDetail/lockSet/configuringWifi/wifiList/wifiList_page.dart index bd976e9d..02231a71 100755 --- a/lib/main/lockDetail/lockSet/configuringWifi/wifiList/wifiList_page.dart +++ b/lib/main/lockDetail/lockSet/configuringWifi/wifiList/wifiList_page.dart @@ -78,10 +78,10 @@ class _WifiListPageState extends State { borderRadius: 20.w, padding: EdgeInsets.only(top: 25.w, bottom: 25.w), onClick: () { - Get.toNamed(Routers.configuringWifiPage, - arguments: { - 'lockSetInfoData': state.lockSetInfoData.value - }); + Get.toNamed(Routers.configuringWifiPage, arguments: { + 'lockSetInfoData': state.lockSetInfoData.value, + 'pageName': state.pageName.value, + }); }), SizedBox( height: 64.h, diff --git a/lib/mine/addLock/saveLock/saveLock_logic.dart b/lib/mine/addLock/saveLock/saveLock_logic.dart index ce1cc799..42d4e6de 100755 --- a/lib/mine/addLock/saveLock/saveLock_logic.dart +++ b/lib/mine/addLock/saveLock/saveLock_logic.dart @@ -491,25 +491,12 @@ class SaveLockLogic extends BaseGetXController { // 查询锁设置信息 final LockSetInfoEntity entity = - await ApiRepository.to.getLockSettingInfoDataIsNotLoadingIcon( + await ApiRepository.to.getLockSettingInfoData( lockId: state.lockId.toString(), ); if (entity.errorCode!.codeIsSuccessful) { state.lockSetInfoData.value = entity.data!; if (state.lockSetInfoData.value.lockFeature?.wifi == 1) { - // await Future.delayed(const Duration(seconds: 1), () {c - // Get.close(state.isFromMap == 1 - // ? (CommonDataManage().seletLockType == 0 ? 4 : 5) - // : (CommonDataManage().seletLockType == 0 ? 5 : 6)); - // }); - // //刚刚配对完,需要对开锁页锁死 2 秒 - // await Future.delayed(const Duration(milliseconds: 200), () { - // if (Get.isRegistered()) { - // Get.find() - // .functionBlocker - // .countdownProhibited(duration: const Duration(seconds: 2)); - // } - // }); // 如果是wifi锁,需要配置WIFI Get.toNamed(Routers.wifiListPage, arguments: { 'lockSetInfoData': state.lockSetInfoData.value, diff --git a/lib/network/api_provider.dart b/lib/network/api_provider.dart index 2139c1af..99a05695 100755 --- a/lib/network/api_provider.dart +++ b/lib/network/api_provider.dart @@ -1811,6 +1811,9 @@ class ApiProvider extends BaseProvider { Future getGatewayConfiguration(int timeout) => post(getGatewayConfigURL.toUrl, jsonEncode({}), timeout: timeout); + Future getGatewayConfigurationNotLoading(int timeout) => + post(getGatewayConfigURL.toUrl, jsonEncode({}), timeout: timeout,isUnShowLoading: true); + Future gatewayConnectionLockListLoadData( int gatewayId, int timeout) => post(gatewayListByLockURL.toUrl, jsonEncode({'gatewayId': gatewayId}), diff --git a/lib/network/api_repository.dart b/lib/network/api_repository.dart index 6ef31dcd..1bb84daa 100755 --- a/lib/network/api_repository.dart +++ b/lib/network/api_repository.dart @@ -2010,6 +2010,14 @@ class ApiRepository { return GetGatewayConfigurationEntity.fromJson(res.body); } + // 获取网关配置 + Future getGatewayConfigurationNotLoading( + {required int timeout}) async { + final res = await apiProvider.getGatewayConfigurationNotLoading(timeout); + return GetGatewayConfigurationEntity.fromJson(res.body); + } + + // 移除坏锁 Future removeBrokenLockData( {required List lockIdList}) async { From 3be6f98358d7d4727d5fc7b68391fc276eb8f6e7 Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 26 Feb 2025 11:35:46 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix=EF=BC=9A=E6=94=B9=E4=B8=BA=E5=8F=AA?= =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=BD=93=E6=97=A5=E5=BE=AA=E7=8E=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lockDetail/lockSet/catEyeSet/videoSlot/videoSlot_page.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/main/lockDetail/lockSet/catEyeSet/videoSlot/videoSlot_page.dart b/lib/main/lockDetail/lockSet/catEyeSet/videoSlot/videoSlot_page.dart index 49aeb165..95ea40c3 100755 --- a/lib/main/lockDetail/lockSet/catEyeSet/videoSlot/videoSlot_page.dart +++ b/lib/main/lockDetail/lockSet/catEyeSet/videoSlot/videoSlot_page.dart @@ -197,7 +197,7 @@ class _VideoSlotPageState extends State { Obx(() => Text( isEndTime == false ? '${'当日'.tr}${state.startDate.value}' - : '${'次日'.tr}${state.endDate.value}', + : '${'当日'.tr}${state.endDate.value}', style: TextStyle( color: state.isCustom.value == true ? AppColors.blueTextTipsColor From d7ad32d3c29f0c62d774015e6e69b332903f7fe4 Mon Sep 17 00:00:00 2001 From: liyi Date: Wed, 26 Feb 2025 16:47:27 +0800 Subject: [PATCH 4/5] =?UTF-8?q?fix=EF=BC=9A=E5=A2=9E=E5=8A=A0=E8=A7=86?= =?UTF-8?q?=E9=A2=91=E6=97=A5=E5=BF=97=E4=B8=AD=E7=9A=84=E2=80=9C=E5=B7=B2?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E2=80=9D=E5=8A=9F=E8=83=BD=E5=92=8C=E2=80=9C?= =?UTF-8?q?=E4=BA=91=E5=AD=98=E2=80=9D=E5=BD=93=E4=B8=AD=E7=9A=84=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../editVideoLog/editVideoLog_logic.dart | 146 ++++++++++ .../editVideoLog/editVideoLog_page.dart | 179 ++++++++---- .../editVideoLog/editVideoLog_state.dart | 6 +- .../videoLog/videoLog/videoLog_logic.dart | 114 ++++++++ .../videoLog/videoLog/videoLog_page.dart | 263 +++++++++++++----- .../videoLog/videoLog/videoLog_state.dart | 1 + .../widget/full_screenImage_page.dart | 32 ++- pubspec.yaml | 2 +- 8 files changed, 612 insertions(+), 131 deletions(-) diff --git a/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_logic.dart b/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_logic.dart index 8ad36896..116b8819 100755 --- a/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_logic.dart +++ b/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_logic.dart @@ -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(); + Future deleteLockCloudStorageList() async { final VersionUndateEntity entity = await ApiRepository.to.deleteLockCloudStorageList( @@ -86,6 +92,146 @@ class EditVideoLogLogic extends BaseGetXController { } } + // 批量删除指定文件路径列表中的文件 + Future deleteDownloadsByPaths(List 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 logData = + Map.from(json.decode(content)); + + // 过滤出需要保留的文件 + final filteredLogData = {}; + + 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 recordDownloadTime(String filePath) async { + final appDocDir = await getApplicationDocumentsDirectory(); + final logFilePath = '${appDocDir.path}/download_log.json'; + + // 读取现有的日志文件 + Map logData = {}; + if (await File(logFilePath).exists()) { + final content = await File(logFilePath).readAsString(); + logData = Map.from(json.decode(content)); + } + + // 添加新的下载记录 + logData[filePath] = DateTime.now().millisecondsSinceEpoch; + + // 写入日志文件 + await File(logFilePath).writeAsString(json.encode(logData)); + } + +// 下载文件方法(支持视频和图片) + Future 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 _requestPermission() async { final status = await Permission.storage.request(); diff --git a/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_page.dart b/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_page.dart index 86faa5b8..c2475b4e 100755 --- a/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_page.dart +++ b/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_page.dart @@ -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 { Widget bottomBottomBtnWidget() { return SizedBox( width: 1.sw, - child: - Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - 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: [ + 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 _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: >{ @@ -240,11 +257,28 @@ class _EditVideoLogPageState extends State { } Future _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 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 { _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'); } } diff --git a/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_state.dart b/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_state.dart index e7ae301e..359e6069 100755 --- a/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_state.dart +++ b/lib/main/lockDetail/videoLog/editVideoLog/editVideoLog_state.dart @@ -12,9 +12,13 @@ class EditVideoLogState { if (map['lockId'] != null) { getLockId.value = map['lockId']; } + if (map['isNavLocal'] != null) { + isNavLocal.value = map['isNavLocal']; + } } RxList selectVideoLogList = [].obs; //选中的视频列表 RxBool isSelectAll = false.obs; - RxList videoLogList = [].obs; //全部的视频列表 + RxList videoLogList = [].obs; //全部的视频列表 RxInt getLockId = 0.obs; + var isNavLocal = false.obs; } diff --git a/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart b/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart index b19264a5..e90c9793 100755 --- a/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart +++ b/lib/main/lockDetail/videoLog/videoLog/videoLog_logic.dart @@ -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 listDownloadedVideos() async { + Directory appDocDir = await getApplicationDocumentsDirectory(); + Directory downloadsDir = Directory('${appDocDir.path}/downloads'); + + if (await downloadsDir.exists()) { + List 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> groupDownloadsByDay() async { + final appDocDir = await getApplicationDocumentsDirectory(); + final logFilePath = '${appDocDir.path}/download_log.json'; + + // 初始化结果映射 + Map> groupedDownloads = {}; + + if (await File(logFilePath).exists()) { + final content = await File(logFilePath).readAsString(); + final logData = Map.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 = []; + groupedDownloads.forEach((dateKey, recordList) { + cloudStorageData.add( + CloudStorageData(date: dateKey, recordList: recordList), + ); + }); + state.lockVideoList.value = cloudStorageData; + return cloudStorageData; + } + + // 获取文件的下载时间 + Future 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.from(json.decode(content)); + return logData[filePath]; + } + return null; + } + + // 清空下载目录和日志文件 + Future 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(); diff --git a/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart b/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart index 7911a215..97bd6ab6 100755 --- a/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart +++ b/lib/main/lockDetail/videoLog/videoLog/videoLog_page.dart @@ -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 { void initState() { // TODO: implement initState super.initState(); + logic.groupDownloadsByDay(); } @override @@ -50,53 +53,94 @@ class _VideoLogPageState extends State { 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: [ - 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: [ + Container( + margin: EdgeInsets.only( + left: 20.w, top: 15.w, bottom: 15.w), + child: Row(children: [ + 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: [ + Container( margin: EdgeInsets.only( left: 20.w, top: 15.w, bottom: 15.w), - child: Row(children: [ - 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: [ - Container( - margin: EdgeInsets.only( - left: 20.w, top: 15.w, bottom: 15.w), - child: Row(children: [ - Text('2023.10.2$index', - style: TextStyle(fontSize: 20.sp)), - ])), - mainListView(index, CloudStorageData()), - ], - ); - }) - : NoData())), + child: Row( + children: [ + 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 { onPressed: () { setState(() { state.isNavLocal.value = false; + state.lockVideoList.clear(); + // logic.clearDownloads(); }); }, child: Obx(() => Text('云存'.tr, @@ -122,21 +168,27 @@ class _VideoLogPageState extends State { 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 { iconSize: 30, color: Colors.black54, onPressed: () { - Get.toNamed(Routers.editVideoLogPage, arguments: { - 'videoDataList': state.videoLogList.value, - 'lockId': state.getLockId.value - }); + if (state.isNavLocal.value) { + Get.toNamed(Routers.editVideoLogPage, + arguments: { + 'videoDataList': state.lockVideoList.value, + 'lockId': state.getLockId.value, + 'isNavLocal': state.isNavLocal.value + }); + } else { + Get.toNamed(Routers.editVideoLogPage, + arguments: { + 'videoDataList': state.videoLogList.value, + 'lockId': state.getLockId.value, + 'isNavLocal': state.isNavLocal.value + }); + } }, ) // TextButton( @@ -286,6 +349,28 @@ class _VideoLogPageState extends State { ); } + 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 { ), 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 { _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'); } } diff --git a/lib/main/lockDetail/videoLog/videoLog/videoLog_state.dart b/lib/main/lockDetail/videoLog/videoLog/videoLog_state.dart index b703686b..8b2a7960 100755 --- a/lib/main/lockDetail/videoLog/videoLog/videoLog_state.dart +++ b/lib/main/lockDetail/videoLog/videoLog/videoLog_state.dart @@ -8,6 +8,7 @@ class VideoLogState { var localList = []; var getLockId = 0.obs; var videoLogList = [].obs; + var lockVideoList = [].obs; var videoCoverList = [].obs; VideoLogState() { diff --git a/lib/main/lockDetail/videoLog/widget/full_screenImage_page.dart b/lib/main/lockDetail/videoLog/widget/full_screenImage_page.dart index f2da5bf9..1166b3de 100644 --- a/lib/main/lockDetail/videoLog/widget/full_screenImage_page.dart +++ b/lib/main/lockDetail/videoLog/widget/full_screenImage_page.dart @@ -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 动态选择图片加载方式 ), ), ), ), ); } -} \ No newline at end of file + +// 根据 imageUrl 判断并返回对应的 ImageProvider + ImageProvider _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'); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 7d0cc89b..feb2c0ee 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 From 97ab7d03b5da8ccc8fca66f257340b51dd7dba3b Mon Sep 17 00:00:00 2001 From: Liuyf Date: Wed, 26 Feb 2025 19:08:46 +0800 Subject: [PATCH 5/5] =?UTF-8?q?fix:=20=E4=BC=98=E5=8C=96=E4=B8=8A=E6=8A=A5?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E9=98=B2=E6=AD=A2=E4=B8=80=E6=A0=B7?= =?UTF-8?q?=E7=9A=84token=E9=87=8D=E5=A4=8D=E4=B8=8A=E6=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/network/request_interceptor.dart | 5 ++ lib/tools/bindings/app_binding.dart | 18 +++-- lib/tools/debounce_throttle_tool.dart | 23 ++++++ lib/tools/push/xs_jPhush.dart | 101 ++++++++++++++------------ lib/tools/storage.dart | 3 +- 5 files changed, 99 insertions(+), 51 deletions(-) create mode 100644 lib/tools/debounce_throttle_tool.dart diff --git a/lib/network/request_interceptor.dart b/lib/network/request_interceptor.dart index 46f43aea..c6c87ba2 100755 --- a/lib/network/request_interceptor.dart +++ b/lib/network/request_interceptor.dart @@ -28,5 +28,10 @@ FutureOr requestInterceptor(Request request) async { xToken = LoginData.fromJson(jsonDecode(data)).accessToken; } request.headers['Authorization'] = "Bearer ${xToken ?? ''}"; + + final String? deviceID = await Storage.getString(appDeviceID); + if (deviceID != null && deviceID.isNotEmpty) { + request.headers['deviceID'] = deviceID; + } return request; } diff --git a/lib/tools/bindings/app_binding.dart b/lib/tools/bindings/app_binding.dart index f87b295b..82dd0b8e 100755 --- a/lib/tools/bindings/app_binding.dart +++ b/lib/tools/bindings/app_binding.dart @@ -1,12 +1,20 @@ - import 'package:get/get.dart'; - -import '../../network/api_provider.dart'; -import '../../network/api_repository.dart'; +import 'package:star_lock/tools/storage.dart'; +import 'package:uuid/uuid.dart'; class AppBindings extends Bindings { @override void dependencies() { + initDeviceId(); } -} \ No newline at end of file + Future initDeviceId() async { + final String? deviceID = await Storage.getString(appDeviceID); + final bool isNullOrBlank = GetUtils.isNullOrBlank(deviceID) ?? true; + if (isNullOrBlank) { + final String uuidV4 = const Uuid().v4(); + print('initDeviceId UUID:v4: $uuidV4'); + await Storage.setString(appDeviceID, uuidV4); + } + } +} diff --git a/lib/tools/debounce_throttle_tool.dart b/lib/tools/debounce_throttle_tool.dart new file mode 100644 index 00000000..f42f0d12 --- /dev/null +++ b/lib/tools/debounce_throttle_tool.dart @@ -0,0 +1,23 @@ +import 'dart:async'; + +import 'package:flutter/widgets.dart'; + +class DebounceThrottleTool { + DebounceThrottleTool(this._debounceTime, this._callback); + + Timer? _timer; + final Duration _debounceTime; + final void Function(T param) _callback; + + void trigger(T param) { + _timer?.cancel(); + _timer = Timer(_debounceTime, () { + _callback(param); + _timer = null; + }); + } + + void cancel() { + _timer?.cancel(); + } +} diff --git a/lib/tools/push/xs_jPhush.dart b/lib/tools/push/xs_jPhush.dart index 701fee30..7639dd86 100755 --- a/lib/tools/push/xs_jPhush.dart +++ b/lib/tools/push/xs_jPhush.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:ffi'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -9,6 +10,7 @@ import 'package:star_lock/flavors.dart'; import 'package:star_lock/mine/minePersonInfo/minePersonInfoEditAccount/minePersonInfoEditAccount/mineUnbindPhoneOrEmail_entity.dart'; import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/tools/baseGetXController.dart'; +import 'package:star_lock/tools/debounce_throttle_tool.dart'; import 'package:star_lock/tools/push/message_management.dart'; import 'package:star_lock/tools/push/notification_service.dart'; import 'package:star_lock/tools/storage.dart'; @@ -28,31 +30,32 @@ class XSJPushProvider { 9: 'jiguang' }; final JPush jpush = JPush(); - Completer>? _jpushRegistrationIdCompleter; - Completer>? _vendorTokenCompleter; + DebounceThrottleTool? _debounceThrottleTool; Future resetJPushService() async { debugPrint("resetJPushService start"); - jpush.setup( - appKey: '', - channel: 'flutter_channel', - production: F.isProductionEnv, - debug: !F.isProductionEnv, - ); + for (final MapEntry entry in channelTypeMapping.entries) { + await Storage.removeData('old_${entry.value}'); + } + debugPrint("resetJPushService end"); } // appKey: 251fc8074820d122b6de58d2--鑫泓佳AppKey // appKey: 7ff37d174c1a568a89e98dad--sky Future initJPushService() async { debugPrint("initJPushService start"); - _jpushRegistrationIdCompleter = Completer>(); - _vendorTokenCompleter = Completer>(); final String? data = await Storage.getString(saveUserLoginData); if (data == null || data.isEmpty) { AppLog.log('No user data found.'); return; } + _debounceThrottleTool = DebounceThrottleTool( + const Duration(milliseconds: 1500), + (dynamic param) { + bindPushChannels(); + }, + ); AppLog.log('jPushKey ${F.jPushKey}'); // final String? bundleIdentifier = // await NativeInteractionTool().getBundleIdentifier(); @@ -86,35 +89,20 @@ class XSJPushProvider { case CMD_GET_REGISTRATION_ID: final bool isNullOrBlank = GetUtils.isNullOrBlank(data['message']) ?? true; - if (!(_jpushRegistrationIdCompleter?.isCompleted ?? true) && - !isNullOrBlank) { - await Storage.setString(pushDeviceID, data['message']); - AppLog.log('flutter get registration id : ${data['message']}'); - _jpushRegistrationIdCompleter?.complete({ - 'channel': 'jiguang', - 'channelToken': data['message'] - }); - final String? channel2TokenStr = - await Storage.getString(vendorPushChannelInfo); - if (Platform.isAndroid && - !(_vendorTokenCompleter?.isCompleted ?? true) && - channel2TokenStr != null) { - _vendorTokenCompleter?.complete(jsonDecode(channel2TokenStr)); - } + if (!isNullOrBlank) { + AppLog.log( + 'flutter get jiguang registration id : ${data['message']}'); + await Storage.setString('jiguang', data['message']); + _debounceThrottleTool?.trigger(data['message']); } break; case CMD_GET_TOKEN: final bool isNullOrBlank = GetUtils.isNullOrBlank(data['token']) ?? true; - if (!(_vendorTokenCompleter?.isCompleted ?? true) && - !isNullOrBlank) { - final Map channel2Token = { - 'channel': channelTypeMapping[data['platform']], - 'channelToken': data['token'] - }; + if (!isNullOrBlank) { await Storage.setString( - vendorPushChannelInfo, jsonEncode(channel2Token)); - _vendorTokenCompleter?.complete(channel2Token); + channelTypeMapping[data['platform']] ?? '', data['token']); + _debounceThrottleTool?.trigger(data['token']); } break; } @@ -147,31 +135,54 @@ class XSJPushProvider { return Future.value(); }, ); - bindPushChannels(); } // jpush 统一推送通道设备绑定 Future bindPushChannels() async { try { - if (_jpushRegistrationIdCompleter == null || - _vendorTokenCompleter == null) { + debugPrint("BindPushChannels start"); + bool needReBindPushToken = false; + final Map newVendorsPushToken = {}; + final Map oldVendorsPushToken = {}; + for (final String channel in channelTypeMapping.values) { + debugPrint("BindPushChannels channel $channel"); + final String? newVendorToken = await Storage.getString(channel); + debugPrint("BindPushChannels newVendorToken $newVendorToken"); + newVendorsPushToken[channel] = newVendorToken ?? ''; + final String? oldVendorToken = await Storage.getString('old_$channel'); + debugPrint("BindPushChannels oldVendorToken $oldVendorToken"); + oldVendorsPushToken['old_$channel'] = oldVendorToken ?? ''; + if (newVendorToken != oldVendorToken) { + needReBindPushToken = true; + } + } + if (!needReBindPushToken) { + AppLog.log('vendorToken 未变动,无需重新绑定'); return; } - debugPrint("await PushChannels start"); - final List> channels = await Future.wait([ - _jpushRegistrationIdCompleter!.future, - _vendorTokenCompleter!.future - ]); - final String? registrationId = await Storage.getString(pushDeviceID); - debugPrint("await PushChannels end"); + + final List> channels = newVendorsPushToken.entries + .where((entry) => !(GetUtils.isNullOrBlank(entry.value) ?? true)) + .map((entry) => { + 'channel': entry.key, + 'channelToken': entry.value + }) + .toList(); + debugPrint("BindPushChannels ${channels}"); + final String? deviceID = await Storage.getString(appDeviceID); final MineUnbindPhoneOrEmailEntity entity = - await ApiRepository.to.pushBindChannels(registrationId!, channels); + await ApiRepository.to.pushBindChannels(deviceID!, channels); if (entity.errorCode!.codeIsSuccessful) { - AppLog.log('绑定成功'); + AppLog.log('vendorToken绑定成功,准备更新本地缓存'); + await Future.wait(channels.map((Map entry) => + Storage.setString( + 'old_${entry['channel']}', entry['channelToken']))); + AppLog.log('vendorToken绑定成功,更新本地缓存完成'); } else { AppLog.log('绑定失败'); } } catch (e) { + AppLog.log('vendorToken绑定失败,下次重启应用重新绑定'); AppLog.log('Error binding device ID: $e'); } } diff --git a/lib/tools/storage.dart b/lib/tools/storage.dart index 5132a39c..2ed936aa 100755 --- a/lib/tools/storage.dart +++ b/lib/tools/storage.dart @@ -24,7 +24,8 @@ const String isAgreeCamera = 'isAgreeCamera'; //是否同意获取相机/相册 const String isShowUpdateVersion = 'isShowUpdateVersion'; //是否更新弹窗 const String saveLockAlias = 'saveLockAlias'; //锁别名 -const String pushDeviceID = 'pushDeviceID'; //推送设备ID +const String appDeviceID = 'appDeviceID'; //推送设备ID +const String pushDeviceID = 'pushDeviceID'; //Jpush推送设备ID const String vendorPushChannelInfo = 'pushChannelInfo'; //推送设备ID const String saveIsVip = 'saveIsVip'; //是否是VIP