
- 创建SettingPage视图,包含用户信息管理、头像设置、昵称编辑等功能。 - 实现SettingViewModel,处理设置页面的业务逻辑,包括头像上传、昵称更新等。 - 添加相机和相册选择功能,支持头像更换。 - 更新MainPage和MainViewModel,添加导航逻辑以支持设置页面的访问。 - 完善本地化支持,确保多语言兼容性。 - 新增相关测试建议,确保功能完整性和用户体验。
195 lines
6.2 KiB
Swift
195 lines
6.2 KiB
Swift
import SwiftUI
|
||
import Combine
|
||
|
||
// MARK: - IDLogin ViewModel
|
||
|
||
@MainActor
|
||
class IDLoginViewModel: ObservableObject {
|
||
// MARK: - Published Properties
|
||
@Published var userID: String = ""
|
||
@Published var password: String = ""
|
||
@Published var isPasswordVisible: Bool = false
|
||
@Published var isLoading: Bool = false
|
||
@Published var errorMessage: String?
|
||
@Published var showRecoverPassword: Bool = false
|
||
@Published var loginStep: LoginStep = .input
|
||
|
||
// MARK: - Ticket 相关状态
|
||
@Published var isTicketLoading: Bool = false
|
||
@Published var ticketError: String?
|
||
|
||
// MARK: - Callbacks
|
||
var onBack: (() -> Void)?
|
||
var onLoginSuccess: (() -> Void)?
|
||
|
||
// MARK: - Private Properties
|
||
private var cancellables = Set<AnyCancellable>()
|
||
|
||
// MARK: - Enums
|
||
enum LoginStep: Equatable {
|
||
case input // 初始状态
|
||
case authenticating // 正在进行 OAuth 认证
|
||
case gettingTicket // 正在获取 Ticket
|
||
case completed // 认证完成
|
||
case failed // 认证失败
|
||
}
|
||
|
||
// MARK: - Computed Properties
|
||
var isLoginButtonEnabled: Bool {
|
||
return !isLoading && !userID.isEmpty && !password.isEmpty
|
||
}
|
||
|
||
// MARK: - Public Methods
|
||
func onBackTapped() {
|
||
onBack?()
|
||
}
|
||
|
||
func onLoginTapped() {
|
||
guard isLoginButtonEnabled else { return }
|
||
|
||
isLoading = true
|
||
errorMessage = nil
|
||
ticketError = nil
|
||
loginStep = .authenticating
|
||
|
||
Task {
|
||
do {
|
||
let result = try await performLogin()
|
||
await MainActor.run {
|
||
self.handleLoginResult(result)
|
||
}
|
||
} catch {
|
||
await MainActor.run {
|
||
self.handleLoginError(error)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
func onRecoverPasswordTapped() {
|
||
showRecoverPassword = true
|
||
}
|
||
|
||
func onRecoverPasswordBack() {
|
||
showRecoverPassword = false
|
||
}
|
||
|
||
// MARK: - Private Methods
|
||
private func performLogin() async throws -> Bool {
|
||
// 第一步:OAuth认证
|
||
let accountModel = try await performOAuthAuthentication()
|
||
|
||
// 第二步:获取Ticket
|
||
let completeAccountModel = try await performTicketRequest(accountModel: accountModel)
|
||
|
||
// 第三步:保存完整的AccountModel
|
||
await UserInfoManager.saveAccountModel(completeAccountModel)
|
||
|
||
// 第四步:获取用户信息(如果API没有返回)
|
||
await fetchUserInfoIfNeeded(accountModel: completeAccountModel)
|
||
|
||
return true
|
||
}
|
||
|
||
// MARK: - OAuth认证
|
||
private func performOAuthAuthentication() async throws -> AccountModel {
|
||
// 使用LoginHelper创建登录请求(包含DES加密)
|
||
guard let loginRequest = await LoginHelper.createIDLoginRequest(
|
||
userID: userID,
|
||
password: password
|
||
) else {
|
||
throw APIError.custom("DES加密失败")
|
||
}
|
||
|
||
let apiService = LiveAPIService()
|
||
let response: IDLoginResponse = try await apiService.request(loginRequest)
|
||
|
||
if response.code == 200, let data = response.data {
|
||
// 保存用户信息(如果API返回了用户信息)
|
||
if let userInfo = data.userInfo {
|
||
await UserInfoManager.saveUserInfo(userInfo)
|
||
}
|
||
|
||
// 创建账户模型(此时ticket为空)
|
||
guard let accountModel = AccountModel.from(loginData: data) else {
|
||
throw APIError.custom("账户信息无效")
|
||
}
|
||
|
||
return accountModel
|
||
} else {
|
||
throw APIError.custom(response.message ?? "Login failed")
|
||
}
|
||
}
|
||
|
||
// MARK: - Ticket获取
|
||
private func performTicketRequest(accountModel: AccountModel) async throws -> AccountModel {
|
||
await MainActor.run {
|
||
self.isTicketLoading = true
|
||
self.ticketError = nil
|
||
self.loginStep = .gettingTicket
|
||
}
|
||
|
||
let apiService = LiveAPIService()
|
||
|
||
// 创建ticket请求
|
||
let ticketRequest = TicketHelper.createTicketRequest(
|
||
accessToken: accountModel.accessToken ?? "",
|
||
uid: accountModel.uid.flatMap { Int($0) }
|
||
)
|
||
|
||
let ticketResponse: TicketResponse = try await apiService.request(ticketRequest)
|
||
|
||
await MainActor.run {
|
||
self.isTicketLoading = false
|
||
}
|
||
|
||
if ticketResponse.isSuccess {
|
||
if let ticket = ticketResponse.ticket {
|
||
debugInfoSync("✅ Ticket 获取成功: \(ticket)")
|
||
|
||
// 更新AccountModel,添加ticket
|
||
let completeAccountModel = accountModel.withTicket(ticket)
|
||
return completeAccountModel
|
||
} else {
|
||
throw APIError.custom("Ticket为空")
|
||
}
|
||
} else {
|
||
throw APIError.custom(ticketResponse.errorMessage)
|
||
}
|
||
}
|
||
|
||
// MARK: - 用户信息获取
|
||
private func fetchUserInfoIfNeeded(accountModel: AccountModel) async {
|
||
// 如果API没有返回用户信息,则从服务器获取
|
||
let apiService = LiveAPIService()
|
||
if let userInfo = await UserInfoManager.fetchUserInfoFromServer(
|
||
uid: accountModel.uid,
|
||
apiService: apiService
|
||
) {
|
||
await UserInfoManager.saveUserInfo(userInfo)
|
||
debugInfoSync("✅ 用户信息获取成功")
|
||
} else {
|
||
debugErrorSync("❌ 用户信息获取失败,但不影响登录流程")
|
||
}
|
||
}
|
||
|
||
private func handleLoginResult(_ success: Bool) {
|
||
isLoading = false
|
||
isTicketLoading = false
|
||
if success {
|
||
loginStep = .completed
|
||
debugInfoSync("✅ ID 登录完整流程成功")
|
||
onLoginSuccess?()
|
||
}
|
||
}
|
||
|
||
private func handleLoginError(_ error: Error) {
|
||
isLoading = false
|
||
isTicketLoading = false
|
||
errorMessage = error.localizedDescription
|
||
loginStep = .failed
|
||
debugErrorSync("❌ ID 登录失败: \(error.localizedDescription)")
|
||
}
|
||
}
|
||
|