Add preferences
This commit is contained in:
parent
df00108dae
commit
75be4141dd
|
@ -15,6 +15,9 @@
|
|||
D65B18BC27504FE7004A9448 /* Token.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18BB27504FE7004A9448 /* Token.swift */; };
|
||||
D65B18BE275051A1004A9448 /* LocalData.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18BD275051A1004A9448 /* LocalData.swift */; };
|
||||
D65B18C127505348004A9448 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65B18C027505348004A9448 /* HomeViewController.swift */; };
|
||||
D68408E827947D0800E327D2 /* PrefsSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */; };
|
||||
D68408ED2794803D00E327D2 /* PrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68408EC2794803D00E327D2 /* PrefsView.swift */; };
|
||||
D68408EF2794808E00E327D2 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68408EE2794808E00E327D2 /* Preferences.swift */; };
|
||||
D68B303627907D9200E8B3FA /* ExcerptGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68B303527907D9200E8B3FA /* ExcerptGenerator.swift */; };
|
||||
D68B303D2792204B00E8B3FA /* read.js in Resources */ = {isa = PBXBuildFile; fileRef = D68B303C2792204B00E8B3FA /* read.js */; };
|
||||
D68B30402792729A00E8B3FA /* AppSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68B303F2792729A00E8B3FA /* AppSplitViewController.swift */; };
|
||||
|
@ -101,6 +104,9 @@
|
|||
D65B18BB27504FE7004A9448 /* Token.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Token.swift; sourceTree = "<group>"; };
|
||||
D65B18BD275051A1004A9448 /* LocalData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalData.swift; sourceTree = "<group>"; };
|
||||
D65B18C027505348004A9448 /* HomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = "<group>"; };
|
||||
D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsSceneDelegate.swift; sourceTree = "<group>"; };
|
||||
D68408EC2794803D00E327D2 /* PrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsView.swift; sourceTree = "<group>"; };
|
||||
D68408EE2794808E00E327D2 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = "<group>"; };
|
||||
D68B3032278FDD1A00E8B3FA /* Reader-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Reader-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
D68B303527907D9200E8B3FA /* ExcerptGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExcerptGenerator.swift; sourceTree = "<group>"; };
|
||||
D68B3037279099FD00E8B3FA /* liblolhtml.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = liblolhtml.a; path = "lol-html/c-api/target/aarch64-apple-ios-sim/release/liblolhtml.a"; sourceTree = "<group>"; };
|
||||
|
@ -188,6 +194,7 @@
|
|||
D65B18B027504691004A9448 /* Login */,
|
||||
D6E2434A278B455C0005E546 /* Items */,
|
||||
D6E2436C278BD80B0005E546 /* Read */,
|
||||
D68408E927947E3800E327D2 /* Preferences */,
|
||||
);
|
||||
path = Screens;
|
||||
sourceTree = "<group>";
|
||||
|
@ -209,6 +216,14 @@
|
|||
path = Home;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D68408E927947E3800E327D2 /* Preferences */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D68408EC2794803D00E327D2 /* PrefsView.swift */,
|
||||
);
|
||||
path = Preferences;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D68B302E278FDCE200E8B3FA /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -262,12 +277,14 @@
|
|||
D68B3032278FDD1A00E8B3FA /* Reader-Bridging-Header.h */,
|
||||
D6C687EB272CD27600874C10 /* AppDelegate.swift */,
|
||||
D6C687ED272CD27600874C10 /* SceneDelegate.swift */,
|
||||
D68408E727947D0800E327D2 /* PrefsSceneDelegate.swift */,
|
||||
D65B18B527504920004A9448 /* FervorController.swift */,
|
||||
D65B18BD275051A1004A9448 /* LocalData.swift */,
|
||||
D6E24368278BABB40005E546 /* UIColor+App.swift */,
|
||||
D6EB531E278E4A7500AD2E61 /* StretchyMenuInteraction.swift */,
|
||||
D68B303527907D9200E8B3FA /* ExcerptGenerator.swift */,
|
||||
D68B304127932ED500E8B3FA /* UserActivities.swift */,
|
||||
D68408EE2794808E00E327D2 /* Preferences.swift */,
|
||||
D6A8A33527766E9300CCEC72 /* CoreData */,
|
||||
D65B18AF2750468B004A9448 /* Screens */,
|
||||
D6C687F7272CD27700874C10 /* Assets.xcassets */,
|
||||
|
@ -545,6 +562,7 @@
|
|||
D6E2435F278B97240005E546 /* Group+CoreDataClass.swift in Sources */,
|
||||
D6E24369278BABB40005E546 /* UIColor+App.swift in Sources */,
|
||||
D6E2435D278B97240005E546 /* Item+CoreDataClass.swift in Sources */,
|
||||
D68408E827947D0800E327D2 /* PrefsSceneDelegate.swift in Sources */,
|
||||
D6EB531D278C89C300AD2E61 /* AppNavigationController.swift in Sources */,
|
||||
D6E24360278B97240005E546 /* Group+CoreDataProperties.swift in Sources */,
|
||||
D6E2434C278B456A0005E546 /* ItemsViewController.swift in Sources */,
|
||||
|
@ -554,6 +572,8 @@
|
|||
D68B30402792729A00E8B3FA /* AppSplitViewController.swift in Sources */,
|
||||
D65B18BE275051A1004A9448 /* LocalData.swift in Sources */,
|
||||
D65B18B22750469D004A9448 /* LoginViewController.swift in Sources */,
|
||||
D68408ED2794803D00E327D2 /* PrefsView.swift in Sources */,
|
||||
D68408EF2794808E00E327D2 /* Preferences.swift in Sources */,
|
||||
D68B303627907D9200E8B3FA /* ExcerptGenerator.swift in Sources */,
|
||||
D6E24363278BA1410005E546 /* ItemCollectionViewCell.swift in Sources */,
|
||||
D6E2436E278BD8160005E546 /* ReadViewController.swift in Sources */,
|
||||
|
|
|
@ -8,22 +8,40 @@
|
|||
import UIKit
|
||||
import WebKit
|
||||
import OSLog
|
||||
import Combine
|
||||
|
||||
@main
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||
swizzleWKWebView()
|
||||
|
||||
Preferences.shared.objectWillChange
|
||||
.debounce(for: .milliseconds(250), scheduler: RunLoop.main)
|
||||
.sink { _ in
|
||||
Preferences.save()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// MARK: UISceneSession Lifecycle
|
||||
|
||||
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
|
||||
// Called when a new scene session is being created.
|
||||
// Use this method to select a configuration to create the new scene with.
|
||||
return UISceneConfiguration(name: "main", sessionRole: connectingSceneSession.role)
|
||||
let name: String
|
||||
#if targetEnvironment(macCatalyst)
|
||||
if options.userActivities.first?.activityType == NSUserActivity.preferencesType {
|
||||
name = "prefs"
|
||||
} else {
|
||||
name = "main"
|
||||
}
|
||||
#else
|
||||
name = "main"
|
||||
#endif
|
||||
return UISceneConfiguration(name: name, sessionRole: connectingSceneSession.role)
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
|
||||
|
@ -34,7 +52,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
override func buildMenu(with builder: UIMenuBuilder) {
|
||||
if builder.system == .main {
|
||||
|
||||
builder.insertSibling(UIMenu(options: .displayInline, children: [
|
||||
UIKeyCommand(title: "Preferences…", action: #selector(showPreferences), input: ",", modifierFlags: .command)
|
||||
]), afterMenu: .about)
|
||||
|
||||
var children = [UIMenuElement]()
|
||||
let accounts: [UIMenuElement] = LocalData.accounts.map { account in
|
||||
|
@ -45,7 +65,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
let state: UIAction.State
|
||||
if let activeScene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }),
|
||||
(activeScene.delegate as! SceneDelegate).fervorController?.account?.id == account.id {
|
||||
let sceneDelegate = activeScene.delegate as? SceneDelegate,
|
||||
sceneDelegate.fervorController?.account?.id == account.id {
|
||||
state = .on
|
||||
} else {
|
||||
state = .off
|
||||
|
@ -89,6 +110,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
} as (@convention(block) (WKWebView) -> Void))
|
||||
originalIMP = class_replaceMethod(WKWebView.self, selector, imp, "v@:")
|
||||
}
|
||||
|
||||
@objc private func showPreferences() {
|
||||
UIApplication.shared.requestSceneSessionActivation(nil, userActivity: .preferences(), options: nil, errorHandler: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<dict>
|
||||
<key>NSUserActivityTypes</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.preferences</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.add-account</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).activity.activate-account</string>
|
||||
</array>
|
||||
|
@ -21,6 +22,12 @@
|
|||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).PrefsSceneDelegate</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>prefs</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// Preferences.swift
|
||||
// Reader
|
||||
//
|
||||
// Created by Shadowfacts on 1/16/22.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class Preferences: Codable, ObservableObject {
|
||||
|
||||
private static let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
private static let archiveURL = Preferences.documentsDirectory.appendingPathComponent("preferences").appendingPathExtension("plist")
|
||||
private static let decoder = PropertyListDecoder()
|
||||
private static let encoder = PropertyListEncoder()
|
||||
|
||||
static var shared = load()
|
||||
|
||||
private static func load() -> Preferences {
|
||||
if let data = try? Data(contentsOf: archiveURL),
|
||||
let prefs = try? decoder.decode(Preferences.self, from: data) {
|
||||
return prefs
|
||||
} else {
|
||||
return Preferences()
|
||||
}
|
||||
}
|
||||
|
||||
static func save() {
|
||||
if let data = try? encoder.encode(shared) {
|
||||
try? data.write(to: archiveURL, options: .noFileProtection)
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
}
|
||||
|
||||
required init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
self.appearance = try container.decode(UIUserInterfaceStyle.self, forKey: .appearance)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(appearance, forKey: .appearance)
|
||||
}
|
||||
|
||||
var appearance = UIUserInterfaceStyle.unspecified {
|
||||
willSet {
|
||||
objectWillChange.send()
|
||||
}
|
||||
didSet {
|
||||
NotificationCenter.default.post(name: .userInterfaceStyleChanged, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case appearance
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension UIUserInterfaceStyle: Codable {
|
||||
}
|
||||
|
||||
extension Notification.Name {
|
||||
static let userInterfaceStyleChanged = Notification.Name("userInterfaceStyleChanged")
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// PrefsSceneDelegate.swift
|
||||
// Reader
|
||||
//
|
||||
// Created by Shadowfacts on 1/16/22.
|
||||
//
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
|
||||
class PrefsSceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
guard let windowScene = scene as? UIWindowScene else {
|
||||
return
|
||||
}
|
||||
|
||||
window = UIWindow(windowScene: windowScene)
|
||||
window!.tintColor = .appTintColor
|
||||
windowScene.sizeRestrictions?.minimumSize = CGSize(width: 640, height: 480)
|
||||
windowScene.sizeRestrictions?.maximumSize = CGSize(width: 640, height: 480)
|
||||
|
||||
window!.rootViewController = UIHostingController(rootView: PrefsView())
|
||||
|
||||
if let titlebar = windowScene.titlebar {
|
||||
titlebar.toolbarStyle = .preference
|
||||
titlebar.toolbar = NSToolbar(identifier: .init("ReaderPrefsToolbar"))
|
||||
titlebar.toolbar!.delegate = self
|
||||
titlebar.toolbar!.allowsUserCustomization = false
|
||||
}
|
||||
|
||||
window!.makeKeyAndVisible()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUserInterfaceStyle), name: .userInterfaceStyleChanged, object: nil)
|
||||
updateUserInterfaceStyle()
|
||||
}
|
||||
|
||||
@objc private func updateUserInterfaceStyle() {
|
||||
window?.overrideUserInterfaceStyle = Preferences.shared.appearance
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension PrefsSceneDelegate: NSToolbarDelegate {
|
||||
func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
return []
|
||||
}
|
||||
|
||||
func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
|
@ -54,6 +54,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
#endif
|
||||
|
||||
window!.makeKeyAndVisible()
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(updateUserInterfaceStyle), name: .userInterfaceStyleChanged, object: nil)
|
||||
updateUserInterfaceStyle()
|
||||
}
|
||||
|
||||
func sceneDidDisconnect(_ scene: UIScene) {
|
||||
|
@ -116,6 +119,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
ExcerptGenerator.generateAll(fervorController)
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func updateUserInterfaceStyle() {
|
||||
window?.overrideUserInterfaceStyle = Preferences.shared.appearance
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
//
|
||||
// PrefsView.swift
|
||||
// Reader
|
||||
//
|
||||
// Created by Shadowfacts on 1/16/22.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PrefsView: View {
|
||||
@ObservedObject private var preferences = Preferences.shared
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
GroupBox {
|
||||
VStack {
|
||||
appearance
|
||||
}
|
||||
.padding()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
}
|
||||
}.padding()
|
||||
}
|
||||
|
||||
private var appearance: some View {
|
||||
Picker("Appearance", selection: $preferences.appearance) {
|
||||
Text("System").tag(UIUserInterfaceStyle.unspecified)
|
||||
Text("Dark").tag(UIUserInterfaceStyle.dark)
|
||||
Text("Light").tag(UIUserInterfaceStyle.light)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PrefsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PrefsView()
|
||||
}
|
||||
}
|
|
@ -9,9 +9,14 @@ import Foundation
|
|||
|
||||
extension NSUserActivity {
|
||||
|
||||
static let preferencesType = "net.shadowfacts.Reader.activity.preferences"
|
||||
static let addAccountType = "net.shadowfacts.Reader.activity.add-account"
|
||||
static let activateAccountType = "net.shadowfacts.Reader.activity.activate-account"
|
||||
|
||||
static func preferences() -> NSUserActivity {
|
||||
return NSUserActivity(activityType: preferencesType)
|
||||
}
|
||||
|
||||
static func addAccount() -> NSUserActivity {
|
||||
return NSUserActivity(activityType: addAccountType)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue