Files
to-live-photo/to-live-photo/to-live-photo/AppState.swift
empty 4bcad4d4b8 fix: 安全审查 P0-P2 问题修复(26项)
P0 关键修复:
- 移除 exit(0) 强制退出,改为应用语言设置后下次启动生效
- 修复 LivePhotoValidator hasResumed data race,引入线程安全 ResumeOnce
- 修复 addAssetID(toVideo:) continuation 泄漏,添加 writer/reader 启动状态检查
- 修复 OnboardingView "跳过" 按钮未国际化
- 修复 LanguageManager "跟随系统" 硬编码中文
- .gitignore 补全 AI 工具目录

P1 架构与 UI 修复:
- 修复 RealESRGANProcessor actor 隔离违规
- 修复 ODRManager continuation 生命周期保护
- TiledImageProcessor 改为流式拼接,降低内存峰值
- EditorView 硬编码颜色统一为设计系统
- ProcessingView 取消导航竞态修复
- 反馈诊断包添加知情同意提示

P2 代码质量与合规:
- EditorView/WallpaperGuideView 硬编码间距圆角统一为设计令牌
- PrivacyPolicyView 设计系统颜色统一
- HomeView 重复 onChange 合并
- PHAuthorizationStatus 改为英文技术术语
- Analytics 日志 assetId 脱敏
- 隐私政策补充 localIdentifier 存储说明
- 清理孤立的 subscription 翻译 key
- 脚本硬编码绝对路径改为相对路径
- DesignSystem SoftSlider 类型不匹配编译错误修复

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-02-07 20:04:41 +08:00

184 lines
5.9 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// AppState.swift
// to-live-photo
//
// App +
//
import SwiftUI
import PhotosUI
import LivePhotoCore
enum AppRoute: Hashable {
case home
case editor(videoURL: URL)
case processing(videoURL: URL, exportParams: ExportParams)
case result(workflowResult: LivePhotoWorkflowResult)
case wallpaperGuide(assetId: String)
case settings
}
@MainActor
@Observable
final class AppState {
var navigationPath = NavigationPath()
var processingProgress: LivePhotoBuildProgress?
var processingError: AppError?
var isProcessing = false
var isCancelling = false
///
private var currentExportParams: ExportParams?
private var workflow: LivePhotoWorkflow?
private var currentProcessingTask: Task<LivePhotoWorkflowResult?, Never>?
private var currentWorkId: UUID?
init() {
do {
workflow = try LivePhotoWorkflow()
} catch {
#if DEBUG
print("Failed to init LivePhotoWorkflow: \(error)")
#endif
}
}
func navigateTo(_ route: AppRoute) {
navigationPath.append(route)
}
func popToRoot() {
navigationPath = NavigationPath()
}
func pop() {
if !navigationPath.isEmpty {
navigationPath.removeLast()
}
}
func cancelProcessing() {
guard isProcessing, !isCancelling else { return }
isCancelling = true
//
currentProcessingTask?.cancel()
//
if let workId = currentWorkId, let workflow {
Task {
await workflow.cleanupWork(workId: workId)
await MainActor.run {
self.isProcessing = false
self.isCancelling = false
self.currentWorkId = nil
self.currentProcessingTask = nil
self.processingProgress = nil
self.pop()
}
}
} else {
isProcessing = false
isCancelling = false
currentWorkId = nil
currentProcessingTask = nil
processingProgress = nil
pop()
}
Analytics.shared.log(.buildLivePhotoCancel)
}
func startProcessing(videoURL: URL, exportParams: ExportParams) async -> LivePhotoWorkflowResult? {
guard let workflow else {
processingError = AppError(code: "LPB-001", message: "初始化失败", suggestedActions: ["重启 App"])
return nil
}
isProcessing = true
isCancelling = false
processingProgress = nil
processingError = nil
currentExportParams = exportParams
let workId = UUID()
currentWorkId = workId
Analytics.shared.log(.buildLivePhotoStart)
let task = Task<LivePhotoWorkflowResult?, Never> {
do {
//
try Task.checkCancellation()
let state = self
let result = try await workflow.buildSaveValidate(
workId: workId,
sourceVideoURL: videoURL,
coverImageURL: nil,
exportParams: exportParams
) { progress in
Task { @MainActor in
state.processingProgress = progress
}
}
//
try Task.checkCancellation()
await MainActor.run {
state.isProcessing = false
state.currentWorkId = nil
state.currentProcessingTask = nil
//
if let params = state.currentExportParams {
RecentWorksManager.shared.addWork(
assetId: result.savedAssetId,
aspectRatio: params.aspectRatio.rawValue,
compatibilityMode: params.compatibilityMode
)
}
state.currentExportParams = nil
}
Analytics.shared.log(.buildLivePhotoSuccess)
Analytics.shared.log(.saveAlbumSuccess, parameters: ["assetId": String(result.savedAssetId.prefix(8)) + "..."])
return result
} catch is CancellationError {
//
return nil
} catch let error as AppError {
await MainActor.run {
self.isProcessing = false
self.processingError = error
self.currentWorkId = nil
self.currentProcessingTask = nil
}
Analytics.shared.log(.buildLivePhotoFail, parameters: [
"code": error.code,
"stage": error.stage?.rawValue ?? "unknown",
"message": error.message
])
if error.stage == .saveToAlbum {
Analytics.shared.log(.saveAlbumFail, parameters: ["code": error.code])
}
return nil
} catch {
await MainActor.run {
self.isProcessing = false
self.processingError = AppError(code: "LPB-901", message: "未知错误", underlyingErrorDescription: error.localizedDescription, suggestedActions: ["重试"])
self.currentWorkId = nil
self.currentProcessingTask = nil
}
Analytics.shared.logError(.buildLivePhotoFail, error: error)
return nil
}
}
currentProcessingTask = task
return await task.value
}
}