feat: 多端邀请接入

This commit is contained in:
2026-01-19 07:21:28 -08:00
parent b441346630
commit 8e48a6ff8a
19 changed files with 476 additions and 71 deletions
+141
View File
@@ -25,11 +25,15 @@ import 'package:kaer_with_panels/app/widgets/dialogs/hi_dialog.dart';
import 'package:kaer_with_panels/app/services/api_service/kr_auth_api.dart';
import 'package:kaer_with_panels/app/services/kr_subscribe_service.dart';
import 'package:kaer_with_panels/app/utils/kr_common_util.dart';
import 'package:kaer_with_panels/app/utils/kr_device_util.dart';
import 'package:openinstall_flutter_plugin/openinstall_flutter_plugin.dart';
import 'dart:io' show Platform;
class KRAppRunData {
static final KRAppRunData _instance = KRAppRunData._internal();
static const String _keyUserInfo = 'USER_INFO';
static const bool inviteDebugMode = true;
/// 登录token
String? kr_token;
@@ -61,6 +65,9 @@ class KRAppRunData {
// 需要被监听的属性,用 obs 包装
final kr_isLogin = false.obs;
// 存储临时待绑定的邀请码(处理唤醒数据比登录完成早的情况)
String? _kr_pendingInviteCode;
KRAppRunData._internal();
factory KRAppRunData() => _instance;
@@ -447,6 +454,16 @@ class KRAppRunData {
kr_isLogin.value = true;
print('✅ 已标记为登录状态');
// 静默邀请绑定
if (inviteDebugMode) {
// Debug 模式下等待调试弹窗完成,避免页面跳转
await _kr_handleSilentInvitation();
} else {
// 正式环境异步执行,不阻塞主流程
_kr_handleSilentInvitation();
}
_logStepTiming('设备登录完成');
return true;
},
@@ -496,6 +513,9 @@ class KRAppRunData {
KRLogUtil.kr_i('✅ Token和账号验证通过,设置登录状态为true', tag: 'AppRunData');
KRLogUtil.kr_i('📊 恢复账号: ${kr_account.value}', tag: 'AppRunData');
kr_isLogin.value = true;
// 🔧 新增:恢复登录状态后也尝试检测一次静默邀请(重要:针对已安装后启动的情况)
_kr_handleSilentInvitation();
} else {
// 账号信息为空,清理旧数据
KRLogUtil.kr_w('⚠️ 账号信息为空,清理该条用户数据', tag: 'AppRunData');
@@ -633,4 +653,125 @@ class KRAppRunData {
KRLogUtil.kr_e('📚 [AppRunData] 错误堆栈: $stackTrace', tag: 'AppRunData');
}
}
/// 处理静默邀请
Future<void> _kr_handleSilentInvitation() async {
KRLogUtil.kr_i('🚀 开始处理静默邀请...', tag: 'AppRunData');
String? inviteCode;
// 1. 先检查是否有之前通过唤醒/安装暂存的邀请码
if (_kr_pendingInviteCode != null && _kr_pendingInviteCode!.isNotEmpty) {
inviteCode = _kr_pendingInviteCode;
KRLogUtil.kr_i('📎 使用暂存的待绑定邀请码: $inviteCode', tag: 'AppRunData');
}
// 2. 如果没有暂存码,尝试从平台环境获取
if (inviteCode == null || inviteCode!.isEmpty) {
try {
if (Platform.isMacOS || Platform.isWindows) {
inviteCode = await KRDeviceUtil().kr_getDesktopInviteCode();
} else if (Platform.isAndroid || Platform.isIOS) {
final Completer<String?> completer = Completer<String?>();
OpeninstallFlutterPlugin().install((data) async {
final code = kr_parseInviteCodeFromData(data);
KRLogUtil.kr_i('收到 OpenInstall 安装数据: $data, 解析出邀请码: $code', tag: 'AppRunData');
if (!completer.isCompleted) completer.complete(code);
});
inviteCode = await completer.future
.timeout(const Duration(seconds: 8), onTimeout: () => null);
}
} catch (e) {
KRLogUtil.kr_e('获取静默邀请码异常: $e', tag: 'AppRunData');
}
}
if (inviteCode != null && inviteCode!.isNotEmpty) {
KRLogUtil.kr_i('🔍 最终识别到邀请码: $inviteCode', tag: 'AppRunData');
if (inviteDebugMode) {
// Debug 模式下弹出对话框确认
final bool isDesktop = Platform.isMacOS || Platform.isWindows;
await HIDialog.show(
title: isDesktop ? '调试:唤醒识别到邀请码' : '调试:邀请码绑定确认',
message: isDesktop
? '桌面端识别到邀请码:$inviteCode\n是否进行绑定?'
: '识别到邀请码:$inviteCode\n是否进行绑定?',
confirmText: isDesktop ? '绑定' : '确认绑定',
cancelText: isDesktop ? '跳过' : '取消',
onConfirm: () async {
await _kr_performInviteBinding(inviteCode!);
_kr_pendingInviteCode = null; // 绑定后清除
},
onCancel: () {
_kr_pendingInviteCode = null; // 取消也清除,避免重复弹窗
},
);
} else {
// 正式环境静默绑定
await _kr_performInviteBinding(inviteCode!);
_kr_pendingInviteCode = null; // 绑定后清除
}
} else {
KRLogUtil.kr_i('⚠️ 未识别到有效的邀请码,跳外静默绑定', tag: 'AppRunData');
}
}
/// 执行邀请码绑定请求
Future<void> _kr_performInviteBinding(String inviteCode) async {
KRLogUtil.kr_i('🚀 准备执行邀请码绑定: $inviteCode', tag: 'AppRunData');
final result =
await KRUserApi().hi_inviteCode(inviteCode, isSilentInvite: true);
result.fold(
(error) => KRLogUtil.kr_w('❌ 邀请绑定失败: ${error.msg}', tag: 'AppRunData'),
(_) => KRLogUtil.kr_i('✅ 邀请绑定成功', tag: 'AppRunData'),
);
}
/// 公开方法:直接处理 OpenInstall 返回的原始数据(用于唤醒等场景)
Future<void> kr_handleOpenInstallData(Map<dynamic, dynamic> data) async {
final code = kr_parseInviteCodeFromData(data);
if (code != null && code.isNotEmpty) {
KRLogUtil.kr_i('🔗 收到 OpenInstall 原始参数并触发解析: $code', tag: 'AppRunData');
// 暂存该邀请码
_kr_pendingInviteCode = code;
// 如果当前已经是登录状态,则由于是唤醒(Hot Start)触发,直接按业务逻辑处理
if (kr_isLogin.value) {
KRLogUtil.kr_i('✅ 用户已登录,立即处理唤醒绑定', tag: 'AppRunData');
if (inviteDebugMode) {
await HIDialog.show(
title: '调试:唤醒识别到邀请码',
message: '唤醒数据解析到邀请码:$code\n是否进行绑定?',
confirmText: '绑定',
cancelText: '跳过',
onConfirm: () async {
await _kr_performInviteBinding(code);
_kr_pendingInviteCode = null;
},
onCancel: () => _kr_pendingInviteCode = null,
);
} else {
_kr_performInviteBinding(code).then((_) => _kr_pendingInviteCode = null);
}
} else {
KRLogUtil.kr_i('⏳ 用户未登录,已暂存邀请码,等待登录完成后自动绑定', tag: 'AppRunData');
}
}
}
/// 从 OpenInstall 数据中解析邀请码
String? kr_parseInviteCodeFromData(Map<dynamic, dynamic> data) {
try {
if (data.containsKey('bindData')) {
final bindDataStr = data['bindData'] as String?;
if (bindDataStr != null && bindDataStr.isNotEmpty) {
final Map<String, dynamic> bindData = jsonDecode(bindDataStr);
return bindData['inviteCode']?.toString();
}
}
} catch (e) {
KRLogUtil.kr_e('解析 OpenInstall 数据中邀请码失败: $e', tag: 'AppRunData');
}
return null;
}
}