Files
e-party-iOS/yana/Features/LoginFeature.swift
edwinQQQ 3d00e459e3 feat: 更新文档和视图以支持iOS 17及优化用户体验
- 更新Yana项目文档,调整适用版本至iOS 17,确保与最新开发环境兼容。
- 在多个视图中重构代码,优化状态管理和视图逻辑,提升用户体验。
- 添加默认初始化器以简化状态管理,确保各个Feature的状态一致性。
- 更新视图组件,移除不必要的硬编码,增强代码可读性和维护性。
- 修复多个视图中的逻辑错误,确保功能正常运行。
2025-07-29 17:57:42 +08:00

244 lines
9.8 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 LoginFeature {
@ObservableState
struct State: Equatable {
var account: String = ""
var password: String = ""
var isLoading = false
var error: String?
var idLoginState = IDLoginFeature.State()
var emailLoginState = EMailLoginFeature.State() //
// Account Model Ticket
var accountModel: AccountModel?
var isTicketLoading = false
var ticketError: String?
var loginStep: LoginStep = .initial
// -
var isInitialized = false
// true
var isAnyLoginCompleted: Bool {
idLoginState.loginStep == .completed || emailLoginState.loginStep == .completed
}
enum LoginStep: Equatable {
case initial //
case authenticating // OAuth
case gettingTicket // Ticket
case completed //
case failed //
}
init() {
//
self.account = ""
self.password = ""
}
}
enum Action {
case onAppear
case updateAccount(String)
case updatePassword(String)
case login
case loginResponse(TaskResult<IDLoginResponse>)
case idLogin(IDLoginFeature.Action)
case emailLogin(EMailLoginFeature.Action) // action
// HomeFeature action
// Ticket actions
case requestTicket(accessToken: String)
case ticketResponse(TaskResult<TicketResponse>)
case clearTicketError
case resetLogin
}
@Dependency(\.apiService) var apiService
var body: some ReducerOf<Self> {
Scope(state: \.idLoginState, action: \.idLogin) {
IDLoginFeature()
}
Scope(state: \.emailLoginState, action: \.emailLogin) {
EMailLoginFeature()
}
Reduce { state, action in
switch action {
case .onAppear:
//
guard !state.isInitialized else {
debugInfoSync("🚀 LoginFeature: 已初始化,跳过重复执行")
return .none
}
state.isInitialized = true
debugInfoSync("🚀 LoginFeature: 首次初始化")
//
return .none
case let .updateAccount(account):
state.account = account
return .none
case let .updatePassword(password):
state.password = password
return .none
case .login:
state.isLoading = true
state.error = nil
state.ticketError = nil
state.loginStep = .authenticating
// 使accountpassword
return .run { [account = state.account, password = state.password] send in
do {
// 使LoginHelper
guard let loginRequest = await LoginHelper.createIDLoginRequest(userID: account, password: password) else {
await send(.loginResponse(.failure(APIError.decodingError("加密失败"))))
return
}
//
let response = try await apiService.request(loginRequest)
await send(.loginResponse(.success(response)))
} catch {
if let apiError = error as? APIError {
await send(.loginResponse(.failure(apiError)))
} else {
await send(.loginResponse(.failure(APIError.unknown(error.localizedDescription))))
}
}
}
case let .loginResponse(.success(response)):
state.isLoading = false
if response.isSuccess {
// OAuth
state.error = nil
// AccountModel
if let loginData = response.data,
let accountModel = AccountModel.from(loginData: loginData) {
state.accountModel = accountModel
debugInfoSync("✅ OAuth 认证成功")
debugInfoSync("🔑 Access Token: \(accountModel.accessToken ?? "nil")")
debugInfoSync("🆔 用户 UID: \(accountModel.uid ?? "nil")")
// ticket
return .send(.requestTicket(accessToken: accountModel.accessToken!))
} else {
state.error = "登录数据格式错误"
state.loginStep = .failed
}
} else {
state.error = response.errorMessage
state.loginStep = .failed
}
return .none
case let .loginResponse(.failure(error)):
state.isLoading = false
state.error = error.localizedDescription
state.loginStep = .failed
return .none
case let .requestTicket(accessToken):
state.isTicketLoading = true
state.ticketError = nil
state.loginStep = .gettingTicket
return .run { [accountModel = state.accountModel] send in
do {
// AccountModel uid Int
let uid = accountModel?.uid != nil ? Int(accountModel!.uid!) : nil
let ticketRequest = TicketHelper.createTicketRequest(accessToken: accessToken, uid: uid)
let response = try await apiService.request(ticketRequest)
await send(.ticketResponse(.success(response)))
} catch {
debugErrorSync("❌ Ticket 获取失败: \(error)")
await send(.ticketResponse(.failure(APIError.networkError(error.localizedDescription))))
}
}
case let .ticketResponse(.success(response)):
state.isTicketLoading = false
if response.isSuccess {
state.ticketError = nil
state.loginStep = .completed
debugInfoSync("✅ 完整登录流程成功")
debugInfoSync("🎫 Ticket 获取成功: \(response.ticket ?? "nil")")
// AccountModel ticket
if let ticket = response.ticket {
if let oldAccountModel = state.accountModel {
let newAccountModel = oldAccountModel.withTicket(ticket)
state.accountModel = newAccountModel
// Effect AccountModel
return .run { _ in
await UserInfoManager.saveAccountModel(newAccountModel)
}
} else {
debugErrorSync("❌ AccountModel 不存在,无法保存 ticket")
state.ticketError = "内部错误:账户信息丢失"
state.loginStep = .failed
}
} else {
state.ticketError = "Ticket 为空"
state.loginStep = .failed
}
} else {
state.ticketError = response.errorMessage
state.loginStep = .failed
}
return .none
case let .ticketResponse(.failure(error)):
state.isTicketLoading = false
state.ticketError = error.localizedDescription
state.loginStep = .failed
debugErrorSync("❌ Ticket 获取失败: \(error.localizedDescription)")
return .none
case .clearTicketError:
state.ticketError = nil
return .none
case .resetLogin:
state.isLoading = false
state.isTicketLoading = false
state.error = nil
state.ticketError = nil
state.accountModel = nil // AccountModel
state.loginStep = .initial
// Effect
return .run { _ in
await UserInfoManager.clearAllAuthenticationData()
}
case .idLogin:
// IDLoginfeature
return .none
case .emailLogin:
// EmailLoginfeature
return .none
}
}
}
}
// 使
// extension Notification.Name {
// static let ticketSuccess = Notification.Name("ticketSuccess")
// }