Skip to content

Commit

Permalink
Improve status updating mechanism (#1210)
Browse files Browse the repository at this point in the history
  • Loading branch information
kimar authored Jan 30, 2024
1 parent c0c795e commit 383a75e
Show file tree
Hide file tree
Showing 41 changed files with 702 additions and 364 deletions.
4 changes: 4 additions & 0 deletions Mastodon.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
2A3D9B7E29A8F33A00F30313 /* StatusHistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3D9B7D29A8F33A00F30313 /* StatusHistoryView.swift */; };
2A3F6FE3292ECB5E002E6DA7 /* FollowedTagsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */; };
2A3F6FE5292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */; };
2A409F832B5955290044E472 /* MastodonStatusThreadViewModel+State.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A409F822B5955290044E472 /* MastodonStatusThreadViewModel+State.swift */; };
2A506CF4292CD85800059C37 /* FollowedTagsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */; };
2A506CF6292D040100059C37 /* HashtagTimelineHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A506CF5292D040100059C37 /* HashtagTimelineHeaderView.swift */; };
2A64515E29642A8A00CD8B8A /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A6451022964223800CD8B8A /* UniformTypeIdentifiers.framework */; };
Expand Down Expand Up @@ -642,6 +643,7 @@
2A3D9B7D29A8F33A00F30313 /* StatusHistoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusHistoryView.swift; sourceTree = "<group>"; };
2A3F6FE2292ECB5E002E6DA7 /* FollowedTagsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewModel.swift; sourceTree = "<group>"; };
2A3F6FE4292F6E44002E6DA7 /* FollowedTagsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsTableViewCell.swift; sourceTree = "<group>"; };
2A409F822B5955290044E472 /* MastodonStatusThreadViewModel+State.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MastodonStatusThreadViewModel+State.swift"; sourceTree = "<group>"; };
2A506CF3292CD85800059C37 /* FollowedTagsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowedTagsViewController.swift; sourceTree = "<group>"; };
2A506CF5292D040100059C37 /* HashtagTimelineHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashtagTimelineHeaderView.swift; sourceTree = "<group>"; };
2A6451022964223800CD8B8A /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; };
Expand Down Expand Up @@ -2690,6 +2692,7 @@
DB938F0E2624119800E5B6C1 /* ThreadViewModel+LoadThreadState.swift */,
DB938F0826240F3C00E5B6C1 /* RemoteThreadViewModel.swift */,
DB0FCB7F27968F70006C02E2 /* MastodonStatusThreadViewModel.swift */,
2A409F822B5955290044E472 /* MastodonStatusThreadViewModel+State.swift */,
);
path = Thread;
sourceTree = "<group>";
Expand Down Expand Up @@ -4018,6 +4021,7 @@
0FB3D31E25E534C700AAD544 /* PickServerCategoryCollectionViewCell.swift in Sources */,
DB0FCB882796BDA9006C02E2 /* SearchItem.swift in Sources */,
DB6180ED26391C6C0018D199 /* TransitioningMath.swift in Sources */,
2A409F832B5955290044E472 /* MastodonStatusThreadViewModel+State.swift in Sources */,
2D6DE40026141DF600A63F6A /* SearchViewModel.swift in Sources */,
D886FBD329DF710F00272017 /* WelcomeSeparatorView.swift in Sources */,
DB0617FD27855BFE0030EE79 /* ServerRuleItem.swift in Sources */,
Expand Down
38 changes: 33 additions & 5 deletions Mastodon/Diffable/Status/StatusItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,39 @@ extension StatusItem {
case leaf(context: Context)

public var record: MastodonStatus {
switch self {
case .root(let threadContext),
.reply(let threadContext),
.leaf(let threadContext):
return threadContext.status
get {
switch self {
case .root(let threadContext),
.reply(let threadContext),
.leaf(let threadContext):
return threadContext.status
}
}

set {
switch self {
case let .root(threadContext):
self = .root(context: .init(
status: newValue,
displayUpperConversationLink: threadContext.displayUpperConversationLink,
displayBottomConversationLink: threadContext.displayBottomConversationLink)
)

case let .reply(threadContext):
self = .reply(context: .init(
status: newValue,
displayUpperConversationLink: threadContext.displayUpperConversationLink,
displayBottomConversationLink: threadContext.displayBottomConversationLink)
)

case let .leaf(threadContext):
self = .leaf(context: .init(
status: newValue,
displayUpperConversationLink: threadContext.displayUpperConversationLink,
displayBottomConversationLink: threadContext.displayBottomConversationLink)
)

}
}
}
}
Expand Down
7 changes: 4 additions & 3 deletions Mastodon/Protocol/Provider/DataSourceFacade+Bookmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ import MastodonCore
import MastodonSDK

extension DataSourceFacade {
@MainActor
public static func responseToStatusBookmarkAction(
provider: NeedsDependency & AuthContextProvider & DataSourceProvider,
status: MastodonStatus
) async throws {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
selectionFeedbackGenerator.selectionChanged()

let updatedStatus = try await provider.context.apiService.bookmark(
record: status,
Expand All @@ -27,6 +28,6 @@ extension DataSourceFacade {
let newStatus: MastodonStatus = .fromEntity(updatedStatus)
newStatus.isSensitiveToggled = status.isSensitiveToggled

provider.update(status: newStatus)
provider.update(status: newStatus, intent: .bookmark(updatedStatus.bookmarked == true))
}
}
7 changes: 4 additions & 3 deletions Mastodon/Protocol/Provider/DataSourceFacade+Favorite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import MastodonSDK
import MastodonCore

extension DataSourceFacade {
@MainActor
public static func responseToStatusFavoriteAction(
provider: DataSourceProvider & AuthContextProvider,
status: MastodonStatus
) async throws {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
selectionFeedbackGenerator.selectionChanged()

let updatedStatus = try await provider.context.apiService.favorite(
status: status,
Expand All @@ -26,6 +27,6 @@ extension DataSourceFacade {
let newStatus: MastodonStatus = .fromEntity(updatedStatus)
newStatus.isSensitiveToggled = status.isSensitiveToggled

provider.update(status: newStatus)
provider.update(status: newStatus, intent: .favorite(updatedStatus.favourited == true))
}
}
7 changes: 4 additions & 3 deletions Mastodon/Protocol/Provider/DataSourceFacade+Reblog.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import MastodonUI
import MastodonSDK

extension DataSourceFacade {
@MainActor
static func responseToStatusReblogAction(
provider: DataSourceProvider & AuthContextProvider,
status: MastodonStatus
) async throws {
let selectionFeedbackGenerator = await UISelectionFeedbackGenerator()
await selectionFeedbackGenerator.selectionChanged()
let selectionFeedbackGenerator = UISelectionFeedbackGenerator()
selectionFeedbackGenerator.selectionChanged()

let updatedStatus = try await provider.context.apiService.reblog(
status: status,
Expand All @@ -27,6 +28,6 @@ extension DataSourceFacade {
newStatus.reblog?.isSensitiveToggled = status.isSensitiveToggled
newStatus.isSensitiveToggled = status.isSensitiveToggled

provider.update(status: newStatus)
provider.update(status: newStatus, intent: .reblog(updatedStatus.reblogged == true))
}
}
6 changes: 3 additions & 3 deletions Mastodon/Protocol/Provider/DataSourceFacade+Status.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ extension DataSourceFacade {
authenticationBox: dependency.authContext.mastodonAuthenticationBox
).value.asMastodonStatus

dependency.delete(status: deletedStatus)
dependency.update(status: deletedStatus, intent: .delete)
}

}
Expand Down Expand Up @@ -430,7 +430,7 @@ extension DataSourceFacade {
}

extension DataSourceFacade {

@MainActor
static func responseToToggleSensitiveAction(
dependency: NeedsDependency & DataSourceProvider,
status: MastodonStatus
Expand All @@ -440,7 +440,7 @@ extension DataSourceFacade {
let newStatus: MastodonStatus = .fromEntity(_status.entity)
newStatus.isSensitiveToggled = !_status.isSensitiveToggled

dependency.update(status: newStatus)
dependency.update(status: newStatus, intent: .toggleSensitive(newStatus.isSensitiveToggled))
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
// MARK: - Follow Request
extension NotificationTableViewCellDelegate where Self: DataSourceProvider & AuthContextProvider {

@MainActor
func tableViewCell(
_ cell: UITableViewCell,
notificationView: NotificationView,
Expand All @@ -105,14 +106,30 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
return
}

try await DataSourceFacade.responseToUserFollowRequestAction(
dependency: self,
notification: notification,
query: .accept
)
let originalTransientFollowRequestState = notificationView.viewModel.transientFollowRequestState
let originalFollowRequestState = notificationView.viewModel.followRequestState

notificationView.viewModel.transientFollowRequestState = .init(state: .isAccepting)
notificationView.viewModel.followRequestState = .init(state: .isAccepting)

do {
try await DataSourceFacade.responseToUserFollowRequestAction(
dependency: self,
notification: notification,
query: .accept
)

notificationView.viewModel.transientFollowRequestState = .init(state: .isAccept)
notificationView.viewModel.followRequestState = .init(state: .isAccept)
} catch {
notificationView.viewModel.transientFollowRequestState = originalTransientFollowRequestState
notificationView.viewModel.followRequestState = originalFollowRequestState
throw error
}
} // end Task
}

@MainActor
func tableViewCell(
_ cell: UITableViewCell,
notificationView: NotificationView,
Expand All @@ -129,11 +146,26 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
return
}

try await DataSourceFacade.responseToUserFollowRequestAction(
dependency: self,
notification: notification,
query: .reject
)
let originalTransientFollowRequestState = notificationView.viewModel.transientFollowRequestState
let originalFollowRequestState = notificationView.viewModel.followRequestState

notificationView.viewModel.transientFollowRequestState = .init(state: .isRejecting)
notificationView.viewModel.followRequestState = .init(state: .isRejecting)

do {
try await DataSourceFacade.responseToUserFollowRequestAction(
dependency: self,
notification: notification,
query: .reject
)

notificationView.viewModel.transientFollowRequestState = .init(state: .isReject)
notificationView.viewModel.followRequestState = .init(state: .isReject)
} catch {
notificationView.viewModel.transientFollowRequestState = originalTransientFollowRequestState
notificationView.viewModel.followRequestState = originalFollowRequestState
throw error
}
} // end Task
}

Expand Down
3 changes: 1 addition & 2 deletions Mastodon/Protocol/Provider/DataSourceProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,5 @@ extension DataSourceItem {

protocol DataSourceProvider: ViewControllerWithDependencies {
func item(from source: DataSourceItem.Source) async -> DataSourceItem?
func update(status: MastodonStatus)
func delete(status: MastodonStatus)
func update(status: MastodonStatus, intent: MastodonStatus.UpdateIntent)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,10 @@ extension DiscoveryCommunityViewController: DataSourceProvider {
}
}

func update(status: MastodonStatus) {
viewModel.dataController.update(status: status)
func update(status: MastodonStatus, intent: MastodonStatus.UpdateIntent) {
viewModel.dataController.update(status: status, intent: intent)
}

func delete(status: MastodonStatus) {
viewModel.dataController.setRecords(
viewModel.dataController.records.filter { $0.id != status.id }
)
}


@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,10 @@ extension DiscoveryPostsViewController: DataSourceProvider {
}
}

func update(status: MastodonStatus) {
viewModel.dataController.update(status: status)
func update(status: MastodonStatus, intent: MastodonStatus.UpdateIntent) {
viewModel.dataController.update(status: status, intent: intent)
}

func delete(status: MastodonStatus) {
viewModel.dataController.setRecords(
viewModel.dataController.records.filter { $0.id != status.id }
)
}


@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,10 @@ extension HashtagTimelineViewController: DataSourceProvider {
}
}

func update(status: MastodonStatus) {
viewModel.dataController.update(status: status)
func update(status: MastodonStatus, intent: MastodonStatus.UpdateIntent) {
viewModel.dataController.update(status: status, intent: intent)
}

func delete(status: MastodonStatus) {
viewModel.dataController.deleteRecord(status)
}


@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,10 @@ extension HomeTimelineViewController: DataSourceProvider {
}
}

func update(status: MastodonStatus) {
viewModel.dataController.update(status: status)
func update(status: MastodonStatus, intent: MastodonStatus.UpdateIntent) {
viewModel.dataController.update(status: status, intent: intent)
}

func delete(status: MastodonStatus) {
viewModel.dataController.records = viewModel.dataController.records.filter { $0.id != status.id }
}


@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)
Expand Down
5 changes: 3 additions & 2 deletions Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,10 @@ extension HomeTimelineViewController {
}
.store(in: &disposeBag)

context.publisherService.statusPublishResult.sink { result in
if case .success(.edit) = result {
context.publisherService.statusPublishResult.receive(on: DispatchQueue.main).sink { result in
if case .success(.edit(let status)) = result {
self.viewModel.hasPendingStatusEditReload = true
self.viewModel.dataController.update(status: .fromEntity(status.value), intent: .edit)
}
}.store(in: &disposeBag)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ extension HomeTimelineViewModel.LoadLatestState {

Task {
let latestStatusIDs: [Status.ID] = latestFeedRecords.compactMap { record in
return record.status?.id
return record.status?.reblog?.id ?? record.status?.id
}

do {
Expand All @@ -103,7 +103,7 @@ extension HomeTimelineViewModel.LoadLatestState {

// stop refresher if no new statuses
let statuses = response.value
let newStatuses = statuses.filter { !latestStatusIDs.contains($0.id) }
let newStatuses = statuses.filter { status in !latestStatusIDs.contains(where: { $0 == status.reblog?.id || $0 == status.id }) }

if newStatuses.isEmpty {
viewModel.didLoadLatest.send()
Expand All @@ -112,10 +112,10 @@ extension HomeTimelineViewModel.LoadLatestState {
viewModel.homeTimelineNavigationBarTitleViewModel.newPostsIncoming()
}

var newRecords: [MastodonFeed] = newStatuses.map {
MastodonFeed.fromStatus(.fromEntity($0), kind: .home)
}
viewModel.dataController.records = {
var newRecords: [MastodonFeed] = newStatuses.map {
MastodonFeed.fromStatus(.fromEntity($0), kind: .home)
}
var oldRecords = viewModel.dataController.records
for (i, record) in newRecords.enumerated() {
if let index = oldRecords.firstIndex(where: { $0.status?.reblog?.id == record.id || $0.status?.id == record.id }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,10 @@ extension NotificationTimelineViewController: DataSourceProvider {
}
}

func update(status: MastodonStatus) {
viewModel.dataController.update(status: status)
func update(status: MastodonStatus, intent: MastodonStatus.UpdateIntent) {
viewModel.dataController.update(status: status, intent: intent)
}

func delete(status: MastodonStatus) {
viewModel.dataController.delete(status: status)
}


@MainActor
private func indexPath(for cell: UITableViewCell) async -> IndexPath? {
return tableView.indexPath(for: cell)
Expand Down
Loading

0 comments on commit 383a75e

Please sign in to comment.