Add character counter
This commit is contained in:
parent
d1b45ddf7b
commit
3da1a7badd
|
@ -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?
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"/>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue