login_qr_cubit.dart 5.47 KB
import 'dart:convert';

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

class LoginQrState extends Equatable {
  final int status;
  final Uint8List? image;
  final String tip;

  const LoginQrState({
    this.status = 0,
    this.image,
    this.tip = '',
  });

  LoginQrState copyWith({
    int? status,
    Uint8List? image,
    String? tip,
  }) {
    return LoginQrState(
      status: status ?? this.status,
      image: image ?? this.image,
      tip: tip ?? this.tip,
    );
  }

  @override
  List<Object?> get props => [
        status,
        image,
        tip,
      ];
}

class LoginQrCubit extends Cubit<LoginQrState> {
  late final Fluwx _fluwx;
  late final FluwxCancelable _fluwxCancelable;
  late final WechatAuthRepository _wechatAuthRepository;

  LoginQrCubit(super.initialState) {
    _fluwx = getIt.get<Fluwx>();
    _fluwxCancelable = _fluwx.addSubscriber(_responseListener);
    _wechatAuthRepository = getIt.get<WechatAuthRepository>();

    init();
  }

  Future<void> init() async {
    // sdk_ticket
    var resultData = await _wechatAuthRepository.getTicket() 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 sdkTicket = resultData['data'];

    // 当前时间戳
    var timestamp = DateTime.now().millisecondsSinceEpoch;
    var signature = _sig(Constant.wxAppId, '$timestamp', sdkTicket, '$timestamp');

    var authResult = await _fluwx.authBy(
      which: QRCode(
        appId: Constant.wxAppId,
        scope: 'snsapi_userinfo',
        nonceStr: '$timestamp',
        timestamp: '$timestamp',
        signature: signature,
      ),
    );

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

  void _responseListener(WeChatResponse response) async {
    if (response is WeChatAuthGotQRCodeResponse) {
      int? errCode = response.errCode;
      if (errCode != null && errCode == 0) {
        emit(state.copyWith(
          status: 1,
          image: response.qrCode,
          tip: '打开微信,扫描二维码登录',
        ));
      } else {
        emit(state.copyWith(tip: '错误 $errCode'));
      }
    } else if (response is WeChatQRCodeScannedResponse) {
      int? errCode = response.errCode;
      if (errCode != null && errCode == 0) {
        emit(state.copyWith(
          status: 2,
          tip: '在微信中轻触允许即可登录',
        ));
      } else {
        emit(state.copyWith(tip: '错误 $errCode'));
      }
    } else if (response is WeChatAuthByQRCodeFinishedResponse) {
      int? errCode = response.errCode;
      if (errCode != null && errCode == 0) {
        _doLogin(response.authCode!);
      } else if (errCode == -4) {
        // 用户主动取消
        emit(state.copyWith(
          status: 3,
          tip: '你已取消此次登录',
        ));
      } else {
        // 其它非 0 错误码(含二维码过期/超时/网络异常等),引导用户刷新
        emit(state.copyWith(
          status: 4,
          tip: '请刷新二维码,再次登录',
        ));
      }
    }
  }

  String _sig(String appId, String nonceStr, String sdkTicket, String timestamp) {
    String str = 'appid=$appId&noncestr=$nonceStr&sdk_ticket=$sdkTicket&timestamp=$timestamp';
    return sha1.convert(utf8.encode(str)).toString();
  }

  Future<void> _doLogin(String code) async {
    var resultData = await _wechatAuthRepository.codeToSk(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');
  }

  void goLoginMain() {
    router.go('/loginMain');
  }

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

  /// 刷新二维码:停止当前授权流程并重新走 init() 生成新的二维码
  Future<void> refresh() async {
    // 重置为加载中状态(tip 与初次进入区分,避免 Equatable 短路掉相同 state 的 emit)
    emit(const LoginQrState(status: 0, tip: '正在重新生成二维码...'));
    try {
      _fluwx.stopAuthByQRCode();
    } catch (e) {
      debugPrint('stopAuthByQRCode error: $e');
    }
    await init();
  }

  @override
  Future<void> close() {
    _fluwxCancelable.cancel();
    _fluwx.stopAuthByQRCode();
    return super.close();
  }
}