feat: v1.1 P1 功能实现 + 体验优化(4 项新功能 + 9 项修复)
## 新功能 ### 视频时长放宽(Feature 5) - LivePhotoCore: targetDuration 从固定 0.917 秒改为动态计算(0.5-5.0 秒) - LivePhotoCore: keyFrameTime 按比例映射到目标视频时长 - EditorView: slider 上限从 1.5 秒提升至 5.0 秒 - EditorView: 超过 2 秒显示壁纸兼容性提示,超过 3 秒显示分享截断提示 ### 自定义封面帧(Feature 4) - ExportParams: 新增 coverImageURL 字段 - EditorView: 封面帧区域增加 PhotosPicker 从相册导入照片作为封面 - AppState: coverImageURL 传递到 LivePhotoWorkflow - EditorView: onDisappear 时清理临时封面文件,避免磁盘泄漏 ### 收藏/模板预设(Feature 6) - 新增 PresetManager.swift: EditingPreset 模型 + 预设管理器 - 支持保存/加载/删除预设,最多 10 个 - UserDefaults + iCloud KV Store 双重持久化 - EditorView: 预设保存/加载 UI + 空状态 ContentUnavailableView ### iCloud 同步(Feature 7) - RecentWorksManager: 新增 NSUbiquitousKeyValueStore 同步 - PresetManager: 同步支持 iCloud KV Store - 合并策略:按 ID 去重,保留最新记录,限制条数上限 ## Bug 修复 ### P0 级 - ResultView: Live Photo 预览比例从固定值改为动态计算 aspectRatio - EditorView: 修复本地化字符串拼接问题(使用 String(localized:) 替代硬编码) ### P1 级 - EditorView: 预设列表空状态显示 ContentUnavailableView - ProcessingView: 进度环与百分比文本动画同步(统一 overallProgress 计算) ### P2 级 - EditorView: 添加触觉反馈(比例切换、生成按钮、预设保存、封面导入) - HomeView: 删除作品触觉反馈 + 入场动画优化 - ProcessingView: 脉冲动画背景尺寸微调(160-175pt) - LaunchScreen: 品牌启动画面(App Icon + 标题 + 副标题) ## 本地化 - 新增约 25 个本地化 key,覆盖 8 种语言 (zh-Hans, zh-Hant, en, ja, ko, es, fr, ar) - 包含:预设管理、封面导入、时长提示、删除确认、 空状态、诊断建议等全部新增 UI 文案 ## 改动文件(16 个) - Sources/LivePhotoCore/LivePhotoCore.swift — 动态时长 + coverImageURL - to-live-photo/AppState.swift — coverImageURL 传递 - to-live-photo/PresetManager.swift — 新增预设管理器 - to-live-photo/RecentWorksManager.swift — iCloud 同步 - to-live-photo/DesignSystem.swift — 新增设计令牌 - to-live-photo/Localizable.xcstrings — 25+ 本地化 key - to-live-photo/Views/EditorView.swift — 4 项新功能 UI - to-live-photo/Views/HomeView.swift — 删除作品 + 触觉反馈 - to-live-photo/Views/ProcessingView.swift — 进度环动画修复 - to-live-photo/Views/ResultView.swift — 预览比例修复 + Live Photo 预览 - to-live-photo/Base.lproj/LaunchScreen.storyboard — 品牌启动画面 - to-live-photo/Assets.xcassets/Launch*.colorset — 启动画面颜色资源 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,6 +134,7 @@ public struct ExportParams: Codable, Sendable, Hashable {
|
||||
public var aspectRatio: AspectRatioTemplate
|
||||
public var compatibilityMode: Bool
|
||||
public var targetFrameRate: Int
|
||||
public var coverImageURL: URL?
|
||||
public var aiEnhanceConfig: AIEnhanceConfig
|
||||
|
||||
public init(
|
||||
@@ -148,6 +149,7 @@ public struct ExportParams: Codable, Sendable, Hashable {
|
||||
aspectRatio: AspectRatioTemplate = .original,
|
||||
compatibilityMode: Bool = false,
|
||||
targetFrameRate: Int = 60,
|
||||
coverImageURL: URL? = nil,
|
||||
aiEnhanceConfig: AIEnhanceConfig = .disabled
|
||||
) {
|
||||
self.trimStart = trimStart
|
||||
@@ -161,6 +163,7 @@ public struct ExportParams: Codable, Sendable, Hashable {
|
||||
self.aspectRatio = aspectRatio
|
||||
self.compatibilityMode = compatibilityMode
|
||||
self.targetFrameRate = targetFrameRate
|
||||
self.coverImageURL = coverImageURL
|
||||
self.aiEnhanceConfig = aiEnhanceConfig
|
||||
}
|
||||
|
||||
@@ -518,8 +521,9 @@ public actor LivePhotoBuilder {
|
||||
destinationURL: trimmedURL
|
||||
)
|
||||
|
||||
// 关键:将视频变速到约 1 秒,与 metadata.mov 的时间标记匹配
|
||||
let targetDuration = CMTimeMake(value: 550, timescale: 600) // ~0.917 秒,与 live-wallpaper 一致
|
||||
// 根据用户选择的裁剪时长动态计算目标时长(上限 5 秒)
|
||||
let trimmedSeconds = min(max(exportParams.trimEnd - exportParams.trimStart, 0.5), 5.0)
|
||||
let targetDuration = CMTime(seconds: trimmedSeconds, preferredTimescale: 600)
|
||||
progress?(LivePhotoBuildProgress(stage: .normalize, fraction: 0.5))
|
||||
let scaledVideoURL = try await scaleVideoToTargetDuration(
|
||||
sourceURL: trimmedVideoURL,
|
||||
@@ -531,8 +535,9 @@ public actor LivePhotoBuilder {
|
||||
destinationURL: scaledURL
|
||||
)
|
||||
|
||||
// 计算关键帧时间:目标视频的中间位置(0.5 秒处,与 metadata.mov 的 still-image-time 匹配)
|
||||
let relativeKeyFrameTime = 0.5 // 固定为 0.5 秒,与 metadata.mov 匹配
|
||||
// 计算关键帧在目标视频中的绝对时间位置
|
||||
let keyFrameRatio = (exportParams.keyFrameTime - exportParams.trimStart) / max(0.001, exportParams.trimEnd - exportParams.trimStart)
|
||||
let relativeKeyFrameTime = max(0, min(trimmedSeconds, keyFrameRatio * trimmedSeconds))
|
||||
|
||||
progress?(LivePhotoBuildProgress(stage: .extractKeyFrame, fraction: 0))
|
||||
let keyPhotoURL = try await resolveKeyPhotoURL(
|
||||
|
||||
Reference in New Issue
Block a user