fix(widgets): 리퀴드 글라스 테마 시인성 개선 및 틴트/클리어 렌더링 모드 대응

- 글라스 테마: 틴트 셀 불투명도 0.5→0.72, 테두리 강화로 흰 텍스트 대비 확보
- 글라스 테마 전용 텍스트 그림자(glassTextShadow) 추가 — 홈 위젯 5종 셀 콘텐츠에 적용
- widgetRenderingMode != .fullColor(홈 화면 틴트/클리어)일 때 커스텀 배경·강제
  컬러 스킴을 끄고 시스템 바이브런트 렌더링에 맡기도록 분기
- 잠금화면 위젯: AccessoryWidgetBackground + widgetAccentable + .primary 전경색으로
  비브런트/악센트 렌더링에서 시인성 확보
- 미리보기 하네스: 글라스 테마가 시스템 컬러 스킴을 따르도록 수정

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
This commit is contained in:
songyc macbook 2026-07-10 21:18:43 +09:00
parent bef503465c
commit bc6cd010b7
7 changed files with 85 additions and 31 deletions

View File

@ -13,6 +13,7 @@ import WidgetKit
struct WidgetPreviewScreen: View { struct WidgetPreviewScreen: View {
let showLock: Bool let showLock: Bool
@Environment(\.colorScheme) private var systemScheme
/// -widgetTheme light|dark|glass (§8.1 ) /// -widgetTheme light|dark|glass (§8.1 )
private let theme = UserDefaults.standard.string(forKey: "widgetTheme") private let theme = UserDefaults.standard.string(forKey: "widgetTheme")
@ -209,7 +210,8 @@ struct WidgetPreviewScreen: View {
AppTheme.background AppTheme.background
} }
} }
.environment(\.colorScheme, theme.forcedScheme ?? .light) //
.environment(\.colorScheme, theme.forcedScheme ?? systemScheme)
.clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous)) .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
.shadow(color: .black.opacity(0.08), radius: 6, y: 2) .shadow(color: .black.opacity(0.08), radius: 6, y: 2)
} }

View File

@ -123,6 +123,7 @@ struct ActionRunCellView: View {
.lineLimit(1) .lineLimit(1)
} }
.foregroundStyle(.white) .foregroundStyle(.white)
.glassTextShadow()
.padding(fullBleed ? 14 : (compact ? 8 : 12)) .padding(fullBleed ? 14 : (compact ? 8 : 12))
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.background(WidgetTintedCellBackground(color: cell.color, fullBleed: fullBleed)) .background(WidgetTintedCellBackground(color: cell.color, fullBleed: fullBleed))

View File

@ -85,6 +85,7 @@ struct GoalBarsCellView: View {
SpanBarRow(label: "주간", ratio: goal.weekRatio, color: goal.color) SpanBarRow(label: "주간", ratio: goal.weekRatio, color: goal.color)
SpanBarRow(label: "월간", ratio: goal.monthRatio, color: goal.color) SpanBarRow(label: "월간", ratio: goal.monthRatio, color: goal.color)
} }
.glassTextShadow()
.padding(fullBleed ? 14 : 10) .padding(fullBleed ? 14 : 10)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.background(WidgetCellBackground(fullBleed: fullBleed)) .background(WidgetCellBackground(fullBleed: fullBleed))
@ -240,6 +241,7 @@ struct QuestRingCellView: View {
.lineLimit(1) .lineLimit(1)
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
.glassTextShadow()
} }
} }
@ -285,6 +287,7 @@ struct GoalQuestGridWidgetView: View {
} }
} }
} }
.glassTextShadow()
.padding(13) .padding(13)
.widgetTheme(entry.theme) .widgetTheme(entry.theme)
} }

View File

