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