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 76fc8e66..2ec29f6a 100755 --- a/android/app/src/main/kotlin/com/skychip/lock/MainActivity.kt +++ b/android/app/src/main/kotlin/com/skychip/lock/MainActivity.kt @@ -1,6 +1,8 @@ package com.skychip.lock import android.content.Intent +import android.content.Context +import android.content.Context.MODE_PRIVATE import android.net.Uri import android.os.Bundle import android.util.Log @@ -11,6 +13,55 @@ import io.flutter.plugins.GeneratedPluginRegistrant import android.bluetooth.BluetoothAdapter; import androidx.core.content.FileProvider import java.io.File +import org.json.JSONObject + +private fun flagsToString(flags: Int): String { + val flagsList = mutableListOf() + + if (flags and Intent.FLAG_ACTIVITY_NEW_TASK != 0) flagsList.add("FLAG_ACTIVITY_NEW_TASK") + if (flags and Intent.FLAG_ACTIVITY_CLEAR_TOP != 0) flagsList.add("FLAG_ACTIVITY_CLEAR_TOP") + if (flags and Intent.FLAG_ACTIVITY_SINGLE_TOP != 0) flagsList.add("FLAG_ACTIVITY_SINGLE_TOP") + if (flags and Intent.FLAG_ACTIVITY_CLEAR_TASK != 0) flagsList.add("FLAG_ACTIVITY_CLEAR_TASK") + if (flags and Intent.FLAG_ACTIVITY_NO_HISTORY != 0) flagsList.add("FLAG_ACTIVITY_NO_HISTORY") + if (flags and Intent.FLAG_ACTIVITY_MULTIPLE_TASK != 0) flagsList.add("FLAG_ACTIVITY_MULTIPLE_TASK") + if (flags and Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS != 0) flagsList.add("FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS") + if (flags and Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT != 0) flagsList.add("FLAG_ACTIVITY_BROUGHT_TO_FRONT") + if (flags and Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED != 0) flagsList.add("FLAG_ACTIVITY_RESET_TASK_IF_NEEDED") + if (flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0) flagsList.add("FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY") + if (flags and Intent.FLAG_ACTIVITY_FORWARD_RESULT != 0) flagsList.add("FLAG_ACTIVITY_FORWARD_RESULT") + if (flags and Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP != 0) flagsList.add("FLAG_ACTIVITY_PREVIOUS_IS_TOP") + if (flags and Intent.FLAG_GRANT_READ_URI_PERMISSION != 0) flagsList.add("FLAG_GRANT_READ_URI_PERMISSION") + if (flags and Intent.FLAG_GRANT_WRITE_URI_PERMISSION != 0) flagsList.add("FLAG_GRANT_WRITE_URI_PERMISSION") + + return if (flagsList.isEmpty()) "No flags set" else flagsList.joinToString(", ") +} + +fun Intent.debugPrint(tag: String = "INTENT_DEBUG") { + Log.d(tag, "===== Intent Debug Information =====") + + listOf( + "Action" to action, + "Data" to data, + "Type" to type, + "Component" to component, + "Package" to `package`, + "Flags" to "${Integer.toHexString(flags)} ${flagsToString(flags)}", + "Source Bounds" to sourceBounds + ).forEach { (key, value) -> + Log.d(tag, "$key: $value") + } + + categories?.forEach { category -> + Log.d(tag, "Category: $category") + } + + extras?.keySet()?.forEach { key -> + val value = extras?.get(key) + Log.d(tag, "Extra - $key: $value (${value?.javaClass?.simpleName})") + } + + Log.d(tag, "===== End Intent Debug =====") +} class MainActivity : FlutterActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -55,6 +106,23 @@ class MainActivity : FlutterActivity() { result.notImplemented() // 没有实现的方法 } } + + MethodChannel( + flutterEngine?.dartExecutor!!.binaryMessenger, + "starLockFlutterPushCache" + ).setMethodCallHandler { call, result -> + if(call.method == "getPendingPush") { + val map = PushCache.popPush(this) + Log.i("PUSH_INTENT", "获取缓存的推送数据:$map") + result.success(map) + } else { + result.notImplemented() // 没有实现的方法 + } + Log.i("PUSH_INTENT", "怎么回事:$call") + } + + Log.i("PUSH_INTENT", "应用启动点击推送:$intent") + PushIntentHandler.handlePushIntent(this, intent) // 统一处理所有厂商推送点击数据 } fun shareText(text: String?, subject: String = "", imageUrl: String = "") { @@ -114,4 +182,72 @@ class MainActivity : FlutterActivity() { } } } + + // 处理应用已在运行时点击推送的情况 + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + setIntent(intent) + Log.i("PUSH_INTENT", "应用在运行点击推送:$intent") + val map = PushIntentHandler.handlePushIntent(this, intent) + try { + val flutterEngine: FlutterEngine? = this.flutterEngine + MethodChannel( + flutterEngine?.dartExecutor!!.binaryMessenger, + "starLockFlutterReceive" + ).invokeMethod("receivePush", map) + Log.i("PUSH_INTENT", "已主动发送 receivePush 到 Flutter:$map") + } catch (e: Exception) { + Log.e("PUSH_INTENT", "发送 receivePush 到 Flutter 失败: ${e.message}") + } + } +} +/// 推送数据缓存 +object PushCache { + + private const val SP_NAME = "push_cache_sp" + private const val KEY_PENDING_PUSH = "pending_push_json" + + /** 保存推送数据 */ + fun savePush(context: Context, data: Map) { + val sp = context.getSharedPreferences(SP_NAME, MODE_PRIVATE) + sp.edit().putString(KEY_PENDING_PUSH, JSONObject(data).toString()).apply() + } + + /** 取出并清空 */ + fun popPush(context: Context): Map? { + val sp = context.getSharedPreferences(SP_NAME, MODE_PRIVATE) + val text = sp.getString(KEY_PENDING_PUSH, null) ?: return null + + sp.edit().remove(KEY_PENDING_PUSH).apply() + + val json = JSONObject(text) + return json.keys().asSequence().associateWith { json.get(it) } + } +} + +/// 推送数据处理 +object PushIntentHandler { + + 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") + } + } + + // 保存到 SharedPreferences + PushCache.savePush(context, map) + return map + } } diff --git a/lib/app_settings/app_settings.dart b/lib/app_settings/app_settings.dart index a79ebe66..e09cf7b2 100755 --- a/lib/app_settings/app_settings.dart +++ b/lib/app_settings/app_settings.dart @@ -5,7 +5,7 @@ import 'package:get/get.dart'; import 'package:star_lock/mine/about/debug/debug_console.dart'; class AppLog { - static bool _printLog = false; + static bool _printLog = true; static bool _onlyError = false; static void showLog({required bool printLog, bool? onlyError}) { @@ -14,6 +14,7 @@ class AppLog { } static void log(String msg, {StackTrace? stackTrace, bool? error}) { + debugPrint(msg); msg = '${DateTime.now().toIso8601String()} : $msg'; DebugConsole.info(msg, stackTrace: stackTrace, isErr: error ?? false); if (!kDebugMode) { diff --git a/lib/main.dart b/lib/main.dart index 6a3963db..7bb2a367 100755 --- a/lib/main.dart +++ b/lib/main.dart @@ -17,6 +17,7 @@ import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/network/start_chart_api.dart'; import 'package:star_lock/talk/starChart/handle/impl/debug_Info_model.dart'; import 'package:star_lock/talk/starChart/status/appLifecycle_observer.dart'; +import 'package:star_lock/tools/NativeInteractionTool.dart'; import 'package:star_lock/tools/baseGetXController.dart'; import 'package:star_lock/tools/bugly/bugly_tool.dart'; import 'package:star_lock/tools/callkit_handler.dart'; @@ -24,11 +25,13 @@ import 'package:star_lock/tools/device_info_service.dart'; import 'package:star_lock/tools/eventBusEventManage.dart'; import 'package:star_lock/tools/jverify_one_click_login.dart'; import 'package:star_lock/tools/platform_info_services.dart'; +import 'package:star_lock/tools/push/message_management.dart'; import 'package:star_lock/tools/push/notification_service.dart'; import 'package:star_lock/tools/push/xs_jPhush.dart'; import 'package:star_lock/tools/storage.dart'; import 'package:star_lock/translations/current_locale_tool.dart'; import 'package:star_lock/translations/trans_lib.dart'; +import 'package:star_lock/tools/remote_unlock_coordinator.dart'; import 'apm/apm_helper.dart'; import 'app.dart'; @@ -108,5 +111,15 @@ Future privacySDKInitialization() async { final XSJPushProvider jpushProvider = XSJPushProvider(); await jpushProvider.initJPushService(); NotificationService().init(); // 初始化通知服务 - + RemoteUnlockCoordinator.init(); + NativeInteractionTool().setupPushReceiver(); + final Map? push = + await NativeInteractionTool().getPendingPush(); + if(push != null) { + print('哈哈: $push'); + if(push.isNotEmpty) { + // do something + MessageManagement.shuntingBus(push); + } + } } diff --git a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart index 432c2052..732e4e47 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_logic.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_logic.dart @@ -113,6 +113,50 @@ class LockDetailLogic extends BaseGetXController { }); } + // 初始化远程解锁请求监听器 + void initRemoteUnlockRequestListener() { + // 监听推送消息触发的远程解锁请求 + eventBus.on().listen((RemoteUnlockRequestEvent event) { + // 只处理与当前锁相关的远程解锁请求 + if (event.lockId == state.keyInfos.value.lockId) { + print('触发!!'); + showPushRemoteUnlockRequest(timeoutSeconds: event.timeoutSeconds ?? 60); + } + }); + } + + // 显示推送消息触发的远程解锁请求界面 + void showPushRemoteUnlockRequest({required int timeoutSeconds}) { + state.showPushRemoteUnlockRequest.value = true; + state.pushRemoteUnlockCountdownSeconds.value = timeoutSeconds; + + // 启动倒计时 + state.pushRemoteUnlockRequestTimer?.cancel(); + state.pushRemoteUnlockRequestTimer = Timer.periodic(const Duration(seconds: 1), (Timer timer) { + if (state.showPushRemoteUnlockRequest.value && state.pushRemoteUnlockCountdownSeconds.value > 0) { + state.pushRemoteUnlockCountdownSeconds.value--; + } else if (state.showPushRemoteUnlockRequest.value && state.pushRemoteUnlockCountdownSeconds.value <= 0) { + // 倒计时结束,自动拒绝远程解锁请求 + rejectPushRemoteUnlockRequest(); + } + }); + } + + // 接受推送消息触发的远程解锁请求 + void acceptPushRemoteUnlockRequest() { + state.pushRemoteUnlockRequestTimer?.cancel(); + state.showPushRemoteUnlockRequest.value = false; + state.pushRemoteUnlockCountdownSeconds.value = 60; + remoteOpenLock(); + } + + // 拒绝推送消息触发的远程解锁请求 + void rejectPushRemoteUnlockRequest() { + state.pushRemoteUnlockRequestTimer?.cancel(); + state.showPushRemoteUnlockRequest.value = false; + state.pushRemoteUnlockCountdownSeconds.value = 60; + } + // 开门数据解析 Future _replyOpenLock(Reply reply) async { final int status = reply.data[6]; diff --git a/lib/main/lockDetail/lockDetail/lockDetail_page.dart b/lib/main/lockDetail/lockDetail/lockDetail_page.dart index e7d2f9cc..b897ac78 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_page.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:math' show pi; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -10,7 +11,6 @@ import 'package:star_lock/app_settings/app_colors.dart'; import 'package:star_lock/flavors.dart'; import 'package:star_lock/main/lockDetail/lockDetail/device_network_info.dart'; import 'package:star_lock/main/lockDetail/lockDetail/lockDetail_state.dart'; -import 'package:star_lock/main/lockDetail/lockSet/lockSet/lockSetInfo_entity.dart'; import 'package:star_lock/main/lockMian/lockMain/lockMain_logic.dart'; import 'package:star_lock/mine/gateway/addGateway/gatewayConfigurationWifi/getGatewayConfiguration_entity.dart'; import 'package:star_lock/network/api_repository.dart'; @@ -49,6 +49,7 @@ class _LockDetailPageState extends State with TickerProviderStat @override void initState() { + print("LockDetailPage initState"); state.animationController = AnimationController(duration: const Duration(seconds: 1), vsync: this); state.animationController?.repeat(); //动画开始、结束、向前移动或向后移动时会调用StatusListener @@ -70,6 +71,7 @@ class _LockDetailPageState extends State with TickerProviderStat _initRefreshLockDetailInfoDataEventAction(); logic.initReplySubscription(); logic.initLockSetOpenOrCloseCheckInRefreshLockDetailWithAttendanceAction(); + logic.initRemoteUnlockRequestListener(); // 初始化远程解锁请求监听器 logic.loadData(lockListInfoItemEntity: widget.lockListInfoItemEntity, isOnlyOneData: widget.isOnlyOneData); } @@ -94,7 +96,13 @@ class _LockDetailPageState extends State with TickerProviderStat @override Widget build(BuildContext context) { - return F.sw(skyCall: skWidget, xhjCall: xhjWidget); + return Obx(() { + final bool overlayVisible = state.showPushRemoteUnlockRequest.value; + return PopScope( + canPop: !overlayVisible, + child: F.sw(skyCall: skWidget, xhjCall: xhjWidget), + ); + }); } //鑫泓佳布局 @@ -512,14 +520,25 @@ class _LockDetailPageState extends State with TickerProviderStat ], ), ), - Visibility( - visible: state.iSClosedUnlockSuccessfulPopup.value, - child: Container( - width: 1.sw, - height: 1.sh - ScreenUtil().statusBarHeight * 2, - color: Colors.black.withOpacity(0.3), - child: _unlockSuccessWidget()), - ) + Obx(() => Visibility( + visible: state.iSClosedUnlockSuccessfulPopup.value, + child: Container( + width: 1.sw, + height: 1.sh - ScreenUtil().statusBarHeight * 2, + color: Colors.black.withOpacity(0.3), + 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(), + ), + )) ]), ], ); @@ -608,6 +627,7 @@ class _LockDetailPageState extends State with TickerProviderStat Icons.info, // 使用内置的 warning 图标,它是一个叹号 color: AppColors.mainColor, // 设置图标颜色为红色 size: 25.w, // 设置图标大小为 30 + ), ), SizedBox(width: 20.w), @@ -1325,6 +1345,93 @@ 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(() { + // 计算虚线圆环的可见部分(根据倒计时进度) + final double progress = state.pushRemoteUnlockCountdownSeconds.value / 60.0; + + return CustomPaint( + size: Size(168.r, 168.r), + painter: DottedCirclePainter(progress: progress), + ); + }); + } + //如果需要实名认证,需认证完成,方可开锁 Future isNeedRealNameAuthThenOpenLock() async { final bool isNetWork = await logic.isConnected() ?? false; @@ -1457,3 +1564,46 @@ class _LockDetailPageState extends State with TickerProviderStat BlueManage().disconnect(); } } + +// 推送远程解锁倒计时动画绘制器 +class DottedCirclePainter extends CustomPainter { // 0.0 to 1.0 + + DottedCirclePainter({required this.progress}); + final double progress; + + @override + void paint(Canvas canvas, Size size) { + final Paint paint = Paint() + ..color = AppColors.mainColor + ..strokeWidth = 4.w + ..style = PaintingStyle.stroke; + + final double radius = size.width / 2 - 2.w; + final Offset center = Offset(size.width / 2, size.height / 2); + + // 绘制虚线圆环(根据进度显示部分) + final Path path = Path(); + final double angle = -pi / 2 + 2 * pi * progress.clamp(0.0, 1.0); + + for (int i = 0; i <= 60; i++) { + final double startAngle = (2 * pi / 60) * i - pi / 2; + final double endAngle = startAngle + (2 * pi / 60) * 0.7; + + if (startAngle <= angle) { + final Path segmentPath = Path(); + segmentPath.arcTo( + Rect.fromCircle(center: center, radius: radius), + startAngle, + endAngle - startAngle, + true, + ); + path.addPath(segmentPath, Offset.zero); + } + } + + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} diff --git a/lib/main/lockDetail/lockDetail/lockDetail_state.dart b/lib/main/lockDetail/lockDetail/lockDetail_state.dart index eb4b6d30..60066f6a 100755 --- a/lib/main/lockDetail/lockDetail/lockDetail_state.dart +++ b/lib/main/lockDetail/lockDetail/lockDetail_state.dart @@ -80,4 +80,9 @@ class LockDetailState { List uploadRemoteControlDataList = [];// 上传遥控的数据 List uploadLockSetDataList = [];// 上传锁设置数据 -} + + // 远程开锁请求(推送消息触发) + RxBool showPushRemoteUnlockRequest = false.obs; // 是否显示推送远程开锁确认界面 + RxInt pushRemoteUnlockCountdownSeconds = 60.obs; // 推送远程开锁倒计时秒数 + Timer? pushRemoteUnlockRequestTimer; // 推送远程开锁倒计时定时器 +} \ No newline at end of file diff --git a/lib/starLockApplication/starLockApplication.dart b/lib/starLockApplication/starLockApplication.dart index 12d1fb10..fb635026 100755 --- a/lib/starLockApplication/starLockApplication.dart +++ b/lib/starLockApplication/starLockApplication.dart @@ -24,7 +24,6 @@ class _StarLockApplicationState extends State { @override void initState() { super.initState(); - } @override diff --git a/lib/tools/NativeInteractionTool.dart b/lib/tools/NativeInteractionTool.dart index 5326d2bf..8875f4a3 100755 --- a/lib/tools/NativeInteractionTool.dart +++ b/lib/tools/NativeInteractionTool.dart @@ -1,5 +1,6 @@ import 'package:flutter/services.dart'; import 'package:star_lock/flavors.dart'; +import 'package:star_lock/tools/push/message_management.dart'; import 'package:star_lock/tools/push/xs_jPhush.dart'; import '../app_settings/app_settings.dart'; @@ -8,12 +9,14 @@ import '../app_settings/app_settings.dart'; class NativeInteractionConfig { static String methodSendChannel = 'starLockFlutterSend'; static String receiveEventChannel = 'starLockFlutterReceive'; + static String methodPushChannel = 'starLockFlutterPushCache'; } ///原生交互flutter向原生发送消息 typedef BlockBlueStatus = void Function(String status); class NativeInteractionTool { + MethodChannel? _pushCacheChannel; MethodChannel sendChannel = MethodChannel(NativeInteractionConfig.methodSendChannel); MethodChannel receiveChannel = @@ -46,7 +49,6 @@ class NativeInteractionTool { // 获取设备蓝牙开启/关闭状态 final String message = call.arguments; blockBlueStatus(message); - // AppLog.log('收到原生发送的信息getBlueStatus: $message'); break; default: throw MissingPluginException(); @@ -54,6 +56,41 @@ class NativeInteractionTool { }); } + /// 初始化推送接收(前台/后台切入场景) + void setupPushReceiver() { + receiveChannel.setMethodCallHandler((MethodCall call) async { + switch (call.method) { + case 'receivePush': + final Map data = call.arguments; + try { + final Map push = Map.from(data); + print('收到原生 receivePush:$push'); + MessageManagement.shuntingBus(push); + } catch (e) { + print('NativeInteractionTool.setupPushReceiver 解析失败:$e'); + } + break; + case 'getBlueStatus': + break; + default: + throw MissingPluginException(); + } + }); + } + + /// 获取待处理的推送消息 + Future?> getPendingPush() async { + _pushCacheChannel ??= MethodChannel(NativeInteractionConfig.methodPushChannel); + print('进入getPendingPush'); + try { + final Map? data = await _pushCacheChannel!.invokeMethod('getPendingPush'); + return data != null ? Map.from(data) : null; + } catch (e) { + print("获取缓存推送消息失败: '${e.toString()}'."); + return null; + } + } + Future getBundleIdentifier() async { try { final String? bundleIdentifier = diff --git a/lib/tools/eventBusEventManage.dart b/lib/tools/eventBusEventManage.dart index 37511292..63fcd59a 100755 --- a/lib/tools/eventBusEventManage.dart +++ b/lib/tools/eventBusEventManage.dart @@ -86,6 +86,21 @@ class LockSetChangeSetRefreshLockDetailWithType { dynamic setResult; } +/// 远程开锁请求事件(门铃触发) +class RemoteUnlockRequestEvent { + RemoteUnlockRequestEvent({required this.lockId, this.timeoutSeconds = 60}); + + int lockId; + int timeoutSeconds; +} + +/// 推送原始数据事件 +class PushExtraEvent { + PushExtraEvent(this.data); + + Map data; +} + /// 获取到视频流数据然后刷新界面 class GetTVDataRefreshUI { GetTVDataRefreshUI(this.tvList); diff --git a/lib/tools/push/message_management.dart b/lib/tools/push/message_management.dart index 93f7c28d..e4d2cb5a 100644 --- a/lib/tools/push/message_management.dart +++ b/lib/tools/push/message_management.dart @@ -13,6 +13,7 @@ class MessageManagement { if (message.isEmpty) { return; } + print('MessageManagement.shunting呀: $message'); Map extra = {}; if (GetPlatform.isAndroid) { extra = _androidAnalysis(message); @@ -27,7 +28,7 @@ class MessageManagement { return; } AppLog.log(message.toString()); - _shuntingBus(extra); + shuntingBus(extra); } //android解析 @@ -74,8 +75,10 @@ class MessageManagement { } //识别参数分发消息 - static void _shuntingBus(Map data) { - final int eventNo = data['eventNo'] ?? -1; + static void shuntingBus(Map data) { + // final int eventNo = data['eventNo'] ?? -1; + final int eventNo = int.tryParse(data['eventNo']?.toString() ?? '') ?? -1; + eventBus.fire(PushExtraEvent(data)); switch (eventNo) { case MessageConstant.keyStateChange: final int keyId = data['keyId']; @@ -108,7 +111,14 @@ class MessageManagement { eventBus.fire(RefreshLockInfoDataEvent(keyId: keyId, lockId: lockId)); break; case MessageConstant.talkPushBigImage: - // XSJPushProvider().showCustomNotification(data); + 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'); + } break; default: diff --git a/lib/tools/push/xs_jPhush.dart b/lib/tools/push/xs_jPhush.dart index 2763d2d0..0a474e54 100755 --- a/lib/tools/push/xs_jPhush.dart +++ b/lib/tools/push/xs_jPhush.dart @@ -4,6 +4,7 @@ import 'dart:ffi'; import 'dart:io'; import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:jpush_flutter/jpush_flutter.dart'; import 'package:star_lock/flavors.dart'; @@ -13,11 +14,15 @@ import 'package:star_lock/network/api_repository.dart'; import 'package:star_lock/tools/baseGetXController.dart'; import 'package:star_lock/tools/callkit_handler.dart'; import 'package:star_lock/tools/debounce_throttle_tool.dart'; +import 'package:star_lock/tools/push/message_constant.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'; +import 'package:star_lock/tools/showCupertinoAlertView.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; import '../../app_settings/app_settings.dart'; +import '../NativeInteractionTool.dart'; class XSJPushProvider { static const Map channelTypeMapping = { @@ -62,18 +67,18 @@ class XSJPushProvider { // final String? bundleIdentifier = // await NativeInteractionTool().getBundleIdentifier(); // print('bundleIdentifier: $bundleIdentifier'); + addJPushEventHandler(); jpush.setup( appKey: F.jPushKey, channel: 'flutter_channel', production: F.isProductionEnv, - debug: !F.isProductionEnv, + debug: true,//!F.isProductionEnv, ); jpush.setAuth(enable: true); jpush.applyPushAuthority( const NotificationSettingsIOS(sound: true, alert: true, badge: false), ); - addJPushEventHandler(); AppLog.log('JPush initialized.'); debugPrint("initJPushService end"); } @@ -117,7 +122,47 @@ class XSJPushProvider { }, onOpenNotification: (Map message) async { AppLog.log('onOpenNotification: $message'); - debugPrint("addJPushEventHandler onOpenNotification:$message"); + print("addJPushEventHandler onOpenNotification:$message"); + try { + await MessageManagement.shunting(message); + Map extra = {}; + if (GetPlatform.isAndroid) { + final Map extras = message['extras']; + final extraData = extras['cn.jpush.android.EXTRA']; + if (extraData is String) { + extra = json.decode(extraData); + } else if (extraData is Map) { + extra = {}; + extraData.forEach((key, value) { + extra[key.toString()] = value; + }); + } + } else if (GetPlatform.isIOS) { + final Map extras = message['extras']; + extras.forEach((Object? key, Object? value) { + extra[key!.toString()] = value; + }); + } + + final int eventNo = extra['eventNo'] ?? -1; + if (eventNo == MessageConstant.talkPushBigImage) { + final int lockId = extra['lockId'] ?? 0; + ShowCupertinoAlertView().isToRemoteUnLockCountdownAlert( + timeoutSeconds: 60, + onAccept: () async { + final entity = await ApiRepository.to.remoteOpenLock( + lockId: lockId.toString(), + timeOut: 60, + ); + if (entity.errorCode!.codeIsSuccessful) { + EasyLoading.showToast('已开锁'.tr); + } + }, + ); + } + } catch (e) { + AppLog.log('onOpenNotification handle error: $e'); + } }, onReceiveMessage: (Map message) async { AppLog.log('onReceiveMessage: $message'); diff --git a/lib/tools/remote_unlock_coordinator.dart b/lib/tools/remote_unlock_coordinator.dart new file mode 100644 index 00000000..ceda0496 --- /dev/null +++ b/lib/tools/remote_unlock_coordinator.dart @@ -0,0 +1,118 @@ +import 'dart:async'; +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'; +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'; + +class RemoteUnlockCoordinator { + factory RemoteUnlockCoordinator() => _instance; + RemoteUnlockCoordinator._(); + static final RemoteUnlockCoordinator _instance = RemoteUnlockCoordinator._(); + + StreamSubscription? _sub; + bool _inited = false; + + final List _pending = []; + + static void init() { + _instance._init(); + } + + void _init() { + if (_inited) return; + _inited = true; + _sub = eventBus.on().listen((PushExtraEvent evt) async { + final Map data = evt.data; + final int eventNo = int.tryParse(data['eventNo']?.toString() ?? '') ?? -1; + 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; + } + } + if (Get.context == null) { + _pending.add(event); + _waitAppReadyAndFlush(); + return; + } + await _navigateToLockDetailAndRefire(event); + }); + } + + void _waitAppReadyAndFlush() { + Timer.periodic(const Duration(milliseconds: 100), (Timer t) async { + print('等待app启动中...${Get.context}'); + if (Get.context != null) { + t.cancel(); + for (final RemoteUnlockRequestEvent e in List.from(_pending)) { + await _navigateToLockDetailAndRefire(e); + print('导航到指定界面成功'); + } + _pending.clear(); + } + }); + } + + 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)); + } + + eventBus.fire(RemoteUnlockRequestEvent(lockId: event.lockId, timeoutSeconds: event.timeoutSeconds)); + } + + Future _findLockItem(int lockId) async { + final LockListInfoGroupEntity? stored = await Storage.getLockMainListData(); + LockListInfoItemEntity? item; + if (stored != null) { + for (final GroupList g in stored.groupList ?? []) { + for (final LockListInfoItemEntity l in g.lockList ?? []) { + if ((l.lockId ?? 0) == lockId) { + item = l; + break; + } + } + if (item != null) break; + } + } + if (item != null) return item; + + final LockListInfoEntity res = await ApiRepository.to.getStarLockListInfo(pageNo: 1, pageSize: 50, isUnShowLoading: true); + for (final GroupList g in res.data?.groupList ?? []) { + for (final LockListInfoItemEntity l in g.lockList ?? []) { + if ((l.lockId ?? 0) == lockId) { + return l; + } + } + } + return null; + } +} + diff --git a/lib/tools/showCupertinoAlertView.dart b/lib/tools/showCupertinoAlertView.dart index cf585bf1..097652bf 100755 --- a/lib/tools/showCupertinoAlertView.dart +++ b/lib/tools/showCupertinoAlertView.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:typed_data'; import 'dart:ui'; @@ -323,6 +324,66 @@ class ShowCupertinoAlertView { ); } + void isToRemoteUnLockCountdownAlert({required int timeoutSeconds, required Function onAccept}) { + int seconds = timeoutSeconds; + Timer? timer; + showCupertinoDialog( + context: Get.context!, + builder: (BuildContext context) { + return StatefulBuilder(builder: (context, setState) { + timer ??= Timer.periodic(const Duration(seconds: 1), (Timer t) { + if (seconds <= 1) { + t.cancel(); + timer = null; + Get.back(); + } else { + seconds = seconds - 1; + setState(() {}); + } + }); + return CupertinoAlertDialog( + title: Container(), + content: Column( + children: [ + Text('远程开锁请求'.tr), + SizedBox(height: 10.h), + Text('${seconds} s', style: TextStyle(fontSize: 26.sp, color: AppColors.mainColor)), + ], + ), + actions: [ + CupertinoDialogAction( + onPressed: () { + timer?.cancel(); + timer = null; + Get.back(); + }, + child: Text( + '拒绝'.tr, + style: TextStyle(color: AppColors.mainColor), + ), + ), + CupertinoDialogAction( + onPressed: () async { + timer?.cancel(); + timer = null; + await onAccept(); + Get.back(); + }, + child: Text( + '同意'.tr, + style: TextStyle(color: AppColors.mainColor), + ), + ), + ], + ); + }); + }, + ).whenComplete(() { + timer?.cancel(); + timer = null; + }); + } + // 购买按钮 void showBuyTipWithContentAlert( {required String titleStr, required Function sureClick}) { diff --git a/pubspec.yaml b/pubspec.yaml index bf10df09..ac874a48 100755 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -218,7 +218,7 @@ dependencies: jpush_flutter: git: - url: git@code.star-lock.cn:StarlockTeam/jpush_flutter.git + url: https://code.skychip.top/sky/jpush_flutter.git ref: 656df9ee91b1ec8b96aa1208a6b0df27a4516067 #视频播放器