Skip to content
43 changes: 43 additions & 0 deletions V2er/General/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

import SwiftUI

struct RootView<Content: View> : View {

Check failure on line 9 in V2er/General/RootView.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Colon Spacing Violation: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals (colon)
var content: Content

init(@ViewBuilder content: ()-> Content) {

Check failure on line 12 in V2er/General/RootView.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Returning Whitespace Violation: Return arrow and return type should be separated by a single space or on a separate line (return_arrow_whitespace)
self.content = content()
}

var body:some View {

Check failure on line 16 in V2er/General/RootView.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Colon Spacing Violation: Colons should be next to the identifier when specifying a type and next to the key in dictionary literals (colon)
EmptyView()
.withHostingWindow { window in
V2erApp.window = window
Expand All @@ -27,6 +27,49 @@
override var preferredStatusBarStyle: UIStatusBarStyle {
return V2erApp.statusBarState
}

override func viewDidLoad() {
super.viewDidLoad()
// Apply the saved appearance mode
if let savedMode = UserDefaults.standard.string(forKey: "appearanceMode") {
applyAppearanceFromString(savedMode)
}

// Listen for appearance changes
NotificationCenter.default.addObserver(
self,
selector: #selector(handleAppearanceChange),
name: NSNotification.Name("AppearanceDidChange"),
object: nil
)
}

@objc private func handleAppearanceChange(_ notification: Notification) {
if let appearance = notification.object as? AppearanceMode {
applyAppearanceFromString(appearance.rawValue)
}
}

func applyAppearanceFromString(_ modeString: String) {
let style: UIUserInterfaceStyle
switch modeString {
case "light":
style = .light
case "dark":
style = .dark
default:
style = .unspecified
}
overrideUserInterfaceStyle = style

// Force the view to redraw
view.setNeedsDisplay()
view.setNeedsLayout()
}

deinit {
NotificationCenter.default.removeObserver(self)
}
}

extension View {
Expand All @@ -43,7 +86,7 @@
}
}


Check failure on line 89 in V2er/General/RootView.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Vertical Whitespace Violation: Limit vertical whitespace to a single empty line; currently 2 (vertical_whitespace)
struct RootHostView: View {
@EnvironmentObject private var store: Store

Expand Down
195 changes: 144 additions & 51 deletions V2er/General/V2erApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//

import SwiftUI
import Combine

@main
struct V2erApp: App {
Expand All @@ -32,63 +33,155 @@ struct V2erApp: App {
RootHostView()
.environmentObject(store)
.preferredColorScheme(store.appState.settingState.appearance.colorScheme)
.onAppear {
updateNavigationBarAppearance(for: store.appState.settingState.appearance)
updateWindowInterfaceStyle(for: store.appState.settingState.appearance)
}
.onChange(of: store.appState.settingState.appearance) { newValue in
updateNavigationBarAppearance(for: newValue)
updateWindowInterfaceStyle(for: newValue)
}
}
}
} .onAppear {
updateAppearance(store.appState.settingState.appearance)
} .onChange(of: store.appState.settingState.appearance) { newValue in
updateAppearance(newValue)
} }
}

private func updateAppearance(_ appearance: AppearanceMode) {
updateNavigationBarAppearance(for: appearance)
updateWindowInterfaceStyle(for: appearance)
}

static func updateAppearanceStatic(_ appearance: AppearanceMode) {
updateNavigationBarAppearanceStatic(for: appearance)
updateWindowInterfaceStyleStatic(for: appearance)
}

static func updateNavigationBarAppearanceStatic(for appearance: AppearanceMode) {
DispatchQueue.main.async {
let navbarAppearance = UINavigationBarAppearance()

// 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.standardAppearance = navbarAppearance
navAppearance.compactAppearance = navbarAppearance
navAppearance.scrollEdgeAppearance = navbarAppearance
navAppearance.backgroundColor = .clear
navAppearance.tintColor = tintColor

// Force refresh of current navigation controllers
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
windowScene.windows.forEach { window in
window.subviews.forEach { _ in
window.tintColor = tintColor
} } } }
}

