Browse Source

Development

master
Dwayne Harris 6 years ago
parent
commit
b8416ff314
  1. 16
      elpha-ios.xcodeproj/project.pbxproj
  2. 97
      elpha-ios/AccountTableViewController.swift
  3. 6
      elpha-ios/AlertManager.swift
  4. 2
      elpha-ios/AlertView.xib
  5. BIN
      elpha-ios/Assets.xcassets/Icons/Timelines.imageset/timelines.pdf
  6. 5
      elpha-ios/AttachmentManager.swift
  7. 79
      elpha-ios/AttachmentPageViewController.swift
  8. 45
      elpha-ios/AttachmentViewController.swift
  9. 30
      elpha-ios/AuthenticateViewController.swift
  10. 106
      elpha-ios/Base.lproj/Main.storyboard
  11. 6
      elpha-ios/InstanceViewController.swift
  12. 20
      elpha-ios/InstancesTableViewController.swift
  13. 22
      elpha-ios/MastodonAPI.swift
  14. 129
      elpha-ios/StatusTableViewController.swift
  15. 84
      elpha-ios/StatusView.swift
  16. 81
      elpha-ios/TimelineTableViewController.swift
  17. 52
      elpha-ios/TimelinesViewController.swift

16
elpha-ios.xcodeproj/project.pbxproj

