import SwiftUI import Combine // MARK: - EMailLogin ViewModel @MainActor class EMailLoginViewModel: ObservableObject { // MARK: - Published Properties @Published var email: String = "" @Published var verificationCode: String = "" @Published var codeCountdown: Int = 0 @Published var isLoading: Bool = false @Published var isCodeLoading: Bool = false @Published var errorMessage: String? @Published var loginStep: LoginStep = .input // MARK: - Callbacks var onBack: (() -> Void)? var onLoginSuccess: (() -> Void)? // MARK: - Private Properties private var cancellables = Set() private var timerCancellable: AnyCancellable? // MARK: - Enums enum LoginStep: Equatable { case input case completed } // MARK: - Computed Properties var isLoginButtonEnabled: Bool { return !isLoading && !email.isEmpty && !verificationCode.isEmpty } var getCodeButtonText: String { if codeCountdown > 0 { return "\(codeCountdown)s" } else { return "Get" } } var isCodeButtonEnabled: Bool { return !isCodeLoading && codeCountdown == 0 && !email.isEmpty } // MARK: - Public Methods func onBackTapped() { onBack?() } func onEmailChanged(_ newEmail: String) { email = newEmail } func onVerificationCodeChanged(_ newCode: String) { verificationCode = newCode } func onGetVerificationCodeTapped() { guard isCodeButtonEnabled else { return } isCodeLoading = true errorMessage = nil Task { do { let result = try await requestVerificationCode() await MainActor.run { self.handleCodeRequestResult(result) } } catch { await MainActor.run { self.handleCodeRequestError(error) } } } } func onLoginTapped() { guard isLoginButtonEnabled else { return } isLoading = true errorMessage = nil Task { do { let result = try await performLogin() await MainActor.run { self.handleLoginResult(result) } } catch { await MainActor.run { self.handleLoginError(error) } } } } func resetState() { email = "" verificationCode = "" codeCountdown = 0 isLoading = false isCodeLoading = false errorMessage = nil loginStep = .input stopCountdown() } // MARK: - Private Methods private func requestVerificationCode() async throws -> Bool { return false // let request = EmailVerificationCodeRequest(email: email) // let apiService = LiveAPIService() // let response: EmailVerificationCodeResponse = try await apiService.request(request) // // if response.code == 200 { // return true // } else { // throw APIError.serverError(response.message ?? "Failed to send verification code") // } } private func performLogin() async throws -> Bool { return false // let request = EmailLoginRequest( // email: email, // verificationCode: verificationCode // ) // // let apiService = LiveAPIService() // let response: EmailLoginResponse = try await apiService.request(request) // // if response.code == 200, let data = response.data { // // 保存用户信息 // await UserInfoManager.saveUserInfo(data) // // // 创建并保存账户模型 // let accountModel = AccountModel( // uid: data.uid, // accessToken: data.accessToken, // tokenType: data.tokenType, // refreshToken: data.refreshToken, // expiresIn: data.expiresIn // ) // await UserInfoManager.saveAccountModel(accountModel) // // // 获取用户详细信息 // if let userInfo = await UserInfoManager.fetchUserInfoFromServer( // uid: String(data.uid), // apiService: apiService // ) { // await UserInfoManager.saveUserInfo(userInfo) // } // // return true // } else { // throw APIError.serverError(response.message ?? "Login failed") // } } private func handleCodeRequestResult(_ success: Bool) { isCodeLoading = false if success { startCountdown() } } private func handleCodeRequestError(_ error: Error) { isCodeLoading = false errorMessage = error.localizedDescription } private func handleLoginResult(_ success: Bool) { isLoading = false if success { loginStep = .completed onLoginSuccess?() } } private func handleLoginError(_ error: Error) { isLoading = false errorMessage = error.localizedDescription } private func startCountdown() { stopCountdown() codeCountdown = 60 timerCancellable = Timer.publish(every: 1.0, on: .main, in: .common) .autoconnect() .sink { _ in if self.codeCountdown > 0 { self.codeCountdown -= 1 } else { self.stopCountdown() } } } private func stopCountdown() { timerCancellable?.cancel() timerCancellable = nil } } // MARK: - EMailLogin View struct EMailLoginPage: View { @StateObject private var viewModel = EMailLoginViewModel() let onBack: () -> Void let onLoginSuccess: () -> Void @FocusState private var focusedField: Field? enum Field { case email case verificationCode } var body: some View { GeometryReader { geometry in ZStack { LoginBackgroundView() VStack(spacing: 0) { LoginHeaderView(onBack: { viewModel.onBackTapped() }) Spacer().frame(height: 60) Text("Email Login") .font(.system(size: 28, weight: .bold)) .foregroundColor(.white) .padding(.bottom, 60) VStack(spacing: 24) { // 邮箱输入框 emailInputField // 验证码输入框(带获取按钮) verificationCodeInputField } .padding(.horizontal, 32) Spacer() .frame(height: 80) // 登录按钮 LoginButtonView( isLoading: viewModel.isLoading, isEnabled: viewModel.isLoginButtonEnabled, onTap: { viewModel.onLoginTapped() } ) .padding(.horizontal, 32) Spacer() } } } .navigationBarHidden(true) .onAppear { viewModel.onBack = onBack viewModel.onLoginSuccess = onLoginSuccess viewModel.resetState() #if DEBUG viewModel.email = "exzero@126.com" #endif } .onDisappear { // viewModel.stopCountdown() } .onChange(of: viewModel.email) { _, newEmail in viewModel.onEmailChanged(newEmail) } .onChange(of: viewModel.verificationCode) { _, newCode in viewModel.onVerificationCodeChanged(newCode) } .onChange(of: viewModel.isCodeLoading) { _, isCodeLoading in if !isCodeLoading && viewModel.errorMessage == nil { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { focusedField = .verificationCode } } } .onChange(of: viewModel.loginStep) { _, newStep in debugInfoSync("🔄 EMailLoginView: loginStep 变化为 \(newStep)") if newStep == .completed { debugInfoSync("✅ EMailLoginView: 登录成功,准备关闭自身") } } } // MARK: - UI Components private var emailInputField: some View { CustomInputField( type: .text, placeholder: "Please enter email", text: $viewModel.email ) .focused($focusedField, equals: .email) } private var verificationCodeInputField: some View { CustomInputField( type: .verificationCode, placeholder: "Please enter verification code", text: $viewModel.verificationCode, onGetCode: { viewModel.onGetVerificationCodeTapped() }, isCodeButtonEnabled: viewModel.isCodeButtonEnabled, isCodeLoading: viewModel.isCodeLoading, getCodeButtonText: viewModel.getCodeButtonText ) .focused($focusedField, equals: .verificationCode) } } #Preview { EMailLoginPage( onBack: {}, onLoginSuccess: {} ) }