Browse Source

Refactor status table view controllers and add refresh timelines setting

master
Dwayne Harris 6 years ago
parent
commit
fc296cd8de
  1. 54
      elpha-ios/AbstractStatusTableViewController.swift
  2. 2
      elpha-ios/AccountTableViewCell.swift
  3. 69
      elpha-ios/AccountTableViewController.swift
  4. 79
      elpha-ios/Base.lproj/Main.storyboard
  5. 17
      elpha-ios/SettingsManager.swift
  6. 6
      elpha-ios/SettingsTableViewController.swift
  7. 2
      elpha-ios/StatusTableViewCell.swift
  8. 262
      elpha-ios/StatusTableViewController.swift
  9. 2
      elpha-ios/TimelineTableViewCell.swift
  10. 53
      elpha-ios/TimelineTableViewController.swift

54
elpha-ios/AbstractStatusTableViewController.swift

@ -6,13 +6,18 @@
// Copyright © 2018 Elpha. All rights reserved.
//
import CoreData
import UIKit
import SafariServices
protocol AbstractStatusTableViewCell {
var statusView: StatusView! { get set }
}
class AbstractStatusTableViewController: UITableViewController, StatusViewDelegate {
let fetchLimit = 20
var currentPaginationContext: String = ""
var cellHeightsDictionary: NSMutableDictionary = [:]
var fetchedResultsController: NSFetchedResultsController<StatusMO>? = nil
var loading: Bool = false {
didSet {
@ -77,6 +82,53 @@ class AbstractStatusTableViewController: UITableViewController, StatusViewDelega
func hideTapped() {}
func boostTapped() {}
func favoriteTapped() {}
func updateCell(_ cell: AbstractStatusTableViewCell, withStatusAt indexPath: IndexPath) {
guard let status = fetchedResultsController?.object(at: indexPath) else {
fatalError("CoreData error")
}
cell.statusView.update(withStatus: status)
cell.statusView.topDividerView.isHidden = indexPath.row == 0
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 {
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
}
}
}
extension AbstractStatusTableViewController: SFSafariViewControllerDelegate {

2
elpha-ios/AccountTableViewCell.swift

@ -8,6 +8,6 @@
import UIKit
class AccountTableViewCell: UITableViewCell {
class AccountTableViewCell: UITableViewCell, AbstractStatusTableViewCell {
@IBOutlet var statusView: StatusView!
}

69
elpha-ios/AccountTableViewController.swift

@ -74,8 +74,7 @@ class AccountTableViewController: AbstractStatusTableViewController {
@IBOutlet var statusTypeSegmentedControl: UISegmentedControl!
@IBOutlet var contentTextView: UITextViewFixed!
@IBOutlet var fieldTableView: UITableView!
var fetchedResultsController: NSFetchedResultsController<StatusMO>? = nil
var account: AccountMO? = nil
var fieldTableViewController: FieldTableViewController? = nil
@ -320,8 +319,28 @@ extension AccountTableViewController: 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.none)
case NSFetchedResultsChangeType.delete:
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.none)
case NSFetchedResultsChangeType.update:
if let cell = tableView.cellForRow(at: indexPath!) as? AccountTableViewCell {
updateCell(cell, withStatusAt: indexPath!)
}
case NSFetchedResultsChangeType.move:
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.fade)
tableView.insertRows(at: [newIndexPath!], with: UITableView.RowAnimation.fade)
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.reloadData()
tableView.endUpdates()
}
override func numberOfSections(in tableView: UITableView) -> Int {
@ -351,50 +370,8 @@ extension AccountTableViewController: NSFetchedResultsControllerDelegate {
fatalError("Unable to find reusable cell")
}
guard let status = fetchedResultsController?.object(at: indexPath) else {
fatalError("CoreData error")
}
updateCell(cell, withStatusAt: indexPath)
cell.statusView.delegate = self
cell.statusView.update(withStatus: status)
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 {
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
}

79
elpha-ios/Base.lproj/Main.storyboard

@ -848,7 +848,7 @@
</constraints>
</view>
<view key="tableFooterView" contentMode="scaleToFill" id="ldk-rb-fMp">
<rect key="frame" x="0.0" y="379" width="375" height="78"/>
<rect key="frame" x="0.0" y="423" width="375" height="78"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="Logo" translatesAutoresizingMaskIntoConstraints="NO" id="jPl-Xz-kzi">
@ -883,7 +883,7 @@
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C0x-LV-N9f">
<rect key="frame" x="311" y="6.3333333333333321" width="51" height="30.999999999999996"/>
<color key="onTintColor" name="Primary"/>
<color key="onTintColor" name="Secondary"/>
<connections>
<action selector="gifChanged:" destination="uE6-HE-IqH" eventType="valueChanged" id="hPj-3q-Ydq"/>
</connections>
@ -898,9 +898,39 @@
</constraints>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="h71-4T-hjk">
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="VSP-oq-u7i">
<rect key="frame" x="0.0" y="291" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VSP-oq-u7i" id="Fxa-bc-hk1">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.666666666666664"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Automatically refresh Timelines" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ogq-nL-LaO">
<rect key="frame" x="16" y="14" width="287" height="15.666666666666664"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<color key="textColor" name="Primary"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="vhI-B5-5SO">
<rect key="frame" x="311" y="6.3333333333333321" width="51" height="30.999999999999996"/>
<color key="onTintColor" name="Secondary"/>
<connections>
<action selector="refreshTimelinesChanged:" destination="uE6-HE-IqH" eventType="valueChanged" id="5vD-JF-YP4"/>
</connections>
</switch>
</subviews>
<constraints>
<constraint firstItem="vhI-B5-5SO" firstAttribute="centerY" secondItem="Fxa-bc-hk1" secondAttribute="centerY" id="Dma-Dt-x6i"/>
<constraint firstItem="ogq-nL-LaO" firstAttribute="centerY" secondItem="Fxa-bc-hk1" secondAttribute="centerY" id="mKE-tf-uIz"/>
<constraint firstItem="vhI-B5-5SO" firstAttribute="leading" secondItem="ogq-nL-LaO" secondAttribute="trailing" constant="8" id="mcl-tp-Xq2"/>
<constraint firstAttribute="trailing" secondItem="vhI-B5-5SO" secondAttribute="trailing" constant="15" id="s0F-GC-Sip"/>
<constraint firstItem="ogq-nL-LaO" firstAttribute="leading" secondItem="Fxa-bc-hk1" secondAttribute="leadingMargin" id="zwe-aB-VLV"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="h71-4T-hjk">
<rect key="frame" x="0.0" y="335" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="h71-4T-hjk" id="elQ-iL-6A9">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.666666666666664"/>
<autoresizingMask key="autoresizingMask"/>
@ -919,14 +949,14 @@
</constraints>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="VSP-oq-u7i">
<rect key="frame" x="0.0" y="335" width="375" height="44"/>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" id="buH-Mv-FHu">
<rect key="frame" x="0.0" y="379" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="VSP-oq-u7i" id="Fxa-bc-hk1">
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="buH-Mv-FHu" id="Xya-rO-5Dy">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.666666666666664"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Clear Data Store" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ogq-nL-LaO">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Clear Data Store" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="K77-zb-cI5">
<rect key="frame" x="16" y="14" width="343" height="15.666666666666664"/>
<fontDescription key="fontDescription" type="system" pointSize="13"/>
<color key="textColor" red="1" green="0.14913141730000001" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@ -934,9 +964,9 @@
</label>
</subviews>
<constraints>
<constraint firstAttribute="trailingMargin" secondItem="ogq-nL-LaO" secondAttribute="trailing" id="UxT-2R-LfY"/>
<constraint firstItem="ogq-nL-LaO" firstAttribute="centerY" secondItem="Fxa-bc-hk1" secondAttribute="centerY" id="mKE-tf-uIz"/>
<constraint firstItem="ogq-nL-LaO" firstAttribute="leading" secondItem="Fxa-bc-hk1" secondAttribute="leadingMargin" id="zwe-aB-VLV"/>
<constraint firstItem="K77-zb-cI5" firstAttribute="leading" secondItem="Xya-rO-5Dy" secondAttribute="leadingMargin" id="15U-0U-2bm"/>
<constraint firstAttribute="trailingMargin" secondItem="K77-zb-cI5" secondAttribute="trailing" id="JR4-d4-1oL"/>
<constraint firstItem="K77-zb-cI5" firstAttribute="centerY" secondItem="Xya-rO-5Dy" secondAttribute="centerY" id="otv-kZ-aQs"/>
</constraints>
</tableViewCellContentView>
</tableViewCell>
@ -953,6 +983,7 @@
<outlet property="displayNameLabel" destination="GQi-GM-PKI" id="zLZ-WU-NVK"/>
<outlet property="gifSwitch" destination="C0x-LV-N9f" id="YRg-aI-jIf"/>
<outlet property="headerImageView" destination="X8g-yM-3O2" id="cbm-aS-gTF"/>
<outlet property="refreshTimelinesSwitch" destination="vhI-B5-5SO" id="UkT-HP-Ssl"/>
<outlet property="usernameLabel" destination="qd7-N9-7cC" id="XXg-Re-px7"/>
</connections>
</tableViewController>
@ -1316,15 +1347,15 @@
<scene sceneID="E93-GN-Ar4">
<objects>
<tableViewController storyboardIdentifier="StatusTableViewController" id="RAJ-ub-len" customClass="StatusTableViewController" customModule="elpha_ios" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="grouped" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="18" sectionFooterHeight="18" id="3Kx-zr-iiy">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="none" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="3Kx-zr-iiy">
<rect key="frame" x="0.0" y="0.0" width="375" height="812"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" cocoaTouchSystemColor="groupTableViewBackgroundColor"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="MainStatusTableViewCell" rowHeight="582" id="8ys-Fm-1Yg" customClass="MainStatusTableViewCell" customModule="elpha_ios" customModuleProvider="target">
<rect key="frame" x="0.0" y="55.333333333333314" width="375" height="582"/>
<rect key="frame" x="0.0" y="28" width="375" height="582"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="8ys-Fm-1Yg" id="Hj7-kC-H7L">
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="8ys-Fm-1Yg" id="Hj7-kC-H7L">
<rect key="frame" x="0.0" y="0.0" width="375" height="582"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
@ -1351,7 +1382,7 @@
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="Background Secondary"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="LHS-vw-fZE" firstAttribute="top" secondItem="7I8-6E-s1Q" secondAttribute="top" id="BGV-3e-Lcz"/>
@ -1391,7 +1422,7 @@
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="Background Secondary"/>
<constraints>
<constraint firstItem="me9-4K-dS0" firstAttribute="centerY" secondItem="C8A-x8-8TG" secondAttribute="centerY" id="Ptv-cd-e1w"/>
<constraint firstItem="peo-RN-z7p" firstAttribute="top" secondItem="C8A-x8-8TG" secondAttribute="top" id="YL7-OU-RIQ"/>
@ -1422,7 +1453,7 @@
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="Background Secondary"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="3Ew-31-ZWY" secondAttribute="trailing" id="3Jq-Ho-6AR"/>
<constraint firstAttribute="height" constant="30" id="FpV-XH-nDq"/>
@ -1449,7 +1480,7 @@
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="Background Secondary"/>
<gestureRecognizers/>
<constraints>
<constraint firstAttribute="width" constant="60" id="Re8-4t-uoD"/>
@ -1480,7 +1511,7 @@
</constraints>
</imageView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="Background Secondary"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="06U-FB-yto" firstAttribute="leading" secondItem="Jcf-Cv-8ki" secondAttribute="trailing" constant="8" id="7La-R0-13y"/>
@ -1503,7 +1534,7 @@
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" scrollEnabled="NO" editable="NO" text="Content" textAlignment="natural" translatesAutoresizingMaskIntoConstraints="NO" id="Qno-L9-nUh" customClass="UITextViewFixed" customModule="elpha_ios" customModuleProvider="target">
<rect key="frame" x="8" y="0.0" width="359" height="32"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="Background Secondary"/>
<color key="tintColor" name="Primary"/>
<color key="textColor" name="Text"/>
<fontDescription key="fontDescription" type="system" pointSize="16"/>
@ -1511,7 +1542,7 @@
<dataDetectorType key="dataDetectorTypes" phoneNumber="YES" link="YES" address="YES" calendarEvent="YES" shipmentTrackingNumber="YES" flightNumber="YES" lookupSuggestion="YES"/>
</textView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<color key="backgroundColor" name="Background Secondary"/>
<constraints>
<constraint firstAttribute="bottom" secondItem="Qno-L9-nUh" secondAttribute="bottom" id="M8W-95-H1q"/>
<constraint firstItem="Qno-L9-nUh" firstAttribute="top" secondItem="bsb-4z-qCl" secondAttribute="top" id="QpL-s9-Tq7"/>
@ -1529,6 +1560,7 @@
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" name="Background Secondary"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="7I8-6E-s1Q" secondAttribute="trailing" id="5hL-Ma-O1w"/>
<constraint firstItem="C8A-x8-8TG" firstAttribute="top" secondItem="hBh-J5-s0Y" secondAttribute="bottom" constant="15" id="6u3-CR-ugk"/>
@ -1566,7 +1598,7 @@
</connections>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" preservesSuperviewLayoutMargins="YES" selectionStyle="none" indentationWidth="10" reuseIdentifier="StatusTableViewCell" rowHeight="120" id="cLF-Rc-b4K" customClass="StatusTableViewCell" customModule="elpha_ios" customModuleProvider="target">
<rect key="frame" x="0.0" y="637.33333333333337" width="375" height="120"/>
<rect key="frame" x="0.0" y="610" width="375" height="120"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="cLF-Rc-b4K" id="jhE-cB-Cj1">
<rect key="frame" x="0.0" y="0.0" width="375" height="120"/>
@ -1822,6 +1854,9 @@
<namedColor name="Background Primary">
<color red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="Background Secondary">
<color red="0.93725490196078431" green="0.93725490196078431" blue="0.93725490196078431" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>
<namedColor name="Primary">
<color red="0.32549019607843138" green="0.20392156862745098" blue="0.55686274509803924" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</namedColor>

17
elpha-ios/SettingsManager.swift

@ -9,13 +9,26 @@
import Foundation
class SettingsManager {
private static let gifKey = "settings:gif"
private static let refreshTimelinesKey = "settings:refreshTimelines"
public static var automaticallyPlayGIFs: Bool {
get {
return UserDefaults.standard.bool(forKey: "settings:gif")
return UserDefaults.standard.bool(forKey: gifKey)
}
set {
UserDefaults.standard.set(newValue, forKey: "settings:gif")
UserDefaults.standard.set(newValue, forKey: gifKey)
}
}
public static var automaticallyRefreshTimelines: Bool {
get {
return UserDefaults.standard.bool(forKey: refreshTimelinesKey)
}
set {
UserDefaults.standard.set(newValue, forKey: refreshTimelinesKey)
}
}
}

6
elpha-ios/SettingsTableViewController.swift

@ -15,6 +15,7 @@ class SettingsTableViewController: UITableViewController {
@IBOutlet var displayNameLabel: UILabel!
@IBOutlet var usernameLabel: UILabel!
@IBOutlet var gifSwitch: UISwitch!
@IBOutlet var refreshTimelinesSwitch: UISwitch!
override func viewDidLoad() {
super.viewDidLoad()
@ -34,6 +35,7 @@ class SettingsTableViewController: UITableViewController {
}
gifSwitch.isOn = SettingsManager.automaticallyPlayGIFs
refreshTimelinesSwitch.isOn = SettingsManager.automaticallyRefreshTimelines
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@ -52,4 +54,8 @@ class SettingsTableViewController: UITableViewController {
@IBAction func gifChanged(_ sender: Any) {
SettingsManager.automaticallyPlayGIFs = gifSwitch.isOn
}
@IBAction func refreshTimelinesChanged(_ sender: Any) {
SettingsManager.automaticallyRefreshTimelines = refreshTimelinesSwitch.isOn
}
}

2
elpha-ios/StatusTableViewCell.swift

@ -8,6 +8,6 @@
import UIKit
class StatusTableViewCell: UITableViewCell {
class StatusTableViewCell: UITableViewCell, AbstractStatusTableViewCell {
@IBOutlet var statusView: StatusView!
}

262
elpha-ios/StatusTableViewController.swift

@ -11,15 +11,8 @@ import Kingfisher
import UIKit
import SafariServices
enum StatusType {
case ancestor, status, descendant
}
class StatusTableViewController: AbstractStatusTableViewController, UIGestureRecognizerDelegate {
public var status: StatusMO? = nil
var ancestors: [StatusMO] = []
var descendants: [StatusMO] = []
var feedbackGenerator: UINotificationFeedbackGenerator? = nil
override func viewDidLoad() {
@ -27,11 +20,12 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec
feedbackGenerator = UINotificationFeedbackGenerator()
navigationItem.title = "Toot"
initializeFetchedResultsController()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
loadStatuses()
fetchStatuses { error in
if error != nil {
@ -162,8 +156,6 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec
CoreDataManager.shared.saveContext()
self.loading = false
self.loadStatuses()
completion(nil)
}
}
@ -171,7 +163,85 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec
}
}
func loadStatuses() {
override func updateCell(_ cell: AbstractStatusTableViewCell, withStatusAt indexPath: IndexPath) {
guard let status = fetchedResultsController?.object(at: indexPath) else {
fatalError("CoreData error")
}
cell.statusView.delegate = self
cell.statusView.update(withStatus: status)
cell.statusView.topDividerView.isHidden = indexPath.row == 0
cell.statusView.bottomDividerView.isHidden = false
cell.statusView.topLoadMoreView.isHidden = true
cell.statusView.bottomLoadMoreView.isHidden = true
cell.statusView.replyView.isHidden = true
}
func updateMainCell(_ cell: MainStatusTableViewCell, withStatus status: StatusMO) {
cell.avatarImageView.setRoundedCorners()
func updateAccountView(status: StatusMO) {
if let account = status.account {
let options: KingfisherOptionsInfo = SettingsManager.automaticallyPlayGIFs ? [] : [.onlyLoadFirstFrame]
cell.avatarImageView.kf.setImage(with: account.avatarURL!, options: options)
cell.displayNameLabel.text = account.displayName
cell.usernameLabel.text = "@\(account.acct!)"
}
}
if let reblog = status.reblog {
updateAccountView(status: reblog)
} else {
updateAccountView(status: status)
}
if let content = status.content {
cell.contentTextView.attributedText = content.htmlAttributed(size: 16)
}
cell.attachmentsView.backgroundColor = UIColor.white
cell.attachmentsView.isHidden = true
if let attachments = status.attachments, attachments.count > 0 {
cell.attachmentsView.isHidden = false
cell.attachmentsView.isUserInteractionEnabled = true
let attachmentManager = AttachmentManager()
attachmentManager.delegate = self
attachmentManager.setupAttachmentView(cell.attachmentsView, withAttachments: attachments)
}
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
cell.timestampDateLabel.text = dateFormatter.string(from: status.createdAt!)
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .short
cell.timestampTimeLabel.text = dateFormatter.string(from: status.createdAt!)
cell.repliesLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.repliesCount), number: .decimal)
cell.boostsLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.reblogsCount), number: .decimal)
cell.favoritesLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.favoritesCount), number: .decimal)
if status.reblogged {
cell.boostsImageView.image = UIImage(named: "Boost Bold")
} else {
cell.boostsImageView.image = UIImage(named: "Boost Regular")
}
if status.favorited {
cell.favoritesImageView.image = UIImage(named: "Star Filled")
} else {
cell.favoritesImageView.image = UIImage(named: "Star Regular")
}
}
}
extension StatusTableViewController: NSFetchedResultsControllerDelegate {
func initializeFetchedResultsController() {
guard let status = status else {
return
}
@ -179,54 +249,57 @@ class StatusTableViewController: AbstractStatusTableViewController, UIGestureRec
let request = NSFetchRequest<StatusMO>(entityName: "Status")
request.predicate = NSPredicate(format: "id == %@ OR ANY ancestors == %@ OR ANY descendants == %@", status.id!, status, status)
request.sortDescriptors = [
NSSortDescriptor(key: "createdAt", ascending: false),
NSSortDescriptor(key: "createdAt", ascending: true),
]
ancestors = []
descendants = []
fetchedResultsController = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: CoreDataManager.shared.context,
sectionNameKeyPath: nil,
cacheName: nil
)
fetchedResultsController!.delegate = self
do {
let statuses = try CoreDataManager.shared.context.fetch(request)
statuses.forEach { s in
switch s.createdAt!.compare(status.createdAt!) {
case .orderedAscending:
ancestors.append(s)
case .orderedSame:
return
case .orderedDescending:
descendants.append(s)
}
}
ancestors.reverse()
descendants.reverse()
self.tableView.reloadData()
try fetchedResultsController!.performFetch()
} catch {
print("\(error)")
}
}
override func boostTapped() {
loadStatuses()
}
override func favoriteTapped() {
loadStatuses()
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
override func revealTapped() {
loadStatuses()
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.none)
case NSFetchedResultsChangeType.delete:
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.none)
case NSFetchedResultsChangeType.update:
if let cell = tableView.cellForRow(at: indexPath!) as? StatusTableViewCell {
updateCell(cell, withStatusAt: indexPath!)
}
if let cell = tableView.cellForRow(at: indexPath!) as? MainStatusTableViewCell {
updateMainCell(cell, withStatus: anObject as! StatusMO)
}
case NSFetchedResultsChangeType.move:
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.fade)
tableView.insertRows(at: [newIndexPath!], with: UITableView.RowAnimation.fade)
}
}
override func hideTapped() {
loadStatuses()
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
extension StatusTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 3
return 1
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
@ -236,45 +309,28 @@ extension StatusTableViewController {
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
switch indexPath.section {
case 0: return CellHeightManager.get(status: self.ancestors[indexPath.row])
case 2: return CellHeightManager.get(status: self.descendants[indexPath.row])
default: return UITableView.automaticDimension
guard let status = fetchedResultsController?.object(at: indexPath) else {
return UITableView.automaticDimension
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let height = CGFloat(25)
switch section {
case 0: return ancestors.count > 0 ? height : 0
case 2: return descendants.count > 0 ? height : 0
default: return height
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch section {
case 0: return ancestors.count > 0 ? "In Reply To" : nil
case 2: return descendants.count > 0 ? "Replies" : nil
default: return "Toot"
if status == self.status {
return UITableView.automaticDimension
}
return CellHeightManager.get(status: status)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0: return ancestors.count
case 2: return descendants.count
default: return 1
guard let count = fetchedResultsController?.fetchedObjects?.count else {
return 0
}
return count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var status: StatusMO
switch indexPath.section {
case 0: status = self.ancestors[indexPath.row]
case 2: status = self.descendants[indexPath.row]
default: status = self.status!
guard let status = fetchedResultsController?.object(at: indexPath) else {
fatalError("CoreData error")
}
if status == self.status {
@ -282,66 +338,9 @@ extension StatusTableViewController {
fatalError("Unable to find reusable cell")
}
updateMainCell(cell, withStatus: status)
cell.contentTextView.delegate = self
cell.avatarImageView.setRoundedCorners()
func updateAccountView(status: StatusMO) {
if let account = status.account {
let options: KingfisherOptionsInfo = SettingsManager.automaticallyPlayGIFs ? [] : [.onlyLoadFirstFrame]
cell.avatarImageView.kf.setImage(with: account.avatarURL!, options: options)
cell.displayNameLabel.text = account.displayName
cell.usernameLabel.text = "@\(account.acct!)"
}
}
if let reblog = status.reblog {
updateAccountView(status: reblog)
} else {
updateAccountView(status: status)
}
if let content = status.content {
cell.contentTextView.attributedText = content.htmlAttributed(size: 16)
}
cell.attachmentsView.backgroundColor = UIColor.white
cell.attachmentsView.isHidden = true
if let attachments = status.attachments, attachments.count > 0 {
cell.attachmentsView.isHidden = false
cell.attachmentsView.isUserInteractionEnabled = true
let attachmentManager = AttachmentManager()
attachmentManager.delegate = self
attachmentManager.setupAttachmentView(cell.attachmentsView, withAttachments: attachments)
}
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
cell.timestampDateLabel.text = dateFormatter.string(from: status.createdAt!)
dateFormatter.dateStyle = .none
dateFormatter.timeStyle = .short
cell.timestampTimeLabel.text = dateFormatter.string(from: status.createdAt!)
cell.repliesLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.repliesCount), number: .decimal)
cell.boostsLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.reblogsCount), number: .decimal)
cell.favoritesLabel.text = NumberFormatter.localizedString(from: NSNumber(value: status.favoritesCount), number: .decimal)
if status.reblogged {
cell.boostsImageView.image = UIImage(named: "Boost Bold")
} else {
cell.boostsImageView.image = UIImage(named: "Boost Regular")
}
if status.favorited {
cell.favoritesImageView.image = UIImage(named: "Star Filled")
} else {
cell.favoritesImageView.image = UIImage(named: "Star Regular")
}
return cell
} else {
@ -349,13 +348,8 @@ extension StatusTableViewController {
fatalError("Unable to find reusable cell")
}
updateCell(cell, withStatusAt: indexPath)
cell.statusView.delegate = self
cell.statusView.update(withStatus: status)
cell.statusView.topDividerView.isHidden = indexPath.row == 0
cell.statusView.bottomDividerView.isHidden = false
cell.statusView.topLoadMoreView.isHidden = true
cell.statusView.bottomLoadMoreView.isHidden = true
cell.statusView.replyView.isHidden = true
return cell
}

2
elpha-ios/TimelineTableViewCell.swift

@ -8,6 +8,6 @@
import UIKit
class TimelineTableViewCell: UITableViewCell {
class TimelineTableViewCell: UITableViewCell, AbstractStatusTableViewCell {
@IBOutlet var statusView: StatusView!
}

53
elpha-ios/TimelineTableViewController.swift

@ -13,7 +13,6 @@ import SafariServices
class TimelineTableViewController: AbstractStatusTableViewController {
var feedbackGenerator: UINotificationFeedbackGenerator? = nil
var fetchedResultsController: NSFetchedResultsController<StatusMO>? = nil
override var currentPaginationContext: String {
get {
@ -77,6 +76,10 @@ class TimelineTableViewController: AbstractStatusTableViewController {
if fetchedResultsController?.fetchedObjects?.count ?? 0 == 0 {
initializeFetchedResultsController()
}
if SettingsManager.automaticallyRefreshTimelines {
self.fetch()
}
}
override func loadMoreTapped(status: StatusMO, direction: PaginationDirection) {
@ -324,7 +327,7 @@ extension TimelineTableViewController: NSFetchedResultsControllerDelegate {
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.none)
case NSFetchedResultsChangeType.update:
if let cell = tableView.cellForRow(at: indexPath!) as? TimelineTableViewCell {
cell.statusView.update(withStatus: anObject as! StatusMO)
updateCell(cell, withStatusAt: indexPath!)
}
case NSFetchedResultsChangeType.move:
tableView.deleteRows(at: [indexPath!], with: UITableView.RowAnimation.fade)
@ -365,52 +368,8 @@ extension TimelineTableViewController {
fatalError("Unable to find reusable cell")
}
guard let status = fetchedResultsController?.object(at: indexPath) else {
fatalError("CoreData error")
}
updateCell(cell, withStatusAt: indexPath)
cell.statusView.delegate = self
cell.statusView.update(withStatus: status)
cell.statusView.topDividerView.isHidden = indexPath.row == 0
cell.statusView.bottomDividerView.isHidden = false
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 {
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
}

Loading…
Cancel
Save