login_main_cubit.dart 11.9 KB
import 'dart:async';
import 'dart:io';

import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/user_auth_repository.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:appframe/utils/login_util.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:fluwx/fluwx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';

class LoginMainState extends Equatable {
  final bool agreed;
  final bool showAgreed;
  final bool showNeedWechatForApple;
  final int loginType; // 1=Wechat,2=Apple
  final String appleUserIdentifier;
  final bool loading;
  final bool wechatInstalled;
  final bool showPrivacyFirstTime;

  const LoginMainState({
    this.agreed = false,
    this.showAgreed = false,
    this.showNeedWechatForApple = false,
    this.loginType = 0,
    this.appleUserIdentifier = '',
    this.loading = false,
    this.wechatInstalled = false,
    this.showPrivacyFirstTime = false,
  });

  LoginMainState copyWith({
    bool? agreed,
    bool? showAgreed,
    bool? showNeedWechatForApple,
    int? loginType,
    String? appleUserIdentifier,
    bool? loading,
    bool? wechatInstalled,
    bool? showPrivacyFirstTime,
  }) {
    return LoginMainState(
      agreed: agreed ?? this.agreed,
      showAgreed: showAgreed ?? this.showAgreed,
      showNeedWechatForApple: showNeedWechatForApple ?? this.showNeedWechatForApple,
      loginType: loginType ?? this.loginType,
      appleUserIdentifier: appleUserIdentifier ?? this.appleUserIdentifier,
      loading: loading ?? this.loading,
      wechatInstalled: wechatInstalled ?? this.wechatInstalled,
      showPrivacyFirstTime: showPrivacyFirstTime ?? this.showPrivacyFirstTime,
    );
  }

  @override
  List<Object?> get props => [
        agreed,
        showAgreed,
        showNeedWechatForApple,
        loginType,
        appleUserIdentifier,
        loading,
        wechatInstalled,
        showPrivacyFirstTime,
      ];
}

class LoginMainCubit extends Cubit<LoginMainState> with WidgetsBindingObserver {
  late final Fluwx _fluwx;
  late final FluwxCancelable _fluwxCancelable;
  late final WechatAuthRepository _wechatAuthRepository;
  late final UserAuthRepository _userAuthRepository;

  // 是否正在等待微信授权回调;用于多微信选择器取消、用户中途返回等异常场景的兜底
  bool _waitingWechatAuth = false;
  // 总超时(请求已投递但长时间无任何回调时强制复位)
  Timer? _wechatAuthTimeoutTimer;
  // App 回到前台后的短延时(用于判定系统层取消)
  Timer? _wechatAuthResumeTimer;

  LoginMainCubit(super.initialState) {
    WidgetsBinding.instance.addObserver(this);
    _fluwx = getIt.get<Fluwx>();

    // 处理微信安装检测
    _fluwx.isWeChatInstalled.then((value) {
      emit(state.copyWith(wechatInstalled: value));
    });

    _fluwxCancelable = _fluwx.addSubscriber(_responseListener);
    _wechatAuthRepository = getIt.get<WechatAuthRepository>();
    _userAuthRepository = getIt.get<UserAuthRepository>();

    // 检查是否首次打开登录页面,显示个人信息收集提示
    if(Platform.isIOS) {
      _checkFirstTimePrivacy();
    }
  }

  // 检查是否首次打开,显示个人信息收集提示
  void _checkFirstTimePrivacy() async {
    var sharedPreferences = getIt.get<SharedPreferences>();
    var hasShownPrivacy = sharedPreferences.getBool(Constant.hasShownPrivacyFirstTimeKey) ?? false;
    if (!hasShownPrivacy) {
      emit(state.copyWith(showPrivacyFirstTime: true));
    }
  }

  // 确认已显示个人信息收集提示
  void confirmPrivacyFirstTime() async {
    var sharedPreferences = getIt.get<SharedPreferences>();
    await sharedPreferences.setBool(Constant.hasShownPrivacyFirstTimeKey, true);
    emit(state.copyWith(showPrivacyFirstTime: false));
  }

  void toggleAgreed(bool value) {
    emit(state.copyWith(agreed: value));
  }

