import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:umeng_verify_sdk/umeng_verify_sdk.dart'; import '../../../common/config/app_h5_urls.dart'; import '../../../common/tracking/umeng_service.dart'; enum OneKeyLoginOutcome { success, fallback, cancelled, unsupported, busy } /// Runs only after an explicit user login action. Do not add an availability /// probe or `accelerateLoginPage` outside this class: Xiaomi rejects that as /// background carrier-auth prefetch. class OneKeyLoginService { Future? _operation; bool _authPageOpened = false; bool _terminal = false; Timer? _resumeCancellation; void Function(OneKeyLoginOutcome)? _completeCurrent; Future start({ required BuildContext context, required Future Function(String token) verifyToken, VoidCallback? onNativeRequestDispatched, VoidCallback? onAuthPageOpened, }) { if (_operation != null) return Future.value(OneKeyLoginOutcome.busy); _terminal = false; _authPageOpened = false; final operation = _start( context: context, verifyToken: verifyToken, onNativeRequestDispatched: onNativeRequestDispatched, onAuthPageOpened: onAuthPageOpened, ); _operation = operation; return operation.whenComplete(() { _operation = null; _completeCurrent = null; }); } Future _start({ required BuildContext context, required Future Function(String token) verifyToken, VoidCallback? onNativeRequestDispatched, VoidCallback? onAuthPageOpened, }) async { if (kIsWeb || (!Platform.isAndroid && !Platform.isIOS)) { return OneKeyLoginOutcome.unsupported; } final uiConfig = _uiConfig(context); await UmengService.initOneKeyLoginIfAllowed(); final completer = Completer(); void complete(OneKeyLoginOutcome outcome) { if (_terminal) return; _terminal = true; _resumeCancellation?.cancel(); _resumeCancellation = null; _clearCallbacks(); completer.complete(outcome); } _completeCurrent = complete; void handleUiResult(Object? result) { final code = _code(result); if (code == '700000') complete(OneKeyLoginOutcome.cancelled); 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( const Duration(seconds: 20), onTimeout: () => OneKeyLoginOutcome.fallback, ); } finally { _clearCallbacks(); } } void onAppResumed() { if (!Platform.isAndroid || !_authPageOpened || _terminal || _resumeCancellation != null) { return; } _resumeCancellation = Timer(const Duration(milliseconds: 500), () { _resumeCancellation = null; if (_terminal) return; _completeCurrent?.call(OneKeyLoginOutcome.cancelled); _closeNativePage(); }); } UMCustomModel _uiConfig(BuildContext context) { const ink = Color(0xFF202124); const muted = Color(0xFF8A8F98); const background = Color(0xFFFFFFFF); final model = UMCustomModel() ..logoIsHidden = true ..numberColor = ink.toARGB32() ..numberFont = 30 ..navTitle = ['', Colors.transparent.toARGB32(), 0] ..navColor = background.toARGB32() ..backgroundColor_ios = background.toARGB32() ..sloganText = ['', background.toARGB32(), 0] ..privacyPreText = '我已阅读并同意' ..privacyOne = ['《隐私政策》', AppH5Urls.privacyUrl] ..privacyTwo = ['《用户协议》', AppH5Urls.userProtocolUrl] ..privacyColors = [muted.toARGB32(), ink.toARGB32()] ..changeBtnTitle = ['其他手机号登录', ink.toARGB32(), 16] ..loginBtnText = ['本机号码一键登录', Colors.white.toARGB32(), 16] ..privacyFont = 12 ..checkBoxIsChecked = false; if (Platform.isAndroid) { model.numberFrame = [-1, 156, -1, -1]; model.loginBtnFrame = [-1, 283, -1, -1, 52]; model.changeBtnFrame = [368, -1, -1, -1]; model.privacyFrame = [-1, 427, -1, -1]; } else { final title = UMCustomWidget('title', UMCustomWidgetType.textView) ..title = '金值' ..titleFont = 26 ..titleColor = ink.toARGB32() ..width = MediaQuery.sizeOf(context).width.toInt() ..height = 54 ..top = 26 ..textAlignment = UMCustomWidgetTextAlignmentType.center ..isClickEnable = false ..rootViewId = UMRootViewId.body; model.customWidget = [title.toJsonMap()]; model.privacyFont = 13; model.checkBoxWH = 20; model.checkBoxIsChecked = true; } 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() { if (Platform.isIOS) { unawaited(UmengVerifySdk.cancelLoginVCAnimated(true)); } else { UmengVerifySdk.quitLoginPage_android(); } } }