Reimplement favorite/reblog state (IOS-176)

This commit is contained in:
Marcus Kida 2023-11-23 11:21:52 +01:00
parent 9809e69751
commit e0671eb324
No known key found for this signature in database
GPG Key ID: 19FF64E08013CA40
26 changed files with 148 additions and 14 deletions

View File

@ -18,9 +18,11 @@ extension DataSourceFacade {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
_ = try await provider.context.apiService.favorite(
let newStatus = try await provider.context.apiService.favorite(
status: status,
authenticationBox: provider.authContext.mastodonAuthenticationBox
)
).value
provider.update(status: .fromEntity(newStatus))
}
}

View File

@ -18,9 +18,11 @@ extension DataSourceFacade {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
_ = try await provider.context.apiService.reblog(
let newStatus = try await provider.context.apiService.reblog(
status: status,
authenticationBox: provider.authContext.mastodonAuthenticationBox
)
} // end func
).value
provider.update(status: .fromEntity(newStatus))
}
}

View File

@ -439,7 +439,7 @@ extension StatusTableViewCellDelegate where Self: DataSourceProvider & AuthConte
assertionFailure("only works for status data provider")
return
}
try await DataSourceFacade.responseToActionToolbar(
provider: self,
status: status,

View File

@ -46,4 +46,5 @@ extension DataSourceItem {
protocol DataSourceProvider: ViewControllerWithDependencies {
func item(from source: DataSourceItem.Source) async -> DataSourceItem?
func update(status: MastodonStatus)
}

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
extension DiscoveryCommunityViewController: DataSourceProvider {
func item(from source: DataSourceItem.Source) async -> DataSourceItem? {
@ -27,6 +28,10 @@ extension DiscoveryCommunityViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
viewModel.statusFetchedResultsController.update(status: status)
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
extension DiscoveryPostsViewController: DataSourceProvider {
func item(from source: DataSourceItem.Source) async -> DataSourceItem? {
@ -27,6 +28,10 @@ extension DiscoveryPostsViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
viewModel.statusFetchedResultsController.update(status: status)
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
extension HashtagTimelineViewController: DataSourceProvider {
func item(from source: DataSourceItem.Source) async -> DataSourceItem? {
@ -27,6 +28,10 @@ extension HashtagTimelineViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
viewModel.fetchedResultsController.update(status: status)
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
extension HomeTimelineViewController: DataSourceProvider {
func item(from source: DataSourceItem.Source) async -> DataSourceItem? {
@ -35,6 +36,10 @@ extension HomeTimelineViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
viewModel.fetchedResultsController.update(status: status)
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -37,6 +37,10 @@ extension NotificationTimelineViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
viewModel.feedFetchedResultsController.update(status: status)
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
extension BookmarkViewController: DataSourceProvider {
func item(from source: DataSourceItem.Source) async -> DataSourceItem? {
@ -27,6 +28,10 @@ extension BookmarkViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
viewModel.statusFetchedResultsController.update(status: status)
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
extension FamiliarFollowersViewController: DataSourceProvider {
func item(from source: DataSourceItem.Source) async -> DataSourceItem? {
@ -27,6 +28,10 @@ extension FamiliarFollowersViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
assertionFailure("Implement not required in this class")
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
extension FavoriteViewController: DataSourceProvider {
func item(from source: DataSourceItem.Source) async -> DataSourceItem? {
@ -27,6 +28,10 @@ extension FavoriteViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
viewModel.statusFetchedResultsController.update(status: status)
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -11,6 +11,7 @@ import Combine
import MastodonCore
import MastodonUI
import MastodonLocalization
import MastodonSDK
final class FollowerListViewController: UIViewController, NeedsDependency {
@ -152,6 +153,10 @@ extension FollowerListViewController: DataSourceProvider {
return nil
}
}
func update(status: MastodonStatus) {
assertionFailure("Implement not required in this class")
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {

View File

@ -12,6 +12,7 @@ import MastodonLocalization
import MastodonCore
import MastodonUI
import CoreDataStack
import MastodonSDK
final class FollowingListViewController: UIViewController, NeedsDependency {
@ -148,6 +149,10 @@ extension FollowingListViewController: DataSourceProvider {
return nil
}
}
func update(status: MastodonStatus) {
assertionFailure("Implement not required in this class")
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
extension UserTimelineViewController: DataSourceProvider {
func item(from source: DataSourceItem.Source) async -> DataSourceItem? {
@ -27,6 +28,10 @@ extension UserTimelineViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
viewModel.statusFetchedResultsController.update(status: status)
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
extension FavoritedByViewController: DataSourceProvider {
func item(from source: DataSourceItem.Source) async -> DataSourceItem? {
@ -27,6 +28,10 @@ extension FavoritedByViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
assertionFailure("Implement not required in this class")
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -6,8 +6,10 @@
//
import UIKit
import MastodonSDK
extension RebloggedByViewController: DataSourceProvider {
func item(from source: DataSourceItem.Source) async -> DataSourceItem? {
var _indexPath = source.indexPath
if _indexPath == nil, let cell = source.tableViewCell {
@ -27,6 +29,10 @@ extension RebloggedByViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
assertionFailure("Implement not required in this class")
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
// MARK: - DataSourceProvider
extension SearchHistoryViewController: DataSourceProvider {
@ -28,6 +29,10 @@ extension SearchHistoryViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
assertionFailure("Implement not required in this class")
}
@MainActor
private func indexPath(for cell: UICollectionViewCell) async -> IndexPath? {
return collectionView.indexPath(for: cell)

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
// MARK: - DataSourceProvider
extension SearchResultViewController: DataSourceProvider {
@ -32,6 +33,10 @@ extension SearchResultViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
viewModel.statusFetchedResultsController.update(status: status)
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -6,6 +6,7 @@
//
import UIKit
import MastodonSDK
// MARK: - DataSourceProvider
extension ThreadViewController: DataSourceProvider {
@ -28,6 +29,10 @@ extension ThreadViewController: DataSourceProvider {
}
}
func update(status: MastodonStatus) {
viewModel.root = .root(context: .init(status: status))
}
@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)

View File

@ -38,6 +38,28 @@ final public class FeedFetchedResultsController {
records = try await load(kind: kind, sinceId: lastId)
}
}
public func update(status: MastodonStatus) {
var newRecords = Array(records)
for (i, record) in newRecords.enumerated() {
if record.status?.id == status.id {
newRecords[i] = .fromStatus(status, kind: record.kind)
} else if let reblog = status.reblog, reblog.id == record.status?.id {
newRecords[i] = .fromStatus(status, kind: record.kind)
} else if let reblog = record.status?.reblog, reblog.id == status.id {
newRecords[i] = .fromStatus(status, kind: record.kind)
switch status.entity.reblogged {
case .some(true):
newRecords[i].status?.reblog = status
case .some(false):
newRecords[i].status?.reblog = nil
case .none:
break
}
}
}
records = newRecords
}
}
private extension FeedFetchedResultsController {

View File

@ -35,4 +35,17 @@ public final class StatusFetchedResultsController {
public func appendRecords(_ records: [MastodonStatus]) {
self.records += records
}
@MainActor
public func update(status: MastodonStatus) {
var newRecords = Array(records)
for (i, record) in newRecords.enumerated() {
if record.id == status.id {
newRecords[i] = status
} else if let reblog = record.reblog, reblog.id == status.id {
newRecords[i].reblog = status
}
}
records = newRecords
}
}

View File

@ -39,7 +39,7 @@ extension APIService {
let reblogContext = MastodonReblogContext(
statusID: _status.id,
isReblogged: !isReblogged,
isReblogged: isReblogged,
rebloggedCount: rebloggedCount
)
return reblogContext

View File

@ -136,11 +136,22 @@ extension Mastodon.Entity.Status {
extension Mastodon.Entity.Status: Hashable {
public static func == (lhs: Mastodon.Entity.Status, rhs: Mastodon.Entity.Status) -> Bool {
lhs.uri == rhs.uri && lhs.id == rhs.id
lhs.uri == rhs.uri &&
lhs.id == rhs.id &&
lhs.reblog == rhs.reblog &&
lhs.favourited == rhs.favourited &&
lhs.reblogged == rhs.reblogged &&
lhs.bookmarked == rhs.bookmarked &&
lhs.pinned == rhs.pinned
}
public func hash(into hasher: inout Hasher) {
hasher.combine(uri)
hasher.combine(id)
hasher.combine(reblog)
hasher.combine(favourited)
hasher.combine(reblogged)
hasher.combine(bookmarked)
hasher.combine(pinned)
}
}

View File

@ -54,13 +54,15 @@ public extension MastodonFeed {
extension MastodonFeed: Hashable {
public static func == (lhs: MastodonFeed, rhs: MastodonFeed) -> Bool {
lhs.id == rhs.id && (lhs.status?.id == rhs.status?.id || lhs.notification?.id == rhs.notification?.id)
lhs.id == rhs.id &&
lhs.status?.entity == rhs.status?.entity &&
lhs.status?.reblog?.entity == rhs.status?.reblog?.entity
}
public func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(status)
hasher.combine(notification)
hasher.combine(status?.entity)
hasher.combine(status?.reblog?.entity)
}
}

View File

@ -8,7 +8,7 @@ public final class MastodonStatus: ObservableObject {
public typealias ID = Mastodon.Entity.Status.ID
@Published public var entity: Mastodon.Entity.Status
@Published public private(set) var reblog: MastodonStatus?
@Published public var reblog: MastodonStatus?
@Published public var isSensitiveToggled: Bool = false
@ -36,12 +36,13 @@ extension MastodonStatus {
extension MastodonStatus: Hashable {
public static func == (lhs: MastodonStatus, rhs: MastodonStatus) -> Bool {
lhs.entity.id == rhs.entity.id
lhs.entity == rhs.entity &&
lhs.reblog?.entity == rhs.reblog?.entity
}
public func hash(into hasher: inout Hasher) {
hasher.combine(entity)
hasher.combine(isSensitiveToggled)
hasher.combine(reblog?.entity)
}
}