diff --git a/elpha-ios/Base.lproj/Main.storyboard b/elpha-ios/Base.lproj/Main.storyboard
index 38c1526..74b040a 100644
--- a/elpha-ios/Base.lproj/Main.storyboard
+++ b/elpha-ios/Base.lproj/Main.storyboard
@@ -1066,44 +1066,44 @@
-
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
@@ -1115,28 +1115,28 @@
-
+
-
+
-
+
-
+
-
+
-
+
@@ -1145,7 +1145,7 @@
-
+
@@ -1178,19 +1181,19 @@
-
+
@@ -1233,7 +1236,7 @@
-
+
@@ -1248,13 +1251,13 @@
-
+
-
+
diff --git a/elpha-ios/ComposeViewController.swift b/elpha-ios/ComposeViewController.swift
index bf1fa53..7b6781d 100644
--- a/elpha-ios/ComposeViewController.swift
+++ b/elpha-ios/ComposeViewController.swift
@@ -21,11 +21,15 @@ class ComposeViewController: UIViewController {
@IBOutlet var tootButton: UIButton!
@IBOutlet var bottomConstraint: NSLayoutConstraint!
+ let characterLimit = 500
+ var feedbackGenerator: UINotificationFeedbackGenerator? = nil
var replyToStatus: StatusMO? = nil
override func viewDidLoad() {
super.viewDidLoad()
+ feedbackGenerator = UINotificationFeedbackGenerator()
+
replyToAvatarImageView.layer.cornerRadius = 10
replyToAvatarImageView.layer.masksToBounds = true
@@ -84,11 +88,34 @@ class ComposeViewController: UIViewController {
@IBAction func dismissTapped(_ sender: Any) {
dismiss(animated: true)
}
+
+ @IBAction func tootTapped(_ sender: Any) {
+ if let content = statusTextView.text {
+ if !content.isEmpty && content.count <= characterLimit {
+ feedbackGenerator?.prepare()
+
+ MastodonAPI.postStatus(content: content, replyToID: replyToStatus?.id, spoilerText: contentWarningTextField.text) { error in
+ guard error == nil else {
+ self.feedbackGenerator?.notificationOccurred(.error)
+ AlertManager.shared.show(message: "Couldn't toot!", category: .error)
+ return
+ }
+
+ self.feedbackGenerator?.notificationOccurred(.success)
+ AlertManager.shared.show(message: "Toot!")
+
+ self.statusTextView.text = ""
+ self.contentWarningTextField.text = ""
+ self.dismiss(animated: true)
+ }
+ }
+ }
+ }
}
extension ComposeViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
let characters = statusTextView.text!.count
- statusCharacterCountLabel.text = String(500 - characters)
+ statusCharacterCountLabel.text = String(characterLimit - characters)
}
}
diff --git a/elpha-ios/FLAnimatedImageView+LoadImageURL.swift b/elpha-ios/FLAnimatedImageView+LoadImageURL.swift
index 44a2339..b3c6788 100644
--- a/elpha-ios/FLAnimatedImageView+LoadImageURL.swift
+++ b/elpha-ios/FLAnimatedImageView+LoadImageURL.swift
@@ -49,7 +49,7 @@ extension FLAnimatedImageView {
self.image = UIImage(named: "Help")
}
- func loadImageURL(_ url: URL) {
+ func loadImageURL(_ url: URL, contentMode: UIView.ContentMode = .scaleAspectFit) {
if let data = ImageCache.get(url: url) {
if url.absoluteString.hasSuffix(".gif") {
self.animatedImage = FLAnimatedImage(animatedGIFData: data)
@@ -63,7 +63,7 @@ extension FLAnimatedImageView {
if let data = response.data {
DispatchQueue.main.async {
ImageCache.set(url: url, data: data)
- self.contentMode = .scaleAspectFill
+ self.contentMode = contentMode
if url.absoluteString.hasSuffix(".gif") {
self.animatedImage = FLAnimatedImage(animatedGIFData: data)
diff --git a/elpha-ios/MastodonAPI.swift b/elpha-ios/MastodonAPI.swift
index e5b9abb..6b9f3be 100644
--- a/elpha-ios/MastodonAPI.swift
+++ b/elpha-ios/MastodonAPI.swift
@@ -14,8 +14,7 @@ typealias JSONArray = [Any]
typealias JSONObjectArray = [JSONObject]
enum MastodonRequestError: Error {
- case noResponse
- case unauthenticated
+ case noResponse, unauthenticated
}
enum TimelineCategory: String {
@@ -26,6 +25,10 @@ enum PaginationDirection: String {
case prev, next
}
+enum StatusVisibility: String {
+ case direct, `private`, unlisted, `public`
+}
+
public class PaginationItem: NSObject, NSCoding {
let direction: PaginationDirection
let statusID: String
@@ -360,6 +363,30 @@ class MastodonAPI {
}
}
+ static func postStatus(content: String, replyToID: String?, spoilerText: String?, visibility: StatusVisibility = .public, completion: @escaping (Error?) -> Void) {
+ var parameters: Parameters = [
+ "status": content,
+ "visibility": visibility.rawValue,
+ ]
+
+ if let replyToID = replyToID {
+ parameters["in_reply_to_id"] = replyToID
+ }
+
+ if let spoilerText = spoilerText {
+ parameters["spoiler_text"] = spoilerText
+ }
+
+ self.request(path: "api/v1/statuses", method: .post, parameters: parameters) { _, _, error in
+ guard error == nil else {
+ completion(error)
+ return
+ }
+
+ completion(nil)
+ }
+ }
+
static func registerApp(serverURL: URL, completion: @escaping (JSONObject?, Error?) -> Void) {
let requestURL = serverURL.appendingPathComponent("api/v1/apps")
let parameters: Parameters = [
diff --git a/elpha-ios/StatusTableViewController.swift b/elpha-ios/StatusTableViewController.swift
index eb791aa..cbad0d2 100644
--- a/elpha-ios/StatusTableViewController.swift
+++ b/elpha-ios/StatusTableViewController.swift
@@ -46,7 +46,7 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec
@IBAction func boostTapped(_ sender: Any) {
if let status = status {
- feedbackGenerator!.prepare()
+ feedbackGenerator?.prepare()
if status.reblogged {
status.reblogged = false
@@ -55,12 +55,12 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec
MastodonAPI.unreblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
- self.feedbackGenerator!.notificationOccurred(.error)
+ self.feedbackGenerator?.notificationOccurred(.error)
return
}
AlertManager.shared.show(message: "Unboosted", category: .boosted)
- self.feedbackGenerator!.notificationOccurred(.success)
+ self.feedbackGenerator?.notificationOccurred(.success)
}
} else {
status.reblogged = true
@@ -69,12 +69,12 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec
MastodonAPI.reblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
- self.feedbackGenerator!.notificationOccurred(.error)
+ self.feedbackGenerator?.notificationOccurred(.error)
return
}
AlertManager.shared.show(message: "Boosted!", category: .boosted)
- self.feedbackGenerator!.notificationOccurred(.success)
+ self.feedbackGenerator?.notificationOccurred(.success)
}
}
@@ -84,7 +84,7 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec
@IBAction func favoriteTapped(_ sender: Any) {
if let status = status {
- feedbackGenerator!.prepare()
+ feedbackGenerator?.prepare()
if status.favorited {
status.favorited = false
@@ -93,12 +93,12 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec
MastodonAPI.unfavorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
- self.feedbackGenerator!.notificationOccurred(.error)
+ self.feedbackGenerator?.notificationOccurred(.error)
return
}
AlertManager.shared.show(message: "Unfavorited", category: .favorited)
- self.feedbackGenerator!.notificationOccurred(.success)
+ self.feedbackGenerator?.notificationOccurred(.success)
}
} else {
status.favorited = true
@@ -107,12 +107,12 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec
MastodonAPI.favorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
- self.feedbackGenerator!.notificationOccurred(.error)
+ self.feedbackGenerator?.notificationOccurred(.error)
return
}
AlertManager.shared.show(message: "Favorited!", category: .favorited)
- self.feedbackGenerator!.notificationOccurred(.success)
+ self.feedbackGenerator?.notificationOccurred(.success)
}
}
@@ -262,6 +262,8 @@ extension StatusTableViewController {
fatalError("Unable to find reusable cell")
}
+ cell.contentTextView.delegate = self
+
cell.avatarImageView.layer.shadowColor = UIColor.black.cgColor
cell.avatarImageView.layer.shadowOpacity = 0.8
cell.avatarImageView.layer.shadowOffset = CGSize.zero
@@ -368,3 +370,22 @@ extension StatusTableViewController: AttachmentManagerDelegate {
self.attachmentTapped(status: status!, index: index)
}
}
+
+extension StatusTableViewController: UITextViewDelegate {
+ func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
+ if let mentions = status?.mentions {
+ for mention in mentions {
+ if URL == mention.url {
+ if let account = MastodonDataManager.account(id: mention.id) {
+ self.accountTapped(account: account)
+ }
+
+ return false
+ }
+ }
+ }
+
+ self.urlTapped(url: URL)
+ return false
+ }
+}
diff --git a/elpha-ios/StatusView.swift b/elpha-ios/StatusView.swift
index 6c7d3e9..966d4b2 100644
--- a/elpha-ios/StatusView.swift
+++ b/elpha-ios/StatusView.swift
@@ -125,7 +125,7 @@ class StatusView: UIView {
@IBAction func boostTapped(_ sender: Any) {
if let status = status {
- feedbackGenerator!.prepare()
+ feedbackGenerator?.prepare()
if status.reblogged {
status.reblogged = false
@@ -134,12 +134,12 @@ class StatusView: UIView {
MastodonAPI.unreblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
- self.feedbackGenerator!.notificationOccurred(.error)
+ self.feedbackGenerator?.notificationOccurred(.error)
return
}
AlertManager.shared.show(message: "Unboosted", category: .boosted)
- self.feedbackGenerator!.notificationOccurred(.success)
+ self.feedbackGenerator?.notificationOccurred(.success)
}
} else {
status.reblogged = true
@@ -148,12 +148,12 @@ class StatusView: UIView {
MastodonAPI.reblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
- self.feedbackGenerator!.notificationOccurred(.error)
+ self.feedbackGenerator?.notificationOccurred(.error)
return
}
AlertManager.shared.show(message: "Boosted!", category: .boosted)
- self.feedbackGenerator!.notificationOccurred(.success)
+ self.feedbackGenerator?.notificationOccurred(.success)
}
}
@@ -164,7 +164,7 @@ class StatusView: UIView {
@IBAction func favoriteTapped(_ sender: Any) {
if let status = status {
- feedbackGenerator!.prepare()
+ feedbackGenerator?.prepare()
if status.favorited {
status.favorited = false
@@ -173,12 +173,12 @@ class StatusView: UIView {
MastodonAPI.unfavorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
- self.feedbackGenerator!.notificationOccurred(.error)
+ self.feedbackGenerator?.notificationOccurred(.error)
return
}
AlertManager.shared.show(message: "Unfavorited", category: .favorited)
- self.feedbackGenerator!.notificationOccurred(.success)
+ self.feedbackGenerator?.notificationOccurred(.success)
}
} else {
status.favorited = true
@@ -187,12 +187,12 @@ class StatusView: UIView {
MastodonAPI.favorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
- self.feedbackGenerator!.notificationOccurred(.error)
+ self.feedbackGenerator?.notificationOccurred(.error)
return
}
AlertManager.shared.show(message: "Favorited!", category: .favorited)
- self.feedbackGenerator!.notificationOccurred(.success)
+ self.feedbackGenerator?.notificationOccurred(.success)
}
}
diff --git a/elpha-ios/TimelineTableViewController.swift b/elpha-ios/TimelineTableViewController.swift
index ee63e24..fe7fb50 100644
--- a/elpha-ios/TimelineTableViewController.swift
+++ b/elpha-ios/TimelineTableViewController.swift
@@ -199,7 +199,7 @@ class TimelineTableViewController: AbstractStatusTableViewController {
DispatchQueue.main.async {
var newStatusCount = 0
- self.feedbackGenerator!.prepare()
+ self.feedbackGenerator?.prepare()
for (index, status) in data.enumerated() {
if let upsertResult = MastodonDataManager.upsertStatus(status) {
@@ -239,7 +239,7 @@ class TimelineTableViewController: AbstractStatusTableViewController {
if newStatusCount > 0 {
let pluralization = newStatusCount == 1 ? "" : "s"
AlertManager.shared.show(message: "\(newStatusCount) new toot\(pluralization)", category: .newStatuses)
- self.feedbackGenerator!.notificationOccurred(.success)
+ self.feedbackGenerator?.notificationOccurred(.success)
}
CoreDataManager.shared.saveContext()