Show pinned posts on profiles

Closes #53
This commit is contained in:
Shadowfacts 2019-11-17 10:44:42 -05:00
parent af65aa88e0
commit c91a7baaa6
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
3 changed files with 72 additions and 24 deletions

View File

@ -22,6 +22,13 @@ class ProfileTableViewController: EnhancedTableViewController {
}
}
var pinnedStatusIDs: [String] = [] {
didSet {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
var timelineSegments: [TimelineSegment<Status>] = [] {
didSet {
DispatchQueue.main.async {
@ -98,6 +105,13 @@ class ProfileTableViewController: EnhancedTableViewController {
updateUIForPreferences()
getStatuses(onlyPinned: true) { (response) in
guard case let .success(statuses, _) = response else { fatalError() }
MastodonCache.addAll(statuses: statuses)
self.pinnedStatusIDs = statuses.map { $0.id }
}
getStatuses() { response in
guard case let .success(statuses, pagination) = response else { fatalError() }
@ -114,8 +128,8 @@ class ProfileTableViewController: EnhancedTableViewController {
navigationItem.title = account.realDisplayName
}
func getStatuses(for range: RequestRange = .default, completion: @escaping Client.Callback<[Status]>) {
let request = Account.getStatuses(accountID, range: range, onlyMedia: false, pinned: false, excludeReplies: !Preferences.shared.showRepliesInProfiles)
func getStatuses(for range: RequestRange = .default, onlyPinned: Bool = false, completion: @escaping Client.Callback<[Status]>) {
let request = Account.getStatuses(accountID, range: range, onlyMedia: false, pinned: onlyPinned, excludeReplies: !Preferences.shared.showRepliesInProfiles)
MastodonController.client.run(request, completion: completion)
}
@ -128,27 +142,38 @@ class ProfileTableViewController: EnhancedTableViewController {
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1 + timelineSegments.count
// 1 section for header, 1 section for pinned, rest for timeline
return 2 + timelineSegments.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return accountID == nil || MastodonCache.account(for: accountID) == nil ? 0 : 1
} else if section == 1 {
return pinnedStatusIDs.count
} else {
return timelineSegments[section - 1].count
return timelineSegments[section - 2].count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
switch indexPath.section {
case 0:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "headerCell", for: indexPath) as? ProfileHeaderTableViewCell else { fatalError() }
cell.selectionStyle = .none
cell.delegate = self
cell.updateUI(for: accountID)
return cell
} else {
case 1:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() }
let statusID = timelineSegments[indexPath.section - 1][indexPath.row]
let statusID = pinnedStatusIDs[indexPath.row]
cell.showPinned = true
cell.updateUI(statusID: statusID)
cell.delegate = self
return cell
default:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "statusCell", for: indexPath) as? StatusTableViewCell else { fatalError() }
let statusID = timelineSegments[indexPath.section - 2][indexPath.row]
cell.updateUI(statusID: statusID)
cell.delegate = self
return cell
@ -156,14 +181,14 @@ class ProfileTableViewController: EnhancedTableViewController {
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if timelineSegments.count > 0 && indexPath.section == timelineSegments.count && indexPath.row == timelineSegments[indexPath.section - 1].count - 1 {
if timelineSegments.count > 0 && indexPath.section - 1 == timelineSegments.count && indexPath.row == timelineSegments[indexPath.section - 2].count - 1 {
guard let older = older else { return }
getStatuses(for: older) { response in
guard case let .success(newStatuses, pagination) = response else { fatalError() }
MastodonCache.addAll(statuses: newStatuses)
self.timelineSegments[indexPath.section - 1].append(objects: newStatuses)
self.timelineSegments[indexPath.section - 2].append(objects: newStatuses)
self.older = pagination?.older
}
@ -246,8 +271,8 @@ extension ProfileTableViewController: ProfileHeaderTableViewCellDelegate {
extension ProfileTableViewController: UITableViewDataSourcePrefetching {
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths where indexPath.section > 0 {
let statusID = timelineSegments[indexPath.section - 1][indexPath.row]
for indexPath in indexPaths where indexPath.section > 1 {
let statusID = timelineSegments[indexPath.section - 2][indexPath.row]
guard let status = MastodonCache.status(for: statusID) else { continue }
ImageCache.avatars.get(status.account.avatar, completion: nil)
for attachment in status.attachments {
@ -257,8 +282,8 @@ extension ProfileTableViewController: UITableViewDataSourcePrefetching {
}
func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths where indexPath.section > 0 {
let statusID = timelineSegments[indexPath.section - 1][indexPath.row]
for indexPath in indexPaths where indexPath.section > 1 {
let statusID = timelineSegments[indexPath.section - 2][indexPath.row]
guard let status = MastodonCache.status(for: statusID) else { continue }
ImageCache.avatars.cancel(status.account.avatar)
for attachment in status.attachments {

View File

@ -42,6 +42,7 @@ class StatusTableViewCell: UITableViewCell {
@IBOutlet weak var favoriteButton: UIButton!
@IBOutlet weak var reblogButton: UIButton!
@IBOutlet weak var moreButton: UIButton!
@IBOutlet weak var pinnedStackView: UIStackView!
var statusID: String!
var accountID: String!
@ -58,6 +59,7 @@ class StatusTableViewCell: UITableViewCell {
reblogButton.tintColor = reblogged ? UIColor(displayP3Red: 1, green: 0.8, blue: 0, alpha: 1) : tintColor
}
}
var showPinned: Bool = false
var collapsible = false {
didSet {
@ -153,6 +155,9 @@ class StatusTableViewCell: UITableViewCell {
setCollapsed(collapsible, animated: false)
contentWarningLabel.text = status.spoilerText
contentWarningLabel.isHidden = status.spoilerText.isEmpty
let pinned = status.pinned ?? false
pinnedStackView.isHidden = !(pinned && showPinned)
}
private func updateStatusState(status: Status) {
@ -226,6 +231,7 @@ class StatusTableViewCell: UITableViewCell {
updateTimestampWorkItem?.cancel()
updateTimestampWorkItem = nil
attachmentsView.subviews.forEach { $0.removeFromSuperview() }
showPinned = false
}
override func setSelected(_ selected: Bool, animated: Bool) {

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
@ -16,6 +16,21 @@
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" alignment="top" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="yNh-ac-v6c">
<rect key="frame" x="16" y="8" width="343" height="224"/>
<subviews>
<stackView hidden="YES" opaque="NO" contentMode="scaleToFill" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="3aF-uU-owK">
<rect key="frame" x="0.0" y="0.0" width="77" height="0.0"/>
<subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="pin.fill" catalog="system" translatesAutoresizingMaskIntoConstraints="NO" id="LRh-Cc-1br">
<rect key="frame" x="0.0" y="-0.5" width="20" height="1.5"/>
<color key="tintColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
</imageView>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Pinned" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="meL-En-2jB">
<rect key="frame" x="24" y="0.0" width="53" height="0.0"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" systemColor="secondaryLabelColor" red="0.23529411759999999" green="0.23529411759999999" blue="0.26274509800000001" alpha="0.59999999999999998" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</stackView>
<label opaque="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="252" verticalCompressionResistancePriority="751" text="Reblogged by Person" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lDH-50-AJZ">
<rect key="frame" x="0.0" y="0.0" width="163.5" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
@ -23,7 +38,7 @@
<nil key="highlightedColor"/>
</label>
<view contentMode="scaleToFill" verticalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="ve3-Y1-NQH">
<rect key="frame" x="0.0" y="28.5" width="343" height="165.5"/>
<rect key="frame" x="0.0" y="28.5" width="343" height="103.5"/>
<subviews>
<imageView contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="QMP-j2-HLn">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
@ -37,7 +52,7 @@
</constraints>
</imageView>
<stackView opaque="NO" contentMode="scaleToFill" verticalCompressionResistancePriority="751" axis="vertical" spacing="4" translatesAutoresizingMaskIntoConstraints="NO" id="gIY-Wp-RSk">
<rect key="frame" x="58" y="0.0" width="277" height="165.5"/>
<rect key="frame" x="58" y="0.0" width="277" height="103.5"/>
<subviews>
<stackView opaque="NO" contentMode="scaleToFill" spacing="8" translatesAutoresizingMaskIntoConstraints="NO" id="3Sm-P0-ySf">
<rect key="frame" x="0.0" y="0.0" width="277" height="20.5"/>
@ -101,7 +116,7 @@
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="TopLeft" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Content" textAlignment="natural" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HrJ-t9-KcD" customClass="StatusContentLabel" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="0.0" y="83" width="277" height="82.5"/>
<rect key="frame" x="0.0" y="83" width="277" height="20.5"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
@ -120,17 +135,17 @@
</constraints>
</view>
<view hidden="YES" contentMode="scaleToFill" verticalCompressionResistancePriority="1" translatesAutoresizingMaskIntoConstraints="NO" id="nbq-yr-2mA" customClass="AttachmentsContainerView" customModule="Tusker" customModuleProvider="target">
<rect key="frame" x="0.0" y="198" width="343" height="0.0"/>
<rect key="frame" x="0.0" y="136" width="343" height="0.0"/>
<color key="backgroundColor" systemColor="secondarySystemBackgroundColor" red="0.94901960780000005" green="0.94901960780000005" blue="0.96862745100000003" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" priority="999" constant="200" id="J42-49-2MU"/>
</constraints>
</view>
<stackView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" distribution="equalSpacing" alignment="bottom" translatesAutoresizingMaskIntoConstraints="NO" id="Zlb-yt-NTw">
<rect key="frame" x="0.0" y="202" width="343" height="22"/>
<rect key="frame" x="0.0" y="140" width="343" height="84"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" contentHorizontalAlignment="leading" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rKF-yF-KIa">
<rect key="frame" x="0.0" y="0.0" width="21" height="22"/>
<rect key="frame" x="0.0" y="62" width="21" height="22"/>
<accessibility key="accessibilityConfiguration" label="Reply"/>
<state key="normal" image="arrowshape.turn.up.left.fill" catalog="system"/>
<connections>
@ -138,7 +153,7 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="x0t-TR-jJ4">
<rect key="frame" x="107" y="0.0" width="22" height="22"/>
<rect key="frame" x="107" y="62" width="22" height="22"/>
<accessibility key="accessibilityConfiguration" label="Favorite"/>
<state key="normal" image="star.fill" catalog="system"/>
<connections>
@ -146,7 +161,7 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="6tW-z8-Qh9">
<rect key="frame" x="215.5" y="0.0" width="22.5" height="22"/>
<rect key="frame" x="215.5" y="62" width="22.5" height="22"/>
<accessibility key="accessibilityConfiguration" label="Reblog"/>
<state key="normal" image="repeat" catalog="system"/>
<connections>
@ -154,7 +169,7 @@
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="982-J4-NGl">
<rect key="frame" x="324" y="0.0" width="19" height="22"/>
<rect key="frame" x="324" y="62" width="19" height="22"/>
<accessibility key="accessibilityConfiguration" label="More Actions"/>
<state key="normal" image="ellipsis" catalog="system"/>
<connections>
@ -189,6 +204,7 @@
<outlet property="displayNameLabel" destination="gll-xe-FSr" id="63y-He-xy1"/>
<outlet property="favoriteButton" destination="x0t-TR-jJ4" id="Ohz-bs-Ebr"/>
<outlet property="moreButton" destination="982-J4-NGl" id="Xga-I4-CzK"/>
<outlet property="pinnedStackView" destination="3aF-uU-owK" id="O71-WE-G6U"/>
<outlet property="reblogButton" destination="6tW-z8-Qh9" id="i9h-QA-ZPd"/>
<outlet property="reblogLabel" destination="lDH-50-AJZ" id="uJf-Pt-cEP"/>
<outlet property="replyButton" destination="rKF-yF-KIa" id="rul-lk-bIR"/>
@ -202,6 +218,7 @@
<image name="arrowshape.turn.up.left.fill" catalog="system" width="64" height="52"/>
<image name="chevron.down" catalog="system" width="64" height="36"/>
<image name="ellipsis" catalog="system" width="64" height="18"/>
<image name="pin.fill" catalog="system" width="58" height="64"/>
<image name="repeat" catalog="system" width="64" height="48"/>
<image name="star.fill" catalog="system" width="64" height="58"/>
</resources>