Browse Source

Add character counter

pixelfed
Shadowfacts 3 years ago
parent
commit
3da1a7badd
Signed by: shadowfacts GPG Key ID: 94A5AB95422746E5
  1. 6
      Pachyderm/Model/Instance.swift
  2. 4
      PachydermTests/CharacterCounterTests.swift
  3. 1
      Tusker/AppDelegate.swift
  4. 9
      Tusker/Controllers/MastodonController.swift
  5. 15
      Tusker/Screens/Compose/Compose.storyboard
  6. 23
      Tusker/Screens/Compose/ComposeViewController.swift
  7. 6
      Tusker/XCallbackURL/XCBActions.swift

6
Pachyderm/Model/Instance.swift

@ -15,8 +15,10 @@ public class Instance: Decodable {
public let email: String
public let version: String
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.
public let stats: Stats?

4
PachydermTests/CharacterCounterTests.swift

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

1
Tusker/AppDelegate.swift

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

9
Tusker/Controllers/MastodonController.swift

@ -16,6 +16,7 @@ class MastodonController {
var client: Client!
var account: Account!
var instance: Instance!
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
}
}
}

15
Tusker/Screens/Compose/Compose.storyboard

@ -1,10 +1,10 @@
<?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">
<adaptation id="fullscreen"/>
</device>
<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="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -109,8 +109,14 @@
<action selector="visibilityPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="aeI-Wi-7Mz"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wg4-nL-Q7B">
<rect key="frame" x="138" y="0.0" width="205" height="30"/>
<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="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"/>
<connections>
<action selector="postPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="HqO-kb-qcz"/>
@ -172,6 +178,7 @@
</barButtonItem>
</navigationItem>
<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="inReplyToAvatarImageView" destination="2fn-pv-lwV" id="SMy-L1-bI1"/>
<outlet property="inReplyToContainerView" destination="l0G-YS-00D" id="XO0-HW-WVf"/>

23
Tusker/Screens/Compose/ComposeViewController.swift

@ -37,6 +37,7 @@ class ComposeViewController: UIViewController {
@IBOutlet weak var inReplyToContentLabel: StatusContentLabel!
@IBOutlet weak var inReplyToLabel: UILabel!
@IBOutlet weak var statusTextView: UITextView!
@IBOutlet weak var charactersRemainingLabel: UILabel!
@IBOutlet weak var visibilityButton: UIButton!
@IBOutlet weak var postButton: UIButton!
@IBOutlet weak var contentWarningTextField: UITextField!
@ -72,6 +73,7 @@ class ComposeViewController: UIViewController {
statusTextView.placeholder = "What is on your mind?"
statusTextView.layer.cornerRadius = 5
statusTextView.layer.masksToBounds = true
statusTextView.delegate = self
visibilityButton.setTitle(visibility.displayName, for: .normal)
contentWarningTextField.delegate = self
@ -118,6 +120,8 @@ class ComposeViewController: UIViewController {
statusTextView.textViewDidChange(statusTextView)
}
updateCharactersRemaining()
progressView.progress = 0
}
@ -134,6 +138,19 @@ class ComposeViewController: UIViewController {
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
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 {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let selectedImage = info[.originalImage] as? UIImage {

6
Tusker/XCallbackURL/XCBActions.swift

@ -126,6 +126,12 @@ struct XCBActions {
var status = ""
if let mentioning = mentioning { status += mentioning }
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)
MastodonController.shared.client.run(request) { response in
if case let .success(status, _) = response {

Loading…
Cancel
Save