feat: 更新项目配置和功能模块
- 修改Package.swift以支持iOS 15和macOS 12。 - 更新swift-tca-architecture-guidelines.mdc中的alwaysApply设置为false。 - 注释掉AppDelegate中的NIMSDK导入,移除不再使用的NIMConfigurationManager和NIMSessionManager文件。 - 添加新的API相关文件,包括EMailLoginFeature、IDLoginFeature和相关视图,增强登录功能。 - 更新APIConstants和APIEndpoints以反映新的API路径。 - 添加本地化支持文件,包含英文和中文简体的本地化字符串。 - 新增字体管理和安全工具类,支持AES和DES加密。 - 更新Xcode项目配置,调整版本号和启动画面设置。
This commit is contained in:
@@ -5,7 +5,7 @@ struct ConfigView: View {
|
||||
let store: StoreOf<ConfigFeature>
|
||||
|
||||
var body: some View {
|
||||
WithViewStore(self.store, observe: { $0 }) { viewStore in
|
||||
WithPerceptionTracking {
|
||||
NavigationView {
|
||||
VStack(spacing: 20) {
|
||||
// 标题
|
||||
@@ -16,7 +16,7 @@ struct ConfigView: View {
|
||||
|
||||
// 状态显示
|
||||
Group {
|
||||
if viewStore.isLoading {
|
||||
if store.isLoading {
|
||||
VStack {
|
||||
ProgressView()
|
||||
.scaleEffect(1.5)
|
||||
@@ -26,7 +26,7 @@ struct ConfigView: View {
|
||||
.padding(.top, 8)
|
||||
}
|
||||
.frame(height: 100)
|
||||
} else if let errorMessage = viewStore.errorMessage {
|
||||
} else if let errorMessage = store.errorMessage {
|
||||
VStack {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.font(.system(size: 40))
|
||||
@@ -43,13 +43,13 @@ struct ConfigView: View {
|
||||
.padding(.horizontal)
|
||||
|
||||
Button("清除错误") {
|
||||
viewStore.send(.clearError)
|
||||
store.send(.clearError)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.padding(.top)
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
} else if let configData = viewStore.configData {
|
||||
} else if let configData = store.configData {
|
||||
// 配置数据显示
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
@@ -102,7 +102,7 @@ struct ConfigView: View {
|
||||
.cornerRadius(12)
|
||||
}
|
||||
|
||||
if let lastUpdated = viewStore.lastUpdated {
|
||||
if let lastUpdated = store.lastUpdated {
|
||||
Text("最后更新: \(lastUpdated, style: .time)")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
@@ -130,21 +130,21 @@ struct ConfigView: View {
|
||||
// 操作按钮
|
||||
VStack(spacing: 12) {
|
||||
Button(action: {
|
||||
viewStore.send(.loadConfig)
|
||||
store.send(.loadConfig)
|
||||
}) {
|
||||
HStack {
|
||||
if viewStore.isLoading {
|
||||
if store.isLoading {
|
||||
ProgressView()
|
||||
.progressViewStyle(CircularProgressViewStyle(tint: .white))
|
||||
.scaleEffect(0.8)
|
||||
} else {
|
||||
Image(systemName: "arrow.clockwise")
|
||||
}
|
||||
Text(viewStore.isLoading ? "加载中..." : "加载配置")
|
||||
Text(store.isLoading ? "加载中..." : "加载配置")
|
||||
}
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.disabled(viewStore.isLoading)
|
||||
.disabled(store.isLoading)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 50)
|
||||
|
||||
@@ -152,7 +152,7 @@ struct ConfigView: View {
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding()
|
||||
|
||||
}
|
||||
}
|
||||
.navigationBarHidden(true)
|
||||
|
145
yana/Features/EMailLoginFeature.swift
Normal file
145
yana/Features/EMailLoginFeature.swift
Normal file
@@ -0,0 +1,145 @@
|
||||
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 codeCountdown: Int = 0
|
||||
var isCodeButtonEnabled: Bool = true
|
||||
|
||||
// Debug模式下的默认值
|
||||
#if DEBUG
|
||||
init() {
|
||||
self.email = "85494536@gmail.com"
|
||||
self.verificationCode = ""
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
enum Action: Equatable {
|
||||
case emailChanged(String)
|
||||
case verificationCodeChanged(String)
|
||||
case getVerificationCodeTapped
|
||||
case loginButtonTapped(email: String, verificationCode: String)
|
||||
case forgotPasswordTapped
|
||||
case codeCountdownTick
|
||||
case setLoading(Bool)
|
||||
case setCodeLoading(Bool)
|
||||
case setError(String?)
|
||||
case startCodeCountdown
|
||||
case resetCodeCountdown
|
||||
}
|
||||
|
||||
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 = "email_login.email_required".localized
|
||||
return .none
|
||||
}
|
||||
|
||||
guard isValidEmail(state.email) else {
|
||||
state.errorMessage = "email_login.invalid_email".localized
|
||||
return .none
|
||||
}
|
||||
|
||||
state.isCodeLoading = true
|
||||
state.errorMessage = nil
|
||||
|
||||
return .run { send in
|
||||
// 模拟获取验证码API调用
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000) // 1秒
|
||||
await send(.setCodeLoading(false))
|
||||
await send(.startCodeCountdown)
|
||||
}
|
||||
|
||||
case .loginButtonTapped(let email, let verificationCode):
|
||||
guard !email.isEmpty && !verificationCode.isEmpty else {
|
||||
state.errorMessage = "email_login.fields_required".localized
|
||||
return .none
|
||||
}
|
||||
|
||||
guard isValidEmail(email) else {
|
||||
state.errorMessage = "email_login.invalid_email".localized
|
||||
return .none
|
||||
}
|
||||
|
||||
state.isLoading = true
|
||||
state.errorMessage = nil
|
||||
|
||||
return .run { send in
|
||||
// 模拟登录API调用
|
||||
try await Task.sleep(nanoseconds: 2_000_000_000) // 2秒
|
||||
await send(.setLoading(false))
|
||||
// 这里应该处理实际的登录逻辑
|
||||
print("🔐 邮箱登录尝试: \(email), 验证码: \(verificationCode)")
|
||||
}
|
||||
|
||||
case .forgotPasswordTapped:
|
||||
// 处理忘记密码逻辑
|
||||
print("📧 忘记密码点击")
|
||||
return .none
|
||||
|
||||
case .codeCountdownTick:
|
||||
if state.codeCountdown > 0 {
|
||||
state.codeCountdown -= 1
|
||||
state.isCodeButtonEnabled = false
|
||||
|
||||
return .run { send in
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000) // 1秒
|
||||
await send(.codeCountdownTick)
|
||||
}
|
||||
} else {
|
||||
state.isCodeButtonEnabled = true
|
||||
return .none
|
||||
}
|
||||
|
||||
case .setLoading(let isLoading):
|
||||
state.isLoading = isLoading
|
||||
return .none
|
||||
|
||||
case .setCodeLoading(let isLoading):
|
||||
state.isCodeLoading = isLoading
|
||||
return .none
|
||||
|
||||
case .setError(let error):
|
||||
state.errorMessage = error
|
||||
return .none
|
||||
|
||||
case .startCodeCountdown:
|
||||
state.codeCountdown = 60
|
||||
state.isCodeButtonEnabled = false
|
||||
return .send(.codeCountdownTick)
|
||||
|
||||
case .resetCodeCountdown:
|
||||
state.codeCountdown = 0
|
||||
state.isCodeButtonEnabled = true
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helper Methods
|
||||
private func isValidEmail(_ email: String) -> Bool {
|
||||
let emailRegex = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"
|
||||
let emailPredicate = NSPredicate(format: "SELF MATCHES %@", emailRegex)
|
||||
return emailPredicate.evaluate(with: email)
|
||||
}
|
||||
}
|
209
yana/Features/IDLoginFeature.swift
Normal file
209
yana/Features/IDLoginFeature.swift
Normal file
@@ -0,0 +1,209 @@
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
|
||||
@Reducer
|
||||
struct IDLoginFeature {
|
||||
@ObservableState
|
||||
struct State: Equatable {
|
||||
var userID: String = ""
|
||||
var password: String = ""
|
||||
var isPasswordVisible = false
|
||||
var isLoading = false
|
||||
var errorMessage: String?
|
||||
|
||||
// 新增:Ticket 相关状态
|
||||
var accessToken: String?
|
||||
var ticket: String?
|
||||
var isTicketLoading = false
|
||||
var ticketError: String?
|
||||
var loginStep: LoginStep = .initial
|
||||
var uid: Int? // 修改:保存用户 uid,类型改为Int
|
||||
|
||||
enum LoginStep: Equatable {
|
||||
case initial // 初始状态
|
||||
case authenticating // 正在进行 OAuth 认证
|
||||
case gettingTicket // 正在获取 Ticket
|
||||
case completed // 认证完成
|
||||
case failed // 认证失败
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
init() {
|
||||
// 移除测试用的硬编码凭据
|
||||
self.userID = ""
|
||||
self.password = ""
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
enum Action: Equatable {
|
||||
case togglePasswordVisibility
|
||||
case loginButtonTapped(userID: String, password: String)
|
||||
case forgotPasswordTapped
|
||||
case backButtonTapped
|
||||
case loginResponse(TaskResult<IDLoginResponse>)
|
||||
|
||||
// 新增:Ticket 相关 actions
|
||||
case requestTicket(accessToken: String)
|
||||
case ticketResponse(TaskResult<TicketResponse>)
|
||||
case clearTicketError
|
||||
case resetLogin
|
||||
}
|
||||
|
||||
@Dependency(\.apiService) var apiService
|
||||
|
||||
var body: some ReducerOf<Self> {
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case .togglePasswordVisibility:
|
||||
state.isPasswordVisible.toggle()
|
||||
return .none
|
||||
|
||||
case let .loginButtonTapped(userID, password):
|
||||
state.userID = userID
|
||||
state.password = password
|
||||
state.isLoading = true
|
||||
state.errorMessage = nil
|
||||
state.ticketError = nil
|
||||
state.loginStep = .authenticating
|
||||
|
||||
// 实现真实的ID登录API调用
|
||||
return .run { send in
|
||||
do {
|
||||
// 使用LoginHelper创建加密的登录请求
|
||||
guard let loginRequest = LoginHelper.createIDLoginRequest(userID: userID, 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 .forgotPasswordTapped:
|
||||
// TODO: 处理忘记密码
|
||||
return .none
|
||||
|
||||
case .backButtonTapped:
|
||||
// 由父级处理返回逻辑
|
||||
return .none
|
||||
|
||||
case let .loginResponse(.success(response)):
|
||||
state.isLoading = false
|
||||
if response.isSuccess {
|
||||
// OAuth 认证成功,清除错误信息
|
||||
state.errorMessage = nil
|
||||
state.accessToken = response.data?.accessToken
|
||||
state.uid = response.data?.uid // 保存 uid
|
||||
|
||||
// 保存用户信息(如果有)
|
||||
if let userInfo = response.data?.userInfo {
|
||||
UserInfoManager.saveUserInfo(userInfo)
|
||||
}
|
||||
|
||||
print("✅ ID 登录 OAuth 认证成功")
|
||||
if let accessToken = response.data?.accessToken {
|
||||
print("🔑 Access Token: \(accessToken)")
|
||||
// 自动获取 ticket,传递 uid
|
||||
return .send(.requestTicket(accessToken: accessToken))
|
||||
}
|
||||
if let uid = response.data?.uid {
|
||||
print("🆔 用户 UID: \(uid)")
|
||||
}
|
||||
} else {
|
||||
state.errorMessage = response.errorMessage
|
||||
state.loginStep = .failed
|
||||
}
|
||||
return .none
|
||||
|
||||
case let .loginResponse(.failure(error)):
|
||||
state.isLoading = false
|
||||
state.errorMessage = error.localizedDescription
|
||||
state.loginStep = .failed
|
||||
return .none
|
||||
|
||||
case let .requestTicket(accessToken):
|
||||
state.isTicketLoading = true
|
||||
state.ticketError = nil
|
||||
state.loginStep = .gettingTicket
|
||||
|
||||
return .run { [uid = state.uid] send in
|
||||
do {
|
||||
// 使用 TicketHelper 创建请求,传递 uid
|
||||
let ticketRequest = TicketHelper.createTicketRequest(accessToken: accessToken, uid: uid)
|
||||
let response = try await apiService.request(ticketRequest)
|
||||
await send(.ticketResponse(.success(response)))
|
||||
} catch {
|
||||
print("❌ ID登录 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.ticket = response.ticket
|
||||
state.loginStep = .completed
|
||||
|
||||
print("✅ ID 登录完整流程成功")
|
||||
print("🎫 Ticket 获取成功: \(response.ticket ?? "nil")")
|
||||
|
||||
// 保存认证信息到本地存储(包括用户信息)
|
||||
if let accessToken = state.accessToken,
|
||||
let ticket = response.ticket {
|
||||
// 从之前的登录响应中获取用户信息
|
||||
let userInfo = UserInfoManager.getUserInfo()
|
||||
UserInfoManager.saveCompleteAuthenticationData(
|
||||
accessToken: accessToken,
|
||||
ticket: ticket,
|
||||
uid: state.uid,
|
||||
userInfo: userInfo
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: 触发导航到主界面
|
||||
|
||||
} 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
|
||||
print("❌ ID 登录 Ticket 获取失败: \(error.localizedDescription)")
|
||||
return .none
|
||||
|
||||
case .clearTicketError:
|
||||
state.ticketError = nil
|
||||
return .none
|
||||
|
||||
case .resetLogin:
|
||||
state.isLoading = false
|
||||
state.isTicketLoading = false
|
||||
state.errorMessage = nil
|
||||
state.ticketError = nil
|
||||
state.accessToken = nil
|
||||
state.ticket = nil
|
||||
state.uid = nil // 清除 uid
|
||||
state.loginStep = .initial
|
||||
|
||||
// 清除本地存储的认证信息
|
||||
UserInfoManager.clearAllAuthenticationData()
|
||||
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,12 +1,6 @@
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
|
||||
struct LoginResponse: Codable, Equatable {
|
||||
let status: String
|
||||
let message: String?
|
||||
let token: String?
|
||||
}
|
||||
|
||||
@Reducer
|
||||
struct LoginFeature {
|
||||
@ObservableState
|
||||
@@ -15,11 +9,29 @@ struct LoginFeature {
|
||||
var password: String = ""
|
||||
var isLoading = false
|
||||
var error: String?
|
||||
var idLoginState = IDLoginFeature.State()
|
||||
|
||||
// 新增:Ticket 相关状态
|
||||
var accessToken: String?
|
||||
var ticket: String?
|
||||
var isTicketLoading = false
|
||||
var ticketError: String?
|
||||
var loginStep: LoginStep = .initial
|
||||
var uid: Int? // 修改:保存用户 uid,类型改为Int
|
||||
|
||||
enum LoginStep: Equatable {
|
||||
case initial // 初始状态
|
||||
case authenticating // 正在进行 OAuth 认证
|
||||
case gettingTicket // 正在获取 Ticket
|
||||
case completed // 认证完成
|
||||
case failed // 认证失败
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
init() {
|
||||
self.account = "3184"
|
||||
self.password = "a0d5da073d14731cc7a01ecaa17b9174"
|
||||
// 移除测试用的硬编码凭据
|
||||
self.account = ""
|
||||
self.password = ""
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -28,56 +40,168 @@ struct LoginFeature {
|
||||
case updateAccount(String)
|
||||
case updatePassword(String)
|
||||
case login
|
||||
case loginResponse(TaskResult<LoginResponse>)
|
||||
case loginResponse(TaskResult<IDLoginResponse>)
|
||||
case idLogin(IDLoginFeature.Action)
|
||||
|
||||
// 新增:Ticket 相关 actions
|
||||
case requestTicket(accessToken: String)
|
||||
case ticketResponse(TaskResult<TicketResponse>)
|
||||
case clearTicketError
|
||||
case resetLogin
|
||||
}
|
||||
|
||||
@Dependency(\.apiService) var apiService
|
||||
|
||||
var body: some ReducerOf<Self> {
|
||||
// Reduce { state, action in
|
||||
// switch action {
|
||||
// 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
|
||||
//
|
||||
// let loginBody = [
|
||||
// "account": state.account,
|
||||
// "password": state.password
|
||||
// ]
|
||||
//
|
||||
// return .run { send in
|
||||
// do {
|
||||
// let response: LoginResponse = try await APIClientManager.shared.post(
|
||||
// path: APIConstants.Endpoints.login,
|
||||
// body: loginBody,
|
||||
// headers: APIConstants.defaultHeaders
|
||||
// )
|
||||
// await send(.loginResponse(.success(response)))
|
||||
// } catch {
|
||||
// await send(.loginResponse(.failure(error)))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// case let .loginResponse(.success(response)):
|
||||
// state.isLoading = false
|
||||
// if response.status == "success" {
|
||||
// // TODO: 处理登录成功,保存 token 等
|
||||
// } else {
|
||||
// state.error = response.message ?? "登录失败"
|
||||
// }
|
||||
// return .none
|
||||
//
|
||||
// case let .loginResponse(.failure(error)):
|
||||
// state.isLoading = false
|
||||
// state.error = error.localizedDescription
|
||||
// return .none
|
||||
// }
|
||||
// }
|
||||
Scope(state: \.idLoginState, action: \.idLogin) {
|
||||
IDLoginFeature()
|
||||
}
|
||||
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
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
|
||||
|
||||
// 实现登录逻辑(使用account和password)
|
||||
return .run { [account = state.account, password = state.password] send in
|
||||
do {
|
||||
// 使用LoginHelper创建加密的登录请求
|
||||
guard let loginRequest = 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
|
||||
state.accessToken = response.data?.accessToken
|
||||
state.uid = response.data?.uid // 保存 uid
|
||||
|
||||
print("✅ OAuth 认证成功")
|
||||
if let accessToken = response.data?.accessToken {
|
||||
print("🔑 Access Token: \(accessToken)")
|
||||
// 自动获取 ticket,传递 uid
|
||||
return .send(.requestTicket(accessToken: accessToken))
|
||||
}
|
||||
if let userInfo = response.data?.userInfo {
|
||||
print("👤 用户信息: \(userInfo)")
|
||||
}
|
||||
if let uid = response.data?.uid {
|
||||
print("🆔 用户 UID: \(uid)")
|
||||
}
|
||||
} 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 { [uid = state.uid] send in
|
||||
do {
|
||||
// 使用 TicketHelper 创建请求,传递 uid
|
||||
let ticketRequest = TicketHelper.createTicketRequest(accessToken: accessToken, uid: uid)
|
||||
let response = try await apiService.request(ticketRequest)
|
||||
await send(.ticketResponse(.success(response)))
|
||||
} catch {
|
||||
print("❌ 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.ticket = response.ticket
|
||||
state.loginStep = .completed
|
||||
|
||||
print("✅ 完整登录流程成功")
|
||||
print("🎫 Ticket 获取成功: \(response.ticket ?? "nil")")
|
||||
|
||||
// 保存认证信息到本地存储
|
||||
if let accessToken = state.accessToken,
|
||||
let ticket = response.ticket {
|
||||
UserInfoManager.saveCompleteAuthenticationData(
|
||||
accessToken: accessToken,
|
||||
ticket: ticket,
|
||||
uid: state.uid,
|
||||
userInfo: nil // LoginFeature 中没有用户信息,由具体的登录页面传递
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: 触发导航到主界面
|
||||
|
||||
} 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
|
||||
print("❌ 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.accessToken = nil
|
||||
state.ticket = nil
|
||||
state.uid = nil // 清除 uid
|
||||
state.loginStep = .initial
|
||||
|
||||
// 清除本地存储的认证信息
|
||||
UserInfoManager.clearAllAuthenticationData()
|
||||
|
||||
return .none
|
||||
|
||||
case .idLogin:
|
||||
// IDLogin动作由子feature处理
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
39
yana/Features/SplashFeature.swift
Normal file
39
yana/Features/SplashFeature.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
import Foundation
|
||||
import ComposableArchitecture
|
||||
|
||||
@Reducer
|
||||
struct SplashFeature {
|
||||
@ObservableState
|
||||
struct State: Equatable {
|
||||
var isLoading = true
|
||||
var shouldShowMainApp = false
|
||||
}
|
||||
|
||||
enum Action: Equatable {
|
||||
case onAppear
|
||||
case splashFinished
|
||||
}
|
||||
|
||||
var body: some ReducerOf<Self> {
|
||||
Reduce { state, action in
|
||||
switch action {
|
||||
case .onAppear:
|
||||
state.isLoading = true
|
||||
state.shouldShowMainApp = false
|
||||
|
||||
// 1秒延迟后显示主应用 (iOS 15.5+ 兼容)
|
||||
return .run { send in
|
||||
try await Task.sleep(nanoseconds: 1_000_000_000) // 1秒 = 1,000,000,000 纳秒
|
||||
await send(.splashFinished)
|
||||
}
|
||||
|
||||
case .splashFinished:
|
||||
state.isLoading = false
|
||||
state.shouldShowMainApp = true
|
||||
// 发送通知
|
||||
NotificationCenter.default.post(name: .splashFinished, object: nil)
|
||||
return .none
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user