Add character counter

This commit is contained in:
Shadowfacts 2018-09-29 22:20:17 -04:00
parent ad83e3a9f7
commit c7476803de
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
7 changed files with 58 additions and 6 deletions

View File

@ -15,8 +15,10 @@ public class Instance: Decodable {
public let email: String public let email: String
public let version: String public let version: String
public let urls: [String: URL] public let urls: [String: URL]
public let languages: [String]
public let contactAccount: Account // pleroma doesn't currently implement these
public let languages: [String]?
public let contactAccount: Account?
// MARK: Unofficial additions to the Mastodon API. // MARK: Unofficial additions to the Mastodon API.
public let stats: Stats? public let stats: Stats?

View File

@ -17,6 +17,10 @@ class CharacterCounterTests: XCTestCase {
override func tearDown() { override func tearDown() {
} }
func testCountEmpty() {
XCTAssertEqual(CharacterCounter.count(text: ""), 0)
}
func testCountPlainText() { func testCountPlainText() {
XCTAssertEqual(CharacterCounter.count(text: "This is an example message"), 26) XCTAssertEqual(CharacterCounter.count(text: "This is an example message"), 26)
XCTAssertEqual(CharacterCounter.count(text: "This is an example message with an Emoji: 😄"), 43) XCTAssertEqual(CharacterCounter.count(text: "This is an example message with an Emoji: 😄"), 43)

View File

@ -19,6 +19,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if LocalData.shared.onboardingComplete { if LocalData.shared.onboardingComplete {
MastodonController.shared.createClient() MastodonController.shared.createClient()
MastodonController.shared.getOwnAccount() MastodonController.shared.getOwnAccount()
MastodonController.shared.getOwnInstance()
} else { } else {
showOnboarding() showOnboarding()
} }

View File

@ -16,6 +16,7 @@ class MastodonController {
var client: Client! var client: Client!
var account: Account! var account: Account!
var instance: Instance!
private init() { private init() {
} }
@ -61,4 +62,12 @@ class MastodonController {
} }
} }
func getOwnInstance() {
let request = client.getInstance()
client.run(request) { (response) in
guard case let .success(instance, _) = response else { fatalError() }
self.instance = instance
}
}
} }

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14313.13.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="8eV-YC-Spl"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.15" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="8eV-YC-Spl">
<device id="retina4_7" orientation="portrait"> <device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/> <adaptation id="fullscreen"/>
</device> </device>
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.9"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.9"/>
<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>
@ -109,8 +109,14 @@
<action selector="visibilityPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="aeI-Wi-7Mz"/> <action selector="visibilityPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="aeI-Wi-7Mz"/>
</connections> </connections>
</button> </button>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wg4-nL-Q7B"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="249" verticalHuggingPriority="251" text="500" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3x1-3A-7c4">
<rect key="frame" x="138" y="0.0" width="205" height="30"/> <rect key="frame" x="138" y="0.0" width="166" height="30"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wg4-nL-Q7B">
<rect key="frame" x="312" y="0.0" width="31" height="30"/>
<state key="normal" title="Post"/> <state key="normal" title="Post"/>
<connections> <connections>
<action selector="postPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="HqO-kb-qcz"/> <action selector="postPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="HqO-kb-qcz"/>
@ -172,6 +178,7 @@
</barButtonItem> </barButtonItem>
</navigationItem> </navigationItem>
<connections> <connections>
<outlet property="charactersRemainingLabel" destination="3x1-3A-7c4" id="Bek-Rl-mMY"/>
<outlet property="contentWarningTextField" destination="Rbm-SO-Cpp" id="d1F-yi-CnV"/> <outlet property="contentWarningTextField" destination="Rbm-SO-Cpp" id="d1F-yi-CnV"/>
<outlet property="inReplyToAvatarImageView" destination="2fn-pv-lwV" id="SMy-L1-bI1"/> <outlet property="inReplyToAvatarImageView" destination="2fn-pv-lwV" id="SMy-L1-bI1"/>
<outlet property="inReplyToContainerView" destination="l0G-YS-00D" id="XO0-HW-WVf"/> <outlet property="inReplyToContainerView" destination="l0G-YS-00D" id="XO0-HW-WVf"/>