@ -14,6 +14,8 @@
151AD4D9216899AD00F07403 /* AlamofireImage.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1517EA842159D72200DE80D6 /* AlamofireImage.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
151AD4E621689A0F00F07403 /* Alamofire.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 157405C3215890BC00EEAAEB /* Alamofire.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
152734D22186DC74003DB3C8 /* TimelinesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 152734D12186DC74003DB3C8 /* TimelinesViewController.swift */; };
152FB0F8218ADC1A001D6574 /* AttachmentPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 152FB0F7218ADC1A001D6574 /* AttachmentPageViewController.swift */; };
152FB0FA218ADDD0001D6574 /* AttachmentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 152FB0F9218ADDD0001D6574 /* AttachmentViewController.swift */; };
1539509121894A38009BA6E7 /* AlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1539509021894A38009BA6E7 /* AlertManager.swift */; };
156FF015217289380074D9CA /* AccountTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156FF014217289380074D9CA /* AccountTableViewCell.swift */; };
156FF0312174797E0074D9CA /* StatusTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 156FF0302174797E0074D9CA /* StatusTableViewController.swift */; };
@ -25,7 +27,7 @@
157405B12151A5DA00EEAAEB /* README.md in Resources */ = {isa = PBXBuildFile; fileRef = 157405AF2151A5DA00EEAAEB /* README.md */; };
157405B42151A93E00EEAAEB /* InstancesDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 157405B32151A93E00EEAAEB /* InstancesDataManager.swift */; };
157405D1215890D700EEAAEB /* Alamofire.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 157405C3215890BC00EEAAEB /* Alamofire.framework */; };
1574148D2169AD0100C841BD /* AttachmentsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1574148C2169AD0100C841BD /* AttachmentsManager.swift */; };
1574148D2169AD0100C841BD /* AttachmentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1574148C2169AD0100C841BD /* AttachmentManager.swift */; };
159026AE2162CF5600D362DD /* TimelineTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 159026AD2162CF5600D362DD /* TimelineTableViewCell.swift */; };
159026D02163069600D362DD /* Date+TimeAgo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 159026CF2163069600D362DD /* Date+TimeAgo.swift */; };
159048AF214F5015004F4014 /* InstancesTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 159048AE214F5015004F4014 /* InstancesTableViewCell.swift */; };
@ -195,6 +197,8 @@
15131EF5216DBA820092B252 /* AccountNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountNavigationController.swift; sourceTree = "<group>"; };
1517EA6F2159D72200DE80D6 /* AlamofireImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AlamofireImage.xcodeproj; path = Frameworks/AlamofireImage/AlamofireImage.xcodeproj; sourceTree = "<group>"; };
152734D12186DC74003DB3C8 /* TimelinesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelinesViewController.swift; sourceTree = "<group>"; };
152FB0F7218ADC1A001D6574 /* AttachmentPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentPageViewController.swift; sourceTree = "<group>"; };
152FB0F9218ADDD0001D6574 /* AttachmentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentViewController.swift; sourceTree = "<group>"; };
1539509021894A38009BA6E7 /* AlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertManager.swift; sourceTree = "<group>"; };
156FF014217289380074D9CA /* AccountTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountTableViewCell.swift; sourceTree = "<group>"; };
156FF0302174797E0074D9CA /* StatusTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusTableViewController.swift; sourceTree = "<group>"; };
@ -206,7 +210,7 @@
157405AF2151A5DA00EEAAEB /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
157405B32151A93E00EEAAEB /* InstancesDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesDataManager.swift; sourceTree = "<group>"; };
157405B7215890BC00EEAAEB /* Alamofire.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Alamofire.xcodeproj; path = Frameworks/Alamofire/Alamofire.xcodeproj; sourceTree = "<group>"; };
1574148C2169AD0100C841BD /* AttachmentsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentsManager.swift; sourceTree = "<group>"; };
1574148C2169AD0100C841BD /* AttachmentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentManager.swift; sourceTree = "<group>"; };
159026AD2162CF5600D362DD /* TimelineTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineTableViewCell.swift; sourceTree = "<group>"; };
159026CF2163069600D362DD /* Date+TimeAgo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+TimeAgo.swift"; sourceTree = "<group>"; };
159048AE214F5015004F4014 /* InstancesTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesTableViewCell.swift; sourceTree = "<group>"; };
@ -274,7 +278,7 @@
isa = PBXGroup;
children = (
1539509021894A38009BA6E7 /* AlertManager.swift */,
1574148C2169AD0100C841BD /* AttachmentsManager.swift */,
1574148C2169AD0100C841BD /* AttachmentManager.swift */,
15960E7B213272CD00C38CE9 /* AuthenticationManager.swift */,
15A79B42215EB959007A326E /* CoreDataManager.swift */,
157405B32151A93E00EEAAEB /* InstancesDataManager.swift */,
@ -399,6 +403,8 @@
children = (
15131EF5216DBA820092B252 /* AccountNavigationController.swift */,
15131EF3216DB8B90092B252 /* AccountTableViewController.swift */,
152FB0F7218ADC1A001D6574 /* AttachmentPageViewController.swift */,
152FB0F9218ADDD0001D6574 /* AttachmentViewController.swift */,
15960E7D21329FED00C38CE9 /* AuthenticateViewController.swift */,
15960E83213774FC00C38CE9 /* InstancesTableViewController.swift */,
157405A72150588A00EEAAEB /* InstanceViewController.swift */,
@ -608,12 +614,14 @@
buildActionMask = 2147483647;
files = (
1539509121894A38009BA6E7 /* AlertManager.swift in Sources */,
152FB0FA218ADDD0001D6574 /* AttachmentViewController.swift in Sources */,
159048AF214F5015004F4014 /* InstancesTableViewCell.swift in Sources */,
15960E84213774FC00C38CE9 /* InstancesTableViewController.swift in Sources */,
15960E7021321FA500C38CE9 /* Elpha.xcdatamodeld in Sources */,
15F9981721629965009E58DA /* TimelineTableViewController.swift in Sources */,
157405B42151A93E00EEAAEB /* InstancesDataManager.swift in Sources */,
156FF0312174797E0074D9CA /* StatusTableViewController.swift in Sources */,
152FB0F8218ADC1A001D6574 /* AttachmentPageViewController.swift in Sources */,
15131EF4216DB8B90092B252 /* AccountTableViewController.swift in Sources */,
15F998352162C0E8009E58DA /* MastodonDataManager.swift in Sources */,
15960E7A2132387A00C38CE9 /* MainTabBarController.swift in Sources */,
@ -634,7 +642,7 @@
159026D02163069600D362DD /* Date+TimeAgo.swift in Sources */,
15A79B43215EB959007A326E /* CoreDataManager.swift in Sources */,
15BB72AB2171A8D4002F1FA4 /* TimelinesTableViewCell.swift in Sources */,
1574148D2169AD0100C841BD /* AttachmentsManager.swift in Sources */,
1574148D2169AD0100C841BD /* AttachmentManager.swift in Sources */,
156FF015217289380074D9CA /* AccountTableViewCell.swift in Sources */,
15960E822136668500C38CE9 /* TimelinesNavigationController.swift in Sources */,
156FF04F2175CDBC0074D9CA /* MainStatusTableViewCell.swift in Sources */,

97
elpha-ios/AccountTableViewController.swift

@ -150,8 +150,9 @@ class AccountTableViewController: UITableViewController {
func fetchStatuses(withPagination pagination: PaginationItem? = nil, completion: @escaping (Error?) -> Void) {
if let account = account {
func requestCompletion(data: [JSONObject]?, pagination: [PaginationItem]?, error: Error?) {
func requestCompletion(data: JSONObjectArray?, pagination: [PaginationItem]?, error: Error?) {
guard let data = data, error == nil else {
self.loading = false
completion(error)
return
}
@ -184,6 +185,18 @@ class AccountTableViewController: UITableViewController {
}
}
MastodonAPI.pinnedStatuses(accountID: account.id!, limit: fetchLimit) { data, error in
guard let data = data, error == nil else {
self.loading = false
completion(error)
return
}
data.forEach { status in
_ = MastodonDataManager.upsertStatus(status.merging(["pinned": true]) { (_, new) in new })
}
}
CoreDataManager.shared.saveContext()
self.loading = false
@ -296,14 +309,42 @@ extension AccountTableViewController: NSFetchedResultsControllerDelegate {
cell.statusView.topLoadMoreView.isHidden = true
cell.statusView.bottomLoadMoreView.isHidden = true
let statusCount = fetchedResultsController?.fetchedObjects?.count ?? 0
if let markers = status.markers {
markers.forEach { marker in
if marker.context == self.currentPaginationContext {
print("Found marker: \(marker)")
switch marker.item.direction {
case .prev:
if indexPath.row > 0, let previousStatus = fetchedResultsController?.object(at: IndexPath(row: indexPath.row - 1, section: indexPath.section)) {
let previousMarkers = previousStatus.markers ?? []
let previousMarker = previousMarkers.first { $0.context == self.currentPaginationContext && $0.item.direction == .next }
if previousStatus.id! != marker.item.statusID && previousMarker != nil {
cell.statusView.topLoadMoreView.isHidden = false
cell.statusView.topDividerView.isHidden = true
}
}
case .next:
if indexPath.row < statusCount - 1, let nextStatus = fetchedResultsController?.object(at: IndexPath(row: indexPath.row + 1, section: indexPath.section)) {
let nextMarkers = nextStatus.markers ?? []
let nextMarker = nextMarkers.first { $0.context == self.currentPaginationContext && $0.item.direction == .prev }
if nextStatus.id! != marker.item.statusID && nextMarker != nil {
cell.statusView.bottomLoadMoreView.isHidden = false
cell.statusView.bottomDividerView.isHidden = true
}
}
}
}
}
}
if indexPath.row == statusCount - 1 {
cell.statusView.bottomLoadMoreView.isHidden = false
cell.statusView.bottomDividerView.isHidden = true
}
return cell
}
}
@ -327,50 +368,6 @@ extension AccountTableViewController: StatusViewDelegate {
}
}
func favoriteTapped(status: StatusMO) {
if status.favorited {
MastodonAPI.unfavorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Unfavorited", category: .favorited)
}
} else {
MastodonAPI.favorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Favorited!", category: .favorited)
}
}
}
func reblogTapped(status: StatusMO) {
if status.reblogged {
MastodonAPI.unreblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Unboosted", category: .boosted)
}
} else {
MastodonAPI.reblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Boosted!", category: .boosted)
}
}
}
func loadMoreTapped(status: StatusMO, direction: PaginationDirection) {
if let markers = status.markers {
markers.forEach { marker in
@ -384,4 +381,12 @@ extension AccountTableViewController: StatusViewDelegate {
}
}
}
func attachmentTapped(status: StatusMO) {
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "AttachmentPageViewController") as? AttachmentPageViewController {
controller.status = status
self.navigationController?.pushViewController(controller, animated: true)
}
}
}