private func updateNavigationBarAppearance(for appearance: AppearanceMode) {
let navbarAppearance = UINavigationBarAppearance()

// 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.standardAppearance = navbarAppearance
navAppearance.compactAppearance = navbarAppearance
navAppearance.scrollEdgeAppearance = navbarAppearance
navAppearance.backgroundColor = .clear
navAppearance.tintColor = tintColor
DispatchQueue.main.async {
let navbarAppearance = UINavigationBarAppearance()

// 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.standardAppearance = navbarAppearance
navAppearance.compactAppearance = navbarAppearance
navAppearance.scrollEdgeAppearance = navbarAppearance
navAppearance.backgroundColor = .clear
navAppearance.tintColor = tintColor

// Force refresh of current navigation controllers
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene {
windowScene.windows.forEach { window in
window.subviews.forEach { _ in
window.tintColor = tintColor
} } } }
}

static func updateWindowInterfaceStyleStatic(for appearance: AppearanceMode) {
DispatchQueue.main.async {
let style: UIUserInterfaceStyle
switch appearance {
case .light:
style = .light
case .dark:
style = .dark
case .system:
style = .unspecified
}

// Update all connected scenes
UIApplication.shared.connectedScenes.forEach { scene in
if let windowScene = scene as? UIWindowScene {
windowScene.windows.forEach { window in
window.overrideUserInterfaceStyle = style
} } }
// Also update the stored window if available
if let window = V2erApp.window {
window.overrideUserInterfaceStyle = style
}
// Update the root hosting controller
if let rootHostingController = V2erApp.rootViewController as? RootHostingController<RootHostView> {
rootHostingController.applyAppearanceFromString(appearance.rawValue)
}
// Force a redraw
UIApplication.shared.connectedScenes.forEach { scene in
if let windowScene = scene as? UIWindowScene {
windowScene.windows.forEach { $0.setNeedsDisplay() }
} } }
}

private func updateWindowInterfaceStyle(for appearance: AppearanceMode) {
// Get all windows and update their interface style
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }

let style: UIUserInterfaceStyle
switch appearance {
case .light:
style = .light
case .dark:
style = .dark
case .system:
style = .unspecified
}

// Update all windows in the scene
windowScene.windows.forEach { window in
window.overrideUserInterfaceStyle = style
}
DispatchQueue.main.async {
let style: UIUserInterfaceStyle
switch appearance {
case .light:
style = .light
case .dark:
style = .dark
case .system:
style = .unspecified
}

// Update all connected scenes
UIApplication.shared.connectedScenes.forEach { scene in
if let windowScene = scene as? UIWindowScene {
windowScene.windows.forEach { window in
window.overrideUserInterfaceStyle = style
} } }
// Also update the stored window if available
if let window = V2erApp.window {
window.overrideUserInterfaceStyle = style
}
// Update the root hosting controller
if let rootHostingController = V2erApp.rootViewController as? RootHostingController<RootHostView> {
rootHostingController.applyAppearanceFromString(appearance.rawValue)
}
// Force a redraw
UIApplication.shared.connectedScenes.forEach { scene in
if let windowScene = scene as? UIWindowScene {
windowScene.windows.forEach { $0.setNeedsDisplay() }
} } }
}

static func changeStatusBarStyle(_ style: UIStatusBarStyle) {
Expand Down
9 changes: 7 additions & 2 deletions V2er/State/DataFlow/Reducers/SettingReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@ import Foundation
func settingStateReducer(_ state: SettingState, _ action: Action) -> (SettingState, Action?) {
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")
UserDefaults.standard.synchronize()

// Post notification for immediate UI update
NotificationCenter.default.post(name: NSNotification.Name("AppearanceDidChange"), object: action.appearance)

followingAction = nil
default:
break
}

return (state, followingAction)
}

Expand Down
2 changes: 1 addition & 1 deletion V2er/State/DataFlow/State/SettingState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import SwiftUI

struct SettingState: FluxState {
var appearance: AppearanceMode = .system

init() {
// Load saved preference
if let savedMode = UserDefaults.standard.string(forKey: "appearanceMode"),
Expand Down
10 changes: 3 additions & 7 deletions V2er/View/Settings/AppearanceSettingView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,13 @@ 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 {
Expand All @@ -35,14 +32,13 @@ struct AppearanceSettingView: View {
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 {
if store.appState.settingState.appearance == mode {
Image(systemName: "checkmark")
.foregroundColor(.tintColor)
.font(.system(size: 14, weight: .semibold))
Expand Down
Loading