2
2
mirror of https://github.com/mastodon/mastodon-ios synced 2025-04-11 22:58:02 +02:00

Do not allow setting FeedController’s records from outside the class.

Because we want to be certain that incoming records are always correctly filtered.

Fixes #1354 [BUG] Mastodon iOS App Ignores "Hide completely" Filter action Setting
This commit is contained in:
shannon 2024-12-16 14:29:33 -05:00
parent d5e8300066
commit eb5433ec2b
5 changed files with 59 additions and 43 deletions

View File

@ -112,18 +112,21 @@ final class HomeTimelineViewController: UIViewController, MediaPreviewableViewCo
let showFollowingAction = UIAction(title: L10n.Scene.HomeTimeline.TimelineMenu.following, image: .init(systemName: "house")) { [weak self] _ in
guard let self, let viewModel = self.viewModel else { return }
viewModel.timelineContext = .home
viewModel.dataController.records = []
viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.ContextSwitch.self)
timelineSelectorButton.setAttributedTitle(
.init(string: L10n.Scene.HomeTimeline.TimelineMenu.following, attributes: [
.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold))
]),
for: .normal)
timelineSelectorButton.sizeToFit()
timelineSelectorButton.menu = generateTimelineSelectorMenu()
Task { [weak self] in
guard let self else { return }
viewModel.timelineContext = .home
await viewModel.dataController.setRecordsAfterFiltering([])
viewModel.loadLatestStateMachine.enter(HomeTimelineViewModel.LoadLatestState.ContextSwitch.self)
self.timelineSelectorButton.setAttributedTitle(
.init(string: L10n.Scene.HomeTimeline.TimelineMenu.following, attributes: [
.font: UIFontMetrics(forTextStyle: .headline).scaledFont(for: .systemFont(ofSize: 20, weight: .semibold))
]),
for: .normal)
self.timelineSelectorButton.sizeToFit()
self.timelineSelectorButton.menu = self.generateTimelineSelectorMenu()
}
}
let showLocalTimelineAction = UIAction(title: L10n.Scene.HomeTimeline.TimelineMenu.localCommunity, image: .init(systemName: "building.2")) { [weak self] action in

View File

@ -87,7 +87,7 @@ extension HomeTimelineViewModel.LoadLatestState {
return
}
viewModel.dataController.records = []
await viewModel.dataController.setRecordsAfterFiltering([])
var snapshot = NSDiffableDataSourceSnapshot<StatusSection, StatusItem>()
snapshot.appendSections([.main])
snapshot.appendItems([.topLoader], toSection: .main)
@ -153,7 +153,7 @@ extension HomeTimelineViewModel.LoadLatestState {
if statuses.isEmpty {
// stop refresher if no new statuses
viewModel.dataController.records = []
await viewModel.dataController.setRecordsAfterFiltering([])
viewModel.didLoadLatest.send()
} else {
var toAdd = [MastodonFeed]()
@ -169,7 +169,8 @@ extension HomeTimelineViewModel.LoadLatestState {
toAdd.last?.hasMore = latestFeedRecords.isNotEmpty
}
viewModel.dataController.records = (toAdd + latestFeedRecords).removingDuplicates()
let newRecords = (toAdd + latestFeedRecords).removingDuplicates()
await viewModel.dataController.setRecordsAfterFiltering(newRecords)
}
viewModel.timelineIsEmpty.value = (latestStatusIDs.isEmpty && statuses.isEmpty) ? {

View File

@ -94,9 +94,12 @@ final class HomeTimelineViewModel: NSObject {
self.authenticationBox = authenticationBox
self.dataController = FeedDataController(authenticationBox: authenticationBox, kind: .home(timeline: timelineContext))
super.init()
self.dataController.records = (try? PersistenceManager.shared.cachedTimeline(.homeTimeline(authenticationBox)).map {
let initialRecords = (try? PersistenceManager.shared.cachedTimeline(.homeTimeline(authenticationBox)).map {
MastodonFeed.fromStatus($0, kind: .home)
}) ?? []
Task {
await self.dataController.setRecordsAfterFiltering(initialRecords)
}
authenticationBox.inMemoryCache.$followingUserIds.sink { [weak self] _ in
self?.homeTimelineNeedRefresh.send()
@ -231,10 +234,13 @@ extension HomeTimelineViewModel {
}
let combinedRecords = Array(head + feedItems + tail)
dataController.records = combinedRecords
record.isLoadingMore = false
record.hasMore = false
Task {
await dataController.setRecordsAfterFiltering(combinedRecords)
record.isLoadingMore = false
record.hasMore = false
}
}
}

View File

@ -54,18 +54,22 @@ final class NotificationTimelineViewModel {
self.scope = scope
self.dataController = FeedDataController(authenticationBox: authenticationBox, kind: scope.feedKind)
self.notificationPolicy = notificationPolicy
switch scope {
case .everything:
self.dataController.records = (try? FileManager.default.cachedNotificationsAll(for: authenticationBox))?.map({ notification in
MastodonFeed.fromNotification(notification, relationship: nil, kind: .notificationAll)
}) ?? []
case .mentions:
self.dataController.records = (try? FileManager.default.cachedNotificationsMentions(for: authenticationBox))?.map({ notification in
MastodonFeed.fromNotification(notification, relationship: nil, kind: .notificationMentions)
}) ?? []
case .fromAccount(_):
self.dataController.records = []
Task {
switch scope {
case .everything:
let initialRecords = (try? FileManager.default.cachedNotificationsAll(for: authenticationBox))?.map({ notification in
MastodonFeed.fromNotification(notification, relationship: nil, kind: .notificationAll)
}) ?? []
await self.dataController.setRecordsAfterFiltering(initialRecords)
case .mentions:
let initialRecords = (try? FileManager.default.cachedNotificationsMentions(for: authenticationBox))?.map({ notification in
MastodonFeed.fromNotification(notification, relationship: nil, kind: .notificationMentions)
}) ?? []
await self.dataController.setRecordsAfterFiltering(initialRecords)
case .fromAccount(_):
await self.dataController.setRecordsAfterFiltering([])
}
}
self.dataController.$records

View File

@ -9,7 +9,7 @@ final public class FeedDataController {
private let logger = Logger(subsystem: "FeedDataController", category: "Data")
private static let entryNotFoundMessage = "Failed to find suitable record. Depending on the context this might result in errors (data not being updated) or can be discarded (e.g. when there are mixed data sources where an entry might or might not exist)."
@Published public var records: [MastodonFeed] = []
@Published public private(set) var records: [MastodonFeed] = []
private let authenticationBox: MastodonAuthenticationBox
private let kind: MastodonFeed.Kind
@ -25,21 +25,27 @@ final public class FeedDataController {
if let filterBox {
Task { [weak self] in
guard let self else { return }
self.records = await self.filter(self.records, forFeed: kind, with: filterBox)
await self.setRecordsAfterFiltering(self.records)
}
}
}
.store(in: &subscriptions)
}
public func setRecordsAfterFiltering(_ newRecords: [MastodonFeed]) async {
guard let filterBox = StatusFilterService.shared.activeFilterBox else { self.records = newRecords; return }
self.records = await self.filter(self.records, forFeed: kind, with: filterBox)
}
public func appendRecordsAfterFiltering(_ additionalRecords: [MastodonFeed]) async {
guard let filterBox = StatusFilterService.shared.activeFilterBox else { self.records += additionalRecords; return }
self.records += await self.filter(additionalRecords, forFeed: kind, with: filterBox)
}
public func loadInitial(kind: MastodonFeed.Kind) {
Task {
let unfilteredRecords = try await load(kind: kind, maxID: nil)
if let filterBox = StatusFilterService.shared.activeFilterBox {
records = await filter(unfilteredRecords, forFeed: kind, with: filterBox)
} else {
records = unfilteredRecords
}
await setRecordsAfterFiltering(unfilteredRecords)
}
}
@ -50,11 +56,7 @@ final public class FeedDataController {
}
let unfiltered = try await load(kind: kind, maxID: lastId)
if let filterBox = StatusFilterService.shared.activeFilterBox {
records += await filter(unfiltered, forFeed: kind, with: filterBox)
} else {
records += unfiltered
}
await self.appendRecordsAfterFiltering(unfiltered)
}
}