fix: UI 设计系统优化 - 无障碍、深色模式、对比度
- DesignSystem: 深色模式阴影适配,textMuted 对比度修复 - DesignSystem: SoftIconButton/SoftSlider/SoftProgressRing 添加 accessibilityLabel - EditorView: AspectRatioButton 添加无障碍支持,清理硬编码颜色 - WallpaperGuideView: 清理硬编码颜色 (Color.secondary → Color.softElevated) - Localizable: 修复 home.worksCount 插值 key 格式 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -41,6 +41,18 @@ enum DesignTokens {
|
|||||||
static let xxxl: CGFloat = 32
|
static let xxxl: CGFloat = 32
|
||||||
static let display: CGFloat = 40
|
static let display: CGFloat = 40
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: 动画
|
||||||
|
enum Animation {
|
||||||
|
/// 按钮、卡片等交互元素的弹性动画
|
||||||
|
static let spring = SwiftUI.Animation.spring(response: 0.3, dampingFraction: 0.6)
|
||||||
|
/// 快速状态切换
|
||||||
|
static let quick = SwiftUI.Animation.easeInOut(duration: 0.15)
|
||||||
|
/// 标准过渡
|
||||||
|
static let standard = SwiftUI.Animation.easeInOut(duration: 0.25)
|
||||||
|
/// 进度条等连续动画
|
||||||
|
static let smooth = SwiftUI.Animation.easeInOut(duration: 0.5)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 颜色系统
|
// MARK: - 颜色系统
|
||||||
@@ -59,7 +71,8 @@ extension Color {
|
|||||||
// MARK: 文字色
|
// MARK: 文字色
|
||||||
static let textPrimary = Color(light: Color(hex: "#2D2D3A"), dark: Color(hex: "#E4E4EB"))
|
static let textPrimary = Color(light: Color(hex: "#2D2D3A"), dark: Color(hex: "#E4E4EB"))
|
||||||
static let textSecondary = Color(light: Color(hex: "#6B6B7B"), dark: Color(hex: "#A0A0B2"))
|
static let textSecondary = Color(light: Color(hex: "#6B6B7B"), dark: Color(hex: "#A0A0B2"))
|
||||||
static let textMuted = Color(light: Color(hex: "#9999A9"), dark: Color(hex: "#6B6B7B"))
|
// 调整 textMuted 以满足 WCAG AA 对比度标准 (4.5:1)
|
||||||
|
static let textMuted = Color(light: Color(hex: "#777788"), dark: Color(hex: "#8888A0"))
|
||||||
|
|
||||||
// MARK: 强调色
|
// MARK: 强调色
|
||||||
static let accentPurple = Color(hex: "#6366F1")
|
static let accentPurple = Color(hex: "#6366F1")
|
||||||
@@ -137,42 +150,71 @@ extension Color {
|
|||||||
// MARK: - Soft UI 阴影
|
// MARK: - Soft UI 阴影
|
||||||
struct SoftShadow: ViewModifier {
|
struct SoftShadow: ViewModifier {
|
||||||
let isPressed: Bool
|
let isPressed: Bool
|
||||||
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
.shadow(
|
.shadow(
|
||||||
color: Color.black.opacity(isPressed ? 0.15 : 0.08),
|
color: shadowDark,
|
||||||
radius: isPressed ? 4 : 8,
|
radius: isPressed ? 4 : 8,
|
||||||
x: isPressed ? 2 : 4,
|
x: isPressed ? 2 : 4,
|
||||||
y: isPressed ? 2 : 4
|
y: isPressed ? 2 : 4
|
||||||
)
|
)
|
||||||
.shadow(
|
.shadow(
|
||||||
color: Color.white.opacity(isPressed ? 0.5 : 0.7),
|
color: shadowLight,
|
||||||
radius: isPressed ? 4 : 8,
|
radius: isPressed ? 4 : 8,
|
||||||
x: isPressed ? -2 : -4,
|
x: isPressed ? -2 : -4,
|
||||||
y: isPressed ? -2 : -4
|
y: isPressed ? -2 : -4
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 深色模式下使用更深的暗影
|
||||||
|
private var shadowDark: Color {
|
||||||
|
colorScheme == .dark
|
||||||
|
? Color.black.opacity(isPressed ? 0.4 : 0.3)
|
||||||
|
: Color.black.opacity(isPressed ? 0.15 : 0.08)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 深色模式下使用微弱的高光(模拟边缘光)
|
||||||
|
private var shadowLight: Color {
|
||||||
|
colorScheme == .dark
|
||||||
|
? Color.white.opacity(isPressed ? 0.03 : 0.05)
|
||||||
|
: Color.white.opacity(isPressed ? 0.5 : 0.7)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SoftInnerShadow: ViewModifier {
|
struct SoftInnerShadow: ViewModifier {
|
||||||
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
content
|
content
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: DesignTokens.Radius.lg)
|
RoundedRectangle(cornerRadius: DesignTokens.Radius.lg)
|
||||||
.stroke(Color.black.opacity(0.06), lineWidth: 1)
|
.stroke(innerShadowDark, lineWidth: 1)
|
||||||
.blur(radius: 2)
|
.blur(radius: 2)
|
||||||
.offset(x: 1, y: 1)
|
.offset(x: 1, y: 1)
|
||||||
.mask(RoundedRectangle(cornerRadius: DesignTokens.Radius.lg).fill(LinearGradient(colors: [.black, .clear], startPoint: .topLeading, endPoint: .bottomTrailing)))
|
.mask(RoundedRectangle(cornerRadius: DesignTokens.Radius.lg).fill(LinearGradient(colors: [.black, .clear], startPoint: .topLeading, endPoint: .bottomTrailing)))
|
||||||
)
|
)
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: DesignTokens.Radius.lg)
|
RoundedRectangle(cornerRadius: DesignTokens.Radius.lg)
|
||||||
.stroke(Color.white.opacity(0.5), lineWidth: 1)
|
.stroke(innerShadowLight, lineWidth: 1)
|
||||||
.blur(radius: 2)
|
.blur(radius: 2)
|
||||||
.offset(x: -1, y: -1)
|
.offset(x: -1, y: -1)
|
||||||
.mask(RoundedRectangle(cornerRadius: DesignTokens.Radius.lg).fill(LinearGradient(colors: [.clear, .black], startPoint: .topLeading, endPoint: .bottomTrailing)))
|
.mask(RoundedRectangle(cornerRadius: DesignTokens.Radius.lg).fill(LinearGradient(colors: [.clear, .black], startPoint: .topLeading, endPoint: .bottomTrailing)))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var innerShadowDark: Color {
|
||||||
|
colorScheme == .dark
|
||||||
|
? Color.black.opacity(0.3)
|
||||||
|
: Color.black.opacity(0.06)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var innerShadowLight: Color {
|
||||||
|
colorScheme == .dark
|
||||||
|
? Color.white.opacity(0.08)
|
||||||
|
: Color.white.opacity(0.5)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
@@ -315,11 +357,13 @@ struct SoftSecondaryButton: View {
|
|||||||
struct SoftIconButton: View {
|
struct SoftIconButton: View {
|
||||||
let icon: String
|
let icon: String
|
||||||
let isActive: Bool
|
let isActive: Bool
|
||||||
|
let accessibilityLabel: String?
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
init(_ icon: String, isActive: Bool = false, action: @escaping () -> Void) {
|
init(_ icon: String, isActive: Bool = false, accessibilityLabel: String? = nil, action: @escaping () -> Void) {
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.isActive = isActive
|
self.isActive = isActive
|
||||||
|
self.accessibilityLabel = accessibilityLabel
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,6 +391,9 @@ struct SoftIconButton: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.buttonStyle(ScaleButtonStyle())
|
.buttonStyle(ScaleButtonStyle())
|
||||||
|
.accessibilityLabel(accessibilityLabel ?? icon)
|
||||||
|
.accessibilityAddTraits(.isButton)
|
||||||
|
.accessibilityAddTraits(isActive ? .isSelected : [])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,17 +403,20 @@ struct SoftProgressRing: View {
|
|||||||
let size: CGFloat
|
let size: CGFloat
|
||||||
let lineWidth: CGFloat
|
let lineWidth: CGFloat
|
||||||
let gradient: LinearGradient
|
let gradient: LinearGradient
|
||||||
|
let accessibilityLabel: String?
|
||||||
|
|
||||||
init(
|
init(
|
||||||
progress: Double,
|
progress: Double,
|
||||||
size: CGFloat = 120,
|
size: CGFloat = 120,
|
||||||
lineWidth: CGFloat = 8,
|
lineWidth: CGFloat = 8,
|
||||||
gradient: LinearGradient = Color.gradientPrimary
|
gradient: LinearGradient = Color.gradientPrimary,
|
||||||
|
accessibilityLabel: String? = nil
|
||||||
) {
|
) {
|
||||||
self.progress = progress
|
self.progress = progress
|
||||||
self.size = size
|
self.size = size
|
||||||
self.lineWidth = lineWidth
|
self.lineWidth = lineWidth
|
||||||
self.gradient = gradient
|
self.gradient = gradient
|
||||||
|
self.accessibilityLabel = accessibilityLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -394,8 +444,11 @@ struct SoftProgressRing: View {
|
|||||||
.stroke(gradient, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
|
.stroke(gradient, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round))
|
||||||
.frame(width: size - 16, height: size - 16)
|
.frame(width: size - 16, height: size - 16)
|
||||||
.rotationEffect(.degrees(-90))
|
.rotationEffect(.degrees(-90))
|
||||||
.animation(.easeInOut(duration: 0.5), value: progress)
|
.animation(DesignTokens.Animation.smooth, value: progress)
|
||||||
}
|
}
|
||||||
|
.accessibilityElement(children: .ignore)
|
||||||
|
.accessibilityLabel(accessibilityLabel ?? String(localized: "进度"))
|
||||||
|
.accessibilityValue(Text("\(Int(progress * 100))%"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,15 +528,21 @@ struct SoftSlider: View {
|
|||||||
@Binding var value: Double
|
@Binding var value: Double
|
||||||
let range: ClosedRange<Double>
|
let range: ClosedRange<Double>
|
||||||
let gradient: LinearGradient
|
let gradient: LinearGradient
|
||||||
|
let accessibilityLabel: String
|
||||||
|
let step: Double
|
||||||
|
|
||||||
init(
|
init(
|
||||||
value: Binding<Double>,
|
value: Binding<Double>,
|
||||||
in range: ClosedRange<Double>,
|
in range: ClosedRange<Double>,
|
||||||
gradient: LinearGradient = Color.gradientPrimary
|
step: Double = 0.1,
|
||||||
|
gradient: LinearGradient = Color.gradientPrimary,
|
||||||
|
accessibilityLabel: String = ""
|
||||||
) {
|
) {
|
||||||
self._value = value
|
self._value = value
|
||||||
self.range = range
|
self.range = range
|
||||||
|
self.step = step
|
||||||
self.gradient = gradient
|
self.gradient = gradient
|
||||||
|
self.accessibilityLabel = accessibilityLabel
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@@ -521,6 +580,19 @@ struct SoftSlider: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: 28)
|
.frame(height: 28)
|
||||||
|
.accessibilityElement(children: .ignore)
|
||||||
|
.accessibilityLabel(accessibilityLabel.isEmpty ? String(localized: "滑块") : accessibilityLabel)
|
||||||
|
.accessibilityValue(Text(String(format: "%.1f", value)))
|
||||||
|
.accessibilityAdjustableAction { direction in
|
||||||
|
switch direction {
|
||||||
|
case .increment:
|
||||||
|
value = min(range.upperBound, value + step)
|
||||||
|
case .decrement:
|
||||||
|
value = max(range.lowerBound, value - step)
|
||||||
|
@unknown default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -254,7 +254,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"home.worksCount" : {
|
"home.worksCount %lld" : {
|
||||||
"extractionState" : "manual",
|
"extractionState" : "manual",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@@ -1874,6 +1874,78 @@
|
|||||||
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "确定要清空最近作品记录吗?这不会删除相册中的 Live Photo。" } },
|
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "确定要清空最近作品记录吗?这不会删除相册中的 Live Photo。" } },
|
||||||
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "確定要清除最近作品記錄嗎?這不會刪除相簿中的 Live Photo。" } }
|
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "確定要清除最近作品記錄嗎?這不會刪除相簿中的 Live Photo。" } }
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"已选中" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : { "stringUnit" : { "state" : "translated", "value" : "Selected" } },
|
||||||
|
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "已选中" } },
|
||||||
|
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "已選中" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"进度" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : { "stringUnit" : { "state" : "translated", "value" : "Progress" } },
|
||||||
|
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "进度" } },
|
||||||
|
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "進度" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"滑块" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : { "stringUnit" : { "state" : "translated", "value" : "Slider" } },
|
||||||
|
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "滑块" } },
|
||||||
|
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "滑桿" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accessibility.settings" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : { "stringUnit" : { "state" : "translated", "value" : "Settings" } },
|
||||||
|
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "设置" } },
|
||||||
|
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "設定" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accessibility.play" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : { "stringUnit" : { "state" : "translated", "value" : "Play" } },
|
||||||
|
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "播放" } },
|
||||||
|
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "播放" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accessibility.pause" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : { "stringUnit" : { "state" : "translated", "value" : "Pause" } },
|
||||||
|
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "暂停" } },
|
||||||
|
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "暫停" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accessibility.livePhoto" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : { "stringUnit" : { "state" : "translated", "value" : "Live Photo" } },
|
||||||
|
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "Live Photo 作品" } },
|
||||||
|
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "Live Photo 作品" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accessibility.aspectRatio" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : { "stringUnit" : { "state" : "translated", "value" : "Aspect ratio %@" } },
|
||||||
|
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "画面比例 %@" } },
|
||||||
|
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "畫面比例 %@" } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"accessibility.duration" : {
|
||||||
|
"extractionState" : "manual",
|
||||||
|
"localizations" : {
|
||||||
|
"en" : { "stringUnit" : { "state" : "translated", "value" : "Duration" } },
|
||||||
|
"zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "时长" } },
|
||||||
|
"zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "時長" } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"version" : "1.0"
|
"version" : "1.0"
|
||||||
|
|||||||
@@ -86,8 +86,8 @@ struct EditorView: View {
|
|||||||
compatibilitySection
|
compatibilitySection
|
||||||
generateButton
|
generateButton
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, DesignTokens.Spacing.xl)
|
||||||
.padding(.vertical, 16)
|
.padding(.vertical, DesignTokens.Spacing.lg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,11 +117,11 @@ struct EditorView: View {
|
|||||||
compatibilitySection
|
compatibilitySection
|
||||||
generateButton
|
generateButton
|
||||||
}
|
}
|
||||||
.padding(.vertical, 16)
|
.padding(.vertical, DesignTokens.Spacing.lg)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: 360)
|
.frame(maxWidth: 360)
|
||||||
}
|
}
|
||||||
.padding(24)
|
.padding(DesignTokens.Spacing.xxl)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - iPad 裁剪预览(更大尺寸)
|
// MARK: - iPad 裁剪预览(更大尺寸)
|
||||||
@@ -239,10 +239,10 @@ struct EditorView: View {
|
|||||||
|
|
||||||
Text("选择适合壁纸的比例,锁屏推荐使用「锁屏」或「全屏」")
|
Text("选择适合壁纸的比例,锁屏推荐使用「锁屏」或「全屏」")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
.padding(16)
|
.padding(16)
|
||||||
.background(Color.secondary.opacity(0.1))
|
.background(Color.softElevated)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,26 +271,26 @@ struct EditorView: View {
|
|||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: 8))
|
||||||
} else {
|
} else {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.fill(Color.secondary.opacity(0.2))
|
.fill(Color.softPressed)
|
||||||
.frame(width: 80, height: 120)
|
.frame(width: 80, height: 120)
|
||||||
.overlay {
|
.overlay {
|
||||||
Image(systemName: "photo")
|
Image(systemName: "photo")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text("此图片将作为 Live Photo 的静态封面")
|
Text("此图片将作为 Live Photo 的静态封面")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
Text("拖动下方滑杆选择封面时刻")
|
Text("拖动下方滑杆选择封面时刻")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(16)
|
.padding(16)
|
||||||
.background(Color.secondary.opacity(0.1))
|
.background(Color.softElevated)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,10 +317,10 @@ struct EditorView: View {
|
|||||||
|
|
||||||
Text("Live Photo 壁纸推荐时长:1 ~ 1.5 秒")
|
Text("Live Photo 壁纸推荐时长:1 ~ 1.5 秒")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
.padding(16)
|
.padding(16)
|
||||||
.background(Color.secondary.opacity(0.1))
|
.background(Color.softElevated)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,10 +348,10 @@ struct EditorView: View {
|
|||||||
|
|
||||||
Text("选择视频中的某一帧作为 Live Photo 的封面")
|
Text("选择视频中的某一帧作为 Live Photo 的封面")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
.padding(16)
|
.padding(16)
|
||||||
.background(Color.secondary.opacity(0.1))
|
.background(Color.softElevated)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,7 +368,7 @@ struct EditorView: View {
|
|||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text("使用 AI 提升封面画质")
|
Text("使用 AI 提升封面画质")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,7 +388,7 @@ struct EditorView: View {
|
|||||||
.scaleEffect(0.8)
|
.scaleEffect(0.8)
|
||||||
Text("正在下载 AI 模型...")
|
Text("正在下载 AI 模型...")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgressView(value: aiModelDownloadProgress)
|
ProgressView(value: aiModelDownloadProgress)
|
||||||
@@ -396,7 +396,7 @@ struct EditorView: View {
|
|||||||
|
|
||||||
Text(String(format: "%.0f%%", aiModelDownloadProgress * 100))
|
Text(String(format: "%.0f%%", aiModelDownloadProgress * 100))
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
.padding(.leading, 4)
|
.padding(.leading, 4)
|
||||||
}
|
}
|
||||||
@@ -434,7 +434,7 @@ struct EditorView: View {
|
|||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
.padding(.leading, 4)
|
.padding(.leading, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -445,7 +445,7 @@ struct EditorView: View {
|
|||||||
.font(.caption)
|
.font(.caption)
|
||||||
Text("当前设备不支持 AI 增强")
|
Text("当前设备不支持 AI 增强")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
.padding(.top, 4)
|
.padding(.top, 4)
|
||||||
}
|
}
|
||||||
@@ -472,7 +472,7 @@ struct EditorView: View {
|
|||||||
.font(.headline)
|
.font(.headline)
|
||||||
Text("适用于较旧设备或生成失败时")
|
Text("适用于较旧设备或生成失败时")
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -509,12 +509,12 @@ struct EditorView: View {
|
|||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
.padding(.leading, 4)
|
.padding(.leading, 4)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(16)
|
.padding(16)
|
||||||
.background(Color.secondary.opacity(0.1))
|
.background(Color.softElevated)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,7 +543,7 @@ struct EditorView: View {
|
|||||||
|
|
||||||
Text(suggestion.description)
|
Text(suggestion.description)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
|
|
||||||
if let actionText = suggestion.actionText {
|
if let actionText = suggestion.actionText {
|
||||||
Button {
|
Button {
|
||||||
@@ -804,7 +804,7 @@ struct AspectRatioButton: View {
|
|||||||
VStack(spacing: 4) {
|
VStack(spacing: 4) {
|
||||||
// 比例图标
|
// 比例图标
|
||||||
RoundedRectangle(cornerRadius: 4)
|
RoundedRectangle(cornerRadius: 4)
|
||||||
.stroke(isSelected ? Color.accentColor : Color.secondary, lineWidth: 2)
|
.stroke(isSelected ? Color.accentColor : Color.textSecondary, lineWidth: 2)
|
||||||
.frame(width: iconWidth, height: iconHeight)
|
.frame(width: iconWidth, height: iconHeight)
|
||||||
.background(
|
.background(
|
||||||
isSelected ? Color.accentColor.opacity(0.1) : Color.clear
|
isSelected ? Color.accentColor.opacity(0.1) : Color.clear
|
||||||
@@ -814,14 +814,18 @@ struct AspectRatioButton: View {
|
|||||||
Text(template.displayName)
|
Text(template.displayName)
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.fontWeight(isSelected ? .semibold : .regular)
|
.fontWeight(isSelected ? .semibold : .regular)
|
||||||
.foregroundStyle(isSelected ? .primary : .secondary)
|
.foregroundColor(isSelected ? .textPrimary : .textSecondary)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.padding(.vertical, 8)
|
.padding(.vertical, DesignTokens.Spacing.sm)
|
||||||
.background(isSelected ? Color.accentColor.opacity(0.1) : Color.clear)
|
.background(isSelected ? Color.accentColor.opacity(0.1) : Color.clear)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: DesignTokens.Radius.sm))
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
.accessibilityElement(children: .ignore)
|
||||||
|
.accessibilityLabel(String(localized: "accessibility.aspectRatio \(template.displayName)"))
|
||||||
|
.accessibilityAddTraits(.isButton)
|
||||||
|
.accessibilityAddTraits(isSelected ? .isSelected : [])
|
||||||
}
|
}
|
||||||
|
|
||||||
private var iconWidth: CGFloat {
|
private var iconWidth: CGFloat {
|
||||||
@@ -957,7 +961,7 @@ struct ScaleButtonStyle: ButtonStyle {
|
|||||||
configuration.label
|
configuration.label
|
||||||
.scaleEffect(configuration.isPressed ? 0.96 : 1.0)
|
.scaleEffect(configuration.isPressed ? 0.96 : 1.0)
|
||||||
.opacity(configuration.isPressed ? 0.9 : 1.0)
|
.opacity(configuration.isPressed ? 0.9 : 1.0)
|
||||||
.animation(.easeInOut(duration: 0.15), value: configuration.isPressed)
|
.animation(DesignTokens.Animation.quick, value: configuration.isPressed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ struct HomeView: View {
|
|||||||
.navigationBarTitleDisplayMode(.large)
|
.navigationBarTitleDisplayMode(.large)
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
SoftIconButton("gearshape") {
|
SoftIconButton("gearshape", accessibilityLabel: String(localized: "accessibility.settings")) {
|
||||||
appState.navigateTo(.settings)
|
appState.navigateTo(.settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -351,13 +351,17 @@ struct RecentWorkCard: View {
|
|||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.scaleEffect(isPressed ? 0.97 : 1.0)
|
.scaleEffect(isPressed ? 0.97 : 1.0)
|
||||||
.animation(.spring(response: 0.3, dampingFraction: 0.6), value: isPressed)
|
.animation(DesignTokens.Animation.spring, value: isPressed)
|
||||||
.onLongPressGesture(minimumDuration: .infinity, pressing: { pressing in
|
.onLongPressGesture(minimumDuration: .infinity, pressing: { pressing in
|
||||||
isPressed = pressing
|
isPressed = pressing
|
||||||
}, perform: {})
|
}, perform: {})
|
||||||
.onAppear {
|
.onAppear {
|
||||||
thumbnailLoader.load(assetId: work.assetLocalIdentifier)
|
thumbnailLoader.load(assetId: work.assetLocalIdentifier)
|
||||||
}
|
}
|
||||||
|
.accessibilityElement(children: .ignore)
|
||||||
|
.accessibilityLabel(String(localized: "accessibility.livePhoto"))
|
||||||
|
.accessibilityHint(Text("\(work.aspectRatioDisplayName), \(work.createdAt.formatted(.relative(presentation: .named)))"))
|
||||||
|
.accessibilityAddTraits(.isButton)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +370,7 @@ struct HomeButtonStyle: ButtonStyle {
|
|||||||
func makeBody(configuration: Configuration) -> some View {
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
configuration.label
|
configuration.label
|
||||||
.scaleEffect(configuration.isPressed ? 0.97 : 1.0)
|
.scaleEffect(configuration.isPressed ? 0.97 : 1.0)
|
||||||
.animation(.spring(response: 0.3, dampingFraction: 0.6), value: configuration.isPressed)
|
.animation(DesignTokens.Animation.spring, value: configuration.isPressed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ struct SettingsView: View {
|
|||||||
Label(String(localized: "settings.cacheSize"), systemImage: "internaldrive")
|
Label(String(localized: "settings.cacheSize"), systemImage: "internaldrive")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(cacheSize)
|
Text(cacheSize)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
@@ -111,7 +111,7 @@ struct SettingsView: View {
|
|||||||
Label(String(localized: "settings.version"), systemImage: "info.circle")
|
Label(String(localized: "settings.version"), systemImage: "info.circle")
|
||||||
Spacer()
|
Spacer()
|
||||||
Text(appVersion)
|
Text(appVersion)
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
|
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
@@ -175,7 +175,7 @@ struct SettingsView: View {
|
|||||||
.labelStyle(.iconOnly)
|
.labelStyle(.iconOnly)
|
||||||
case .notDetermined:
|
case .notDetermined:
|
||||||
Label(String(localized: "settings.notDetermined"), systemImage: "questionmark.circle.fill")
|
Label(String(localized: "settings.notDetermined"), systemImage: "questionmark.circle.fill")
|
||||||
.foregroundStyle(.secondary)
|
.foregroundColor(.textSecondary)
|
||||||
.labelStyle(.iconOnly)
|
.labelStyle(.iconOnly)
|
||||||
@unknown default:
|
@unknown default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ struct WallpaperGuideView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.padding(12)
|
.padding(12)
|
||||||
.background(Color.secondary.opacity(0.1))
|
.background(Color.softElevated)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,7 +316,7 @@ struct FAQRow: View {
|
|||||||
}
|
}
|
||||||
.padding(14)
|
.padding(14)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
.background(Color.secondary.opacity(0.08))
|
.background(Color.softElevated)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user