diff --git a/Artwork/Icons/Favorite.svg b/Artwork/Icons/Favorite.svg new file mode 100644 index 00000000..90bbd0bc --- /dev/null +++ b/Artwork/Icons/Favorite.svg @@ -0,0 +1,73 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/Artwork/Icons/Reblog.svg b/Artwork/Icons/Reblog.svg new file mode 100644 index 00000000..3935d931 --- /dev/null +++ b/Artwork/Icons/Reblog.svg @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/MyPlayground.playground/Contents.swift b/MyPlayground.playground/Contents.swift new file mode 100644 index 00000000..d7b3d126 --- /dev/null +++ b/MyPlayground.playground/Contents.swift @@ -0,0 +1,13 @@ +import UIKit + +func test(_ nillable: String?) { + defer { + print("defer") + } + guard let value = nillable else { return } + print(value) +} + +test("test") +print("------") +test(nil) diff --git a/MyPlayground.playground/contents.xcplayground b/MyPlayground.playground/contents.xcplayground new file mode 100644 index 00000000..9f5f2f40 --- /dev/null +++ b/MyPlayground.playground/contents.xcplayground @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Tusker.xcodeproj/project.pbxproj b/Tusker.xcodeproj/project.pbxproj index 9b84c491..f3f6a8f2 100644 --- a/Tusker.xcodeproj/project.pbxproj +++ b/Tusker.xcodeproj/project.pbxproj @@ -798,6 +798,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = HGYVAQA9FW; INFOPLIST_FILE = Tusker/Info.plist; @@ -807,6 +808,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Tusker; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -816,6 +818,7 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = HGYVAQA9FW; INFOPLIST_FILE = Tusker/Info.plist; @@ -825,6 +828,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = net.shadowfacts.Tusker; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/Tusker.xcworkspace/contents.xcworkspacedata b/Tusker.xcworkspace/contents.xcworkspacedata index 9a8e8fd8..043d424d 100644 --- a/Tusker.xcworkspace/contents.xcworkspacedata +++ b/Tusker.xcworkspace/contents.xcworkspacedata @@ -1,6 +1,9 @@ + + diff --git a/Tusker/Assets.xcassets/Favorite.imageset/Contents.json b/Tusker/Assets.xcassets/Favorite.imageset/Contents.json new file mode 100644 index 00000000..b3fe065f --- /dev/null +++ b/Tusker/Assets.xcassets/Favorite.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Favorite.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template", + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Tusker/Assets.xcassets/Favorite.imageset/Favorite.pdf b/Tusker/Assets.xcassets/Favorite.imageset/Favorite.pdf new file mode 100644 index 00000000..3f7a12e8 Binary files /dev/null and b/Tusker/Assets.xcassets/Favorite.imageset/Favorite.pdf differ diff --git a/Tusker/Assets.xcassets/Reblog.imageset/Contents.json b/Tusker/Assets.xcassets/Reblog.imageset/Contents.json new file mode 100644 index 00000000..53d902aa --- /dev/null +++ b/Tusker/Assets.xcassets/Reblog.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "Reblog.pdf", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + }, + "properties" : { + "template-rendering-intent" : "template", + "preserves-vector-representation" : true + } +} \ No newline at end of file diff --git a/Tusker/Assets.xcassets/Reblog.imageset/Reblog.pdf b/Tusker/Assets.xcassets/Reblog.imageset/Reblog.pdf new file mode 100644 index 00000000..8048630d Binary files /dev/null and b/Tusker/Assets.xcassets/Reblog.imageset/Reblog.pdf differ diff --git a/Tusker/Extensions/UIViewController+Delegates.swift b/Tusker/Extensions/UIViewController+Delegates.swift index 936fc08b..d9463f4f 100644 --- a/Tusker/Extensions/UIViewController+Delegates.swift +++ b/Tusker/Extensions/UIViewController+Delegates.swift @@ -77,6 +77,9 @@ extension StatusTableViewCellDelegate where Self: UIViewController { present(vc, animated: true) } + func updatedStatus(for cell: StatusTableViewCell, status: Status) { + } + } extension LargeImageViewControllerDelegate where Self: UIViewController { diff --git a/Tusker/Screens/Timeline/TimelineTableViewController.swift b/Tusker/Screens/Timeline/TimelineTableViewController.swift index 35ecbaef..0e19b485 100644 --- a/Tusker/Screens/Timeline/TimelineTableViewController.swift +++ b/Tusker/Screens/Timeline/TimelineTableViewController.swift @@ -133,5 +133,10 @@ class TimelineTableViewController: UITableViewController { } -extension TimelineTableViewController: StatusTableViewCellDelegate {} +extension TimelineTableViewController: StatusTableViewCellDelegate { + func updatedStatus(for cell: StatusTableViewCell, status: Status) { + let indexPath = tableView.indexPath(for: cell)! + statuses[indexPath.row] = status + } +} extension TimelineTableViewController: LargeImageViewControllerDelegate {} diff --git a/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift b/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift index 9498d574..771e64f9 100644 --- a/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift +++ b/Tusker/Views/Status/ConversationMainStatusTableViewCell.swift @@ -19,12 +19,24 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive @IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var timestampLabel: UILabel! @IBOutlet weak var attachmentsView: UIView! + @IBOutlet weak var favoriteButton: UIButton! + @IBOutlet weak var reblogButton: UIButton! var status: Status! var account: Account! - var avatarURL: URL? + var favorited: Bool = false { + didSet { + favoriteButton.tintColor = favorited ? UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1) : tintColor + } + } + var reblogged: Bool = false { + didSet { + reblogButton.tintColor = reblogged ? UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1) : tintColor + } + } + var avatarURL: URL? var updateTimestampWorkItem: DispatchWorkItem? override func awakeFromNib() { @@ -37,6 +49,7 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive avatarImageView.layer.masksToBounds = true attachmentsView.layer.cornerRadius = 5 attachmentsView.layer.masksToBounds = true + contentLabel.delegate = self } func updateUIForPreferences() { @@ -97,8 +110,11 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive attachmentsView.isHidden = true } + let realStatus = status.reblog ?? status + favorited = realStatus.favourited ?? false + reblogged = realStatus.reblogged ?? false + contentLabel.status = status - contentLabel.delegate = self } func updateTimestamp() { @@ -147,6 +163,60 @@ class ConversationMainStatusTableViewCell: UITableViewCell, PreferencesAdaptive delegate?.reply(to: status) } + @IBAction func favoritePressed(_ sender: Any) { + let oldValue = favorited + favorited = !favorited + + let realStatus: Status = status.reblog ?? status + let req = favorited ? Statuses.favourite(id: realStatus.id) : Statuses.unfavourite(id: realStatus.id) + + MastodonController.shared.client.run(req) { result in + guard case .success = result else { + print("Couldn't favorite status \(realStatus.id)") + // todo: display error message + DispatchQueue.main.async { + self.favorited = oldValue + + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.error) + } + return + } + + DispatchQueue.main.async { + let generator = UIImpactFeedbackGenerator(style: .light) + generator.impactOccurred() + } + } + } + + @IBAction func reblogPressed(_ sender: Any) { + let oldValue = reblogged + reblogged = !reblogged + + let realStatus: Status = status.reblog ?? status + let req = reblogged ? Statuses.reblog(id: realStatus.id) : Statuses.unreblog(id: realStatus.id) + + MastodonController.shared.client.run(req) { result in + guard case .success = result else { + print("Couldn't reblog status \(realStatus.id)") + // todo: display error message + DispatchQueue.main.async { + self.reblogged = oldValue + + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.error) + } + return + } + + DispatchQueue.main.async { + let generator = UIImpactFeedbackGenerator(style: .light) + generator.impactOccurred() + } + } + } + } extension ConversationMainStatusTableViewCell: HTMLContentLabelDelegate { diff --git a/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib b/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib index 1b333c2f..0dfa51d8 100644 --- a/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib +++ b/Tusker/Views/Status/ConversationMainStatusTableViewCell.xib @@ -79,21 +79,43 @@ - - + + + + + + + @@ -116,6 +138,8 @@ + + @@ -123,6 +147,8 @@ + + diff --git a/Tusker/Views/Status/StatusTableViewCell.swift b/Tusker/Views/Status/StatusTableViewCell.swift index 0645a823..357a49d1 100644 --- a/Tusker/Views/Status/StatusTableViewCell.swift +++ b/Tusker/Views/Status/StatusTableViewCell.swift @@ -25,6 +25,8 @@ protocol StatusTableViewCellDelegate { func showLargeImage(_ image: UIImage, description: String?, animatingFrom originView: UIView) + func updatedStatus(for cell: StatusTableViewCell, status: Status) + } class StatusTableViewCell: UITableViewCell, PreferencesAdaptive { @@ -38,11 +40,24 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive { @IBOutlet weak var reblogLabel: UILabel! @IBOutlet weak var timestampLabel: UILabel! @IBOutlet weak var attachmentsView: UIView! + @IBOutlet weak var favoriteButton: UIButton! + @IBOutlet weak var reblogButton: UIButton! var status: Status! var account: Account! var reblogger: Account? + var favorited: Bool = false { + didSet { + favoriteButton.tintColor = favorited ? UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1) : tintColor + } + } + var reblogged: Bool = false { + didSet { + reblogButton.tintColor = reblogged ? UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1) : tintColor + } + } + var avatarURL: URL? var updateTimestampWorkItem: DispatchWorkItem? var attachmentDataTasks: [URLSessionDataTask] = [] @@ -129,6 +144,10 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive { } + let realStatus = status.reblog ?? status + favorited = realStatus.favourited ?? false + reblogged = realStatus.reblogged ?? false + contentLabel.status = status } @@ -192,6 +211,60 @@ class StatusTableViewCell: UITableViewCell, PreferencesAdaptive { delegate?.selected(account: reblogger) } + @IBAction func favoritePressed(_ sender: Any) { + let oldValue = favorited + favorited = !favorited + + let realStatus: Status = status.reblog ?? status + let req = favorited ? Statuses.favourite(id: realStatus.id) : Statuses.unfavourite(id: realStatus.id) + + MastodonController.shared.client.run(req) { result in + guard case .success = result else { + print("Couldn't favorite status \(realStatus.id)") + // todo: display error message + DispatchQueue.main.async { + self.favorited = oldValue + + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.error) + } + return + } + + DispatchQueue.main.async { + let generator = UIImpactFeedbackGenerator(style: .light) + generator.impactOccurred() + } + } + } + + @IBAction func reblogPressed(_ sender: Any) { + let oldValue = reblogged + reblogged = !reblogged + + let realStatus: Status = status.reblog ?? status + let req = reblogged ? Statuses.reblog(id: realStatus.id) : Statuses.unreblog(id: realStatus.id) + + MastodonController.shared.client.run(req) { result in + guard case .success = result else { + print("Couldn't reblog status \(realStatus.id)") + // todo: display error message + DispatchQueue.main.async { + self.reblogged = oldValue + + let generator = UINotificationFeedbackGenerator() + generator.notificationOccurred(.error) + } + return + } + + DispatchQueue.main.async { + let generator = UIImpactFeedbackGenerator(style: .light) + generator.impactOccurred() + } + } + } + } extension StatusTableViewCell: HTMLContentLabelDelegate { diff --git a/Tusker/Views/Status/StatusTableViewCell.xib b/Tusker/Views/Status/StatusTableViewCell.xib index cc9f1757..704abfa2 100644 --- a/Tusker/Views/Status/StatusTableViewCell.xib +++ b/Tusker/Views/Status/StatusTableViewCell.xib @@ -86,13 +86,12 @@ - - + + + + + + + @@ -123,6 +145,8 @@ + + @@ -131,6 +155,8 @@ + +