fix: 安全审查 P3 问题修复(10项)

无障碍与用户体验:
- 补全 OnboardingView/ProcessingView/ResultView/WallpaperGuideView 无障碍标注
- ContentView 添加后台切换截屏保护(模糊覆盖层)
- EditorView iPad 右侧面板改为响应式宽度(minWidth:320, maxWidth:420)
- ResultView CelebrationParticles 改用 Task 替代 DispatchQueue 延迟
- DesignSystem accessibility key 从中文改为英文
- EditorView/WallpaperGuideView 生成按钮 accentColor 统一为设计系统
- OnboardingView 第四页添加相册权限预告提示

核心库优化:
- LivePhotoLogger 添加日志安全注释
- CacheManager.makeWorkPaths 添加磁盘空间预检查(<500MB 报错)
- ImageFormatConverter BGRA/ARGB 转换改用 vImagePermuteChannels 加速

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
empty
2026-02-07 20:11:50 +08:00
parent 4bcad4d4b8
commit a49fee4b52
10 changed files with 10193 additions and 10050 deletions

View File

@@ -212,21 +212,25 @@ enum ImageFormatConverter {
bytesPerRow: Int
) -> [UInt8] {
var result = [UInt8](repeating: 0, count: width * height * 4)
let dstBytesPerRow = width * 4
for y in 0..<height {
let srcRow = baseAddress.advanced(by: y * bytesPerRow).assumingMemoryBound(to: UInt8.self)
let dstOffset = y * width * 4
var srcBuffer = vImage_Buffer(
data: baseAddress,
height: vImagePixelCount(height),
width: vImagePixelCount(width),
rowBytes: bytesPerRow
)
for x in 0..<width {
let srcIdx = x * 4
let dstIdx = dstOffset + x * 4
// BGRA -> RGBA swap
result[dstIdx + 0] = srcRow[srcIdx + 2] // R
result[dstIdx + 1] = srcRow[srcIdx + 1] // G
result[dstIdx + 2] = srcRow[srcIdx + 0] // B
result[dstIdx + 3] = srcRow[srcIdx + 3] // A
}
result.withUnsafeMutableBufferPointer { dstPtr in
var dstBuffer = vImage_Buffer(
data: dstPtr.baseAddress!,
height: vImagePixelCount(height),
width: vImagePixelCount(width),
rowBytes: dstBytesPerRow
)
// BGRA RGBA: [2,1,0,3]
let permuteMap: [UInt8] = [2, 1, 0, 3]
vImagePermuteChannels_ARGB8888(&srcBuffer, &dstBuffer, permuteMap, vImage_Flags(kvImageNoFlags))
}
return result
@@ -239,21 +243,25 @@ enum ImageFormatConverter {
bytesPerRow: Int
) -> [UInt8] {
var result = [UInt8](repeating: 0, count: width * height * 4)
let dstBytesPerRow = width * 4
for y in 0..<height {
let srcRow = baseAddress.advanced(by: y * bytesPerRow).assumingMemoryBound(to: UInt8.self)
let dstOffset = y * width * 4
var srcBuffer = vImage_Buffer(
data: baseAddress,
height: vImagePixelCount(height),
width: vImagePixelCount(width),
rowBytes: bytesPerRow
)
for x in 0..<width {
let srcIdx = x * 4
let dstIdx = dstOffset + x * 4
// ARGB -> RGBA swap
result[dstIdx + 0] = srcRow[srcIdx + 1] // R
result[dstIdx + 1] = srcRow[srcIdx + 2] // G
result[dstIdx + 2] = srcRow[srcIdx + 3] // B
result[dstIdx + 3] = srcRow[srcIdx + 0] // A
}
result.withUnsafeMutableBufferPointer { dstPtr in
var dstBuffer = vImage_Buffer(
data: dstPtr.baseAddress!,
height: vImagePixelCount(height),
width: vImagePixelCount(width),
rowBytes: dstBytesPerRow
)
// ARGB RGBA: [1,2,3,0]
let permuteMap: [UInt8] = [1, 2, 3, 0]
vImagePermuteChannels_ARGB8888(&srcBuffer, &dstBuffer, permuteMap, vImage_Flags(kvImageNoFlags))
}
return result

View File

@@ -260,6 +260,18 @@ public struct CacheManager: Sendable {
}
public func makeWorkPaths(workId: UUID) throws -> LivePhotoWorkPaths {
// 500MB
let resourceValues = try baseDirectory.resourceValues(forKeys: [.volumeAvailableCapacityForImportantUsageKey])
if let availableCapacity = resourceValues.volumeAvailableCapacityForImportantUsage,
availableCapacity < 500_000_000 {
throw AppError(
code: "LPB-001",
stage: .normalize,
message: String(localized: "error.insufficientDiskSpace"),
suggestedActions: [String(localized: "error.clearCache"), String(localized: "error.freeSpace")]
)
}
let workDir = baseDirectory.appendingPathComponent(workId.uuidString, isDirectory: true)
try FileManager.default.createDirectory(at: workDir, withIntermediateDirectories: true)
return LivePhotoWorkPaths(
@@ -291,10 +303,14 @@ public struct LivePhotoLogger: Sendable {
self.logger = os.Logger(subsystem: subsystem, category: category)
}
///
/// - Important: 使 .public
public func info(_ message: String) {
logger.info("\(message, privacy: .public)")
}
///
/// - Important: 使 .public
public func error(_ message: String) {
logger.error("\(message, privacy: .public)")
}