  void confirmAgreed() {
    emit(state.copyWith(agreed: true, showAgreed: false));
    if (state.loginType == 1) {
      wechatAuth();
    } else if (state.loginType == 2) {
      appleAuth();
    }
  }

  void cancelAgreed() {
    emit(state.copyWith(showAgreed: false));
  }

  ///
  /// 通过 Apple 登录
  ///
  void appleAuth() async {
    emit(state.copyWith(loginType: 2));

    if (!state.agreed) {
      emit(state.copyWith(showAgreed: true));
      return;
    }

    AuthorizationCredentialAppleID credential = await SignInWithApple.getAppleIDCredential(
      scopes: [
        AppleIDAuthorizationScopes.email,
        AppleIDAuthorizationScopes.fullName,
      ],
    );

    debugPrint('用户唯一标识: ${credential.userIdentifier}');
    debugPrint('用户邮箱: ${credential.email}');
    debugPrint('用户姓名: ${credential.givenName} ${credential.familyName}');
    // 应将 credential.authorizationCode 发送给后端服务器,由后端与 Apple 服务器验证该码的有效性
    debugPrint('授权码 (authorizationCode): ${credential.authorizationCode}');
    debugPrint('身份令牌 (identityToken): ${credential.identityToken}');

    if (credential.userIdentifier == null) {
      Fluttertoast.showToast(msg: '授权失败', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
      return;
    }

    ///
    /// 处理 credential,发送到服务器获取用户信息。有关联用户信息,则登录成功;没有关联用户信息,则弹出提示框提示用户授权微信认证
    ///
    var resultData = await _userAuthRepository.appleLogin(
        credential.userIdentifier!, credential.authorizationCode, credential.identityToken!) as Map<String, dynamic>?;
    if (resultData == null) {
      Fluttertoast.showToast(msg: '登录请求处理失败', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
      return;
    }
    if (resultData['code'] != 0) {
      Fluttertoast.showToast(msg: resultData['error'], gravity: ToastGravity.TOP, backgroundColor: Colors.red);
      return;
    }

    var data = resultData['data'] as Map<String, dynamic>;
    int binding = resultData['binding'];
    var visitor = binding == 1 ? 0 : 1;
    if (visitor == 1) {
      var sharedPreferences = getIt.get<SharedPreferences>();
      sharedPreferences.setString('auth_visitor_type', 'apple');
      sharedPreferences.setString('auth_visitor_id', credential.userIdentifier!);
    }
    LoginUtil.handleLoginSuccess(data, visitor, 'router');
    // if (binding == 1) {
    //   _handleLoginSuccess(data, 0);
    // } else {
    //   // 未绑定时,也会返回 sessionCode 和 userCode
    //   if (state.wechatInstalled) {
    //     // 已安装微信APP,直接拉起微信授权,不使用 sessionCode 和 userCode
    //     // 设置 appleUserIdentifier 状态,通知用户需要授权微信认证
    //     emit(state.copyWith(appleUserIdentifier: data['appleUid']!, showNeedWechatForApple: true));
    //   } else {
    //     // 未安装微信APP,使用 sessionCode 和 userCode
    //     _handleLoginSuccess(data, 1);
    //   }
    // }
  }

  // Future<void> wechatAuthForApple() async {
  //   emit(state.copyWith(showNeedWechatForApple: false, loginType: 2));
  //
  //   var authResult = await _fluwx.authBy(
  //     which: NormalAuth(scope: 'snsapi_userinfo', state: 'wechat_sdk_test'),
  //   );
  //
  //   if (!authResult) {
  //     Fluttertoast.showToast(msg: '微信授权处理失败', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
  //     return;
  //   }
  //
  //   // 控制显示加载框,并启动等待微信授权回调的兜底机制
  //   emit(state.copyWith(loading: true));
  //   _startWechatAuthWaiting();
  // }

  Future<void> wechatAuth() async {
    emit(state.copyWith(loginType: 1));

    if (!state.agreed) {
      emit(state.copyWith(showAgreed: true));
      return;
    }

    var authResult = await _fluwx.authBy(
      which: NormalAuth(scope: 'snsapi_userinfo', state: 'wechat_sdk_test'),
    );

    if (!authResult) {
      Fluttertoast.showToast(msg: '微信授权处理失败', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
      return;
    }

    // 控制显示加载框,并启动等待微信授权回调的兜底机制
    emit(state.copyWith(loading: true));
    _startWechatAuthWaiting();
  }

  void goLoginPhone() {
    router.go('/loginPhone');
  }

  void goLoginQr() {
    router.go('/loginQr');
  }

  void _responseListener(WeChatResponse response) async {
    if (response is WeChatAuthResponse) {
      // 收到正式回调,结束等待状态(不弹提示)
      _finishWechatAuthWaiting();
      if (response.code == null || response.code == '') {
        emit(state.copyWith(loading: false));
        return;
      }

      var resultData = await _wechatAuthRepository.codeToSk(response.code!) as Map<String, dynamic>?;

      // 请求接口异常
      if (resultData == null) {
        Fluttertoast.showToast(msg: '登录请求处理失败', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
        return;
      }

      // 状态码错误
      if (resultData['resultCode'] != '001') {
        Fluttertoast.showToast(msg: '登录请求状态失败', gravity: ToastGravity.TOP, backgroundColor: Colors.red);
        return;
      }

      var data = resultData['data'] as Map<String, dynamic>;
      LoginUtil.handleLoginSuccess(data, 0, 'router');
    }
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState appState) {
    // App 回到前台时,如果仍在等待微信授权回调,给真实回调一个短窗口;
    // 窗口内仍未收到 WeChatAuthResponse,则认定为系统层取消(典型场景:
    // 设备装有多个微信,系统弹出选择器后用户点击取消,微信 SDK 根本不会回调)。
    if (appState == AppLifecycleState.resumed && _waitingWechatAuth) {
      _wechatAuthResumeTimer?.cancel();
      _wechatAuthResumeTimer = Timer(const Duration(milliseconds: 1500), () {
        if (_waitingWechatAuth) {
          _finishWechatAuthWaiting(reason: _WechatAuthFinishReason.cancel);
        }
      });
    }
  }

  // 标记进入"等待微信授权回调"状态,并启动总超时
  void _startWechatAuthWaiting() {
    _waitingWechatAuth = true;
    _wechatAuthTimeoutTimer?.cancel();
    _wechatAuthTimeoutTimer = Timer(const Duration(seconds: 30), () {
      if (_waitingWechatAuth) {
        _finishWechatAuthWaiting(reason: _WechatAuthFinishReason.timeout);
      }
    });
  }

  // 结束等待,统一复位 loading 状态并按场景给出提示
  void _finishWechatAuthWaiting({
    _WechatAuthFinishReason reason = _WechatAuthFinishReason.response,
  }) {
    if (!_waitingWechatAuth) return;
    _waitingWechatAuth = false;
    _wechatAuthTimeoutTimer?.cancel();
    _wechatAuthResumeTimer?.cancel();
    _wechatAuthTimeoutTimer = null;
    _wechatAuthResumeTimer = null;
    if (state.loading) {
      emit(state.copyWith(loading: false));
    }
    switch (reason) {
      case _WechatAuthFinishReason.cancel:
        Fluttertoast.showToast(msg: '已取消微信授权', gravity: ToastGravity.TOP);
        break;
      case _WechatAuthFinishReason.timeout:
        Fluttertoast.showToast(
          msg: '微信授权超时,请重试',
          gravity: ToastGravity.TOP,
          backgroundColor: Colors.red,
        );
        break;
      case _WechatAuthFinishReason.response:
      // 正常收到回调时不弹提示
        break;
    }
  }

  @override
  Future<void> close() {
    WidgetsBinding.instance.removeObserver(this);
    _wechatAuthTimeoutTimer?.cancel();
    _wechatAuthResumeTimer?.cancel();
    _fluwxCancelable.cancel();
    return super.close();
  }


}

enum _WechatAuthFinishReason {
  // 收到了正式的 WeChatAuthResponse
  response,
  // App 已回到前台但仍无回调,判定为系统层/微信侧取消
  cancel,
  // 总超时
  timeout,
}