diff --git a/yana/Views/EMailLoginView.swift b/yana/Views/EMailLoginView.swift index d500901..9660245 100644 --- a/yana/Views/EMailLoginView.swift +++ b/yana/Views/EMailLoginView.swift @@ -25,7 +25,7 @@ struct EMailLoginView: View { if codeCountdown > 0 { return "\(codeCountdown)s" } else { - return LocalizedString("email_login.get_code", comment: "") + return "Get" } } @@ -151,117 +151,125 @@ private struct LoginContentView: View { .padding(.horizontal, 16) .padding(.top, 8) Spacer().frame(height: 60) - Text(LocalizedString("email_login.title", comment: "")) - .font(.system(size: 28, weight: .medium)) + Text("Email Login") + .font(.system(size: 28, weight: .bold)) .foregroundColor(.white) - .padding(.bottom, 80) + .padding(.bottom, 60) - VStack(spacing: 20) { + VStack(spacing: 24) { // 邮箱输入框 - VStack(alignment: .leading, spacing: 8) { - HStack { - Image("email icon") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 20, height: 20) - Text(LocalizedString("email_login.email", comment: "")) - .font(.system(size: 16)) - .foregroundColor(.white) - } - - TextField("", text: $email) - .textFieldStyle(PlainTextFieldStyle()) - .font(.system(size: 16)) - .foregroundColor(.white) - .padding(.horizontal, 16) - .padding(.vertical, 12) - .background(Color.white.opacity(0.1)) - .cornerRadius(8) - .focused($focusedField, equals: .email) - .keyboardType(.emailAddress) - .autocapitalization(.none) - } + emailInputField - // 验证码输入框 - VStack(alignment: .leading, spacing: 8) { - HStack { - Image("id icon") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 20, height: 20) - Text(LocalizedString("email_login.verification_code", comment: "")) - .font(.system(size: 16)) - .foregroundColor(.white) - } - - HStack { - TextField("", text: $verificationCode) - .textFieldStyle(PlainTextFieldStyle()) - .font(.system(size: 16)) - .foregroundColor(.white) - .padding(.horizontal, 16) - .padding(.vertical, 12) - .background(Color.white.opacity(0.1)) - .cornerRadius(8) - .focused($focusedField, equals: .verificationCode) - .keyboardType(.numberPad) - - Button(action: { - store.send(.getVerificationCodeTapped) - }) { - Text(getCodeButtonText) - .font(.system(size: 14, weight: .medium)) - .foregroundColor(.white) - .padding(.horizontal, 16) - .padding(.vertical, 12) - .background(isCodeButtonEnabled ? Color.blue : Color.gray) - .cornerRadius(8) - } - .disabled(!isCodeButtonEnabled) - } - } - - // 登录按钮 - Button(action: { - store.send(.loginButtonTapped(email: email, verificationCode: verificationCode)) - }) { - if store.isLoading { - ProgressView() - .progressViewStyle(CircularProgressViewStyle(tint: .white)) - .scaleEffect(1.2) - } else { - Text(LocalizedString("email_login.login", comment: "")) - .font(.system(size: 16, weight: .medium)) - .foregroundColor(.white) - } - } - .frame(maxWidth: .infinity) - .padding(.vertical, 16) - .background(isLoginButtonEnabled ? Color.blue : Color.gray) - .cornerRadius(8) - .disabled(!isLoginButtonEnabled) - .padding(.top, 20) - - // 错误信息显示 - if let errorMessage = store.errorMessage { - Text(errorMessage) - .font(.system(size: 14)) - .foregroundColor(.red) - .padding(.top, 16) - .padding(.horizontal, 32) - } - - Spacer() + // 验证码输入框(带获取按钮) + verificationCodeInputField } + .padding(.horizontal, 32) - // 添加API Loading和错误处理视图 - APILoadingEffectView() + 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 { diff --git a/yana/Views/IDLoginView.swift b/yana/Views/IDLoginView.swift index aaad9a8..2b4cade 100644 --- a/yana/Views/IDLoginView.swift +++ b/yana/Views/IDLoginView.swift @@ -31,85 +31,125 @@ struct IDLoginHeaderView: View { } } -// MARK: - 输入框组件 -struct IDLoginInputFieldView: View { - let iconName: String - let title: String - let text: Binding - let onChange: (String) -> Void - - var body: some View { - VStack(alignment: .leading, spacing: 8) { - HStack { - Image(iconName) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 20, height: 20) - Text(title) - .font(.system(size: 16)) - .foregroundColor(.white) - } - - TextField("", text: text) - .textFieldStyle(PlainTextFieldStyle()) - .font(.system(size: 16)) - .foregroundColor(.white) - .padding(.horizontal, 16) - .padding(.vertical, 12) - .background(Color.white.opacity(0.1)) - .cornerRadius(8) - .onChange(of: text.wrappedValue) { newValue in - onChange(newValue) - } - } - } +// MARK: - 通用输入框组件 +enum InputFieldType { + case text + case number + case password + case verificationCode } -// MARK: - 密码输入框组件 -struct IDLoginPasswordFieldView: View { - let password: Binding - let isPasswordVisible: Binding - let onChange: (String) -> Void +struct CustomInputField: View { + let type: InputFieldType + let placeholder: String + let text: Binding + let isPasswordVisible: Binding? + let onGetCode: (() -> Void)? + let isCodeButtonEnabled: Bool + let isCodeLoading: Bool + let getCodeButtonText: String + + init( + type: InputFieldType, + placeholder: String, + text: Binding, + isPasswordVisible: Binding? = nil, + onGetCode: (() -> Void)? = nil, + isCodeButtonEnabled: Bool = false, + isCodeLoading: Bool = false, + getCodeButtonText: String = "" + ) { + self.type = type + self.placeholder = placeholder + self.text = text + self.isPasswordVisible = isPasswordVisible + self.onGetCode = onGetCode + self.isCodeButtonEnabled = isCodeButtonEnabled + self.isCodeLoading = isCodeLoading + self.getCodeButtonText = getCodeButtonText + } var body: some View { - VStack(alignment: .leading, spacing: 8) { - HStack { - Image("email icon") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 20, height: 20) - Text(LocalizedString("id_login.password", comment: "")) - .font(.system(size: 16)) - .foregroundColor(.white) - } + 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 { + // 输入框 Group { - if isPasswordVisible.wrappedValue { - TextField("", text: password) - .textFieldStyle(PlainTextFieldStyle()) - } else { - SecureField("", text: password) - .textFieldStyle(PlainTextFieldStyle()) + switch type { + case .text, .number: + TextField("", text: text) + .placeholder(when: text.wrappedValue.isEmpty) { + Text(placeholder) + .foregroundColor(.white.opacity(0.6)) + } + .keyboardType(type == .number ? .numberPad : .default) + case .password: + if let isPasswordVisible = isPasswordVisible { + if isPasswordVisible.wrappedValue { + TextField("", text: text) + .placeholder(when: text.wrappedValue.isEmpty) { + Text(placeholder) + .foregroundColor(.white.opacity(0.6)) + } + } else { + SecureField("", text: text) + .placeholder(when: text.wrappedValue.isEmpty) { + Text(placeholder) + .foregroundColor(.white.opacity(0.6)) + } + } + } + case .verificationCode: + TextField("", text: text) + .placeholder(when: text.wrappedValue.isEmpty) { + Text(placeholder) + .foregroundColor(.white.opacity(0.6)) + } + .keyboardType(.numberPad) } } + .foregroundColor(.white) + .font(.system(size: 16)) - Button(action: { - isPasswordVisible.wrappedValue.toggle() - }) { - Image(systemName: isPasswordVisible.wrappedValue ? "eye.slash" : "eye") - .foregroundColor(.white.opacity(0.7)) + // 右侧按钮 + if type == .password, let isPasswordVisible = isPasswordVisible { + Button(action: { + isPasswordVisible.wrappedValue.toggle() + }) { + Image(systemName: isPasswordVisible.wrappedValue ? "eye.slash" : "eye") + .foregroundColor(.white.opacity(0.7)) + .font(.system(size: 18)) + } + } else if type == .verificationCode, let onGetCode = onGetCode { + Button(action: onGetCode) { + ZStack { + if 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 || isCodeLoading) } } - .font(.system(size: 16)) - .foregroundColor(.white) - .padding(.horizontal, 16) - .padding(.vertical, 12) - .background(Color.white.opacity(0.1)) - .cornerRadius(8) - .onChange(of: password.wrappedValue) { newValue in - onChange(newValue) - } + .padding(.horizontal, 24) } } } @@ -128,7 +168,7 @@ struct IDLoginButtonView: View { .progressViewStyle(CircularProgressViewStyle(tint: .white)) .scaleEffect(1.2) } else { - Text(LocalizedString("id_login.login", comment: "")) + Text("Login") .font(.system(size: 16, weight: .medium)) .foregroundColor(.white) } @@ -136,25 +176,9 @@ struct IDLoginButtonView: View { } .frame(maxWidth: .infinity) .padding(.vertical, 16) - .background(isEnabled ? Color.blue : Color.gray) + .background(isEnabled ? Color(red: 0.5, green: 0.3, blue: 0.8) : Color.gray) .cornerRadius(8) .disabled(!isEnabled) - .padding(.top, 20) - } -} - -// MARK: - 错误信息组件 -struct IDLoginErrorView: View { - let errorMessage: String? - - var body: some View { - if let errorMessage = errorMessage { - Text(errorMessage) - .font(.system(size: 14)) - .foregroundColor(.red) - .padding(.top, 16) - .padding(.horizontal, 32) - } } } @@ -192,61 +216,58 @@ struct IDLoginView: View { .frame(height: 60) // 标题 - Text(LocalizedString("id_login.title", comment: "")) - .font(.system(size: 28, weight: .medium)) + Text("ID Login") + .font(.system(size: 28, weight: .bold)) .foregroundColor(.white) - .padding(.bottom, 80) + .padding(.bottom, 60) // 输入框区域 - VStack(spacing: 20) { - // 用户ID输入框 - IDLoginInputFieldView( - iconName: "id icon", - title: LocalizedString("id_login.user_id", comment: ""), - text: $userID, - onChange: { newValue in - store.send(.userIDChanged(newValue)) - } + VStack(spacing: 24) { + // 用户ID输入框(只允许数字) + CustomInputField( + type: .number, + placeholder: "Please enter ID", + text: $userID ) - // 密码输入框 - IDLoginPasswordFieldView( - password: $password, - isPasswordVisible: $isPasswordVisible, - onChange: { newValue in - store.send(.passwordChanged(newValue)) - } + // 密码输入框(带眼睛按钮) + CustomInputField( + type: .password, + placeholder: "Please enter password", + text: $password, + isPasswordVisible: $isPasswordVisible ) - - // 忘记密码按钮 - HStack { - Spacer() - Button(action: { - showRecoverPassword = true - }) { - Text(LocalizedString("id_login.forgot_password", comment: "")) - .font(.system(size: 14)) - .foregroundColor(.white.opacity(0.8)) - } - } - - // 登录按钮 - IDLoginButtonView( - isLoading: store.isLoading, - isEnabled: isLoginButtonEnabled, - onTap: { - store.send(.loginButtonTapped(userID: userID, password: password)) - } - ) - - // 错误信息显示 - IDLoginErrorView(errorMessage: store.errorMessage) - - Spacer() } + .padding(.horizontal, 32) - // API Loading视图 - APILoadingEffectView() + Spacer() + .frame(height: 80) + + // 忘记密码按钮 + HStack { + Spacer() + Button(action: { + showRecoverPassword = true + }) { + Text("Forgot Password?") + .font(.system(size: 14)) + .foregroundColor(.white.opacity(0.8)) + } + } + .padding(.horizontal, 32) + .padding(.bottom, 20) + + // 登录按钮 + IDLoginButtonView( + isLoading: store.isLoading, + isEnabled: isLoginButtonEnabled, + onTap: { + store.send(.loginButtonTapped(userID: userID, password: password)) + } + ) + .padding(.horizontal, 32) + + Spacer() } } }