实现直接从安卓原生获取点击推送事件和参数,传入flutter处理推送。
实现了远程开锁请求功能的ui和开锁 测试了vivo和小米手机支持上述功能,三星、荣耀不支持,其他未测试。 修改了打印调试信息,后续需要恢复原状
This commit is contained in:
parent
7b9a3a0daf
commit
2619288234
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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}',
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 从下级返回 当前界面即将出现
|
/// 从下级返回 当前界面即将出现
|
||||||
|
|||||||
@ -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; // 已处理的推送请求操作时间戳,避免重复显示
|
||||||
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 推送原始数据事件
|
/// 推送原始数据事件
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user