Files
e-party-iOS/yana/Views/IDLoginView.swift
edwinQQQ fdfa39f0b7 feat: 更新EMailLoginView和IDLoginView以增强用户体验和界面一致性
- 将LocalizedString替换为硬编码字符串,提升代码可读性。
- 重构输入框组件,使用CustomInputField以统一输入框样式和逻辑。
- 更新按钮文本和样式,确保一致性和视觉效果。
- 调整布局和间距,优化用户界面体验。
- 增加验证码输入框的获取按钮功能,提升交互性。
2025-07-31 19:14:14 +08:00

324 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import SwiftUI
import ComposableArchitecture
import Perception
// MARK: -
struct IDLoginBackgroundView: View {
var body: some View {
Image("bg")
.resizable()
.aspectRatio(contentMode: .fill)
.ignoresSafeArea(.all)
}
}
// MARK: -
struct IDLoginHeaderView: View {
let onBack: () -> Void
var body: some View {
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)
}
}
// MARK: -
enum InputFieldType {
case text
case number
case password
case verificationCode
}
struct CustomInputField: View {
let type: InputFieldType
let placeholder: String
let text: Binding<String>
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 {
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 {
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))
//
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)
}
}
.padding(.horizontal, 24)
}
}
}
// MARK: -
struct IDLoginButtonView: View {
let isLoading: Bool
let isEnabled: Bool
let onTap: () -> Void
var body: some View {
Button(action: onTap) {
Group {
if 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(isEnabled ? Color(red: 0.5, green: 0.3, blue: 0.8) : Color.gray)
.cornerRadius(8)
.disabled(!isEnabled)
}
}
// MARK: -
struct IDLoginView: View {
let store: StoreOf<IDLoginFeature>
let onBack: () -> Void
@Binding var showIDLogin: Bool
// 使@StateUI
@State private var userID: String = ""
@State private var password: String = ""
@State private var isPasswordVisible: Bool = false
//
@State private var showRecoverPassword: Bool = false
//
private var isLoginButtonEnabled: Bool {
return !store.isLoading && !userID.isEmpty && !password.isEmpty
}
var body: some View {
WithPerceptionTracking {
GeometryReader { geometry in
ZStack {
//
IDLoginBackgroundView()
VStack(spacing: 0) {
//
IDLoginHeaderView(onBack: onBack)
Spacer()
.frame(height: 60)
//
Text("ID Login")
.font(.system(size: 28, weight: .bold))
.foregroundColor(.white)
.padding(.bottom, 60)
//
VStack(spacing: 24) {
// ID
CustomInputField(
type: .number,
placeholder: "Please enter ID",
text: $userID
)
//
CustomInputField(
type: .password,
placeholder: "Please enter password",
text: $password,
isPasswordVisible: $isPasswordVisible
)
}
.padding(.horizontal, 32)
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()
}
}
}
.navigationBarHidden(true)
.navigationDestination(isPresented: $showRecoverPassword) {
WithPerceptionTracking {
RecoverPasswordView(
store: Store(
initialState: RecoverPasswordFeature.State()
) {
RecoverPasswordFeature()
},
onBack: {
showRecoverPassword = false
}
)
.navigationBarHidden(true)
}
}
.onAppear {
let _ = WithPerceptionTracking {
// TCA
userID = store.userID
password = store.password
isPasswordVisible = store.isPasswordVisible
#if DEBUG
debugInfoSync("🐛 Debug模式: 已移除硬编码测试凭据")
#endif
}
}
.onChange(of: store.loginStep) { newStep in
debugInfoSync("🔄 IDLoginView: loginStep 变化为 \(newStep)")
if newStep == .completed {
debugInfoSync("✅ IDLoginView: 登录成功,准备关闭自身")
showIDLogin = false
}
}
}
}
}
//#Preview {
// IDLoginView(
// store: Store(
// initialState: IDLoginFeature.State()
// ) {
// IDLoginFeature()
// },
// onBack: {},
// showIDLogin: .constant(true)
// )
//}