262 lines
8.3 KiB
Swift
262 lines
8.3 KiB
Swift
//
|
|
// TravelAlbumEntryViewModel.swift
|
|
// suixinkan
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// 旅拍相册入口页 ViewModel,负责列表、创建相册和相册码状态。
|
|
final class TravelAlbumEntryViewModel {
|
|
enum CreateMode: Int, Sendable {
|
|
case preShoot = 0
|
|
case preOrder = 1
|
|
|
|
var apiType: Int {
|
|
switch self {
|
|
case .preShoot: 1
|
|
case .preOrder: 2
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 相册码弹窗状态。
|
|
struct AlbumCodeState: Sendable, Equatable {
|
|
let albumId: Int
|
|
let albumName: String
|
|
var qrImageUrl: String
|
|
var isLoading: Bool
|
|
var isDownloading: Bool
|
|
}
|
|
|
|
private(set) var albums: [TravelAlbum] = []
|
|
private(set) var albumTotal = 0
|
|
private(set) var availableOrders: [TravelAlbumAvailableOrder] = []
|
|
private(set) var isLoading = false
|
|
private(set) var isCreating = false
|
|
private(set) var isCreateSheetVisible = false
|
|
private(set) var albumCodeState: AlbumCodeState?
|
|
|
|
var onStateChange: (() -> Void)?
|
|
var onShowMessage: ((String) -> Void)?
|
|
var onCreatedAlbum: ((TravelAlbumCreateResponse) -> Void)?
|
|
|
|
private let currentScenicIdProvider: () -> Int
|
|
private let dateProvider: () -> Date
|
|
private let autoRetouchConfigurationStore: any TravelAlbumAutoRetouchConfigurationStoring
|
|
|
|
init(
|
|
currentScenicIdProvider: @escaping () -> Int = { AppStore.shared.session.currentScenicId },
|
|
dateProvider: @escaping () -> Date = Date.init,
|
|
autoRetouchConfigurationStore: any TravelAlbumAutoRetouchConfigurationStoring = TravelAlbumAutoRetouchConfigurationStore.shared
|
|
) {
|
|
self.currentScenicIdProvider = currentScenicIdProvider
|
|
self.dateProvider = dateProvider
|
|
self.autoRetouchConfigurationStore = autoRetouchConfigurationStore
|
|
}
|
|
|
|
/// 重新拉取相册列表。
|
|
func loadAlbums(api: any TravelAlbumServing) async {
|
|
isLoading = true
|
|
notifyStateChange()
|
|
defer {
|
|
isLoading = false
|
|
notifyStateChange()
|
|
}
|
|
|
|
do {
|
|
let response = try await api.list(page: 1, pageSize: 20)
|
|
albumTotal = response.total
|
|
albums = response.list
|
|
} catch is CancellationError {
|
|
return
|
|
} catch {
|
|
albums = []
|
|
albumTotal = 0
|
|
onShowMessage?(error.localizedDescription)
|
|
}
|
|
}
|
|
|
|
/// 打开创建相册弹窗。
|
|
func openCreateSheet(api: any TravelAlbumServing) async {
|
|
guard !isCreating else { return }
|
|
guard currentScenicIdProvider() > 0 else {
|
|
onShowMessage?("请先选择景区")
|
|
return
|
|
}
|
|
isCreateSheetVisible = true
|
|
notifyStateChange()
|
|
await loadAvailableOrders(api: api)
|
|
}
|
|
|
|
/// 关闭创建相册弹窗。
|
|
func dismissCreateSheet() {
|
|
isCreateSheetVisible = false
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 拉取可绑定订单。
|
|
func loadAvailableOrders(api: any TravelAlbumServing) async {
|
|
do {
|
|
availableOrders = try await api.availableOrders()
|
|
notifyStateChange()
|
|
} catch is CancellationError {
|
|
return
|
|
} catch {
|
|
availableOrders = []
|
|
notifyStateChange()
|
|
}
|
|
}
|
|
|
|
/// 校验并创建相册任务。
|
|
func confirmCreate(
|
|
mode: CreateMode,
|
|
freeCount: String,
|
|
singlePrice: String,
|
|
packagePrice: String,
|
|
autoRetouchConfiguration: TravelAlbumAutoRetouchConfiguration = .disabled,
|
|
order: TravelAlbumAvailableOrder?,
|
|
api: any TravelAlbumServing
|
|
) async {
|
|
guard !isCreating else { return }
|
|
guard currentScenicIdProvider() > 0 else {
|
|
onShowMessage?("请先选择景区")
|
|
return
|
|
}
|
|
|
|
if mode == .preOrder && order == nil {
|
|
onShowMessage?("请选择绑定订单")
|
|
return
|
|
}
|
|
if mode == .preShoot && singlePrice.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
onShowMessage?("请输入单张照片价格")
|
|
return
|
|
}
|
|
let materialPrice = Double(singlePrice)
|
|
if mode == .preShoot && materialPrice == nil {
|
|
onShowMessage?("请输入有效的单张照片价格")
|
|
return
|
|
}
|
|
if !autoRetouchConfiguration.isValid {
|
|
onShowMessage?("请选择修图模板")
|
|
return
|
|
}
|
|
|
|
let albumName: String
|
|
switch mode {
|
|
case .preOrder:
|
|
albumName = order?.projectName.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty ?? buildTodayAlbumName()
|
|
case .preShoot:
|
|
albumName = buildTodayAlbumName()
|
|
}
|
|
|
|
let request: TravelAlbumCreateRequest
|
|
switch mode {
|
|
case .preShoot:
|
|
request = TravelAlbumCreateRequest(
|
|
name: albumName,
|
|
type: mode.apiType,
|
|
orderNumber: nil,
|
|
materialNum: Int(freeCount) ?? 0,
|
|
materialPrice: materialPrice,
|
|
materialPackagePrice: Double(packagePrice) ?? 0,
|
|
photoPrice: 0
|
|
)
|
|
case .preOrder:
|
|
request = TravelAlbumCreateRequest(
|
|
name: albumName,
|
|
type: mode.apiType,
|
|
orderNumber: order?.orderNumber,
|
|
materialNum: nil,
|
|
materialPrice: nil,
|
|
materialPackagePrice: nil,
|
|
photoPrice: nil
|
|
)
|
|
}
|
|
|
|
isCreating = true
|
|
notifyStateChange()
|
|
defer {
|
|
isCreating = false
|
|
notifyStateChange()
|
|
}
|
|
|
|
do {
|
|
let response = try await api.create(request)
|
|
autoRetouchConfigurationStore.save(autoRetouchConfiguration, albumID: response.id)
|
|
isCreateSheetVisible = false
|
|
onShowMessage?("任务创建成功")
|
|
onCreatedAlbum?(response)
|
|
await loadAlbums(api: api)
|
|
} catch is CancellationError {
|
|
return
|
|
} catch {
|
|
let message = error.localizedDescription
|
|
if message.contains("订单已关联") {
|
|
onShowMessage?("该订单已关联旅拍相册,请刷新后重新选择")
|
|
await loadAvailableOrders(api: api)
|
|
} else {
|
|
onShowMessage?(message)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 打开相册码弹窗并加载二维码。
|
|
func openAlbumCode(album: TravelAlbum, api: any TravelAlbumServing) async {
|
|
albumCodeState = AlbumCodeState(
|
|
albumId: album.id,
|
|
albumName: album.name,
|
|
qrImageUrl: "",
|
|
isLoading: true,
|
|
isDownloading: false
|
|
)
|
|
notifyStateChange()
|
|
do {
|
|
let response = try await api.mpCode(id: album.id)
|
|
guard albumCodeState?.albumId == album.id else { return }
|
|
albumCodeState?.qrImageUrl = response.mpCodeOssUrl.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
albumCodeState?.isLoading = false
|
|
notifyStateChange()
|
|
} catch is CancellationError {
|
|
return
|
|
} catch {
|
|
guard albumCodeState?.albumId == album.id else { return }
|
|
albumCodeState?.isLoading = false
|
|
notifyStateChange()
|
|
onShowMessage?(error.localizedDescription.isEmpty ? "获取小程序码失败" : error.localizedDescription)
|
|
}
|
|
}
|
|
|
|
/// 关闭相册码弹窗。
|
|
func dismissAlbumCode() {
|
|
albumCodeState = nil
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 设置二维码下载状态。
|
|
func setDownloadingQRCode(_ isDownloading: Bool) {
|
|
albumCodeState?.isDownloading = isDownloading
|
|
notifyStateChange()
|
|
}
|
|
|
|
/// 生成 Android 同款今日相册名。
|
|
func buildTodayAlbumName() -> String {
|
|
let formatter = DateFormatter()
|
|
formatter.calendar = Calendar(identifier: .gregorian)
|
|
formatter.locale = Locale(identifier: "en_US_POSIX")
|
|
formatter.dateFormat = "yyyy-MM-dd"
|
|
let todayText = formatter.string(from: dateProvider())
|
|
let todayCount = albums.filter { $0.createdAt.hasPrefix(todayText) }.count
|
|
return "\(todayText)-\(String(format: "%03d", todayCount + 1))"
|
|
}
|
|
|
|
private func notifyStateChange() {
|
|
onStateChange?()
|
|
}
|
|
}
|
|
|
|
private extension String {
|
|
var nonEmpty: String? {
|
|
isEmpty ? nil : self
|
|
}
|
|
}
|