Start rewriting preferences in SwiftUI

This commit is contained in:
Shadowfacts 2019-06-13 17:53:17 -07:00
parent b478f6e982
commit f2e3870850
Signed by: shadowfacts
GPG Key ID: 83FB3304046BADA4
12 changed files with 367 additions and 21 deletions

View File

@ -7,8 +7,14 @@
objects = {
/* Begin PBXBuildFile section */
0427033622B30B3D000D31B6 /* Preference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0427033522B30B3D000D31B6 /* Preference.swift */; };
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0427033722B30F5F000D31B6 /* BehaviorPrefsView.swift */; };
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0427033922B31269000D31B6 /* AdvancedPrefsView.swift */; };
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0427037B22B316B9000D31B6 /* SilentActionPrefs.swift */; };
04496BD721625361001F1B23 /* ContentLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04496BD621625361001F1B23 /* ContentLabel.swift */; };
0450531F22B0097E00100BA2 /* Timline+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450531E22B0097E00100BA2 /* Timline+UI.swift */; };
04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04586B4022B2FFB10021BD04 /* PreferencesView.swift */; };
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04586B4222B301470021BD04 /* AppearancePrefsView.swift */; };
0461A3902163CBAE00C0A807 /* Cache.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0461A38F2163CBAE00C0A807 /* Cache.framework */; };
0461A3912163CBAE00C0A807 /* Cache.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 0461A38F2163CBAE00C0A807 /* Cache.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */; };
@ -235,8 +241,14 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0427033522B30B3D000D31B6 /* Preference.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preference.swift; sourceTree = "<group>"; };
0427033722B30F5F000D31B6 /* BehaviorPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BehaviorPrefsView.swift; sourceTree = "<group>"; };
0427033922B31269000D31B6 /* AdvancedPrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdvancedPrefsView.swift; sourceTree = "<group>"; };
0427037B22B316B9000D31B6 /* SilentActionPrefs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SilentActionPrefs.swift; sourceTree = "<group>"; };
04496BD621625361001F1B23 /* ContentLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentLabel.swift; sourceTree = "<group>"; };
0450531E22B0097E00100BA2 /* Timline+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Timline+UI.swift"; sourceTree = "<group>"; };
04586B4022B2FFB10021BD04 /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = "<group>"; };
04586B4222B301470021BD04 /* AppearancePrefsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearancePrefsView.swift; sourceTree = "<group>"; };
0461A38F2163CBAE00C0A807 /* Cache.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Cache.framework; sourceTree = BUILT_PRODUCTS_DIR; };
04DACE8B212CB14B009840C4 /* MainTabBarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainTabBarViewController.swift; sourceTree = "<group>"; };
04DACE8D212CC7CC009840C4 /* ImageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = "<group>"; };
@ -702,6 +714,11 @@
D67E0512216438A7000E0927 /* AppearanceTableViewController.swift */,
D627FF80217FE8F400CC0648 /* BehaviorTableViewController.swift */,
D6C693C92161253F007D6A6D /* SilentActionPermissionsTableViewController.swift */,
04586B4022B2FFB10021BD04 /* PreferencesView.swift */,
04586B4222B301470021BD04 /* AppearancePrefsView.swift */,
0427033722B30F5F000D31B6 /* BehaviorPrefsView.swift */,
0427033922B31269000D31B6 /* AdvancedPrefsView.swift */,
0427037B22B316B9000D31B6 /* SilentActionPrefs.swift */,
);
path = Preferences;
sourceTree = "<group>";
@ -760,6 +777,7 @@
D663626121360B1900C9CBA2 /* Preferences.swift */,
D663626321360D2300C9CBA2 /* AvatarStyle.swift */,
D66362692136163000C9CBA2 /* PreferencesAdaptive.swift */,
0427033522B30B3D000D31B6 /* Preference.swift */,
);
path = Preferences;
sourceTree = "<group>";
@ -1353,6 +1371,7 @@
buildActionMask = 2147483647;
files = (
D6285B5321EA708700FE4B39 /* StatusFormat.swift in Sources */,
0427033A22B31269000D31B6 /* AdvancedPrefsView.swift in Sources */,
D6757A822157E8FA00721E32 /* XCBSession.swift in Sources */,
04DACE8C212CB14B009840C4 /* MainTabBarViewController.swift in Sources */,
D6C693F92162E4DB007D6A6D /* StatusContentLabel.swift in Sources */,
@ -1381,11 +1400,13 @@
D6538945214D6D7500E3CEFC /* TableViewSwipeActionProvider.swift in Sources */,
D6E0DC8E216EDF1E00369478 /* Previewing.swift in Sources */,
D6BED174212667E900F02DA0 /* StatusTableViewCell.swift in Sources */,
0427033822B30F5F000D31B6 /* BehaviorPrefsView.swift in Sources */,
D64D0AAD2128D88B005A6F37 /* LocalData.swift in Sources */,
D6C94D892139E6EC00CB5196 /* AttachmentView.swift in Sources */,
D6C693EF216192C2007D6A6D /* TuskerNavigationDelegate.swift in Sources */,
D6C94D872139E62700CB5196 /* LargeImageViewController.swift in Sources */,
D6434EB3215B1856001A919A /* XCBRequest.swift in Sources */,
0427033622B30B3D000D31B6 /* Preference.swift in Sources */,
D663626221360B1900C9CBA2 /* Preferences.swift in Sources */,
D6333B792139AEFD00CE884A /* Date+TimeAgo.swift in Sources */,
D641C77F213DC78A004B4513 /* InlineTextAttachment.swift in Sources */,
@ -1401,12 +1422,14 @@
D627FF76217E923E00CC0648 /* DraftsManager.swift in Sources */,
D663626821360E2C00C9CBA2 /* PreferencesTableViewController.swift in Sources */,
D64F80E2215875CC00BEF393 /* XCBActionType.swift in Sources */,
04586B4322B301470021BD04 /* AppearancePrefsView.swift in Sources */,
D67C57AF21E28EAD00C3118B /* Array+Uniques.swift in Sources */,
D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */,
D67E0513216438A7000E0927 /* AppearanceTableViewController.swift in Sources */,
D6C693FC2162FE6F007D6A6D /* LoadingViewController.swift in Sources */,
D646C95A213B5D0500269FB5 /* LargeImageInteractionController.swift in Sources */,
D6F953EC212519E700CF0F2B /* TimelineTableViewController.swift in Sources */,
04586B4122B2FFB10021BD04 /* PreferencesView.swift in Sources */,
D663626A2136163000C9CBA2 /* PreferencesAdaptive.swift in Sources */,
D667E5EB21349EF80057A976 /* ProfileHeaderTableViewCell.swift in Sources */,
D641C77D213CB024004B4513 /* FollowNotificationTableViewCell.swift in Sources */,
@ -1414,6 +1437,7 @@
D6757A7C2157E01900721E32 /* XCBManager.swift in Sources */,
D6C693CF216125FC007D6A6D /* SilentActionPermissionTableViewCell.swift in Sources */,
D6F1F84D2193B56E00F5FE67 /* Cache.swift in Sources */,
0427037C22B316B9000D31B6 /* SilentActionPrefs.swift in Sources */,
D6757A7E2157E02600721E32 /* XCBRequestSpec.swift in Sources */,
D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */,
D6BC8748219738E1006163F1 /* EnhancedTableViewController.swift in Sources */,

View File

@ -24,19 +24,17 @@ extension Status.Visibility {
}
}
var image: UIImage {
let name: String
var imageName: String {
switch self {
case .public:
name = "globe"
return "globe"
case .unlisted:
name = "lock.open.fill"
return "lock.open.fill"
case .private:
name = "lock.fill"
return "lock.fill"
case .direct:
name = "envelope.fill"
return "envelope.fill"
}
return UIImage(systemName: name)!
}
}

