Skip to content

Commit

Permalink
Data model changes to enable grouped notifications
Browse files Browse the repository at this point in the history
Work in progress. This will run but will not show any notifications.

Contributes to IOS-355
Contributes to IOS-253
  • Loading branch information
whattherestimefor committed Jan 9, 2025
1 parent 4dd856f commit 1009371
Show file tree
Hide file tree
Showing 19 changed files with 762 additions and 142 deletions.
1 change: 1 addition & 0 deletions Mastodon/Diffable/Status/MastodonItemIdentifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import CoreDataStack
import MastodonUI
import MastodonSDK

//@available(*, deprecated, message: "migrate to MastodonFeedItemIdentifier")
enum MastodonItemIdentifier: Hashable {
case feed(MastodonFeed)
case feedLoader(feed: MastodonFeed)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,11 @@ import CoreDataStack
import MastodonSDK
import MastodonCore

extension NotificationTableViewCell {
final class ViewModel {
let value: Value

init(value: Value) {
self.value = value
}

enum Value {
case feed(MastodonFeed)
}
}
}

extension NotificationTableViewCell {

func configure(
tableView: UITableView,
viewModel: ViewModel,
notificationIdentifier: MastodonFeedItemIdentifier,
delegate: NotificationTableViewCellDelegate?,
authenticationBox: MastodonAuthenticationBox
) {
Expand All @@ -40,11 +26,8 @@ extension NotificationTableViewCell {
notificationView.statusView.frame.size.width = tableView.frame.width - containerViewHorizontalMargin
notificationView.quoteStatusView.frame.size.width = tableView.frame.width - containerViewHorizontalMargin // the as same width as statusView
}

switch viewModel.value {
case .feed(let feed):
notificationView.configure(feed: feed, authenticationBox: authenticationBox)
}

notificationView.configure(notificationItem: notificationIdentifier)

self.delegate = delegate
}
Expand Down
6 changes: 3 additions & 3 deletions Mastodon/Scene/Notification/NotificationItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Foundation
import MastodonSDK

enum NotificationItem: Hashable {
case filteredNotifications(policy: Mastodon.Entity.NotificationPolicy)
case feed(record: MastodonFeed)
case feedLoader(record: MastodonFeed)
case filteredNotificationsInfo(policy: Mastodon.Entity.NotificationPolicy)
case notification(MastodonFeedItemIdentifier)
case feedLoader(MastodonFeedItemIdentifier)
case bottomLoader
}
12 changes: 6 additions & 6 deletions Mastodon/Scene/Notification/NotificationSection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ extension NotificationSection {

return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
switch item {
case .feed(let feed):
if let notification = feed.notification, let accountWarning = notification.accountWarning {
case .notification(let notificationItem):
if let notification = MastodonFeedItemCacheManager.shared.cachedItem(notificationItem) as? Mastodon.Entity.Notification, let accountWarning = notification.accountWarning {
let cell = tableView.dequeueReusableCell(withIdentifier: AccountWarningNotificationCell.reuseIdentifier, for: indexPath) as! AccountWarningNotificationCell
cell.configure(with: accountWarning)
return cell
Expand All @@ -51,7 +51,7 @@ extension NotificationSection {
configure(
tableView: tableView,
cell: cell,
viewModel: NotificationTableViewCell.ViewModel(value: .feed(feed)),
itemIdentifier: notificationItem,
configuration: configuration
)
return cell
Expand All @@ -66,7 +66,7 @@ extension NotificationSection {
cell.activityIndicatorView.startAnimating()
return cell

case .filteredNotifications(let policy):
case .filteredNotificationsInfo(let policy):
let cell = tableView.dequeueReusableCell(withIdentifier: NotificationFilteringBannerTableViewCell.reuseIdentifier, for: indexPath) as! NotificationFilteringBannerTableViewCell
cell.configure(with: policy)

Expand All @@ -81,7 +81,7 @@ extension NotificationSection {
static func configure(
tableView: UITableView,
cell: NotificationTableViewCell,
viewModel: NotificationTableViewCell.ViewModel,
itemIdentifier: MastodonFeedItemIdentifier,
configuration: Configuration
) {
StatusSection.setupStatusPollDataSource(
Expand All @@ -96,7 +96,7 @@ extension NotificationSection {

cell.configure(
tableView: tableView,
viewModel: viewModel,
notificationIdentifier: itemIdentifier,
delegate: configuration.notificationTableViewCellDelegate,
authenticationBox: configuration.authenticationBox
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,27 @@ extension NotificationTimelineViewController: DataSourceProvider {
}

switch item {
case .feed(let feed):
let item: DataSourceItem? = {
guard feed.kind == .notificationAll || feed.kind == .notificationMentions else { return nil }

if let notification = feed.notification {
let mastodonNotification = MastodonNotification.fromEntity(notification, relationship: nil)
return .notification(record: mastodonNotification)
} else {
return nil
}
}()
return item
case .filteredNotifications(let policy):
case .notification(let notificationItem):
switch notificationItem {
case .notification, .notificationGroup:
let item: DataSourceItem? = {
// guard feed.kind == .notificationAll || feed.kind == .notificationMentions else { return nil }

if let notification = MastodonFeedItemCacheManager.shared.cachedItem(notificationItem) as? Mastodon.Entity.Notification {
let mastodonNotification = MastodonNotification.fromEntity(notification, relationship: nil)
return .notification(record: mastodonNotification)
} else {
return nil
}
}()
return item
case .status:
assertionFailure("unexpected item in notifications feed")
return nil
}
case .filteredNotificationsInfo(let policy):
return DataSourceItem.notificationBanner(policy: policy)
case .bottomLoader, .feedLoader(_):
case .bottomLoader, .feedLoader:
return nil
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import UIKit
import Combine
import CoreDataStack
import MastodonCore
import MastodonSDK
import MastodonLocalization

class NotificationTimelineViewController: UIViewController, MediaPreviewableViewController {
Expand Down Expand Up @@ -264,7 +265,7 @@ extension NotificationTimelineViewController: TableViewControllerNavigateable {

static func validNavigateableItem(_ item: NotificationItem) -> Bool {
switch item {
case .feed:
case .notification:
return true
default:
return false
Expand All @@ -278,10 +279,43 @@ extension NotificationTimelineViewController: TableViewControllerNavigateable {

Task { @MainActor in
switch item {
case .feed(let record):
guard let notification = record.notification else { return }
case .notification(let notificationItem):
let status: Mastodon.Entity.Status?
let account: Mastodon.Entity.Account?
switch notificationItem {
case .notification(let id):
guard let notification = MastodonFeedItemCacheManager.shared.cachedItem(notificationItem) as? Mastodon.Entity.Notification else {
status = nil
account = nil
break
}
status = notification.status
account = notification.account

case .notificationGroup(let id):
guard let notificationGroup = MastodonFeedItemCacheManager.shared.cachedItem(notificationItem) as? Mastodon.Entity.NotificationGroup else {
status = nil
account = nil
break
}
if let statusID = notificationGroup.statusID {
status = MastodonFeedItemCacheManager.shared.cachedItem(.status(id: statusID)) as? Mastodon.Entity.Status
} else {
status = nil
}
if notificationGroup.sampleAccountIDs.count == 1, let theOneAccountID = notificationGroup.sampleAccountIDs.first {
account = MastodonFeedItemCacheManager.shared.fullAccount(theOneAccountID)
} else {
account = nil
}
case .status:
assertionFailure("unexpected element in notifications feed")
status = nil
account = nil
break
}

if let status = notification.status {
if let status {
let threadViewModel = ThreadViewModel(
authenticationBox: self.viewModel.authenticationBox,
optionalRoot: .root(context: .init(status: .fromEntity(status)))
Expand All @@ -291,9 +325,8 @@ extension NotificationTimelineViewController: TableViewControllerNavigateable {
from: self,
transition: .show
)
} else {

await DataSourceFacade.coordinateToProfileScene(provider: self, account: notification.account)
} else if let account {
await DataSourceFacade.coordinateToProfileScene(provider: self, account: account)
}
default:
break
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ extension NotificationTimelineViewModel {
snapshot.appendSections([.main])
diffableDataSource?.apply(snapshot)

dataController.$records
feedLoader.$records
.receive(on: DispatchQueue.main)
.sink { [weak self] records in
guard let self else { return }
Expand All @@ -39,30 +39,17 @@ extension NotificationTimelineViewModel {
let oldSnapshot = diffableDataSource.snapshot()
var newSnapshot: NSDiffableDataSourceSnapshot<NotificationSection, NotificationItem> = {
let newItems = records.map { record in
NotificationItem.feed(record: record)
NotificationItem.notification(record)
}
var snapshot = NSDiffableDataSourceSnapshot<NotificationSection, NotificationItem>()
snapshot.appendSections([.main])
if self.scope == .everything, let notificationPolicy = self.notificationPolicy, notificationPolicy.summary.pendingRequestsCount > 0 {
snapshot.appendItems([.filteredNotifications(policy: notificationPolicy)])
snapshot.appendItems([.filteredNotificationsInfo(policy: notificationPolicy)])
}
snapshot.appendItems(newItems.removingDuplicates(), toSection: .main)
return snapshot
}()

let anchors: [MastodonFeed] = records.filter { $0.hasMore == true }
let itemIdentifiers = newSnapshot.itemIdentifiers
for (index, item) in itemIdentifiers.enumerated() {
guard case let .feed(record) = item else { continue }
guard anchors.contains(where: { feed in feed.id == record.id }) else { continue }
let isLast = index + 1 == itemIdentifiers.count
if isLast {
newSnapshot.insertItems([.bottomLoader], afterItem: item)
} else {
newSnapshot.insertItems([.feedLoader(record: record)], afterItem: item)
}
}

let hasChanges = newSnapshot.itemIdentifiers != oldSnapshot.itemIdentifiers
if !hasChanges {
self.didLoadLatest.send()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ extension NotificationTimelineViewModel.LoadOldestState {
class Initial: NotificationTimelineViewModel.LoadOldestState {
override func isValidNextState(_ stateClass: AnyClass) -> Bool {
guard let viewModel = viewModel else { return false }
guard !viewModel.dataController.records.isEmpty else { return false }
guard !viewModel.feedLoader.records.isEmpty else { return false }
return stateClass == Loading.self
}
}
Expand All @@ -50,7 +50,7 @@ extension NotificationTimelineViewModel.LoadOldestState {

guard let viewModel = viewModel, let stateMachine = stateMachine else { return }

guard let lastFeedRecord = viewModel.dataController.records.last else {
guard let lastFeedRecord = viewModel.feedLoader.records.last else {
stateMachine.enter(Fail.self)
return
}
Expand All @@ -71,7 +71,7 @@ extension NotificationTimelineViewModel.LoadOldestState {
}

Task {
let _maxID: Mastodon.Entity.Notification.ID? = lastFeedRecord.notification?.id
let _maxID: Mastodon.Entity.Notification.ID? = lastFeedRecord.id

guard let maxID = _maxID else {
self.enter(state: Fail.self)
Expand All @@ -80,8 +80,8 @@ extension NotificationTimelineViewModel.LoadOldestState {

do {
let response = try await APIService.shared.notifications(
maxID: maxID,
accountID: accountID,
olderThan: maxID,
fromAccount: accountID,
scope: scope,
authenticationBox: viewModel.authenticationBox
)
Expand Down
Loading

0 comments on commit 1009371

Please sign in to comment.