feat: 更新EMailLoginView和IDLoginView以增强用户体验和界面一致性
- 将LocalizedString替换为硬编码字符串,提升代码可读性。 - 重构输入框组件,使用CustomInputField以统一输入框样式和逻辑。 - 更新按钮文本和样式,确保一致性和视觉效果。 - 调整布局和间距,优化用户界面体验。 - 增加验证码输入框的获取按钮功能,提升交互性。
This commit is contained in:
@@ -25,7 +25,7 @@ struct EMailLoginView: View {
|
|||||||
if codeCountdown > 0 {
|
if codeCountdown > 0 {
|
||||||
return "\(codeCountdown)s"
|
return "\(codeCountdown)s"
|
||||||
} else {
|
} else {
|
||||||
return LocalizedString("email_login.get_code", comment: "")
|
return "Get"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,117 +151,125 @@ private struct LoginContentView: View {
|
|||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
.padding(.top, 8)
|
.padding(.top, 8)
|
||||||
Spacer().frame(height: 60)
|
Spacer().frame(height: 60)
|
||||||
Text(LocalizedString("email_login.title", comment: ""))
|
Text("Email Login")
|
||||||
.font(.system(size: 28, weight: .medium))
|
.font(.system(size: 28, weight: .bold))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.padding(.bottom, 80)
|
.padding(.bottom, 60)
|
||||||
|
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 24) {
|
||||||
// 邮箱输入框
|
// 邮箱输入框
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
emailInputField
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证码输入框
|
// 验证码输入框(带获取按钮)
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
verificationCodeInputField
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal, 32)
|
||||||
|
|
||||||
// 添加API Loading和错误处理视图
|
Spacer()
|
||||||
APILoadingEffectView()
|
.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)
|
.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 {
|
//#Preview {
|
||||||
|
@@ -31,85 +31,125 @@ struct IDLoginHeaderView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 输入框组件
|
// MARK: - 通用输入框组件
|
||||||
struct IDLoginInputFieldView: View {
|
enum InputFieldType {
|
||||||
let iconName: String
|
case text
|
||||||
let title: String
|
case number
|
||||||
let text: Binding<String>
|
case password
|
||||||
let onChange: (String) -> Void
|
case verificationCode
|
||||||
|
|
||||||
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: - 密码输入框组件
|
struct CustomInputField: View {
|
||||||
struct IDLoginPasswordFieldView: View {
|
let type: InputFieldType
|
||||||
let password: Binding<String>
|
let placeholder: String
|
||||||
let isPasswordVisible: Binding<Bool>
|
let text: Binding<String>
|
||||||
let onChange: (String) -> Void
|
let isPasswordVisible: Binding<Bool>?
|
||||||
|
let onGetCode: (() -> Void)?
|
||||||
|
let isCodeButtonEnabled: Bool
|
||||||
|
let isCodeLoading: Bool
|
||||||
|
let getCodeButtonText: String
|
||||||
|
|
||||||
|
init(
|
||||||
|
type: InputFieldType,
|
||||||
|
placeholder: String,
|
||||||
|
text: Binding<String>,
|
||||||
|
isPasswordVisible: Binding<Bool>? = 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 {
|
var body: some View {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
ZStack {
|
||||||
HStack {
|
RoundedRectangle(cornerRadius: 25)
|
||||||
Image("email icon")
|
.fill(Color.white.opacity(0.1))
|
||||||
.resizable()
|
.overlay(
|
||||||
.aspectRatio(contentMode: .fit)
|
RoundedRectangle(cornerRadius: 25)
|
||||||
.frame(width: 20, height: 20)
|
.stroke(Color.white.opacity(0.3), lineWidth: 1)
|
||||||
Text(LocalizedString("id_login.password", comment: ""))
|
)
|
||||||
.font(.system(size: 16))
|
.frame(height: 56)
|
||||||
.foregroundColor(.white)
|
|
||||||
}
|
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
|
// 输入框
|
||||||
Group {
|
Group {
|
||||||
if isPasswordVisible.wrappedValue {
|
switch type {
|
||||||
TextField("", text: password)
|
case .text, .number:
|
||||||
.textFieldStyle(PlainTextFieldStyle())
|
TextField("", text: text)
|
||||||
} else {
|
.placeholder(when: text.wrappedValue.isEmpty) {
|
||||||
SecureField("", text: password)
|
Text(placeholder)
|
||||||
.textFieldStyle(PlainTextFieldStyle())
|
.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()
|
if type == .password, let isPasswordVisible = isPasswordVisible {
|
||||||
}) {
|
Button(action: {
|
||||||
Image(systemName: isPasswordVisible.wrappedValue ? "eye.slash" : "eye")
|
isPasswordVisible.wrappedValue.toggle()
|
||||||
.foregroundColor(.white.opacity(0.7))
|
}) {
|
||||||
|
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))
|
.padding(.horizontal, 24)
|
||||||
.foregroundColor(.white)
|
|
||||||
.padding(.horizontal, 16)
|
|
||||||
.padding(.vertical, 12)
|
|
||||||
.background(Color.white.opacity(0.1))
|
|
||||||
.cornerRadius(8)
|
|
||||||
.onChange(of: password.wrappedValue) { newValue in
|
|
||||||
onChange(newValue)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,7 +168,7 @@ struct IDLoginButtonView: View {
|
|||||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||||
.scaleEffect(1.2)
|
.scaleEffect(1.2)
|
||||||
} else {
|
} else {
|
||||||
Text(LocalizedString("id_login.login", comment: ""))
|
Text("Login")
|
||||||
.font(.system(size: 16, weight: .medium))
|
.font(.system(size: 16, weight: .medium))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
}
|
}
|
||||||
@@ -136,25 +176,9 @@ struct IDLoginButtonView: View {
|
|||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(.vertical, 16)
|
.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)
|
.cornerRadius(8)
|
||||||
.disabled(!isEnabled)
|
.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)
|
.frame(height: 60)
|
||||||
|
|
||||||
// 标题
|
// 标题
|
||||||
Text(LocalizedString("id_login.title", comment: ""))
|
Text("ID Login")
|
||||||
.font(.system(size: 28, weight: .medium))
|
.font(.system(size: 28, weight: .bold))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.padding(.bottom, 80)
|
.padding(.bottom, 60)
|
||||||
|
|
||||||
// 输入框区域
|
// 输入框区域
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: 24) {
|
||||||
// 用户ID输入框
|
// 用户ID输入框(只允许数字)
|
||||||
IDLoginInputFieldView(
|
CustomInputField(
|
||||||
iconName: "id icon",
|
type: .number,
|
||||||
title: LocalizedString("id_login.user_id", comment: ""),
|
placeholder: "Please enter ID",
|
||||||
text: $userID,
|
text: $userID
|
||||||
onChange: { newValue in
|
|
||||||
store.send(.userIDChanged(newValue))
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 密码输入框
|
// 密码输入框(带眼睛按钮)
|
||||||
IDLoginPasswordFieldView(
|
CustomInputField(
|
||||||
password: $password,
|
type: .password,
|
||||||
isPasswordVisible: $isPasswordVisible,
|
placeholder: "Please enter password",
|
||||||
onChange: { newValue in
|
text: $password,
|
||||||
store.send(.passwordChanged(newValue))
|
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视图
|
Spacer()
|
||||||
APILoadingEffectView()
|
.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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user