app-starlock/lib/tools/push/xs_jPhush.dart
Xie Jing 7b9a3a0daf feat(锁详情): 添加推送触发的远程开锁功能
实现推送消息触发的远程开锁请求流程,包括:
1. 新增RemoteUnlockRequestEvent和PushExtraEvent事件类型
2. 在锁详情页添加倒计时弹窗和动画效果
3. 实现Native层推送数据缓存和转发机制
4. 添加RemoteUnlockCoordinator处理应用未启动时的推送导航
2025-11-29 14:45:42 +08:00

270 lines
10 KiB
Dart
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:convert';
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';
import 'package:star_lock/login/register/entity/checkIP_entity.dart';
import 'package:star_lock/mine/minePersonInfo/minePersonInfoEditAccount/minePersonInfoEditAccount/mineUnbindPhoneOrEmail_entity.dart';
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>{
1: 'xiaomi',
2: 'huawei',
3: 'meizu',
4: 'oppo',
5: 'vivo',
7: 'honor',
8: 'fcm',
9: 'jiguang',
10: 'apns',
};
final JPush jpush = JPush();
DebounceThrottleTool? _debounceThrottleTool;
Future<void> resetJPushService() async {
debugPrint("resetJPushService start");
for (final MapEntry<int, String> entry in channelTypeMapping.entries) {
await Storage.removeData('old_${entry.value}');
}
debugPrint("resetJPushService end");
}
// appKey: 251fc8074820d122b6de58d2--鑫泓佳AppKey
// appKey: 7ff37d174c1a568a89e98dad--sky
Future<void> initJPushService() async {
debugPrint("initJPushService start");
final String? data = await Storage.getString(saveUserLoginData);
if (data == null || data.isEmpty) {
AppLog.log('No user data found.');
return;
}
_debounceThrottleTool = DebounceThrottleTool(
const Duration(milliseconds: 1500),
(dynamic param) {
bindPushChannels();
},
);
AppLog.log('jPushKey ${F.jPushKey}');
// final String? bundleIdentifier =
// await NativeInteractionTool().getBundleIdentifier();
// print('bundleIdentifier: $bundleIdentifier');
addJPushEventHandler();
jpush.setup(
appKey: F.jPushKey,
channel: 'flutter_channel',
production: F.isProductionEnv,
debug: true,//!F.isProductionEnv,
);
jpush.setAuth(enable: true);
jpush.applyPushAuthority(
const NotificationSettingsIOS(sound: true, alert: true, badge: false),
);
AppLog.log('JPush initialized.');
debugPrint("initJPushService end");
}
static const int CMD_GET_REGISTRATION_ID = 2005; //getRegistrationID 异步回调
static const int CMD_GET_TOKEN = 10000; //厂商 token 注册回调
//极光推送事件处理方法
void addJPushEventHandler() {
jpush.addEventHandler(
onCommandResult: (Map<String, dynamic> data) async {
AppLog.log('onCommandResult: $data');
debugPrint("addJPushEventHandler onCommandResult:$data");
final int cmdCode = data['cmd'];
switch (cmdCode) {
case CMD_GET_REGISTRATION_ID:
final bool isNullOrBlank =
GetUtils.isNullOrBlank(data['message']) ?? true;
if (!isNullOrBlank) {
AppLog.log(
'flutter get jiguang registration id : ${data['message']}');
await Storage.setString('jiguang', data['message']);
_debounceThrottleTool?.trigger(data['message']);
}
break;
case CMD_GET_TOKEN:
final bool isNullOrBlank =
GetUtils.isNullOrBlank(data['token']) ?? true;
if (!isNullOrBlank) {
await Storage.setString(
channelTypeMapping[data['platform']] ?? '', data['token']);
_debounceThrottleTool?.trigger(data['token']);
}
break;
}
},
onReceiveNotification: (Map<String, dynamic> message) async {
AppLog.log('onReceiveNotification: $message');
debugPrint('addJPushEventHandler onReceiveNotification:$message');
// showCustomNotification(message);
},
onOpenNotification: (Map<String, dynamic> message) async {
AppLog.log('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');
debugPrint("addJPushEventHandler onReceiveMessage:$message");
//这里接收自定义消息
MessageManagement.shunting(message);
},
onReceiveNotificationAuthorization: (Map<String, dynamic> message) async {
AppLog.log('onReceiveNotificationAuthorization: $message');
debugPrint(
"addJPushEventHandler onReceiveNotificationAuthorization:$message");
},
onInAppMessageShow: (Map<String, dynamic> message) async {
AppLog.log('onInAppMessageShow: $message');
debugPrint("addJPushEventHandler onInAppMessageShow:$message");
},
onConnected: (Map<String, dynamic> message) async {
return Future.value();
},
);
}
// jpush 统一推送通道设备绑定
Future<void> bindPushChannels() async {
try {
debugPrint("BindPushChannels start");
bool needReBindPushToken = false;
final Map<String, String> newVendorsPushToken = <String, String>{};
final Map<String, String> oldVendorsPushToken = <String, String>{};
for (final String channel in channelTypeMapping.values) {
debugPrint("BindPushChannels channel $channel");
final String? newVendorToken = await Storage.getString(channel);
debugPrint("BindPushChannels newVendorToken $newVendorToken");
newVendorsPushToken[channel] = newVendorToken ?? '';
final String? oldVendorToken = await Storage.getString('old_$channel');
debugPrint("BindPushChannels oldVendorToken $oldVendorToken");
oldVendorsPushToken['old_$channel'] = oldVendorToken ?? '';
if (newVendorToken != oldVendorToken) {
needReBindPushToken = true;
}
}
if (!needReBindPushToken) {
AppLog.log('vendorToken 未变动,无需重新绑定');
return;
}
final List<Map<String, dynamic>> channels = newVendorsPushToken.entries
.where((entry) => !(GetUtils.isNullOrBlank(entry.value) ?? true))
.map((entry) => <String, dynamic>{
'channel': entry.key,
'channelToken': entry.value
})
.toList();
if (Platform.isIOS) {
final CheckIPEntity entity =
await ApiRepository.to.checkIpAction(ip: '');
debugPrint('entity: ${jsonEncode(entity)}');
if (entity.errorCode!.codeIsSuccessful) {
if ('中国' != entity.data!.name) {
String? token = await CallKitHandler.getVoipToken();
if (token != null && token.isNotEmpty) {
channels.forEach((element) {
if(element['channel'] == 'apns') {
element['voipToken'] = token;
}
});
} else {
debugPrint(
'iOS VoIP Token is null or empty, not adding to channels');
}
} else {
debugPrint('国内用户不上报VOIP Token');
}
}
}
debugPrint("BindPushChannels ${channels}");
final String? deviceID = await Storage.getString(appDeviceID);
final MineUnbindPhoneOrEmailEntity entity =
await ApiRepository.to.pushBindChannels(deviceID!, channels);
if (entity.errorCode!.codeIsSuccessful) {
AppLog.log('vendorToken绑定成功准备更新本地缓存');
await Future.wait(channels.map((Map<String, dynamic> entry) =>
Storage.setString(
'old_${entry['channel']}', entry['channelToken'])));
AppLog.log('vendorToken绑定成功更新本地缓存完成');
} else {
AppLog.log('绑定失败');
}
} catch (e) {
AppLog.log('vendorToken绑定失败下次重启应用重新绑定');
AppLog.log('Error binding device ID: $e');
}
}
void showCustomNotification(Map<String, dynamic> data) {
final String title = data['notification']['android']['title'] ?? '默认标题';
final String content = data['notification']['android']['alert'] ?? '默认内容';
final String imageUrl = data['notification']['android']['extras']
?['image_url']; // 从 extras 获取图片
NotificationService().showImageNotification(title, content, imageUrl);
}
}