View File

@ -0,0 +1,60 @@
// Preference.swift
// Tusker
//
// Created by Shadowfacts on 6/13/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import SwiftUI
@propertyWrapper
struct Preference<Value>: BindingConvertible {
let path: WritableKeyPath<Preferences, Value>
let binding: Binding<Value>
init(_ path: WritableKeyPath<Preferences, Value>) {
self.path = path
self.binding = Binding(getValue: {
return Preferences.shared[keyPath: path]
}, setValue: { (newValue) in
Preferences.shared[keyPath: path] = newValue
})
}
var value: Value {
get {
return Preferences.shared[keyPath: path]
}
set {
Preferences.shared[keyPath: path] = newValue
}
}
}
@propertyWrapper
struct MappedPreference<Value, PrefValue>: BindingConvertible {
let path: WritableKeyPath<Preferences, PrefValue>
let fromPref: (PrefValue) -> Value
let toPref: (Value) -> PrefValue
let binding: Binding<Value>
init(_ path: WritableKeyPath<Preferences, PrefValue>, fromPref: @escaping (PrefValue) -> Value, toPref: @escaping (Value) -> PrefValue) {
self.path = path
self.fromPref = fromPref
self.toPref = toPref
self.binding = Binding(getValue: {
return fromPref(Preferences.shared[keyPath: path])
}, setValue: { (newValue) in
Preferences.shared[keyPath: path] = toPref(newValue)
})
}
var value: Value {
get {
return fromPref(Preferences.shared[keyPath: path])
}
set {
Preferences.shared[keyPath: path] = toPref(newValue)
}
}
}

