diff --git a/V2er/Assets.xcassets/Colors/AppBackground.colorset/Contents.json b/V2er/Assets.xcassets/Colors/AppBackground.colorset/Contents.json new file mode 100644 index 0000000..3d8ee2e --- /dev/null +++ b/V2er/Assets.xcassets/Colors/AppBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.980", + "green" : "0.980", + "red" : "0.980" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.110", + "green" : "0.110", + "red" : "0.110" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/BackgroundColor.colorset/Contents.json b/V2er/Assets.xcassets/Colors/BackgroundColor.colorset/Contents.json new file mode 100644 index 0000000..3d84274 --- /dev/null +++ b/V2er/Assets.xcassets/Colors/BackgroundColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.800", + "blue" : "0.886", + "green" : "0.886", + "red" : "0.886" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.800", + "blue" : "0.118", + "green" : "0.110", + "red" : "0.110" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/BorderColor.colorset/Contents.json b/V2er/Assets.xcassets/Colors/BorderColor.colorset/Contents.json new file mode 100644 index 0000000..462bf3e --- /dev/null +++ b/V2er/Assets.xcassets/Colors/BorderColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.800", + "blue" : "0.910", + "green" : "0.910", + "red" : "0.910" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.800", + "blue" : "0.247", + "green" : "0.235", + "red" : "0.235" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/DimColor.colorset/Contents.json b/V2er/Assets.xcassets/Colors/DimColor.colorset/Contents.json new file mode 100644 index 0000000..f6c9cf5 --- /dev/null +++ b/V2er/Assets.xcassets/Colors/DimColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.600", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.600", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/ItemBackground.colorset/Contents.json b/V2er/Assets.xcassets/Colors/ItemBackground.colorset/Contents.json new file mode 100644 index 0000000..a431dd9 --- /dev/null +++ b/V2er/Assets.xcassets/Colors/ItemBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x1E", + "green" : "0x1C", + "red" : "0x1C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/LightGray.colorset/Contents.json b/V2er/Assets.xcassets/Colors/LightGray.colorset/Contents.json new file mode 100644 index 0000000..1beb2f7 --- /dev/null +++ b/V2er/Assets.xcassets/Colors/LightGray.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF5", + "green" : "0xF5", + "red" : "0xF5" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2E", + "green" : "0x2C", + "red" : "0x2C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/PrimaryText.colorset/Contents.json b/V2er/Assets.xcassets/Colors/PrimaryText.colorset/Contents.json new file mode 100644 index 0000000..c4fd6eb --- /dev/null +++ b/V2er/Assets.xcassets/Colors/PrimaryText.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x00", + "green" : "0x00", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/SecondaryBackground.colorset/Contents.json b/V2er/Assets.xcassets/Colors/SecondaryBackground.colorset/Contents.json new file mode 100644 index 0000000..cca56c7 --- /dev/null +++ b/V2er/Assets.xcassets/Colors/SecondaryBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.961", + "green" : "0.961", + "red" : "0.961" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.153", + "green" : "0.153", + "red" : "0.153" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/SecondaryText.colorset/Contents.json b/V2er/Assets.xcassets/Colors/SecondaryText.colorset/Contents.json new file mode 100644 index 0000000..41b70f4 --- /dev/null +++ b/V2er/Assets.xcassets/Colors/SecondaryText.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x55", + "green" : "0x55", + "red" : "0x55" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xAA", + "green" : "0xAA", + "red" : "0xAA" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/SelectionColor.colorset/Contents.json b/V2er/Assets.xcassets/Colors/SelectionColor.colorset/Contents.json new file mode 100644 index 0000000..da7da1f --- /dev/null +++ b/V2er/Assets.xcassets/Colors/SelectionColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.900", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.900", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/Separator.colorset/Contents.json b/V2er/Assets.xcassets/Colors/Separator.colorset/Contents.json new file mode 100644 index 0000000..dbb0d23 --- /dev/null +++ b/V2er/Assets.xcassets/Colors/Separator.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE8", + "green" : "0xE8", + "red" : "0xE8" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2E", + "green" : "0x2C", + "red" : "0x2C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/TertiaryBackground.colorset/Contents.json b/V2er/Assets.xcassets/Colors/TertiaryBackground.colorset/Contents.json new file mode 100644 index 0000000..65deedd --- /dev/null +++ b/V2er/Assets.xcassets/Colors/TertiaryBackground.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF5", + "green" : "0xF5", + "red" : "0xF5" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x1A", + "green" : "0x1A", + "red" : "0x1A" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/TertiaryText.colorset/Contents.json b/V2er/Assets.xcassets/Colors/TertiaryText.colorset/Contents.json new file mode 100644 index 0000000..3b03d34 --- /dev/null +++ b/V2er/Assets.xcassets/Colors/TertiaryText.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x99", + "green" : "0x99", + "red" : "0x99" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x66", + "green" : "0x66", + "red" : "0x66" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/TintColor.colorset/Contents.json b/V2er/Assets.xcassets/Colors/TintColor.colorset/Contents.json new file mode 100644 index 0000000..db7950d --- /dev/null +++ b/V2er/Assets.xcassets/Colors/TintColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x38", + "green" : "0x38", + "red" : "0x38" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xFF", + "red" : "0xFF" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/URLColor.colorset/Contents.json b/V2er/Assets.xcassets/Colors/URLColor.colorset/Contents.json new file mode 100644 index 0000000..84b6cc8 --- /dev/null +++ b/V2er/Assets.xcassets/Colors/URLColor.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x87", + "green" : "0x80", + "red" : "0x77" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xB8", + "green" : "0xA3", + "red" : "0x94" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/V2er/Assets.xcassets/Colors/bodyText.colorset/Contents.json b/V2er/Assets.xcassets/Colors/bodyText.colorset/Contents.json index 6e8d0a5..94e7045 100644 --- a/V2er/Assets.xcassets/Colors/bodyText.colorset/Contents.json +++ b/V2er/Assets.xcassets/Colors/bodyText.colorset/Contents.json @@ -4,10 +4,28 @@ "color" : { "color-space" : "srgb", "components" : { - "alpha" : "1.000", - "blue" : "0x55", - "green" : "0x55", - "red" : "0x55" + "alpha" : "0.750", + "blue" : "0.000", + "green" : "0.000", + "red" : "0.000" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "0.850", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" } }, "idiom" : "universal" @@ -17,4 +35,4 @@ "author" : "xcode", "version" : 1 } -} +} \ No newline at end of file diff --git a/V2er/General/Color.swift b/V2er/General/Color.swift index e98129a..9016edf 100644 --- a/V2er/General/Color.swift +++ b/V2er/General/Color.swift @@ -26,24 +26,62 @@ extension Color { self.frame(width: .infinity) } - public static let border = hex(0xE8E8E8, alpha: 0.8) - static let lightGray = hex(0xF5F5F5) - static let almostClear = hex(0xFFFFFF, alpha: 0.000001) - static let debugColor = hex(0xFF0000, alpha: 0.1) -// static let bodyText = hex(0x555555) - static let bodyText = hex(0x000000, alpha: 0.75) - static let tintColor = hex(0x383838) - static let bgColor = hex(0xE2E2E2, alpha: 0.8) - static let itemBg: Color = .white - static let dim = hex(0x000000, alpha: 0.6) -// static let url = hex(0x60c2d4) - static let url = hex(0x778087) + // MARK: - Adaptive Colors for Dark Mode + + // Background Colors + public static let background = Color("AppBackground") + public static let secondaryBackground = Color("SecondaryBackground") + public static let tertiaryBackground = Color("TertiaryBackground") + public static let itemBackground = Color("ItemBackground") + + // Text Colors + public static let primaryText = Color("PrimaryText") + public static let secondaryText = Color("SecondaryText") + public static let tertiaryText = Color("TertiaryText") + + // UI Element Colors + public static let separator = Color("Separator") + public static let tint = Color("TintColor") + public static let selection = Color("SelectionColor") + + // Legacy colors with dark mode support + public static let border = Color("BorderColor") + public static let lightGray = Color("LightGray") + public static let almostClear = hex(0xFFFFFF, alpha: 0.000001) + public static let debugColor = hex(0xFF0000, alpha: 0.1) + public static let bodyText = Color("BodyText") + public static let tintColor = Color("TintColor") + public static let bgColor = Color("BackgroundColor") + public static let itemBg = Color("ItemBackground") + public static let dim = Color("DimColor") + public static let url = Color("URLColor") public var uiColor: UIColor { return UIColor(self) } } +// MARK: - Dynamic Color Creation +extension Color { + static func dynamic(light: Color, dark: Color) -> Color { + return Color(UIColor { traitCollection in + switch traitCollection.userInterfaceStyle { + case .dark: + return UIColor(dark) + default: + return UIColor(light) + } + }) + } + + static func dynamicHex(light: Int, dark: Int, alpha: CGFloat = 1.0) -> Color { + return dynamic( + light: Color.hex(light, alpha: alpha), + dark: Color.hex(dark, alpha: alpha) + ) + } +} + extension UIColor { convenience init(hex: Int, alpha: CGFloat = 1.0) { let components = ( @@ -55,18 +93,47 @@ extension UIColor { } } - - +// MARK: - Preview struct Color_Previews: PreviewProvider { static var previews: some View { - VStack { - Color.hex(0xFBFBFB).frame(width: 100, height: 100) - Color.hex(0x00FF00, alpha: 0.2).frame(width: 100, height: 100) - Color.hex(0xFF00FF).frame(width: 100, height: 100) - Color.tintColor.frame(width: 100, height: 100) - Color.lightGray.frame(width: 100, height: 100) - Color.border.frame(width: 100, height: 100).opacity(0.5) + Group { + // Light Mode Preview + VStack(spacing: 20) { + Text("Light Mode") + .font(.title) + HStack { + Color.background.frame(width: 80, height: 80) + Color.secondaryBackground.frame(width: 80, height: 80) + Color.itemBackground.frame(width: 80, height: 80) + } + HStack { + Color.primaryText.frame(width: 80, height: 80) + Color.secondaryText.frame(width: 80, height: 80) + Color.tintColor.frame(width: 80, height: 80) + } + } + .padding() + .background(Color.background) + .environment(\.colorScheme, .light) + + // Dark Mode Preview + VStack(spacing: 20) { + Text("Dark Mode") + .font(.title) + HStack { + Color.background.frame(width: 80, height: 80) + Color.secondaryBackground.frame(width: 80, height: 80) + Color.itemBackground.frame(width: 80, height: 80) + } + HStack { + Color.primaryText.frame(width: 80, height: 80) + Color.secondaryText.frame(width: 80, height: 80) + Color.tintColor.frame(width: 80, height: 80) + } + } + .padding() + .background(Color.background) + .environment(\.colorScheme, .dark) } - } -} +} \ No newline at end of file diff --git a/V2er/General/V2erApp.swift b/V2er/General/V2erApp.swift index 737d201..563714c 100644 --- a/V2er/General/V2erApp.swift +++ b/V2er/General/V2erApp.swift @@ -14,35 +14,60 @@ struct V2erApp: App { public static var rootViewController: UIViewController? public static var statusBarState: UIStatusBarStyle = .darkContent public static var window: UIWindow? + @StateObject private var store = Store.shared init() { setupApperance() } private func setupApperance() { + // Navigation bar appearance will be set dynamically based on color scheme + let navAppearance = UINavigationBar.appearance() + navAppearance.isTranslucent = true + } + + var body: some Scene { + WindowGroup { + RootView { + RootHostView() + .environmentObject(store) + .preferredColorScheme(store.appState.settingState.appearance.colorScheme) + .onAppear { + updateNavigationBarAppearance(for: store.appState.settingState.appearance) + } + .onChange(of: store.appState.settingState.appearance) { newValue in + updateNavigationBarAppearance(for: newValue) + } + } + } + } + + private func updateNavigationBarAppearance(for appearance: AppearanceMode) { let navbarAppearance = UINavigationBarAppearance() - let tintColor = UIColor.black + + // Determine if we should use dark mode + let isDarkMode: Bool + switch appearance { + case .light: + isDarkMode = false + case .dark: + isDarkMode = true + case .system: + isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark + } + + let tintColor = isDarkMode ? UIColor.white : UIColor.black navbarAppearance.titleTextAttributes = [.foregroundColor: tintColor] navbarAppearance.largeTitleTextAttributes = [.foregroundColor: tintColor] navbarAppearance.backgroundColor = .clear let navAppearance = UINavigationBar.appearance() - navAppearance.isTranslucent = true navAppearance.standardAppearance = navbarAppearance navAppearance.compactAppearance = navbarAppearance navAppearance.scrollEdgeAppearance = navbarAppearance navAppearance.backgroundColor = .clear navAppearance.tintColor = tintColor } - - var body: some Scene { - WindowGroup { - RootView { - RootHostView() - .environmentObject(Store.shared) - } - } - } static func changeStatusBarStyle(_ style: UIStatusBarStyle) { guard style != statusBarState else { return } diff --git a/V2er/Info.plist b/V2er/Info.plist index bf7762a..df25606 100644 --- a/V2er/Info.plist +++ b/V2er/Info.plist @@ -54,8 +54,6 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight - UIUserInterfaceStyle - Light UIViewControllerBasedStatusBarAppearance diff --git a/V2er/State/DataFlow/Reducers/SettingReducer.swift b/V2er/State/DataFlow/Reducers/SettingReducer.swift index 631fec2..ec3613e 100644 --- a/V2er/State/DataFlow/Reducers/SettingReducer.swift +++ b/V2er/State/DataFlow/Reducers/SettingReducer.swift @@ -9,7 +9,20 @@ import Foundation func settingStateReducer(_ state: SettingState, _ action: Action) -> (SettingState, Action?) { - return (SettingState(), nil) + var state = state + var followingAction: Action? = action + + switch action { + case let action as SettingActions.ChangeAppearanceAction: + state.appearance = action.appearance + // Save to UserDefaults + UserDefaults.standard.set(action.appearance.rawValue, forKey: "appearanceMode") + followingAction = nil + default: + break + } + + return (state, followingAction) } diff --git a/V2er/State/DataFlow/State/SettingState.swift b/V2er/State/DataFlow/State/SettingState.swift index 8070bc2..29b6e58 100644 --- a/V2er/State/DataFlow/State/SettingState.swift +++ b/V2er/State/DataFlow/State/SettingState.swift @@ -7,7 +7,54 @@ // import Foundation +import SwiftUI struct SettingState: FluxState { + var appearance: AppearanceMode = .system + init() { + // Load saved preference + if let savedMode = UserDefaults.standard.string(forKey: "appearanceMode"), + let mode = AppearanceMode(rawValue: savedMode) { + self.appearance = mode + } + } } + +enum AppearanceMode: String, CaseIterable { + case light = "light" + case dark = "dark" + case system = "system" + + var displayName: String { + switch self { + case .light: + return "浅色" + case .dark: + return "深色" + case .system: + return "跟随系统" + } + } + + var colorScheme: ColorScheme? { + switch self { + case .light: + return .light + case .dark: + return .dark + case .system: + return nil + } + } +} + +// Temporary: Define actions here until SettingActions.swift is properly added to project +struct SettingActions { + private static let R: Reducer = .setting + + struct ChangeAppearanceAction: Action { + var target: Reducer = R + let appearance: AppearanceMode + } +} \ No newline at end of file diff --git a/V2er/View/Feed/FeedItemView.swift b/V2er/View/Feed/FeedItemView.swift index 8fc602b..0318d53 100644 --- a/V2er/View/Feed/FeedItemView.swift +++ b/V2er/View/Feed/FeedItemView.swift @@ -37,6 +37,7 @@ struct FeedItemView: View { .padding(.vertical, 4) Text("评论\(data.replyNum.safe)") .font(.footnote) + .foregroundColor(.secondaryText) .greedyWidth(.trailing) } .padding(12) diff --git a/V2er/View/Login/LoginPage.swift b/V2er/View/Login/LoginPage.swift index fe08e1a..5fc0108 100644 --- a/V2er/View/Login/LoginPage.swift +++ b/V2er/View/Login/LoginPage.swift @@ -91,8 +91,8 @@ struct LoginPage: StateView { .submitLabel(.continue) .padding(.horizontal, padding) .frame(maxWidth: .infinity, maxHeight: height) - Color.gray - .opacity(0.2) + Color.separator + .opacity(0.5) .padding(.vertical, 14) .frame(width: 1.5, height: height) .padding(.horizontal, 2) @@ -116,8 +116,8 @@ struct LoginPage: StateView { .submitLabel(.go) .keyboardType(.asciiCapable) .disableAutocorrection(true) - Color.gray - .opacity(0.2) + Color.separator + .opacity(0.5) .padding(.vertical, 14) .frame(width: 1.5, height: height) .padding(.horizontal, 2) @@ -152,7 +152,7 @@ struct LoginPage: StateView { } label: { Text("Login") .font(.headline) - .foregroundColor(.white) + .foregroundColor(Color.itemBackground) .padding() .greedyWidth() .background(Color.tintColor) diff --git a/V2er/View/Me/MePage.swift b/V2er/View/Me/MePage.swift index 0732387..336f01a 100644 --- a/V2er/View/Me/MePage.swift +++ b/V2er/View/Me/MePage.swift @@ -31,17 +31,17 @@ struct MePage: BaseHomePageView { if !AccountState.hasSignIn() { VStack { Text("登录查看更多") - .foregroundColor(.white) + .foregroundColor(.primaryText) .font(.title2) Button { dispatch(LoginActions.ShowLoginPageAction()) } label: { Text("登录") .font(.headline) - .foregroundColor(.white) + .foregroundColor(Color.itemBackground) .padding() .padding(.horizontal, 50) - .background(Color.black) + .background(Color.tintColor) .cornerRadius(15) } } @@ -71,12 +71,12 @@ struct MePage: BaseHomePageView { .foregroundColor(Color.bodyText) Image(systemName: "chevron.right") .font(.body.weight(.regular)) - .foregroundColor(Color.gray) + .foregroundColor(Color.secondaryText) } } .padding(.horizontal, 12) .padding(.vertical, 16) - .background(.white) + .background(Color.itemBackground) .padding(.bottom, 8) .to { UserDetailPage(userId: AccountState.userName) diff --git a/V2er/View/Me/UserDetailPage.swift b/V2er/View/Me/UserDetailPage.swift index 1a9b225..536df15 100644 --- a/V2er/View/Me/UserDetailPage.swift +++ b/V2er/View/Me/UserDetailPage.swift @@ -42,7 +42,7 @@ struct UserDetailPage: StateView { } var foreGroundColor: SwiftUI.Color { - return shouldHideNavbar ? .white.opacity(0.9) : .tintColor + return shouldHideNavbar ? Color.primaryText.opacity(0.9) : .tintColor } var body: some View { @@ -77,7 +77,7 @@ struct UserDetailPage: StateView { .fade(duration: 0.25) .resizable() .blur(radius: 80, opaque: true) - .overlay(Color.black.opacity(withAnimation {shouldHideNavbar ? 0.3 : 0.1})) + .overlay(Color.dynamic(light: .black, dark: .white).opacity(withAnimation {shouldHideNavbar ? 0.3 : 0.1})) .frame(maxWidth: .infinity, maxHeight: height) Spacer().background(.clear) } @@ -172,7 +172,7 @@ struct UserDetailPage: StateView { AvatarView(url: model.avatar, size: heightOfNodeImage) HStack(alignment: .center,spacing: 4) { Circle() - .fill(state.model.isOnline ? .green : .gray) + .fill(state.model.isOnline ? .green : Color.secondaryText) .frame(width: 8, height: 8) Text(model.userName) .font(.headline.weight(.semibold)) @@ -203,7 +203,7 @@ struct UserDetailPage: StateView { .background(Color.lightGray, in: RoundedRectangle(cornerRadius: 10)) .padding(.horizontal, 12) .padding(.vertical, 10) - .background(.white) + .background(Color.itemBackground) .clipCorner(12, corners: [.topLeft, .topRight]) } @@ -298,7 +298,7 @@ struct UserDetailPage: StateView { NavigationLink(destination: TagDetailPage()) { Text(data.tag) .font(.footnote) - .foregroundColor(.black) + .foregroundColor(Color.primaryText) .lineLimit(1) .padding(.horizontal, 14) .padding(.vertical, 8) @@ -339,14 +339,14 @@ struct UserDetailPage: StateView { } label: { Text(title) .fontWeight(.bold) - .foregroundColor(isSelected ? .white.opacity(0.9) : .tintColor) + .foregroundColor(isSelected ? Color.itemBackground.opacity(0.9) : .tintColor) .frame(maxWidth: .infinity) .padding(.vertical, 8) .background { VStack { if isSelected { RoundedRectangle(cornerRadius: 10) - .fill(.black) + .fill(Color.primaryText) .matchedGeometryEffect(id: "TAB", in: animation) } } diff --git a/V2er/View/Settings/AppearanceSettingView.swift b/V2er/View/Settings/AppearanceSettingView.swift index 095b0aa..a531739 100644 --- a/V2er/View/Settings/AppearanceSettingView.swift +++ b/V2er/View/Settings/AppearanceSettingView.swift @@ -9,22 +9,91 @@ import SwiftUI struct AppearanceSettingView: View { + @EnvironmentObject private var store: Store + @State private var selectedAppearance: AppearanceMode = .system + var body: some View { formView .navBar("外观设置") + .onAppear { + selectedAppearance = store.appState.settingState.appearance + } } @ViewBuilder private var formView: some View { ScrollView { - SectionItemView("字体大小") -// .to {} + VStack(spacing: 0) { + // Dark Mode Section + VStack(alignment: .leading, spacing: 12) { + Text("主题") + .font(.caption) + .foregroundColor(.secondaryText) + .padding(.horizontal) + .padding(.top, 8) + + VStack(spacing: 0) { + ForEach(AppearanceMode.allCases, id: \.self) { mode in + Button(action: { + selectedAppearance = mode + dispatch(SettingActions.ChangeAppearanceAction(appearance: mode)) + }) { + HStack { + Text(mode.displayName) + .foregroundColor(.primaryText) + Spacer() + if selectedAppearance == mode { + Image(systemName: "checkmark") + .foregroundColor(.tintColor) + .font(.system(size: 14, weight: .semibold)) + } + } + .padding(.horizontal) + .padding(.vertical, 12) + .background(Color.itemBackground) + } + + if mode != AppearanceMode.allCases.last { + Divider() + .padding(.leading) + } + } + } + .background(Color.itemBackground) + .cornerRadius(10) + .padding(.horizontal) + } + .padding(.top) + + // Font Size Section + VStack(alignment: .leading, spacing: 12) { + Text("字体") + .font(.caption) + .foregroundColor(.secondaryText) + .padding(.horizontal) + .padding(.top, 24) + + SectionItemView("字体大小") + .background(Color.itemBackground) + .cornerRadius(10) + .padding(.horizontal) + } + } } + .background(Color.background.ignoresSafeArea()) } } struct AppearanceSettingView_Previews: PreviewProvider { static var previews: some View { - AppearanceSettingView() + Group { + AppearanceSettingView() + .environmentObject(Store.shared) + .environment(\.colorScheme, .light) + + AppearanceSettingView() + .environmentObject(Store.shared) + .environment(\.colorScheme, .dark) + } } -} +} \ No newline at end of file diff --git a/V2er/View/Settings/SettingsPage.swift b/V2er/View/Settings/SettingsPage.swift index 5ab607a..6bd932c 100644 --- a/V2er/View/Settings/SettingsPage.swift +++ b/V2er/View/Settings/SettingsPage.swift @@ -22,8 +22,11 @@ struct SettingsPage: View { private var formView: some View { ScrollView { VStack(spacing: 0) { - SectionItemView("通用设置", showDivider: false) + SectionItemView("外观设置", showDivider: false) .padding(.top, 8) + .to { AppearanceSettingView() } + + SectionItemView("通用设置") .to { OtherSettingsView() } SectionItemView("帮助与反馈") diff --git a/V2er/View/Tag/TagDetailPage.swift b/V2er/View/Tag/TagDetailPage.swift index dcd5aa7..4f00162 100644 --- a/V2er/View/Tag/TagDetailPage.swift +++ b/V2er/View/Tag/TagDetailPage.swift @@ -40,7 +40,7 @@ struct TagDetailPage: StateView, InstanceIdentifiable { } private var foreGroundColor: Color { - shouldHideNavbar ? .white.opacity(0.9) : .tintColor + shouldHideNavbar ? Color.primaryText.opacity(0.9) : .tintColor } private var statusBarStyle: UIStatusBarStyle { @@ -77,7 +77,7 @@ struct TagDetailPage: StateView, InstanceIdentifiable { .fade(duration: 0.25) .resizable() .blur(radius: 80, opaque: true) - .overlay(Color.black.opacity(withAnimation {shouldHideNavbar ? 0.3 : 0.1})) + .overlay(Color.dynamic(light: .black, dark: .white).opacity(withAnimation {shouldHideNavbar ? 0.3 : 0.1})) .frame(height: bannerViewHeight * 1.2 + max(scrollY, 0)) Spacer() } @@ -206,7 +206,7 @@ struct TagDetailPage: StateView, InstanceIdentifiable { } } } - .background(.white) + .background(Color.itemBackground) .clipCorner(12, corners: [.topLeft, .topRight]) } diff --git a/V2er/View/Widget/NodeView.swift b/V2er/View/Widget/NodeView.swift index 6c68252..19f8641 100644 --- a/V2er/View/Widget/NodeView.swift +++ b/V2er/View/Widget/NodeView.swift @@ -25,11 +25,11 @@ struct NodeView: View { } label: { Text(name) .font(.footnote) - .foregroundColor(.black) + .foregroundColor(Color.dynamic(light: .hex(0x666666), dark: .hex(0xCCCCCC))) .lineLimit(1) .padding(.horizontal, 14) .padding(.vertical, 8) - .background(Color.lightGray) + .background(Color.dynamic(light: Color.hex(0xF5F5F5), dark: Color.hex(0x2C2C2E))) } } diff --git a/V2er/View/Widget/RichTextView/Webview.swift b/V2er/View/Widget/RichTextView/Webview.swift index b592ea0..131f466 100644 --- a/V2er/View/Widget/RichTextView/Webview.swift +++ b/V2er/View/Widget/RichTextView/Webview.swift @@ -111,7 +111,7 @@ struct Webview : UIViewRepresentable { return """ @@ -121,7 +121,7 @@ struct Webview : UIViewRepresentable { return """ @@ -132,12 +132,12 @@ struct Webview : UIViewRepresentable { diff --git a/V2er/View/Widget/SectionItemView.swift b/V2er/View/Widget/SectionItemView.swift index 5fdec03..9688df9 100644 --- a/V2er/View/Widget/SectionItemView.swift +++ b/V2er/View/Widget/SectionItemView.swift @@ -27,7 +27,7 @@ struct SectionItemView: View { SectionView(title, icon: icon, showDivider: showDivider) { Image(systemName: "chevron.right") .font(.body.weight(.regular)) - .foregroundColor(.gray) + .foregroundColor(.secondaryText) .padding(.trailing, paddingH) } } @@ -65,6 +65,6 @@ struct SectionView: View { .padding(.vertical, 17) .divider(showDivider ? 0.8 : 0.0) } - .background(.white) + .background(Color.itemBackground) } } diff --git a/V2er/View/Widget/TabBar.swift b/V2er/View/Widget/TabBar.swift index 889aab5..35a76e9 100644 --- a/V2er/View/Widget/TabBar.swift +++ b/V2er/View/Widget/TabBar.swift @@ -76,7 +76,7 @@ struct TabBar: View { VStack { Text(num.string) .font(.system(size: 10)) - .foregroundColor(.white) + .foregroundColor(Color.itemBackground) .padding(4) .background { Circle() diff --git a/V2er/View/Widget/Toast.swift b/V2er/View/Widget/Toast.swift index 463e48f..de9221f 100644 --- a/V2er/View/Widget/Toast.swift +++ b/V2er/View/Widget/Toast.swift @@ -58,9 +58,9 @@ extension View { self if isPresented.wrappedValue { content() - .visualBlur(bg: .white.opacity(0.95)) + .visualBlur(bg: Color.itemBackground.opacity(0.95)) .cornerRadius(99) - .shadow(color: .black.opacity(0.2), radius: 1.5) + .shadow(color: Color.primaryText.opacity(0.2), radius: 1.5) .padding(.top, paddingTop) .transition(AnyTransition.move(edge: .top)) .zIndex(1) diff --git a/website b/website index c3bbc04..b5b4f70 160000 --- a/website +++ b/website @@ -1 +1 @@ -Subproject commit c3bbc04bb0d0c1f262b1b6e28a464c8b1144e199 +Subproject commit b5b4f70430003ea84edf40eaea25dc484076505a