Files
e-party-iOS/yana/Features/EMailLoginFeature.swift
edwinQQQ 0fe3b6cb7a feat: 新增用户信息获取功能及相关模型
- 在APIEndpoints.swift中新增getUserInfo端点以支持获取用户信息。
- 在APIModels.swift中实现获取用户信息请求和响应模型,处理用户信息的请求与解析。
- 在UserInfoManager中新增方法以从服务器获取用户信息,并在登录成功后自动获取用户信息。
- 在SettingFeature中新增用户信息刷新状态管理,支持用户信息的刷新操作。
- 在SettingView中集成用户信息刷新按钮,提升用户体验。
- 在SplashFeature中实现自动获取用户信息的逻辑,优化用户登录流程。
- 在yanaAPITests中添加用户信息相关的单元测试,确保功能的正确性。
2025-07-23 11:46:46 +08:00

209 lines
8.3 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 Foundation
import ComposableArchitecture
@Reducer
struct EMailLoginFeature {
@ObservableState
struct State: Equatable {
var email: String = ""
var verificationCode: String = ""
var isLoading: Bool = false
var isCodeLoading: Bool = false
var errorMessage: String? = nil
var isCodeSent: Bool = false
//
var loginStep: LoginStep = .initial
enum LoginStep: Equatable {
case initial
case authenticating
case completed
case failed
}
#if DEBUG
init() {
self.email = "exzero@126.com"
self.verificationCode = ""
self.loginStep = .initial
}
#endif
}
enum Action {
case emailChanged(String)
case verificationCodeChanged(String)
case getVerificationCodeTapped
case getCodeResponse(Result<EmailGetCodeResponse, Error>)
case loginButtonTapped(email: String, verificationCode: String)
case loginResponse(Result<AccountModel, Error>)
case forgotPasswordTapped
case resetState
}
@Dependency(\.apiService) var apiService
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .emailChanged(let email):
state.email = email
state.errorMessage = nil
return .none
case .verificationCodeChanged(let code):
state.verificationCode = code
state.errorMessage = nil
return .none
case .getVerificationCodeTapped:
guard !state.email.isEmpty else {
state.errorMessage = NSLocalizedString("email_login.email_required", comment: "")
return .none
}
guard ValidationHelper.isValidEmail(state.email) else {
state.errorMessage = NSLocalizedString("email_login.invalid_email", comment: "")
return .none
}
state.isCodeLoading = true
state.isCodeSent = false //
state.errorMessage = nil
return .run { [email = state.email] send in
do {
guard let request = LoginHelper.createEmailGetCodeRequest(email: email) else {
await send(.getCodeResponse(.failure(APIError.encryptionFailed)))
return
}
let response = try await apiService.request(request)
await send(.getCodeResponse(.success(response)))
} catch {
await send(.getCodeResponse(.failure(error)))
}
}
case .getCodeResponse(.success(let response)):
state.isCodeLoading = false
if response.isSuccess {
state.isCodeSent = true
return .none
} else {
state.errorMessage = response.errorMessage
return .none
}
case .getCodeResponse(.failure(let error)):
state.isCodeLoading = false
if let apiError = error as? APIError {
state.errorMessage = apiError.localizedDescription
} else {
state.errorMessage = "验证码发送失败,请检查网络连接"
}
return .none
case .loginButtonTapped(let email, let verificationCode):
guard !email.isEmpty && !verificationCode.isEmpty else {
state.errorMessage = NSLocalizedString("email_login.fields_required", comment: "")
return .none
}
guard ValidationHelper.isValidEmail(email) else {
state.errorMessage = NSLocalizedString("email_login.invalid_email", comment: "")
return .none
}
state.isLoading = true
state.errorMessage = nil
state.loginStep = .authenticating
return .run { send in
do {
guard let request = await LoginHelper.createEmailLoginRequest(email: email, code: verificationCode) else {
await send(.loginResponse(.failure(APIError.encryptionFailed)))
return
}
let response = try await apiService.request(request)
if response.isSuccess, let loginData = response.data {
guard let accountModel = AccountModel.from(loginData: loginData) else {
await send(.loginResponse(.failure(APIError.invalidResponse)))
return
}
// Ticket
let ticketRequest = TicketHelper.createTicketRequest(
accessToken: accountModel.accessToken ?? "",
uid: accountModel.uid.flatMap { Int($0) }
)
let ticketResponse = try await apiService.request(ticketRequest)
if ticketResponse.isSuccess, let ticket = ticketResponse.ticket {
let completeAccount = accountModel.withTicket(ticket)
await send(.loginResponse(.success(completeAccount)))
} else {
await send(.loginResponse(.failure(APIError.ticketFailed)))
}
} else {
await send(.loginResponse(.failure(APIError.custom(response.errorMessage))))
}
} catch {
await send(.loginResponse(.failure(error)))
}
}
case .loginResponse(.success(let accountModel)):
state.isLoading = false
state.loginStep = .completed
// Effect AccountModel
return .run { _ in
await UserInfoManager.saveAccountModel(accountModel)
//
debugInfoSync("🔄 邮箱登录成功,开始获取用户信息")
if let _ = await UserInfoManager.fetchUserInfoFromServer(
uid: accountModel.uid,
apiService: apiService
) {
debugInfoSync("✅ 用户信息获取成功")
} else {
debugErrorSync("❌ 用户信息获取失败,但不影响登录流程")
}
}
case .loginResponse(.failure(let error)):
state.isLoading = false
state.loginStep = .failed
if let apiError = error as? APIError {
state.errorMessage = apiError.localizedDescription
} else {
state.errorMessage = "登录失败,请重试"
}
return .none
case .forgotPasswordTapped:
return .none
case .resetState:
state.email = ""
state.verificationCode = ""
state.isLoading = false
state.isCodeLoading = false
state.errorMessage = nil
state.isCodeSent = false
state.loginStep = .initial
return .none
}
}
}
}