Files
suixinkan_uikit/suixinkan/Features/TravelAlbum/ViewModels/TravelAlbumDetailViewModel.swift
2026-07-07 17:23:31 +08:00

232 lines
6.9 KiB
Swift
Raw Permalink 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.

//
// TravelAlbumDetailViewModel.swift
// suixinkan
//
import Foundation
/// ViewModel
final class TravelAlbumDetailViewModel {
enum Tab: Int, Sendable {
case all = 0
case purchased = 1
}
///
enum SortOption: Int, CaseIterable, Sendable {
case createdAsc = 1
case createdDesc = 2
case fileNameAsc = 3
case fileNameDesc = 4
var title: String {
switch self {
case .createdAsc: "创建时间正序"
case .createdDesc: "创建时间倒序"
case .fileNameAsc: "文件名正序"
case .fileNameDesc: "文件名倒序"
}
}
}
private(set) var album: TravelAlbum?
private(set) var materials: [TravelAlbumMaterial] = []
private(set) var allPhotoCount = 0
private(set) var selectedTab: Tab = .all
private(set) var sortOption: SortOption = .createdDesc
private(set) var isLoading = true
private(set) var isRefreshing = false
private(set) var isSelectionMode = false
private(set) var selectedMaterialIds: Set<Int> = []
var onStateChange: (() -> Void)?
var onShowMessage: ((String) -> Void)?
var onDeletedAlbum: ((Int) -> Void)?
let albumId: Int
private var currentPage = 1
private var canLoadMore = false
private var isLoadingMore = false
private let pageSize = 30
init(albumId: Int) {
self.albumId = albumId
}
///
func refreshAll(api: any TravelAlbumServing) async {
guard albumId > 0 else {
isLoading = false
onShowMessage?("相册不存在")
notifyStateChange()
return
}
isRefreshing = true
notifyStateChange()
await loadAlbumInfo(api: api)
await loadAllPhotoCount(api: api)
await loadMaterials(reset: true, api: api)
isRefreshing = false
isLoading = false
notifyStateChange()
}
///
func loadAlbumInfo(api: any TravelAlbumServing) async {
do {
album = try await api.info(id: albumId)
notifyStateChange()
} catch is CancellationError {
return
} catch {
onShowMessage?(error.localizedDescription)
}
}
///
func loadAllPhotoCount(api: any TravelAlbumServing) async {
do {
let response = try await api.materialList(
userEquityTravelId: albumId,
page: 1,
pageSize: 1,
orderBy: sortOption.rawValue,
isPurchased: nil
)
allPhotoCount = response.total
notifyStateChange()
} catch is CancellationError {
return
} catch {
return
}
}
/// tab
func selectTab(_ tab: Tab, api: any TravelAlbumServing) async {
guard selectedTab != tab else { return }
selectedTab = tab
isSelectionMode = false
selectedMaterialIds = []
notifyStateChange()
await loadMaterials(reset: true, api: api)
}
///
func setSortOption(_ option: SortOption, api: any TravelAlbumServing) async {
guard sortOption != option else { return }
sortOption = option
notifyStateChange()
await loadMaterials(reset: true, api: api)
}
///
func loadMaterials(reset: Bool, api: any TravelAlbumServing) async {
guard albumId > 0 else { return }
if reset {
currentPage = 1
canLoadMore = false
} else {
guard canLoadMore, !isLoadingMore else { return }
isLoadingMore = true
currentPage += 1
}
do {
let response = try await api.materialList(
userEquityTravelId: albumId,
page: currentPage,
pageSize: pageSize,
orderBy: sortOption.rawValue,
isPurchased: selectedTab == .purchased ? 1 : nil
)
materials = reset ? response.list : materials + response.list
canLoadMore = materials.count < response.total
if selectedTab == .all {
allPhotoCount = response.total
}
isLoadingMore = false
notifyStateChange()
} catch is CancellationError {
return
} catch {
if !reset && currentPage > 1 {
currentPage -= 1
}
isLoadingMore = false
onShowMessage?(error.localizedDescription)
}
}
///
func loadMoreIfNeeded(lastVisibleIndex: Int, api: any TravelAlbumServing) async {
if lastVisibleIndex >= materials.count - 6 {
await loadMaterials(reset: false, api: api)
}
}
///
func toggleSelectionMode() {
guard selectedTab == .all else { return }
isSelectionMode.toggle()
if !isSelectionMode {
selectedMaterialIds = []
}
notifyStateChange()
}
///
func toggleMaterialSelection(_ material: TravelAlbumMaterial) {
guard isSelectionMode else { return }
guard material.status == 1 else {
onShowMessage?("仅未购买素材可删除")
return
}
if selectedMaterialIds.contains(material.id) {
selectedMaterialIds.remove(material.id)
} else {
selectedMaterialIds.insert(material.id)
}
notifyStateChange()
}
///
func deleteSelectedMaterials(api: any TravelAlbumServing) async {
let ids = Array(selectedMaterialIds)
guard !ids.isEmpty else {
onShowMessage?("请选择要删除的素材")
return
}
do {
for id in ids {
try await api.deleteMaterial(id: id)
}
onShowMessage?("删除成功")
isSelectionMode = false
selectedMaterialIds = []
await refreshAll(api: api)
} catch is CancellationError {
return
} catch {
onShowMessage?(error.localizedDescription.isEmpty ? "删除失败" : error.localizedDescription)
}
}
///
func deleteAlbum(api: any TravelAlbumServing) async {
do {
try await api.deleteAlbum(id: albumId)
onShowMessage?("相册已删除")
onDeletedAlbum?(albumId)
} catch is CancellationError {
return
} catch {
onShowMessage?(error.localizedDescription.isEmpty ? "删除失败" : error.localizedDescription)
}
}
private func notifyStateChange() {
onStateChange?()
}
}