// // TravelAlbumAutoRetouchModels.swift // suixinkan // import Foundation /// 自动修图第一级方式,页面上始终只展示“不修图”和“AI 修图”两个选项。 enum TravelAlbumRetouchMode: String, CaseIterable, Hashable, Sendable { case disabled case aiRetouch /// 修图方式的用户可见名称。 var title: String { switch self { case .disabled: return "不修图" case .aiRetouch: return "AI 修图" } } } /// 相册自动 AI 修图配置,用于在新建相册与照片上传页之间传递模板选择。 struct TravelAlbumAutoRetouchConfiguration: Codable, Equatable, Sendable { let isEnabled: Bool let templateID: String? /// 默认不启用自动修图。 static let disabled = TravelAlbumAutoRetouchConfiguration(isEnabled: false, templateID: nil) /// 根据已选模板创建启用状态配置。 static func enabled(templateID: String) -> TravelAlbumAutoRetouchConfiguration { TravelAlbumAutoRetouchConfiguration(isEnabled: true, templateID: templateID) } /// 当前配置对应的修图模板。 var template: TravelAlbumEditPreset? { guard isEnabled, let templateID else { return nil } return TravelAlbumEditPreset.autoRetouchOptions.first { $0.id == templateID } } /// 是否可以作为完整的自动修图配置提交。 var isValid: Bool { !isEnabled || template != nil } /// 上传页紧凑设置项的展示文案。 var uploadOptionTitle: String { isEnabled ? TravelAlbumRetouchMode.aiRetouch.title : TravelAlbumRetouchMode.disabled.title } } extension TravelAlbumEditPreset { /// 自动修图可选模板,不包含手动修图中的“还原为原图”。 static var autoRetouchOptions: [TravelAlbumEditPreset] { defaultOptions.filter { $0.effect != .original } } } /// 单张 OTG 照片的自动 AI 修图状态,与原有上传状态分开计算。 enum TravelAlbumAutoRetouchState: String, Codable, Equatable, Sendable { case none = "NONE" case processing = "PROCESSING" case completed = "COMPLETED" case failed = "FAILED" } /// 相册自动修图配置读写接口,便于 ViewModel 与单元测试注入。 protocol TravelAlbumAutoRetouchConfigurationStoring: AnyObject { /// 读取指定服务端相册的配置。 func configuration(albumID: Int) -> TravelAlbumAutoRetouchConfiguration /// 保存指定服务端相册的配置。 func save(_ configuration: TravelAlbumAutoRetouchConfiguration, albumID: Int) /// 删除指定相册的本地配置。 func remove(albumID: Int) } /// 使用 UserDefaults 按服务端相册 ID 持久化自动 AI 修图配置。 final class TravelAlbumAutoRetouchConfigurationStore: TravelAlbumAutoRetouchConfigurationStoring { static let shared = TravelAlbumAutoRetouchConfigurationStore() private let userDefaults: UserDefaults private let keyPrefix: String /// 创建配置存储;测试可传入独立 UserDefaults suite。 init( userDefaults: UserDefaults = .standard, keyPrefix: String = "travelAlbum.autoRetouch.album" ) { self.userDefaults = userDefaults self.keyPrefix = keyPrefix } func configuration(albumID: Int) -> TravelAlbumAutoRetouchConfiguration { guard albumID > 0, let data = userDefaults.data(forKey: key(albumID: albumID)), let configuration = try? JSONDecoder().decode(TravelAlbumAutoRetouchConfiguration.self, from: data), configuration.isValid else { return .disabled } return configuration } func save(_ configuration: TravelAlbumAutoRetouchConfiguration, albumID: Int) { guard albumID > 0 else { return } let normalized = configuration.isValid ? configuration : .disabled guard let data = try? JSONEncoder().encode(normalized) else { return } userDefaults.set(data, forKey: key(albumID: albumID)) } func remove(albumID: Int) { guard albumID > 0 else { return } userDefaults.removeObject(forKey: key(albumID: albumID)) } private func key(albumID: Int) -> String { "\(keyPrefix).\(albumID)" } }