fix:苹果一键登录页面,深浅色
No files matched your search
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="52dp"
|
||||||
|
android:height="60dp"
|
||||||
|
android:viewportWidth="104"
|
||||||
|
android:viewportHeight="120">
|
||||||
|
<path
|
||||||
|
android:fillColor="#F7F7F7"
|
||||||
|
android:pathData="M31,48L15,60L31,72L36,67L27,61L43,61L43,53L27,53L36,53Z" />
|
||||||
|
</vector>
|
||||||
|
After Width: | Height: | Size: 394 B |
|
After Width: | Height: | Size: 751 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 479 B |
|
After Width: | Height: | Size: 369 B |
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="52dp"
|
||||||
|
android:height="60dp"
|
||||||
|
android:viewportWidth="104"
|
||||||
|
android:viewportHeight="120">
|
||||||
|
<path
|
||||||
|
android:fillColor="#252529"
|
||||||
|
android:pathData="M31,48L15,60L31,72L36,67L27,61L43,61L43,53L27,53L36,53Z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"images":[{"filename":"jinzhi_onetap_back.png","idiom":"universal","scale":"1x"},{"idiom":"universal","scale":"2x"},{"idiom":"universal","scale":"3x"}],"info":{"author":"xcode","version":1}}
|
||||||
|
After Width: | Height: | Size: 394 B |
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "filename" : "jinzhi_onetap_back_day.png", "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "jinzhi_onetap_back_day@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "jinzhi_onetap_back_day@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 382 B |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 463 B |
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "filename" : "jinzhi_onetap_back_night.png", "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "jinzhi_onetap_back_night@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "jinzhi_onetap_back_night@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 322 B |
|
After Width: | Height: | Size: 598 B |
|
After Width: | Height: | Size: 461 B |
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "filename" : "jinzhi_onetap_button.png", "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "jinzhi_onetap_button@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "jinzhi_onetap_button@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{ "filename" : "jinzhi_onetap_button_pressed.png", "idiom" : "universal", "scale" : "1x" },
|
||||||
|
{ "filename" : "jinzhi_onetap_button_pressed@2x.png", "idiom" : "universal", "scale" : "2x" },
|
||||||
|
{ "filename" : "jinzhi_onetap_button_pressed@3x.png", "idiom" : "universal", "scale" : "3x" }
|
||||||
|
],
|
||||||
|
"info" : { "author" : "xcode", "version" : 1 }
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1 @@
|
|||||||
|
{"images":[{"filename":"jinzhi_onetap_checked.png","idiom":"universal","scale":"1x"},{"idiom":"universal","scale":"2x"},{"idiom":"universal","scale":"3x"}],"info":{"author":"xcode","version":1}}
|
||||||
|
After Width: | Height: | Size: 479 B |
@@ -0,0 +1 @@
|
|||||||
|
{"images":[{"filename":"jinzhi_onetap_unchecked.png","idiom":"universal","scale":"1x"},{"idiom":"universal","scale":"2x"},{"idiom":"universal","scale":"3x"}],"info":{"author":"xcode","version":1}}
|
||||||
|
After Width: | Height: | Size: 369 B |
@@ -29,11 +29,17 @@ abstract final class UmengService {
|
|||||||
|
|
||||||
static Future<void> _init() async {
|
static Future<void> _init() async {
|
||||||
await DeviceHeaderService.to.ensureInitialized();
|
await DeviceHeaderService.to.ensureInitialized();
|
||||||
await UmengCommonSdk.initCommon(
|
// Keep the carrier-login launch path independent of UMeng Common's
|
||||||
|
// network initialization. Waiting for this future is what left the login
|
||||||
|
// overlay spinning forever in the base project; U-Verify is configured
|
||||||
|
// immediately below by initOneKeyLoginIfAllowed, as in Yanting.
|
||||||
|
unawaited(
|
||||||
|
UmengCommonSdk.initCommon(
|
||||||
UmengConfig.androidAppKey,
|
UmengConfig.androidAppKey,
|
||||||
UmengConfig.iosAppKey,
|
UmengConfig.iosAppKey,
|
||||||
DeviceHeaderService.to.commonHeaders()['User-Channel'] ?? 'default',
|
DeviceHeaderService.to.commonHeaders()['User-Channel'] ?? 'default',
|
||||||
UmengConfig.androidPushSecret,
|
UmengConfig.androidPushSecret,
|
||||||
|
).catchError((Object _) {}),
|
||||||
);
|
);
|
||||||
_ready = true;
|
_ready = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,8 @@ import '../../../common/router/app_router.dart';
|
|||||||
import 'auth_controller.dart';
|
import 'auth_controller.dart';
|
||||||
import 'one_key_login_service.dart';
|
import 'one_key_login_service.dart';
|
||||||
|
|
||||||
/// Shared login entry: launch the carrier-owned authorization page first and
|
|
||||||
/// use the Flutter SMS page only as a deliberate fallback.
|
|
||||||
abstract final class LoginLauncher {
|
abstract final class LoginLauncher {
|
||||||
static final _service = OneKeyLoginService();
|
static final OneKeyLoginService _service = OneKeyLoginService();
|
||||||
static bool _starting = false;
|
static bool _starting = false;
|
||||||
|
|
||||||
static Future<void> start(
|
static Future<void> start(
|
||||||
@@ -21,45 +19,12 @@ abstract final class LoginLauncher {
|
|||||||
}) async {
|
}) async {
|
||||||
if (_starting) return;
|
if (_starting) return;
|
||||||
_starting = true;
|
_starting = true;
|
||||||
final dismissLoading = _showLoading(context);
|
|
||||||
try {
|
|
||||||
final observer = _OneKeyLoginLifecycleObserver(_service);
|
|
||||||
WidgetsBinding.instance.addObserver(observer);
|
|
||||||
late final OneKeyLoginOutcome outcome;
|
|
||||||
try {
|
|
||||||
outcome = await _service.start(
|
|
||||||
context: context,
|
|
||||||
verifyToken: ref.read(authProvider.notifier).verifyOneKeyLogin,
|
|
||||||
onNativeRequestDispatched: dismissLoading,
|
|
||||||
onAuthPageOpened: dismissLoading,
|
|
||||||
);
|
|
||||||
} catch (_) {
|
|
||||||
outcome = OneKeyLoginOutcome.fallback;
|
|
||||||
} finally {
|
|
||||||
WidgetsBinding.instance.removeObserver(observer);
|
|
||||||
}
|
|
||||||
if (!context.mounted) return;
|
|
||||||
if (outcome == OneKeyLoginOutcome.success) {
|
|
||||||
context.go(next?.isNotEmpty == true ? next! : AppRoutes.profile);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (outcome == OneKeyLoginOutcome.fallback ||
|
|
||||||
outcome == OneKeyLoginOutcome.unsupported) {
|
|
||||||
final target = next == null || next.isEmpty
|
|
||||||
? AppRoutes.login
|
|
||||||
: '${AppRoutes.login}?next=${Uri.encodeComponent(next)}';
|
|
||||||
if (GoRouterState.of(context).matchedLocation != AppRoutes.login) {
|
|
||||||
await context.push(target);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
dismissLoading();
|
|
||||||
_starting = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static VoidCallback _showLoading(BuildContext context) {
|
var loadingVisible = false;
|
||||||
var visible = true;
|
|
||||||
|
void showLoading() {
|
||||||
|
if (loadingVisible || !context.mounted) return;
|
||||||
|
loadingVisible = true;
|
||||||
unawaited(
|
unawaited(
|
||||||
showDialog<void>(
|
showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -71,21 +36,78 @@ abstract final class LoginLauncher {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return () {
|
}
|
||||||
if (visible && context.mounted) {
|
|
||||||
|
void dismissLoading() {
|
||||||
|
if (!loadingVisible || !context.mounted) return;
|
||||||
|
loadingVisible = false;
|
||||||
Navigator.of(context, rootNavigator: true).pop();
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
}
|
}
|
||||||
visible = false;
|
|
||||||
};
|
try {
|
||||||
|
final observer = _OneKeyLoginLifecycleObserver(_service);
|
||||||
|
var observerAdded = false;
|
||||||
|
late final OneKeyLoginOutcome outcome;
|
||||||
|
try {
|
||||||
|
WidgetsBinding.instance.addObserver(observer);
|
||||||
|
observerAdded = true;
|
||||||
|
// Carrier-auth initialization only begins after this explicit login
|
||||||
|
// action. Keep the loading indicator visible while the native SDK
|
||||||
|
// determines whether one-key login can be launched.
|
||||||
|
showLoading();
|
||||||
|
outcome = await _service.start(
|
||||||
|
context: context,
|
||||||
|
verifyToken: ref.read(authProvider.notifier).verifyOneKeyLogin,
|
||||||
|
// The native carrier page follows the device, while linked H5 pages
|
||||||
|
// retain the appearance selected inside the app.
|
||||||
|
appIsDark: Theme.of(context).brightness == Brightness.dark,
|
||||||
|
nativeIsDark:
|
||||||
|
MediaQuery.platformBrightnessOf(context) == Brightness.dark,
|
||||||
|
onNativeRequestDispatched: dismissLoading,
|
||||||
|
onAuthPageOpened: dismissLoading,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
dismissLoading();
|
||||||
|
if (observerAdded) WidgetsBinding.instance.removeObserver(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
switch (outcome) {
|
||||||
|
case OneKeyLoginOutcome.success:
|
||||||
|
if (next != null && next.isNotEmpty) GoRouter.of(context).go(next);
|
||||||
|
return;
|
||||||
|
case OneKeyLoginOutcome.cancelled:
|
||||||
|
case OneKeyLoginOutcome.busy:
|
||||||
|
return;
|
||||||
|
case OneKeyLoginOutcome.fallback:
|
||||||
|
case OneKeyLoginOutcome.unsupported:
|
||||||
|
await openSmsLogin(context, next: next);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
_starting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> openSmsLogin(
|
||||||
|
BuildContext context, {
|
||||||
|
String? next,
|
||||||
|
Future<void> Function()? onReturn,
|
||||||
|
}) async {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (GoRouterState.of(context).matchedLocation == AppRoutes.login) return;
|
||||||
|
final location = next == null || next.isEmpty
|
||||||
|
? AppRoutes.login
|
||||||
|
: '${AppRoutes.login}?next=${Uri.encodeComponent(next)}';
|
||||||
|
await GoRouter.of(context).push(location);
|
||||||
|
if (context.mounted) await onReturn?.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _OneKeyLoginLifecycleObserver with WidgetsBindingObserver {
|
class _OneKeyLoginLifecycleObserver with WidgetsBindingObserver {
|
||||||
_OneKeyLoginLifecycleObserver(this._service);
|
_OneKeyLoginLifecycleObserver(this.service);
|
||||||
final OneKeyLoginService _service;
|
final OneKeyLoginService service;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
if (state == AppLifecycleState.resumed) _service.onAppResumed();
|
if (state == AppLifecycleState.resumed) service.onAppResumed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,139 +7,286 @@ import 'package:umeng_verify_sdk/umeng_verify_sdk.dart';
|
|||||||
|
|
||||||
import '../../../common/config/app_h5_urls.dart';
|
import '../../../common/config/app_h5_urls.dart';
|
||||||
import '../../../common/tracking/umeng_service.dart';
|
import '../../../common/tracking/umeng_service.dart';
|
||||||
|
import '../utils/one_key_login_result.dart';
|
||||||
|
|
||||||
enum OneKeyLoginOutcome { success, fallback, cancelled, unsupported, busy }
|
enum OneKeyLoginOutcome { success, fallback, cancelled, unsupported, busy }
|
||||||
|
|
||||||
/// Runs only after an explicit user login action. Do not add an availability
|
enum OneKeyLoginPlatform { android, ios, unsupported }
|
||||||
/// probe or `accelerateLoginPage` outside this class: Xiaomi rejects that as
|
|
||||||
/// background carrier-auth prefetch.
|
|
||||||
class OneKeyLoginService {
|
|
||||||
Future<OneKeyLoginOutcome>? _operation;
|
|
||||||
bool _authPageOpened = false;
|
|
||||||
bool _terminal = false;
|
|
||||||
Timer? _resumeCancellation;
|
|
||||||
void Function(OneKeyLoginOutcome)? _completeCurrent;
|
|
||||||
|
|
||||||
Future<OneKeyLoginOutcome> start({
|
typedef OneKeyLoginCallback = void Function(Object? result);
|
||||||
required BuildContext context,
|
|
||||||
required Future<void> Function(String token) verifyToken,
|
abstract interface class OneKeyLoginGateway {
|
||||||
VoidCallback? onNativeRequestDispatched,
|
OneKeyLoginPlatform get platform;
|
||||||
VoidCallback? onAuthPageOpened,
|
|
||||||
}) {
|
void setTokenCallback(OneKeyLoginCallback? callback);
|
||||||
if (_operation != null) return Future.value(OneKeyLoginOutcome.busy);
|
|
||||||
_terminal = false;
|
void setUiCallback(OneKeyLoginCallback? callback);
|
||||||
_authPageOpened = false;
|
|
||||||
final operation = _start(
|
void setWidgetCallback(OneKeyLoginCallback? callback);
|
||||||
context: context,
|
|
||||||
verifyToken: verifyToken,
|
Future<void> requestLogin(UMCustomModel uiConfig);
|
||||||
onNativeRequestDispatched: onNativeRequestDispatched,
|
|
||||||
onAuthPageOpened: onAuthPageOpened,
|
void closeAuthPage();
|
||||||
|
|
||||||
|
void clearCallbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
class UmengOneKeyLoginGateway implements OneKeyLoginGateway {
|
||||||
|
const UmengOneKeyLoginGateway();
|
||||||
|
|
||||||
|
@override
|
||||||
|
OneKeyLoginPlatform get platform {
|
||||||
|
if (kIsWeb) return OneKeyLoginPlatform.unsupported;
|
||||||
|
if (Platform.isAndroid) return OneKeyLoginPlatform.android;
|
||||||
|
if (Platform.isIOS) return OneKeyLoginPlatform.ios;
|
||||||
|
return OneKeyLoginPlatform.unsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setTokenCallback(OneKeyLoginCallback? callback) {
|
||||||
|
if (platform == OneKeyLoginPlatform.ios) {
|
||||||
|
UmengVerifySdk.getLoginTokenCallback(callback);
|
||||||
|
} else if (platform == OneKeyLoginPlatform.android) {
|
||||||
|
UmengVerifySdk.setTokenResultCallback_android(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setUiCallback(OneKeyLoginCallback? callback) {
|
||||||
|
if (platform == OneKeyLoginPlatform.android) {
|
||||||
|
UmengVerifySdk.setUIClickCallback_android(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void setWidgetCallback(OneKeyLoginCallback? callback) {
|
||||||
|
if (platform == OneKeyLoginPlatform.ios) {
|
||||||
|
UmengVerifySdk.getWidgetEventCallback(callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> requestLogin(UMCustomModel uiConfig) async {
|
||||||
|
await UmengVerifySdk.getLoginTokenWithTimeout(3, uiConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void closeAuthPage() {
|
||||||
|
if (platform == OneKeyLoginPlatform.ios) {
|
||||||
|
unawaited(
|
||||||
|
UmengVerifySdk.cancelLoginVCAnimated(
|
||||||
|
true,
|
||||||
|
).catchError((Object _) => null),
|
||||||
);
|
);
|
||||||
_operation = operation;
|
} else if (platform == OneKeyLoginPlatform.android) {
|
||||||
return operation.whenComplete(() {
|
UmengVerifySdk.quitLoginPage_android();
|
||||||
_operation = null;
|
}
|
||||||
_completeCurrent = null;
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clearCallbacks() {
|
||||||
|
UmengVerifySdk.getLoginTokenCallback(null);
|
||||||
|
UmengVerifySdk.getWidgetEventCallback(null);
|
||||||
|
UmengVerifySdk.setTokenResultCallback_android(null);
|
||||||
|
UmengVerifySdk.setUIClickCallback_android(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class OneKeyLoginSdkResult {
|
||||||
|
const OneKeyLoginSdkResult({
|
||||||
|
required this.outcome,
|
||||||
|
this.token,
|
||||||
|
this.code,
|
||||||
|
this.reason,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final OneKeyLoginOutcome outcome;
|
||||||
|
final String? token;
|
||||||
|
final String? code;
|
||||||
|
final String? reason;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<OneKeyLoginOutcome> _start({
|
class OneKeyLoginSdkSession {
|
||||||
required BuildContext context,
|
OneKeyLoginSdkSession(
|
||||||
required Future<void> Function(String token) verifyToken,
|
this._gateway, {
|
||||||
VoidCallback? onNativeRequestDispatched,
|
this.onNativeRequestDispatched,
|
||||||
VoidCallback? onAuthPageOpened,
|
this.onAuthPageOpened,
|
||||||
}) async {
|
});
|
||||||
if (kIsWeb || (!Platform.isAndroid && !Platform.isIOS)) {
|
|
||||||
return OneKeyLoginOutcome.unsupported;
|
|
||||||
}
|
|
||||||
final uiConfig = _uiConfig(context);
|
|
||||||
await UmengService.initOneKeyLoginIfAllowed();
|
|
||||||
final completer = Completer<OneKeyLoginOutcome>();
|
|
||||||
|
|
||||||
void complete(OneKeyLoginOutcome outcome) {
|
final OneKeyLoginGateway _gateway;
|
||||||
if (_terminal) return;
|
final VoidCallback? onNativeRequestDispatched;
|
||||||
|
final VoidCallback? onAuthPageOpened;
|
||||||
|
final _result = Completer<OneKeyLoginSdkResult>();
|
||||||
|
var _started = false;
|
||||||
|
var _terminal = false;
|
||||||
|
var _closed = false;
|
||||||
|
var _authPageOpened = false;
|
||||||
|
Timer? _resumeCancellation;
|
||||||
|
|
||||||
|
Future<OneKeyLoginSdkResult> start(UMCustomModel uiConfig) {
|
||||||
|
if (_started) return _result.future;
|
||||||
|
_started = true;
|
||||||
|
|
||||||
|
if (_gateway.platform == OneKeyLoginPlatform.unsupported) {
|
||||||
|
_complete(
|
||||||
|
const OneKeyLoginSdkResult(
|
||||||
|
outcome: OneKeyLoginOutcome.unsupported,
|
||||||
|
reason: 'unsupported_platform',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return _result.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Availability checking already pre-accelerates the carrier page. Start
|
||||||
|
// the token request directly here; the SDK performs its own preflight when
|
||||||
|
// needed and reports its timeout/error through the registered callback.
|
||||||
|
unawaited(_startNativeLogin(uiConfig));
|
||||||
|
return _result.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _startNativeLogin(UMCustomModel uiConfig) async {
|
||||||
|
try {
|
||||||
|
_gateway.setTokenCallback(_handleTokenResult);
|
||||||
|
_gateway.setWidgetCallback((Object? _) {});
|
||||||
|
_gateway.setUiCallback(_handleUiResult);
|
||||||
|
final nativeRequest = _gateway.requestLogin(uiConfig);
|
||||||
|
onNativeRequestDispatched?.call();
|
||||||
|
await nativeRequest;
|
||||||
|
} catch (_) {
|
||||||
|
final completed = _complete(
|
||||||
|
const OneKeyLoginSdkResult(
|
||||||
|
outcome: OneKeyLoginOutcome.unsupported,
|
||||||
|
reason: 'launch_error',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (completed) close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleUiResult(Object? result) {
|
||||||
|
final code = extractUmengLoginCode(result);
|
||||||
|
if (isUmengCancel(result)) {
|
||||||
|
_complete(
|
||||||
|
OneKeyLoginSdkResult(
|
||||||
|
outcome: OneKeyLoginOutcome.cancelled,
|
||||||
|
code: code,
|
||||||
|
reason: 'cancel',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if (isUmengManualSwitch(result)) {
|
||||||
|
_complete(
|
||||||
|
OneKeyLoginSdkResult(
|
||||||
|
outcome: OneKeyLoginOutcome.fallback,
|
||||||
|
code: code,
|
||||||
|
reason: 'manual_switch',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleTokenResult(Object? result) {
|
||||||
|
final code = extractUmengLoginCode(result);
|
||||||
|
// On iOS, 700002 only means the user tapped the authorization page's
|
||||||
|
// login button. It is followed by a separate 600000 (or 600xxx) token
|
||||||
|
// callback, so completing the session here discards that callback.
|
||||||
|
if (_gateway.platform == OneKeyLoginPlatform.ios && code == '700002') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Android `checkEnvAvailable_android` reports 600024 through the same
|
||||||
|
// token callback. It means the terminal supports authentication, not that
|
||||||
|
// this login attempt failed; wait for the actual login callback instead.
|
||||||
|
if (isUmengEnvironmentAvailable(result)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isUmengAuthPage(result)) {
|
||||||
|
if (_authPageOpened) return;
|
||||||
|
_authPageOpened = true;
|
||||||
|
onAuthPageOpened?.call();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isUmengCancel(result)) {
|
||||||
|
_complete(
|
||||||
|
OneKeyLoginSdkResult(
|
||||||
|
outcome: OneKeyLoginOutcome.cancelled,
|
||||||
|
code: code,
|
||||||
|
reason: 'cancel',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isUmengManualSwitch(result)) {
|
||||||
|
_complete(
|
||||||
|
OneKeyLoginSdkResult(
|
||||||
|
outcome: OneKeyLoginOutcome.fallback,
|
||||||
|
code: code,
|
||||||
|
reason: 'manual_switch',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isUmengFallbackLoginCode(result)) {
|
||||||
|
_complete(
|
||||||
|
OneKeyLoginSdkResult(
|
||||||
|
outcome: OneKeyLoginOutcome.fallback,
|
||||||
|
code: code,
|
||||||
|
reason: 'sdk_fallback',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!isUmengTokenSuccess(result) || result is! Map) {
|
||||||
|
_complete(
|
||||||
|
OneKeyLoginSdkResult(
|
||||||
|
outcome: OneKeyLoginOutcome.fallback,
|
||||||
|
code: code,
|
||||||
|
reason: 'token_failed',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final token = result['token']?.toString();
|
||||||
|
if (token == null || token.isEmpty) {
|
||||||
|
_complete(
|
||||||
|
OneKeyLoginSdkResult(
|
||||||
|
outcome: OneKeyLoginOutcome.fallback,
|
||||||
|
code: code,
|
||||||
|
reason: 'token_empty',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_complete(
|
||||||
|
OneKeyLoginSdkResult(
|
||||||
|
outcome: OneKeyLoginOutcome.success,
|
||||||
|
token: token,
|
||||||
|
code: code,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _complete(OneKeyLoginSdkResult result) {
|
||||||
|
if (_terminal) return false;
|
||||||
_terminal = true;
|
_terminal = true;
|
||||||
_resumeCancellation?.cancel();
|
_resumeCancellation?.cancel();
|
||||||
_resumeCancellation = null;
|
_resumeCancellation = null;
|
||||||
_clearCallbacks();
|
_gateway.clearCallbacks();
|
||||||
completer.complete(outcome);
|
_result.complete(result);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_completeCurrent = complete;
|
void cancel() {
|
||||||
|
_complete(
|
||||||
void handleUiResult(Object? result) {
|
const OneKeyLoginSdkResult(
|
||||||
final code = _code(result);
|
outcome: OneKeyLoginOutcome.cancelled,
|
||||||
if (code == '700000') complete(OneKeyLoginOutcome.cancelled);
|
reason: 'disposed',
|
||||||
if (code == '700001') complete(OneKeyLoginOutcome.fallback);
|
),
|
||||||
}
|
|
||||||
|
|
||||||
void handleTokenResult(Object? result) {
|
|
||||||
final code = _code(result);
|
|
||||||
// iOS 700002 is only the native button click; token follows later.
|
|
||||||
if (Platform.isIOS && code == '700002') return;
|
|
||||||
// 600024 is Android environment availability, not a login result.
|
|
||||||
if (code == '600024') return;
|
|
||||||
if (code == '600001') {
|
|
||||||
if (!_authPageOpened) {
|
|
||||||
_authPageOpened = true;
|
|
||||||
onAuthPageOpened?.call();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (code == '700000') {
|
|
||||||
complete(OneKeyLoginOutcome.cancelled);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (code == '700001') {
|
|
||||||
complete(OneKeyLoginOutcome.fallback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (code != '600000' || result is! Map) {
|
|
||||||
complete(OneKeyLoginOutcome.fallback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final token = result['token']?.toString();
|
|
||||||
if (token == null || token.isEmpty) {
|
|
||||||
complete(OneKeyLoginOutcome.fallback);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
unawaited(() async {
|
|
||||||
try {
|
|
||||||
await verifyToken(token);
|
|
||||||
complete(OneKeyLoginOutcome.success);
|
|
||||||
} catch (_) {
|
|
||||||
complete(OneKeyLoginOutcome.fallback);
|
|
||||||
} finally {
|
|
||||||
_closeNativePage();
|
|
||||||
}
|
|
||||||
}());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Platform.isIOS) {
|
|
||||||
UmengVerifySdk.getLoginTokenCallback(handleTokenResult);
|
|
||||||
UmengVerifySdk.getWidgetEventCallback((_) {});
|
|
||||||
} else {
|
|
||||||
UmengVerifySdk.setTokenResultCallback_android(handleTokenResult);
|
|
||||||
UmengVerifySdk.setUIClickCallback_android(handleUiResult);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
final request = UmengVerifySdk.getLoginTokenWithTimeout(3, uiConfig);
|
|
||||||
onNativeRequestDispatched?.call();
|
|
||||||
unawaited(
|
|
||||||
request.catchError((Object _) {
|
|
||||||
complete(OneKeyLoginOutcome.unsupported);
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
return await completer.future.timeout(
|
close();
|
||||||
const Duration(seconds: 20),
|
|
||||||
onTimeout: () => OneKeyLoginOutcome.fallback,
|
|
||||||
);
|
|
||||||
} finally {
|
|
||||||
_clearCallbacks();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onAppResumed() {
|
void onAppResumed() {
|
||||||
if (!Platform.isAndroid ||
|
if (_gateway.platform != OneKeyLoginPlatform.android ||
|
||||||
!_authPageOpened ||
|
!_authPageOpened ||
|
||||||
_terminal ||
|
_terminal ||
|
||||||
_resumeCancellation != null) {
|
_resumeCancellation != null) {
|
||||||
@@ -147,72 +294,263 @@ class OneKeyLoginService {
|
|||||||
}
|
}
|
||||||
_resumeCancellation = Timer(const Duration(milliseconds: 500), () {
|
_resumeCancellation = Timer(const Duration(milliseconds: 500), () {
|
||||||
_resumeCancellation = null;
|
_resumeCancellation = null;
|
||||||
if (_terminal) return;
|
final completed = _complete(
|
||||||
_completeCurrent?.call(OneKeyLoginOutcome.cancelled);
|
const OneKeyLoginSdkResult(
|
||||||
_closeNativePage();
|
outcome: OneKeyLoginOutcome.cancelled,
|
||||||
|
reason: 'auth_page_returned',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (completed) close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
UMCustomModel _uiConfig(BuildContext context) {
|
void close() {
|
||||||
const ink = Color(0xFF202124);
|
if (_closed) return;
|
||||||
const muted = Color(0xFF8A8F98);
|
_closed = true;
|
||||||
const background = Color(0xFFFFFFFF);
|
_gateway.closeAuthPage();
|
||||||
final model = UMCustomModel()
|
}
|
||||||
..logoIsHidden = true
|
}
|
||||||
..numberColor = ink.toARGB32()
|
|
||||||
..numberFont = 30
|
class OneKeyLoginService {
|
||||||
..navTitle = ['', Colors.transparent.toARGB32(), 0]
|
OneKeyLoginService({
|
||||||
..navColor = background.toARGB32()
|
OneKeyLoginGateway gateway = const UmengOneKeyLoginGateway(),
|
||||||
..backgroundColor_ios = background.toARGB32()
|
Future<void> Function() initializeSdk =
|
||||||
..sloganText = ['', background.toARGB32(), 0]
|
UmengService.initOneKeyLoginIfAllowed,
|
||||||
..privacyPreText = '我已阅读并同意'
|
}) : _gateway = gateway,
|
||||||
..privacyOne = ['《隐私政策》', AppH5Urls.privacyUrl]
|
_initializeSdk = initializeSdk;
|
||||||
..privacyTwo = ['《用户协议》', AppH5Urls.userProtocolUrl]
|
|
||||||
..privacyColors = [muted.toARGB32(), ink.toARGB32()]
|
final OneKeyLoginGateway _gateway;
|
||||||
..changeBtnTitle = ['其他手机号登录', ink.toARGB32(), 16]
|
final Future<void> Function() _initializeSdk;
|
||||||
..loginBtnText = ['本机号码一键登录', Colors.white.toARGB32(), 16]
|
OneKeyLoginSdkSession? _activeSession;
|
||||||
..privacyFont = 12
|
Future<OneKeyLoginOutcome>? _activeOperation;
|
||||||
..checkBoxIsChecked = false;
|
_OneKeyLoginPhase _phase = _OneKeyLoginPhase.idle;
|
||||||
if (Platform.isAndroid) {
|
int _generation = 0;
|
||||||
model.numberFrame = [-1, 156, -1, -1];
|
|
||||||
model.loginBtnFrame = [-1, 283, -1, -1, 52];
|
Future<OneKeyLoginOutcome> start({
|
||||||
model.changeBtnFrame = [368, -1, -1, -1];
|
required BuildContext context,
|
||||||
model.privacyFrame = [-1, 427, -1, -1];
|
required Future<void> Function(String loginToken) verifyToken,
|
||||||
} else {
|
required bool appIsDark,
|
||||||
final title = UMCustomWidget('title', UMCustomWidgetType.textView)
|
required bool nativeIsDark,
|
||||||
|
VoidCallback? onNativeRequestDispatched,
|
||||||
|
VoidCallback? onAuthPageOpened,
|
||||||
|
}) {
|
||||||
|
if (_phase == _OneKeyLoginPhase.authPageVisible ||
|
||||||
|
_phase == _OneKeyLoginPhase.verifying) {
|
||||||
|
return Future<OneKeyLoginOutcome>.value(OneKeyLoginOutcome.busy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_phase == _OneKeyLoginPhase.launching) {
|
||||||
|
_activeSession?.cancel();
|
||||||
|
_activeSession = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final generation = ++_generation;
|
||||||
|
_phase = _OneKeyLoginPhase.launching;
|
||||||
|
final operation = _start(
|
||||||
|
generation: generation,
|
||||||
|
context: context,
|
||||||
|
verifyToken: verifyToken,
|
||||||
|
appIsDark: appIsDark,
|
||||||
|
nativeIsDark: nativeIsDark,
|
||||||
|
onNativeRequestDispatched: () {
|
||||||
|
if (generation != _generation) return;
|
||||||
|
onNativeRequestDispatched?.call();
|
||||||
|
},
|
||||||
|
onAuthPageOpened: () {
|
||||||
|
if (generation != _generation) return;
|
||||||
|
_phase = _OneKeyLoginPhase.authPageVisible;
|
||||||
|
onAuthPageOpened?.call();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_activeOperation = operation;
|
||||||
|
return operation.whenComplete(() {
|
||||||
|
if (generation == _generation && identical(_activeOperation, operation)) {
|
||||||
|
_activeOperation = null;
|
||||||
|
_phase = _OneKeyLoginPhase.idle;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<OneKeyLoginOutcome> _start({
|
||||||
|
required int generation,
|
||||||
|
required BuildContext context,
|
||||||
|
required Future<void> Function(String loginToken) verifyToken,
|
||||||
|
required bool appIsDark,
|
||||||
|
required bool nativeIsDark,
|
||||||
|
VoidCallback? onNativeRequestDispatched,
|
||||||
|
VoidCallback? onAuthPageOpened,
|
||||||
|
}) async {
|
||||||
|
if (_gateway.platform == OneKeyLoginPlatform.unsupported) {
|
||||||
|
return OneKeyLoginOutcome.unsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _initializeSdk();
|
||||||
|
if (generation != _generation) return OneKeyLoginOutcome.cancelled;
|
||||||
|
if (!context.mounted) return OneKeyLoginOutcome.cancelled;
|
||||||
|
final uiConfig = _buildUiConfig(context, appIsDark, nativeIsDark);
|
||||||
|
final session = OneKeyLoginSdkSession(
|
||||||
|
_gateway,
|
||||||
|
onNativeRequestDispatched: onNativeRequestDispatched,
|
||||||
|
onAuthPageOpened: onAuthPageOpened,
|
||||||
|
);
|
||||||
|
_activeSession = session;
|
||||||
|
try {
|
||||||
|
final sdkResult = await session.start(uiConfig);
|
||||||
|
if (!context.mounted) {
|
||||||
|
session.close();
|
||||||
|
return OneKeyLoginOutcome.cancelled;
|
||||||
|
}
|
||||||
|
if (sdkResult.outcome != OneKeyLoginOutcome.success) {
|
||||||
|
session.close();
|
||||||
|
return sdkResult.outcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
final token = sdkResult.token;
|
||||||
|
if (token == null || token.isEmpty) {
|
||||||
|
session.close();
|
||||||
|
return OneKeyLoginOutcome.fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (generation == _generation) {
|
||||||
|
_phase = _OneKeyLoginPhase.verifying;
|
||||||
|
}
|
||||||
|
await verifyToken(token);
|
||||||
|
session.close();
|
||||||
|
return OneKeyLoginOutcome.success;
|
||||||
|
} catch (_) {
|
||||||
|
session.close();
|
||||||
|
return OneKeyLoginOutcome.fallback;
|
||||||
|
} finally {
|
||||||
|
if (identical(_activeSession, session)) {
|
||||||
|
_activeSession = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void cancel() {
|
||||||
|
_generation++;
|
||||||
|
_activeSession?.cancel();
|
||||||
|
_activeSession = null;
|
||||||
|
_activeOperation = null;
|
||||||
|
_phase = _OneKeyLoginPhase.idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAppResumed() {
|
||||||
|
_activeSession?.onAppResumed();
|
||||||
|
}
|
||||||
|
|
||||||
|
UMCustomModel _buildUiConfig(
|
||||||
|
BuildContext context,
|
||||||
|
bool appIsDark,
|
||||||
|
bool nativeIsDark,
|
||||||
|
) {
|
||||||
|
final width = MediaQuery.of(context).size.width;
|
||||||
|
// UVerify follows Android's system theme, which can differ from Flutter's
|
||||||
|
// active theme. iOS colors are fixed at launch, so it receives a palette
|
||||||
|
// resolved from the device appearance.
|
||||||
|
final useDarkPalette =
|
||||||
|
_gateway.platform == OneKeyLoginPlatform.ios && nativeIsDark;
|
||||||
|
final pageBg = useDarkPalette ? const Color(0xFF0F0F0F) : Colors.white;
|
||||||
|
final titleInk = useDarkPalette
|
||||||
|
? const Color(0xFFF0F0F0)
|
||||||
|
: const Color(0xFF252529);
|
||||||
|
final hintInk = useDarkPalette
|
||||||
|
? const Color(0xFFA1A1AA)
|
||||||
|
: const Color(0xFF8E8E93);
|
||||||
|
const buttonInk = Color(0xFF0F1A00);
|
||||||
|
final transparent = Colors.transparent.toARGB32();
|
||||||
|
final titleColor = titleInk.toARGB32();
|
||||||
|
final hintColor = hintInk.toARGB32();
|
||||||
|
final buttonTextColor = buttonInk.toARGB32();
|
||||||
|
final bgColor = pageBg.toARGB32();
|
||||||
|
final protocolUrl = AppH5UrlsHelper.buildUrlWithNight(
|
||||||
|
AppH5Urls.userProtocolUrl,
|
||||||
|
appIsDark,
|
||||||
|
);
|
||||||
|
final privacyUrl = AppH5UrlsHelper.buildUrlWithNight(
|
||||||
|
AppH5Urls.privacyUrl,
|
||||||
|
appIsDark,
|
||||||
|
);
|
||||||
|
|
||||||
|
final model = UMCustomModel();
|
||||||
|
final titleWidget = UMCustomWidget('title', UMCustomWidgetType.textView)
|
||||||
..title = '金值'
|
..title = '金值'
|
||||||
..titleFont = 26
|
..titleFont = 28
|
||||||
..titleColor = ink.toARGB32()
|
|
||||||
..width = MediaQuery.sizeOf(context).width.toInt()
|
|
||||||
..height = 54
|
|
||||||
..top = 26
|
|
||||||
..textAlignment = UMCustomWidgetTextAlignmentType.center
|
..textAlignment = UMCustomWidgetTextAlignmentType.center
|
||||||
|
..top = 26
|
||||||
|
..width = width.toInt()
|
||||||
|
..height = 54
|
||||||
|
..titleColor = titleColor
|
||||||
..isClickEnable = false
|
..isClickEnable = false
|
||||||
..rootViewId = UMRootViewId.body;
|
..rootViewId = UMRootViewId.body;
|
||||||
model.customWidget = [title.toJsonMap()];
|
|
||||||
|
model.logoIsHidden = true;
|
||||||
|
model.numberColor = titleColor;
|
||||||
|
model.numberFont = 32;
|
||||||
|
model.sloganText = ['', bgColor, 0];
|
||||||
|
model.navTitle = ['', transparent, 0];
|
||||||
|
model.privacyPreText = '我已阅读并同意';
|
||||||
|
model.privacyOne = ['《隐私政策》', privacyUrl];
|
||||||
|
model.privacyTwo = ['《用户协议》', protocolUrl];
|
||||||
|
model.privacyColors = [hintColor, titleColor];
|
||||||
|
model.changeBtnTitle = ['其他手机号登录', titleColor, 16];
|
||||||
|
model.privacyOperatorPreText = '《';
|
||||||
|
model.privacyOperatorSufText = '》';
|
||||||
|
model.autoHideLoginLoading = false;
|
||||||
|
model.customWidget = [titleWidget.toJsonMap()];
|
||||||
|
|
||||||
|
if (_gateway.platform == OneKeyLoginPlatform.ios) {
|
||||||
|
model.loginBtnText = ['本机号码一键登录', buttonTextColor, 20];
|
||||||
|
model.loginBtnBgImgs_ios = [
|
||||||
|
'jinzhi_onetap_button',
|
||||||
|
'jinzhi_onetap_button',
|
||||||
|
'jinzhi_onetap_button_pressed',
|
||||||
|
];
|
||||||
|
model.backgroundColor_ios = bgColor;
|
||||||
|
model.navColor = bgColor;
|
||||||
|
// Always use our paired assets so both appearances keep the same iOS
|
||||||
|
// chevron geometry and differ only in color.
|
||||||
|
model.navBackImage = useDarkPalette
|
||||||
|
? 'jinzhi_onetap_back_night'
|
||||||
|
: 'jinzhi_onetap_back_day';
|
||||||
|
model.privacyConectTexts = ['和', ''];
|
||||||
model.privacyFont = 13;
|
model.privacyFont = 13;
|
||||||
model.checkBoxWH = 20;
|
model.checkBoxImages = [
|
||||||
|
'jinzhi_onetap_unchecked',
|
||||||
|
'jinzhi_onetap_checked',
|
||||||
|
];
|
||||||
|
// 原生 SDK 会按该尺寸直接放大图片;36pt 会让协议勾选显得过重。
|
||||||
|
model.checkBoxWH = 22;
|
||||||
model.checkBoxIsChecked = true;
|
model.checkBoxIsChecked = true;
|
||||||
|
} else {
|
||||||
|
model.numberFrame = [-1, 156, -1, -1];
|
||||||
|
// Keep the same Android SDK frame as caizhi. The capsule's visible
|
||||||
|
// geometry is determined by the native drawable's intrinsic size.
|
||||||
|
model.loginBtnFrame = [-1, 283, -1, -1, 56];
|
||||||
|
model.changeBtnFrame = [368, -1, -1, -1];
|
||||||
|
model.privacyFrame = [-1, 427, -1, -1];
|
||||||
|
// Android only accepts width/height here. The back drawable itself uses
|
||||||
|
// caizhi's 48×120 transparent canvas to position the arrow correctly.
|
||||||
|
model.navBackButtonFrame = [-1, -1, 72, 120];
|
||||||
|
model.loginBtnText = ['本机号码一键登录', buttonTextColor, 16];
|
||||||
|
model.loginBtnBgImg_android = 'jinzhi_onetap_button';
|
||||||
|
model.navBackImage = 'jinzhi_onetap_back_daynight';
|
||||||
|
model.navColor = transparent;
|
||||||
|
model.privacyFont = 12;
|
||||||
|
model.privacyConectTexts = ['和', '、'];
|
||||||
|
model.checkBoxWH = 14;
|
||||||
|
model.checkBoxIsChecked = false;
|
||||||
|
model.checkBoxImages = [
|
||||||
|
'jinzhi_onetap_unchecked',
|
||||||
|
'jinzhi_onetap_checked',
|
||||||
|
];
|
||||||
|
model.privacyNavColor = bgColor;
|
||||||
|
model.privacyNavTitleColor = titleColor;
|
||||||
|
model.privacyNavTitleFont = 16;
|
||||||
}
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _code(Object? result) => result is Map
|
|
||||||
? (result['code'] ?? result['resultCode'] ?? '').toString()
|
|
||||||
: '';
|
|
||||||
|
|
||||||
void _clearCallbacks() {
|
|
||||||
UmengVerifySdk.getLoginTokenCallback(null);
|
|
||||||
UmengVerifySdk.getWidgetEventCallback(null);
|
|
||||||
UmengVerifySdk.setTokenResultCallback_android(null);
|
|
||||||
UmengVerifySdk.setUIClickCallback_android(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _closeNativePage() {
|
enum _OneKeyLoginPhase { idle, launching, authPageVisible, verifying }
|
||||||
if (Platform.isIOS) {
|
|
||||||
unawaited(UmengVerifySdk.cancelLoginVCAnimated(true));
|
|
||||||
} else {
|
|
||||||
UmengVerifySdk.quitLoginPage_android();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
const _umengFallbackCodes = {
|
||||||
|
'600002',
|
||||||
|
'600004',
|
||||||
|
'600005',
|
||||||
|
'600007',
|
||||||
|
'600008',
|
||||||
|
'600009',
|
||||||
|
'600010',
|
||||||
|
'600011',
|
||||||
|
'600012',
|
||||||
|
'600013',
|
||||||
|
'600014',
|
||||||
|
'600015',
|
||||||
|
'600017',
|
||||||
|
'600021',
|
||||||
|
'600023',
|
||||||
|
'600025',
|
||||||
|
'600026',
|
||||||
|
};
|
||||||
|
|
||||||
|
String? extractUmengLoginCode(Object? result) {
|
||||||
|
if (result is! Map) return null;
|
||||||
|
final code = result['code'] ?? result['resultCode'];
|
||||||
|
final value = code?.toString();
|
||||||
|
return value == null || value.isEmpty ? null : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isUmengTokenSuccess(Object? result) =>
|
||||||
|
extractUmengLoginCode(result) == '600000';
|
||||||
|
bool isUmengAuthPage(Object? result) =>
|
||||||
|
extractUmengLoginCode(result) == '600001';
|
||||||
|
bool isUmengEnvironmentAvailable(Object? result) =>
|
||||||
|
extractUmengLoginCode(result) == '600024';
|
||||||
|
bool isUmengCancel(Object? result) => extractUmengLoginCode(result) == '700000';
|
||||||
|
bool isUmengManualSwitch(Object? result) =>
|
||||||
|
extractUmengLoginCode(result) == '700001';
|
||||||
|
bool isUmengFallbackLoginCode(Object? result) {
|
||||||
|
final code = extractUmengLoginCode(result);
|
||||||
|
return code != null && _umengFallbackCodes.contains(code);
|
||||||
|
}
|
||||||