refactor: 替换硬编码值为 DesignTokens + 添加触觉反馈和动画
硬编码值替换: - spacing: 20/24/16/12/8/4 → DesignTokens.Spacing.xl/xxl/lg/md/sm/xs - cornerRadius: 16/8/4 → DesignTokens.Radius.lg/sm/xs - padding(.top, 8) / padding(.leading, 4) → DesignTokens.Spacing.sm/xs 生成按钮替换: - 手动实现的渐变按钮替换为 SoftPrimaryButton 组件 - 添加底部安全间距 padding(.bottom, DesignTokens.Spacing.sm) 触觉反馈(iOS 17+ sensoryFeedback): - 比例选择切换:.selection - 生成按钮点击:.impact(weight: .medium) - 裁剪手势结束:.impact(weight: .light) 动画优化: - AspectRatioButton 添加选中状态过渡动画 - CropOverlay 添加比例切换平滑过渡动画 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,9 @@ struct EditorView: View {
|
|||||||
// 视频诊断
|
// 视频诊断
|
||||||
@State private var videoDiagnosis: VideoDiagnosis?
|
@State private var videoDiagnosis: VideoDiagnosis?
|
||||||
|
|
||||||
|
// 触觉反馈触发
|
||||||
|
@State private var generateTapCount: Int = 0
|
||||||
|
|
||||||
/// 是否使用 iPad 分栏布局(regular 宽度 + 横屏)
|
/// 是否使用 iPad 分栏布局(regular 宽度 + 横屏)
|
||||||
private var useIPadLayout: Bool {
|
private var useIPadLayout: Bool {
|
||||||
horizontalSizeClass == .regular
|
horizontalSizeClass == .regular
|
||||||
@@ -67,13 +70,16 @@ struct EditorView: View {
|
|||||||
.onDisappear {
|
.onDisappear {
|
||||||
player?.pause()
|
player?.pause()
|
||||||
}
|
}
|
||||||
|
.sensoryFeedback(.selection, trigger: selectedAspectRatio)
|
||||||
|
.sensoryFeedback(.impact(weight: .medium), trigger: generateTapCount)
|
||||||
|
.sensoryFeedback(.impact(weight: .light), trigger: lastCropScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - iPhone 布局(单列滚动)
|
// MARK: - iPhone 布局(单列滚动)
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var iPhoneLayout: some View {
|
private var iPhoneLayout: some View {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 20) {
|
VStack(spacing: DesignTokens.Spacing.xl) {
|
||||||
cropPreview(height: 360)
|
cropPreview(height: 360)
|
||||||
|
|
||||||
if let diagnosis = videoDiagnosis, !diagnosis.suggestions.isEmpty {
|
if let diagnosis = videoDiagnosis, !diagnosis.suggestions.isEmpty {
|
||||||
@@ -96,9 +102,9 @@ struct EditorView: View {
|
|||||||
// MARK: - iPad 布局(左右分栏)
|
// MARK: - iPad 布局(左右分栏)
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var iPadLayout: some View {
|
private var iPadLayout: some View {
|
||||||
HStack(alignment: .top, spacing: 24) {
|
HStack(alignment: .top, spacing: DesignTokens.Spacing.xxl) {
|
||||||
// 左侧:视频预览
|
// 左侧:视频预览
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: DesignTokens.Spacing.lg) {
|
||||||
cropPreview(height: 500, dynamicHeight: true)
|
cropPreview(height: 500, dynamicHeight: true)
|
||||||
|
|
||||||
if let diagnosis = videoDiagnosis, !diagnosis.suggestions.isEmpty {
|
if let diagnosis = videoDiagnosis, !diagnosis.suggestions.isEmpty {
|
||||||
@@ -111,7 +117,7 @@ struct EditorView: View {
|
|||||||
|
|
||||||
// 右侧:参数控制
|
// 右侧:参数控制
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 16) {
|
VStack(spacing: DesignTokens.Spacing.lg) {
|
||||||
coverFrameSection
|
coverFrameSection
|
||||||
durationSection
|
durationSection
|
||||||
keyFrameSection
|
keyFrameSection
|
||||||
@@ -178,8 +184,8 @@ struct EditorView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(width: containerWidth, height: containerHeight)
|
.frame(width: containerWidth, height: containerHeight)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
.clipShape(RoundedRectangle(cornerRadius: DesignTokens.Radius.lg))
|
||||||
.background(Color.black.clipShape(RoundedRectangle(cornerRadius: 16)))
|
.background(Color.black.clipShape(RoundedRectangle(cornerRadius: DesignTokens.Radius.lg)))
|
||||||
}
|
}
|
||||||
.frame(height: height)
|
.frame(height: height)
|
||||||
}
|
}
|
||||||
@@ -187,7 +193,7 @@ struct EditorView: View {
|
|||||||
// MARK: - 比例模板选择
|
// MARK: - 比例模板选择
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var aspectRatioSection: some View {
|
private var aspectRatioSection: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: DesignTokens.Spacing.md) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "aspectratio")
|
Image(systemName: "aspectratio")
|
||||||
.foregroundStyle(.tint)
|
.foregroundStyle(.tint)
|
||||||
@@ -195,7 +201,7 @@ struct EditorView: View {
|
|||||||
.font(.headline)
|
.font(.headline)
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: DesignTokens.Spacing.sm) {
|
||||||
ForEach(AspectRatioTemplate.allCases, id: \.self) { template in
|
ForEach(AspectRatioTemplate.allCases, id: \.self) { template in
|
||||||
AspectRatioButton(
|
AspectRatioButton(
|
||||||
template: template,
|
template: template,
|
||||||
@@ -221,7 +227,7 @@ struct EditorView: View {
|
|||||||
// MARK: - 封面帧预览
|
// MARK: - 封面帧预览
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var coverFrameSection: some View {
|
private var coverFrameSection: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: DesignTokens.Spacing.md) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "photo")
|
Image(systemName: "photo")
|
||||||
.foregroundStyle(.tint)
|
.foregroundStyle(.tint)
|
||||||
@@ -234,15 +240,15 @@ struct EditorView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: DesignTokens.Spacing.md) {
|
||||||
if let coverImage {
|
if let coverImage {
|
||||||
Image(uiImage: coverImage)
|
Image(uiImage: coverImage)
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fill)
|
.aspectRatio(contentMode: .fill)
|
||||||
.frame(width: 80, height: 120)
|
.frame(width: 80, height: 120)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 8))
|
.clipShape(RoundedRectangle(cornerRadius: DesignTokens.Radius.sm))
|
||||||
} else {
|
} else {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: DesignTokens.Radius.sm)
|
||||||
.fill(Color.softPressed)
|
.fill(Color.softPressed)
|
||||||
.frame(width: 80, height: 120)
|
.frame(width: 80, height: 120)
|
||||||
.overlay {
|
.overlay {
|
||||||
@@ -251,7 +257,7 @@ struct EditorView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: DesignTokens.Spacing.xs) {
|
||||||
Text(String(localized: "editor.coverFrameHint1"))
|
Text(String(localized: "editor.coverFrameHint1"))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.textSecondary)
|
.foregroundColor(.textSecondary)
|
||||||
@@ -269,7 +275,7 @@ struct EditorView: View {
|
|||||||
// MARK: - 时长控制
|
// MARK: - 时长控制
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var durationSection: some View {
|
private var durationSection: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: DesignTokens.Spacing.md) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "timer")
|
Image(systemName: "timer")
|
||||||
.foregroundStyle(.tint)
|
.foregroundStyle(.tint)
|
||||||
@@ -306,7 +312,7 @@ struct EditorView: View {
|
|||||||
// MARK: - 封面帧时间选择
|
// MARK: - 封面帧时间选择
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var keyFrameSection: some View {
|
private var keyFrameSection: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: DesignTokens.Spacing.md) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "clock")
|
Image(systemName: "clock")
|
||||||
.foregroundStyle(.tint)
|
.foregroundStyle(.tint)
|
||||||
@@ -344,7 +350,7 @@ struct EditorView: View {
|
|||||||
// MARK: - AI 超分辨率开关
|
// MARK: - AI 超分辨率开关
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var aiEnhanceSection: some View {
|
private var aiEnhanceSection: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: DesignTokens.Spacing.md) {
|
||||||
Toggle(isOn: $aiEnhanceEnabled) {
|
Toggle(isOn: $aiEnhanceEnabled) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "wand.and.stars.inverse")
|
Image(systemName: "wand.and.stars.inverse")
|
||||||
@@ -368,8 +374,8 @@ struct EditorView: View {
|
|||||||
|
|
||||||
// 模型下载进度
|
// 模型下载进度
|
||||||
if aiModelDownloading {
|
if aiModelDownloading {
|
||||||
VStack(alignment: .leading, spacing: 8) {
|
VStack(alignment: .leading, spacing: DesignTokens.Spacing.sm) {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: DesignTokens.Spacing.sm) {
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.scaleEffect(0.8)
|
.scaleEffect(0.8)
|
||||||
Text(String(localized: "editor.aiModelDownloading"))
|
Text(String(localized: "editor.aiModelDownloading"))
|
||||||
@@ -384,13 +390,13 @@ struct EditorView: View {
|
|||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
.foregroundColor(.textSecondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
.padding(.leading, 4)
|
.padding(.leading, DesignTokens.Spacing.xs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if aiEnhanceEnabled && !aiModelDownloading {
|
if aiEnhanceEnabled && !aiModelDownloading {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
if aiModelNeedsDownload {
|
if aiModelNeedsDownload {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: DesignTokens.Spacing.xs) {
|
||||||
Image(systemName: "arrow.down.circle")
|
Image(systemName: "arrow.down.circle")
|
||||||
.foregroundStyle(.orange)
|
.foregroundStyle(.orange)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
@@ -398,21 +404,21 @@ struct EditorView: View {
|
|||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: DesignTokens.Spacing.xs) {
|
||||||
Image(systemName: "sparkles")
|
Image(systemName: "sparkles")
|
||||||
.foregroundStyle(Color.accentPurple)
|
.foregroundStyle(Color.accentPurple)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
Text(String(localized: "editor.aiResolutionBoost"))
|
Text(String(localized: "editor.aiResolutionBoost"))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: DesignTokens.Spacing.xs) {
|
||||||
Image(systemName: "clock")
|
Image(systemName: "clock")
|
||||||
.foregroundStyle(Color.accentPurple)
|
.foregroundStyle(Color.accentPurple)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
Text(String(localized: "editor.aiProcessingTime"))
|
Text(String(localized: "editor.aiProcessingTime"))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: DesignTokens.Spacing.xs) {
|
||||||
Image(systemName: "cpu")
|
Image(systemName: "cpu")
|
||||||
.foregroundStyle(Color.accentPurple)
|
.foregroundStyle(Color.accentPurple)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
@@ -421,11 +427,11 @@ struct EditorView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundColor(.textSecondary)
|
.foregroundColor(.textSecondary)
|
||||||
.padding(.leading, 4)
|
.padding(.leading, DesignTokens.Spacing.xs)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !AIEnhancer.isAvailable() {
|
if !AIEnhancer.isAvailable() {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: DesignTokens.Spacing.xs) {
|
||||||
Image(systemName: "exclamationmark.triangle")
|
Image(systemName: "exclamationmark.triangle")
|
||||||
.foregroundStyle(.yellow)
|
.foregroundStyle(.yellow)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
@@ -433,7 +439,7 @@ struct EditorView: View {
|
|||||||
.font(.caption)
|
.font(.caption)
|
||||||
.foregroundColor(.textSecondary)
|
.foregroundColor(.textSecondary)
|
||||||
}
|
}
|
||||||
.padding(.top, 4)
|
.padding(.top, DesignTokens.Spacing.xs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(DesignTokens.Spacing.lg)
|
.padding(DesignTokens.Spacing.lg)
|
||||||
@@ -448,7 +454,7 @@ struct EditorView: View {
|
|||||||
// MARK: - 兼容模式开关
|
// MARK: - 兼容模式开关
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var compatibilitySection: some View {
|
private var compatibilitySection: some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: DesignTokens.Spacing.md) {
|
||||||
Toggle(isOn: $compatibilityMode) {
|
Toggle(isOn: $compatibilityMode) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "gearshape.2")
|
Image(systemName: "gearshape.2")
|
||||||
@@ -466,28 +472,28 @@ struct EditorView: View {
|
|||||||
|
|
||||||
if compatibilityMode {
|
if compatibilityMode {
|
||||||
VStack(alignment: .leading, spacing: 6) {
|
VStack(alignment: .leading, spacing: 6) {
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: DesignTokens.Spacing.xs) {
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.foregroundStyle(.green)
|
.foregroundStyle(.green)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
Text(String(localized: "editor.resolution720p"))
|
Text(String(localized: "editor.resolution720p"))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: DesignTokens.Spacing.xs) {
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.foregroundStyle(.green)
|
.foregroundStyle(.green)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
Text(String(localized: "editor.framerate30fps"))
|
Text(String(localized: "editor.framerate30fps"))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: DesignTokens.Spacing.xs) {
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.foregroundStyle(.green)
|
.foregroundStyle(.green)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
Text(String(localized: "editor.codecH264"))
|
Text(String(localized: "editor.codecH264"))
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
HStack(spacing: 4) {
|
HStack(spacing: DesignTokens.Spacing.xs) {
|
||||||
Image(systemName: "checkmark.circle.fill")
|
Image(systemName: "checkmark.circle.fill")
|
||||||
.foregroundStyle(.green)
|
.foregroundStyle(.green)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
@@ -496,7 +502,7 @@ struct EditorView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundColor(.textSecondary)
|
.foregroundColor(.textSecondary)
|
||||||
.padding(.leading, 4)
|
.padding(.leading, DesignTokens.Spacing.xs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(DesignTokens.Spacing.lg)
|
.padding(DesignTokens.Spacing.lg)
|
||||||
@@ -507,7 +513,7 @@ struct EditorView: View {
|
|||||||
// MARK: - 诊断建议
|
// MARK: - 诊断建议
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func diagnosisSection(diagnosis: VideoDiagnosis) -> some View {
|
private func diagnosisSection(diagnosis: VideoDiagnosis) -> some View {
|
||||||
VStack(alignment: .leading, spacing: 12) {
|
VStack(alignment: .leading, spacing: DesignTokens.Spacing.md) {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "exclamationmark.triangle.fill")
|
Image(systemName: "exclamationmark.triangle.fill")
|
||||||
.foregroundStyle(.yellow)
|
.foregroundStyle(.yellow)
|
||||||
@@ -517,12 +523,12 @@ struct EditorView: View {
|
|||||||
|
|
||||||
ForEach(diagnosis.suggestions.indices, id: \.self) { index in
|
ForEach(diagnosis.suggestions.indices, id: \.self) { index in
|
||||||
let suggestion = diagnosis.suggestions[index]
|
let suggestion = diagnosis.suggestions[index]
|
||||||
HStack(alignment: .top, spacing: 12) {
|
HStack(alignment: .top, spacing: DesignTokens.Spacing.md) {
|
||||||
Image(systemName: suggestion.icon)
|
Image(systemName: suggestion.icon)
|
||||||
.foregroundStyle(suggestion.iconColor)
|
.foregroundStyle(suggestion.iconColor)
|
||||||
.frame(width: 24)
|
.frame(width: 24)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: DesignTokens.Spacing.xs) {
|
||||||
Text(suggestion.title)
|
Text(suggestion.title)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.fontWeight(.medium)
|
.fontWeight(.medium)
|
||||||
@@ -555,22 +561,15 @@ struct EditorView: View {
|
|||||||
// MARK: - 生成按钮
|
// MARK: - 生成按钮
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var generateButton: some View {
|
private var generateButton: some View {
|
||||||
Button {
|
SoftPrimaryButton(
|
||||||
|
String(localized: "editor.generateButton"),
|
||||||
|
icon: "wand.and.stars",
|
||||||
|
gradient: Color.gradientPrimary
|
||||||
|
) {
|
||||||
startProcessing()
|
startProcessing()
|
||||||
} label: {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "wand.and.stars")
|
|
||||||
Text(String(localized: "editor.generateButton"))
|
|
||||||
}
|
|
||||||
.font(.headline)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.padding()
|
|
||||||
.background(Color.gradientPrimary)
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 14))
|
|
||||||
}
|
}
|
||||||
.buttonStyle(ScaleButtonStyle())
|
.padding(.top, DesignTokens.Spacing.sm)
|
||||||
.padding(.top, 8)
|
.padding(.bottom, DesignTokens.Spacing.sm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - 方法
|
// MARK: - 方法
|
||||||
@@ -758,6 +757,7 @@ struct EditorView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func startProcessing() {
|
private func startProcessing() {
|
||||||
|
generateTapCount += 1
|
||||||
Analytics.shared.log(.editorGenerateClick, parameters: [
|
Analytics.shared.log(.editorGenerateClick, parameters: [
|
||||||
"trimStart": trimStart,
|
"trimStart": trimStart,
|
||||||
"trimEnd": trimEnd,
|
"trimEnd": trimEnd,
|
||||||
@@ -795,15 +795,15 @@ struct AspectRatioButton: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Button(action: action) {
|
Button(action: action) {
|
||||||
VStack(spacing: 4) {
|
VStack(spacing: DesignTokens.Spacing.xs) {
|
||||||
// 比例图标
|
// 比例图标
|
||||||
RoundedRectangle(cornerRadius: 4)
|
RoundedRectangle(cornerRadius: DesignTokens.Spacing.xs)
|
||||||
.stroke(isSelected ? Color.accentPurple : Color.textSecondary, lineWidth: 2)
|
.stroke(isSelected ? Color.accentPurple : Color.textSecondary, lineWidth: 2)
|
||||||
.frame(width: iconWidth, height: iconHeight)
|
.frame(width: iconWidth, height: iconHeight)
|
||||||
.background(
|
.background(
|
||||||
isSelected ? Color.accentPurple.opacity(0.1) : Color.clear
|
isSelected ? Color.accentPurple.opacity(0.1) : Color.clear
|
||||||
)
|
)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: 4))
|
.clipShape(RoundedRectangle(cornerRadius: DesignTokens.Spacing.xs))
|
||||||
|
|
||||||
Text(template.displayName)
|
Text(template.displayName)
|
||||||
.font(.caption2)
|
.font(.caption2)
|
||||||
@@ -814,6 +814,7 @@ struct AspectRatioButton: View {
|
|||||||
.padding(.vertical, DesignTokens.Spacing.sm)
|
.padding(.vertical, DesignTokens.Spacing.sm)
|
||||||
.background(isSelected ? Color.accentPurple.opacity(0.1) : Color.clear)
|
.background(isSelected ? Color.accentPurple.opacity(0.1) : Color.clear)
|
||||||
.clipShape(RoundedRectangle(cornerRadius: DesignTokens.Radius.sm))
|
.clipShape(RoundedRectangle(cornerRadius: DesignTokens.Radius.sm))
|
||||||
|
.animation(DesignTokens.Animation.quick, value: isSelected)
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
.accessibilityElement(children: .ignore)
|
.accessibilityElement(children: .ignore)
|
||||||
@@ -858,17 +859,18 @@ struct CropOverlay: View {
|
|||||||
.mask(
|
.mask(
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.overlay(
|
.overlay(
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: DesignTokens.Radius.sm)
|
||||||
.frame(width: cropSize.width, height: cropSize.height)
|
.frame(width: cropSize.width, height: cropSize.height)
|
||||||
.blendMode(.destinationOut)
|
.blendMode(.destinationOut)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 裁剪框边框
|
// 裁剪框边框
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: DesignTokens.Radius.sm)
|
||||||
.stroke(Color.white, lineWidth: 2)
|
.stroke(Color.white, lineWidth: 2)
|
||||||
.frame(width: cropSize.width, height: cropSize.height)
|
.frame(width: cropSize.width, height: cropSize.height)
|
||||||
}
|
}
|
||||||
|
.animation(DesignTokens.Animation.standard, value: aspectRatio)
|
||||||
}
|
}
|
||||||
.allowsHitTesting(false)
|
.allowsHitTesting(false)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user