fix: 代码审查 P2 建议项修复(22 项体验优化)

EditorView (8 项):
- 时长警告图标区分:分享警告改用 square.and.arrow.up
- coverExtractionTask 竞态防护:新增 isViewActive 守卫
- sensoryFeedback 优化:缩放触觉仅在手势结束时触发
- iPad 右侧面板增加水平内边距
- 预设列表/兼容模式/AI 区域硬编码间距替换为 DesignTokens
- 诊断按钮 padding 替换为 DesignTokens
- generateButton 补充 accessibilityLabel

PresetManager + RecentWorksManager (5 项):
- iCloud 合并回写 + 防循环标志位
- iCloud 配额防护(>900KB 跳过写入)
- QuotaViolation/AccountChange 事件处理
- EditingPreset 自定义 Codable(decodeIfPresent + 默认值)
- RecentWork aspectRatio 枚举化
- 清理 saveToLocalOnly 死代码

ResultView + ProcessingView + HomeView (5 项):
- ResultView animateIn 改用 structured concurrency
- ProcessingView 阶段数提取为常量
- ProcessingView 脉冲动画去重
- HomeView 删除触觉升级为 .warning
- HomeView Cancel 按钮本地化

LivePhotoCore + AppState (4 项):
- coverImageURL 参数去重,内部从 exportParams 读取
- progress 回调钳位 min(pct, 1.0)
- CropRect 新增 clamped() 归一化校验
- AppState 初始化错误增加 os.Logger 日志

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
empty
2026-02-08 00:34:44 +08:00
parent f3bcaf4651
commit c826689ee4
8 changed files with 146 additions and 80 deletions

View File

@@ -82,13 +82,23 @@ public struct CropRect: Codable, Sendable, Hashable {
///
public static let full = CropRect()
/// [0, 1] CropRect x+width <= 1, y+height <= 1
public func clamped() -> CropRect {
let clampedX = min(max(x, 0), 1)
let clampedY = min(max(y, 0), 1)
let clampedW = min(max(width, 0), 1 - clampedX)
let clampedH = min(max(height, 0), 1 - clampedY)
return CropRect(x: clampedX, y: clampedY, width: clampedW, height: clampedH)
}
///
public func toPixelRect(videoSize: CGSize) -> CGRect {
CGRect(
x: x * videoSize.width,
y: y * videoSize.height,
width: width * videoSize.width,
height: height * videoSize.height
let safe = clamped()
return CGRect(
x: safe.x * videoSize.width,
y: safe.y * videoSize.height,
width: safe.width * videoSize.width,
height: safe.height * videoSize.height
)
}
}
@@ -521,10 +531,10 @@ public actor LivePhotoBuilder {
public func buildResources(
workId: UUID = UUID(),
sourceVideoURL: URL,
coverImageURL: URL? = nil,
exportParams: ExportParams = ExportParams(),
progress: (@Sendable (LivePhotoBuildProgress) -> Void)? = nil
) async throws -> LivePhotoBuildOutput {
let coverImageURL = exportParams.coverImageURL
let assetIdentifier = UUID().uuidString
let paths = try cacheManager.makeWorkPaths(workId: workId)
@@ -1070,7 +1080,7 @@ public actor LivePhotoBuilder {
if let sampleBuffer = videoReaderOutput.copyNextSampleBuffer() {
currentFrameCount += 1
let pct = Double(currentFrameCount) / Double(frameCount)
progress(pct)
progress(min(pct, 1.0))
videoWriterInput.append(sampleBuffer)
} else {
videoWriterInput.markAsFinished()
@@ -1192,14 +1202,12 @@ public actor LivePhotoWorkflow {
public func buildSaveValidate(
workId: UUID = UUID(),
sourceVideoURL: URL,
coverImageURL: URL? = nil,
exportParams: ExportParams = ExportParams(),
progress: (@Sendable (LivePhotoBuildProgress) -> Void)? = nil
) async throws -> LivePhotoWorkflowResult {
let output = try await builder.buildResources(
workId: workId,
sourceVideoURL: sourceVideoURL,
coverImageURL: coverImageURL,
exportParams: exportParams,
progress: progress
)