fix(theme): 테마 즉시 적용 및 위젯 테마 실적용 + 리퀴드 글라스 구현

- AppGroup.defaults를 단일 인스턴스(static let)로 변경: 접근마다 새
  UserDefaults를 만들어 @AppStorage(store:) 관찰이 끊기던 문제 수정
  → 설정에서 테마를 바꾸면 재시작 없이 즉시 적용됨
- 위젯 containerBackground에 강제 컬러 스킴을 직접 지정: 시스템이
  배경을 위젯 환경으로 그려 라이트/다크 고정·앱 테마 일치가 실제
  위젯에서 무시되던 문제 수정
- 테마 변경 시 위젯 타임라인 갱신 (앱 테마와 일치 옵션 즉시 반영)
- 리퀴드 글라스 실구현: 머티리얼+하이라이트 배경(GlassWidgetBackground),
  유리 테두리 셀(WidgetCellBackground), 색이 비치는 틴트 셀
  (WidgetTintedCellBackground, 행동 실행 위젯 적용)
- 검증 인자 -themeAutoToggle YES 추가 (3초 후 테마 반전)

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 14:44:36 +09:00
parent 828cf2e906
commit e5550eb106
6 changed files with 80 additions and 14 deletions

View File

@ -38,6 +38,14 @@ struct ContentView: View {
DebugSeed.seedIfRequested(context: context)
DebugSeed.autoStartIfRequested(context: context)
DebugSeed.pinGoalsIfRequested(context: context)
// : -themeAutoToggle YES 3 (App Group defaults)
// ' '
if UserDefaults.standard.bool(forKey: "themeAutoToggle") {
Task {
try? await Task.sleep(for: .seconds(3))
AppGroup.defaults.set(theme == "dark" ? "light" : "dark", forKey: SettingsKeys.theme)
}
}
#endif
try? await Task.sleep(for: .seconds(1.2))
withAnimation(.easeOut(duration: 0.4)) {

View File

@ -7,6 +7,7 @@
import SwiftUI
import SwiftData
import WidgetKit
struct SettingsView: View {
@Query(sort: [SortDescriptor(\Goal.sortOrder), SortDescriptor(\Goal.createdAt)]) private var goals: [Goal]
@ -142,6 +143,10 @@ struct SettingsView: View {
.scrollContentBackground(.hidden)
.background(AppTheme.background)
.navigationTitle("설정")
.onChange(of: theme) {
// ' '
WidgetCenter.shared.reloadAllTimelines()
}
#if DEBUG
// : -settingsScrollGoal YES , -settingsScrollPremium YES
.onAppear {

View File

@ -204,7 +204,7 @@ struct WidgetPreviewScreen: View {
.frame(width: size.width, height: size.height)
.background {
if theme == .glass {
Rectangle().fill(.ultraThinMaterial)
GlassWidgetBackground()
} else {
AppTheme.background
}

View File

@ -18,9 +18,10 @@ import Observation
nonisolated enum AppGroup {
static let identifier = "group.com.yechan.HaruDanim"
static var defaults: UserDefaults {
UserDefaults(suiteName: identifier) ?? .standard
}
/// : @AppStorage(store:)
/// UserDefaults
/// ( )
static let defaults: UserDefaults = UserDefaults(suiteName: identifier) ?? .standard
}
// MARK: - /

View File

@ -123,10 +123,7 @@ struct ActionRunCellView: View {
.foregroundStyle(.white)
.padding(compact ? 8 : 12)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading)
.background(
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(cell.color)
)
.background(WidgetTintedCellBackground(color: cell.color))
.overlay {
if cell.isRunning {
RoundedRectangle(cornerRadius: 14, style: .continuous)

View File

@ -66,12 +66,33 @@ struct WidgetThemeModifier: ViewModifier {
content
.environment(\.widgetGlass, true)
.containerBackground(for: .widget) {
Rectangle().fill(.ultraThinMaterial)
GlassWidgetBackground()
}
case .light, .dark, .matchApp:
let scheme = theme.forcedScheme ?? .light
content
.environment(\.colorScheme, theme.forcedScheme ?? .light)
.containerBackground(AppTheme.background, for: .widget)
.environment(\.colorScheme, scheme)
.containerBackground(for: .widget) {
// containerBackground ( )
//
// '/ ' ' '
AppTheme.background
.environment(\.colorScheme, scheme)
}
}
}
}
/// (§8.1-3): +
struct GlassWidgetBackground: View {
var body: some View {
ZStack {
Rectangle().fill(.ultraThinMaterial)
LinearGradient(
colors: [.white.opacity(0.28), .white.opacity(0.06), .clear],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
}
}
}
@ -83,12 +104,46 @@ extension View {
}
}
/// : ,
/// : , +
struct WidgetCellBackground: View {
@Environment(\.widgetGlass) private var glass
var body: some View {
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(glass ? AnyShapeStyle(.thinMaterial) : AnyShapeStyle(AppTheme.surface))
if glass {
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(.thinMaterial)
.overlay {
RoundedRectangle(cornerRadius: 14, style: .continuous)
.strokeBorder(.white.opacity(0.25), lineWidth: 1)
}
} else {
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(AppTheme.surface)
}
}
}
/// :
///
struct WidgetTintedCellBackground: View {
@Environment(\.widgetGlass) private var glass
let color: Color
var body: some View {
if glass {
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(.thinMaterial)
.overlay {
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(color.opacity(0.5))
}
.overlay {
RoundedRectangle(cornerRadius: 14, style: .continuous)
.strokeBorder(.white.opacity(0.3), lineWidth: 1)
}
} else {
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(color)
}
}
}