2021-01-28 09:10:30 +01:00
|
|
|
//
|
|
|
|
// TimelineSection.swift
|
|
|
|
// Mastodon
|
|
|
|
//
|
|
|
|
// Created by sxiaojian on 2021/1/27.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Combine
|
|
|
|
import CoreData
|
|
|
|
import CoreDataStack
|
|
|
|
import UIKit
|
2021-04-19 12:06:02 +02:00
|
|
|
import AVKit
|
2021-07-07 11:51:47 +02:00
|
|
|
import AlamofireImage
|
2021-06-28 13:41:41 +02:00
|
|
|
import MastodonMeta
|
2021-07-09 13:07:12 +02:00
|
|
|
import MastodonSDK
|
|
|
|
import NaturalLanguage
|
2022-10-08 07:43:06 +02:00
|
|
|
import MastodonCore
|
2022-01-27 14:23:39 +01:00
|
|
|
import MastodonUI
|
2021-06-28 13:41:41 +02:00
|
|
|
|
2021-02-24 09:11:48 +01:00
|
|
|
enum StatusSection: Equatable, Hashable {
|
2021-01-28 09:10:30 +01:00
|
|
|
case main
|
|
|
|
}
|
|
|
|
|
2021-02-24 09:11:48 +01:00
|
|
|
extension StatusSection {
|
2021-06-19 12:33:29 +02:00
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
struct Configuration {
|
2022-12-12 16:41:13 +01:00
|
|
|
let context: AppContext
|
2022-10-09 14:07:57 +02:00
|
|
|
let authContext: AuthContext
|
2022-01-27 14:23:39 +01:00
|
|
|
weak var statusTableViewCellDelegate: StatusTableViewCellDelegate?
|
|
|
|
weak var timelineMiddleLoaderTableViewCellDelegate: TimelineMiddleLoaderTableViewCellDelegate?
|
2022-02-15 12:44:45 +01:00
|
|
|
let filterContext: Mastodon.Entity.Filter.Context?
|
|
|
|
let activeFilters: Published<[Mastodon.Entity.Filter]>.Publisher?
|
2022-01-27 14:23:39 +01:00
|
|
|
}
|
2021-07-15 03:56:26 +02:00
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
static func diffableDataSource(
|
|
|
|
tableView: UITableView,
|
|
|
|
context: AppContext,
|
|
|
|
configuration: Configuration
|
|
|
|
) -> UITableViewDiffableDataSource<StatusSection, StatusItem> {
|
|
|
|
tableView.register(StatusTableViewCell.self, forCellReuseIdentifier: String(describing: StatusTableViewCell.self))
|
|
|
|
tableView.register(TimelineMiddleLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineMiddleLoaderTableViewCell.self))
|
|
|
|
tableView.register(StatusThreadRootTableViewCell.self, forCellReuseIdentifier: String(describing: StatusThreadRootTableViewCell.self))
|
|
|
|
tableView.register(TimelineBottomLoaderTableViewCell.self, forCellReuseIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self))
|
|
|
|
|
|
|
|
return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
2021-01-28 09:10:30 +01:00
|
|
|
switch item {
|
2022-01-27 14:23:39 +01:00
|
|
|
case .feed(let record):
|
2021-02-23 08:16:55 +01:00
|
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
2022-01-27 14:23:39 +01:00
|
|
|
context.managedObjectContext.performAndWait {
|
|
|
|
guard let feed = record.object(in: context.managedObjectContext) else { return }
|
|
|
|
configure(
|
|
|
|
context: context,
|
|
|
|
tableView: tableView,
|
|
|
|
cell: cell,
|
|
|
|
viewModel: StatusTableViewCell.ViewModel(value: .feed(feed)),
|
|
|
|
configuration: configuration
|
|
|
|
)
|
2021-02-07 07:42:50 +01:00
|
|
|
}
|
|
|
|
return cell
|
2022-01-27 14:23:39 +01:00
|
|
|
case .feedLoader(let record):
|
|
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineMiddleLoaderTableViewCell.self), for: indexPath) as! TimelineMiddleLoaderTableViewCell
|
|
|
|
context.managedObjectContext.performAndWait {
|
|
|
|
guard let feed = record.object(in: context.managedObjectContext) else { return }
|
|
|
|
configure(
|
2021-03-10 07:36:28 +01:00
|
|
|
cell: cell,
|
2022-01-27 14:23:39 +01:00
|
|
|
feed: feed,
|
|
|
|
configuration: configuration
|
2021-03-10 07:36:28 +01:00
|
|
|
)
|
2021-05-12 12:26:53 +02:00
|
|
|
}
|
2021-04-13 13:46:42 +02:00
|
|
|
return cell
|
2022-01-27 14:23:39 +01:00
|
|
|
case .status(let record):
|
|
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
|
|
|
context.managedObjectContext.performAndWait {
|
|
|
|
guard let status = record.object(in: context.managedObjectContext) else { return }
|
|
|
|
configure(
|
|
|
|
context: context,
|
|
|
|
tableView: tableView,
|
|
|
|
cell: cell,
|
|
|
|
viewModel: StatusTableViewCell.ViewModel(value: .status(status)),
|
|
|
|
configuration: configuration
|
|
|
|
)
|
|
|
|
}
|
2021-02-07 07:42:50 +01:00
|
|
|
return cell
|
2022-01-27 14:23:39 +01:00
|
|
|
case .thread(let thread):
|
|
|
|
let cell = dequeueConfiguredReusableCell(
|
|
|
|
context: context,
|
|
|
|
tableView: tableView,
|
|
|
|
indexPath: indexPath,
|
|
|
|
configuration: ThreadCellRegistrationConfiguration(
|
|
|
|
thread: thread,
|
|
|
|
configuration: configuration
|
|
|
|
)
|
|
|
|
)
|
2021-02-04 08:09:58 +01:00
|
|
|
return cell
|
2021-04-13 13:46:42 +02:00
|
|
|
case .topLoader:
|
|
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
2022-01-27 14:23:39 +01:00
|
|
|
cell.activityIndicatorView.startAnimating()
|
2021-04-13 13:46:42 +02:00
|
|
|
return cell
|
2021-02-03 06:01:50 +01:00
|
|
|
case .bottomLoader:
|
|
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: TimelineBottomLoaderTableViewCell.self), for: indexPath) as! TimelineBottomLoaderTableViewCell
|
2022-01-27 14:23:39 +01:00
|
|
|
cell.activityIndicatorView.startAnimating()
|
2021-07-15 11:26:04 +02:00
|
|
|
return cell
|
2022-01-27 14:23:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} // end func
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
extension StatusSection {
|
|
|
|
|
|
|
|
struct ThreadCellRegistrationConfiguration {
|
|
|
|
let thread: StatusItem.Thread
|
|
|
|
let configuration: Configuration
|
|
|
|
}
|
|
|
|
|
|
|
|
static func dequeueConfiguredReusableCell(
|
|
|
|
context: AppContext,
|
|
|
|
tableView: UITableView,
|
|
|
|
indexPath: IndexPath,
|
|
|
|
configuration: ThreadCellRegistrationConfiguration
|
|
|
|
) -> UITableViewCell {
|
|
|
|
let managedObjectContext = context.managedObjectContext
|
|
|
|
|
|
|
|
switch configuration.thread {
|
|
|
|
case .root(let threadContext):
|
|
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusThreadRootTableViewCell.self), for: indexPath) as! StatusThreadRootTableViewCell
|
|
|
|
managedObjectContext.performAndWait {
|
|
|
|
guard let status = threadContext.status.object(in: managedObjectContext) else { return }
|
|
|
|
StatusSection.configure(
|
|
|
|
context: context,
|
|
|
|
tableView: tableView,
|
|
|
|
cell: cell,
|
|
|
|
viewModel: StatusThreadRootTableViewCell.ViewModel(value: .status(status)),
|
|
|
|
configuration: configuration.configuration
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return cell
|
|
|
|
case .reply(let threadContext),
|
|
|
|
.leaf(let threadContext):
|
|
|
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: StatusTableViewCell.self), for: indexPath) as! StatusTableViewCell
|
|
|
|
managedObjectContext.performAndWait {
|
|
|
|
guard let status = threadContext.status.object(in: managedObjectContext) else { return }
|
|
|
|
StatusSection.configure(
|
|
|
|
context: context,
|
|
|
|
tableView: tableView,
|
|
|
|
cell: cell,
|
|
|
|
viewModel: StatusTableViewCell.ViewModel(value: .status(status)),
|
|
|
|
configuration: configuration.configuration
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return cell
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
extension StatusSection {
|
|
|
|
|
|
|
|
public static func setupStatusPollDataSource(
|
|
|
|
context: AppContext,
|
2022-10-09 14:07:57 +02:00
|
|
|
authContext: AuthContext,
|
2022-01-27 14:23:39 +01:00
|
|
|
statusView: StatusView
|
|
|
|
) {
|
|
|
|
let managedObjectContext = context.managedObjectContext
|
|
|
|
statusView.pollTableViewDiffableDataSource = UITableViewDiffableDataSource<PollSection, PollItem>(tableView: statusView.pollTableView) { tableView, indexPath, item in
|
|
|
|
switch item {
|
2023-03-02 11:06:13 +01:00
|
|
|
case .history:
|
|
|
|
return nil
|
2022-01-27 14:23:39 +01:00
|
|
|
case .option(let record):
|
|
|
|
// Fix cell reuse animation issue
|
|
|
|
let cell: PollOptionTableViewCell = {
|
|
|
|
let _cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PollOptionTableViewCell.self) + "@\(indexPath.row)#\(indexPath.section)") as? PollOptionTableViewCell
|
|
|
|
_cell?.prepareForReuse()
|
|
|
|
return _cell ?? PollOptionTableViewCell()
|
|
|
|
}()
|
|
|
|
|
2022-10-09 14:07:57 +02:00
|
|
|
cell.pollOptionView.viewModel.authContext = authContext
|
2022-01-27 14:23:39 +01:00
|
|
|
|
|
|
|
managedObjectContext.performAndWait {
|
|
|
|
guard let option = record.object(in: managedObjectContext) else {
|
|
|
|
assertionFailure()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cell.pollOptionView.configure(pollOption: option)
|
|
|
|
|
|
|
|
// trigger update if needs
|
|
|
|
let needsUpdatePoll: Bool = {
|
|
|
|
// check first option in poll to trigger update poll only once
|
2023-03-02 11:06:13 +01:00
|
|
|
guard
|
|
|
|
let poll = option.poll,
|
|
|
|
option.index == 0
|
|
|
|
else { return false }
|
2022-01-27 14:23:39 +01:00
|
|
|
|
|
|
|
guard !poll.expired else {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
let now = Date()
|
|
|
|
let timeIntervalSinceUpdate = now.timeIntervalSince(poll.updatedAt)
|
|
|
|
#if DEBUG
|
|
|
|
let autoRefreshTimeInterval: TimeInterval = 3 // speedup testing
|
|
|
|
#else
|
|
|
|
let autoRefreshTimeInterval: TimeInterval = 30
|
|
|
|
#endif
|
|
|
|
|
|
|
|
guard timeIntervalSinceUpdate > autoRefreshTimeInterval else {
|
|
|
|
return false
|
|
|
|
}
|
2023-09-21 12:52:05 +02:00
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
return true
|
|
|
|
}()
|
|
|
|
|
2022-10-09 14:07:57 +02:00
|
|
|
if needsUpdatePoll {
|
2023-03-02 11:06:13 +01:00
|
|
|
guard let poll = option.poll else { return }
|
|
|
|
let pollRecord: ManagedObjectRecord<Poll> = .init(objectID: poll.objectID)
|
2022-01-27 14:23:39 +01:00
|
|
|
Task { [weak context] in
|
|
|
|
guard let context = context else { return }
|
|
|
|
_ = try await context.apiService.poll(
|
|
|
|
poll: pollRecord,
|
2022-10-09 14:07:57 +02:00
|
|
|
authenticationBox: authContext.mastodonAuthenticationBox
|
2022-01-27 14:23:39 +01:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} // end managedObjectContext.performAndWait
|
2021-04-06 10:43:08 +02:00
|
|
|
return cell
|
2021-01-28 09:10:30 +01:00
|
|
|
}
|
|
|
|
}
|
2022-01-27 14:23:39 +01:00
|
|
|
var _snapshot = NSDiffableDataSourceSnapshot<PollSection, PollItem>()
|
|
|
|
_snapshot.appendSections([.main])
|
2022-12-17 20:26:20 +01:00
|
|
|
statusView.pollTableViewDiffableDataSource?.applySnapshotUsingReloadData(_snapshot)
|
2022-01-27 14:23:39 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-02 11:06:13 +01:00
|
|
|
extension StatusSection {
|
|
|
|
|
|
|
|
public static func setupStatusPollHistoryDataSource(
|
|
|
|
context: AppContext,
|
|
|
|
authContext: AuthContext,
|
|
|
|
statusView: StatusView
|
|
|
|
) {
|
|
|
|
statusView.pollTableViewDiffableDataSource = UITableViewDiffableDataSource<PollSection, PollItem>(tableView: statusView.pollTableView) { tableView, indexPath, item in
|
|
|
|
switch item {
|
|
|
|
case .option:
|
|
|
|
return nil
|
|
|
|
case let .history(option):
|
|
|
|
// Fix cell reuse animation issue
|
|
|
|
let cell: PollOptionTableViewCell = {
|
|
|
|
let _cell = tableView.dequeueReusableCell(withIdentifier: String(describing: PollOptionTableViewCell.self) + "@\(indexPath.row)#\(indexPath.section)") as? PollOptionTableViewCell
|
|
|
|
_cell?.prepareForReuse()
|
|
|
|
return _cell ?? PollOptionTableViewCell()
|
|
|
|
}()
|
|
|
|
|
|
|
|
cell.pollOptionView.configure(historyPollOption: option)
|
|
|
|
|
|
|
|
return cell
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-27 14:23:39 +01:00
|
|
|
extension StatusSection {
|
|
|
|
|
|
|
|
static func configure(
|
|
|
|
context: AppContext,
|
|
|
|
tableView: UITableView,
|
|
|
|
cell: StatusTableViewCell,
|
|
|
|
viewModel: StatusTableViewCell.ViewModel,
|
|
|
|
configuration: Configuration
|
|
|
|
) {
|
|
|
|
setupStatusPollDataSource(
|
|
|
|
context: context,
|
2022-10-09 14:07:57 +02:00
|
|
|
authContext: configuration.authContext,
|
2022-01-27 14:23:39 +01:00
|
|
|
statusView: cell.statusView
|
|
|
|
)
|
|
|
|
|
2022-12-12 16:41:13 +01:00
|
|
|
cell.statusView.viewModel.context = configuration.context
|
2022-10-09 14:07:57 +02:00
|
|
|
cell.statusView.viewModel.authContext = configuration.authContext
|
2022-01-27 14:23:39 +01:00
|
|
|
|
|
|
|
cell.configure(
|
|
|
|
tableView: tableView,
|
|
|
|
viewModel: viewModel,
|
|
|
|
delegate: configuration.statusTableViewCellDelegate
|
|
|
|
)
|
2022-02-15 12:44:45 +01:00
|
|
|
|
|
|
|
cell.statusView.viewModel.filterContext = configuration.filterContext
|
|
|
|
configuration.activeFilters?
|
|
|
|
.assign(to: \.activeFilters, on: cell.statusView.viewModel)
|
|
|
|
.store(in: &cell.disposeBag)
|
2022-01-27 14:23:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static func configure(
|
|
|
|
context: AppContext,
|
|
|
|
tableView: UITableView,
|
|
|
|
cell: StatusThreadRootTableViewCell,
|
|
|
|
viewModel: StatusThreadRootTableViewCell.ViewModel,
|
|
|
|
configuration: Configuration
|
|
|
|
) {
|
|
|
|
setupStatusPollDataSource(
|
|
|
|
context: context,
|
2022-10-09 14:07:57 +02:00
|
|
|
authContext: configuration.authContext,
|
2022-01-27 14:23:39 +01:00
|
|
|
statusView: cell.statusView
|
|
|
|
)
|
|
|
|
|
2022-12-19 07:33:20 +01:00
|
|
|
cell.statusView.viewModel.context = configuration.context
|
2022-10-09 14:07:57 +02:00
|
|
|
cell.statusView.viewModel.authContext = configuration.authContext
|
2022-01-27 14:23:39 +01:00
|
|
|
|
|
|
|
cell.configure(
|
|
|
|
tableView: tableView,
|
|
|
|
viewModel: viewModel,
|
|
|
|
delegate: configuration.statusTableViewCellDelegate
|
|
|
|
)
|
2022-02-15 12:44:45 +01:00
|
|
|
|
|
|
|
cell.statusView.viewModel.filterContext = configuration.filterContext
|
|
|
|
configuration.activeFilters?
|
|
|
|
.assign(to: \.activeFilters, on: cell.statusView.viewModel)
|
|
|
|
.store(in: &cell.disposeBag)
|
2022-01-27 14:23:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
static func configure(
|
|
|
|
cell: TimelineMiddleLoaderTableViewCell,
|
|
|
|
feed: Feed,
|
|
|
|
configuration: Configuration
|
|
|
|
) {
|
|
|
|
cell.configure(
|
|
|
|
feed: feed,
|
|
|
|
delegate: configuration.timelineMiddleLoaderTableViewCellDelegate
|
|
|
|
)
|
2021-01-28 09:10:30 +01:00
|
|
|
}
|
2022-01-27 14:23:39 +01:00
|
|
|
|
2021-03-03 12:34:29 +01:00
|
|
|
}
|