@ -102,6 +102,7 @@ struct LockGoalWidgetView: View {
default: default:
VStack(spacing: 2) { VStack(spacing: 2) {
Image(systemName: "crown.fill") Image(systemName: "crown.fill")
.widgetAccentable()
Text("프리미엄") Text("프리미엄")
.font(.caption2) .font(.caption2)
} }
@ -132,31 +133,45 @@ struct LockGoalWidgetView: View {
.containerBackground(.clear, for: .widget) .containerBackground(.clear, for: .widget)
} }
/// (/ )
/// (AccessoryWidgetBackground) ,
/// .primary + .widgetAccentable()
@ViewBuilder @ViewBuilder
private func circularView(_ goal: GoalSnapshot) -> some View { private func circularView(_ goal: GoalSnapshot) -> some View {
switch entry.style { switch entry.style {
case .gauge: case .gauge:
Gauge(value: min(max(entry.ratio, 0), 1)) { Gauge(value: min(max(entry.ratio, 0), 1)) {
Image(systemName: goal.symbolName) Image(systemName: goal.symbolName)
.widgetAccentable()
} currentValueLabel: { } currentValueLabel: {
Text("\(Int((entry.ratio * 100).rounded()))") Text("\(Int((entry.ratio * 100).rounded()))")
.font(.system(size: 14, weight: .bold).monospacedDigit()) .font(.system(size: 14, weight: .bold).monospacedDigit())
.foregroundStyle(.primary)
} }
.gaugeStyle(.accessoryCircular) .gaugeStyle(.accessoryCircular)
case .number: case .number:
VStack(spacing: 0) { ZStack {
Text("\(Int((entry.ratio * 100).rounded()))%") AccessoryWidgetBackground()
.font(.system(size: 16, weight: .bold).monospacedDigit()) VStack(spacing: 0) {
Text(entry.spanLabel) Text("\(Int((entry.ratio * 100).rounded()))%")
.font(.system(size: 9)) .font(.system(size: 16, weight: .bold).monospacedDigit())
.foregroundStyle(.secondary) .foregroundStyle(.primary)
.widgetAccentable()
Text(entry.spanLabel)
.font(.system(size: 9))
.foregroundStyle(.secondary)
}
} }
case .dots: case .dots:
// ( 6) // ( 6)
VStack(spacing: 3) { ZStack {
Image(systemName: goal.symbolName) AccessoryWidgetBackground()
.font(.system(size: 12, weight: .semibold)) VStack(spacing: 3) {
questDots(goal, size: 6) Image(systemName: goal.symbolName)
.font(.system(size: 12, weight: .semibold))
.widgetAccentable()
questDots(goal, size: 6)
}
} }
} }
} }
@ -170,6 +185,8 @@ struct LockGoalWidgetView: View {
.font(.caption.weight(.semibold)) .font(.caption.weight(.semibold))
.lineLimit(1) .lineLimit(1)
} }
.foregroundStyle(.primary)
.widgetAccentable()
Gauge(value: min(max(entry.ratio, 0), 1)) { EmptyView() } Gauge(value: min(max(entry.ratio, 0), 1)) { EmptyView() }
.gaugeStyle(.accessoryLinearCapacity) .gaugeStyle(.accessoryLinearCapacity)
HStack { HStack {

View File

@ -122,6 +122,7 @@ struct QuestRingActionCellView: View {
.font(.caption2.monospacedDigit()) .font(.caption2.monospacedDigit())
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
} }
.glassTextShadow()
.frame(maxWidth: .infinity, maxHeight: .infinity) .frame(maxWidth: .infinity, maxHeight: .infinity)
.padding(fullBleed ? 10 : 6) .padding(fullBleed ? 10 : 6)
.background(WidgetCellBackground(fullBleed: fullBleed)) .background(WidgetCellBackground(fullBleed: fullBleed))

View File

@ -190,6 +190,7 @@ struct StatsChartWidgetView: View {
} }
} }
} }
.glassTextShadow()
.padding(13) .padding(13)
.widgetTheme(entry.theme) .widgetTheme(entry.theme)
} }

View File

@ -57,28 +57,37 @@ extension EnvironmentValues {
// MARK: - // MARK: -
struct WidgetThemeModifier: ViewModifier { struct WidgetThemeModifier: ViewModifier {
@Environment(\.widgetRenderingMode) private var renderingMode
let theme: WidgetThemeOption let theme: WidgetThemeOption
func body(content: Content) -> some View { func body(content: Content) -> some View {
switch theme { if renderingMode != .fullColor {
case .glass: // /( ) :
// : // , ·
// ()
content content
.environment(\.widgetGlass, true) .containerBackground(for: .widget) { Color.clear }
.containerBackground(for: .widget) { } else {
GlassWidgetBackground() switch theme {
} case .glass:
case .light, .dark, .matchApp: // :
let scheme = theme.forcedScheme ?? .light content
content .environment(\.widgetGlass, true)
.environment(\.colorScheme, scheme) .containerBackground(for: .widget) {
.containerBackground(for: .widget) { GlassWidgetBackground()
// containerBackground ( ) }
// case .light, .dark, .matchApp:
// '/ ' ' ' let scheme = theme.forcedScheme ?? .light
AppTheme.background content
.environment(\.colorScheme, scheme) .environment(\.colorScheme, scheme)
} .containerBackground(for: .widget) {
// containerBackground ( )
//
// '/ ' ' '
AppTheme.background
.environment(\.colorScheme, scheme)
}
}
} }
} }
} }
@ -97,11 +106,30 @@ struct GlassWidgetBackground: View {
} }
} }
/// · .
/// .
struct GlassTextShadowModifier: ViewModifier {
@Environment(\.widgetGlass) private var glass
func body(content: Content) -> some View {
if glass {
content.shadow(color: .black.opacity(0.3), radius: 2, x: 0, y: 1)
} else {
content
}
}
}
extension View { extension View {
/// ( + ) /// ( + )
func widgetTheme(_ theme: WidgetThemeOption) -> some View { func widgetTheme(_ theme: WidgetThemeOption) -> some View {
modifier(WidgetThemeModifier(theme: theme)) modifier(WidgetThemeModifier(theme: theme))
} }
/// ( )
func glassTextShadow() -> some View {
modifier(GlassTextShadowModifier())
}
} }
/// : , + /// : , +
@ -147,9 +175,10 @@ struct WidgetTintedCellBackground: View {
@ViewBuilder @ViewBuilder
private func styled(_ shape: some InsettableShape) -> some View { private func styled(_ shape: some InsettableShape) -> some View {
if glass { if glass {
//
shape.fill(.thinMaterial) shape.fill(.thinMaterial)
.overlay { shape.fill(color.opacity(0.5)) } .overlay { shape.fill(color.opacity(0.72)) }
.overlay { shape.strokeBorder(.white.opacity(0.3), lineWidth: 1) } .overlay { shape.strokeBorder(.white.opacity(0.35), lineWidth: 1) }
} else { } else {
shape.fill(color) shape.fill(color)
} }