// // ProfileEditViewModel.swift // suixinkan // import Foundation /// 资料编辑页 ViewModel,负责头像与昵称的本地编辑与提交保存。 final class ProfileEditViewModel { private let initialNickname: String private let initialAvatarURL: String private(set) var editingNickname = "" private(set) var pendingAvatarData: Data? private(set) var pendingAvatarFileName: String? private(set) var isSaving = false private(set) var avatarUploadProgress: Int? var onStateChange: (() -> Void)? /// 当前展示用的远程头像 URL(未选择新图时使用初始值)。 var displayAvatarURL: String { initialAvatarURL } /// 使用「我的」页当前展示的快照初始化编辑态。 init(nickname: String, avatarURL: String) { initialNickname = nickname initialAvatarURL = avatarURL editingNickname = nickname == "未设置昵称" ? "" : nickname } func updateEditingNickname(_ value: String) { editingNickname = value notifyStateChange() } func prepareAvatarImage(data: Data, timestamp: TimeInterval = Date().timeIntervalSince1970) throws { let processed = try AvatarImageProcessor.process(data: data, timestamp: timestamp) pendingAvatarData = processed.data pendingAvatarFileName = processed.fileName notifyStateChange() } /// 提交昵称与头像变更;无变更时不请求网络。 func saveProfile(api: ProfileAPI, uploader: any OSSUploadServing, scenicId: Int) async throws { let nextNickname = editingNickname.trimmingCharacters(in: .whitespacesAndNewlines) guard !nextNickname.isEmpty else { throw ProfileValidationError.emptyNickname } let nicknameChanged = nextNickname != initialNickname let avatarChanged = pendingAvatarData != nil guard nicknameChanged || avatarChanged else { return } guard !isSaving else { return } isSaving = true avatarUploadProgress = avatarChanged ? 1 : nil notifyStateChange() defer { isSaving = false avatarUploadProgress = nil notifyStateChange() } let uploadedAvatarURL = try await uploadPendingAvatarIfNeeded(uploader: uploader, scenicId: scenicId) if let uploadedAvatarURL { try await api.updateUserAvatarURL(uploadedAvatarURL) } if nicknameChanged { try await api.updateUserInfo(nickname: nextNickname) } AppStore.shared.userName = nextNickname if let uploadedAvatarURL { AppStore.shared.avatar = uploadedAvatarURL } clearPendingAvatar() NotificationCenter.default.post(name: NotificationName.userProfileDidUpdate, object: nil) } private func uploadPendingAvatarIfNeeded(uploader: any OSSUploadServing, scenicId: Int) async throws -> String? { guard let pendingAvatarData else { return nil } let fileName = pendingAvatarFileName ?? "avatar_\(Int(Date().timeIntervalSince1970)).jpg" return try await uploader.uploadUserAvatar(data: pendingAvatarData, fileName: fileName, scenicId: scenicId) { [weak self] progress in self?.avatarUploadProgress = progress self?.notifyStateChange() } } private func clearPendingAvatar() { pendingAvatarData = nil pendingAvatarFileName = nil avatarUploadProgress = nil } private func notifyStateChange() { onStateChange?() } }