change_phone_controller.dart 4.65 KB
import 'dart:async';

import 'package:doublefeel_flutter/app/modules/change_phone/services/change_phone_gateway.dart';
import 'package:doublefeel_flutter/app/routes/app_pages.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/data/local/local_storage.dart';
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

enum ChangePhoneStep { verifyOldPhone, bindNewPhone }

class ChangePhoneController extends GetxController {
  ChangePhoneController({
    required UserPreferencesStorage userPreferencesStorage,
    required LocalStorage localStorage,
    required ChangePhoneGateway gateway,
  })  : _userPreferencesStorage = userPreferencesStorage,
        _localStorage = localStorage,
        _gateway = gateway;

  final UserPreferencesStorage _userPreferencesStorage;
  final LocalStorage _localStorage;
  final ChangePhoneGateway _gateway;

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

  final step = ChangePhoneStep.verifyOldPhone.obs;
  final phoneInput = ''.obs;
  final codeInput = ''.obs;
  final isRequestingCode = false.obs;
  final isSubmitting = false.obs;
  final countdownSeconds = 0.obs;
  Timer? _countdownTimer;

  String get cleanPhone => phoneInput.value.replaceAll(RegExp(r'\s+'), '');

  bool get isPhoneValid => RegExp(r'^\d{11}$').hasMatch(cleanPhone);

  bool get canRequestCode =>
      isPhoneValid && !isRequestingCode.value && countdownSeconds.value == 0;

  bool get canSubmit =>
      isPhoneValid && codeInput.value.isNotEmpty && !isSubmitting.value;

  bool get isOldPhoneStep => step.value == ChangePhoneStep.verifyOldPhone;

  bool get canChangePhone => _localStorage.lastLoginMethod == 'phone';

  String get maskedOldPhone {
    final phone = _userPreferencesStorage
        .preferences.value.meUserInfo?.telephone
        ?.replaceAll(RegExp(r'\s+'), '');
    if (phone == null || phone.length != 11) return '123****1212';
    return '${phone.substring(0, 3)}****${phone.substring(7)}';
  }

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

  @override
  void onReady() {
    super.onReady();
    if (!canChangePhone) {
      AppToast.show('仅支持手机号登录的账号更换手机号');
      Get.back();
    }
  }

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

    isRequestingCode.value = true;
    try {
      await _gateway.requestVerificationCode(
        phone: cleanPhone,
        isOldPhone: isOldPhoneStep,
      );
      _startCountdown();
      codeFocusNode.requestFocus();
    } on UnsupportedError {
      AppToast.show('手机号更换服务暂未开通');
    } finally {
      isRequestingCode.value = false;
    }
  }

  Future<void> submit() async {
    if (!canSubmit) return;

    isSubmitting.value = true;
    try {
      if (isOldPhoneStep) {
        await _gateway.verifyOldPhone(
          phone: cleanPhone,
          verificationCode: codeInput.value,
        );
        _showNewPhoneStep();
      } else {
        await _gateway.bindNewPhone(
          phone: cleanPhone,
          verificationCode: codeInput.value,
        );
        Get.back();
      }
    } on UnsupportedError {
      AppToast.show('手机号更换服务暂未开通');
    } finally {
      isSubmitting.value = false;
    }
  }

  void clearPhone() => phoneController.clear();

  void openHelp() => Get.toNamed(Routes.HELP);

  void _onPhoneChanged() {
    final newPhone = phoneController.text;
    if (phoneInput.value != newPhone &&
        (countdownSeconds.value > 0 || codeInput.value.isNotEmpty)) {
      _resetVerificationState();
    }
    phoneInput.value = newPhone;
  }

  void _showNewPhoneStep() {
    step.value = ChangePhoneStep.bindNewPhone;
    phoneController.clear();
    codeController.clear();
    _resetVerificationState();
  }

  void _resetVerificationState() {
    _countdownTimer?.cancel();
    countdownSeconds.value = 0;
    codeController.clear();
  }

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

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