124 lines
4.0 KiB
Dart
124 lines
4.0 KiB
Dart
import 'dart:convert';
|
||
import 'dart:math';
|
||
|
||
import 'package:android_id/android_id.dart';
|
||
import 'package:client_proxy_framework/client_proxy_framework.dart';
|
||
import 'package:crypto/crypto.dart' show md5;
|
||
import 'package:device_info_plus/device_info_plus.dart';
|
||
import 'package:flutter/foundation.dart';
|
||
import 'package:shared_preferences/shared_preferences.dart';
|
||
|
||
import '../user/user_state.dart';
|
||
|
||
const _prefsKeyFallbackDeviceId = 'persisted_device_id';
|
||
|
||
Future<String> _persistedFallbackDeviceId() async {
|
||
final prefs = await SharedPreferences.getInstance();
|
||
var id = prefs.getString(_prefsKeyFallbackDeviceId);
|
||
if (id != null && id.isNotEmpty) return id;
|
||
final random = Random.secure();
|
||
final bytes = List<int>.generate(16, (_) => random.nextInt(256));
|
||
id = base64UrlEncode(bytes).replaceAll('=', '');
|
||
await prefs.setString(_prefsKeyFallbackDeviceId, id);
|
||
return id;
|
||
}
|
||
|
||
class AppAuthCallbacks implements AuthServiceCallbacks {
|
||
/// 与 app_client 一致:Android 用 Settings.Secure.ANDROID_ID(android_id 包,非 Build.ID);
|
||
/// iOS 用 identifierForVendor;失败或其它平台用 SharedPreferences 持久化随机 id。
|
||
@override
|
||
Future<String> getDeviceId() async {
|
||
switch (defaultTargetPlatform) {
|
||
case TargetPlatform.android:
|
||
final androidId = await const AndroidId().getId();
|
||
if (androidId != null && androidId.isNotEmpty) {
|
||
return androidId;
|
||
}
|
||
return _persistedFallbackDeviceId();
|
||
case TargetPlatform.iOS:
|
||
final ios = await DeviceInfoPlugin().iosInfo;
|
||
final idfv = ios.identifierForVendor;
|
||
if (idfv != null && idfv.isNotEmpty) return idfv;
|
||
return _persistedFallbackDeviceId();
|
||
default:
|
||
return _persistedFallbackDeviceId();
|
||
}
|
||
}
|
||
|
||
@override
|
||
String computeSign(String deviceId) {
|
||
return md5.convert(utf8.encode(deviceId)).toString().toUpperCase();
|
||
}
|
||
|
||
@override
|
||
void onLoginSuccess(FastLoginResponse data) {
|
||
UserState.applyLogin(
|
||
userId: data.userId,
|
||
credits: data.credits,
|
||
avatar: data.avatar,
|
||
userName: data.userName,
|
||
);
|
||
}
|
||
|
||
@override
|
||
void onCommonInfoLoaded(CommonInfoResponse data) {
|
||
if (data.credits != null) UserState.setCredits(data.credits!);
|
||
if (data.avatar != null) UserState.setAvatar(data.avatar!);
|
||
if (data.userName != null) UserState.setUserName(data.userName!);
|
||
}
|
||
|
||
@override
|
||
void onLoginFailed(String msg) {
|
||
debugPrint('[AuthService] Login failed: $msg');
|
||
}
|
||
}
|
||
|
||
/// 应用侧登录封装;Adjust 归因桥已由框架 [FrameworkAuthService.init] 默认注册。
|
||
class AuthService {
|
||
static final _authCallbacks = AppAuthCallbacks();
|
||
|
||
static Future<void> init() async {
|
||
FrameworkAuthService.init(_authCallbacks);
|
||
await FrameworkAuthService.start();
|
||
}
|
||
|
||
static Future<void> get loginComplete => FrameworkAuthService.loginComplete;
|
||
|
||
/// 登录流程是否已结束(含 fast_login + common_info 链路);用于遮罩,勿用 [loginComplete] 的 Future(首帧可能为 null)。
|
||
static ValueNotifier<bool> get isLoginComplete =>
|
||
FrameworkAuthService.isLoginComplete;
|
||
|
||
/// 在 **登录成功**([UserState.userId] 非空)且 [isLoginComplete] 为 `true` 之后执行 [onReady]。
|
||
///
|
||
/// 若登录流程已结束但无用户(失败),调用一次 [onFailed]。若 [isLoginComplete] 已为 `true`,同步执行。
|
||
///
|
||
/// 返回在 [State.dispose] 中调用的取消函数,避免界面已销毁仍监听。
|
||
static VoidCallback whenLoginSucceeded({
|
||
required VoidCallback onReady,
|
||
VoidCallback? onFailed,
|
||
}) {
|
||
void runOnce() {
|
||
final uid = UserState.userId.value;
|
||
if (uid != null && uid.isNotEmpty) {
|
||
onReady();
|
||
} else {
|
||
onFailed?.call();
|
||
}
|
||
}
|
||
|
||
void listener() {
|
||
if (!isLoginComplete.value) return;
|
||
isLoginComplete.removeListener(listener);
|
||
runOnce();
|
||
}
|
||
|
||
if (isLoginComplete.value) {
|
||
runOnce();
|
||
return () {};
|
||
}
|
||
|
||
isLoginComplete.addListener(listener);
|
||
return () => isLoginComplete.removeListener(listener);
|
||
}
|
||
}
|