Re-add public timeline descriptions
This commit is contained in:
parent
253fb8d27d
commit
a38c89a17f
|
@ -215,6 +215,7 @@
|
|||
D6AC956723C4347E008C9946 /* MainSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */; };
|
||||
D6ACE1AC240C3BAD004EA8E2 /* Ambassador.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65F613023AE99E000F3CFD3 /* Ambassador.framework */; };
|
||||
D6ACE1AD240C3BAD004EA8E2 /* Embassy.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D65F612D23AE990C00F3CFD3 /* Embassy.framework */; };
|
||||
D6ADB6E828E8C878009924AB /* PublicTimelineDescriptionCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ADB6E728E8C878009924AB /* PublicTimelineDescriptionCollectionViewCell.swift */; };
|
||||
D6AEBB3E2321638100E5038B /* UIActivity+Types.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */; };
|
||||
D6AEBB412321642700E5038B /* SendMesasgeActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */; };
|
||||
D6AEBB432321685E00E5038B /* OpenInSafariActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */; };
|
||||
|
@ -560,6 +561,7 @@
|
|||
D6A6C11425B62E9700298D0F /* CacheExpiry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheExpiry.swift; sourceTree = "<group>"; };
|
||||
D6A6C11A25B63CEE00298D0F /* MemoryCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryCache.swift; sourceTree = "<group>"; };
|
||||
D6AC956623C4347E008C9946 /* MainSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainSceneDelegate.swift; sourceTree = "<group>"; };
|
||||
D6ADB6E728E8C878009924AB /* PublicTimelineDescriptionCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublicTimelineDescriptionCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
D6AEBB3D2321638100E5038B /* UIActivity+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIActivity+Types.swift"; sourceTree = "<group>"; };
|
||||
D6AEBB402321642700E5038B /* SendMesasgeActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMesasgeActivity.swift; sourceTree = "<group>"; };
|
||||
D6AEBB422321685E00E5038B /* OpenInSafariActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInSafariActivity.swift; sourceTree = "<group>"; };
|
||||
|
@ -897,6 +899,7 @@
|
|||
D6BC9DD6232D7811002CA326 /* TimelinesPageViewController.swift */,
|
||||
D65234D225618EFA001AF9CF /* TimelineTableViewController.swift */,
|
||||
D66C900A28DAB7FD00217BF2 /* TimelineViewController.swift */,
|
||||
D6ADB6E728E8C878009924AB /* PublicTimelineDescriptionCollectionViewCell.swift */,
|
||||
);
|
||||
path = Timeline;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1889,6 +1892,7 @@
|
|||
D61A45E628DC0F2F002BE511 /* ConfirmLoadMoreCollectionViewCell.swift in Sources */,
|
||||
D663626C21361C6700C9CBA2 /* Account+Preferences.swift in Sources */,
|
||||
D67895C0246870DE00D4CD9E /* LocalAccountAvatarView.swift in Sources */,
|
||||
D6ADB6E828E8C878009924AB /* PublicTimelineDescriptionCollectionViewCell.swift in Sources */,
|
||||
D6333B372137838300CE884A /* AttributedString+Helpers.swift in Sources */,
|
||||
D622757824EE133700B82A16 /* ComposeAssetPicker.swift in Sources */,
|
||||
D681A29A249AD62D0085E54E /* LargeImageContentView.swift in Sources */,
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
//
|
||||
// PublicTimelineDescriptionCollectionViewCell.swift
|
||||
// Tusker
|
||||
//
|
||||
// Created by Shadowfacts on 10/1/22.
|
||||
// Copyright © 2022 Shadowfacts. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class PublicTimelineDescriptionCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
weak var mastodonController: MastodonController!
|
||||
|
||||
var local = false {
|
||||
didSet {
|
||||
updateLabel()
|
||||
}
|
||||
}
|
||||
var didDismiss: (() -> Void)?
|
||||
|
||||
private let label = UILabel()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
contentView.backgroundColor = .tintColor
|
||||
label.font = .preferredFont(forTextStyle: .body)
|
||||
label.numberOfLines = 0
|
||||
label.textColor = .white
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(label)
|
||||
NSLayoutConstraint.activate([
|
||||
label.leadingAnchor.constraint(equalToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1),
|
||||
contentView.trailingAnchor.constraint(equalToSystemSpacingAfter: label.trailingAnchor, multiplier: 1),
|
||||
label.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1),
|
||||
contentView.bottomAnchor.constraint(equalToSystemSpacingBelow: label.bottomAnchor, multiplier: 1),
|
||||
])
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func updateLabel() {
|
||||
let str = NSMutableAttributedString()
|
||||
let instanceStr = NSAttributedString(string: mastodonController.instanceURL.host!, attributes: [
|
||||
.font: UIFont(descriptor: .preferredFontDescriptor(withTextStyle: .body).withSymbolicTraits(.traitBold)!, size: 0)
|
||||
])
|
||||
if local {
|
||||
str.append(NSAttributedString(string: "The local timeline shows public posts from only "))
|
||||
str.append(instanceStr)
|
||||
str.append(NSAttributedString(string: "."))
|
||||
} else {
|
||||
str.append(NSAttributedString(string: "The federated timeline shows public posts from all users that "))
|
||||
str.append(instanceStr)
|
||||
str.append(NSAttributedString(string: " knows about."))
|
||||
}
|
||||
label.attributedText = str
|
||||
}
|
||||
|
||||
}
|
|
@ -22,6 +22,8 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
let confirmLoadMore = PassthroughSubject<Void, Never>()
|
||||
private var newer: RequestRange?
|
||||
private var older: RequestRange?
|
||||
// stored separately because i don't want to query the snapshot every time the user scrolls
|
||||
private var isShowingTimelineDescription = false
|
||||
|
||||
var collectionView: UICollectionView {
|
||||
view as! UICollectionView
|
||||
|
@ -63,6 +65,7 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
// collectionView.dragDelegate = self
|
||||
|
||||
registerTimelineLikeCells()
|
||||
collectionView.register(PublicTimelineDescriptionCollectionViewCell.self, forCellWithReuseIdentifier: "publicTimelineDescription")
|
||||
dataSource = createDataSource()
|
||||
applyInitialSnapshot()
|
||||
|
||||
|
@ -94,6 +97,16 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
config.text = try! doc.text()
|
||||
cell.contentConfiguration = config
|
||||
}
|
||||
let timelineDescriptionCell = UICollectionView.CellRegistration<PublicTimelineDescriptionCollectionViewCell, Item> { [unowned self] cell, indexPath, item in
|
||||
guard case .public(let local) = timeline else {
|
||||
fatalError()
|
||||
}
|
||||
cell.mastodonController = self.mastodonController
|
||||
cell.local = local
|
||||
cell.didDismiss = { [unowned self] in
|
||||
self.removeTimelineDescriptionCell()
|
||||
}
|
||||
}
|
||||
return UICollectionViewDiffableDataSource(collectionView: collectionView) { [unowned self] collectionView, indexPath, itemIdentifier in
|
||||
switch itemIdentifier {
|
||||
case .status(_, _):
|
||||
|
@ -102,13 +115,22 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
return loadingIndicatorCell(for: indexPath)
|
||||
case .confirmLoadMore:
|
||||
return confirmLoadMoreCell(for: indexPath)
|
||||
case .publicTimelineDescription:
|
||||
self.isShowingTimelineDescription = true
|
||||
return collectionView.dequeueConfiguredReusableCell(using: timelineDescriptionCell, for: indexPath, item: itemIdentifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func applyInitialSnapshot() {
|
||||
// TODO: this might not be necessary
|
||||
// TODO: yes it is, for public timeline descriptions
|
||||
if case .public(let local) = timeline,
|
||||
(local && !Preferences.shared.hasShownLocalTimelineDescription) ||
|
||||
(!local && Preferences.shared.hasShownFederatedTimelineDescription) {
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.appendSections([.header])
|
||||
snapshot.appendItems([.publicTimelineDescription], toSection: .header)
|
||||
dataSource.apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
|
@ -119,6 +141,13 @@ class TimelineViewController: UIViewController, TimelineLikeCollectionViewContro
|
|||
}
|
||||
}
|
||||
|
||||
private func removeTimelineDescriptionCell() {
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.deleteSections([.header])
|
||||
dataSource.apply(snapshot, animatingDifferences: true)
|
||||
isShowingTimelineDescription = false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension TimelineViewController {
|
||||
|
@ -135,8 +164,7 @@ extension TimelineViewController {
|
|||
case status(id: String, state: StatusState)
|
||||
case loadingIndicator
|
||||
case confirmLoadMore
|
||||
// // TODO: remove local param from this
|
||||
// case publicTimelineDescription(local: Bool)
|
||||
case publicTimelineDescription
|
||||
|
||||
static func fromTimelineItem(_ id: String) -> Self {
|
||||
return .status(id: id, state: .unknown)
|
||||
|
@ -150,8 +178,8 @@ extension TimelineViewController {
|
|||
return true
|
||||
case (.confirmLoadMore, .confirmLoadMore):
|
||||
return true
|
||||
// case let (.publicTimelineDescription(local: a), .publicTimelineDescription(local: b)):
|
||||
// return a == b
|
||||
case (.publicTimelineDescription, .publicTimelineDescription):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
@ -166,15 +194,14 @@ extension TimelineViewController {
|
|||
hasher.combine(1)
|
||||
case .confirmLoadMore:
|
||||
hasher.combine(2)
|
||||
// case .publicTimelineDescription(local: let local):
|
||||
// hasher.combine(3)
|
||||
// hasher.combine(local)
|
||||
case .publicTimelineDescription:
|
||||
hasher.combine(3)
|
||||
}
|
||||
}
|
||||
|
||||
var hideSeparators: Bool {
|
||||
switch self {
|
||||
case .loadingIndicator:
|
||||
case .loadingIndicator, .publicTimelineDescription:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
@ -192,8 +219,6 @@ extension TimelineViewController {
|
|||
throw Error.noClient
|
||||
}
|
||||
|
||||
try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
|
||||
|
||||
let request = Client.getStatuses(timeline: timeline)
|
||||
let (statuses, _) = try await mastodonController.run(request)
|
||||
|
||||
|
@ -235,8 +260,6 @@ extension TimelineViewController {
|
|||
throw Error.noOlder
|
||||
}
|
||||
|
||||
try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)
|
||||
|
||||
let request = Client.getStatuses(timeline: timeline, range: older)
|
||||
let (statuses, _) = try await mastodonController.run(request)
|
||||
|
||||
|
@ -273,4 +296,24 @@ extension TimelineViewController: UICollectionViewDelegate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
guard let item = dataSource.itemIdentifier(for: indexPath) else {
|
||||
return
|
||||
}
|
||||
switch item {
|
||||
case .publicTimelineDescription:
|
||||
removeTimelineDescriptionCell()
|
||||
|
||||
default:
|
||||
// TODO: cell selection
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
if isShowingTimelineDescription {
|
||||
removeTimelineDescriptionCell()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,10 @@ class TimelinesPageViewController: SegmentedPageViewController {
|
|||
let home = TimelineViewController(for: .home, mastodonController: mastodonController)
|
||||
home.title = homeTitle
|
||||
|
||||
let federated = TimelineTableViewController(for: .public(local: false), mastodonController: mastodonController)
|
||||
let federated = TimelineViewController(for: .public(local: false), mastodonController: mastodonController)
|
||||
federated.title = federatedTitle
|
||||
|
||||
let local = TimelineTableViewController(for: .public(local: true), mastodonController: mastodonController)
|
||||
let local = TimelineViewController(for: .public(local: true), mastodonController: mastodonController)
|
||||
local.title = localTitle
|
||||
|
||||
super.init(titles: [
|
||||
|
|
|
@ -52,7 +52,7 @@ extension TimelineLikeCollectionViewController {
|
|||
snapshot.appendSections([.footer])
|
||||
}
|
||||
snapshot.appendItems([.confirmLoadMore], toSection: .footer)
|
||||
await dataSource.apply(snapshot, animatingDifferences: false)
|
||||
await apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
for await _ in confirmLoadMore.values {
|
||||
return true
|
||||
|
@ -94,14 +94,14 @@ extension TimelineLikeCollectionViewController {
|
|||
} else {
|
||||
snapshot.appendItems([.loadingIndicator], toSection: .footer)
|
||||
}
|
||||
await dataSource.apply(snapshot, animatingDifferences: false)
|
||||
await apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
func handleRemoveLoadingIndicator() async {
|
||||
let oldContentOffset = collectionView.contentOffset
|
||||
var snapshot = dataSource.snapshot()
|
||||
snapshot.deleteSections([.footer])
|
||||
await dataSource.apply(snapshot, animatingDifferences: false)
|
||||
await apply(snapshot, animatingDifferences: false)
|
||||
// prevent the collection view from scrolling as we remove the loading indicator and add the timeline items
|
||||
collectionView.contentOffset = oldContentOffset
|
||||
}
|
||||
|
@ -123,7 +123,7 @@ extension TimelineLikeCollectionViewController {
|
|||
}
|
||||
snapshot.appendSections([.entries])
|
||||
snapshot.appendItems(timelineItems.map { .fromTimelineItem($0) }, toSection: .entries)
|
||||
await dataSource.apply(snapshot, animatingDifferences: false)
|
||||
await apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
func handleLoadNewerError(_ error: Swift.Error) async {
|
||||
|
@ -156,7 +156,7 @@ extension TimelineLikeCollectionViewController {
|
|||
} else {
|
||||
snapshot.appendItems(items, toSection: .entries)
|
||||
}
|
||||
await dataSource.apply(snapshot, animatingDifferences: false)
|
||||
await apply(snapshot, animatingDifferences: false)
|
||||
|
||||
if let first,
|
||||
let indexPath = dataSource.indexPath(for: first) {
|
||||
|
@ -183,11 +183,20 @@ extension TimelineLikeCollectionViewController {
|
|||
snapshot.deleteItems([.confirmLoadMore])
|
||||
}
|
||||
snapshot.appendItems(timelineItems.map { .fromTimelineItem($0) }, toSection: .entries)
|
||||
await dataSource.apply(snapshot, animatingDifferences: false)
|
||||
await apply(snapshot, animatingDifferences: false)
|
||||
}
|
||||
}
|
||||
|
||||
extension TimelineLikeCollectionViewController {
|
||||
// apply(_:animatingDifferences:) is marked as nonisolated, so just awaiting it doesn't dispatch to the main thread, unlike other async @MainActor methods
|
||||
// but we always want to update the data source on the main thread for consistency, so this method does that
|
||||
func apply(_ snapshot: NSDiffableDataSourceSnapshot<Section, Item>, animatingDifferences: Bool) async {
|
||||
let task = Task { @MainActor in
|
||||
self.dataSource.apply(snapshot, animatingDifferences: animatingDifferences)
|
||||
}
|
||||
await task.value
|
||||
}
|
||||
|
||||
func registerTimelineLikeCells() {
|
||||
collectionView.register(LoadingCollectionViewCell.self, forCellWithReuseIdentifier: "loadingIndicator")
|
||||
collectionView.register(ConfirmLoadMoreCollectionViewCell.self, forCellWithReuseIdentifier: "confirmLoadMore")
|
||||
|
|
Loading…
Reference in New Issue