feat: 更新.gitignore,删除需求文档,优化API调试信息
- 在.gitignore中添加忽略项以排除不必要的文件。 - 删除架构分析需求文档以简化项目文档。 - 在APIEndpoints.swift和LoginModels.swift中移除调试信息的异步调用,提升代码简洁性。 - 在EMailLoginFeature.swift和HomeFeature.swift中新增登录流程状态管理,优化用户体验。 - 在多个视图中调整状态管理和导航逻辑,确保一致性和可维护性。 - 更新Xcode项目配置以增强调试信息的输出格式。
This commit is contained in:
@@ -14,16 +14,41 @@ struct RecoverPasswordView: View {
|
||||
|
||||
// 验证码倒计时状态
|
||||
@State private var countdown: Int = 0
|
||||
private let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
|
||||
@State private var timerCancellable: AnyCancellable?
|
||||
|
||||
// 简化的计算属性
|
||||
private var isEmailValid: Bool {
|
||||
!email.isEmpty
|
||||
}
|
||||
|
||||
private var isVerificationCodeValid: Bool {
|
||||
!verificationCode.isEmpty
|
||||
}
|
||||
|
||||
private var isNewPasswordValid: Bool {
|
||||
!newPassword.isEmpty
|
||||
}
|
||||
|
||||
private var isStoreNotLoading: Bool {
|
||||
!store.isResetLoading
|
||||
}
|
||||
|
||||
private var isCodeNotLoading: Bool {
|
||||
!store.isCodeLoading
|
||||
}
|
||||
|
||||
private var isCountdownFinished: Bool {
|
||||
countdown == 0
|
||||
}
|
||||
|
||||
// 计算确认按钮是否可用
|
||||
private var isConfirmButtonEnabled: Bool {
|
||||
return !store.isResetLoading && !email.isEmpty && !verificationCode.isEmpty && !newPassword.isEmpty
|
||||
isStoreNotLoading && isEmailValid && isVerificationCodeValid && isNewPasswordValid
|
||||
}
|
||||
|
||||
// 计算获取验证码按钮是否可用
|
||||
private var isGetCodeButtonEnabled: Bool {
|
||||
return !store.isCodeLoading && !email.isEmpty && countdown == 0
|
||||
isCodeNotLoading && isEmailValid && isCountdownFinished
|
||||
}
|
||||
|
||||
// 计算获取验证码按钮文本
|
||||
@@ -75,115 +100,13 @@ struct RecoverPasswordView: View {
|
||||
// 输入框区域
|
||||
VStack(spacing: 24) {
|
||||
// 邮箱输入框
|
||||
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(NSLocalizedString("recover_password.placeholder_email", comment: ""))
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 16))
|
||||
.padding(.horizontal, 24)
|
||||
.keyboardType(.emailAddress)
|
||||
.autocapitalization(.none)
|
||||
}
|
||||
emailInputField
|
||||
|
||||
// 验证码输入框(带获取按钮)
|
||||
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(NSLocalizedString("recover_password.placeholder_verification_code", comment: ""))
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 16))
|
||||
.keyboardType(.numberPad)
|
||||
|
||||
// 获取验证码按钮
|
||||
Button(action: {
|
||||
// 立即开始倒计时
|
||||
startCountdown()
|
||||
// 发送API请求
|
||||
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(isGetCodeButtonEnabled ? 0.2 : 0.1))
|
||||
)
|
||||
}
|
||||
.disabled(!isGetCodeButtonEnabled || store.isCodeLoading)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
verificationCodeInputField
|
||||
|
||||
// 新密码输入框
|
||||
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 {
|
||||
if isNewPasswordVisible {
|
||||
TextField("", text: $newPassword)
|
||||
.placeholder(when: newPassword.isEmpty) {
|
||||
Text(NSLocalizedString("recover_password.placeholder_new_password", comment: ""))
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 16))
|
||||
} else {
|
||||
SecureField("", text: $newPassword)
|
||||
.placeholder(when: newPassword.isEmpty) {
|
||||
Text(NSLocalizedString("recover_password.placeholder_new_password", comment: ""))
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 16))
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
isNewPasswordVisible.toggle()
|
||||
}) {
|
||||
Image(systemName: isNewPasswordVisible ? "eye.slash" : "eye")
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
.font(.system(size: 18))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
newPasswordInputField
|
||||
}
|
||||
.padding(.horizontal, 32)
|
||||
|
||||
@@ -191,37 +114,7 @@ struct RecoverPasswordView: View {
|
||||
.frame(height: 80)
|
||||
|
||||
// 确认按钮
|
||||
Button(action: {
|
||||
store.send(.resetPasswordTapped)
|
||||
}) {
|
||||
ZStack {
|
||||
// 渐变背景
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(red: 0.85, green: 0.37, blue: 1.0), // #D85EFF
|
||||
Color(red: 0.54, green: 0.31, blue: 1.0) // #8A4FFF
|
||||
],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 28))
|
||||
|
||||
HStack {
|
||||
if store.isResetLoading {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.scaleEffect(0.8)
|
||||
}
|
||||
Text(store.isResetLoading ? NSLocalizedString("recover_password.resetting", comment: "") : NSLocalizedString("recover_password.confirm_button", comment: ""))
|
||||
.font(.system(size: 18, weight: .semibold))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}
|
||||
.frame(height: 56)
|
||||
}
|
||||
.disabled(!isConfirmButtonEnabled)
|
||||
.opacity(isConfirmButtonEnabled ? 1.0 : 0.5)
|
||||
.padding(.horizontal, 32)
|
||||
confirmButton
|
||||
|
||||
// 错误信息
|
||||
if let errorMessage = store.errorMessage {
|
||||
@@ -237,21 +130,10 @@ struct RecoverPasswordView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
// 每次进入页面都重置状态
|
||||
store.send(.resetState)
|
||||
|
||||
email = ""
|
||||
verificationCode = ""
|
||||
newPassword = ""
|
||||
isNewPasswordVisible = false
|
||||
countdown = 0
|
||||
#if DEBUG
|
||||
email = "exzero@126.com"
|
||||
store.send(.emailChanged(email))
|
||||
#endif
|
||||
resetState()
|
||||
}
|
||||
.onDisappear {
|
||||
countdown = 0
|
||||
stopCountdown()
|
||||
}
|
||||
.onChange(of: email) { newEmail in
|
||||
store.send(.emailChanged(newEmail))
|
||||
@@ -262,43 +144,207 @@ struct RecoverPasswordView: View {
|
||||
.onChange(of: newPassword) { newPassword in
|
||||
store.send(.newPasswordChanged(newPassword))
|
||||
}
|
||||
.onChange(of: store.isCodeLoading) { isCodeLoading in
|
||||
// 当API请求完成且成功时,自动将焦点切换到验证码输入框
|
||||
if !isCodeLoading && store.errorMessage == nil {
|
||||
// 可以在这里添加焦点切换逻辑
|
||||
}
|
||||
}
|
||||
.onChange(of: store.isResetSuccess) { isResetSuccess in
|
||||
// 密码重置成功后自动返回上一页
|
||||
if isResetSuccess {
|
||||
onBack()
|
||||
}
|
||||
}
|
||||
.onReceive(timer) { _ in
|
||||
if countdown > 0 {
|
||||
countdown -= 1
|
||||
}
|
||||
}
|
||||
|
||||
// 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(NSLocalizedString("recover_password.placeholder_email", comment: ""))
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 16))
|
||||
.padding(.horizontal, 24)
|
||||
.keyboardType(.emailAddress)
|
||||
.autocapitalization(.none)
|
||||
}
|
||||
}
|
||||
|
||||
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(NSLocalizedString("recover_password.placeholder_verification_code", comment: ""))
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 16))
|
||||
.keyboardType(.numberPad)
|
||||
|
||||
// 获取验证码按钮
|
||||
Button(action: {
|
||||
startCountdown()
|
||||
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(isGetCodeButtonEnabled ? 0.2 : 0.1))
|
||||
)
|
||||
}
|
||||
.disabled(!isGetCodeButtonEnabled || store.isCodeLoading)
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
}
|
||||
|
||||
private var newPasswordInputField: 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 {
|
||||
if isNewPasswordVisible {
|
||||
TextField("", text: $newPassword)
|
||||
.placeholder(when: newPassword.isEmpty) {
|
||||
Text(NSLocalizedString("recover_password.placeholder_new_password", comment: ""))
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 16))
|
||||
} else {
|
||||
SecureField("", text: $newPassword)
|
||||
.placeholder(when: newPassword.isEmpty) {
|
||||
Text(NSLocalizedString("recover_password.placeholder_new_password", comment: ""))
|
||||
.foregroundColor(.white.opacity(0.6))
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 16))
|
||||
}
|
||||
|
||||
Button(action: {
|
||||
isNewPasswordVisible.toggle()
|
||||
}) {
|
||||
Image(systemName: isNewPasswordVisible ? "eye.slash" : "eye")
|
||||
.foregroundColor(.white.opacity(0.7))
|
||||
.font(.system(size: 18))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
}
|
||||
}
|
||||
|
||||
private var confirmButton: some View {
|
||||
Button(action: {
|
||||
store.send(.resetPasswordTapped)
|
||||
}) {
|
||||
ZStack {
|
||||
// 渐变背景
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(red: 0.85, green: 0.37, blue: 1.0), // #D85EFF
|
||||
Color(red: 0.54, green: 0.31, blue: 1.0) // #8A4FFF
|
||||
],
|
||||
startPoint: .leading,
|
||||
endPoint: .trailing
|
||||
)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 28))
|
||||
|
||||
HStack {
|
||||
if store.isResetLoading {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.scaleEffect(0.8)
|
||||
}
|
||||
Text(store.isResetLoading ? NSLocalizedString("recover_password.resetting", comment: "") : NSLocalizedString("recover_password.confirm_button", comment: ""))
|
||||
.font(.system(size: 18, weight: .semibold))
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
}
|
||||
.frame(height: 56)
|
||||
}
|
||||
.disabled(!isConfirmButtonEnabled)
|
||||
.opacity(isConfirmButtonEnabled ? 1.0 : 0.5)
|
||||
.padding(.horizontal, 32)
|
||||
}
|
||||
|
||||
// MARK: - Private Methods
|
||||
|
||||
private func resetState() {
|
||||
store.send(.resetState)
|
||||
|
||||
email = ""
|
||||
verificationCode = ""
|
||||
newPassword = ""
|
||||
isNewPasswordVisible = false
|
||||
countdown = 0
|
||||
|
||||
#if DEBUG
|
||||
email = "exzero@126.com"
|
||||
store.send(.emailChanged(email))
|
||||
#endif
|
||||
}
|
||||
|
||||
private func startCountdown() {
|
||||
stopCountdown()
|
||||
countdown = 60
|
||||
|
||||
timerCancellable = Timer.publish(every: 1.0, on: .main, in: .common)
|
||||
.autoconnect()
|
||||
.sink { _ in
|
||||
if countdown > 0 {
|
||||
countdown -= 1
|
||||
} else {
|
||||
stopCountdown()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func stopCountdown() {
|
||||
timerCancellable?.cancel()
|
||||
timerCancellable = nil
|
||||
countdown = 0
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
RecoverPasswordView(
|
||||
store: Store(
|
||||
initialState: RecoverPasswordFeature.State()
|
||||
) {
|
||||
RecoverPasswordFeature()
|
||||
},
|
||||
onBack: {}
|
||||
)
|
||||
}
|
||||
//#Preview {
|
||||
// RecoverPasswordView(
|
||||
// store: Store(
|
||||
// initialState: RecoverPasswordFeature.State()
|
||||
// ) {
|
||||
// RecoverPasswordFeature()
|
||||
// },
|
||||
// onBack: {}
|
||||
// )
|
||||
//}
|
||||
|
Reference in New Issue
Block a user