View File

@ -8,10 +8,12 @@
import Foundation
import Pachyderm
import SwiftUI
import Combine
class Preferences: Codable {
class Preferences: Codable, BindableObject {
private(set) static var shared: Preferences = load()
static var shared: Preferences = load()
private static var documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
private static var archiveURL = Preferences.documentsDirectory.appendingPathComponent("preferences").appendingPathExtension("plist")
@ -33,22 +35,34 @@ class Preferences: Codable {
private init() {}
let didChange = PassthroughSubject<Preferences, Never>()
// MARK: - Appearance
var showRepliesInProfiles = false
var avatarStyle = AvatarStyle.roundRect
var hideCustomEmojiInUsernames = false
var showRepliesInProfiles = false { didSet { didChange.send(self) } }
var avatarStyle = AvatarStyle.roundRect { didSet { didChange.send(self) } }
var hideCustomEmojiInUsernames = false { didSet { didChange.send(self) } }
// MARK: - Behavior
var defaultPostVisibility = Status.Visibility.public
var automaticallySaveDrafts = true
var openLinksInApps = true
var defaultPostVisibility = Status.Visibility.public { didSet { didChange.send(self) } }
var automaticallySaveDrafts = true { didSet { didChange.send(self) } }
var openLinksInApps = true { didSet { didChange.send(self) } }
// MARK: - Advanced
var silentActions: [String: Permission] = [:]
var statusContentType: StatusContentType = .plain
var silentActions: [String: Permission] = [:] { didSet { didChange.send(self) } }
var statusContentType: StatusContentType = .plain { didSet { didChange.send(self) } }
}
extension PassthroughSubject: Codable {
public convenience init(from decoder: Decoder) throws {
self.init()
}
public func encode(to encoder: Encoder) throws {
}
}
extension Preferences {
enum Permission: String, Codable {
case undecided, accepted, rejected

View File

@ -103,7 +103,7 @@ class ComposeViewController: UIViewController {
let toolbar = UIToolbar()
contentWarningBarButtonItem = UIBarButtonItem(title: "CW", style: .plain, target: self, action: #selector(contentWarningButtonPressed))
visibilityBarButtonItem = UIBarButtonItem(image: Preferences.shared.defaultPostVisibility.image, style: .plain, target: self, action: #selector(visibilityButtonPressed))
visibilityBarButtonItem = UIBarButtonItem(image: UIImage(systemName: Preferences.shared.defaultPostVisibility.imageName), style: .plain, target: self, action: #selector(visibilityButtonPressed))
toolbar.items = [
contentWarningBarButtonItem,
visibilityBarButtonItem,
@ -297,7 +297,7 @@ class ComposeViewController: UIViewController {
}
func visibilityChanged() {
visibilityBarButtonItem.image = visibility.image
visibilityBarButtonItem.image = UIImage(systemName: visibility.imageName)
}
func saveDraft() {

View File

@ -0,0 +1,41 @@
// AdvancedPrefsView.swift
// Tusker
//
// Created by Shadowfacts on 6/13/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import SwiftUI
import Pachyderm
struct AdvancedPrefsView : View {
@Preference(\.statusContentType) var statusContentType: StatusContentType
var body: some View {
List {
Section(footer: Text("This option is only supported for Pleroma and Mastodon instances with formatting enabled. On all other instances, formatting symbols will remain in the unformatted plain text.").lineLimit(nil)) {
Picker(selection: $statusContentType.binding, label: Text("Post Content Type")) {
ForEach(StatusContentType.allCases.identified(by: \.self)) { type in
Text(type.displayName).tag(type)
}
}
}
Section(header: Text("AUTOMATION")) {
NavigationButton(destination: SilentActionPrefs()) {
Text("Silent Action Permissions")
}
}
}
.listStyle(.grouped)
.navigationBarTitle(Text("Advanced"))
}
}
#if DEBUG
struct AdvancedPrefsView_Previews : PreviewProvider {
static var previews: some View {
AdvancedPrefsView()
}
}
#endif

View File

@ -0,0 +1,40 @@
// AppearancePrefsView.swift
// Tusker
//
// Created by Shadowfacts on 6/13/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import SwiftUI
struct AppearancePrefsView : View {
@Preference(\.showRepliesInProfiles) var showRepliesInProfiles: Bool
@Preference(\.hideCustomEmojiInUsernames) var hideCustomEmojiInUsernames: Bool
@MappedPreference(\.avatarStyle, fromPref: { $0 == .circle }, toPref: { $0 ? .circle : .roundRect })
var useCircularAvatars: Bool
var body: some View {
List {
Toggle(isOn: $showRepliesInProfiles.binding) {
Text("Show Replies in Profiles")
}
Toggle(isOn: $useCircularAvatars.binding) {
Text("Use Circular Avatars")
}
Toggle(isOn: $hideCustomEmojiInUsernames.binding) {
Text("Hide Custom Emoji in Usernames")
}
}
.listStyle(.grouped)
.navigationBarTitle(Text("Appearance"))
}
}
#if DEBUG
struct AppearancePrefsView_Previews : PreviewProvider {
static var previews: some View {
AppearancePrefsView()
}
}
#endif

View File

@ -0,0 +1,49 @@
// BehaviorPrefsView.swift
// Tusker
//
// Created by Shadowfacts on 6/13/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import SwiftUI
import Pachyderm
struct BehaviorPrefsView : View {
@Preference(\.defaultPostVisibility) var defaultPostVisibility: Status.Visibility
@Preference(\.automaticallySaveDrafts) var automaticallySaveDrafts: Bool
@Preference(\.openLinksInApps) var openLinksInApps: Bool
var body: some View {
List {
Section {
Picker(selection: $defaultPostVisibility.binding, label: Text("Default Post Visibility")) {
ForEach(Status.Visibility.allCases.identified(by: \.self)) { visibility in
HStack {
Image(systemName: visibility.imageName)
Text(visibility.displayName)
}
.tag(visibility)
}
}
Toggle(isOn: $automaticallySaveDrafts.binding) {
Text("Automatically Save Drafts")
}
}
Section {
Toggle(isOn: $openLinksInApps.binding) {
Text("Open Links in Apps")
}
}
}
.listStyle(.grouped)
.navigationBarTitle(Text("Behavior"))
}
}
#if DEBUG
struct BehaviorPrefsView_Previews : PreviewProvider {
static var previews: some View {
BehaviorPrefsView()
}
}
#endif

View File

@ -0,0 +1,37 @@
// PreferencesView.swift
// Tusker
//
// Created by Shadowfacts on 6/13/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import SwiftUI
struct PreferencesView : View {
var body: some View {
// workaround: the navigation view is provided by MyProfileTableViewController so that it can inject the Done button
// NavigationView {
List {
NavigationButton(destination: AppearancePrefsView()) {
Text("Appearance")
}
NavigationButton(destination: BehaviorPrefsView()) {
Text("Behavior")
}
NavigationButton(destination: AdvancedPrefsView()) {
Text("Advanced")
}
}
.listStyle(.grouped)
.navigationBarTitle(Text("Preferences"), displayMode: .inline)
// }
}
}
#if DEBUG
struct PreferencesView_Previews : PreviewProvider {
static var previews: some View {
PreferencesView()
}
}
#endif

View File

@ -0,0 +1,74 @@
// SilentActionPrefs.swift
// Tusker
//
// Created by Shadowfacts on 6/13/19.
// Copyright © 2019 Shadowfacts. All rights reserved.
//
import SwiftUI
//struct SilentActionPermission: Identifiable {
// let application: String
// let permission: Preferences.Permission
//
// var id: String {
// return application
// }
//
// init(_ application: String, _ permission: Preferences.Permission) {
// self.application = application
// self.permission = permission
// }
//}
struct SilentActionPrefs : View {
// @MappedPreference(\.silentActions, fromPref: {
// var array = [SilentActionPermission]()
// for (application, permission) in $0 {
// array.append(SilentActionPermission(application, permission))
// }
// return array
// })
// var silentActionPermissions: [SilentActionPermission]
// @Preference(\.silentActions) var silentActions: [String: Preferences.Permission]
@EnvironmentObject var preferences: Preferences
var body: some View {
List(Array(preferences.silentActions.keys).identified(by: \.self)) { source in
SilentActionPermissionCell(source: source)
}.listStyle(.grouped)
// List(Array(silentActions.keys).identified(by: \.self)) { application in
// Text(application)
//// Toggle(isOn: Binding(getValue: { self.silentActions[application] == .accepted }, setValue: { self.silentActions[application] = $0 ? .accepted : .rejected }), label: Text(application))
// }.listStyle(.grouped)
}
}
struct SilentActionPermissionCell: View {
@EnvironmentObject var preferences: Preferences
let source: String
// var binding: Binding<Bool>
init(source: String) {
self.source = source
// self.binding = Binding(getValue: { self.preferences.silentActions[source] == .accepted }, setValue: { self.preferences.silentActions[source] = $0 ? .accepted : .rejected })
}
var body: some View {
Toggle(isOn: Binding(getValue: {
self.preferences.silentActions[self.source] == .accepted
}, setValue: {
self.preferences.silentActions[self.source] = $0 ? .accepted : .rejected
})) {
Text(verbatim: source)
}
}
}
#if DEBUG
struct SilentActionPrefs_Previews : PreviewProvider {
static var previews: some View {
SilentActionPrefs().environmentObject(Preferences.shared)
}
}
#endif

View File

@ -7,6 +7,7 @@
//
import UIKit
import SwiftUI
class MyProfileTableViewController: ProfileTableViewController {
@ -50,7 +51,15 @@ class MyProfileTableViewController: ProfileTableViewController {
}
@objc func preferencesPressed() {
present(PreferencesTableViewController.create(), animated: true)
let view = PreferencesView().environmentObject(Preferences.shared)
let hostingController = UIHostingController(rootView: view)
let navigationController = UINavigationController(rootViewController: hostingController)
hostingController.navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(closePreferences))
present(navigationController, animated: true)
}
@objc func closePreferences() {
dismiss(animated: true)
}
}

View File

@ -21,7 +21,7 @@ extension UIAlertController {
if visibility == currentVisibility {
action.setValue(true, forKey: "checked")
}
action.setValue(visibility.image, forKey: "image")
action.setValue(UIImage(systemName: visibility.imageName), forKey: "image")
addAction(action)
}