diff --git a/Shared/Models/WriteFreelyModel.swift b/Shared/Models/WriteFreelyModel.swift index 617385f..8a1c8aa 100644 --- a/Shared/Models/WriteFreelyModel.swift +++ b/Shared/Models/WriteFreelyModel.swift @@ -1,102 +1,106 @@ import Foundation import WriteFreely import Security import Network // MARK: - WriteFreelyModel final class WriteFreelyModel: ObservableObject { // MARK: - Models @Published var account = AccountModel() @Published var preferences = PreferencesModel() @Published var posts = PostListModel() @Published var editor = PostEditorModel() // MARK: - Error handling @Published var hasError: Bool = false var currentError: Error? { didSet { #if DEBUG print("⚠️ currentError -> didSet \(currentError?.localizedDescription ?? "nil")") print(" > hasError was: \(self.hasError)") #endif DispatchQueue.main.async { #if DEBUG print(" > self.currentError != nil: \(self.currentError != nil)") #endif self.hasError = self.currentError != nil #if DEBUG print(" > hasError is now: \(self.hasError)") #endif } } } // MARK: - State @Published var isLoggingIn: Bool = false @Published var isProcessingRequest: Bool = false @Published var hasNetworkConnection: Bool = true @Published var selectedPost: WFAPost? @Published var selectedCollection: WFACollection? @Published var showAllPosts: Bool = true @Published var isPresentingDeleteAlert: Bool = false @Published var postToDelete: WFAPost? #if os(iOS) @Published var isPresentingSettingsView: Bool = false #endif static var shared = WriteFreelyModel() // swiftlint:disable line_length let helpURL = URL(string: "https://discuss.write.as/c/help/5")! let howToURL = URL(string: "https://discuss.write.as/t/using-the-writefreely-ios-app/1946")! let reviewURL = URL(string: "https://apps.apple.com/app/id1531530896?action=write-review")! let licensesURL = URL(string: "https://github.com/writeas/writefreely-swiftui-multiplatform/tree/main/Shared/Resources/Licenses")! // swiftlint:enable line_length internal var client: WFClient? private let defaults = UserDefaults.shared private let monitor = NWPathMonitor() private let queue = DispatchQueue(label: "NetworkMonitor") internal var postToUpdate: WFAPost? init() { DispatchQueue.main.async { self.preferences.appearance = self.defaults.integer(forKey: WFDefaults.colorSchemeIntegerKey) self.preferences.font = self.defaults.integer(forKey: WFDefaults.defaultFontIntegerKey) self.account.restoreState() + + // Set the appearance + self.preferences.updateAppearance(to: Appearance(rawValue: self.preferences.appearance) ?? .system) + if self.account.isLoggedIn { guard let serverURL = URL(string: self.account.server) else { self.currentError = AccountError.invalidServerURL return } do { guard let token = try self.fetchTokenFromKeychain( username: self.account.username, server: self.account.server ) else { self.currentError = KeychainError.couldNotFetchAccessToken return } self.account.login(WFUser(token: token, username: self.account.username)) self.client = WFClient(for: serverURL) self.client?.user = self.account.user self.fetchUserCollections() self.fetchUserPosts() } catch { self.currentError = KeychainError.couldNotFetchAccessToken return } } } monitor.pathUpdateHandler = { path in DispatchQueue.main.async { self.hasNetworkConnection = path.status == .satisfied } } monitor.start(queue: queue) } } diff --git a/Shared/Preferences/PreferencesModel.swift b/Shared/Preferences/PreferencesModel.swift index 22d4753..a63748a 100644 --- a/Shared/Preferences/PreferencesModel.swift +++ b/Shared/Preferences/PreferencesModel.swift @@ -1,13 +1,54 @@ import SwiftUI +enum Appearance: Int { + case system = 0 + case light = 1 + case dark = 2 +} + class PreferencesModel: ObservableObject { private let defaults = UserDefaults.shared @Published var selectedColorScheme: ColorScheme? @Published var appearance: Int = 0 @Published var font: Int = 0 { didSet { defaults.set(font, forKey: WFDefaults.defaultFontIntegerKey) } } + + @available(iOSApplicationExtension, unavailable) + func updateAppearance(to appearance: Appearance) { + #if os(iOS) + var window: UIWindow? { + guard let scene = UIApplication.shared.connectedScenes.first, + let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate, + let window = windowSceneDelegate.window else { + return nil + } + return window + } + #endif + + switch appearance { + case .light: + #if os(macOS) + NSApp.appearance = NSAppearance(named: .aqua) + #else + window?.overrideUserInterfaceStyle = .light + #endif + case .dark: + #if os(macOS) + NSApp.appearance = NSAppearance(named: .darkAqua) + #else + window?.overrideUserInterfaceStyle = .dark + #endif + default: + #if os(macOS) + NSApp.appearance = nil + #else + window?.overrideUserInterfaceStyle = .unspecified + #endif + } + } } diff --git a/Shared/Preferences/PreferencesView.swift b/Shared/Preferences/PreferencesView.swift index 4467da7..902d809 100644 --- a/Shared/Preferences/PreferencesView.swift +++ b/Shared/Preferences/PreferencesView.swift @@ -1,102 +1,82 @@ import SwiftUI struct PreferencesView: View { @ObservedObject var preferences: PreferencesModel /* We're stuck dropping into AppKit/UIKit to set light/dark schemes for now, * because setting the .preferredColorScheme modifier on views in SwiftUI is * currently unreliable. * * Feedback submitted to Apple: * * FB8382883: "On macOS 11β4, preferredColorScheme modifier does not respect .light ColorScheme" * FB8383053: "On iOS 14β4/macOS 11β4, it is not possible to unset preferredColorScheme after setting * it to either .light or .dark" */ #if os(iOS) var window: UIWindow? { guard let scene = UIApplication.shared.connectedScenes.first, let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate, let window = windowSceneDelegate.window else { return nil } return window } #endif var body: some View { VStack { VStack { Text("Choose the preferred appearance for the app.") .font(.caption) .foregroundColor(.secondary) Picker(selection: $preferences.appearance, label: Text("Appearance")) { Text("System").tag(0) Text("Light").tag(1) Text("Dark").tag(2) } .pickerStyle(SegmentedPickerStyle()) } .padding(.bottom) VStack { Text("Choose the default font for new posts.") .font(.caption) .foregroundColor(.secondary) Picker(selection: $preferences.font, label: Text("Default Font")) { Text("Serif").tag(0) Text("Sans-Serif").tag(1) Text("Monospace").tag(2) } .pickerStyle(SegmentedPickerStyle()) .padding(.bottom) switch preferences.font { case 1: Text("Sample Text") .frame(width: 240, height: 50, alignment: .center) .font(.custom("OpenSans-Regular", size: 20)) case 2: Text("Sample Text") .frame(width: 240, height: 50, alignment: .center) .font(.custom("Hack-Regular", size: 20)) default: Text("Sample Text") .frame(width: 240, height: 50, alignment: .center) .font(.custom("Lora", size: 20)) } } .padding(.bottom) } .onChange(of: preferences.appearance) { value in - switch value { - case 1: - #if os(macOS) - NSApp.appearance = NSAppearance(named: .aqua) - #else - window?.overrideUserInterfaceStyle = .light - #endif - case 2: - #if os(macOS) - NSApp.appearance = NSAppearance(named: .darkAqua) - #else - window?.overrideUserInterfaceStyle = .dark - #endif - default: - #if os(macOS) - NSApp.appearance = nil - #else - window?.overrideUserInterfaceStyle = .unspecified - #endif - } - + preferences.updateAppearance(to: Appearance(rawValue: value) ?? .system) UserDefaults.shared.set(value, forKey: WFDefaults.colorSchemeIntegerKey) } } } struct SwiftUIView_Previews: PreviewProvider { static var previews: some View { PreferencesView(preferences: PreferencesModel()) } }