View File

@ -37,6 +37,7 @@ class ComposeViewController: UIViewController {
@IBOutlet weak var inReplyToContentLabel: StatusContentLabel! @IBOutlet weak var inReplyToContentLabel: StatusContentLabel!
@IBOutlet weak var inReplyToLabel: UILabel! @IBOutlet weak var inReplyToLabel: UILabel!
@IBOutlet weak var statusTextView: UITextView! @IBOutlet weak var statusTextView: UITextView!
@IBOutlet weak var charactersRemainingLabel: UILabel!
@IBOutlet weak var visibilityButton: UIButton! @IBOutlet weak var visibilityButton: UIButton!
@IBOutlet weak var postButton: UIButton! @IBOutlet weak var postButton: UIButton!
@IBOutlet weak var contentWarningTextField: UITextField! @IBOutlet weak var contentWarningTextField: UITextField!
@ -72,6 +73,7 @@ class ComposeViewController: UIViewController {
statusTextView.placeholder = "What is on your mind?" statusTextView.placeholder = "What is on your mind?"
statusTextView.layer.cornerRadius = 5 statusTextView.layer.cornerRadius = 5
statusTextView.layer.masksToBounds = true statusTextView.layer.masksToBounds = true
statusTextView.delegate = self
visibilityButton.setTitle(visibility.displayName, for: .normal) visibilityButton.setTitle(visibility.displayName, for: .normal)
contentWarningTextField.delegate = self contentWarningTextField.delegate = self
@ -118,6 +120,8 @@ class ComposeViewController: UIViewController {
statusTextView.textViewDidChange(statusTextView) statusTextView.textViewDidChange(statusTextView)
} }
updateCharactersRemaining()
progressView.progress = 0 progressView.progress = 0
} }
@ -134,6 +138,19 @@ class ComposeViewController: UIViewController {
mediaStackView.addArrangedSubview(mediaView) mediaStackView.addArrangedSubview(mediaView)
} }
func updateCharactersRemaining() {
let count = CharacterCounter.count(text: statusTextView.text)
let remaining = (MastodonController.shared.instance.maxStatusCharacters ?? 500) - count
if remaining < 0 {
charactersRemainingLabel.textColor = .red
postButton.isEnabled = false
} else {
charactersRemainingLabel.textColor = .darkGray
postButton.isEnabled = true
}
charactersRemainingLabel.text = remaining.description
}
// MARK: - Navigation // MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
@ -279,6 +296,12 @@ extension ComposeViewController: UITextFieldDelegate {
} }
} }
extension ComposeViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
updateCharactersRemaining()
}
}
extension ComposeViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { extension ComposeViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let selectedImage = info[.originalImage] as? UIImage { if let selectedImage = info[.originalImage] as? UIImage {

View File

@ -126,6 +126,12 @@ struct XCBActions {
var status = "" var status = ""
if let mentioning = mentioning { status += mentioning } if let mentioning = mentioning { status += mentioning }
if let text = text { status += text } if let text = text { status += text }
guard CharacterCounter.count(text: status) <= MastodonController.shared.instance.maxStatusCharacters ?? 500 else {
session.complete(with: .error, additionalData: [
"error": "Too many characters. Instance maximum is \(MastodonController.shared.instance.maxStatusCharacters ?? 500)"
])
return
}
let request = MastodonController.shared.client.createStatus(text: status, visibility: Preferences.shared.defaultPostVisibility) let request = MastodonController.shared.client.createStatus(text: status, visibility: Preferences.shared.defaultPostVisibility)
MastodonController.shared.client.run(request) { response in MastodonController.shared.client.run(request) { response in
if case let .success(status, _) = response { if case let .success(status, _) = response {