Store in reply to status in drafts

This commit is contained in:
Shadowfacts 2019-12-14 11:30:35 -05:00
parent 681cdb8bb5
commit 0c78af7d4f
Signed by untrusted user: shadowfacts
GPG Key ID: 94A5AB95422746E5
4 changed files with 135 additions and 62 deletions

View File

@ -39,8 +39,8 @@ class DraftsManager: Codable {
return drafts.sorted(by: { $0.lastModified > $1.lastModified }) return drafts.sorted(by: { $0.lastModified > $1.lastModified })
} }
func create(text: String, contentWarning: String?, attachments: [DraftAttachment]) -> Draft { func create(text: String, contentWarning: String?, inReplyToID: String?, attachments: [DraftAttachment]) -> Draft {
let draft = Draft(text: text, contentWarning: contentWarning, lastModified: Date(), attachments: attachments) let draft = Draft(text: text, contentWarning: contentWarning, inReplyToID: inReplyToID, attachments: attachments)
drafts.append(draft) drafts.append(draft)
return draft return draft
} }
@ -57,15 +57,17 @@ extension DraftsManager {
let id: UUID let id: UUID
private(set) var text: String private(set) var text: String
private(set) var contentWarning: String? private(set) var contentWarning: String?
private(set) var lastModified: Date
private(set) var attachments: [DraftAttachment] private(set) var attachments: [DraftAttachment]
private(set) var inReplyToID: String?
private(set) var lastModified: Date
init(text: String, contentWarning: String?, lastModified: Date, attachments: [DraftAttachment]) { init(text: String, contentWarning: String?, inReplyToID: String?, attachments: [DraftAttachment], lastModified: Date = Date()) {
self.id = UUID() self.id = UUID()
self.text = text self.text = text
self.contentWarning = contentWarning self.contentWarning = contentWarning
self.lastModified = lastModified self.inReplyToID = inReplyToID
self.attachments = attachments self.attachments = attachments
self.lastModified = lastModified
} }
func update(text: String, contentWarning: String?, attachments: [DraftAttachment]) { func update(text: String, contentWarning: String?, attachments: [DraftAttachment]) {

View File

@ -57,6 +57,8 @@ class ComposeViewController: UIViewController {
@IBOutlet weak var statusTextView: UITextView! @IBOutlet weak var statusTextView: UITextView!
@IBOutlet weak var placeholderLabel: UILabel! @IBOutlet weak var placeholderLabel: UILabel!
@IBOutlet weak var inReplyToContainer: UIView!
@IBOutlet weak var inReplyToLabel: UILabel!
@IBOutlet weak var contentWarningContainerView: UIView! @IBOutlet weak var contentWarningContainerView: UIView!
@IBOutlet weak var contentWarningTextField: UITextField! @IBOutlet weak var contentWarningTextField: UITextField!
@ -127,49 +129,7 @@ class ComposeViewController: UIViewController {
} }
} }
if let inReplyToID = inReplyToID, let inReplyTo = MastodonCache.status(for: inReplyToID) { updateInReplyTo()
visibility = inReplyTo.visibility
if Preferences.shared.contentWarningCopyMode == .doNotCopy {
contentWarningEnabled = false
contentWarningContainerView.isHidden = true
} else {
contentWarningEnabled = !inReplyTo.spoilerText.isEmpty
contentWarningContainerView.isHidden = !contentWarningEnabled
if Preferences.shared.contentWarningCopyMode == .prependRe,
!inReplyTo.spoilerText.lowercased().starts(with: "re:") {
contentWarningTextField.text = "re: \(inReplyTo.spoilerText)"
} else {
contentWarningTextField.text = inReplyTo.spoilerText
}
}
let replyView = ComposeStatusReplyView.create()
replyView.updateUI(for: inReplyTo)
stackView.insertArrangedSubview(replyView, at: 0)
self.replyView = replyView
replyAvatarImageViewTopConstraint = replyView.avatarImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8)
replyAvatarImageViewTopConstraint!.isActive = true
let replyLabelContainer = UIView()
replyLabelContainer.backgroundColor = .clear
replyLabelContainer.translatesAutoresizingMaskIntoConstraints = false
let replyLabel = UILabel()
replyLabel.translatesAutoresizingMaskIntoConstraints = false
replyLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
replyLabel.textColor = .secondaryLabel
replyLabelContainer.addSubview(replyLabel)
NSLayoutConstraint.activate([
replyLabel.leadingAnchor.constraint(equalTo: replyLabelContainer.leadingAnchor, constant: 8),
replyLabel.trailingAnchor.constraint(equalTo: replyLabelContainer.trailingAnchor, constant: -8),
replyLabel.topAnchor.constraint(equalTo: replyLabelContainer.topAnchor),
replyLabel.bottomAnchor.constraint(equalTo: replyLabelContainer.bottomAnchor)
])
stackView.insertArrangedSubview(replyLabelContainer, at: 1)
}
// 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)
@ -177,13 +137,65 @@ class ComposeViewController: UIViewController {
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)
}
if inReplyToID == nil { func updateInReplyTo() {
if let replyView = replyView {
replyView.removeFromSuperview()
}
if let inReplyToID = inReplyToID {
if let status = MastodonCache.status(for: inReplyToID) {
updateInReplyTo(inReplyTo: status)
} else {
let loadingVC = LoadingViewController()
embedChild(loadingVC)
MastodonCache.status(for: inReplyToID) { (status) in
guard let status = status else { return }
DispatchQueue.main.async {
self.updateInReplyTo(inReplyTo: status)
loadingVC.removeViewAndController()
}
}
}
} else {
visibility = Preferences.shared.defaultPostVisibility visibility = Preferences.shared.defaultPostVisibility
contentWarningEnabled = false contentWarningEnabled = false
inReplyToContainer.isHidden = true
} }
} }
func updateInReplyTo(inReplyTo: Status) {
visibility = inReplyTo.visibility
if Preferences.shared.contentWarningCopyMode == .doNotCopy {
contentWarningEnabled = false
contentWarningContainerView.isHidden = true
} else {
contentWarningEnabled = !inReplyTo.spoilerText.isEmpty
contentWarningContainerView.isHidden = !contentWarningEnabled
if Preferences.shared.contentWarningCopyMode == .prependRe,
!inReplyTo.spoilerText.lowercased().starts(with: "re:") {
contentWarningTextField.text = "re: \(inReplyTo.spoilerText)"
} else {
contentWarningTextField.text = inReplyTo.spoilerText
}
}
let replyView = ComposeStatusReplyView.create()
replyView.updateUI(for: inReplyTo)
stackView.insertArrangedSubview(replyView, at: 0)
self.replyView = replyView
replyAvatarImageViewTopConstraint = replyView.avatarImageView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8)
replyAvatarImageViewTopConstraint!.isActive = true
inReplyToContainer.isHidden = false
inReplyToLabel.text = "In reply to \(inReplyTo.account.realDisplayName)"
}
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(adjustForKeyboard), name: UIResponder.keyboardWillChangeFrameNotification, object: nil)
@ -338,7 +350,7 @@ class ComposeViewController: UIViewController {
if let currentDraft = self.currentDraft { if let currentDraft = self.currentDraft {
currentDraft.update(text: self.statusTextView.text, contentWarning: cw, attachments: attachments) currentDraft.update(text: self.statusTextView.text, contentWarning: cw, attachments: attachments)
} else { } else {
self.currentDraft = DraftsManager.shared.create(text: self.statusTextView.text, contentWarning: cw, attachments: attachments) self.currentDraft = DraftsManager.shared.create(text: self.statusTextView.text, contentWarning: cw, inReplyToID: inReplyToID, attachments: attachments)
} }
DraftsManager.save() DraftsManager.save()
} }
@ -598,9 +610,30 @@ extension ComposeViewController: DraftsTableViewControllerDelegate {
func draftSelectionCanceled() { func draftSelectionCanceled() {
} }
func shouldSelectDraft(_ draft: DraftsManager.Draft, completion: @escaping (Bool) -> Void) {
if draft.inReplyToID != self.inReplyToID {
// todo: better text for this
let alertController = UIAlertController(title: "Different Reply", message: "The selected draft is a reply to a different status, do you wish to use it?", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (_) in
completion(false)
}))
alertController.addAction(UIAlertAction(title: "Restore Draft", style: .default, handler: { (_) in
completion(true)
}))
// we can't present the alert ourselves, since the compose VC is already presenting the draft selector
// but presenting on the presented view controller seems hacky, is there a better way to do this?
presentedViewController!.present(alertController, animated: true)
} else {
completion(true)
}
}
func draftSelected(_ draft: DraftsManager.Draft) { func draftSelected(_ draft: DraftsManager.Draft) {
self.currentDraft = draft self.currentDraft = draft
inReplyToID = draft.inReplyToID
updateInReplyTo()
statusTextView.text = draft.text statusTextView.text = draft.text
contentWarningEnabled = draft.contentWarning != nil contentWarningEnabled = draft.contentWarning != nil
contentWarningTextField.text = draft.contentWarning contentWarningTextField.text = draft.contentWarning

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/> <device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/> <capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies> </dependencies>
@ -15,6 +15,8 @@
<outlet property="contentView" destination="pcX-rB-RxJ" id="o95-Qa-6N7"/> <outlet property="contentView" destination="pcX-rB-RxJ" id="o95-Qa-6N7"/>
<outlet property="contentWarningContainerView" destination="kU2-7l-MSy" id="Gnq-Jb-kCA"/> <outlet property="contentWarningContainerView" destination="kU2-7l-MSy" id="Gnq-Jb-kCA"/>
<outlet property="contentWarningTextField" destination="T05-p6-vTz" id="Ivu-Ll-ByO"/> <outlet property="contentWarningTextField" destination="T05-p6-vTz" id="Ivu-Ll-ByO"/>
<outlet property="inReplyToContainer" destination="2Dv-Q7-UEA" id="hfG-5j-G5R"/>
<outlet property="inReplyToLabel" destination="Y25-eP-tDE" id="9Ei-3s-dAx"/>
<outlet property="placeholderLabel" destination="EW3-YK-vPC" id="Rsw-Nv-TNz"/> <outlet property="placeholderLabel" destination="EW3-YK-vPC" id="Rsw-Nv-TNz"/>
<outlet property="postProgressView" destination="Tq7-6P-hMT" id="amT-F1-JI0"/> <outlet property="postProgressView" destination="Tq7-6P-hMT" id="amT-F1-JI0"/>
<outlet property="scrollView" destination="6Z0-Vy-hMX" id="ya0-2T-QaV"/> <outlet property="scrollView" destination="6Z0-Vy-hMX" id="ya0-2T-QaV"/>
@ -33,10 +35,10 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<subviews> <subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pcX-rB-RxJ"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pcX-rB-RxJ">
<rect key="frame" x="0.0" y="0.0" width="375" height="342"/> <rect key="frame" x="0.0" y="0.0" width="375" height="371.5"/>
<subviews> <subviews>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="bOB-hF-O9w"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="bOB-hF-O9w">
<rect key="frame" x="0.0" y="0.0" width="375" height="342"/> <rect key="frame" x="0.0" y="0.0" width="375" height="371.5"/>
<subviews> <subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6V0-mH-Mhu"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6V0-mH-Mhu">
<rect key="frame" x="0.0" y="0.0" width="375" height="66"/> <rect key="frame" x="0.0" y="0.0" width="375" height="66"/>
@ -63,11 +65,32 @@
<constraint firstItem="PMB-Wa-Ht0" firstAttribute="leading" secondItem="zZ3-Gv-4P5" secondAttribute="trailing" id="sVv-tH-7eB"/> <constraint firstItem="PMB-Wa-Ht0" firstAttribute="leading" secondItem="zZ3-Gv-4P5" secondAttribute="trailing" id="sVv-tH-7eB"/>
</constraints> </constraints>
</view> </view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="2Dv-Q7-UEA">
<rect key="frame" x="0.0" y="66" width="375" height="33.5"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="In reply to Display Name" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Y25-eP-tDE">
<rect key="frame" x="4" y="8" width="367" height="21.5"/>
<constraints>
<constraint firstAttribute="height" constant="21.5" id="man-Xn-eVt"/>
</constraints>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="Y25-eP-tDE" secondAttribute="bottom" constant="4" id="1sZ-CX-GDU"/>
<constraint firstAttribute="trailing" secondItem="Y25-eP-tDE" secondAttribute="trailing" constant="4" id="I31-Rs-QwW"/>
<constraint firstItem="Y25-eP-tDE" firstAttribute="leading" secondItem="2Dv-Q7-UEA" secondAttribute="leading" constant="4" id="kdQ-zs-u7N"/>
<constraint firstItem="Y25-eP-tDE" firstAttribute="top" secondItem="2Dv-Q7-UEA" secondAttribute="top" constant="8" id="qdC-S5-CgV"/>
</constraints>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kU2-7l-MSy"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="kU2-7l-MSy">
<rect key="frame" x="0.0" y="66" width="375" height="46"/> <rect key="frame" x="0.0" y="99.5" width="375" height="42"/>
<subviews> <subviews>
<textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Write your warning here" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="T05-p6-vTz"> <textField opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" placeholder="Write your warning here" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="T05-p6-vTz">
<rect key="frame" x="4" y="8" width="367" height="30"/> <rect key="frame" x="4" y="4" width="367" height="30"/>
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints> <constraints>
<constraint firstAttribute="height" constant="30" id="yzY-MF-Ukx"/> <constraint firstAttribute="height" constant="30" id="yzY-MF-Ukx"/>
@ -80,11 +103,11 @@
<constraint firstAttribute="trailing" secondItem="T05-p6-vTz" secondAttribute="trailing" constant="4" id="8tG-eW-TG4"/> <constraint firstAttribute="trailing" secondItem="T05-p6-vTz" secondAttribute="trailing" constant="4" id="8tG-eW-TG4"/>
<constraint firstAttribute="bottom" secondItem="T05-p6-vTz" secondAttribute="bottom" constant="8" id="SUL-Hk-uvM"/> <constraint firstAttribute="bottom" secondItem="T05-p6-vTz" secondAttribute="bottom" constant="8" id="SUL-Hk-uvM"/>
<constraint firstItem="T05-p6-vTz" firstAttribute="leading" secondItem="kU2-7l-MSy" secondAttribute="leading" constant="4" id="WGG-B2-lPC"/> <constraint firstItem="T05-p6-vTz" firstAttribute="leading" secondItem="kU2-7l-MSy" secondAttribute="leading" constant="4" id="WGG-B2-lPC"/>
<constraint firstItem="T05-p6-vTz" firstAttribute="top" secondItem="kU2-7l-MSy" secondAttribute="top" constant="8" id="lvW-S0-4k4"/> <constraint firstItem="T05-p6-vTz" firstAttribute="top" secondItem="kU2-7l-MSy" secondAttribute="top" constant="4" id="dN2-Pf-qFQ"/>
</constraints> </constraints>
</view> </view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lhQ-ae-pe9"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="lhQ-ae-pe9">
<rect key="frame" x="0.0" y="112" width="375" height="150"/> <rect key="frame" x="0.0" y="141.5" width="375" height="150"/>
<subviews> <subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="9pn-0T-IHb"> <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="9pn-0T-IHb">
<rect key="frame" x="4" y="0.0" width="367" height="150"/> <rect key="frame" x="4" y="0.0" width="367" height="150"/>
@ -112,7 +135,7 @@
</constraints> </constraints>
</view> </view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="P0F-3w-gI1"> <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="P0F-3w-gI1">
<rect key="frame" x="0.0" y="262" width="375" height="80"/> <rect key="frame" x="0.0" y="291.5" width="375" height="80"/>
<subviews> <subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="752-dD-eAO"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="752-dD-eAO">
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/> <rect key="frame" x="0.0" y="0.0" width="375" height="80"/>

View File

@ -10,6 +10,7 @@ import UIKit
protocol DraftsTableViewControllerDelegate { protocol DraftsTableViewControllerDelegate {
func draftSelectionCanceled() func draftSelectionCanceled()
func shouldSelectDraft(_ draft: DraftsManager.Draft, completion: @escaping (Bool) -> Void)
func draftSelected(_ draft: DraftsManager.Draft) func draftSelected(_ draft: DraftsManager.Draft)
func draftSelectionCompleted() func draftSelectionCompleted()
} }
@ -61,9 +62,23 @@ class DraftsTableViewController: UITableViewController {
} }
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
delegate?.draftSelected(draft(for: indexPath)) let draft = self.draft(for: indexPath)
dismiss(animated: true) { func select() {
self.delegate?.draftSelectionCompleted() delegate?.draftSelected(draft)
dismiss(animated: true) {
self.delegate?.draftSelectionCompleted()
}
}
if let delegate = delegate {
delegate.shouldSelectDraft(draft) { (shouldSelect) in
if shouldSelect {
select()
} else {
tableView.selectRow(at: nil, animated: true, scrollPosition: .none)
}
}
} else {
select()
} }
} }