feat(锁详情): 添加推送触发的远程开锁功能
实现推送消息触发的远程开锁请求流程,包括: 1. 新增RemoteUnlockRequestEvent和PushExtraEvent事件类型 2. 在锁详情页添加倒计时弹窗和动画效果 3. 实现Native层推送数据缓存和转发机制 4. 添加RemoteUnlockCoordinator处理应用未启动时的推送导航
This commit is contained in:
parent
513607e1ef
commit
7b9a3a0daf
@ -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<String>()
|
||||
|
||||
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<String, Any>) {
|
||||
val sp = context.getSharedPreferences(SP_NAME, MODE_PRIVATE)
|
||||
sp.edit().putString(KEY_PENDING_PUSH, JSONObject(data).toString()).apply()
|
||||
}
|
||||
|
||||
/** 取出并清空 */
|
||||
fun popPush(context: Context): Map<String, Any>? {
|
||||
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<String, Any>? {
|
||||
if (intent == null) return null
|
||||
|
||||
intent.debugPrint()
|
||||
Log.i("PUSH_INTENT", "原始推送数据:${intent.extras}")
|
||||
val map = mutableMapOf<String, Any>()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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<void> privacySDKInitialization() async {
|
||||
final XSJPushProvider jpushProvider = XSJPushProvider();
|
||||
await jpushProvider.initJPushService();
|
||||
NotificationService().init(); // 初始化通知服务
|
||||
|
||||
RemoteUnlockCoordinator.init();
|
||||
NativeInteractionTool().setupPushReceiver();
|
||||
final Map<String, dynamic>? push =
|
||||
await NativeInteractionTool().getPendingPush();
|
||||
if(push != null) {
|
||||
print('哈哈: $push');
|
||||
if(push.isNotEmpty) {
|
||||
// do something
|
||||
MessageManagement.shuntingBus(push);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,6 +113,50 @@ class LockDetailLogic extends BaseGetXController {
|
||||
});
|
||||
}
|
||||
|
||||
// 初始化远程解锁请求监听器
|
||||
void initRemoteUnlockRequestListener() {
|
||||
// 监听推送消息触发的远程解锁请求
|
||||
eventBus.on<RemoteUnlockRequestEvent>().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<void> _replyOpenLock(Reply reply) async {
|
||||
final int status = reply.data[6];
|
||||
|
||||
@ -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<LockDetailPage> 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<LockDetailPage> 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<LockDetailPage> 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<LockDetailPage> 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<LockDetailPage> with TickerProviderStat
|
||||
Icons.info, // 使用内置的 warning 图标,它是一个叹号
|
||||
color: AppColors.mainColor, // 设置图标颜色为红色
|
||||
size: 25.w, // 设置图标大小为 30
|
||||
|
||||
),
|
||||
),
|
||||
SizedBox(width: 20.w),
|
||||
@ -1325,6 +1345,93 @@ class _LockDetailPageState extends State<LockDetailPage> 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: <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() {
|
||||
return Obx(() {
|
||||
// 计算虚线圆环的可见部分(根据倒计时进度)
|
||||
final double progress = state.pushRemoteUnlockCountdownSeconds.value / 60.0;
|
||||
|
||||
return CustomPaint(
|
||||
size: Size(168.r, 168.r),
|
||||
painter: DottedCirclePainter(progress: progress),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
//如果需要实名认证,需认证完成,方可开锁
|
||||
Future<void> isNeedRealNameAuthThenOpenLock() async {
|
||||
final bool isNetWork = await logic.isConnected() ?? false;
|
||||
@ -1457,3 +1564,46 @@ class _LockDetailPageState extends State<LockDetailPage> 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;
|
||||
}
|
||||
|
||||
@ -80,4 +80,9 @@ class LockDetailState {
|
||||
List<int> uploadRemoteControlDataList = <int>[];// 上传遥控的数据
|
||||
|
||||
List<int> uploadLockSetDataList = <int>[];// 上传锁设置数据
|
||||
}
|
||||
|
||||
// 远程开锁请求(推送消息触发)
|
||||
RxBool showPushRemoteUnlockRequest = false.obs; // 是否显示推送远程开锁确认界面
|
||||
RxInt pushRemoteUnlockCountdownSeconds = 60.obs; // 推送远程开锁倒计时秒数
|
||||
Timer? pushRemoteUnlockRequestTimer; // 推送远程开锁倒计时定时器
|
||||
}
|
||||
@ -24,7 +24,6 @@ class _StarLockApplicationState extends State<StarLockApplication> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -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<dynamic, dynamic> data = call.arguments;
|
||||
try {
|
||||
final Map<String, dynamic> push = Map<String, dynamic>.from(data);
|
||||
print('收到原生 receivePush:$push');
|
||||
MessageManagement.shuntingBus(push);
|
||||
} catch (e) {
|
||||
print('NativeInteractionTool.setupPushReceiver 解析失败:$e');
|
||||
}
|
||||
break;
|
||||
case 'getBlueStatus':
|
||||
break;
|
||||
default:
|
||||
throw MissingPluginException();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 获取待处理的推送消息
|
||||
Future<Map<String, dynamic>?> getPendingPush() async {
|
||||
_pushCacheChannel ??= MethodChannel(NativeInteractionConfig.methodPushChannel);
|
||||
print('进入getPendingPush');
|
||||
try {
|
||||
final Map<dynamic, dynamic>? data = await _pushCacheChannel!.invokeMethod('getPendingPush');
|
||||
return data != null ? Map<String, dynamic>.from(data) : null;
|
||||
} catch (e) {
|
||||
print("获取缓存推送消息失败: '${e.toString()}'.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> getBundleIdentifier() async {
|
||||
try {
|
||||
final String? bundleIdentifier =
|
||||
|
||||
@ -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<String, dynamic> data;
|
||||
}
|
||||
|
||||
/// 获取到视频流数据然后刷新界面
|
||||
class GetTVDataRefreshUI {
|
||||
GetTVDataRefreshUI(this.tvList);
|
||||
|
||||
@ -13,6 +13,7 @@ class MessageManagement {
|
||||
if (message.isEmpty) {
|
||||
return;
|
||||
}
|
||||
print('MessageManagement.shunting呀: $message');
|
||||
Map<String, dynamic> extra = <String, dynamic>{};
|
||||
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<String, dynamic> data) {
|
||||
final int eventNo = data['eventNo'] ?? -1;
|
||||
static void shuntingBus(Map<String, dynamic> 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:
|
||||
|
||||
@ -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<int, String> channelTypeMapping = <int, String>{
|
||||
@ -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<String, dynamic> message) async {
|
||||
AppLog.log('onOpenNotification: $message');
|
||||
debugPrint("addJPushEventHandler onOpenNotification:$message");
|
||||
print("addJPushEventHandler onOpenNotification:$message");
|
||||
try {
|
||||
await MessageManagement.shunting(message);
|
||||
Map<String, dynamic> extra = <String, dynamic>{};
|
||||
if (GetPlatform.isAndroid) {
|
||||
final Map<Object?, dynamic> 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<Object?, Object?> 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<String, dynamic> message) async {
|
||||
AppLog.log('onReceiveMessage: $message');
|
||||
|
||||
118
lib/tools/remote_unlock_coordinator.dart
Normal file
118
lib/tools/remote_unlock_coordinator.dart
Normal file
@ -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<RemoteUnlockRequestEvent> _pending = <RemoteUnlockRequestEvent>[];
|
||||
|
||||
static void init() {
|
||||
_instance._init();
|
||||
}
|
||||
|
||||
void _init() {
|
||||
if (_inited) return;
|
||||
_inited = true;
|
||||
_sub = eventBus.on<PushExtraEvent>().listen((PushExtraEvent evt) async {
|
||||
final Map<String, dynamic> 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<LockDetailLogic>()) {
|
||||
final int currentLockId = Get.find<LockDetailLogic>().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<RemoteUnlockRequestEvent>.from(_pending)) {
|
||||
await _navigateToLockDetailAndRefire(e);
|
||||
print('导航到指定界面成功');
|
||||
}
|
||||
_pending.clear();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _navigateToLockDetailAndRefire(RemoteUnlockRequestEvent event) async {
|
||||
final LockListInfoItemEntity? item = await _findLockItem(event.lockId);
|
||||
print('导航item: $item');
|
||||
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));
|
||||
}
|
||||
|
||||
Future<LockListInfoItemEntity?> _findLockItem(int lockId) async {
|
||||
final LockListInfoGroupEntity? stored = await Storage.getLockMainListData();
|
||||
LockListInfoItemEntity? item;
|
||||
if (stored != null) {
|
||||
for (final GroupList g in stored.groupList ?? <GroupList>[]) {
|
||||
for (final LockListInfoItemEntity l in g.lockList ?? <LockListInfoItemEntity>[]) {
|
||||
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 ?? <GroupList>[]) {
|
||||
for (final LockListInfoItemEntity l in g.lockList ?? <LockListInfoItemEntity>[]) {
|
||||
if ((l.lockId ?? 0) == lockId) {
|
||||
return l;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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: <Widget>[
|
||||
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}) {
|
||||
|
||||
@ -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
|
||||
|
||||
#视频播放器
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user