Add preferences

This commit is contained in:
Shadowfacts 2022-01-16 11:58:28 -05:00
parent df00108dae
commit 75be4141dd
8 changed files with 235 additions and 5 deletions

View File

@ -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 */,

View File

@ -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)
}
}

View File

@ -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>

67
Reader/Preferences.swift Normal file
View File

@ -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")
}

View File

@ -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

View File

@ -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
}
}

View File

@ -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()
}
}

View File

@ -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)
}