6
elpha-ios/AlertManager.swift

@ -83,12 +83,12 @@ class AlertManager {
view.layoutIfNeeded()
UIView.animate(withDuration: 0.2, animations: {
UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseInOut, animations: {
self.bottomLayoutConstraint?.constant = AlertManager.alertEndPosition
view.layoutIfNeeded()
}) { _ in
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
UIView.animate(withDuration: 0.2, animations: {
UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseInOut, animations: {
self.bottomLayoutConstraint?.constant = AlertManager.alertStartPosition
view.layoutIfNeeded()
}) { _ in
@ -96,7 +96,7 @@ class AlertManager {
_ = self.alerts.remove(at: 0)
if self.alerts.count > 0 {
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.start()
}
}

2
elpha-ios/AlertView.xib

@ -23,7 +23,7 @@
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Message" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uLF-4h-Bxo">
<rect key="frame" x="45" y="8" width="322" height="164"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="14"/>
<color key="textColor" red="0.090196078430000007" green="0.047058823530000002" blue="0.28627450980000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>

BIN
elpha-ios/Assets.xcassets/Icons/Timelines.imageset/timelines.pdf

5
elpha-ios/AttachmentsManager.swift → elpha-ios/AttachmentManager.swift

@ -9,8 +9,8 @@
import AlamofireImage
import UIKit
class AttachmentsManager {
static func setupAttachmentsView(_ view: UIView, withAttachments attachments: NSOrderedSet?) {
class AttachmentManager: NSObject, UIGestureRecognizerDelegate {
static func setupAttachmentView(_ view: UIView, withAttachments attachments: NSOrderedSet?) {
guard let attachments = attachments, attachments.count > 0 else {
return
}
@ -29,7 +29,6 @@ class AttachmentsManager {
let attachment = attachments.firstObject as! AttachmentMO
let imageView = UIImageView()
imageView.contentMode = UIImageView.ContentMode.scaleAspectFill
imageView.af_setImage(
withURL: attachment.url!,

79
elpha-ios/AttachmentPageViewController.swift

@ -0,0 +1,79 @@
//
// AttachmentPageViewController.swift
// elpha-ios
//
// Created by Dwayne Harris on 11/1/18.
// Copyright © 2018 Elpha. All rights reserved.
//
import AlamofireImage
import UIKit
class AttachmentPageViewController: UIPageViewController {
var status: StatusMO? = nil
var controllers: [UIViewController] = []
var attachmentIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.isNavigationBarHidden = true
self.delegate = self
self.dataSource = self
if let status = status, let attachments = status.attachments {
attachments.forEach { attachment in
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "AttachmentViewController") as? AttachmentViewController {
controller.attachment = attachment as? AttachmentMO
controllers.append(controller)
}
}
}
setViewControllers([controllers.first!], direction: .forward, animated: true, completion: nil)
}
func dismissController() {
self.navigationController?.isNavigationBarHidden = false
self.navigationController?.popViewController(animated: true)
}
}
extension AttachmentPageViewController: UIPageViewControllerDataSource, UIPageViewControllerDelegate {
func presentationCount(for pageViewController: UIPageViewController) -> Int {
return controllers.count
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
guard let index = controllers.index(of: viewController) else {
return nil
}
if controllers.count == 1 {
return nil
}
if index == 0 {
return controllers.last
}
return controllers[index - 1]
}
func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
guard let index = controllers.index(of: viewController) else {
return nil
}
if controllers.count == 1 {
return nil
}
if index == controllers.count - 1 {
return controllers.first
}
return controllers[index + 1]
}
}

45
elpha-ios/AttachmentViewController.swift

@ -0,0 +1,45 @@
//
// AttachmentViewController.swift
// elpha-ios
//
// Created by Dwayne Harris on 11/1/18.
// Copyright © 2018 Elpha. All rights reserved.
//
import UIKit
class AttachmentViewController: UIViewController {
@IBOutlet var attachmentImageView: UIImageView!
@IBOutlet var statusLabel: UILabel!
var attachment: AttachmentMO? = nil
override func viewDidLoad() {
super.viewDidLoad()
if let attachment = attachment {
attachmentImageView.af_setImage(withURL: attachment.url!)
if let content = attachment.status?.content {
do {
let styledContent = "<style>html * { font-size: 14px; color: #ffffff; font-family: -apple-system } a:link { color: #ffffff } a:visited { color: #ffffff }</style> \(content)"
let attributedText = try NSAttributedString(
data: styledContent.data(using: String.Encoding.unicode, allowLossyConversion: true)!,
options: [.documentType: NSAttributedString.DocumentType.html],
documentAttributes: nil
)
statusLabel.attributedText = attributedText
} catch {
print("\(error)")
}
}
}
}
@IBAction func imageSwiped(_ sender: Any) {
if let controller = self.parent as? AttachmentPageViewController {
controller.dismissController()
}
}
}

30
elpha-ios/AuthenticateViewController.swift

@ -11,7 +11,7 @@ import CoreData
import UIKit
import SafariServices
class AuthenticateViewController: UIViewController, SFSafariViewControllerDelegate, AuthenticationDelegate {
class AuthenticateViewController: UIViewController {
@IBOutlet var signInButton: UIButton!
@IBOutlet var instanceTextField: UITextField!
@ -28,14 +28,6 @@ class AuthenticateViewController: UIViewController, SFSafariViewControllerDelega
return false
}
func authenticationSuccess() {
self.dismiss(animated: true)
}
func authenticationFailure(error: Error?) {
AlertManager.shared.show(message: error?.localizedDescription ?? "Authentication error", category: .error)
}
func authorize(client: ClientMO) {
AuthenticationManager.authenticationState = NSUUID().uuidString
AuthenticationManager.authenticationClient = client
@ -60,10 +52,6 @@ class AuthenticateViewController: UIViewController, SFSafariViewControllerDelega
present(controller, animated: true)
}
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
controller.dismiss(animated: true)
}
@IBAction func signIn(_ sender: Any) {
guard var host = instanceTextField.text, !host.isEmpty else {
return
@ -119,3 +107,19 @@ class AuthenticateViewController: UIViewController, SFSafariViewControllerDelega
}
}
}
extension AuthenticateViewController: SFSafariViewControllerDelegate {
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
controller.dismiss(animated: true)
}
}
extension AuthenticateViewController: AuthenticationDelegate {
func authenticationSuccess() {
self.dismiss(animated: true)
}
func authenticationFailure(error: Error?) {
AlertManager.shared.show(message: error?.localizedDescription ?? "Authentication error", category: .error)
}
}

106
elpha-ios/Base.lproj/Main.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.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="49e-Tb-3d3">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="49e-Tb-3d3">
<device id="retina5_9" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<capability name="Named colors" minToolsVersion="9.0"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
@ -1047,6 +1047,7 @@
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<constraints>
<constraint firstAttribute="width" constant="60" id="Re8-4t-uoD"/>
<constraint firstAttribute="trailing" secondItem="Tch-E4-Saw" secondAttribute="trailing" constant="8" id="Xty-RA-yBl"/>
@ -1055,6 +1056,9 @@
<constraint firstItem="B1s-Wp-hBJ" firstAttribute="centerY" secondItem="9oD-Y0-ilp" secondAttribute="centerY" id="oWq-Qe-VMg"/>
<constraint firstAttribute="height" constant="30" id="xLI-sT-SnC"/>
</constraints>
<connections>
<outletCollection property="gestureRecognizers" destination="uBg-bz-egu" appends="YES" id="MSk-ph-Hi7"/>
</connections>
</view>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="FZi-le-bmF">
<rect key="frame" x="171" y="533" width="60" height="30"/>
@ -1074,6 +1078,7 @@
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="06U-FB-yto" firstAttribute="leading" secondItem="Jcf-Cv-8ki" secondAttribute="trailing" constant="8" id="7La-R0-13y"/>
<constraint firstItem="06U-FB-yto" firstAttribute="centerY" secondItem="FZi-le-bmF" secondAttribute="centerY" id="HSO-JX-hg7"/>
@ -1082,6 +1087,9 @@
<constraint firstItem="Jcf-Cv-8ki" firstAttribute="centerY" secondItem="FZi-le-bmF" secondAttribute="centerY" id="Zzj-qQ-SrD"/>
<constraint firstAttribute="trailing" secondItem="06U-FB-yto" secondAttribute="trailing" id="uJ8-JC-1Ql"/>
</constraints>
<connections>
<outletCollection property="gestureRecognizers" destination="dVp-l7-qxz" appends="YES" id="37g-LW-Ne9"/>
</connections>
</view>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" spacing="15" translatesAutoresizingMaskIntoConstraints="NO" id="hBh-J5-s0Y">
<rect key="frame" x="0.0" y="96" width="375" height="422"/>
@ -1198,9 +1206,98 @@
<action selector="accountViewTapped:" destination="RAJ-ub-len" id="8BS-4Z-ZQz"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="dVp-l7-qxz">
<connections>
<action selector="boostTapped:" destination="RAJ-ub-len" id="gBv-xx-8fA"/>
</connections>
</tapGestureRecognizer>
<tapGestureRecognizer id="uBg-bz-egu">
<connections>
<action selector="favoriteTapped:" destination="RAJ-ub-len" id="JNI-Rd-xSW"/>
</connections>
</tapGestureRecognizer>
</objects>
<point key="canvasLocation" x="2701.5999999999999" y="-22.167487684729064"/>
</scene>
<!--Attachment Page View Controller-->
<scene sceneID="zGj-2x-VH5">
<objects>
<pageViewController storyboardIdentifier="AttachmentPageViewController" autoresizesArchivedViewToFullSize="NO" definesPresentationContext="YES" providesPresentationContextTransitionStyle="YES" modalPresentationStyle="overCurrentContext" hidesBottomBarWhenPushed="YES" transitionStyle="scroll" navigationOrientation="horizontal" spineLocation="none" id="Wmm-2l-ys2" customClass="AttachmentPageViewController" customModule="elpha_ios" customModuleProvider="target" sceneMemberID="viewController"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="4hr-yC-hqj" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3614" y="-22"/>
</scene>
<!--Attachment View Controller-->
<scene sceneID="lVm-QG-Lzv">
<objects>
<viewController storyboardIdentifier="AttachmentViewController" definesPresentationContext="YES" providesPresentationContextTransitionStyle="YES" modalPresentationStyle="overCurrentContext" hidesBottomBarWhenPushed="YES" id="N19-H3-zNH" customClass="AttachmentViewController" customModule="elpha_ios" customModuleProvider="target" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="559-Im-GBi">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<visualEffectView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="MK4-Zf-UMa">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<view key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="D1u-Lt-W0l">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="LVW-zt-JUe">
<rect key="frame" x="0.0" y="0.0" width="375" height="662"/>
<gestureRecognizers/>
<connections>
<outletCollection property="gestureRecognizers" destination="jGp-MJ-Wqo" appends="YES" id="T0x-sN-qnj"/>
<outletCollection property="gestureRecognizers" destination="d2a-nR-kSR" appends="YES" id="Zpm-xY-TMT"/>
</connections>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Image Caption" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="uOb-Wh-Ew6">
<rect key="frame" x="8" y="677" width="359" height="127"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" name="Background Primary"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="uOb-Wh-Ew6" firstAttribute="top" secondItem="LVW-zt-JUe" secondAttribute="bottom" constant="15" id="01E-pt-r18"/>
<constraint firstItem="LVW-zt-JUe" firstAttribute="top" secondItem="D1u-Lt-W0l" secondAttribute="top" id="1M4-gv-0KW"/>
<constraint firstAttribute="trailing" secondItem="LVW-zt-JUe" secondAttribute="trailing" id="B6e-nm-1KT"/>
<constraint firstAttribute="bottom" secondItem="uOb-Wh-Ew6" secondAttribute="bottom" constant="8" id="BW9-Uf-nL7"/>
<constraint firstAttribute="bottom" secondItem="LVW-zt-JUe" secondAttribute="bottom" constant="150" id="SyJ-A5-Bpw"/>
<constraint firstAttribute="trailing" secondItem="uOb-Wh-Ew6" secondAttribute="trailing" constant="8" id="TRF-Oj-j11"/>
<constraint firstItem="LVW-zt-JUe" firstAttribute="leading" secondItem="D1u-Lt-W0l" secondAttribute="leading" id="ojo-i9-sB4"/>
<constraint firstItem="uOb-Wh-Ew6" firstAttribute="leading" secondItem="D1u-Lt-W0l" secondAttribute="leading" constant="8" id="yN9-W0-tng"/>
</constraints>
</view>
<blurEffect style="dark"/>
</visualEffectView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="0.0" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="MK4-Zf-UMa" firstAttribute="top" secondItem="559-Im-GBi" secondAttribute="top" id="GF1-Ph-D8h"/>
<constraint firstItem="MK4-Zf-UMa" firstAttribute="leading" secondItem="MLI-yW-hwy" secondAttribute="leading" id="d3K-7K-SO6"/>
<constraint firstAttribute="bottom" secondItem="MK4-Zf-UMa" secondAttribute="bottom" id="ggh-85-WwH"/>
<constraint firstItem="MLI-yW-hwy" firstAttribute="trailing" secondItem="MK4-Zf-UMa" secondAttribute="trailing" id="oLe-WB-s1V"/>
</constraints>
<viewLayoutGuide key="safeArea" id="MLI-yW-hwy"/>
</view>
<connections>
<outlet property="attachmentImageView" destination="LVW-zt-JUe" id="Nuk-39-At3"/>
<outlet property="statusLabel" destination="uOb-Wh-Ew6" id="ROf-CT-kTn"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="YeS-EV-qHA" userLabel="First Responder" sceneMemberID="firstResponder"/>
<swipeGestureRecognizer direction="down" id="jGp-MJ-Wqo">
<connections>
<action selector="imageSwiped:" destination="N19-H3-zNH" id="v4O-q7-IMo"/>
</connections>
</swipeGestureRecognizer>
<swipeGestureRecognizer direction="up" id="d2a-nR-kSR">
<connections>
<action selector="imageSwiped:" destination="N19-H3-zNH" id="kqJ-JH-zff"/>
</connections>
</swipeGestureRecognizer>
</objects>
<point key="canvasLocation" x="4367" y="-22"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="UVY-Um-jQm">
<objects>
@ -1264,7 +1361,10 @@
<image name="Logo" width="400" height="400"/>
<image name="Message" width="20" height="20"/>
<image name="Star Regular" width="22" height="22"/>
<image name="Timelines" width="25" height="25"/>
<image name="Timelines" width="15" height="16"/>
<namedColor name="Background Primary">
<color red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="Primary">
<color red="0.54117647058823526" green="0.4823529411764706" blue="0.68235294117647061" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>

6
elpha-ios/InstanceViewController.swift

@ -10,7 +10,7 @@ import AlamofireImage
import UIKit
import SafariServices
class InstanceViewController: UIViewController, SFSafariViewControllerDelegate {
class InstanceViewController: UIViewController {
@IBOutlet var thumbnailImageView: UIImageView!
@IBOutlet var instanceDescriptionLabel: UILabel!
@IBOutlet var webViewButton: UIButton!
@ -79,3 +79,7 @@ class InstanceViewController: UIViewController, SFSafariViewControllerDelegate {
}
}
}
extension InstanceViewController: SFSafariViewControllerDelegate {
}

20
elpha-ios/InstancesTableViewController.swift

@ -10,7 +10,7 @@ import Alamofire
import CoreData
import UIKit
class InstancesTableViewController: UITableViewController, UIViewControllerPreviewingDelegate, NSFetchedResultsControllerDelegate {
class InstancesTableViewController: UITableViewController {
@IBOutlet var mainNavigationItem: UINavigationItem!
let fetchLimit = 20
@ -50,10 +50,6 @@ class InstancesTableViewController: UITableViewController, UIViewControllerPrevi
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.reloadData()
}
override func viewDidLoad() {
super.viewDidLoad()
initializeFetchedResultsController()
@ -142,6 +138,12 @@ class InstancesTableViewController: UITableViewController, UIViewControllerPrevi
}
}
}
}
extension InstancesTableViewController: UIViewControllerPreviewingDelegate {
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
navigationController?.pushViewController(viewControllerToCommit, animated: true)
}
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
guard let indexPath = tableView.indexPathForRow(at: location) else {
@ -160,8 +162,10 @@ class InstancesTableViewController: UITableViewController, UIViewControllerPrevi
return nil
}
}
public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) {
navigationController?.pushViewController(viewControllerToCommit, animated: true)
}
extension InstancesTableViewController: NSFetchedResultsControllerDelegate {
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.reloadData()
}
}

22
elpha-ios/MastodonAPI.swift

@ -277,6 +277,28 @@ class MastodonAPI {
}
}
static func pinnedStatuses(
accountID: String,
limit: Int?,
completion: @escaping (JSONObjectArray?, Error?) -> Void
) {
let parameters: Parameters = [
"only_media": false,
"exclude_replies": false,
"pinned": true,
"limit": limit ?? 20,
]
self.request(path: "api/v1/accounts/\(accountID)/statuses", method: .get, parameters: parameters) { data, pagination, error in
guard error == nil else {
completion(nil, error)
return
}
completion(data as? JSONObjectArray, nil)
}
}
static func favorites(
limit: Int?,
pagination: PaginationItem?,

129
elpha-ios/StatusTableViewController.swift

@ -14,7 +14,7 @@ enum StatusType {
case ancestor, status, descendant
}
class StatusTableViewController: UITableViewController {
class StatusTableViewController: UITableViewController, UIGestureRecognizerDelegate {
public var status: StatusMO? = nil
var ancestors: [StatusMO] = []
var descendants: [StatusMO] = []
@ -50,6 +50,74 @@ class StatusTableViewController: UITableViewController {
self.accountTapped(account: status!.account!)
}
@IBAction func boostTapped(_ sender: Any) {
if let status = status {
if status.reblogged {
status.reblogged = false
status.reblogsCount = status.reblogsCount - 1
MastodonAPI.unreblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Unboosted", category: .boosted)
}
} else {
status.reblogged = true
status.reblogsCount = status.reblogsCount + 1
MastodonAPI.reblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Boosted!", category: .boosted)
}
}
CoreDataManager.shared.saveContext()
}
}
@IBAction func favoriteTapped(_ sender: Any) {
if let status = status {
if status.favorited {
status.favorited = false
status.favoritesCount = status.favoritesCount - 1
MastodonAPI.unfavorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Unfavorited", category: .favorited)
}
} else {
status.favorited = true
status.favoritesCount = status.favoritesCount + 1
MastodonAPI.favorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Favorited!", category: .favorited)
}
}
CoreDataManager.shared.saveContext()
}
}
@objc func attachmentTappedSelf() {
self.attachmentTapped(status: status!)
}
func fetchStatuses(completion: @escaping (Error?) -> Void) {
if let status = status {
loading = true
@ -217,7 +285,14 @@ extension StatusTableViewController {
if let attachments = status.attachments, attachments.count > 0 {
cell.attachmentsView.isHidden = false
AttachmentsManager.setupAttachmentsView(cell.attachmentsView, withAttachments: attachments)
cell.attachmentsView.isUserInteractionEnabled = true
AttachmentManager.setupAttachmentView(cell.attachmentsView, withAttachments: attachments)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(attachmentTappedSelf))
cell.attachmentsView.addGestureRecognizer(tapGestureRecognizer)
tapGestureRecognizer.delegate = self
}
let dateFormatter = DateFormatter()
@ -285,51 +360,15 @@ extension StatusTableViewController: StatusViewDelegate {
}
}
func favoriteTapped(status: StatusMO) {
if status.favorited {
MastodonAPI.unfavorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Unfavorited", category: .favorited)
}
} else {
MastodonAPI.favorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Favorited!", category: .favorited)
}
}
func loadMoreTapped(status: StatusMO, direction: PaginationDirection) {
}
func reblogTapped(status: StatusMO) {
if status.reblogged {
MastodonAPI.unreblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Unboosted", category: .boosted)
}
} else {
MastodonAPI.reblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Boosted!", category: .boosted)
}
func attachmentTapped(status: StatusMO) {
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "AttachmentPageViewController") as? AttachmentPageViewController {
controller.status = status
self.navigationController?.pushViewController(controller, animated: true)
}
}
func loadMoreTapped(status: StatusMO, direction: PaginationDirection) {
}
}

