import SwiftUI import ComposableArchitecture import Combine struct EMailLoginView: View { let store: StoreOf let onBack: () -> Void @Binding var showEmailLogin: Bool // 新增:绑定父视图的显示状态 // 使用本地@State管理UI状态 @State private var email: String = "" @State private var verificationCode: String = "" @State private var codeCountdown: Int = 0 @FocusState private var focusedField: Field? // 倒计时定时器 @State private var timerCancellable: AnyCancellable? // 计算属性 private var isLoginButtonEnabled: Bool { return !store.isLoading && !email.isEmpty && !verificationCode.isEmpty } private var getCodeButtonText: String { if codeCountdown > 0 { return "\(codeCountdown)s" } else { return "Get" } } private var isCodeButtonEnabled: Bool { return !store.isCodeLoading && codeCountdown == 0 && !email.isEmpty } enum Field { case email case verificationCode } var body: some View { WithPerceptionTracking { LoginContentView( store: store, onBack: onBack, email: $email, verificationCode: $verificationCode, codeCountdown: $codeCountdown, focusedField: $focusedField, isLoginButtonEnabled: isLoginButtonEnabled, getCodeButtonText: getCodeButtonText, isCodeButtonEnabled: isCodeButtonEnabled ) .onChange(of: store.loginStep) { newStep in debugInfoSync("🔄 EMailLoginView: loginStep 变化为 \(newStep)") if newStep == .completed { debugInfoSync("✅ EMailLoginView: 登录成功,准备关闭自身") showEmailLogin = false } } } .onAppear { let _ = WithPerceptionTracking { store.send(.resetState) email = "" verificationCode = "" codeCountdown = 0 stopCountdown() #if DEBUG email = "exzero@126.com" store.send(.emailChanged(email)) #endif } } .onDisappear { let _ = WithPerceptionTracking { stopCountdown() } } .onChange(of: email) { newEmail in let _ = WithPerceptionTracking { store.send(.emailChanged(newEmail)) } } .onChange(of: verificationCode) { newCode in let _ = WithPerceptionTracking { store.send(.verificationCodeChanged(newCode)) } } .onChange(of: store.isCodeLoading) { isCodeLoading in let _ = WithPerceptionTracking { if !isCodeLoading && store.errorMessage == nil { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { focusedField = .verificationCode } } } } } private func startCountdown() { stopCountdown() codeCountdown = 60 timerCancellable = Timer.publish(every: 1.0, on: .main, in: .common) .autoconnect() .sink { _ in if codeCountdown > 0 { codeCountdown -= 1 } else { stopCountdown() } } } private func stopCountdown() { timerCancellable?.cancel() timerCancellable = nil } } private struct LoginContentView: View { let store: StoreOf let onBack: () -> Void @Binding var email: String @Binding var verificationCode: String @Binding var codeCountdown: Int @FocusState.Binding var focusedField: EMailLoginView.Field? let isLoginButtonEnabled: Bool let getCodeButtonText: String let isCodeButtonEnabled: Bool var body: some View { GeometryReader { geometry in WithPerceptionTracking { ZStack { Image("bg") .resizable() .aspectRatio(contentMode: .fill) .ignoresSafeArea(.all) VStack(spacing: 0) { HStack { Button(action: { onBack() }) { Image(systemName: "chevron.left") .font(.system(size: 24, weight: .medium)) .foregroundColor(.white) .frame(width: 44, height: 44) } Spacer() } .padding(.horizontal, 16) .padding(.top, 8) 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) // 登录按钮 Button(action: { store.send(.loginButtonTapped(email: email, verificationCode: verificationCode)) }) { if store.isLoading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) .scaleEffect(1.2) } else { Text("Login") .font(.system(size: 16, weight: .medium)) .foregroundColor(.white) } } .frame(maxWidth: .infinity) .padding(.vertical, 16) .background(isLoginButtonEnabled ? Color(red: 0.5, green: 0.3, blue: 0.8) : Color.gray) .cornerRadius(8) .disabled(!isLoginButtonEnabled) .padding(.horizontal, 32) Spacer() } } } } .navigationBarHidden(true) } // MARK: - UI Components private var emailInputField: some View { ZStack { RoundedRectangle(cornerRadius: 25) .fill(Color.white.opacity(0.1)) .overlay( RoundedRectangle(cornerRadius: 25) .stroke(Color.white.opacity(0.3), lineWidth: 1) ) .frame(height: 56) TextField("", text: $email) .placeholder(when: email.isEmpty) { Text("Please enter email") .foregroundColor(.white.opacity(0.6)) } .foregroundColor(.white) .font(.system(size: 16)) .padding(.horizontal, 24) .keyboardType(.emailAddress) .autocapitalization(.none) .focused($focusedField, equals: .email) } } private var verificationCodeInputField: some View { ZStack { RoundedRectangle(cornerRadius: 25) .fill(Color.white.opacity(0.1)) .overlay( RoundedRectangle(cornerRadius: 25) .stroke(Color.white.opacity(0.3), lineWidth: 1) ) .frame(height: 56) HStack { TextField("", text: $verificationCode) .placeholder(when: verificationCode.isEmpty) { Text("Please enter verification code") .foregroundColor(.white.opacity(0.6)) } .foregroundColor(.white) .font(.system(size: 16)) .keyboardType(.numberPad) .focused($focusedField, equals: .verificationCode) // 获取验证码按钮 Button(action: { store.send(.getVerificationCodeTapped) }) { ZStack { if store.isCodeLoading { ProgressView() .progressViewStyle(CircularProgressViewStyle(tint: .white)) .scaleEffect(0.7) } else { Text(getCodeButtonText) .font(.system(size: 14, weight: .medium)) .foregroundColor(.white) } } .frame(width: 60, height: 36) .background( RoundedRectangle(cornerRadius: 15) .fill(Color.white.opacity(isCodeButtonEnabled ? 0.2 : 0.1)) ) } .disabled(!isCodeButtonEnabled || store.isCodeLoading) } .padding(.horizontal, 24) } } } //#Preview { // EMailLoginView( // store: Store( // initialState: EMailLoginFeature.State() // ) { // EMailLoginFeature() // }, // onBack: {}, // showEmailLogin: .constant(true) // ) //}