login_controller.dart 7.08 KB
import 'dart:async';
import 'package:doublefeel_flutter/core/constants/app_const.dart';
import 'package:doublefeel_flutter/l10n/gen/app_localizations.dart';
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/core/network/api/user_api.dart';
import 'package:doublefeel_flutter/core/network/api/vip_api.dart';
import 'package:doublefeel_flutter/core/services/user_state_service.dart';
import 'package:doublefeel_flutter/data/local/local_storage.dart';
import 'package:doublefeel_flutter/data/local/user_account_storage.dart';
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
import 'package:doublefeel_flutter/data/models/user/user_models.dart';
import 'package:doublefeel_flutter/data/models/vip/vip_info.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/app/routes/app_pages.dart';

class LoginController extends GetxController {
  final UserApi _userApi = Get.find<UserApi>();
  final VipApi _vipApi = Get.find<VipApi>();
  final UserPreferencesStorage _userPrefs = Get.find<UserPreferencesStorage>();
  final UserAccountStorage _userAccount = Get.find<UserAccountStorage>();
  final UserStateService _userStateService = Get.find<UserStateService>();

  final phoneController = TextEditingController();
  final codeController = TextEditingController();

  final phoneInput = ''.obs;
  final codeInput = ''.obs;

  final isSendingCode = false.obs;
  final countdownSeconds = 0.obs;
  Timer? _countdownTimer;

  final isLoggingIn = false.obs;
  final hasSentCode = false.obs;

  final termsChecked = false.obs;

  void toggleTermsChecked() {
    termsChecked.value = !termsChecked.value;
  }

  void onPhoneLoginPressed() {
    if (!termsChecked.value) {
      AppToast.show(AppLocalizations.of(Get.context!)!.loginAgreeToTermsToast);
      return;
    }
    Get.toNamed(AppRoutes.phoneLogin);
  }

  void onAppleLoginPressed() {
    if (!termsChecked.value) {
      AppToast.show(AppLocalizations.of(Get.context!)!.loginAgreeToTermsToast);
      return;
    }
    //todo: implement apple login
  }

  void onDebugPressed() {
    Get.toNamed(AppRoutes.debugEnvironment);
  }

  void openUserTerms() {
    Get.toNamed(
      AppRoutes.webview,
      parameters: {'url': AppConst.userTerms},
    );
  }

  void openPrivacyPolicy() {
    Get.toNamed(
      AppRoutes.webview,
      parameters: {'url': AppConst.privacyPolicy},
    );
  }

  @override
  void onInit() {
    super.onInit();
    phoneController.addListener(() {
      phoneInput.value = phoneController.text;
    });
    codeController.addListener(() {
      codeInput.value = codeController.text;
    });
  }

  @override
  void onClose() {
    _countdownTimer?.cancel();
    phoneController.dispose();
    codeController.dispose();
    super.onClose();
  }

  String get cleanPhone => phoneInput.value.replaceAll(' ', '');

  bool get canRequestCode =>
      cleanPhone.length == 11 &&
      !isSendingCode.value &&
      countdownSeconds.value == 0;

  bool get canLogin =>
      cleanPhone.length == 11 &&
      codeInput.value.isNotEmpty &&
      !isLoggingIn.value;

  Future<void> requestVerifyCode() async {
    if (!canRequestCode) return;

    if (cleanPhone.length < 11) {
      AppToast.show(AppLocalizations.of(Get.context!)!.phoneLoginInvalidPhone);
      return;
    }

    isSendingCode.value = true;
    final request = VerifyCodeRequest.login(cleanPhone);
    final result = await _userApi.requestVerifyCode(request);

    isSendingCode.value = false;
    if (result is AppSuccess<void>) {
      AppToast.show(
          AppLocalizations.of(Get.context!)!.phoneLoginCodeSentSuccess);
      _startCountdown();
    }
  }

  void _startCountdown() {
    hasSentCode.value = true;
    _countdownTimer?.cancel();
    countdownSeconds.value = 60;
    _countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
      if (countdownSeconds.value > 1) {
        countdownSeconds.value--;
      } else {
        countdownSeconds.value = 0;
        timer.cancel();
      }
    });
  }

  Future<void> login() async {
    if (!canLogin) return;

    if (cleanPhone.length < 11) {
      AppToast.show(AppLocalizations.of(Get.context!)!.phoneLoginInvalidPhone);
      return;
    }
    if (codeInput.value.isEmpty) {
      AppToast.show(AppLocalizations.of(Get.context!)!.phoneLoginInvalidCode);
      return;
    }

    isLoggingIn.value = true;
    final loginRes = await _userApi.login(
      telephone: cleanPhone,
      verifyCode: codeInput.value,
    );

    if (loginRes is AppSuccess<LoginResponse>) {
      final loginData = loginRes.data;
      final isRegister = loginData.isNewUser == true;
      String accessToken = loginData.accessToken;

      if (isRegister) {
        final regRes = await _userApi.register(
          telephone: cleanPhone,
          verifyCode: codeInput.value,
        );
        if (regRes is AppSuccess<RegisterResponse>) {
          accessToken = regRes.data.accessToken;
        } else {
          isLoggingIn.value = false;
          return;
        }
      }

      // Fetch user info and VIP info
      final userInfoResFuture = _userApi.getUserInfo(accessToken: accessToken);
      final vipInfoResFuture = isRegister
          ? Future.value(null)
          : _vipApi.getVipInfo(accessToken: accessToken);

      final results = await Future.wait([userInfoResFuture, vipInfoResFuture]);
      final userInfoRes = results[0];
      final vipInfoRes = results[1];

      if (userInfoRes is AppSuccess<UserInfoResponse>) {
        final me = userInfoRes.data;
        UserInfoResponse? partner;

        if ((me.pairId ?? 0) > 0) {
          final partnerRes =
              await _userApi.getPartnerUserInfo(accessToken: accessToken);
          if (partnerRes is AppSuccess<BoundUserInfoResponse>) {
            partner = partnerRes.data.partnerUserInfo;
          }
        }

        VipInfo? vip;
        if (vipInfoRes is AppSuccess<VipInfo>) {
          vip = vipInfoRes.data;
        }

        await _userPrefs.updateFromLogin(
          accessToken: accessToken,
          me: me,
          partner: partner,
          vip: vip,
        );

        await _userStateService.onLogin();

        // 用户登录成功即代表同意了协议,持久化到本地供冷启动时 SDK 初始化判断使用
        await Get.find<LocalStorage>().setTermsAgreed(true);

        isLoggingIn.value = false;

        // 根据引导完成状态决定跳转目标
        final userId = me.id ?? 0;
        if (_userAccount.hasCompletedOnboarding(userId)) {
          // 该账号已完成引导,直接进主页
          Get.offAllNamed(AppRoutes.home);
        } else {
          // 新用户或未完成引导,进入引导页(支持断点续做)
          final resumeStage = _userAccount.onboardingResumeStage(userId);
          Get.offAllNamed(
            AppRoutes.userOnboarding,
            arguments:
                resumeStage != null ? {'resumeStage': resumeStage} : null,
          );
        }
      } else {
        isLoggingIn.value = false;
      }
    } else {
      isLoggingIn.value = false;
    }
  }
}