Add preference to require attachment descriptions before posting

Closes #76
This commit is contained in:
Shadowfacts 2020-01-17 21:55:21 -05:00
parent 8178a1f339
commit 23de131290
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
6 changed files with 66 additions and 5 deletions

View File

@ -73,6 +73,7 @@
D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; }; D61AC1D5232E9FA600C54D2D /* InstanceSelectorTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */; };
D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; }; D61AC1D8232EA42D00C54D2D /* InstanceTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */; };
D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; }; D61AC1D9232EA42D00C54D2D /* InstanceTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */; };
D620483223D2A6A3008A63EF /* CompositionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D620483123D2A6A3008A63EF /* CompositionState.swift */; };
D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */; }; D626493323BD751600612E6E /* ShowCameraCollectionViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */; };
D626493523BD94CE00612E6E /* CompositionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachment.swift */; }; D626493523BD94CE00612E6E /* CompositionAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493423BD94CE00612E6E /* CompositionAttachment.swift */; };
D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */; }; D626493823C0FD0000612E6E /* AllPhotosTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */; };
@ -344,6 +345,7 @@
D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = "<group>"; }; D61AC1D4232E9FA600C54D2D /* InstanceSelectorTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSelectorTableViewController.swift; sourceTree = "<group>"; };
D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = "<group>"; }; D61AC1D6232EA42D00C54D2D /* InstanceTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceTableViewCell.swift; sourceTree = "<group>"; };
D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = "<group>"; }; D61AC1D7232EA42D00C54D2D /* InstanceTableViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = InstanceTableViewCell.xib; sourceTree = "<group>"; };
D620483123D2A6A3008A63EF /* CompositionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionState.swift; sourceTree = "<group>"; };
D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = "<group>"; }; D626493123BD751600612E6E /* ShowCameraCollectionViewCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShowCameraCollectionViewCell.xib; sourceTree = "<group>"; };
D626493423BD94CE00612E6E /* CompositionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachment.swift; sourceTree = "<group>"; }; D626493423BD94CE00612E6E /* CompositionAttachment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompositionAttachment.swift; sourceTree = "<group>"; };
D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = "<group>"; }; D626493623C0FD0000612E6E /* AllPhotosTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AllPhotosTableViewCell.swift; sourceTree = "<group>"; };

View File

@ -45,6 +45,7 @@ class Preferences: Codable, ObservableObject {
self.defaultPostVisibility = try container.decode(Status.Visibility.self, forKey: .defaultPostVisibility) self.defaultPostVisibility = try container.decode(Status.Visibility.self, forKey: .defaultPostVisibility)
self.automaticallySaveDrafts = try container.decode(Bool.self, forKey: .automaticallySaveDrafts) self.automaticallySaveDrafts = try container.decode(Bool.self, forKey: .automaticallySaveDrafts)
self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode) self.contentWarningCopyMode = try container.decode(ContentWarningCopyMode.self, forKey: .contentWarningCopyMode)
self.requireAttachmentDescriptions = try container.decode(Bool.self, forKey: .requireAttachmentDescriptions)
self.blurAllMedia = try container.decode(Bool.self, forKey: .blurAllMedia) self.blurAllMedia = try container.decode(Bool.self, forKey: .blurAllMedia)
self.openLinksInApps = try container.decode(Bool.self, forKey: .openLinksInApps) self.openLinksInApps = try container.decode(Bool.self, forKey: .openLinksInApps)
self.useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari) self.useInAppSafari = try container.decode(Bool.self, forKey: .useInAppSafari)
@ -68,6 +69,7 @@ class Preferences: Codable, ObservableObject {
try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility) try container.encode(defaultPostVisibility, forKey: .defaultPostVisibility)
try container.encode(automaticallySaveDrafts, forKey: .automaticallySaveDrafts) try container.encode(automaticallySaveDrafts, forKey: .automaticallySaveDrafts)
try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode) try container.encode(contentWarningCopyMode, forKey: .contentWarningCopyMode)
try container.encode(requireAttachmentDescriptions, forKey: .requireAttachmentDescriptions)
try container.encode(blurAllMedia, forKey: .blurAllMedia) try container.encode(blurAllMedia, forKey: .blurAllMedia)
try container.encode(openLinksInApps, forKey: .openLinksInApps) try container.encode(openLinksInApps, forKey: .openLinksInApps)
try container.encode(useInAppSafari, forKey: .useInAppSafari) try container.encode(useInAppSafari, forKey: .useInAppSafari)
@ -90,6 +92,7 @@ class Preferences: Codable, ObservableObject {
@Published var defaultPostVisibility = Status.Visibility.public @Published var defaultPostVisibility = Status.Visibility.public
@Published var automaticallySaveDrafts = true @Published var automaticallySaveDrafts = true
@Published var contentWarningCopyMode = ContentWarningCopyMode.asIs @Published var contentWarningCopyMode = ContentWarningCopyMode.asIs
@Published var requireAttachmentDescriptions = false
@Published var blurAllMedia = false @Published var blurAllMedia = false
@Published var openLinksInApps = true @Published var openLinksInApps = true
@Published var useInAppSafari = true @Published var useInAppSafari = true
@ -112,6 +115,7 @@ class Preferences: Codable, ObservableObject {
case defaultPostVisibility case defaultPostVisibility
case automaticallySaveDrafts case automaticallySaveDrafts
case contentWarningCopyMode case contentWarningCopyMode
case requireAttachmentDescriptions
case blurAllMedia case blurAllMedia
case openLinksInApps case openLinksInApps
case useInAppSafari case useInAppSafari

View File

@ -37,6 +37,12 @@ class ComposeViewController: UIViewController {
weak var xcbSession: XCBSession? weak var xcbSession: XCBSession?
var postedStatus: Status? var postedStatus: Status?
var compositionState: CompositionState = .valid {
didSet {
postBarButtonItem.isEnabled = compositionState.isValid
}
}
weak var postBarButtonItem: UIBarButtonItem! weak var postBarButtonItem: UIBarButtonItem!
var visibilityBarButtonItem: UIBarButtonItem! var visibilityBarButtonItem: UIBarButtonItem!
var contentWarningBarButtonItem: UIBarButtonItem! var contentWarningBarButtonItem: UIBarButtonItem!
@ -131,6 +137,7 @@ class ComposeViewController: UIViewController {
// we have to set the font here, because the monospaced digit font is not available in IB // we have to set the font here, because the monospaced digit font is not available in IB
charactersRemainingLabel.font = .monospacedDigitSystemFont(ofSize: 17, weight: .regular) charactersRemainingLabel.font = .monospacedDigitSystemFont(ofSize: 17, weight: .regular)
updateCharactersRemaining() updateCharactersRemaining()
updateAttachmentDescriptionsRequired()
updatePlaceholder() updatePlaceholder()
NotificationCenter.default.addObserver(self, selector: #selector(contentWarningTextFieldDidChange), name: UITextField.textDidChangeNotification, object: contentWarningTextField) NotificationCenter.default.addObserver(self, selector: #selector(contentWarningTextFieldDidChange), name: UITextField.textDidChangeNotification, object: contentWarningTextField)
@ -266,17 +273,29 @@ class ComposeViewController: UIViewController {
scrollView.scrollIndicatorInsets = scrollView.contentInset scrollView.scrollIndicatorInsets = scrollView.contentInset
} }
func updateAttachmentDescriptionsRequired() {
if Preferences.shared.requireAttachmentDescriptions {
for case let mediaView as ComposeMediaView in attachmentsStackView.arrangedSubviews {
if mediaView.descriptionTextView.text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
compositionState.formUnion(.requiresAttachmentDescriptions)
return
}
}
}
compositionState.subtract(.requiresAttachmentDescriptions)
}
func updateCharactersRemaining() { func updateCharactersRemaining() {
// TODO: include CW char count
let count = CharacterCounter.count(text: statusTextView.text) let count = CharacterCounter.count(text: statusTextView.text)
let cwCount = contentWarningEnabled ? (contentWarningTextField.text?.count ?? 0) : 0 let cwCount = contentWarningEnabled ? (contentWarningTextField.text?.count ?? 0) : 0
let remaining = (MastodonController.instance.maxStatusCharacters ?? 500) - count - cwCount let remaining = (MastodonController.instance.maxStatusCharacters ?? 500) - count - cwCount
if remaining < 0 { if remaining < 0 {
charactersRemainingLabel.textColor = .red charactersRemainingLabel.textColor = .red
postBarButtonItem.isEnabled = false compositionState.formUnion(.tooManyCharacters)
} else { } else {
charactersRemainingLabel.textColor = .darkGray charactersRemainingLabel.textColor = .darkGray
postBarButtonItem.isEnabled = true compositionState.subtract(.tooManyCharacters)
} }
charactersRemainingLabel.text = String(remaining) charactersRemainingLabel.text = String(remaining)
charactersRemainingLabel.accessibilityLabel = String(format: NSLocalizedString("%d characters remaining", comment: "compose characters remaining accessibility label"), remaining) charactersRemainingLabel.accessibilityLabel = String(format: NSLocalizedString("%d characters remaining", comment: "compose characters remaining accessibility label"), remaining)
@ -454,7 +473,7 @@ class ComposeViewController: UIViewController {
saveDraft() saveDraft()
// disable post button while sending post request // disable post button while sending post request
postBarButtonItem.isEnabled = false compositionState.formUnion(.currentlyPosting)
let contentWarning: String? let contentWarning: String?
if contentWarningEnabled, let cwText = contentWarningTextField.text, !cwText.isEmpty { if contentWarningEnabled, let cwText = contentWarningTextField.text, !cwText.isEmpty {
@ -575,6 +594,7 @@ extension ComposeViewController: AssetPickerViewControllerDelegate {
} }
func assetPicker(_ assetPicker: AssetPickerViewController, didSelectAttachments attachments: [CompositionAttachment]) { func assetPicker(_ assetPicker: AssetPickerViewController, didSelectAttachments attachments: [CompositionAttachment]) {
selectedAttachments.append(contentsOf: attachments) selectedAttachments.append(contentsOf: attachments)
updateAttachmentDescriptionsRequired()
} }
} }
@ -583,6 +603,11 @@ extension ComposeViewController: ComposeMediaViewDelegate {
let index = attachmentsStackView.arrangedSubviews.firstIndex(of: mediaView)! let index = attachmentsStackView.arrangedSubviews.firstIndex(of: mediaView)!
selectedAttachments.remove(at: index) selectedAttachments.remove(at: index)
updateAddAttachmentButton() updateAddAttachmentButton()
updateAttachmentDescriptionsRequired()
}
func descriptionTextViewDidChange(_ mediaView: ComposeMediaView) {
updateAttachmentDescriptionsRequired()
} }
} }
@ -631,6 +656,8 @@ extension ComposeViewController: DraftsTableViewControllerDelegate {
// call the delegate method manually, since setting the text property doesn't call it // call the delegate method manually, since setting the text property doesn't call it
mediaView.textViewDidChange(mediaView.descriptionTextView) mediaView.textViewDidChange(mediaView.descriptionTextView)
} }
updateAttachmentDescriptionsRequired()
} }
func draftSelectionCompleted() { func draftSelectionCompleted() {

View File

@ -0,0 +1,23 @@
//
// CompositionState.swift
// Tusker
//
// Created by Shadowfacts on 1/17/20.
// Copyright © 2020 Shadowfacts. All rights reserved.
//
import Foundation
struct CompositionState: OptionSet {
let rawValue: Int
static let currentlyPosting = CompositionState(rawValue: 1 << 0)
static let tooManyCharacters = CompositionState(rawValue: 1 << 1)
static let requiresAttachmentDescriptions = CompositionState(rawValue: 1 << 2)
static let valid: CompositionState = []
var isValid: Bool {
isEmpty
}
}

View File

@ -40,6 +40,9 @@ struct BehaviorPrefsView: View {
Text("Prepend 're: '").tag(ContentWarningCopyMode.prependRe) Text("Prepend 're: '").tag(ContentWarningCopyMode.prependRe)
Text("Don't copy").tag(ContentWarningCopyMode.doNotCopy) Text("Don't copy").tag(ContentWarningCopyMode.doNotCopy)
} }
Toggle(isOn: $preferences.requireAttachmentDescriptions) {
Text("Require Attachment Descriptions")
}
} }
} }

View File

@ -12,6 +12,7 @@ import AVFoundation
protocol ComposeMediaViewDelegate { protocol ComposeMediaViewDelegate {
func didRemoveMedia(_ mediaView: ComposeMediaView) func didRemoveMedia(_ mediaView: ComposeMediaView)
func descriptionTextViewDidChange(_ mediaView: ComposeMediaView)
} }
class ComposeMediaView: UIView { class ComposeMediaView: UIView {
@ -69,5 +70,6 @@ class ComposeMediaView: UIView {
extension ComposeMediaView: UITextViewDelegate { extension ComposeMediaView: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) { func textViewDidChange(_ textView: UITextView) {
placeholderLabel.isHidden = !descriptionTextView.text.isEmpty placeholderLabel.isHidden = !descriptionTextView.text.isEmpty
delegate?.descriptionTextViewDidChange(self)
} }
} }