Explorar el Código

Add character counter

swiftui-preferences
Shadowfacts hace 1 año
padre
commit
3da1a7badd
Firmado por: Shadowfacts <me@shadowfacts.net> GPG Key ID: 94A5AB95422746E5

+ 4
- 2
Pachyderm/Model/Instance.swift Ver fichero

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

+ 4
- 0
PachydermTests/CharacterCounterTests.swift Ver fichero

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

+ 1
- 0
Tusker/AppDelegate.swift Ver fichero

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

+ 9
- 0
Tusker/Controllers/MastodonController.swift Ver fichero

@@ -16,6 +16,7 @@ class MastodonController {
16 16
     var client: Client!
17 17
     
18 18
     var account: Account!
19
+    var instance: Instance!
19 20
     
20 21
     private init() {
21 22
     }
@@ -61,4 +62,12 @@ class MastodonController {
61 62
         }
62 63
     }
63 64
     
65
+    func getOwnInstance() {
66
+        let request = client.getInstance()
67
+        client.run(request) { (response) in
68
+            guard case let .success(instance, _) = response else { fatalError() }
69
+            self.instance = instance
70
+        }
71
+    }
72
+    
64 73
 }

+ 11
- 4
Tusker/Screens/Compose/Compose.storyboard Ver fichero

@@ -1,10 +1,10 @@
1 1
 <?xml version="1.0" encoding="UTF-8"?>
2
-<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">
2
+<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">
3 3
     <device id="retina4_7" orientation="portrait">
4 4
         <adaptation id="fullscreen"/>
5 5
     </device>
6 6
     <dependencies>
7
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.9"/>
7
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.9"/>
8 8
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
9 9
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
10 10
     </dependencies>
@@ -109,8 +109,14 @@
109 109
                                                                     <action selector="visibilityPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="aeI-Wi-7Mz"/>
110 110
                                                                 </connections>
111 111
                                                             </button>
112
-                                                            <button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="249" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wg4-nL-Q7B">
113
-                                                                <rect key="frame" x="138" y="0.0" width="205" height="30"/>
112
+                                                            <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">
113
+                                                                <rect key="frame" x="138" y="0.0" width="166" height="30"/>
114
+                                                                <fontDescription key="fontDescription" type="system" pointSize="15"/>
115
+                                                                <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
116
+                                                                <nil key="highlightedColor"/>
117
+                                                            </label>
118
+                                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="right" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="wg4-nL-Q7B">
119
+                                                                <rect key="frame" x="312" y="0.0" width="31" height="30"/>
114 120
                                                                 <state key="normal" title="Post"/>
115 121
                                                                 <connections>
116 122
                                                                     <action selector="postPressed:" destination="svD-Ql-HGm" eventType="touchUpInside" id="HqO-kb-qcz"/>
@@ -172,6 +178,7 @@
172 178
                         </barButtonItem>
173 179
                     </navigationItem>
174 180
                     <connections>
181
+                        <outlet property="charactersRemainingLabel" destination="3x1-3A-7c4" id="Bek-Rl-mMY"/>
175 182
                         <outlet property="contentWarningTextField" destination="Rbm-SO-Cpp" id="d1F-yi-CnV"/>
176 183
                         <outlet property="inReplyToAvatarImageView" destination="2fn-pv-lwV" id="SMy-L1-bI1"/>
177 184
                         <outlet property="inReplyToContainerView" destination="l0G-YS-00D" id="XO0-HW-WVf"/>

+ 23
- 0
Tusker/Screens/Compose/ComposeViewController.swift Ver fichero

@@ -37,6 +37,7 @@ class ComposeViewController: UIViewController {
37 37
     @IBOutlet weak var inReplyToContentLabel: StatusContentLabel!
38 38
     @IBOutlet weak var inReplyToLabel: UILabel!
39 39
     @IBOutlet weak var statusTextView: UITextView!
40
+    @IBOutlet weak var charactersRemainingLabel: UILabel!
40 41
     @IBOutlet weak var visibilityButton: UIButton!
41 42
     @IBOutlet weak var postButton: UIButton!
42 43
     @IBOutlet weak var contentWarningTextField: UITextField!
@@ -72,6 +73,7 @@ class ComposeViewController: UIViewController {
72 73
         statusTextView.placeholder = "What is on your mind?"
73 74
         statusTextView.layer.cornerRadius = 5
74 75
         statusTextView.layer.masksToBounds = true
76
+        statusTextView.delegate = self
75 77
         visibilityButton.setTitle(visibility.displayName, for: .normal)
76 78
         contentWarningTextField.delegate = self
77 79
         
@@ -118,6 +120,8 @@ class ComposeViewController: UIViewController {
118 120
             statusTextView.textViewDidChange(statusTextView)
119 121
         }
120 122
         
123
+        updateCharactersRemaining()
124
+        
121 125
         progressView.progress = 0
122 126
     }
123 127
     
@@ -134,6 +138,19 @@ class ComposeViewController: UIViewController {
134 138
         mediaStackView.addArrangedSubview(mediaView)
135 139
     }
136 140
     
141
+    func updateCharactersRemaining() {
142
+        let count = CharacterCounter.count(text: statusTextView.text)
143
+        let remaining = (MastodonController.shared.instance.maxStatusCharacters ?? 500) - count
144
+        if remaining < 0 {
145
+            charactersRemainingLabel.textColor = .red
146
+            postButton.isEnabled = false
147
+        } else {
148
+            charactersRemainingLabel.textColor = .darkGray
149
+            postButton.isEnabled = true
150
+        }
151
+        charactersRemainingLabel.text = remaining.description
152
+    }
153
+    
137 154
     // MARK: - Navigation
138 155
 
139 156
     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
@@ -279,6 +296,12 @@ extension ComposeViewController: UITextFieldDelegate {
279 296
     }
280 297
 }
281 298
 
299
+extension ComposeViewController: UITextViewDelegate {
300
+    func textViewDidChange(_ textView: UITextView) {
301
+        updateCharactersRemaining()
302
+    }
303
+}
304
+
282 305
 extension ComposeViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
283 306
     func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
284 307
         if let selectedImage = info[.originalImage] as? UIImage {

+ 6
- 0
Tusker/XCallbackURL/XCBActions.swift Ver fichero

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

Loading…
Cancelar
Guardar