mycode/myApp/HaruDanim/Widgets/WidgetTheme.swift
songyc macbook e5550eb106 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
2026-07-10 14:44:36 +09:00

150 lines
5.0 KiB
Swift

//
// WidgetTheme.swift
// Haru_DanimWidgets
//
// (CLAUDE.md §8.1)
// 1. 2. / 3. ( )
//
import AppIntents
import SwiftUI
import WidgetKit
enum WidgetThemeOption: String, AppEnum {
case matchApp
case light
case dark
case glass
static let typeDisplayRepresentation = TypeDisplayRepresentation(name: "위젯 테마")
static let caseDisplayRepresentations: [WidgetThemeOption: DisplayRepresentation] = [
.matchApp: "앱 테마와 일치",
.light: "라이트 고정",
.dark: "다크 고정",
.glass: "리퀴드 글라스",
]
/// . .
var forcedScheme: ColorScheme? {
switch self {
case .light:
return .light
case .dark:
return .dark
case .matchApp:
let raw = AppGroup.defaults.string(forKey: SettingsKeys.theme)
?? UserDefaults.standard.string(forKey: SettingsKeys.theme) ?? "light"
return raw == "dark" ? .dark : .light
case .glass:
return nil
}
}
}
// MARK: - : ( )
private struct WidgetGlassKey: EnvironmentKey {
static let defaultValue = false
}
extension EnvironmentValues {
var widgetGlass: Bool {
get { self[WidgetGlassKey.self] }
set { self[WidgetGlassKey.self] = newValue }
}
}
// MARK: -
struct WidgetThemeModifier: ViewModifier {
let theme: WidgetThemeOption
func body(content: Content) -> some View {
switch theme {
case .glass:
// :
content
.environment(\.widgetGlass, true)
.containerBackground(for: .widget) {
GlassWidgetBackground()
}
case .light, .dark, .matchApp:
let scheme = theme.forcedScheme ?? .light
content
.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
)
}
}
}
extension View {
/// ( + )
func widgetTheme(_ theme: WidgetThemeOption) -> some View {
modifier(WidgetThemeModifier(theme: theme))
}
}
/// : , +
struct WidgetCellBackground: View {
@Environment(\.widgetGlass) private var glass
var body: some View {
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)
}
}
}