From 41a31c23b77d73bf0f578acd77607fe39d1661cb Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 24 Jan 2022 22:49:18 -0500 Subject: [PATCH] Allow posting local-only from Glitch instances See #130 --- Pachyderm/Client.swift | 2 +- Pachyderm/Utilities/InstanceType.swift | 24 ---------- Tusker.xcodeproj/project.pbxproj | 4 -- Tusker/InstanceFeatures.swift | 47 ++++++++++++++++++- Tusker/Models/Draft.swift | 19 +++++--- .../Compose/ComposeAttachmentsList.swift | 16 ++----- .../Compose/ComposeHostingController.swift | 14 +++--- Tusker/Screens/Compose/ComposeView.swift | 4 +- .../Explore/ExploreViewController.swift | 4 +- .../Main/MainSidebarViewController.swift | 4 +- .../Status/BaseStatusTableViewCell.swift | 11 ++--- 11 files changed, 79 insertions(+), 70 deletions(-) delete mode 100644 Pachyderm/Utilities/InstanceType.swift diff --git a/Pachyderm/Client.swift b/Pachyderm/Client.swift index 1c50d16f..8218cfe1 100644 --- a/Pachyderm/Client.swift +++ b/Pachyderm/Client.swift @@ -336,7 +336,7 @@ public class Client { pollOptions: [String]? = nil, pollExpiresIn: Int? = nil, pollMultiple: Bool? = nil, - localOnly: Bool? = nil) -> Request { + localOnly: Bool? = nil /* hometown only, not glitch */) -> Request { return Request(method: .post, path: "/api/v1/statuses", body: ParametersBody([ "status" => text, "content_type" => contentType.mimeType, diff --git a/Pachyderm/Utilities/InstanceType.swift b/Pachyderm/Utilities/InstanceType.swift deleted file mode 100644 index 9a8ad33f..00000000 --- a/Pachyderm/Utilities/InstanceType.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// InstanceType.swift -// Pachyderm -// -// Created by Shadowfacts on 9/11/19. -// Copyright © 2019 Shadowfacts. All rights reserved. -// - -import Foundation - -public enum InstanceType { - case mastodon, pleroma -} - -public extension Instance { - var instanceType: InstanceType { - let lowercased = version.lowercased() - if lowercased.contains("pleroma") { - return .pleroma - } else { - return .mastodon - } - } -} diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index bce06545..64392e6a 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -176,7 +176,6 @@ D663626421360D2300C9CBA2 /* AvatarStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626321360D2300C9CBA2 /* AvatarStyle.swift */; }; D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D663626B21361C6700C9CBA2 /* Account+Preferences.swift */; }; D66362752137068A00C9CBA2 /* Visibility+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */; }; - D667383C23299340000A2373 /* InstanceType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667383B23299340000A2373 /* InstanceType.swift */; }; D6674AEA23341F7600E8DF94 /* AppShortcutItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */; }; D667E5E12134937B0057A976 /* TimelineStatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */; }; D667E5F12134D5050057A976 /* UIViewController+Delegates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D667E5F02134D5050057A976 /* UIViewController+Delegates.swift */; }; @@ -589,7 +588,6 @@ D663626321360D2300C9CBA2 /* AvatarStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AvatarStyle.swift; sourceTree = ""; }; D663626B21361C6700C9CBA2 /* Account+Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+Preferences.swift"; sourceTree = ""; }; D66362742137068A00C9CBA2 /* Visibility+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Visibility+Helpers.swift"; sourceTree = ""; }; - D667383B23299340000A2373 /* InstanceType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceType.swift; sourceTree = ""; }; D6674AE923341F7600E8DF94 /* AppShortcutItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppShortcutItems.swift; sourceTree = ""; }; D667E5E02134937B0057A976 /* TimelineStatusTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = TimelineStatusTableViewCell.xib; sourceTree = ""; }; D667E5F02134D5050057A976 /* UIViewController+Delegates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Delegates.swift"; sourceTree = ""; }; @@ -1374,7 +1372,6 @@ D6E6F26221603F8B006A8599 /* CharacterCounter.swift */, D6A3BC7323218C6E00FD64D5 /* TimelineSegment.swift */, D6A3BC7823218E9200FD64D5 /* NotificationGroup.swift */, - D667383B23299340000A2373 /* InstanceType.swift */, D61AC1D2232E928600C54D2D /* InstanceSelector.swift */, ); path = Utilities; @@ -2007,7 +2004,6 @@ buildActionMask = 2147483647; files = ( D61099E5214561AB00432DC2 /* Application.swift in Sources */, - D667383C23299340000A2373 /* InstanceType.swift in Sources */, D62E9983279C69D400C26176 /* WellKnown.swift in Sources */, D61099FF21456A4C00432DC2 /* Status.swift in Sources */, D61099E32144C38900432DC2 /* Emoji.swift in Sources */, diff --git a/Tusker/InstanceFeatures.swift b/Tusker/InstanceFeatures.swift index 21dad119..07f0d6da 100644 --- a/Tusker/InstanceFeatures.swift +++ b/Tusker/InstanceFeatures.swift @@ -10,11 +10,54 @@ import Foundation import Pachyderm struct InstanceFeatures { + private(set) var instanceType = InstanceType.mastodon private(set) var maxStatusChars = 500 - private(set) var localOnlyPosts = false + + var localOnlyPosts: Bool { + instanceType == .hometown || instanceType == .glitch + } + + var mastodonAttachmentRestrictions: Bool { + instanceType.isMastodon + } + + var pollsAndAttachments: Bool { + instanceType == .pleroma + } + + var boostToOriginalAudience: Bool { + instanceType == .pleroma + } mutating func update(instance: Instance, nodeInfo: NodeInfo?) { + if instance.version.contains("glitch") { + instanceType = .glitch + } else if nodeInfo?.software.name == "hometown" { + instanceType = .hometown + } else if instance.version.contains("pleroma") { + instanceType = .pleroma + } else { + instanceType = .mastodon + } + maxStatusChars = instance.maxStatusCharacters ?? 500 - localOnlyPosts = nodeInfo?.software.name == "hometown" + } +} + +extension InstanceFeatures { + enum InstanceType: Equatable { + case mastodon // vanilla + case pleroma + case hometown + case glitch + + var isMastodon: Bool { + switch self { + case .mastodon, .hometown, .glitch: + return true + default: + return false + } + } } } diff --git a/Tusker/Models/Draft.swift b/Tusker/Models/Draft.swift index 3e5ce793..b489f644 100644 --- a/Tusker/Models/Draft.swift +++ b/Tusker/Models/Draft.swift @@ -32,12 +32,6 @@ class Draft: Codable, ObservableObject { poll?.hasContent == true } - var textForPosting: String { - // when using dictation, iOS sometimes leaves a U+FFFC OBJECT REPLACEMENT CHARACTER behind in the text, - // which we want to strip out before actually posting the status - text.replacingOccurrences(of: "\u{fffc}", with: "") - } - init(accountID: String) { self.id = UUID() self.lastModified = Date() @@ -92,6 +86,19 @@ class Draft: Codable, ObservableObject { try container.encode(initialText, forKey: .initialText) } + + func textForPosting(on instance: InstanceFeatures) -> String { + var text = self.text + // when using dictation, iOS sometimes leaves a U+FFFC OBJECT REPLACEMENT CHARACTER behind in the text, + // which we want to strip out before actually posting the status + text = text.replacingOccurrences(of: "\u{fffc}", with: "") + + if localOnly && instance.instanceType == .glitch { + text += " 👁" + } + + return text + } } extension Draft: Equatable { diff --git a/Tusker/Screens/Compose/ComposeAttachmentsList.swift b/Tusker/Screens/Compose/ComposeAttachmentsList.swift index 2acdb3c1..084ecadc 100644 --- a/Tusker/Screens/Compose/ComposeAttachmentsList.swift +++ b/Tusker/Screens/Compose/ComposeAttachmentsList.swift @@ -87,23 +87,17 @@ struct ComposeAttachmentsList: View { } private var canAddAttachment: Bool { - switch mastodonController.instance?.instanceType { - case nil: - return false - case .pleroma: - return true - case .mastodon: + if mastodonController.instanceFeatures.mastodonAttachmentRestrictions { return draft.attachments.count < 4 && draft.attachments.allSatisfy { $0.data.type == .image } && draft.poll == nil + } else { + return true } } private var canAddPoll: Bool { - switch mastodonController.instance?.instanceType { - case nil: - return false - case .pleroma: + if mastodonController.instanceFeatures.pollsAndAttachments { return true - case .mastodon: + } else { return draft.attachments.isEmpty } } diff --git a/Tusker/Screens/Compose/ComposeHostingController.swift b/Tusker/Screens/Compose/ComposeHostingController.swift index 2599263b..f73492e7 100644 --- a/Tusker/Screens/Compose/ComposeHostingController.swift +++ b/Tusker/Screens/Compose/ComposeHostingController.swift @@ -234,13 +234,12 @@ class ComposeHostingController: UIHostingController { override func canPaste(_ itemProviders: [NSItemProvider]) -> Bool { guard itemProviders.allSatisfy({ $0.canLoadObject(ofClass: CompositionAttachment.self) }) else { return false } - switch mastodonController.instance.instanceType { - case .pleroma: - return true - case .mastodon: + if mastodonController.instanceFeatures.mastodonAttachmentRestrictions { guard draft.attachments.allSatisfy({ $0.data.type == .image }) else { return false } // todo: if providers are videos, this technically allows invalid video/image combinations return itemProviders.count + draft.attachments.count <= 4 + } else { + return true } } @@ -321,16 +320,15 @@ extension ComposeHostingController: ComposeUIStateDelegate { extension ComposeHostingController: AssetPickerViewControllerDelegate { func assetPicker(_ assetPicker: AssetPickerViewController, shouldAllowAssetOfType type: CompositionAttachmentData.AttachmentType) -> Bool { - switch mastodonController.instance.instanceType { - case .pleroma: - return true - case .mastodon: + if mastodonController.instanceFeatures.mastodonAttachmentRestrictions { if (type == .video && draft.attachments.count > 0) || draft.attachments.contains(where: { $0.data.type == .video }) || assetPicker.currentCollectionSelectedAssets.contains(where: { $0.type == .video }) { return false } return draft.attachments.count + assetPicker.currentCollectionSelectedAssets.count < 4 + } else { + return true } } diff --git a/Tusker/Screens/Compose/ComposeView.swift b/Tusker/Screens/Compose/ComposeView.swift index 38030040..07f96b24 100644 --- a/Tusker/Screens/Compose/ComposeView.swift +++ b/Tusker/Screens/Compose/ComposeView.swift @@ -209,7 +209,7 @@ struct ComposeView: View { self.isPosting = false case let .success(uploadedAttachments): - let request = Client.createStatus(text: draft.textForPosting, + let request = Client.createStatus(text: draft.textForPosting(on: mastodonController.instanceFeatures), contentType: Preferences.shared.statusContentType, inReplyTo: draft.inReplyToID, media: uploadedAttachments, @@ -220,7 +220,7 @@ struct ComposeView: View { pollOptions: draft.poll?.options.map(\.text), pollExpiresIn: draft.poll == nil ? nil : Int(draft.poll!.duration), pollMultiple: draft.poll?.multiple, - localOnly: mastodonController.instanceFeatures.localOnlyPosts ? draft.localOnly : nil) + localOnly: mastodonController.instanceFeatures.instanceType == .hometown ? draft.localOnly : nil) self.mastodonController.run(request) { (response) in switch response { case let .failure(error): diff --git a/Tusker/Screens/Explore/ExploreViewController.swift b/Tusker/Screens/Explore/ExploreViewController.swift index 66c5de3b..0a74e2db 100644 --- a/Tusker/Screens/Explore/ExploreViewController.swift +++ b/Tusker/Screens/Explore/ExploreViewController.swift @@ -138,7 +138,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate { var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections(Section.allCases.filter { $0 != .discover }) snapshot.appendItems([.bookmarks], toSection: .bookmarks) - if case .mastodon = mastodonController.instance?.instanceType { + if mastodonController.instanceFeatures.instanceType.isMastodon { snapshot.insertSections([.discover], afterSection: .bookmarks) snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover) } @@ -154,7 +154,7 @@ class ExploreViewController: UIViewController, UICollectionViewDelegate { private func ownInstanceLoaded(_ instance: Instance) { var snapshot = self.dataSource.snapshot() - if case .mastodon = instance.instanceType { + if mastodonController.instanceFeatures.instanceType.isMastodon { snapshot.insertSections([.discover], afterSection: .bookmarks) snapshot.appendItems([.trendingTags, .profileDirectory], toSection: .discover) } diff --git a/Tusker/Screens/Main/MainSidebarViewController.swift b/Tusker/Screens/Main/MainSidebarViewController.swift index 8cf56eaa..6aceb87c 100644 --- a/Tusker/Screens/Main/MainSidebarViewController.swift +++ b/Tusker/Screens/Main/MainSidebarViewController.swift @@ -145,7 +145,7 @@ class MainSidebarViewController: UIViewController { snapshot.appendItems([ .tab(.compose) ], toSection: .compose) - if case .mastodon = mastodonController.instance?.instanceType { + if mastodonController.instanceFeatures.instanceType.isMastodon { snapshot.insertSections([.discover], afterSection: .compose) snapshot.appendItems([ .trendingTags, @@ -161,7 +161,7 @@ class MainSidebarViewController: UIViewController { private func ownInstanceLoaded(_ instance: Instance) { var snapshot = self.dataSource.snapshot() - if case .mastodon = mastodonController.instance?.instanceType { + if mastodonController.instanceFeatures.instanceType.isMastodon { snapshot.insertSections([.discover], afterSection: .compose) snapshot.appendItems([ .trendingTags, diff --git a/Tusker/Views/Status/BaseStatusTableViewCell.swift b/Tusker/Views/Status/BaseStatusTableViewCell.swift index 088c6f75..0720fca1 100644 --- a/Tusker/Views/Status/BaseStatusTableViewCell.swift +++ b/Tusker/Views/Status/BaseStatusTableViewCell.swift @@ -166,16 +166,11 @@ class BaseStatusTableViewCell: UITableViewCell, MenuPreviewProvider { } let reblogDisabled: Bool - switch mastodonController.instance?.instanceType { - case nil: - // todo: this handle a race condition in instance public timelines - // a somewhat better solution would be waiting to load the timeline until after the instance is loaded - reblogDisabled = true - case .mastodon: - reblogDisabled = status.visibility == .private || status.visibility == .direct - case .pleroma: + if mastodonController.instanceFeatures.boostToOriginalAudience { // Pleroma allows 'Boost to original audience' for your own private posts reblogDisabled = status.visibility == .direct || (status.visibility == .private && status.account.id != mastodonController.account.id) + } else { + reblogDisabled = status.visibility == .private || status.visibility == .direct } reblogButton.isEnabled = !reblogDisabled && mastodonController.loggedIn