84
elpha-ios/StatusView.swift

@ -12,12 +12,11 @@ import UIKit
protocol StatusViewDelegate {
func accountTapped(account: AccountMO)
func statusTapped(status: StatusMO)
func favoriteTapped(status: StatusMO)
func reblogTapped(status: StatusMO)
func loadMoreTapped(status: StatusMO, direction: PaginationDirection)
func attachmentTapped(status: StatusMO)
}
class StatusView: UIView {
class StatusView: UIView, UIGestureRecognizerDelegate {
@IBOutlet var contentView: UIView!
@IBOutlet var boostView: UIView!
@IBOutlet var boostAvatarImageView: UIImageView!
@ -106,17 +105,65 @@ class StatusView: UIView {
}
@IBAction func boostTapped(_ sender: Any) {
if let delegate = delegate, let status = status {
delegate.reblogTapped(status: status)
status.reblogged = !status.reblogged
if let status = status {
if status.reblogged {
status.reblogged = false
status.reblogsCount = status.reblogsCount - 1
MastodonAPI.unreblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Unboosted", category: .boosted)
}
} else {
status.reblogged = true
status.reblogsCount = status.reblogsCount + 1
MastodonAPI.reblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Boosted!", category: .boosted)
}
}
CoreDataManager.shared.saveContext()
}
}
@IBAction func favoriteTapped(_ sender: Any) {
if let delegate = delegate, let status = status {
delegate.favoriteTapped(status: status)
status.favorited = !status.favorited
if let status = status {
if status.favorited {
status.favorited = false
status.favoritesCount = status.favoritesCount - 1
MastodonAPI.unfavorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Unfavorited", category: .favorited)
}
} else {
status.favorited = true
status.favoritesCount = status.favoritesCount + 1
MastodonAPI.favorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Favorited!", category: .favorited)
}
}
CoreDataManager.shared.saveContext()
}
}
@ -129,6 +176,16 @@ class StatusView: UIView {
}
}
@objc func attachmentTapped() {
if let delegate = delegate, let status = status {
if let reblog = status.reblog {
delegate.attachmentTapped(status: reblog)
} else {
delegate.attachmentTapped(status: status)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
@ -173,7 +230,14 @@ class StatusView: UIView {
if let attachments = status.attachments, attachments.count > 0 {
attachmentsView.isHidden = false
AttachmentsManager.setupAttachmentsView(attachmentsView, withAttachments: attachments)
attachmentsView.isUserInteractionEnabled = true
AttachmentManager.setupAttachmentView(attachmentsView, withAttachments: attachments)
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(attachmentTapped))
attachmentsView.addGestureRecognizer(tapGestureRecognizer)
tapGestureRecognizer.delegate = self
}
if let content = status.content {

81
elpha-ios/TimelineTableViewController.swift

@ -10,7 +10,7 @@ import AlamofireImage
import CoreData
import UIKit
class TimelineTableViewController: UITableViewController, TimelinesViewControllerDelegate {
class TimelineTableViewController: UITableViewController {
var fetchedResultsController: NSFetchedResultsController<StatusMO>? = nil
let fetchLimit = 20
@ -90,15 +90,6 @@ class TimelineTableViewController: UITableViewController, TimelinesViewControlle
}
}
func didSelect(timeline: TimelineMO) {
navigationItem.title = timeline.name
AuthenticationManager.session?.timeline = timeline
CoreDataManager.shared.saveContext()
initializeFetchedResultsController()
tableView.reloadData()
}
func createDefaultTimelines(account: AccountMO) {
let homeTimeline = TimelineMO(context: CoreDataManager.shared.context)
homeTimeline.name = "Home"
@ -382,69 +373,32 @@ extension TimelineTableViewController {
}
}
extension TimelineTableViewController: TimelinesViewControllerDelegate {
func didSelect(timeline: TimelineMO) {
navigationItem.title = timeline.name
AuthenticationManager.session?.timeline = timeline
CoreDataManager.shared.saveContext()
initializeFetchedResultsController()
tableView.reloadData()
}
}
extension TimelineTableViewController: StatusViewDelegate {
func accountTapped(account: AccountMO) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let controller = storyboard.instantiateViewController(withIdentifier: "AccountTableViewController") as? AccountTableViewController {
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "AccountTableViewController") as? AccountTableViewController {
controller.account = account
self.navigationController?.pushViewController(controller, animated: true)
}
}
func statusTapped(status: StatusMO) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
if let controller = storyboard.instantiateViewController(withIdentifier: "StatusTableViewController") as? StatusTableViewController {
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "StatusTableViewController") as? StatusTableViewController {
controller.status = status
self.navigationController?.pushViewController(controller, animated: true)
}
}
func favoriteTapped(status: StatusMO) {
if status.favorited {
MastodonAPI.unfavorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Unfavorited", category: .favorited)
}
} else {
MastodonAPI.favorite(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Favorited!", category: .favorited)
}
}
}
func reblogTapped(status: StatusMO) {
if status.reblogged {
MastodonAPI.unreblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Unboosted", category: .boosted)
}
} else {
MastodonAPI.reblog(statusID: status.id!) { error in
guard error == nil else {
AlertManager.shared.show(message: error!.localizedDescription, category: .error)
return
}
AlertManager.shared.show(message: "Boosted!", category: .boosted)
}
}
}
func loadMoreTapped(status: StatusMO, direction: PaginationDirection) {
if let markers = status.markers {
markers.forEach { marker in
@ -458,4 +412,11 @@ extension TimelineTableViewController: StatusViewDelegate {
}
}
}
func attachmentTapped(status: StatusMO) {
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "AttachmentPageViewController") as? AttachmentPageViewController {
controller.status = status
self.navigationController?.pushViewController(controller, animated: true)
}
}
}

