From 2619288234dbac2f6f193e70de605ce62e081b32 Mon Sep 17 00:00:00 2001 From: Xie Jing Date: Mon, 15 Dec 2025 18:05:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=9B=B4=E6=8E=A5=E4=BB=8E?= =?UTF-8?q?=E5=AE=89=E5=8D=93=E5=8E=9F=E7=94=9F=E8=8E=B7=E5=8F=96=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E6=8E=A8=E9=80=81=E4=BA=8B=E4=BB=B6=E5=92=8C=E5=8F=82?= =?UTF-8?q?=E6=95=B0=EF=BC=8C=E4=BC=A0=E5=85=A5flutter=E5=A4=84=E7=90=86?= =?UTF-8?q?=E6=8E=A8=E9=80=81=E3=80=82=20=E5=AE=9E=E7=8E=B0=E4=BA=86?= =?UTF-8?q?=E8=BF=9C=E7=A8=8B=E5=BC=80=E9=94=81=E8=AF=B7=E6=B1=82=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E7=9A=84ui=E5=92=8C=E5=BC=80=E9=94=81=20=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BA=86vivo=E5=92=8C=E5=B0=8F=E7=B1=B3=E6=89=8B?= =?UTF-8?q?=E6=9C=BA=E6=94=AF=E6=8C=81=E4=B8=8A=E8=BF=B0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=EF=BC=8C=E4=B8=89=E6=98=9F=E3=80=81=E8=8D=A3=E8=80=80=E4=B8=8D?= =?UTF-8?q?=E6=94=AF=E6=8C=81=EF=BC=8C=E5=85=B6=E4=BB=96=E6=9C=AA=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E3=80=82=20=E4=BF=AE=E6=94=B9=E4=BA=86=E6=89=93?= =?UTF-8?q?=E5=8D=B0=E8=B0=83=E8=AF=95=E4=BF=A1=E6=81=AF=EF=BC=8C=E5=90=8E?= =?UTF-8?q?=E7=BB=AD=E9=9C=80=E8=A6=81=E6=81=A2=E5=A4=8D=E5=8E=9F=E7=8A=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/com/skychip/lock/MainActivity.kt | 187 +++++++++++++- .../lockDetail/lockDetail_logic.dart | 17 +- .../lockDetail/lockDetail_page.dart | 237 ++++++------------ .../lockDetail/lockDetail_state.dart | 4 +- lib/tools/eventBusEventManage.dart | 3 +- lib/tools/push/message_management.dart | 16 +- lib/tools/remote_unlock_coordinator.dart | 80 ++++-- 7 files changed, 339 insertions(+), 205 deletions(-) diff --git a/android/app/src/main/kotlin/com/skychip/lock/MainActivity.kt b/android/app/src/main/kotlin/com/skychip/lock/MainActivity.kt index 2ec29f6a..d49415a7 100755 --- a/android/app/src/main/kotlin/com/skychip/lock/MainActivity.kt +++ b/android/app/src/main/kotlin/com/skychip/lock/MainActivity.kt @@ -14,6 +14,7 @@ import android.bluetooth.BluetoothAdapter; import androidx.core.content.FileProvider import java.io.File import org.json.JSONObject +import org.json.JSONArray private fun flagsToString(flags: Int): String { val flagsList = mutableListOf() @@ -228,26 +229,190 @@ object PushCache { /// 推送数据处理 object PushIntentHandler { + private const val PARSE_TAG = "PUSH_PARSE" + + private val CANONICAL_KEY_MAP = mapOf( + "eventno" to "EventNo", + "imageurl" to "ImageUrl", + "lockid" to "LockId", + "operatedate" to "OperateDate", + "locktype" to "LockType" + ) + + private val LEGACY_KEY_MAP = mapOf( + "eventno" to "eventNo", + "imageurl" to "imageUrl", + "lockid" to "lockId", + "operatedate" to "operateDate", + "locktype" to "lockType" + ) + + private val EMBEDDED_JSON_PATTERN = Regex("\\$\\{(.*?)\\}\\$") + + private fun looksLikeJsonObject(s: String): Boolean = s.isNotEmpty() && s.trim().let { it.startsWith("{") && it.endsWith("}") } + private fun looksLikeJsonArray(s: String): Boolean = s.isNotEmpty() && s.trim().let { it.startsWith("[") && it.endsWith("]") } + + private fun extractEmbeddedJson(text: String): Map { + val results = HashMap() + val matches = EMBEDDED_JSON_PATTERN.findAll(text) + for (match in matches) { + val content = match.groupValues[1] + // 构造合法的 JSON:将提取内容包裹在 {} 中,并处理转义字符 + // 注意:小米推送的格式看起来是 "key":value,且包含转义的引号 \" + // 我们尝试将其还原为标准 JSON 字符串 + val jsonString = "{$content}" + .replace("\\\"", "\"") + .replace("\\/", "/") + + try { + val jsonObject = JSONObject(jsonString) + val keys = jsonObject.keys() + while (keys.hasNext()) { + val key = keys.next() + val value = jsonObject.opt(key) + if (value != null) { + results[key.lowercase()] = value.toString() + } + } + } catch (e: Exception) { + Log.w(PARSE_TAG, "尝试解析嵌入 JSON 失败: $jsonString", e) + } + } + return results + } + + private fun flattenExtras(extras: android.os.Bundle): Map { + val out = HashMap(8) + val stack = ArrayDeque() + stack.add(extras) + + while (stack.isNotEmpty()) { + when (val item = stack.removeLast()) { + is android.os.Bundle -> { + for (key in item.keySet()) { + val v = item.get(key) + if (v == null) continue + when (v) { + is android.os.Bundle -> stack.add(v) + is Map<*, *> -> stack.add(v) + is JSONObject -> stack.add(v) + is JSONArray -> stack.add(v) + is String -> { + val s = v + val ls = key.lowercase() + if (looksLikeJsonObject(s)) { + try { stack.add(JSONObject(s)) } catch (_: Exception) {} + } else if (looksLikeJsonArray(s)) { + try { stack.add(JSONArray(s)) } catch (_: Exception) {} + } + out[ls] = s + } + else -> { + out[key.lowercase()] = v.toString() + } + } + } + } + is Map<*, *> -> { + for ((mk, mv) in item.entries) { + val k = mk?.toString() ?: continue + if (mv == null) continue + when (mv) { + is android.os.Bundle -> stack.add(mv) + is Map<*, *> -> stack.add(mv) + is JSONObject -> stack.add(mv) + is JSONArray -> stack.add(mv) + is String -> { + val s = mv + val ls = k.lowercase() + if (looksLikeJsonObject(s)) { + try { stack.add(JSONObject(s)) } catch (_: Exception) {} + } else if (looksLikeJsonArray(s)) { + try { stack.add(JSONArray(s)) } catch (_: Exception) {} + } + out[ls] = s + } + else -> { + out[k.lowercase()] = mv.toString() + } + } + } + } + is JSONObject -> { + val obj = item + val keys = obj.keys() + while (keys.hasNext()) { + val k = keys.next().toString() + val v = obj.opt(k) + if (v == null) continue + when (v) { + is JSONObject -> stack.add(v) + is JSONArray -> stack.add(v) + is String -> out[k.lowercase()] = v + else -> out[k.lowercase()] = v.toString() + } + } + } + is JSONArray -> { + val arr = item + for (i in 0 until arr.length()) { + val e = arr.opt(i) + when (e) { + is JSONObject -> stack.add(e) + is JSONArray -> stack.add(e) + } + } + } + } + } + return out + } + fun handlePushIntent(context: Context, intent: Intent?): Map? { if (intent == null) return null intent.debugPrint() Log.i("PUSH_INTENT", "原始推送数据:${intent.extras}") - val map = mutableMapOf() val extras = intent.extras ?: return null - val targetKeys = arrayOf("lockType", "eventNo", "lockId", - "imageUrl", "operateDate") - for (key in targetKeys) { - val value = extras.getString(key) - if (value != null) { - map[key] = value - Log.i("PushIntentHandler", "key=$key, value=$value") + Log.i(PARSE_TAG, "开始解析 extras") + val flat = flattenExtras(extras).toMutableMap() + + // 二次扫描:尝试从所有字符串值中提取嵌入的 JSON(如小米推送格式) + val embeddedFields = HashMap() + for (value in flat.values) { + val extracted = extractEmbeddedJson(value) + if (extracted.isNotEmpty()) { + embeddedFields.putAll(extracted) + } + } + if (embeddedFields.isNotEmpty()) { + Log.i(PARSE_TAG, "提取到嵌入字段:$embeddedFields") + flat.putAll(embeddedFields) + } + + Log.i(PARSE_TAG, "扁平化后:${JSONObject(flat as Map<*, *>)}") + + val legacyMap = mutableMapOf() + for ((lowerKey, legacyKey) in LEGACY_KEY_MAP) { + val v = flat[lowerKey] + if (v != null) { + legacyMap[legacyKey] = v + Log.i("PushIntentHandler", "key=$legacyKey, value=$v") } } - // 保存到 SharedPreferences - PushCache.savePush(context, map) - return map + val canonicalMap = mutableMapOf() + for ((lowerKey, canonicalKey) in CANONICAL_KEY_MAP) { + val v = flat[lowerKey] + if (v != null) { + canonicalMap[canonicalKey] = v + } + } + val finalJson = JSONObject(canonicalMap as Map<*, *>) + Log.i(PARSE_TAG, "最终生成 JSON:$finalJson") + + PushCache.savePush(context, legacyMap) + return legacyMap } } diff --git a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart index 732e4e47..7561cb08 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart @@ -117,10 +117,15 @@ class LockDetailLogic extends BaseGetXController { void initRemoteUnlockRequestListener() { // 监听推送消息触发的远程解锁请求 eventBus.on().listen((RemoteUnlockRequestEvent event) { - // 只处理与当前锁相关的远程解锁请求 if (event.lockId == state.keyInfos.value.lockId) { - print('触发!!'); - showPushRemoteUnlockRequest(timeoutSeconds: event.timeoutSeconds ?? 60); + // 去重:如果该operateDate已处理过,则忽略 + if (event.operateDate != 0 && event.operateDate == state.handledPushOperateDate) { + return; + } + state.currentPushOperateDate = event.operateDate; + SchedulerBinding.instance.addPostFrameCallback((_) { + showPushRemoteUnlockRequest(timeoutSeconds: event.timeoutSeconds ?? 60); + }); } }); } @@ -147,6 +152,8 @@ class LockDetailLogic extends BaseGetXController { state.pushRemoteUnlockRequestTimer?.cancel(); state.showPushRemoteUnlockRequest.value = false; state.pushRemoteUnlockCountdownSeconds.value = 60; + state.handledPushOperateDate = state.currentPushOperateDate; + state.currentPushOperateDate = 0; remoteOpenLock(); } @@ -155,6 +162,8 @@ class LockDetailLogic extends BaseGetXController { state.pushRemoteUnlockRequestTimer?.cancel(); state.showPushRemoteUnlockRequest.value = false; state.pushRemoteUnlockCountdownSeconds.value = 60; + state.handledPushOperateDate = state.currentPushOperateDate; + state.currentPushOperateDate = 0; } // 开门数据解析 @@ -167,7 +176,7 @@ class LockDetailLogic extends BaseGetXController { final String getMobile = (await Storage.getMobile())!; ApmHelper.instance.trackEvent('open_lock', { 'lock_name': state.keyInfos.value.lockName!, - 'account': getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!, + 'account': getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!, 'date': DateTool().getNowDateWithType(1), 'open_lock_result': '${reply.data}', }); diff --git a/lib/main/lockDetail/lockDetail/lockDetail_page.dart b/lib/main/lockDetail/lockDetail/lockDetail_page.dart index b897ac78..49ed39fa 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_page.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_page.dart @@ -30,6 +30,7 @@ import '../../../common/XSConstantMacro/XSConstantMacro.dart'; import '../../../tools/appRouteObserver.dart'; import '../../../tools/dateTool.dart'; import '../../../tools/eventBusEventManage.dart'; +import '../../../tools/remote_unlock_coordinator.dart'; import '../../lockMian/entity/lockListInfo_entity.dart'; import 'lockDetail_logic.dart'; @@ -96,13 +97,7 @@ class _LockDetailPageState extends State with TickerProviderStat @override Widget build(BuildContext context) { - return Obx(() { - final bool overlayVisible = state.showPushRemoteUnlockRequest.value; - return PopScope( - canPop: !overlayVisible, - child: F.sw(skyCall: skWidget, xhjCall: xhjWidget), - ); - }); + return F.sw(skyCall: skWidget, xhjCall: xhjWidget); } //鑫泓佳布局 @@ -529,16 +524,7 @@ class _LockDetailPageState extends State with TickerProviderStat child: _unlockSuccessWidget()), )) , - // 添加推送远程解锁请求界面 - Obx(() => Visibility( - visible: state.showPushRemoteUnlockRequest.value, - child: Container( - width: 1.sw, - height: 1.sh - ScreenUtil().statusBarHeight * 2, - color: Colors.black.withOpacity(0.3), - child: _pushRemoteUnlockRequestWidget(), - ), - )) + SizedBox.shrink() ]), ], ); @@ -649,69 +635,68 @@ class _LockDetailPageState extends State with TickerProviderStat child: Stack( children: [ Center( - child: GestureDetector( - onTap: () { - if (state.openDoorBtnisUneable.value == true) { - logic.functionBlocker.block(isNeedRealNameAuthThenOpenLock); - } - }, - onLongPressStart: (LongPressStartDetails details) { - if (state.openDoorBtnisUneable.value == true) { - void callback() { - setState(startUnLock); + child: GestureDetector( + onTap: () { + if (state.openDoorBtnisUneable.value == true) { + logic.functionBlocker.block(isNeedRealNameAuthThenOpenLock); } - - logic.functionBlocker.block(callback); - } - }, - child: Stack( - children: [ - FlavorsImg( - child: Image.asset( - state.openDoorBtnisUneable.value == false - ? 'images/main/icon_main_openLockBtn_grey.png' - : (state.isOpenPassageMode.value == 1 - ? 'images/main/icon_main_normallyOpenMode_center.png' - : 'images/main/icon_main_openLockBtn_center.png'), - width: 330.w, - height: 330.w, - // color: AppColors.primaryTopColor, - ), - ), - if (state.openDoorBtnisUneable.value == false) - Positioned( - child: FlavorsImg( - child: Image.asset( - 'images/main/icon_main_openLockBtn_grey.png', - width: 330.w, - height: 330.w, - ), + }, + onLongPressStart: (LongPressStartDetails details) { + if (state.openDoorBtnisUneable.value == true) { + void callback() { + setState(startUnLock); + } + logic.functionBlocker.block(callback); + } + }, + child: Stack( + children: [ + FlavorsImg( + child: Image.asset( + state.openDoorBtnisUneable.value == false + ? 'images/main/icon_main_openLockBtn_grey.png' + : (state.isOpenPassageMode.value == 1 + ? 'images/main/icon_main_normallyOpenMode_center.png' + : 'images/main/icon_main_openLockBtn_center.png'), + width: 330.w, + height: 330.w, ), - ) - else - state.openLockBtnState.value == 1 - ? buildRotationTransition( + ), + if (state.openDoorBtnisUneable.value == false) + Positioned( + child: FlavorsImg( + child: Image.asset( + 'images/main/icon_main_openLockBtn_grey.png', width: 330.w, height: 330.w, - ) - : Positioned( - child: FlavorsImg( - child: Image.asset( - state.isOpenPassageMode.value == 1 - ? 'images/main/icon_main_normallyOpenMode_circle.png' - : 'images/main/icon_main_openLockBtn_circle.png', + ), + ), + ) + else + state.openLockBtnState.value == 1 + ? buildRotationTransition( width: 330.w, height: 330.w, - ), - )), - ], + ) + : Positioned( + child: FlavorsImg( + child: Image.asset( + state.isOpenPassageMode.value == 1 + ? 'images/main/icon_main_normallyOpenMode_circle.png' + : 'images/main/icon_main_openLockBtn_circle.png', + width: 330.w, + height: 330.w, + ), + )), + ], + ), ), - )), + ), Positioned( right: 90.w, bottom: 1, child: Obx(() => Visibility( - visible: state.keyInfos.value.lockSetting!.remoteUnlock == 1, + visible: state.keyInfos.value.lockSetting!.remoteUnlock == 1 && !state.showPushRemoteUnlockRequest.value, child: GestureDetector( onTap: () { ShowCupertinoAlertView().isToRemoteUnLockAlert(remoteUnlockAction: () { @@ -738,17 +723,20 @@ class _LockDetailPageState extends State with TickerProviderStat SizedBox( height: 30.h, ), - Container( - padding: EdgeInsets.symmetric(horizontal: 20.w), - child: Center( - child: Text( - logic.getKeyStatusTextAndShow(), - maxLines: 2, - textAlign: TextAlign.center, - style: TextStyle(fontSize: 22.sp, color: AppColors.btnDisableColor, fontWeight: FontWeight.w500), - ), - ), - ), + Obx(() => Visibility( + visible: !state.showPushRemoteUnlockRequest.value, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 20.w), + child: Center( + child: Text( + logic.getKeyStatusTextAndShow(), + maxLines: 2, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 22.sp, color: AppColors.btnDisableColor, fontWeight: FontWeight.w500), + ), + ), + ), + )), SizedBox( height: 30.h, ), @@ -1345,80 +1333,6 @@ class _LockDetailPageState extends State with TickerProviderStat return formattedTime; } - // 推送消息触发的远程解锁请求界面 - Widget _pushRemoteUnlockRequestWidget() { - return Center( - child: Container( - width: 0.9.sw, - margin: EdgeInsets.symmetric(horizontal: 0.05.sw), - padding: EdgeInsets.symmetric(vertical: 30.h, horizontal: 20.w), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(16.r), - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text('远程开锁请求'.tr, style: TextStyle(fontSize: 28.sp, color: AppColors.blackColor)), - SizedBox(height: 20.h), - // 显示倒计时的虚线圆环动画 - SizedBox( - height: 200.r, - child: Stack( - alignment: Alignment.center, - children: [ - _pushRemoteUnlockCountdownAnimation(), - Obx(() => Text( - '${state.pushRemoteUnlockCountdownSeconds.value} s', - style: TextStyle(fontSize: 40.sp, color: AppColors.mainColor, fontWeight: FontWeight.w600), - )), - ], - ), - ), - SizedBox(height: 20.h), - // 同意和拒绝按钮 - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - GestureDetector( - onTap: () { - logic.rejectPushRemoteUnlockRequest(); - }, - child: Container( - width: 140.w, - height: 48.h, - alignment: Alignment.center, - decoration: BoxDecoration( - color: Colors.redAccent, - borderRadius: BorderRadius.circular(8.r), - ), - child: Text('拒绝'.tr, style: TextStyle(color: Colors.white, fontSize: 22.sp)), - ), - ), - SizedBox(width: 30.w), - GestureDetector( - onTap: () { - logic.acceptPushRemoteUnlockRequest(); - }, - child: Container( - width: 140.w, - height: 48.h, - alignment: Alignment.center, - decoration: BoxDecoration( - color: Colors.green,//AppColors.mainColor, - borderRadius: BorderRadius.circular(8.r), - ), - child: Text('同意'.tr, style: TextStyle(color: Colors.white, fontSize: 22.sp)), - ), - ), - ], - ) - ], - ), - ), - ); - } - // 推送远程解锁倒计时动画 Widget _pushRemoteUnlockCountdownAnimation() { return Obx(() { @@ -1432,6 +1346,16 @@ class _LockDetailPageState extends State with TickerProviderStat }); } + Widget _pushRemoteUnlockCountdownAnimationLarge() { + return Obx(() { + final double progress = state.pushRemoteUnlockCountdownSeconds.value / 60.0; + return CustomPaint( + size: Size(330.w, 330.w), + painter: DottedCirclePainter(progress: progress), + ); + }); + } + //如果需要实名认证,需认证完成,方可开锁 Future isNeedRealNameAuthThenOpenLock() async { final bool isNetWork = await logic.isConnected() ?? false; @@ -1546,6 +1470,11 @@ class _LockDetailPageState extends State with TickerProviderStat logic.cancelBlueConnetctToastTimer(); BlueManage().disconnect(); state.openLockBtnState.value = 0; + state.pushRemoteUnlockRequestTimer?.cancel(); + state.showPushRemoteUnlockRequest.value = false; + state.pushRemoteUnlockCountdownSeconds.value = 60; + state.handledPushOperateDate = state.currentPushOperateDate; + state.currentPushOperateDate = 0; } /// 从下级返回 当前界面即将出现 diff --git a/lib/main/lockDetail/lockDetail/lockDetail_state.dart b/lib/main/lockDetail/lockDetail/lockDetail_state.dart index 60066f6a..da67a7b3 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_state.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_state.dart @@ -85,4 +85,6 @@ class LockDetailState { RxBool showPushRemoteUnlockRequest = false.obs; // 是否显示推送远程开锁确认界面 RxInt pushRemoteUnlockCountdownSeconds = 60.obs; // 推送远程开锁倒计时秒数 Timer? pushRemoteUnlockRequestTimer; // 推送远程开锁倒计时定时器 -} \ No newline at end of file + int currentPushOperateDate = 0; // 当前推送请求操作时间戳 + int handledPushOperateDate = 0; // 已处理的推送请求操作时间戳,避免重复显示 +} diff --git a/lib/tools/eventBusEventManage.dart b/lib/tools/eventBusEventManage.dart index 63fcd59a..8a1395ec 100755 --- a/lib/tools/eventBusEventManage.dart +++ b/lib/tools/eventBusEventManage.dart @@ -88,10 +88,11 @@ class LockSetChangeSetRefreshLockDetailWithType { /// 远程开锁请求事件(门铃触发) class RemoteUnlockRequestEvent { - RemoteUnlockRequestEvent({required this.lockId, this.timeoutSeconds = 60}); + RemoteUnlockRequestEvent({required this.lockId, this.timeoutSeconds = 60, this.operateDate = 0}); int lockId; int timeoutSeconds; + int operateDate; } /// 推送原始数据事件 diff --git a/lib/tools/push/message_management.dart b/lib/tools/push/message_management.dart index e4d2cb5a..ed435a59 100644 --- a/lib/tools/push/message_management.dart +++ b/lib/tools/push/message_management.dart @@ -17,7 +17,7 @@ class MessageManagement { Map extra = {}; if (GetPlatform.isAndroid) { extra = _androidAnalysis(message); - // AppLog.log('MessageManagement.shunting GetPlatform.isAndroid: $extra'); + AppLog.log('MessageManagement.shunting GetPlatform.isAndroid: $extra'); } else if (GetPlatform.isIOS) { extra = _iosAnalysis(message); // AppLog.log('MessageManagement.shunting GetPlatform.isIos: $extra'); @@ -112,12 +112,14 @@ class MessageManagement { break; case MessageConstant.talkPushBigImage: print('MessageManagement._shuntingBus 收到远程开锁请求:$data'); - try { - final int lockId = int.tryParse(data['lockId']?.toString() ?? '') ?? -1; - // NotificationService().showTextNotification('远程开锁请求'.tr, '收到远程开锁请求'.tr); - eventBus.fire(RemoteUnlockRequestEvent(lockId: lockId, timeoutSeconds: 60)); - } catch (e) { - print('MessageManagement._shuntingBus 远程开锁请求异常:$e'); + final int lockType = int.tryParse(data['lockType']?.toString() ?? '') ?? -1; + if (lockType == 1) { // 半自动锁 + try { + final int lockId = int.tryParse(data['lockId']?.toString() ?? '') ?? -1; + eventBus.fire(RemoteUnlockRequestEvent(lockId: lockId, timeoutSeconds: 60)); + } catch (e) { + print('MessageManagement._shuntingBus 远程开锁请求异常:$e'); + } } break; diff --git a/lib/tools/remote_unlock_coordinator.dart b/lib/tools/remote_unlock_coordinator.dart index ceda0496..8cf433e5 100644 --- a/lib/tools/remote_unlock_coordinator.dart +++ b/lib/tools/remote_unlock_coordinator.dart @@ -1,5 +1,7 @@ import 'dart:async'; import 'package:get/get.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:get/get.dart'; import 'package:star_lock/appRouters.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart'; import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.dart'; @@ -7,6 +9,7 @@ import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/tools/eventBusEventManage.dart'; import 'package:star_lock/tools/push/message_constant.dart'; import 'package:star_lock/tools/storage.dart'; +import 'package:star_lock/tools/remote_unlock_overlay.dart'; class RemoteUnlockCoordinator { factory RemoteUnlockCoordinator() => _instance; @@ -17,6 +20,9 @@ class RemoteUnlockCoordinator { bool _inited = false; final List _pending = []; + String? _previousRoute; + dynamic _previousArgs; + bool get hasPrevious => _previousRoute != null; static void init() { _instance._init(); @@ -31,14 +37,22 @@ class RemoteUnlockCoordinator { if (eventNo != MessageConstant.talkPushBigImage) { return; } - final int lockId = int.tryParse(data['lockId']?.toString() ?? '') ?? -1; - final RemoteUnlockRequestEvent event = RemoteUnlockRequestEvent(lockId: lockId, timeoutSeconds: 60); - if (Get.isRegistered()) { - final int currentLockId = Get.find().state.keyInfos.value.lockId ?? 0; - if (currentLockId == event.lockId) { - return; - } - } + final int lockId = int.tryParse( + data['lockId']?.toString() ?? '', + ) ?? -1; + final int operateDate = int.tryParse( + data['operateDate']?.toString() ?? '', + ) ?? 0; + final int currentDate = DateTime.now().millisecondsSinceEpoch; + final int timeoutSeconds = (60 - (currentDate - operateDate) ~/ 1000) < 0 + ? 0 + : (60 - (currentDate - operateDate) ~/ 1000); + final RemoteUnlockRequestEvent event = RemoteUnlockRequestEvent( + lockId: lockId, + timeoutSeconds: timeoutSeconds, + operateDate: operateDate, + ); + // 无论当前是否在目标锁详情页,都显示覆盖层以保证体验一致 if (Get.context == null) { _pending.add(event); _waitAppReadyAndFlush(); @@ -65,27 +79,39 @@ class RemoteUnlockCoordinator { Future _navigateToLockDetailAndRefire(RemoteUnlockRequestEvent event) async { final LockListInfoItemEntity? item = await _findLockItem(event.lockId); print('导航item: $item'); - if (item == null) return; - - Get.toNamed(Routers.lockDetailMainPage, arguments: { - 'keyInfo': item, - 'isOnlyOneData': false, - }); - print('导航到了指定界面:${event.lockId}, ${event.timeoutSeconds}'); - - int tries = 0; - while (tries < 40) { - tries++; - if (Get.isRegistered()) { - final int currentLockId = Get.find().state.keyInfos.value.lockId ?? 0; - if (currentLockId == event.lockId) { - break; - } - } - await Future.delayed(const Duration(milliseconds: 50)); + if (item == null) { + return; } - eventBus.fire(RemoteUnlockRequestEvent(lockId: event.lockId, timeoutSeconds: event.timeoutSeconds)); + final String? lastHandledStr = await Storage.getString('handledRemoteUnlockOperateDate'); + final int lastHandled = int.tryParse(lastHandledStr ?? '0') ?? 0; + if (event.timeoutSeconds <= 0) { + print('远程请求已超时,忽略展示'); + return; + } + if (event.operateDate != 0 && event.operateDate == lastHandled) { + print('重复的远程请求,已处理,忽略展示'); + return; + } + + _previousRoute = Get.currentRoute; + _previousArgs = Get.arguments; + await RemoteUnlockOverlay.show( + lockId: event.lockId, + lockAlias: item.lockAlias ?? item.lockName ?? '', + operateDate: event.operateDate, + timeoutSeconds: event.timeoutSeconds, + ); + + await Storage.setString('handledRemoteUnlockOperateDate', event.operateDate.toString()); + } + + void restorePreviousRouteIfAny() { + if (_previousRoute != null) { + Get.toNamed(_previousRoute!, arguments: _previousArgs); + _previousRoute = null; + _previousArgs = null; + } } Future _findLockItem(int lockId) async {