001project_wildgrowth/ios/WildGrowth/WildGrowth/UserManager.swift

251 lines
10 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 SwiftUI
import Kingfisher
class UserManager: ObservableObject {
static let shared = UserManager()
@Published var isLoggedIn: Bool = false
// currentUser AuthModels.UserInfo UserProfileResponse
//
@Published var currentUser: UserInfo?
@Published var studyStats: (time: Int, lessons: Int) = (0, 0)
/// / URL
@Published var avatarCacheBust: Int = 0
//
var isGuest: Bool {
return !isLoggedIn
}
private let apiClient = APIClient.shared
init() {
checkLoginStatus()
}
func checkLoginStatus() {
if TokenManager.shared.getToken() != nil {
isLoggedIn = true
//
Task { try? await fetchUserProfile() }
}
}
// MARK: - F7: Guest Progress Logic
/// 🎒 [] 1. UserDefaults
func saveGuestProgress(nodeId: String, totalStudyTime: Int, completedSlides: Int) {
let data: [String: Any] = [
"nodeId": nodeId,
"totalStudyTime": totalStudyTime,
"completedSlides": completedSlides,
"timestamp": Date().timeIntervalSince1970
]
UserDefaults.standard.set(data, forKey: "pending_guest_record")
print("📦 [Guest] Progress saved locally for node: \(nodeId)")
}
/// 🔄 [] 2.
func syncGuestProgress() async {
guard let data = UserDefaults.standard.dictionary(forKey: "pending_guest_record"),
let nodeId = data["nodeId"] as? String,
let totalStudyTime = data["totalStudyTime"] as? Int,
let completedSlides = data["completedSlides"] as? Int else {
print("🔄 [Sync] No pending guest record found")
return
}
print("🔄 [Sync] Found pending guest record for node: \(nodeId), syncing to server...")
do {
//
let _ = try await LearningService.shared.completeLesson(
nodeId: nodeId,
totalStudyTime: totalStudyTime,
completedSlides: completedSlides
)
print("✅ [Sync] Guest progress synced successfully!")
//
UserDefaults.standard.removeObject(forKey: "pending_guest_record")
} catch {
print("❌ [Sync] Failed to sync guest progress: \(error)")
//
}
}
// MARK: - Login Handler
/// [] 3.
// 线 @Published
@MainActor
func handleLoginSuccess(_ data: LoginData) {
print("👤 [UserManager] 开始处理登录成功(主线程)")
print("🔍 [UserManager] handleLoginSuccess: 登录响应 digitalId = \(data.user.digitalId ?? "nil")")
_ = TokenManager.shared.saveToken(data.token) //
self.currentUser = data.user
self.isLoggedIn = true
// user
print("👤 [UserManager] 登录状态已更新: isLoggedIn = \(isLoggedIn)")
print("🔍 [UserManager] handleLoginSuccess: 设置后 currentUser.digitalId = \(self.currentUser?.digitalId ?? "nil")")
// 线
Task.detached(priority: .utility) {
await UserManager.shared.syncGuestProgress()
}
//
Task.detached(priority: .utility) {
do {
try await UserManager.shared.fetchUserProfile()
print("👤 [UserManager] 用户资料拉取成功")
} catch {
print("👤 [UserManager] ⚠️ 用户资料拉取失败(非阻塞): \(error)")
}
}
}
func logout() {
TokenManager.shared.deleteToken()
self.isLoggedIn = false
self.currentUser = nil
self.studyStats = (0, 0)
//
}
// MARK: - API
// 1.
@MainActor
func fetchUserProfile() async throws {
print("🔍 [UserManager] fetchUserProfile: 开始调用 API...")
do {
let response: APIResponse<UserProfileResponse> = try await apiClient.request(
endpoint: "/api/user/profile",
method: "GET",
requiresAuth: true
)
let data = response.data
// API digitalId
print("🔍 [UserManager] fetchUserProfile: API 返回 digitalId = \(data.digitalId ?? "nil")")
print("🔍 [UserManager] fetchUserProfile: API 返回完整数据 - id=\(data.id), nickname=\(data.nickname), avatar=\(data.avatar ?? "nil")")
print("🔍 [头像调试] fetchUserProfile 返回 avatar: \(data.avatar ?? "nil")")
// digitalId
let previousAvatar = self.currentUser?.avatar
self.currentUser = UserInfo(
id: data.id,
nickname: data.nickname,
phone: data.phone,
avatar: data.avatar,
digitalId: data.digitalId // ID
)
if data.avatar != previousAvatar { avatarCacheBust += 1 }
// digitalId
print("🔍 [UserManager] fetchUserProfile: 更新后 currentUser.digitalId = \(self.currentUser?.digitalId ?? "nil")")
//
self.studyStats = (data.total_study_time, data.completed_lessons)
print("✅ [UserManager] fetchUserProfile: 完成")
} catch {
print("❌ [UserManager] fetchUserProfile: 失败 - \(error)")
print("❌ [UserManager] fetchUserProfile: 错误详情 - \(error.localizedDescription)")
throw error //
}
}
// 2.
@MainActor
func updateNickname(_ name: String) async throws {
let response: APIResponse<UpdateProfileResponse> = try await apiClient.request(
endpoint: "/api/user/profile",
method: "PUT",
body: ["nickname": name],
requiresAuth: true
)
// digitalId
if let current = currentUser {
self.currentUser = UserInfo(
id: current.id,
nickname: response.data.nickname,
phone: current.phone,
avatar: response.data.avatar ?? current.avatar, // 使
digitalId: current.digitalId // digitalId
)
}
}
//
///
/// - Parameter imageUrl: URL
@MainActor
func updateAvatar(_ imageUrl: String) async throws {
print("🔍 [头像调试] updateAvatar 入参 imageUrl: \(imageUrl)")
let response: APIResponse<UpdateProfileResponse> = try await apiClient.request(
endpoint: "/api/user/profile",
method: "PUT",
body: ["avatar": imageUrl],
requiresAuth: true
)
print("🔍 [头像调试] 更新资料接口返回 response.data.avatar: \(response.data.avatar ?? "nil")")
//
if let current = currentUser {
let oldAvatar = current.avatar
self.currentUser = UserInfo(
id: current.id,
nickname: current.nickname,
phone: current.phone,
avatar: response.data.avatar,
digitalId: current.digitalId
)
print("🔍 [头像调试] 更新后 currentUser.avatar: \(self.currentUser?.avatar ?? "nil")")
avatarCacheBust += 1
// URL key/
[oldAvatar, response.data.avatar].compactMap { $0 }.filter { !$0.isEmpty }.forEach { path in
if let url = apiClient.getImageURL(path) { ImageCache.default.removeImage(forKey: url.absoluteString) }
}
}
}
// 3. ()
@MainActor
func updateSettings(pushEnabled: Bool) async throws {
let _: APIResponse<UserSettingsResponse> = try await apiClient.request(
endpoint: "/api/user/settings",
method: "PUT",
body: ["push_notification": pushEnabled],
requiresAuth: true
)
}
// 4.
func fetchSettings() async throws -> Bool {
let response: APIResponse<UserSettingsResponse> = try await apiClient.request(
endpoint: "/api/user/settings",
method: "GET",
requiresAuth: true
)
return response.data.push_notification
}
// 5. ()
/// - Throws:
@MainActor
func deleteAccount() async throws {
// 使 APIClient
// Endpoint: DELETE /api/user/account
// Response: { "success": true, "message": "..." } -> SimpleResponse
let _: SimpleResponse = try await apiClient.request(
endpoint: "/api/user/account",
method: "DELETE",
requiresAuth: true // Token
)
//
self.logout()
}
}