feat: add travel album module
This commit is contained in:
@ -0,0 +1,231 @@
|
||||
//
|
||||
// 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?()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user