Преглед изворни кода

Add light theme support with a Settings dark mode toggle.

Persist appearance preference and strengthen light-mode control borders so buttons and fields remain clearly defined.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 пре 1 месец
родитељ
комит
ff8955e477

+ 3 - 0
Reddit App/App/RedditAppApp.swift

@@ -4,11 +4,14 @@ import SwiftUI
 @main
 struct RedditAppApp: App {
     @StateObject private var subscriptions = SubscriptionManager()
+    @State private var appearanceManager = AppearanceManager.shared
 
     var body: some Scene {
         WindowGroup {
             FrontPageView()
                 .environmentObject(subscriptions)
+                .environment(appearanceManager)
+                .preferredColorScheme(appearanceManager.isDarkMode ? .dark : .light)
         }
         .defaultSize(width: AppWindow.width, height: AppWindow.height)
         .windowStyle(.hiddenTitleBar)

+ 80 - 0
Reddit App/Managers/AppearanceManager.swift

@@ -0,0 +1,80 @@
+import Foundation
+import SwiftUI
+
+@MainActor
+@Observable
+final class AppearanceManager {
+    static let shared = AppearanceManager()
+
+    private static let storageKey = "isDarkMode"
+
+    var isDarkMode: Bool {
+        didSet {
+            guard isDarkMode != oldValue else { return }
+            UserDefaults.standard.set(isDarkMode, forKey: Self.storageKey)
+        }
+    }
+
+    var palette: ThemePalette {
+        isDarkMode ? .dark : .light
+    }
+
+    init() {
+        if UserDefaults.standard.object(forKey: Self.storageKey) != nil {
+            isDarkMode = UserDefaults.standard.bool(forKey: Self.storageKey)
+        } else {
+            isDarkMode = true
+        }
+    }
+}
+
+struct ThemePalette {
+    let background: Color
+    let sidebarBackground: Color
+    let panelBackground: Color
+    let cardBackground: Color
+    let quickAccessBackground: Color
+    let border: Color
+    let cardBorder: Color
+    let textPrimary: Color
+    let textSecondary: Color
+    let textTertiary: Color
+    let hoverFill: Color
+    let hoverBorder: Color
+    let selectedRowFill: Color
+    let scrim: Color
+
+    static let dark = ThemePalette(
+        background: Color(hex: 0x0B0E14),
+        sidebarBackground: Color(hex: 0x0B0E14),
+        panelBackground: Color(hex: 0x11141C),
+        cardBackground: Color(hex: 0x151922),
+        quickAccessBackground: Color(hex: 0x151922),
+        border: Color.white.opacity(0.06),
+        cardBorder: Color.white.opacity(0.08),
+        textPrimary: Color.white,
+        textSecondary: Color(hex: 0x8E9297),
+        textTertiary: Color(hex: 0x6B7280),
+        hoverFill: Color.white.opacity(0.06),
+        hoverBorder: Color.white.opacity(0.14),
+        selectedRowFill: Color.white.opacity(0.05),
+        scrim: Color.black.opacity(0.35)
+    )
+
+    static let light = ThemePalette(
+        background: Color(hex: 0xF5F6F8),
+        sidebarBackground: Color(hex: 0xFFFFFF),
+        panelBackground: Color(hex: 0xF9FAFB),
+        cardBackground: Color(hex: 0xFFFFFF),
+        quickAccessBackground: Color(hex: 0xF0F2F5),
+        border: Color(hex: 0xE5E7EB),
+        cardBorder: Color(hex: 0xD1D5DB),
+        textPrimary: Color(hex: 0x111827),
+        textSecondary: Color(hex: 0x6B7280),
+        textTertiary: Color(hex: 0x9CA3AF),
+        hoverFill: Color.black.opacity(0.05),
+        hoverBorder: Color(hex: 0x9CA3AF),
+        selectedRowFill: Color.black.opacity(0.06),
+        scrim: Color.black.opacity(0.25)
+    )
+}

+ 18 - 10
Reddit App/Utilities/AppTheme.swift

@@ -1,16 +1,24 @@
 import SwiftUI
 
 enum AppTheme {
-    static let background = Color(hex: 0x0B0E14)
-    static let sidebarBackground = Color(hex: 0x0B0E14)
-    static let panelBackground = Color(hex: 0x11141C)
-    static let cardBackground = Color(hex: 0x151922)
-    static let quickAccessBackground = Color(hex: 0x151922)
-    static let border = Color.white.opacity(0.06)
-    static let cardBorder = Color.white.opacity(0.08)
-    static let textPrimary = Color.white
-    static let textSecondary = Color(hex: 0x8E9297)
-    static let textTertiary = Color(hex: 0x6B7280)
+    private static var palette: ThemePalette {
+        AppearanceManager.shared.palette
+    }
+
+    static var background: Color { palette.background }
+    static var sidebarBackground: Color { palette.sidebarBackground }
+    static var panelBackground: Color { palette.panelBackground }
+    static var cardBackground: Color { palette.cardBackground }
+    static var quickAccessBackground: Color { palette.quickAccessBackground }
+    static var border: Color { palette.border }
+    static var cardBorder: Color { palette.cardBorder }
+    static var textPrimary: Color { palette.textPrimary }
+    static var textSecondary: Color { palette.textSecondary }
+    static var textTertiary: Color { palette.textTertiary }
+    static var hoverFill: Color { palette.hoverFill }
+    static var hoverBorder: Color { palette.hoverBorder }
+    static var selectedRowFill: Color { palette.selectedRowFill }
+    static var scrim: Color { palette.scrim }
 
     static let accentPurple = Color(hex: 0x5833C7)
     static let accentPurpleLight = Color(hex: 0x6B46E5)

+ 2 - 7
Reddit App/Utilities/InteractiveHoverStyle.swift

@@ -5,13 +5,8 @@ import SwiftUI
 
 extension AppTheme {
     static let hoverAnimation: Animation = .easeOut(duration: 0.15)
-    static let hoverFillOpacity: Double = 0.06
-    static let hoverBorderOpacity: Double = 0.14
     static let hoverPressScale: CGFloat = 0.98
     static let hoverLiftScale: CGFloat = 1.012
-
-    static var hoverFill: Color { Color.white.opacity(hoverFillOpacity) }
-    static var hoverBorder: Color { Color.white.opacity(hoverBorderOpacity) }
 }
 
 // MARK: - Button Styles
@@ -145,7 +140,7 @@ private struct HoverCardModifier: ViewModifier {
             .overlay {
                 if isEnabled && isHovered {
                     RoundedRectangle(cornerRadius: cornerRadius)
-                        .fill(isSelected ? Color.white.opacity(0.04) : AppTheme.hoverFill)
+                        .fill(isSelected ? AppTheme.selectedRowFill : AppTheme.hoverFill)
                         .allowsHitTesting(false)
                 }
             }
@@ -197,7 +192,7 @@ private struct SidebarRowHoverModifier: ViewModifier {
                 }
         } else if isSelected {
             RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
-                .fill(Color.white.opacity(0.05))
+                .fill(AppTheme.selectedRowFill)
         } else if isHovered {
             RoundedRectangle(cornerRadius: AppTheme.cornerRadiusSmall)
                 .fill(AppTheme.hoverFill)

+ 46 - 0
Reddit App/Views/Components/SettingsComponents.swift

@@ -47,6 +47,52 @@ struct SettingsShareAppButton: View {
     }
 }
 
+struct SettingsToggleRow: View {
+    let icon: String
+    let title: String
+    var subtitle: String?
+    @Binding var isOn: Bool
+
+    var body: some View {
+        HStack(spacing: 12) {
+            Image(systemName: icon)
+                .font(.system(size: 14, weight: .medium))
+                .foregroundStyle(AppTheme.accentPurple)
+                .frame(width: 32, height: 32)
+                .background(
+                    RoundedRectangle(cornerRadius: 8)
+                        .fill(AppTheme.accentPurple.opacity(0.12))
+                )
+
+            VStack(alignment: .leading, spacing: 2) {
+                Text(title)
+                    .font(.system(size: 13, weight: .medium))
+                    .foregroundStyle(AppTheme.textPrimary)
+                if let subtitle {
+                    Text(subtitle)
+                        .font(.system(size: 10))
+                        .foregroundStyle(AppTheme.textSecondary)
+                }
+            }
+
+            Spacer()
+
+            Toggle("", isOn: $isOn)
+                .labelsHidden()
+                .toggleStyle(.switch)
+        }
+        .padding(.horizontal, 12)
+        .padding(.vertical, 10)
+        .background(AppTheme.panelBackground)
+        .clipShape(RoundedRectangle(cornerRadius: 8))
+        .overlay(
+            RoundedRectangle(cornerRadius: 8)
+                .stroke(AppTheme.cardBorder, lineWidth: 1)
+        )
+        .frame(maxWidth: .infinity, alignment: .leading)
+    }
+}
+
 struct SettingsLinkRow: View {
     let icon: String
     let title: String

+ 4 - 0
Reddit App/Views/FrontPageView.swift

@@ -2,6 +2,7 @@ import SwiftUI
 
 struct FrontPageView: View {
     @EnvironmentObject private var subscriptions: SubscriptionManager
+    @Environment(AppearanceManager.self) private var appearanceManager
     @State private var viewModel = FrontPageViewModel()
     @State private var paywallViewModel = PaywallViewModel()
     @State private var postGeneratorViewModel = PostGeneratorViewModel()
@@ -10,6 +11,8 @@ struct FrontPageView: View {
     @State private var settingsViewModel = SettingsViewModel()
 
     var body: some View {
+        let _ = appearanceManager.isDarkMode
+
         ZStack {
             HStack(spacing: 0) {
                 SidebarView(
@@ -135,5 +138,6 @@ private struct ToolPlaceholderView: View {
 #Preview {
     FrontPageView()
         .environmentObject(SubscriptionManager())
+        .environment(AppearanceManager.shared)
         .frame(width: AppWindow.width, height: AppWindow.height)
 }

+ 1 - 1
Reddit App/Views/PaywallView.swift

@@ -40,7 +40,7 @@ struct PaywallView: View {
                 .frame(width: geometry.size.width, height: geometry.size.height, alignment: .top)
 
                 if subscriptions.isLoadingProducts && !subscriptions.hasAllProductsLoaded {
-                    Color.black.opacity(0.35)
+                    AppTheme.scrim
                         .ignoresSafeArea()
                         .allowsHitTesting(false)
                     ProgressView()

+ 20 - 0
Reddit App/Views/SettingsView.swift

@@ -2,6 +2,7 @@ import SwiftUI
 
 struct SettingsView: View {
     @Bindable var viewModel: SettingsViewModel
+    @Environment(AppearanceManager.self) private var appearance
 
     private enum Layout {
         static let horizontalPadding: CGFloat = 24
@@ -13,6 +14,7 @@ struct SettingsView: View {
 
             ScrollView {
                 VStack(alignment: .leading, spacing: 12) {
+                    appearanceSection
                     shareSection
                     linksSection
                 }
@@ -49,6 +51,23 @@ struct SettingsView: View {
         }
     }
 
+    private var appearanceSection: some View {
+        PostFormCard(
+            title: "Appearance",
+            subtitle: "Customize how the app looks"
+        ) {
+            SettingsToggleRow(
+                icon: "moon.fill",
+                title: "Dark Mode",
+                subtitle: appearance.isDarkMode ? "On" : "Off",
+                isOn: Binding(
+                    get: { appearance.isDarkMode },
+                    set: { appearance.isDarkMode = $0 }
+                )
+            )
+        }
+    }
+
     private var shareSection: some View {
         PostFormCard(
             title: "Share",
@@ -105,5 +124,6 @@ struct SettingsView: View {
 
 #Preview {
     SettingsView(viewModel: SettingsViewModel())
+        .environment(AppearanceManager.shared)
         .frame(width: 800, height: 600)
 }