feat: add reply to header for toot
This commit is contained in:
parent
807dfd9ea7
commit
75d39aabf0
|
@ -145,16 +145,19 @@ public extension Toot {
|
||||||
|
|
||||||
return toot
|
return toot
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(reblogsCount: NSNumber) {
|
func update(reblogsCount: NSNumber) {
|
||||||
if self.reblogsCount.intValue != reblogsCount.intValue {
|
if self.reblogsCount.intValue != reblogsCount.intValue {
|
||||||
self.reblogsCount = reblogsCount
|
self.reblogsCount = reblogsCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(favouritesCount: NSNumber) {
|
func update(favouritesCount: NSNumber) {
|
||||||
if self.favouritesCount.intValue != favouritesCount.intValue {
|
if self.favouritesCount.intValue != favouritesCount.intValue {
|
||||||
self.favouritesCount = favouritesCount
|
self.favouritesCount = favouritesCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(repliesCount: NSNumber?) {
|
func update(repliesCount: NSNumber?) {
|
||||||
guard let count = repliesCount else {
|
guard let count = repliesCount else {
|
||||||
return
|
return
|
||||||
|
@ -163,6 +166,13 @@ public extension Toot {
|
||||||
self.repliesCount = repliesCount
|
self.repliesCount = repliesCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func update(replyTo: Toot?) {
|
||||||
|
if self.replyTo != replyTo {
|
||||||
|
self.replyTo = replyTo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func update(liked: Bool, mastodonUser: MastodonUser) {
|
func update(liked: Bool, mastodonUser: MastodonUser) {
|
||||||
if liked {
|
if liked {
|
||||||
if !(self.favouritedBy ?? Set()).contains(mastodonUser) {
|
if !(self.favouritedBy ?? Set()).contains(mastodonUser) {
|
||||||
|
@ -174,6 +184,7 @@ public extension Toot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(reblogged: Bool, mastodonUser: MastodonUser) {
|
func update(reblogged: Bool, mastodonUser: MastodonUser) {
|
||||||
if reblogged {
|
if reblogged {
|
||||||
if !(self.rebloggedBy ?? Set()).contains(mastodonUser) {
|
if !(self.rebloggedBy ?? Set()).contains(mastodonUser) {
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"user_boosted": "%s boosted",
|
"user_boosted": "%s boosted",
|
||||||
|
"user_replied_to": "Replied to %s",
|
||||||
"show_post": "Show Post",
|
"show_post": "Show Post",
|
||||||
"status_content_warning": "content warning",
|
"status_content_warning": "content warning",
|
||||||
"media_content_warning": "Tap to reveal that may be sensitive",
|
"media_content_warning": "Tap to reveal that may be sensitive",
|
||||||
|
|
|
@ -151,6 +151,9 @@
|
||||||
DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */; };
|
DB6C8C0F25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */; };
|
||||||
DB71FD3625F8A16C00512AE1 /* APIService+Persist+PersistMemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD3525F8A16C00512AE1 /* APIService+Persist+PersistMemo.swift */; };
|
DB71FD3625F8A16C00512AE1 /* APIService+Persist+PersistMemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD3525F8A16C00512AE1 /* APIService+Persist+PersistMemo.swift */; };
|
||||||
DB71FD3C25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD3B25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift */; };
|
DB71FD3C25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD3B25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift */; };
|
||||||
|
DB71FD4625F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD4525F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift */; };
|
||||||
|
DB71FD4C25F8C80E00512AE1 /* StatusPrefetchingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD4B25F8C80E00512AE1 /* StatusPrefetchingService.swift */; };
|
||||||
|
DB71FD5225F8CCAA00512AE1 /* APIService+Status.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */; };
|
||||||
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */; };
|
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */; };
|
||||||
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; };
|
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; };
|
||||||
DB89B9F725C10FD0008580ED /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; };
|
DB89B9F725C10FD0008580ED /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; };
|
||||||
|
@ -399,6 +402,9 @@
|
||||||
DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Error.swift"; sourceTree = "<group>"; };
|
DB6C8C0E25F0A6AE00AAA452 /* Mastodon+Entity+Error.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Mastodon+Entity+Error.swift"; sourceTree = "<group>"; };
|
||||||
DB71FD3525F8A16C00512AE1 /* APIService+Persist+PersistMemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+PersistMemo.swift"; sourceTree = "<group>"; };
|
DB71FD3525F8A16C00512AE1 /* APIService+Persist+PersistMemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+PersistMemo.swift"; sourceTree = "<group>"; };
|
||||||
DB71FD3B25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+PersistCache.swift"; sourceTree = "<group>"; };
|
DB71FD3B25F8A1C500512AE1 /* APIService+Persist+PersistCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Persist+PersistCache.swift"; sourceTree = "<group>"; };
|
||||||
|
DB71FD4525F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StatusProvider+UITableViewDataSourcePrefetching.swift"; sourceTree = "<group>"; };
|
||||||
|
DB71FD4B25F8C80E00512AE1 /* StatusPrefetchingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusPrefetchingService.swift; sourceTree = "<group>"; };
|
||||||
|
DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Status.swift"; sourceTree = "<group>"; };
|
||||||
DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewController.swift; sourceTree = "<group>"; };
|
DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewController.swift; sourceTree = "<group>"; };
|
||||||
DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = "<group>"; };
|
DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = "<group>"; };
|
||||||
DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataStack.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataStack.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
@ -606,6 +612,7 @@
|
||||||
2DF75B9A25D0E27500694EC8 /* StatusProviderFacade.swift */,
|
2DF75B9A25D0E27500694EC8 /* StatusProviderFacade.swift */,
|
||||||
2DF75BA025D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift */,
|
2DF75BA025D0E29D00694EC8 /* StatusProvider+StatusTableViewCellDelegate.swift */,
|
||||||
DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */,
|
DB59F0FD25EF5D96001F1DAB /* StatusProvider+UITableViewDelegate.swift */,
|
||||||
|
DB71FD4525F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift */,
|
||||||
);
|
);
|
||||||
path = StatusProvider;
|
path = StatusProvider;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -655,6 +662,7 @@
|
||||||
DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */,
|
DB4563BC25E11A24004DA0B9 /* KeyboardResponderService.swift */,
|
||||||
2D206B8B25F6015000143C56 /* AudioPlayer.swift */,
|
2D206B8B25F6015000143C56 /* AudioPlayer.swift */,
|
||||||
2DA6054625F716A2006356F9 /* PlaybackState.swift */,
|
2DA6054625F716A2006356F9 /* PlaybackState.swift */,
|
||||||
|
DB71FD4B25F8C80E00512AE1 /* StatusPrefetchingService.swift */,
|
||||||
);
|
);
|
||||||
path = Service;
|
path = Service;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -933,6 +941,7 @@
|
||||||
DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */,
|
DB45FB1C25CA9D23005A8AC7 /* APIService+HomeTimeline.swift */,
|
||||||
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */,
|
DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */,
|
||||||
DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */,
|
DB59F10D25EF724F001F1DAB /* APIService+Poll.swift */,
|
||||||
|
DB71FD5125F8CCAA00512AE1 /* APIService+Status.swift */,
|
||||||
);
|
);
|
||||||
path = APIService;
|
path = APIService;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -1566,6 +1575,7 @@
|
||||||
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
|
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
|
||||||
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
|
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
|
||||||
0FB3D33225E5F50E00AAD544 /* PickServerSearchCell.swift in Sources */,
|
0FB3D33225E5F50E00AAD544 /* PickServerSearchCell.swift in Sources */,
|
||||||
|
DB71FD5225F8CCAA00512AE1 /* APIService+Status.swift in Sources */,
|
||||||
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
||||||
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */,
|
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */,
|
||||||
DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */,
|
DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */,
|
||||||
|
@ -1577,6 +1587,7 @@
|
||||||
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */,
|
2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */,
|
||||||
DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */,
|
DB68A06325E905E000CFDF14 /* UIApplication.swift in Sources */,
|
||||||
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */,
|
DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */,
|
||||||
|
DB71FD4625F8C6D200512AE1 /* StatusProvider+UITableViewDataSourcePrefetching.swift in Sources */,
|
||||||
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */,
|
2D152A8C25C295CC009AA50C /* StatusView.swift in Sources */,
|
||||||
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */,
|
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */,
|
||||||
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */,
|
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */,
|
||||||
|
@ -1589,6 +1600,7 @@
|
||||||
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */,
|
DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */,
|
||||||
2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */,
|
2D69D00A25CAA00300C3A1B2 /* APIService+CoreData+Toot.swift in Sources */,
|
||||||
DB4481C625EE2ADA00BEFB67 /* PollSection.swift in Sources */,
|
DB4481C625EE2ADA00BEFB67 /* PollSection.swift in Sources */,
|
||||||
|
DB71FD4C25F8C80E00512AE1 /* StatusPrefetchingService.swift in Sources */,
|
||||||
2D939AB525EDD8A90076FA61 /* String.swift in Sources */,
|
2D939AB525EDD8A90076FA61 /* String.swift in Sources */,
|
||||||
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */,
|
DB4481B925EE289600BEFB67 /* UITableView.swift in Sources */,
|
||||||
0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */,
|
0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */,
|
||||||
|
|
|
@ -79,12 +79,17 @@ extension StatusSection {
|
||||||
statusItemAttribute: Item.StatusAttribute
|
statusItemAttribute: Item.StatusAttribute
|
||||||
) {
|
) {
|
||||||
// set header
|
// set header
|
||||||
cell.statusView.headerContainerStackView.isHidden = toot.reblog == nil
|
StatusSection.configureHeader(cell: cell, toot: toot)
|
||||||
cell.statusView.headerInfoLabel.text = {
|
ManagedObjectObserver.observe(object: toot)
|
||||||
let author = toot.author
|
.receive(on: DispatchQueue.main)
|
||||||
let name = author.displayName.isEmpty ? author.username : author.displayName
|
.sink { _ in
|
||||||
return L10n.Common.Controls.Status.userBoosted(name)
|
// do nothing
|
||||||
}()
|
} receiveValue: { change in
|
||||||
|
guard case .update(let object) = change.changeType,
|
||||||
|
let newToot = object as? Toot else { return }
|
||||||
|
StatusSection.configureHeader(cell: cell, toot: newToot)
|
||||||
|
}
|
||||||
|
.store(in: &cell.disposeBag)
|
||||||
|
|
||||||
// set name username avatar
|
// set name username avatar
|
||||||
cell.statusView.nameLabel.text = {
|
cell.statusView.nameLabel.text = {
|
||||||
|
@ -225,7 +230,6 @@ extension StatusSection {
|
||||||
guard case .update(let object) = change.changeType,
|
guard case .update(let object) = change.changeType,
|
||||||
let newToot = object as? Toot else { return }
|
let newToot = object as? Toot else { return }
|
||||||
let targetToot = newToot.reblog ?? newToot
|
let targetToot = newToot.reblog ?? newToot
|
||||||
|
|
||||||
let isLike = targetToot.favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false
|
let isLike = targetToot.favouritedBy.flatMap { $0.contains(where: { $0.id == requestUserID }) } ?? false
|
||||||
let favoriteCount = targetToot.favouritesCount.intValue
|
let favoriteCount = targetToot.favouritesCount.intValue
|
||||||
let favoriteCountTitle = StatusSection.formattedNumberTitleForActionButton(favoriteCount)
|
let favoriteCountTitle = StatusSection.formattedNumberTitleForActionButton(favoriteCount)
|
||||||
|
@ -236,6 +240,31 @@ extension StatusSection {
|
||||||
.store(in: &cell.disposeBag)
|
.store(in: &cell.disposeBag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func configureHeader(
|
||||||
|
cell: StatusTableViewCell,
|
||||||
|
toot: Toot
|
||||||
|
) {
|
||||||
|
if toot.reblog != nil {
|
||||||
|
cell.statusView.headerContainerStackView.isHidden = false
|
||||||
|
cell.statusView.headerInfoLabel.attributedText = StatusView.iconAttributedString(image: StatusView.boostIconImage)
|
||||||
|
cell.statusView.headerInfoLabel.text = {
|
||||||
|
let author = toot.author
|
||||||
|
let name = author.displayName.isEmpty ? author.username : author.displayName
|
||||||
|
return L10n.Common.Controls.Status.userBoosted(name)
|
||||||
|
}()
|
||||||
|
} else if let replyTo = toot.replyTo {
|
||||||
|
cell.statusView.headerContainerStackView.isHidden = false
|
||||||
|
cell.statusView.headerInfoLabel.attributedText = StatusView.iconAttributedString(image: StatusView.replyIconImage)
|
||||||
|
cell.statusView.headerInfoLabel.text = {
|
||||||
|
let author = replyTo.author
|
||||||
|
let name = author.displayName.isEmpty ? author.username : author.displayName
|
||||||
|
return L10n.Common.Controls.Status.userRepliedTo(name)
|
||||||
|
}()
|
||||||
|
} else {
|
||||||
|
cell.statusView.headerContainerStackView.isHidden = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static func configure(
|
static func configure(
|
||||||
cell: StatusTableViewCell,
|
cell: StatusTableViewCell,
|
||||||
poll: Poll?,
|
poll: Poll?,
|
||||||
|
|
|
@ -80,6 +80,10 @@ internal enum L10n {
|
||||||
internal static func userBoosted(_ p1: Any) -> String {
|
internal static func userBoosted(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "Common.Controls.Status.UserBoosted", String(describing: p1))
|
return L10n.tr("Localizable", "Common.Controls.Status.UserBoosted", String(describing: p1))
|
||||||
}
|
}
|
||||||
|
/// Replied to %@
|
||||||
|
internal static func userRepliedTo(_ p1: Any) -> String {
|
||||||
|
return L10n.tr("Localizable", "Common.Controls.Status.UserRepliedTo", String(describing: p1))
|
||||||
|
}
|
||||||
internal enum Poll {
|
internal enum Poll {
|
||||||
/// Closed
|
/// Closed
|
||||||
internal static let closed = L10n.tr("Localizable", "Common.Controls.Status.Poll.Closed")
|
internal static let closed = L10n.tr("Localizable", "Common.Controls.Status.Poll.Closed")
|
||||||
|
|
|
@ -119,7 +119,7 @@ extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||||
|
|
||||||
guard let diffableDataSource = cell.statusView.pollTableViewDataSource else { return }
|
guard let diffableDataSource = cell.statusView.pollTableViewDataSource else { return }
|
||||||
let item = diffableDataSource.itemIdentifier(for: indexPath)
|
let item = diffableDataSource.itemIdentifier(for: indexPath)
|
||||||
guard case let .opion(objectID, attribute) = item else { return }
|
guard case let .opion(objectID, _) = item else { return }
|
||||||
guard let option = managedObjectContext.object(with: objectID) as? PollOption else { return }
|
guard let option = managedObjectContext.object(with: objectID) as? PollOption else { return }
|
||||||
|
|
||||||
let poll = option.poll
|
let poll = option.poll
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// StatusProvider+UITableViewDataSourcePrefetching.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-3-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
import CoreData
|
||||||
|
import CoreDataStack
|
||||||
|
|
||||||
|
extension StatusTableViewCellDelegate where Self: StatusProvider {
|
||||||
|
func handleTableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||||
|
// prefetch reply toot
|
||||||
|
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return }
|
||||||
|
let domain = activeMastodonAuthenticationBox.domain
|
||||||
|
|
||||||
|
var statusObjectIDs: [NSManagedObjectID] = []
|
||||||
|
for item in items(indexPaths: indexPaths) {
|
||||||
|
switch item {
|
||||||
|
case .homeTimelineIndex(let objectID, _):
|
||||||
|
let homeTimelineIndex = managedObjectContext.object(with: objectID) as! HomeTimelineIndex
|
||||||
|
statusObjectIDs.append(homeTimelineIndex.toot.objectID)
|
||||||
|
case .toot(let objectID, _):
|
||||||
|
statusObjectIDs.append(objectID)
|
||||||
|
default:
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let backgroundManagedObjectContext = context.backgroundManagedObjectContext
|
||||||
|
backgroundManagedObjectContext.perform { [weak self] in
|
||||||
|
guard let self = self else { return }
|
||||||
|
for objectID in statusObjectIDs {
|
||||||
|
let toot = backgroundManagedObjectContext.object(with: objectID) as! Toot
|
||||||
|
guard let replyToID = toot.inReplyToID, toot.replyTo == nil else {
|
||||||
|
// skip
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
self.context.statusPrefetchingService.prefetchReplyTo(
|
||||||
|
domain: domain,
|
||||||
|
statusObjectID: toot.objectID,
|
||||||
|
statusID: toot.id,
|
||||||
|
replyToStatusID: replyToID,
|
||||||
|
authorizationBox: activeMastodonAuthenticationBox
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,4 +20,5 @@ protocol StatusProvider: NeedsDependency & DisposeBagCollectable & UIViewControl
|
||||||
var managedObjectContext: NSManagedObjectContext { get }
|
var managedObjectContext: NSManagedObjectContext { get }
|
||||||
var tableViewDiffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>? { get }
|
var tableViewDiffableDataSource: UITableViewDiffableDataSource<StatusSection, Item>? { get }
|
||||||
func item(for cell: UITableViewCell?, indexPath: IndexPath?) -> Item?
|
func item(for cell: UITableViewCell?, indexPath: IndexPath?) -> Item?
|
||||||
|
func items(indexPaths: [IndexPath]) -> [Item]
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
"Common.Controls.Status.ShowPost" = "Show Post";
|
"Common.Controls.Status.ShowPost" = "Show Post";
|
||||||
"Common.Controls.Status.StatusContentWarning" = "content warning";
|
"Common.Controls.Status.StatusContentWarning" = "content warning";
|
||||||
"Common.Controls.Status.UserBoosted" = "%@ boosted";
|
"Common.Controls.Status.UserBoosted" = "%@ boosted";
|
||||||
|
"Common.Controls.Status.UserRepliedTo" = "Replied to %@";
|
||||||
"Common.Controls.Timeline.LoadMore" = "Load More";
|
"Common.Controls.Timeline.LoadMore" = "Load More";
|
||||||
"Common.Countable.Photo.Multiple" = "photos";
|
"Common.Countable.Photo.Multiple" = "photos";
|
||||||
"Common.Countable.Photo.Single" = "photo";
|
"Common.Countable.Photo.Single" = "photo";
|
||||||
|
|
|
@ -70,4 +70,18 @@ extension HomeTimelineViewController: StatusProvider {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func items(indexPaths: [IndexPath]) -> [Item] {
|
||||||
|
guard let diffableDataSource = self.viewModel.diffableDataSource else {
|
||||||
|
assertionFailure()
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
var items: [Item] = []
|
||||||
|
for indexPath in indexPaths {
|
||||||
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { continue }
|
||||||
|
items.append(item)
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,7 @@ extension HomeTimelineViewController {
|
||||||
viewModel.tableView = tableView
|
viewModel.tableView = tableView
|
||||||
viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self
|
viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
|
tableView.prefetchDataSource = self
|
||||||
viewModel.setupDiffableDataSource(
|
viewModel.setupDiffableDataSource(
|
||||||
for: tableView,
|
for: tableView,
|
||||||
dependency: self,
|
dependency: self,
|
||||||
|
@ -239,6 +240,13 @@ extension HomeTimelineViewController: UITableViewDelegate {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - UITableViewDataSourcePrefetching
|
||||||
|
extension HomeTimelineViewController: UITableViewDataSourcePrefetching {
|
||||||
|
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||||
|
handleTableView(tableView, prefetchRowsAt: indexPaths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
|
// MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
|
||||||
extension HomeTimelineViewController: ContentOffsetAdjustableTimelineViewControllerDelegate {
|
extension HomeTimelineViewController: ContentOffsetAdjustableTimelineViewControllerDelegate {
|
||||||
func navigationBar() -> UINavigationBar? {
|
func navigationBar() -> UINavigationBar? {
|
||||||
|
|
|
@ -375,7 +375,7 @@ extension MastodonPickServerViewController: UITableViewDelegate {
|
||||||
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
|
||||||
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
|
guard let diffableDataSource = viewModel.diffableDataSource else { return nil }
|
||||||
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return nil }
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { return nil }
|
||||||
guard case let .server(server) = item else { return nil }
|
guard case .server = item else { return nil }
|
||||||
|
|
||||||
if tableView.indexPathForSelectedRow == indexPath {
|
if tableView.indexPathForSelectedRow == indexPath {
|
||||||
tableView.deselectRow(at: indexPath, animated: false)
|
tableView.deselectRow(at: indexPath, animated: false)
|
||||||
|
|
|
@ -45,7 +45,7 @@ extension MastodonPickServerViewModel.LoadIndexedServerState {
|
||||||
viewModel.context.apiService.servers(language: nil, category: nil)
|
viewModel.context.apiService.servers(language: nil, category: nil)
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
switch completion {
|
switch completion {
|
||||||
case .failure(let error):
|
case .failure:
|
||||||
// TODO: handle error
|
// TODO: handle error
|
||||||
stateMachine.enter(Fail.self)
|
stateMachine.enter(Fail.self)
|
||||||
case .finished:
|
case .finished:
|
||||||
|
@ -84,7 +84,7 @@ extension MastodonPickServerViewModel.LoadIndexedServerState {
|
||||||
override func didEnter(from previousState: GKState?) {
|
override func didEnter(from previousState: GKState?) {
|
||||||
super.didEnter(from: previousState)
|
super.didEnter(from: previousState)
|
||||||
|
|
||||||
guard let viewModel = self.viewModel, let stateMachine = self.stateMachine else { return }
|
guard let viewModel = self.viewModel else { return }
|
||||||
viewModel.isLoadingIndexedServers.value = false
|
viewModel.isLoadingIndexedServers.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,7 +176,7 @@ class MastodonPickServerViewModel: NSObject {
|
||||||
switch result {
|
switch result {
|
||||||
case .success(let response):
|
case .success(let response):
|
||||||
self.unindexedServers.send(response.value)
|
self.unindexedServers.send(response.value)
|
||||||
case .failure(let error):
|
case .failure:
|
||||||
// TODO: What should be presented when user inputs invalid search text?
|
// TODO: What should be presented when user inputs invalid search text?
|
||||||
self.unindexedServers.send([])
|
self.unindexedServers.send([])
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,4 +70,18 @@ extension PublicTimelineViewController: StatusProvider {
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func items(indexPaths: [IndexPath]) -> [Item] {
|
||||||
|
guard let diffableDataSource = self.viewModel.diffableDataSource else {
|
||||||
|
assertionFailure()
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
var items: [Item] = []
|
||||||
|
for indexPath in indexPaths {
|
||||||
|
guard let item = diffableDataSource.itemIdentifier(for: indexPath) else { continue }
|
||||||
|
items.append(item)
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ extension PublicTimelineViewController {
|
||||||
viewModel.tableView = tableView
|
viewModel.tableView = tableView
|
||||||
viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self
|
viewModel.contentOffsetAdjustableTimelineViewControllerDelegate = self
|
||||||
tableView.delegate = self
|
tableView.delegate = self
|
||||||
|
tableView.prefetchDataSource = self
|
||||||
viewModel.setupDiffableDataSource(
|
viewModel.setupDiffableDataSource(
|
||||||
for: tableView,
|
for: tableView,
|
||||||
dependency: self,
|
dependency: self,
|
||||||
|
@ -125,6 +126,13 @@ extension PublicTimelineViewController: UITableViewDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - UITableViewDataSourcePrefetching
|
||||||
|
extension PublicTimelineViewController: UITableViewDataSourcePrefetching {
|
||||||
|
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
|
||||||
|
handleTableView(tableView, prefetchRowsAt: indexPaths)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
|
// MARK: - ContentOffsetAdjustableTimelineViewControllerDelegate
|
||||||
extension PublicTimelineViewController: ContentOffsetAdjustableTimelineViewControllerDelegate {
|
extension PublicTimelineViewController: ContentOffsetAdjustableTimelineViewControllerDelegate {
|
||||||
func navigationBar() -> UINavigationBar? {
|
func navigationBar() -> UINavigationBar? {
|
||||||
|
|
|
@ -24,6 +24,29 @@ final class StatusView: UIView {
|
||||||
static let avatarImageCornerRadius: CGFloat = 4
|
static let avatarImageCornerRadius: CGFloat = 4
|
||||||
static let contentWarningBlurRadius: CGFloat = 12
|
static let contentWarningBlurRadius: CGFloat = 12
|
||||||
|
|
||||||
|
static let boostIconImage: UIImage = {
|
||||||
|
let font = UIFont.systemFont(ofSize: 13, weight: .medium)
|
||||||
|
let configuration = UIImage.SymbolConfiguration(font: font)
|
||||||
|
let image = UIImage(systemName: "arrow.2.squarepath", withConfiguration: configuration)!.withTintColor(Asset.Colors.Label.secondary.color)
|
||||||
|
return image
|
||||||
|
}()
|
||||||
|
|
||||||
|
static let replyIconImage: UIImage = {
|
||||||
|
let font = UIFont.systemFont(ofSize: 13, weight: .medium)
|
||||||
|
let configuration = UIImage.SymbolConfiguration(font: font)
|
||||||
|
let image = UIImage(systemName: "arrowshape.turn.up.left.fill", withConfiguration: configuration)!.withTintColor(Asset.Colors.Label.secondary.color)
|
||||||
|
return image
|
||||||
|
}()
|
||||||
|
|
||||||
|
static func iconAttributedString(image: UIImage) -> NSAttributedString {
|
||||||
|
let attributedString = NSMutableAttributedString()
|
||||||
|
let imageTextAttachment = NSTextAttachment()
|
||||||
|
let imageAttribute = NSAttributedString(attachment: imageTextAttachment)
|
||||||
|
imageTextAttachment.image = image
|
||||||
|
attributedString.append(imageAttribute)
|
||||||
|
return attributedString
|
||||||
|
}
|
||||||
|
|
||||||
weak var delegate: StatusViewDelegate?
|
weak var delegate: StatusViewDelegate?
|
||||||
var isStatusTextSensitive = false
|
var isStatusTextSensitive = false
|
||||||
var pollTableViewDataSource: UITableViewDiffableDataSource<PollSection, PollItem>?
|
var pollTableViewDataSource: UITableViewDiffableDataSource<PollSection, PollItem>?
|
||||||
|
@ -33,14 +56,7 @@ final class StatusView: UIView {
|
||||||
|
|
||||||
let headerIconLabel: UILabel = {
|
let headerIconLabel: UILabel = {
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
let attributedString = NSMutableAttributedString()
|
label.attributedText = StatusView.iconAttributedString(image: StatusView.boostIconImage)
|
||||||
let imageTextAttachment = NSTextAttachment()
|
|
||||||
let font = UIFont.systemFont(ofSize: 13, weight: .medium)
|
|
||||||
let configuration = UIImage.SymbolConfiguration(font: font)
|
|
||||||
imageTextAttachment.image = UIImage(systemName: "arrow.2.squarepath", withConfiguration: configuration)?.withTintColor(Asset.Colors.Label.secondary.color)
|
|
||||||
let imageAttribute = NSAttributedString(attachment: imageTextAttachment)
|
|
||||||
attributedString.append(imageAttribute)
|
|
||||||
label.attributedText = attributedString
|
|
||||||
return label
|
return label
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
//
|
||||||
|
// APIService+Status.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-3-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
import CoreData
|
||||||
|
import CoreDataStack
|
||||||
|
import CommonOSLog
|
||||||
|
import DateToolsSwift
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
extension APIService {
|
||||||
|
|
||||||
|
func status(
|
||||||
|
domain: String,
|
||||||
|
statusID: Mastodon.Entity.Status.ID,
|
||||||
|
authorizationBox: AuthenticationService.MastodonAuthenticationBox
|
||||||
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||||
|
let authorization = authorizationBox.userAuthorization
|
||||||
|
return Mastodon.API.Statuses.status(
|
||||||
|
session: session,
|
||||||
|
domain: domain,
|
||||||
|
statusID: statusID,
|
||||||
|
authorization: authorization
|
||||||
|
)
|
||||||
|
.flatMap { response -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> in
|
||||||
|
return APIService.Persist.persistToots(
|
||||||
|
managedObjectContext: self.backgroundManagedObjectContext,
|
||||||
|
domain: domain,
|
||||||
|
query: nil,
|
||||||
|
response: response.map { [$0] },
|
||||||
|
persistType: .lookUp,
|
||||||
|
requestMastodonUserID: nil,
|
||||||
|
log: OSLog.api
|
||||||
|
)
|
||||||
|
.setFailureType(to: Error.self)
|
||||||
|
.tryMap { result -> Mastodon.Response.Content<Mastodon.Entity.Status> in
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
return response
|
||||||
|
case .failure(let error):
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,12 +19,13 @@ extension APIService.Persist {
|
||||||
case `public`
|
case `public`
|
||||||
case home
|
case home
|
||||||
case likeList
|
case likeList
|
||||||
|
case lookUp
|
||||||
}
|
}
|
||||||
|
|
||||||
static func persistToots(
|
static func persistToots(
|
||||||
managedObjectContext: NSManagedObjectContext,
|
managedObjectContext: NSManagedObjectContext,
|
||||||
domain: String,
|
domain: String,
|
||||||
query: Mastodon.API.Timeline.TimelineQuery,
|
query: Mastodon.API.Timeline.TimelineQuery?,
|
||||||
response: Mastodon.Response.Content<[Mastodon.Entity.Status]>,
|
response: Mastodon.Response.Content<[Mastodon.Entity.Status]>,
|
||||||
persistType: PersistTimelineType,
|
persistType: PersistTimelineType,
|
||||||
requestMastodonUserID: MastodonUser.ID?, // could be nil when response from public endpoint
|
requestMastodonUserID: MastodonUser.ID?, // could be nil when response from public endpoint
|
||||||
|
@ -122,6 +123,7 @@ extension APIService.Persist {
|
||||||
case .home: return .homeTimeline
|
case .home: return .homeTimeline
|
||||||
case .public: return .publicTimeline
|
case .public: return .publicTimeline
|
||||||
case .likeList: return .likeList
|
case .likeList: return .likeList
|
||||||
|
case .lookUp: return .lookUp
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -152,7 +154,8 @@ extension APIService.Persist {
|
||||||
// home timeline tasks
|
// home timeline tasks
|
||||||
switch persistType {
|
switch persistType {
|
||||||
case .home:
|
case .home:
|
||||||
guard let requestMastodonUserID = requestMastodonUserID else {
|
guard let query = query,
|
||||||
|
let requestMastodonUserID = requestMastodonUserID else {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
//
|
||||||
|
// StatusPrefetchingService.swift
|
||||||
|
// Mastodon
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-3-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import os.log
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
import CoreData
|
||||||
|
import CoreDataStack
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
|
final class StatusPrefetchingService {
|
||||||
|
|
||||||
|
typealias TaskID = String
|
||||||
|
|
||||||
|
let workingQueue = DispatchQueue(label: "status-prefetching-service-working-queue")
|
||||||
|
|
||||||
|
var disposeBag = Set<AnyCancellable>()
|
||||||
|
private(set) var statusPrefetchingDisposeBagDict: [TaskID: AnyCancellable] = [:]
|
||||||
|
|
||||||
|
weak var apiService: APIService?
|
||||||
|
|
||||||
|
init(apiService: APIService) {
|
||||||
|
self.apiService = apiService
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension StatusPrefetchingService {
|
||||||
|
|
||||||
|
func prefetchReplyTo(
|
||||||
|
domain: String,
|
||||||
|
statusObjectID: NSManagedObjectID,
|
||||||
|
statusID: Mastodon.Entity.Status.ID,
|
||||||
|
replyToStatusID: Mastodon.Entity.Status.ID,
|
||||||
|
authorizationBox: AuthenticationService.MastodonAuthenticationBox
|
||||||
|
) {
|
||||||
|
workingQueue.async { [weak self] in
|
||||||
|
guard let self = self, let apiService = self.apiService else { return }
|
||||||
|
let taskID = domain + "@" + statusID + "->" + replyToStatusID
|
||||||
|
guard self.statusPrefetchingDisposeBagDict[taskID] == nil else { return }
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: prefetching replyTo: %s", ((#file as NSString).lastPathComponent), #line, #function, taskID)
|
||||||
|
|
||||||
|
self.statusPrefetchingDisposeBagDict[taskID] = apiService.status(
|
||||||
|
domain: domain,
|
||||||
|
statusID: replyToStatusID,
|
||||||
|
authorizationBox: authorizationBox
|
||||||
|
)
|
||||||
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
|
// remove task when completed
|
||||||
|
guard let self = self else { return }
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: prefeched replyTo: %s", ((#file as NSString).lastPathComponent), #line, #function, taskID)
|
||||||
|
self.statusPrefetchingDisposeBagDict[taskID] = nil
|
||||||
|
}, receiveValue: { [weak self] _ in
|
||||||
|
guard let self = self else { return }
|
||||||
|
let backgroundManagedObjectContext = apiService.backgroundManagedObjectContext
|
||||||
|
backgroundManagedObjectContext.performChanges {
|
||||||
|
guard let status = backgroundManagedObjectContext.object(with: statusObjectID) as? Toot else { return }
|
||||||
|
do {
|
||||||
|
let predicate = Toot.predicate(domain: domain, id: replyToStatusID)
|
||||||
|
let request = Toot.sortedFetchRequest
|
||||||
|
request.predicate = predicate
|
||||||
|
request.returnsObjectsAsFaults = false
|
||||||
|
request.fetchLimit = 1
|
||||||
|
guard let replyTo = try backgroundManagedObjectContext.fetch(request).first else { return }
|
||||||
|
status.update(replyTo: replyTo)
|
||||||
|
} catch {
|
||||||
|
assertionFailure(error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sink { _ in
|
||||||
|
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: update status replyTo: %s", ((#file as NSString).lastPathComponent), #line, #function, taskID)
|
||||||
|
} receiveValue: { _ in
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
.store(in: &self.disposeBag)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -24,6 +24,8 @@ class AppContext: ObservableObject {
|
||||||
let apiService: APIService
|
let apiService: APIService
|
||||||
let authenticationService: AuthenticationService
|
let authenticationService: AuthenticationService
|
||||||
|
|
||||||
|
let statusPrefetchingService: StatusPrefetchingService
|
||||||
|
|
||||||
let documentStore: DocumentStore
|
let documentStore: DocumentStore
|
||||||
private var documentStoreSubscription: AnyCancellable!
|
private var documentStoreSubscription: AnyCancellable!
|
||||||
|
|
||||||
|
@ -46,6 +48,10 @@ class AppContext: ObservableObject {
|
||||||
apiService: _apiService
|
apiService: _apiService
|
||||||
)
|
)
|
||||||
|
|
||||||
|
statusPrefetchingService = StatusPrefetchingService(
|
||||||
|
apiService: _apiService
|
||||||
|
)
|
||||||
|
|
||||||
documentStore = DocumentStore()
|
documentStore = DocumentStore()
|
||||||
documentStoreSubscription = documentStore.objectWillChange
|
documentStoreSubscription = documentStore.objectWillChange
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// Mastodon+API+Statuses.swift
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by MainasuK Cirno on 2021-3-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
extension Mastodon.API.Statuses {
|
||||||
|
|
||||||
|
static func viewStatusEndpointURL(domain: String, statusID: Mastodon.Entity.Status.ID) -> URL {
|
||||||
|
let pathComponent = "statuses/" + statusID
|
||||||
|
return Mastodon.API.endpointURL(domain: domain).appendingPathComponent(pathComponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// View specific status
|
||||||
|
///
|
||||||
|
/// View information about a status
|
||||||
|
///
|
||||||
|
/// - Since: 0.0.0
|
||||||
|
/// - Version: 3.3.0
|
||||||
|
/// # Last Update
|
||||||
|
/// 2021/3/10
|
||||||
|
/// # Reference
|
||||||
|
/// [Document](https://docs.joinmastodon.org/methods/statuses/)
|
||||||
|
/// - Parameters:
|
||||||
|
/// - session: `URLSession`
|
||||||
|
/// - domain: Mastodon instance domain. e.g. "example.com"
|
||||||
|
/// - statusID: id for status
|
||||||
|
/// - authorization: User token. Could be nil if status is public
|
||||||
|
/// - Returns: `AnyPublisher` contains `Status` nested in the response
|
||||||
|
public static func status(
|
||||||
|
session: URLSession,
|
||||||
|
domain: String,
|
||||||
|
statusID: Mastodon.Entity.Poll.ID,
|
||||||
|
authorization: Mastodon.API.OAuth.Authorization?
|
||||||
|
) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Status>, Error> {
|
||||||
|
let request = Mastodon.API.get(
|
||||||
|
url: viewStatusEndpointURL(domain: domain, statusID: statusID),
|
||||||
|
query: nil,
|
||||||
|
authorization: authorization
|
||||||
|
)
|
||||||
|
return session.dataTaskPublisher(for: request)
|
||||||
|
.tryMap { data, response in
|
||||||
|
let value = try Mastodon.API.decode(type: Mastodon.Entity.Status.self, from: data, response: response)
|
||||||
|
return Mastodon.Response.Content(value: value, response: response)
|
||||||
|
}
|
||||||
|
.eraseToAnyPublisher()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -95,6 +95,7 @@ extension Mastodon.API {
|
||||||
public enum OAuth { }
|
public enum OAuth { }
|
||||||
public enum Onboarding { }
|
public enum Onboarding { }
|
||||||
public enum Polls { }
|
public enum Polls { }
|
||||||
|
public enum Statuses { }
|
||||||
public enum Timeline { }
|
public enum Timeline { }
|
||||||
public enum Favorites { }
|
public enum Favorites { }
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue