317 lines
11 KiB
Swift
317 lines
11 KiB
Swift
//
|
||
// TravelAlbumPhotoPreviewModels.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import Foundation
|
||
|
||
/// 单张照片的 AI 修图状态,控制处理中禁用、结果对比以及再次修图覆盖。
|
||
struct TravelAlbumAIEditResultState: Equatable, Sendable {
|
||
/// 相册缩略图需要展示的 AI 修图状态。
|
||
enum ThumbnailStatus: Equatable, Sendable {
|
||
case none
|
||
case processing
|
||
case edited
|
||
case cover
|
||
case failed
|
||
}
|
||
|
||
/// 当前预览展示的版本。
|
||
enum DisplayMode: Equatable, Sendable {
|
||
case original
|
||
case edited
|
||
case atmosphere
|
||
case cover
|
||
}
|
||
|
||
private(set) var isProcessing = false
|
||
private(set) var didFailLastAttempt = false
|
||
private(set) var appliedPresetID: String?
|
||
private(set) var appliedAtmosphereOptionID: String?
|
||
private(set) var appliedCoverTemplateID: String?
|
||
private(set) var displayMode: DisplayMode = .original
|
||
|
||
/// 已经存在可查看的修图结果。
|
||
var hasEditedResult: Bool {
|
||
appliedPresetID != nil
|
||
}
|
||
|
||
/// 处理期间禁止再次发起 AI 修图。
|
||
var canStartEditing: Bool {
|
||
!isProcessing
|
||
}
|
||
|
||
/// 当前结果是否为封面模板生成的封面图。
|
||
var isCover: Bool {
|
||
appliedCoverTemplateID != nil
|
||
}
|
||
|
||
/// 当前是否已经生成可独立查看的氛围感修图结果。
|
||
var hasAtmosphereResult: Bool {
|
||
appliedAtmosphereOptionID != nil
|
||
}
|
||
|
||
/// 根据修图任务状态生成缩略图角标,处理中和失败优先于历史成功结果。
|
||
var thumbnailStatus: ThumbnailStatus {
|
||
if isProcessing {
|
||
return .processing
|
||
}
|
||
if didFailLastAttempt {
|
||
return .failed
|
||
}
|
||
if isCover {
|
||
return .cover
|
||
}
|
||
return hasEditedResult ? .edited : .none
|
||
}
|
||
|
||
/// 开始修图;已有结果保留到新结果完成,避免处理中画面回退。
|
||
mutating func startProcessing() {
|
||
guard !isProcessing else { return }
|
||
isProcessing = true
|
||
didFailLastAttempt = false
|
||
}
|
||
|
||
/// 完成修图并覆盖旧预设结果,默认展示新的修图后版本。
|
||
mutating func complete(
|
||
presetID: String,
|
||
atmosphereOptionID: String? = nil,
|
||
coverTemplateID: String? = nil
|
||
) {
|
||
isProcessing = false
|
||
didFailLastAttempt = false
|
||
appliedPresetID = presetID
|
||
appliedAtmosphereOptionID = atmosphereOptionID
|
||
appliedCoverTemplateID = coverTemplateID
|
||
displayMode = coverTemplateID == nil ? .edited : .cover
|
||
}
|
||
|
||
/// 处理失败时恢复入口;已有成功结果继续保留。
|
||
mutating func failProcessing() {
|
||
isProcessing = false
|
||
didFailLastAttempt = true
|
||
}
|
||
|
||
/// 选择还原原图后清除历史修图结果和失败状态。
|
||
mutating func restoreOriginal() {
|
||
isProcessing = false
|
||
didFailLastAttempt = false
|
||
appliedPresetID = nil
|
||
appliedAtmosphereOptionID = nil
|
||
appliedCoverTemplateID = nil
|
||
displayMode = .original
|
||
}
|
||
|
||
/// 在已有结果时切换原图或修图后预览。
|
||
mutating func selectDisplayMode(_ mode: DisplayMode) {
|
||
guard hasEditedResult else {
|
||
displayMode = .original
|
||
return
|
||
}
|
||
if mode == .atmosphere, !hasAtmosphereResult {
|
||
displayMode = .edited
|
||
return
|
||
}
|
||
if mode == .cover, !isCover {
|
||
displayMode = .edited
|
||
return
|
||
}
|
||
displayMode = mode
|
||
}
|
||
}
|
||
|
||
/// 相册管理底部操作栏状态,用于在上传与多选操作之间切换。
|
||
struct TravelAlbumSelectionToolbarState: Equatable, Sendable {
|
||
let isSelectionMode: Bool
|
||
let selectedCount: Int
|
||
|
||
/// 普通状态显示上传照片。
|
||
var showsUpload: Bool {
|
||
!isSelectionMode
|
||
}
|
||
|
||
/// 多选状态显示 AI 修图和删除。
|
||
var showsSelectionActions: Bool {
|
||
isSelectionMode
|
||
}
|
||
|
||
/// 至少选中一张照片后才允许执行批量操作。
|
||
var selectionActionsEnabled: Bool {
|
||
isSelectionMode && selectedCount > 0
|
||
}
|
||
}
|
||
|
||
/// 相册照片预览状态,维护当前照片位置并处理左右切换。
|
||
struct TravelAlbumPhotoPreviewState: Equatable, Sendable {
|
||
private(set) var materials: [TravelAlbumMaterial]
|
||
private(set) var currentIndex: Int
|
||
|
||
init(materials: [TravelAlbumMaterial], initialIndex: Int) {
|
||
self.materials = materials
|
||
currentIndex = min(max(initialIndex, 0), max(materials.count - 1, 0))
|
||
}
|
||
|
||
/// 当前正在预览的照片。
|
||
var currentMaterial: TravelAlbumMaterial? {
|
||
materials.indices.contains(currentIndex) ? materials[currentIndex] : nil
|
||
}
|
||
|
||
/// 切换至下一张;已经是最后一张时保持不变。
|
||
@discardableResult
|
||
mutating func moveNext() -> Bool {
|
||
guard currentIndex + 1 < materials.count else { return false }
|
||
currentIndex += 1
|
||
return true
|
||
}
|
||
|
||
/// 切换至上一张;已经是第一张时保持不变。
|
||
@discardableResult
|
||
mutating func movePrevious() -> Bool {
|
||
guard currentIndex > 0 else { return false }
|
||
currentIndex -= 1
|
||
return true
|
||
}
|
||
|
||
/// 删除当前照片,并自动定位到相邻照片。
|
||
@discardableResult
|
||
mutating func removeCurrent() -> Bool {
|
||
guard materials.indices.contains(currentIndex) else { return false }
|
||
materials.remove(at: currentIndex)
|
||
if currentIndex >= materials.count {
|
||
currentIndex = max(materials.count - 1, 0)
|
||
}
|
||
return !materials.isEmpty
|
||
}
|
||
}
|
||
|
||
/// 相册照片 AI 修图预设,当前为本地演示数据,后续可替换为服务端下发的预设列表。
|
||
struct TravelAlbumEditPreset: Hashable, Sendable, Identifiable {
|
||
/// 图像效果类型,用于在预览页生成对应效果图。
|
||
enum Effect: String, Hashable, Sendable {
|
||
case original
|
||
case portrait
|
||
case vintage
|
||
case brocade
|
||
case distantMountain
|
||
case mist
|
||
case summer
|
||
case rich
|
||
}
|
||
|
||
let id: String
|
||
let title: String
|
||
let effect: Effect
|
||
|
||
/// AI 修图底部弹窗展示的默认预设,暂无接口时用于完整演示选择流程。
|
||
static let defaultOptions: [TravelAlbumEditPreset] = [
|
||
TravelAlbumEditPreset(id: "original", title: "还原为原图", effect: .original),
|
||
TravelAlbumEditPreset(id: "portrait", title: "写真-简约肖像", effect: .portrait),
|
||
TravelAlbumEditPreset(id: "vintage", title: "写真-清冷古风", effect: .vintage),
|
||
TravelAlbumEditPreset(id: "brocade", title: "旅拍-锦绣", effect: .brocade),
|
||
TravelAlbumEditPreset(id: "distant_mountain", title: "旅拍-远山", effect: .distantMountain),
|
||
TravelAlbumEditPreset(id: "mist", title: "旅拍-薄雾", effect: .mist),
|
||
TravelAlbumEditPreset(id: "summer", title: "油画-夏日", effect: .summer),
|
||
TravelAlbumEditPreset(id: "rich", title: "油画-浓郁", effect: .rich),
|
||
]
|
||
}
|
||
|
||
/// 可选的氛围感修图样式;选中后在原图精修之外额外生成一张独立结果。
|
||
struct TravelAlbumAtmosphereEditOption: Hashable, Sendable, Identifiable {
|
||
let id: String
|
||
let title: String
|
||
let subtitle: String
|
||
let effect: TravelAlbumEditPreset.Effect
|
||
|
||
/// 暂无接口时用于演示横向滚动单选的氛围感样式。
|
||
static let defaultOptions: [TravelAlbumAtmosphereEditOption] = [
|
||
TravelAlbumAtmosphereEditOption(
|
||
id: "warm_sun",
|
||
title: "日落暖阳",
|
||
subtitle: "温暖柔和",
|
||
effect: .summer
|
||
),
|
||
TravelAlbumAtmosphereEditOption(
|
||
id: "retro_film",
|
||
title: "复古胶片",
|
||
subtitle: "怀旧颗粒感",
|
||
effect: .vintage
|
||
),
|
||
TravelAlbumAtmosphereEditOption(
|
||
id: "clear_blue",
|
||
title: "清透蓝调",
|
||
subtitle: "通透轻盈",
|
||
effect: .mist
|
||
),
|
||
TravelAlbumAtmosphereEditOption(
|
||
id: "forest_mist",
|
||
title: "森系薄雾",
|
||
subtitle: "低饱和自然感",
|
||
effect: .distantMountain
|
||
),
|
||
TravelAlbumAtmosphereEditOption(
|
||
id: "vivid_story",
|
||
title: "浓郁故事",
|
||
subtitle: "高饱和电影感",
|
||
effect: .rich
|
||
),
|
||
]
|
||
|
||
/// 兼容现有结果测试与默认演示数据的首个氛围感样式。
|
||
static let defaultOption = defaultOptions[0]
|
||
}
|
||
|
||
/// 多图 AI 修图使用的封面风格模板,当前为本地演示数据,后续可替换为服务端配置。
|
||
struct TravelAlbumCoverTemplate: Hashable, Sendable, Identifiable {
|
||
/// 封面预览的排版样式,用于生成可辨识的本地效果图。
|
||
enum PreviewStyle: String, Hashable, Sendable {
|
||
case travelMemoir
|
||
case minimal
|
||
case scenicStory
|
||
case film
|
||
}
|
||
|
||
/// 触发展示封面模板选择的最少照片数量。
|
||
static let minimumPhotoCount = 4
|
||
|
||
let id: String
|
||
let title: String
|
||
let previewStyle: PreviewStyle
|
||
/// 当前消耗的精修额度;免费阶段为 0,后续收费可由服务端改为正数。
|
||
let quotaCost: Int
|
||
|
||
/// 当前模板是否为赠送且不占用精修张数。
|
||
var isFreeGift: Bool {
|
||
quotaCost == 0
|
||
}
|
||
|
||
/// 判断本次多选是否需要展示封面模板模块。
|
||
static func shouldShow(for selectedPhotoCount: Int) -> Bool {
|
||
selectedPhotoCount >= minimumPhotoCount
|
||
}
|
||
|
||
/// 暂无接口时用于完整演示单选流程的封面模板。
|
||
static let defaultOptions: [TravelAlbumCoverTemplate] = [
|
||
TravelAlbumCoverTemplate(id: "travel_memoir", title: "旅行纪念册", previewStyle: .travelMemoir, quotaCost: 0),
|
||
TravelAlbumCoverTemplate(id: "minimal", title: "简约留白", previewStyle: .minimal, quotaCost: 0),
|
||
TravelAlbumCoverTemplate(id: "scenic_story", title: "景区故事", previewStyle: .scenicStory, quotaCost: 0),
|
||
TravelAlbumCoverTemplate(id: "film", title: "电影胶片", previewStyle: .film, quotaCost: 0),
|
||
]
|
||
}
|
||
|
||
/// AI 修图弹窗最终提交的样式组合,统一承载原图精修、氛围感和封面模板。
|
||
struct TravelAlbumAIEditSelection: Equatable, Sendable {
|
||
let refinedPreset: TravelAlbumEditPreset
|
||
let atmosphereOption: TravelAlbumAtmosphereEditOption?
|
||
let coverTemplate: TravelAlbumCoverTemplate?
|
||
|
||
/// 每张普通照片需要生成的修图结果数量,不包含原图。
|
||
var resultCountPerPhoto: Int {
|
||
atmosphereOption == nil ? 1 : 2
|
||
}
|
||
|
||
/// 弹窗当前展示并提交的样式类别数量。
|
||
var selectedStyleCount: Int {
|
||
1 + (atmosphereOption == nil ? 0 : 1) + (coverTemplate == nil ? 0 : 1)
|
||
}
|
||
}
|