diff --git a/Tusker/Extensions/Visibility+Helpers.swift b/Tusker/Extensions/Visibility+Helpers.swift index 445c418a..0c8d5b94 100644 --- a/Tusker/Extensions/Visibility+Helpers.swift +++ b/Tusker/Extensions/Visibility+Helpers.swift @@ -64,3 +64,18 @@ extension Status.Visibility { } } + +extension Status.Visibility: Comparable { + public static func < (lhs: Pachyderm.Status.Visibility, rhs: Pachyderm.Status.Visibility) -> Bool { + switch (lhs, rhs) { + case (.direct, .public), (.private, .public), (.unlisted, .public): + return true + case (.direct, .unlisted), (.private, .unlisted): + return true + case (.direct, .private): + return true + default: + return false + } + } +} diff --git a/Tusker/Models/Draft.swift b/Tusker/Models/Draft.swift index 0af72b28..f8af8df5 100644 --- a/Tusker/Models/Draft.swift +++ b/Tusker/Models/Draft.swift @@ -187,14 +187,14 @@ extension MastodonController { func createDraft(inReplyToID: String? = nil, mentioningAcct: String? = nil) -> Draft { var acctsToMention = [String]() - var visibility = Preferences.shared.defaultPostVisibility + var visibility = inReplyToID != nil ? Preferences.shared.defaultReplyVisibility.resolved : Preferences.shared.defaultPostVisibility var contentWarning = "" if let inReplyToID = inReplyToID, let inReplyTo = persistentContainer.status(for: inReplyToID) { acctsToMention.append(inReplyTo.account.acct) acctsToMention.append(contentsOf: inReplyTo.mentions.map(\.acct)) - visibility = inReplyTo.visibility + visibility = min(visibility, inReplyTo.visibility) if !inReplyTo.spoilerText.isEmpty { switch Preferences.shared.contentWarningCopyMode { diff --git a/Tusker/Preferences/Preferences.swift b/Tusker/Preferences/Preferences.swift index 50de575e..11984588 100644 --- a/Tusker/Preferences/Preferences.swift +++ b/Tusker/Preferences/Preferences.swift @@ -45,6 +45,7 @@ class Preferences: Codable, ObservableObject { self.hideActionsInTimeline = try container.decodeIfPresent(Bool.self, forKey: .hideActionsInTimeline) ?? false self.defaultPostVisibility = try container.decode(Status.Visibility.self, forKey: .defaultPostVisibility) + self.defaultReplyVisibility = try container.decodeIfPresent(ReplyVisibility.self, forKey: .defaultReplyVisibility) ?? .sameAsPost self.automaticallySaveDrafts = try container.decode(Bool.self, forKey: .automaticallySaveDrafts) self.requireAttachmentDescriptions = try container.decode(Bool.self, forKey: .requireAttachmentDescriptions) self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode) @@ -84,6 +85,7 @@ class Preferences: Codable, ObservableObject { try container.encode(hideActionsInTimeline, forKey: .hideActionsInTimeline) try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility) + try container.encode(defaultReplyVisibility, forKey: .defaultReplyVisibility) try container.encode(automaticallySaveDrafts, forKey: .automaticallySaveDrafts) try container.encode(requireAttachmentDescriptions, forKey: .requireAttachmentDescriptions) try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode) @@ -122,6 +124,7 @@ class Preferences: Codable, ObservableObject { // MARK: Composing @Published var defaultPostVisibility = Status.Visibility.public + @Published var defaultReplyVisibility = ReplyVisibility.sameAsPost @Published var automaticallySaveDrafts = true @Published var requireAttachmentDescriptions = false @Published var contentWarningCopyMode = ContentWarningCopyMode.asIs @@ -164,6 +167,7 @@ class Preferences: Codable, ObservableObject { case hideActionsInTimeline case defaultPostVisibility + case defaultReplyVisibility case automaticallySaveDrafts case requireAttachmentDescriptions case contentWarningCopyMode @@ -194,4 +198,40 @@ class Preferences: Codable, ObservableObject { } +extension Preferences { + enum ReplyVisibility: Codable, Hashable, CaseIterable { + case sameAsPost + case visibility(Status.Visibility) + + static var allCases: [Preferences.ReplyVisibility] = [.sameAsPost] + Status.Visibility.allCases.map { .visibility($0) } + + var resolved: Status.Visibility { + switch self { + case .sameAsPost: + return Preferences.shared.defaultPostVisibility + case .visibility(let vis): + return vis + } + } + + var displayName: String { + switch self { + case .sameAsPost: + return "Same as Default" + case .visibility(let vis): + return vis.displayName + } + } + + var imageName: String? { + switch self { + case .sameAsPost: + return nil + case .visibility(let vis): + return vis.imageName + } + } + } +} + extension UIUserInterfaceStyle: Codable {} diff --git a/Tusker/Screens/Preferences/ComposingPrefsView.swift b/Tusker/Screens/Preferences/ComposingPrefsView.swift index de875ee4..ef7f35ae 100644 --- a/Tusker/Screens/Preferences/ComposingPrefsView.swift +++ b/Tusker/Screens/Preferences/ComposingPrefsView.swift @@ -14,6 +14,7 @@ struct ComposingPrefsView: View { var body: some View { List { + visibilitySection composingSection replyingSection } @@ -21,9 +22,9 @@ struct ComposingPrefsView: View { .navigationBarTitle("Composing") } - var composingSection: some View { - Section(header: Text("Composing")) { - Picker(selection: $preferences.defaultPostVisibility, label: Text("Default Post Visibility")) { + var visibilitySection: some View { + Section { + Picker(selection: $preferences.defaultPostVisibility, label: Text("Default Visibility")) { ForEach(Status.Visibility.allCases, id: \.self) { visibility in HStack { Image(systemName: visibility.imageName) @@ -33,6 +34,26 @@ struct ComposingPrefsView: View { }//.navigationBarTitle("Default Post Visibility") // navbar title on the ForEach is currently incorrectly applied when the picker is not expanded, see FB6838291 } + Picker(selection: $preferences.defaultReplyVisibility, label: Text("Reply Visibility")) { + ForEach(Preferences.ReplyVisibility.allCases, id: \.self) { visibility in + HStack { + if let imageName = visibility.imageName { + Image(systemName: imageName) + } + Text(visibility.displayName) + } + .tag(visibility) + } + } + } header: { + Text("Visibility") + } footer: { + Text("When starting a reply, Tusker will use your preferred visibility or the visibility of the post to which you're replying, whichever is narrower.") + } + } + + var composingSection: some View { + Section(header: Text("Composing")) { Toggle(isOn: $preferences.automaticallySaveDrafts) { Text("Automatically Save Drafts") }