实现直接从安卓原生获取点击推送事件和参数,传入flutter处理推送。

实现了远程开锁请求功能的ui和开锁
测试了vivo和小米手机支持上述功能,三星、荣耀不支持,其他未测试。
修改了打印调试信息,后续需要恢复原状
This commit is contained in:
Xie Jing 2025-12-15 18:05:27 +08:00
parent 7b9a3a0daf
commit 2619288234
7 changed files with 339 additions and 205 deletions

View File

@ -14,6 +14,7 @@ import android.bluetooth.BluetoothAdapter;
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import java.io.File import java.io.File
import org.json.JSONObject import org.json.JSONObject
import org.json.JSONArray
private fun flagsToString(flags: Int): String { private fun flagsToString(flags: Int): String {
val flagsList = mutableListOf<String>() val flagsList = mutableListOf<String>()
@ -228,26 +229,190 @@ object PushCache {
/// 推送数据处理 /// 推送数据处理
object PushIntentHandler { 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<String, String> {
val results = HashMap<String, String>()
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<String, String> {
val out = HashMap<String, String>(8)
val stack = ArrayDeque<Any>()
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<String, Any>? { fun handlePushIntent(context: Context, intent: Intent?): Map<String, Any>? {
if (intent == null) return null if (intent == null) return null
intent.debugPrint() intent.debugPrint()
Log.i("PUSH_INTENT", "原始推送数据:${intent.extras}") Log.i("PUSH_INTENT", "原始推送数据:${intent.extras}")
val map = mutableMapOf<String, Any>()
val extras = intent.extras ?: return null val extras = intent.extras ?: return null
val targetKeys = arrayOf("lockType", "eventNo", "lockId", Log.i(PARSE_TAG, "开始解析 extras")
"imageUrl", "operateDate") val flat = flattenExtras(extras).toMutableMap()
for (key in targetKeys) {
val value = extras.getString(key) // 二次扫描:尝试从所有字符串值中提取嵌入的 JSON如小米推送格式
if (value != null) { val embeddedFields = HashMap<String, String>()
map[key] = value for (value in flat.values) {
Log.i("PushIntentHandler", "key=$key, value=$value") 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<String, Any>()
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 val canonicalMap = mutableMapOf<String, String>()
PushCache.savePush(context, map) for ((lowerKey, canonicalKey) in CANONICAL_KEY_MAP) {
return 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
} }
} }

View File

@ -117,10 +117,15 @@ class LockDetailLogic extends BaseGetXController {
void initRemoteUnlockRequestListener() { void initRemoteUnlockRequestListener() {
// //
eventBus.on<RemoteUnlockRequestEvent>().listen((RemoteUnlockRequestEvent event) { eventBus.on<RemoteUnlockRequestEvent>().listen((RemoteUnlockRequestEvent event) {
//
if (event.lockId == state.keyInfos.value.lockId) { if (event.lockId == state.keyInfos.value.lockId) {
print('触发!!'); // operateDate已处理过
showPushRemoteUnlockRequest(timeoutSeconds: event.timeoutSeconds ?? 60); 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.pushRemoteUnlockRequestTimer?.cancel();
state.showPushRemoteUnlockRequest.value = false; state.showPushRemoteUnlockRequest.value = false;
state.pushRemoteUnlockCountdownSeconds.value = 60; state.pushRemoteUnlockCountdownSeconds.value = 60;
state.handledPushOperateDate = state.currentPushOperateDate;
state.currentPushOperateDate = 0;
remoteOpenLock(); remoteOpenLock();
} }
@ -155,6 +162,8 @@ class LockDetailLogic extends BaseGetXController {
state.pushRemoteUnlockRequestTimer?.cancel(); state.pushRemoteUnlockRequestTimer?.cancel();
state.showPushRemoteUnlockRequest.value = false; state.showPushRemoteUnlockRequest.value = false;
state.pushRemoteUnlockCountdownSeconds.value = 60; 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())!; final String getMobile = (await Storage.getMobile())!;
ApmHelper.instance.trackEvent('open_lock', { ApmHelper.instance.trackEvent('open_lock', {
'lock_name': state.keyInfos.value.lockName!, 'lock_name': state.keyInfos.value.lockName!,
'account': getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!, 'account': getMobile.isNotEmpty ? getMobile : (await Storage.getEmail())!,
'date': DateTool().getNowDateWithType(1), 'date': DateTool().getNowDateWithType(1),
'open_lock_result': '${reply.data}', 'open_lock_result': '${reply.data}',
}); });

View File

@ -30,6 +30,7 @@ import '../../../common/XSConstantMacro/XSConstantMacro.dart';
import '../../../tools/appRouteObserver.dart'; import '../../../tools/appRouteObserver.dart';
import '../../../tools/dateTool.dart'; import '../../../tools/dateTool.dart';
import '../../../tools/eventBusEventManage.dart'; import '../../../tools/eventBusEventManage.dart';
import '../../../tools/remote_unlock_coordinator.dart';
import '../../lockMian/entity/lockListInfo_entity.dart'; import '../../lockMian/entity/lockListInfo_entity.dart';
import 'lockDetail_logic.dart'; import 'lockDetail_logic.dart';
@ -96,13 +97,7 @@ class _LockDetailPageState extends State<LockDetailPage> with TickerProviderStat
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Obx(() { return F.sw(skyCall: skWidget, xhjCall: xhjWidget);
final bool overlayVisible = state.showPushRemoteUnlockRequest.value;
return PopScope(
canPop: !overlayVisible,
child: F.sw(skyCall: skWidget, xhjCall: xhjWidget),
);
});
} }
// //
@ -529,16 +524,7 @@ class _LockDetailPageState extends State<LockDetailPage> with TickerProviderStat
child: _unlockSuccessWidget()), child: _unlockSuccessWidget()),
)) ))
, ,
// SizedBox.shrink()
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(),
),
))
]), ]),
], ],
); );
@ -649,69 +635,68 @@ class _LockDetailPageState extends State<LockDetailPage> with TickerProviderStat
child: Stack( child: Stack(
children: <Widget>[ children: <Widget>[
Center( Center(
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
if (state.openDoorBtnisUneable.value == true) { if (state.openDoorBtnisUneable.value == true) {
logic.functionBlocker.block(isNeedRealNameAuthThenOpenLock); logic.functionBlocker.block(isNeedRealNameAuthThenOpenLock);
}
},
onLongPressStart: (LongPressStartDetails details) {
if (state.openDoorBtnisUneable.value == true) {
void callback() {
setState(startUnLock);
} }
},
logic.functionBlocker.block(callback); onLongPressStart: (LongPressStartDetails details) {
} if (state.openDoorBtnisUneable.value == true) {
}, void callback() {
child: Stack( setState(startUnLock);
children: <Widget>[ }
FlavorsImg( logic.functionBlocker.block(callback);
child: Image.asset( }
state.openDoorBtnisUneable.value == false },
? 'images/main/icon_main_openLockBtn_grey.png' child: Stack(
: (state.isOpenPassageMode.value == 1 children: <Widget>[
? 'images/main/icon_main_normallyOpenMode_center.png' FlavorsImg(
: 'images/main/icon_main_openLockBtn_center.png'), child: Image.asset(
width: 330.w, state.openDoorBtnisUneable.value == false
height: 330.w, ? 'images/main/icon_main_openLockBtn_grey.png'
// color: AppColors.primaryTopColor, : (state.isOpenPassageMode.value == 1
), ? 'images/main/icon_main_normallyOpenMode_center.png'
), : 'images/main/icon_main_openLockBtn_center.png'),
if (state.openDoorBtnisUneable.value == false) width: 330.w,
Positioned( height: 330.w,
child: FlavorsImg(
child: Image.asset(
'images/main/icon_main_openLockBtn_grey.png',
width: 330.w,
height: 330.w,
),
), ),
) ),
else if (state.openDoorBtnisUneable.value == false)
state.openLockBtnState.value == 1 Positioned(
? buildRotationTransition( child: FlavorsImg(
child: Image.asset(
'images/main/icon_main_openLockBtn_grey.png',
width: 330.w, width: 330.w,
height: 330.w, height: 330.w,
) ),
: Positioned( ),
child: FlavorsImg( )
child: Image.asset( else
state.isOpenPassageMode.value == 1 state.openLockBtnState.value == 1
? 'images/main/icon_main_normallyOpenMode_circle.png' ? buildRotationTransition(
: 'images/main/icon_main_openLockBtn_circle.png',
width: 330.w, width: 330.w,
height: 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( Positioned(
right: 90.w, right: 90.w,
bottom: 1, bottom: 1,
child: Obx(() => Visibility( child: Obx(() => Visibility(
visible: state.keyInfos.value.lockSetting!.remoteUnlock == 1, visible: state.keyInfos.value.lockSetting!.remoteUnlock == 1 && !state.showPushRemoteUnlockRequest.value,
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
ShowCupertinoAlertView().isToRemoteUnLockAlert(remoteUnlockAction: () { ShowCupertinoAlertView().isToRemoteUnLockAlert(remoteUnlockAction: () {
@ -738,17 +723,20 @@ class _LockDetailPageState extends State<LockDetailPage> with TickerProviderStat
SizedBox( SizedBox(
height: 30.h, height: 30.h,
), ),
Container( Obx(() => Visibility(
padding: EdgeInsets.symmetric(horizontal: 20.w), visible: !state.showPushRemoteUnlockRequest.value,
child: Center( child: Container(
child: Text( padding: EdgeInsets.symmetric(horizontal: 20.w),
logic.getKeyStatusTextAndShow(), child: Center(
maxLines: 2, child: Text(
textAlign: TextAlign.center, logic.getKeyStatusTextAndShow(),
style: TextStyle(fontSize: 22.sp, color: AppColors.btnDisableColor, fontWeight: FontWeight.w500), maxLines: 2,
), textAlign: TextAlign.center,
), style: TextStyle(fontSize: 22.sp, color: AppColors.btnDisableColor, fontWeight: FontWeight.w500),
), ),
),
),
)),
SizedBox( SizedBox(
height: 30.h, height: 30.h,
), ),
@ -1345,80 +1333,6 @@ class _LockDetailPageState extends State<LockDetailPage> with TickerProviderStat
return formattedTime; 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: <Widget>[
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() { Widget _pushRemoteUnlockCountdownAnimation() {
return Obx(() { return Obx(() {
@ -1432,6 +1346,16 @@ class _LockDetailPageState extends State<LockDetailPage> 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<void> isNeedRealNameAuthThenOpenLock() async { Future<void> isNeedRealNameAuthThenOpenLock() async {
final bool isNetWork = await logic.isConnected() ?? false; final bool isNetWork = await logic.isConnected() ?? false;
@ -1546,6 +1470,11 @@ class _LockDetailPageState extends State<LockDetailPage> with TickerProviderStat
logic.cancelBlueConnetctToastTimer(); logic.cancelBlueConnetctToastTimer();
BlueManage().disconnect(); BlueManage().disconnect();
state.openLockBtnState.value = 0; state.openLockBtnState.value = 0;
state.pushRemoteUnlockRequestTimer?.cancel();
state.showPushRemoteUnlockRequest.value = false;
state.pushRemoteUnlockCountdownSeconds.value = 60;
state.handledPushOperateDate = state.currentPushOperateDate;
state.currentPushOperateDate = 0;
} }
/// ///

View File

@ -85,4 +85,6 @@ class LockDetailState {
RxBool showPushRemoteUnlockRequest = false.obs; // RxBool showPushRemoteUnlockRequest = false.obs; //
RxInt pushRemoteUnlockCountdownSeconds = 60.obs; // RxInt pushRemoteUnlockCountdownSeconds = 60.obs; //
Timer? pushRemoteUnlockRequestTimer; // Timer? pushRemoteUnlockRequestTimer; //
} int currentPushOperateDate = 0; //
int handledPushOperateDate = 0; //
}

View File

@ -88,10 +88,11 @@ class LockSetChangeSetRefreshLockDetailWithType {
/// ///
class RemoteUnlockRequestEvent { class RemoteUnlockRequestEvent {
RemoteUnlockRequestEvent({required this.lockId, this.timeoutSeconds = 60}); RemoteUnlockRequestEvent({required this.lockId, this.timeoutSeconds = 60, this.operateDate = 0});
int lockId; int lockId;
int timeoutSeconds; int timeoutSeconds;
int operateDate;
} }
/// ///

View File

@ -17,7 +17,7 @@ class MessageManagement {
Map<String, dynamic> extra = <String, dynamic>{}; Map<String, dynamic> extra = <String, dynamic>{};
if (GetPlatform.isAndroid) { if (GetPlatform.isAndroid) {
extra = _androidAnalysis(message); extra = _androidAnalysis(message);
// AppLog.log('MessageManagement.shunting GetPlatform.isAndroid: $extra'); AppLog.log('MessageManagement.shunting GetPlatform.isAndroid: $extra');
} else if (GetPlatform.isIOS) { } else if (GetPlatform.isIOS) {
extra = _iosAnalysis(message); extra = _iosAnalysis(message);
// AppLog.log('MessageManagement.shunting GetPlatform.isIos: $extra'); // AppLog.log('MessageManagement.shunting GetPlatform.isIos: $extra');
@ -112,12 +112,14 @@ class MessageManagement {
break; break;
case MessageConstant.talkPushBigImage: case MessageConstant.talkPushBigImage:
print('MessageManagement._shuntingBus 收到远程开锁请求:$data'); print('MessageManagement._shuntingBus 收到远程开锁请求:$data');
try { final int lockType = int.tryParse(data['lockType']?.toString() ?? '') ?? -1;
final int lockId = int.tryParse(data['lockId']?.toString() ?? '') ?? -1; if (lockType == 1) { //
// NotificationService().showTextNotification('远程开锁请求'.tr, '收到远程开锁请求'.tr); try {
eventBus.fire(RemoteUnlockRequestEvent(lockId: lockId, timeoutSeconds: 60)); final int lockId = int.tryParse(data['lockId']?.toString() ?? '') ?? -1;
} catch (e) { eventBus.fire(RemoteUnlockRequestEvent(lockId: lockId, timeoutSeconds: 60));
print('MessageManagement._shuntingBus 远程开锁请求异常:$e'); } catch (e) {
print('MessageManagement._shuntingBus 远程开锁请求异常:$e');
}
} }
break; break;

View File

@ -1,5 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:get/get.dart'; 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/appRouters.dart';
import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_logic.dart';
import 'package:star_lock/main/lockMian/entity/lockListInfo_entity.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/eventBusEventManage.dart';
import 'package:star_lock/tools/push/message_constant.dart'; import 'package:star_lock/tools/push/message_constant.dart';
import 'package:star_lock/tools/storage.dart'; import 'package:star_lock/tools/storage.dart';
import 'package:star_lock/tools/remote_unlock_overlay.dart';
class RemoteUnlockCoordinator { class RemoteUnlockCoordinator {
factory RemoteUnlockCoordinator() => _instance; factory RemoteUnlockCoordinator() => _instance;
@ -17,6 +20,9 @@ class RemoteUnlockCoordinator {
bool _inited = false; bool _inited = false;
final List<RemoteUnlockRequestEvent> _pending = <RemoteUnlockRequestEvent>[]; final List<RemoteUnlockRequestEvent> _pending = <RemoteUnlockRequestEvent>[];
String? _previousRoute;
dynamic _previousArgs;
bool get hasPrevious => _previousRoute != null;
static void init() { static void init() {
_instance._init(); _instance._init();
@ -31,14 +37,22 @@ class RemoteUnlockCoordinator {
if (eventNo != MessageConstant.talkPushBigImage) { if (eventNo != MessageConstant.talkPushBigImage) {
return; return;
} }
final int lockId = int.tryParse(data['lockId']?.toString() ?? '') ?? -1; final int lockId = int.tryParse(
final RemoteUnlockRequestEvent event = RemoteUnlockRequestEvent(lockId: lockId, timeoutSeconds: 60); data['lockId']?.toString() ?? '',
if (Get.isRegistered<LockDetailLogic>()) { ) ?? -1;
final int currentLockId = Get.find<LockDetailLogic>().state.keyInfos.value.lockId ?? 0; final int operateDate = int.tryParse(
if (currentLockId == event.lockId) { data['operateDate']?.toString() ?? '',
return; ) ?? 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) { if (Get.context == null) {
_pending.add(event); _pending.add(event);
_waitAppReadyAndFlush(); _waitAppReadyAndFlush();
@ -65,27 +79,39 @@ class RemoteUnlockCoordinator {
Future<void> _navigateToLockDetailAndRefire(RemoteUnlockRequestEvent event) async { Future<void> _navigateToLockDetailAndRefire(RemoteUnlockRequestEvent event) async {
final LockListInfoItemEntity? item = await _findLockItem(event.lockId); final LockListInfoItemEntity? item = await _findLockItem(event.lockId);
print('导航item: $item'); print('导航item: $item');
if (item == null) return; if (item == null) {
return;
Get.toNamed(Routers.lockDetailMainPage, arguments: <String, Object?>{
'keyInfo': item,
'isOnlyOneData': false,
});
print('导航到了指定界面:${event.lockId}, ${event.timeoutSeconds}');
int tries = 0;
while (tries < 40) {
tries++;
if (Get.isRegistered<LockDetailLogic>()) {
final int currentLockId = Get.find<LockDetailLogic>().state.keyInfos.value.lockId ?? 0;
if (currentLockId == event.lockId) {
break;
}
}
await Future<void>.delayed(const Duration(milliseconds: 50));
} }
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<LockListInfoItemEntity?> _findLockItem(int lockId) async { Future<LockListInfoItemEntity?> _findLockItem(int lockId) async {