52
elpha-ios/TimelinesViewController.swift

@ -13,7 +13,7 @@ protocol TimelinesViewControllerDelegate {
func didSelect(timeline: TimelineMO)
}
class TimelinesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
class TimelinesViewController: UIViewController {
@IBOutlet var tableView: UITableView!
var delegate: TimelinesViewControllerDelegate? = nil
var fetchedResultsController: NSFetchedResultsController<TimelineMO>? = nil
@ -51,7 +51,33 @@ class TimelinesViewController: UIViewController, UITableViewDataSource, UITableV
print("\(error)")
}
}
}
extension TimelinesViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case NSFetchedResultsChangeType.insert:
tableView.insertRows(at: [newIndexPath!], with: UITableView.RowAnimation.fade)
case NSFetchedResultsChangeType.delete:
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.fade)
case NSFetchedResultsChangeType.update:
return
case NSFetchedResultsChangeType.move:
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.fade)
tableView.insertRows(at: [newIndexPath!], with: UITableView.RowAnimation.fade)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
extension TimelinesViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
@ -109,27 +135,3 @@ class TimelinesViewController: UIViewController, UITableViewDataSource, UITableV
dismiss(animated: true)
}
}
extension TimelinesViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case NSFetchedResultsChangeType.insert:
tableView.insertRows(at: [newIndexPath!], with: UITableView.RowAnimation.fade)
case NSFetchedResultsChangeType.delete:
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.fade)
case NSFetchedResultsChangeType.update:
return
case NSFetchedResultsChangeType.move:
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.fade)
tableView.insertRows(at: [newIndexPath!], with: UITableView.RowAnimation.fade)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
Loading…
Cancel
Save