Support local only posts on Hometown
This commit is contained in:
parent
072e68e97b
commit
f81935e3c3
|
@ -335,7 +335,8 @@ public class Client {
|
|||
language: String? = nil,
|
||||
pollOptions: [String]? = nil,
|
||||
pollExpiresIn: Int? = nil,
|
||||
pollMultiple: Bool? = nil) -> Request<Status> {
|
||||
pollMultiple: Bool? = nil,
|
||||
localOnly: Bool? = nil) -> Request<Status> {
|
||||
return Request<Status>(method: .post, path: "/api/v1/statuses", body: ParametersBody([
|
||||
"status" => text,
|
||||
"content_type" => contentType.mimeType,
|
||||
|
@ -346,6 +347,7 @@ public class Client {
|
|||
"language" => language,
|
||||
"poll[expires_in]" => pollExpiresIn,
|
||||
"poll[multiple]" => pollMultiple,
|
||||
"local_only" => localOnly,
|
||||
] + "media_ids" => media?.map { $0.id } + "poll[options]" => pollOptions))
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ class Draft: Codable, ObservableObject {
|
|||
@Published var inReplyToID: String?
|
||||
@Published var visibility: Status.Visibility
|
||||
@Published var poll: Poll?
|
||||
@Published var localOnly: Bool
|
||||
|
||||
var initialText: String
|
||||
|
||||
|
@ -49,6 +50,7 @@ class Draft: Codable, ObservableObject {
|
|||
self.inReplyToID = nil
|
||||
self.visibility = Preferences.shared.defaultPostVisibility
|
||||
self.poll = nil
|
||||
self.localOnly = false
|
||||
|
||||
self.initialText = ""
|
||||
}
|
||||
|
@ -67,6 +69,7 @@ class Draft: Codable, ObservableObject {
|
|||
self.inReplyToID = try container.decode(String?.self, forKey: .inReplyToID)
|
||||
self.visibility = try container.decode(Status.Visibility.self, forKey: .visibility)
|
||||
self.poll = try container.decode(Poll.self, forKey: .poll)
|
||||
self.localOnly = try container.decodeIfPresent(Bool.self, forKey: .localOnly) ?? false
|
||||
|
||||
self.initialText = try container.decode(String.self, forKey: .initialText)
|
||||
}
|
||||
|
@ -85,6 +88,7 @@ class Draft: Codable, ObservableObject {
|
|||
try container.encode(inReplyToID, forKey: .inReplyToID)
|
||||
try container.encode(visibility, forKey: .visibility)
|
||||
try container.encode(poll, forKey: .poll)
|
||||
try container.encode(localOnly, forKey: .localOnly)
|
||||
|
||||
try container.encode(initialText, forKey: .initialText)
|
||||
}
|
||||
|
@ -109,6 +113,7 @@ extension Draft {
|
|||
case inReplyToID
|
||||
case visibility
|
||||
case poll
|
||||
case localOnly
|
||||
|
||||
case initialText
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
private var mainToolbar: UIToolbar!
|
||||
private var inputAccessoryToolbar: UIToolbar!
|
||||
private var visibilityBarButtonItems = [UIBarButtonItem]()
|
||||
private var localOnlyItems = [UIBarButtonItem]()
|
||||
|
||||
override var inputAccessoryView: UIView? { inputAccessoryToolbar }
|
||||
|
||||
|
@ -54,6 +55,7 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
self.uiState.delegate = self
|
||||
|
||||
// main toolbar is shown at the bottom of the screen, the input accessory is attached to the keyboard while editing
|
||||
// (except for MainComposeTextView which has its own accessory to add formatting buttons)
|
||||
mainToolbar = createToolbar()
|
||||
inputAccessoryToolbar = createToolbar()
|
||||
|
||||
|
@ -73,6 +75,11 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
.sink(receiveValue: self.visibilityChanged)
|
||||
.store(in: &cancellables)
|
||||
|
||||
self.uiState.$draft
|
||||
.flatMap(\.$localOnly)
|
||||
.sink(receiveValue: self.localOnlyChanged)
|
||||
.store(in: &cancellables)
|
||||
|
||||
self.uiState.$draft
|
||||
.flatMap(\.objectWillChange)
|
||||
.debounce(for: .milliseconds(250), scheduler: DispatchQueue.global(qos: .utility))
|
||||
|
@ -114,7 +121,7 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
toolbar.translatesAutoresizingMaskIntoConstraints = false
|
||||
toolbar.isAccessibilityElement = true
|
||||
|
||||
let visibilityItem = UIBarButtonItem(image: UIImage(systemName: draft.visibility.imageName), style: .plain, target: nil, action: nil)
|
||||
let visibilityItem = UIBarButtonItem(image: nil, style: .plain, target: nil, action: nil)
|
||||
visibilityBarButtonItems.append(visibilityItem)
|
||||
visibilityChanged(draft.visibility)
|
||||
|
||||
|
@ -124,6 +131,14 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil),
|
||||
UIBarButtonItem(title: "Drafts", style: .plain, target: self, action: #selector(draftsButtonPresed))
|
||||
]
|
||||
|
||||
if mastodonController.instanceFeatures.localOnlyPosts {
|
||||
let item = UIBarButtonItem(image: nil, style: .plain, target: nil, action: nil)
|
||||
toolbar.items!.insert(item, at: 2)
|
||||
localOnlyItems.append(item)
|
||||
localOnlyChanged(draft.localOnly)
|
||||
}
|
||||
|
||||
return toolbar
|
||||
}
|
||||
|
||||
|
@ -185,11 +200,10 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
private func visibilityChanged(_ newVisibility: Status.Visibility) {
|
||||
for item in visibilityBarButtonItems {
|
||||
item.image = UIImage(systemName: newVisibility.imageName)
|
||||
item.image!.accessibilityLabel = String(format: NSLocalizedString("Visibility: %@", comment: "compose visiblity accessibility label"), draft.visibility.displayName)
|
||||
item.accessibilityLabel = String(format: NSLocalizedString("Visibility: %@", comment: "compose visiblity accessibility label"), draft.visibility.displayName)
|
||||
let elements = Status.Visibility.allCases.map { (visibility) -> UIMenuElement in
|
||||
let state = visibility == newVisibility ? UIMenuElement.State.on : .off
|
||||
return UIAction(title: visibility.displayName, image: UIImage(systemName: visibility.unfilledImageName), identifier: nil, discoverabilityTitle: nil, attributes: [], state: state) { (_) in
|
||||
return UIAction(title: visibility.displayName, image: UIImage(systemName: visibility.unfilledImageName), identifier: nil, discoverabilityTitle: nil, attributes: [], state: state) { [unowned self] (_) in
|
||||
self.draft.visibility = visibility
|
||||
}
|
||||
}
|
||||
|
@ -197,6 +211,27 @@ class ComposeHostingController: UIHostingController<ComposeContainerView> {
|
|||
}
|
||||
}
|
||||
|
||||
private func localOnlyChanged(_ localOnly: Bool) {
|
||||
for item in localOnlyItems {
|
||||
if localOnly {
|
||||
item.image = UIImage(named: "link.broken")
|
||||
item.accessibilityLabel = "Local-only"
|
||||
} else {
|
||||
item.image = UIImage(systemName: "link")
|
||||
item.accessibilityLabel = "Federated"
|
||||
}
|
||||
item.menu = UIMenu(children: [
|
||||
// todo: iOS 15, action subtitles
|
||||
UIAction(title: "Local-only", image: UIImage(named: "link.broken"), state: localOnly ? .on : .off) { [unowned self] (_) in
|
||||
self.draft.localOnly = true
|
||||
},
|
||||
UIAction(title: "Federated", image: UIImage(systemName: "link"), state: localOnly ? .off : .on) { [unowned self] (_) in
|
||||
self.draft.localOnly = false
|
||||
},
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
override func canPaste(_ itemProviders: [NSItemProvider]) -> Bool {
|
||||
guard itemProviders.allSatisfy({ $0.canLoadObject(ofClass: CompositionAttachment.self) }) else { return false }
|
||||
switch mastodonController.instance.instanceType {
|
||||
|
|
|
@ -219,7 +219,8 @@ struct ComposeView: View {
|
|||
language: nil,
|
||||
pollOptions: draft.poll?.options.map(\.text),
|
||||
pollExpiresIn: draft.poll == nil ? nil : Int(draft.poll!.duration),
|
||||
pollMultiple: draft.poll?.multiple)
|
||||
pollMultiple: draft.poll?.multiple,
|
||||
localOnly: mastodonController.instanceFeatures.localOnlyPosts ? draft.localOnly : nil)
|
||||
self.mastodonController.run(request) { (response) in
|
||||
switch response {
|
||||
case let .failure(error):
|
||||
|
|
|
@ -56,7 +56,10 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
|||
var textDidChange: (UITextView) -> Void
|
||||
|
||||
@EnvironmentObject var uiState: ComposeUIState
|
||||
@EnvironmentObject var mastodonController: MastodonController
|
||||
// todo: should these be part of the coordinator?
|
||||
@State var visibilityButton: UIBarButtonItem?
|
||||
@State var localOnlyButton: UIBarButtonItem?
|
||||
|
||||
func makeUIView(context: Context) -> UITextView {
|
||||
let textView = WrappedTextView()
|
||||
|
@ -87,6 +90,22 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
|||
self.visibilityButton = visibilityButton
|
||||
}
|
||||
|
||||
if mastodonController.instanceFeatures.localOnlyPosts {
|
||||
let image: UIImage
|
||||
if uiState.draft.localOnly {
|
||||
image = UIImage(named: "link.broken")!
|
||||
} else {
|
||||
image = UIImage(systemName: "link")!
|
||||
}
|
||||
let item = UIBarButtonItem(image: image, style: .plain, target: nil, action: nil)
|
||||
toolbar.items!.insert(item, at: 2)
|
||||
updateLocalOnlyMenu(item)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.localOnlyButton = item
|
||||
}
|
||||
}
|
||||
|
||||
NotificationCenter.default.addObserver(context.coordinator, selector: #selector(Coordinator.keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(context.coordinator, selector: #selector(Coordinator.keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil)
|
||||
NotificationCenter.default.addObserver(context.coordinator, selector: #selector(Coordinator.keyboardDidHide(_:)), name: UIResponder.keyboardDidHideNotification, object: nil)
|
||||
|
@ -134,6 +153,17 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
|||
visibilityButton.menu = UIMenu(title: "", image: nil, identifier: nil, options: [], children: elements)
|
||||
}
|
||||
|
||||
private func updateLocalOnlyMenu(_ localOnlyButton: UIBarButtonItem) {
|
||||
localOnlyButton.menu = UIMenu(children: [
|
||||
UIAction(title: "Local-only", image: UIImage(named: "link.broken"), state: uiState.draft.localOnly ? .on : .off) { (_) in
|
||||
self.uiState.draft.localOnly = true
|
||||
},
|
||||
UIAction(title: "Federated", image: UIImage(systemName: "link"), state: uiState.draft.localOnly ? .off : .on) { (_) in
|
||||
self.uiState.draft.localOnly = false
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: UITextView, context: Context) {
|
||||
if context.coordinator.skipSettingTextOnNextUpdate {
|
||||
context.coordinator.skipSettingTextOnNextUpdate = false
|
||||
|
@ -145,6 +175,14 @@ struct MainComposeWrappedTextView: UIViewRepresentable {
|
|||
visibilityButton.image = UIImage(systemName: visibility.imageName)
|
||||
updateVisibilityMenu(visibilityButton)
|
||||
}
|
||||
if let localOnlyButton = localOnlyButton {
|
||||
if uiState.draft.localOnly {
|
||||
localOnlyButton.image = UIImage(named: "link.broken")
|
||||
} else {
|
||||
localOnlyButton.image = UIImage(systemName: "link")
|
||||
}
|
||||
updateLocalOnlyMenu(localOnlyButton)
|
||||
}
|
||||
context.coordinator.text = $text
|
||||
context.coordinator.didChange = textDidChange
|
||||
context.coordinator.uiState = uiState
|
||||
|
|
Loading…
Reference in New Issue