Begin replacing CoreData for Feed, MastodonUser, Notification, Status
This commit is contained in:
parent
c80a590306
commit
ee51520bff
|
@ -5,12 +5,11 @@
|
||||||
// Created by sxiaojian on 2021/4/13.
|
// Created by sxiaojian on 2021/4/13.
|
||||||
//
|
//
|
||||||
|
|
||||||
import CoreData
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreDataStack
|
import MastodonSDK
|
||||||
|
|
||||||
enum NotificationItem: Hashable {
|
enum NotificationItem: Hashable {
|
||||||
case feed(record: ManagedObjectRecord<Feed>)
|
case feed(record: FeedNxt)
|
||||||
case feedLoader(record: ManagedObjectRecord<Feed>)
|
case feedLoader(record: FeedNxt)
|
||||||
case bottomLoader
|
case bottomLoader
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,10 +41,9 @@ extension NotificationSection {
|
||||||
|
|
||||||
return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
return UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, item -> UITableViewCell? in
|
||||||
switch item {
|
switch item {
|
||||||
case .feed(let record):
|
case .feed(let feed):
|
||||||
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationTableViewCell.self), for: indexPath) as! NotificationTableViewCell
|
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: NotificationTableViewCell.self), for: indexPath) as! NotificationTableViewCell
|
||||||
context.managedObjectContext.performAndWait {
|
context.managedObjectContext.performAndWait {
|
||||||
guard let feed = record.object(in: context.managedObjectContext) else { return }
|
|
||||||
configure(
|
configure(
|
||||||
context: context,
|
context: context,
|
||||||
tableView: tableView,
|
tableView: tableView,
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonUI
|
import MastodonUI
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
enum StatusItem: Hashable {
|
enum StatusItem: Hashable {
|
||||||
case feed(record: ManagedObjectRecord<Feed>)
|
case feed(record: ManagedObjectRecord<Feed>)
|
||||||
|
@ -24,7 +25,7 @@ extension StatusItem {
|
||||||
case reply(context: Context)
|
case reply(context: Context)
|
||||||
case leaf(context: Context)
|
case leaf(context: Context)
|
||||||
|
|
||||||
public var record: ManagedObjectRecord<Status> {
|
public var record: StatusNxt {
|
||||||
switch self {
|
switch self {
|
||||||
case .root(let threadContext),
|
case .root(let threadContext),
|
||||||
.reply(let threadContext),
|
.reply(let threadContext),
|
||||||
|
@ -37,12 +38,12 @@ extension StatusItem {
|
||||||
|
|
||||||
extension StatusItem.Thread {
|
extension StatusItem.Thread {
|
||||||
class Context: Hashable {
|
class Context: Hashable {
|
||||||
let status: ManagedObjectRecord<Status>
|
let status: StatusNxt
|
||||||
var displayUpperConversationLink: Bool
|
var displayUpperConversationLink: Bool
|
||||||
var displayBottomConversationLink: Bool
|
var displayBottomConversationLink: Bool
|
||||||
|
|
||||||
init(
|
init(
|
||||||
status: ManagedObjectRecord<Status>,
|
status: StatusNxt,
|
||||||
displayUpperConversationLink: Bool = false,
|
displayUpperConversationLink: Bool = false,
|
||||||
displayBottomConversationLink: Bool = false
|
displayBottomConversationLink: Bool = false
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -101,7 +101,7 @@ extension DiscoveryForYouViewController: UITableViewDelegate {
|
||||||
let profileViewModel = CachedProfileViewModel(
|
let profileViewModel = CachedProfileViewModel(
|
||||||
context: context,
|
context: context,
|
||||||
authContext: viewModel.authContext,
|
authContext: viewModel.authContext,
|
||||||
mastodonUser: user
|
mastodonUser: .from(user: user)
|
||||||
)
|
)
|
||||||
_ = coordinator.present(
|
_ = coordinator.present(
|
||||||
scene: .profile(viewModel: profileViewModel),
|
scene: .profile(viewModel: profileViewModel),
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import UIKit
|
import UIKit
|
||||||
import Combine
|
import Combine
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
extension NotificationTableViewCell {
|
extension NotificationTableViewCell {
|
||||||
final class ViewModel {
|
final class ViewModel {
|
||||||
|
@ -18,7 +19,7 @@ extension NotificationTableViewCell {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Value {
|
enum Value {
|
||||||
case feed(Feed)
|
case feed(FeedNxt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ extension NotificationTimelineViewController: DataSourceProvider {
|
||||||
case .feed(let record):
|
case .feed(let record):
|
||||||
let managedObjectContext = context.managedObjectContext
|
let managedObjectContext = context.managedObjectContext
|
||||||
let item: DataSourceItem? = try? await managedObjectContext.perform {
|
let item: DataSourceItem? = try? await managedObjectContext.perform {
|
||||||
guard let feed = record.object(in: managedObjectContext) else { return nil }
|
|
||||||
guard feed.kind == .notificationAll || feed.kind == .notificationMentions else { return nil }
|
guard feed.kind == .notificationAll || feed.kind == .notificationMentions else { return nil }
|
||||||
if let notification = feed.notification {
|
if let notification = feed.notification {
|
||||||
return .notification(record: .init(objectID: notification.objectID))
|
return .notification(record: .init(objectID: notification.objectID))
|
||||||
|
|
|
@ -279,15 +279,15 @@ extension NotificationTimelineViewController: TableViewControllerNavigateable {
|
||||||
|
|
||||||
Task { @MainActor in
|
Task { @MainActor in
|
||||||
switch item {
|
switch item {
|
||||||
case .feed(let record):
|
case .feed(let feed):
|
||||||
guard let feed = record.object(in: self.context.managedObjectContext) else { return }
|
// guard let feed = record.object(in: self.context.managedObjectContext) else { return }
|
||||||
guard let notification = feed.notification else { return }
|
guard let notification = feed.notification else { return }
|
||||||
|
|
||||||
if let stauts = notification.status {
|
if let status = notification.status {
|
||||||
let threadViewModel = ThreadViewModel(
|
let threadViewModel = ThreadViewModel(
|
||||||
context: self.context,
|
context: self.context,
|
||||||
authContext: self.viewModel.authContext,
|
authContext: self.viewModel.authContext,
|
||||||
optionalRoot: .root(context: .init(status: .init(objectID: stauts.objectID)))
|
optionalRoot: .root(context: .init(status: status))
|
||||||
)
|
)
|
||||||
_ = self.coordinator.present(
|
_ = self.coordinator.present(
|
||||||
scene: .thread(viewModel: threadViewModel),
|
scene: .thread(viewModel: threadViewModel),
|
||||||
|
|
|
@ -30,7 +30,7 @@ extension NotificationTimelineViewModel {
|
||||||
snapshot.appendSections([.main])
|
snapshot.appendSections([.main])
|
||||||
diffableDataSource?.apply(snapshot)
|
diffableDataSource?.apply(snapshot)
|
||||||
|
|
||||||
feedFetchedResultsController.$records
|
$notifications
|
||||||
.receive(on: DispatchQueue.main)
|
.receive(on: DispatchQueue.main)
|
||||||
.sink { [weak self] records in
|
.sink { [weak self] records in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
|
@ -48,42 +48,42 @@ extension NotificationTimelineViewModel {
|
||||||
return snapshot
|
return snapshot
|
||||||
}()
|
}()
|
||||||
|
|
||||||
let parentManagedObjectContext = self.context.managedObjectContext
|
// let parentManagedObjectContext = self.context.managedObjectContext
|
||||||
let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
// let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
|
||||||
managedObjectContext.parent = parentManagedObjectContext
|
// managedObjectContext.parent = parentManagedObjectContext
|
||||||
try? await managedObjectContext.perform {
|
// try? await managedObjectContext.perform {
|
||||||
let anchors: [Feed] = {
|
// let anchors: [Feed] = {
|
||||||
let request = Feed.sortedFetchRequest
|
// let request = Feed.sortedFetchRequest
|
||||||
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
// request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||||
Feed.hasMorePredicate(),
|
// Feed.hasMorePredicate(),
|
||||||
self.feedFetchedResultsController.predicate,
|
// self.feedFetchedResultsController.predicate,
|
||||||
])
|
// ])
|
||||||
do {
|
// do {
|
||||||
return try managedObjectContext.fetch(request)
|
// return try managedObjectContext.fetch(request)
|
||||||
} catch {
|
// } catch {
|
||||||
assertionFailure(error.localizedDescription)
|
// assertionFailure(error.localizedDescription)
|
||||||
return []
|
// return []
|
||||||
}
|
// }
|
||||||
}()
|
// }()
|
||||||
|
//
|
||||||
let itemIdentifiers = newSnapshot.itemIdentifiers
|
// let itemIdentifiers = newSnapshot.itemIdentifiers
|
||||||
for (index, item) in itemIdentifiers.enumerated() {
|
// for (index, item) in itemIdentifiers.enumerated() {
|
||||||
guard case let .feed(record) = item else { continue }
|
// guard case let .feed(record) = item else { continue }
|
||||||
guard anchors.contains(where: { feed in feed.objectID == record.objectID }) else { continue }
|
// guard anchors.contains(where: { feed in feed.objectID == record.objectID }) else { continue }
|
||||||
let isLast = index + 1 == itemIdentifiers.count
|
// let isLast = index + 1 == itemIdentifiers.count
|
||||||
if isLast {
|
// if isLast {
|
||||||
newSnapshot.insertItems([.bottomLoader], afterItem: item)
|
// newSnapshot.insertItems([.bottomLoader], afterItem: item)
|
||||||
} else {
|
// } else {
|
||||||
newSnapshot.insertItems([.feedLoader(record: record)], afterItem: item)
|
// newSnapshot.insertItems([.feedLoader(record: record)], afterItem: item)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
let hasChanges = newSnapshot.itemIdentifiers != oldSnapshot.itemIdentifiers
|
// let hasChanges = newSnapshot.itemIdentifiers != oldSnapshot.itemIdentifiers
|
||||||
if !hasChanges {
|
// if !hasChanges {
|
||||||
self.didLoadLatest.send()
|
// self.didLoadLatest.send()
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
await self.updateSnapshotUsingReloadData(snapshot: newSnapshot)
|
await self.updateSnapshotUsingReloadData(snapshot: newSnapshot)
|
||||||
self.didLoadLatest.send()
|
self.didLoadLatest.send()
|
||||||
|
|
|
@ -20,11 +20,11 @@ final class NotificationTimelineViewModel {
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
let authContext: AuthContext
|
let authContext: AuthContext
|
||||||
let scope: Scope
|
let scope: Scope
|
||||||
let feedFetchedResultsController: FeedFetchedResultsController
|
// let feedFetchedResultsController: FeedFetchedResultsController
|
||||||
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
let listBatchFetchViewModel = ListBatchFetchViewModel()
|
||||||
@Published var isLoadingLatest = false
|
@Published var isLoadingLatest = false
|
||||||
@Published var lastAutomaticFetchTimestamp: Date?
|
@Published var lastAutomaticFetchTimestamp: Date?
|
||||||
|
@Published var notifications = [FeedNxt]()
|
||||||
// output
|
// output
|
||||||
var diffableDataSource: UITableViewDiffableDataSource<NotificationSection, NotificationItem>?
|
var diffableDataSource: UITableViewDiffableDataSource<NotificationSection, NotificationItem>?
|
||||||
var didLoadLatest = PassthroughSubject<Void, Never>()
|
var didLoadLatest = PassthroughSubject<Void, Never>()
|
||||||
|
@ -51,57 +51,57 @@ final class NotificationTimelineViewModel {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.authContext = authContext
|
self.authContext = authContext
|
||||||
self.scope = scope
|
self.scope = scope
|
||||||
self.feedFetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
// self.feedFetchedResultsController = FeedFetchedResultsController(managedObjectContext: context.managedObjectContext)
|
||||||
// end init
|
// end init
|
||||||
|
|
||||||
feedFetchedResultsController.predicate = NotificationTimelineViewModel.feedPredicate(
|
// feedFetchedResultsController.predicate = NotificationTimelineViewModel.feedPredicate(
|
||||||
authenticationBox: authContext.mastodonAuthenticationBox,
|
// authenticationBox: authContext.mastodonAuthenticationBox,
|
||||||
scope: scope
|
// scope: scope
|
||||||
)
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NotificationTimelineViewModel {
|
extension NotificationTimelineViewModel {
|
||||||
|
//
|
||||||
typealias Scope = APIService.MastodonNotificationScope
|
typealias Scope = APIService.MastodonNotificationScope
|
||||||
|
//
|
||||||
static func feedPredicate(
|
// static func feedPredicate(
|
||||||
authenticationBox: MastodonAuthenticationBox,
|
// authenticationBox: MastodonAuthenticationBox,
|
||||||
scope: Scope
|
// scope: Scope
|
||||||
) -> NSPredicate {
|
// ) -> NSPredicate {
|
||||||
let domain = authenticationBox.domain
|
// let domain = authenticationBox.domain
|
||||||
let userID = authenticationBox.userID
|
// let userID = authenticationBox.userID
|
||||||
let acct = Feed.Acct.mastodon(
|
// let acct = Feed.Acct.mastodon(
|
||||||
domain: domain,
|
// domain: domain,
|
||||||
userID: userID
|
// userID: userID
|
||||||
)
|
// )
|
||||||
|
//
|
||||||
let predicate: NSPredicate = {
|
// let predicate: NSPredicate = {
|
||||||
switch scope {
|
// switch scope {
|
||||||
case .everything:
|
// case .everything:
|
||||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
// return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||||
Feed.hasNotificationPredicate(),
|
// Feed.hasNotificationPredicate(),
|
||||||
Feed.predicate(
|
// Feed.predicate(
|
||||||
kind: .notificationAll,
|
// kind: .notificationAll,
|
||||||
acct: acct
|
// acct: acct
|
||||||
)
|
// )
|
||||||
])
|
// ])
|
||||||
case .mentions:
|
// case .mentions:
|
||||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
// return NSCompoundPredicate(andPredicateWithSubpredicates: [
|
||||||
Feed.hasNotificationPredicate(),
|
// Feed.hasNotificationPredicate(),
|
||||||
Feed.predicate(
|
// Feed.predicate(
|
||||||
kind: .notificationMentions,
|
// kind: .notificationMentions,
|
||||||
acct: acct
|
// acct: acct
|
||||||
),
|
// ),
|
||||||
Feed.notificationTypePredicate(types: scope.includeTypes ?? [])
|
// Feed.notificationTypePredicate(types: scope.includeTypes ?? [])
|
||||||
])
|
// ])
|
||||||
}
|
// }
|
||||||
}()
|
// }()
|
||||||
return predicate
|
// return predicate
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NotificationTimelineViewModel {
|
extension NotificationTimelineViewModel {
|
||||||
|
@ -112,11 +112,15 @@ extension NotificationTimelineViewModel {
|
||||||
defer { isLoadingLatest = false }
|
defer { isLoadingLatest = false }
|
||||||
|
|
||||||
do {
|
do {
|
||||||
_ = try await context.apiService.notifications(
|
let result = try await context.apiService.notifications(
|
||||||
maxID: nil,
|
maxID: nil,
|
||||||
scope: scope,
|
scope: scope,
|
||||||
authenticationBox: authContext.mastodonAuthenticationBox
|
authenticationBox: authContext.mastodonAuthenticationBox
|
||||||
)
|
)
|
||||||
|
|
||||||
|
notifications = result.value.map {
|
||||||
|
FeedNxt.from(notification: $0, as: .notificationAll)
|
||||||
|
}
|
||||||
} catch {
|
} catch {
|
||||||
didLoadLatest.send()
|
didLoadLatest.send()
|
||||||
}
|
}
|
||||||
|
@ -127,18 +131,18 @@ extension NotificationTimelineViewModel {
|
||||||
guard case let .feedLoader(record) = item else { return }
|
guard case let .feedLoader(record) = item else { return }
|
||||||
|
|
||||||
let managedObjectContext = context.managedObjectContext
|
let managedObjectContext = context.managedObjectContext
|
||||||
let key = "LoadMore@\(record.objectID)"
|
// let key = "LoadMore@\(record.objectID)"
|
||||||
|
|
||||||
// return when already loading state
|
// return when already loading state
|
||||||
guard managedObjectContext.cache(froKey: key) == nil else { return }
|
// guard managedObjectContext.cache(froKey: key) == nil else { return }
|
||||||
|
|
||||||
guard let feed = record.object(in: managedObjectContext) else { return }
|
// guard let feed = record.object(in: managedObjectContext) else { return }
|
||||||
guard let maxID = feed.notification?.id else { return }
|
guard let maxID = record.notification?.id else { return }
|
||||||
// keep transient property live
|
// keep transient property live
|
||||||
managedObjectContext.cache(feed, key: key)
|
// managedObjectContext.cache(feed, key: key)
|
||||||
defer {
|
// defer {
|
||||||
managedObjectContext.cache(nil, key: key)
|
// managedObjectContext.cache(nil, key: key)
|
||||||
}
|
// }
|
||||||
|
|
||||||
// fetch data
|
// fetch data
|
||||||
do {
|
do {
|
||||||
|
|
|
@ -8,10 +8,11 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import CoreDataStack
|
import CoreDataStack
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
final class CachedProfileViewModel: ProfileViewModel {
|
final class CachedProfileViewModel: ProfileViewModel {
|
||||||
|
|
||||||
init(context: AppContext, authContext: AuthContext, mastodonUser: MastodonUser) {
|
init(context: AppContext, authContext: AuthContext, mastodonUser: MastodonUserNxt) {
|
||||||
super.init(context: context, authContext: authContext, optionalMastodonUser: mastodonUser)
|
super.init(context: context, authContext: authContext, optionalMastodonUser: mastodonUser)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,8 +33,8 @@ class ProfileViewModel: NSObject {
|
||||||
// input
|
// input
|
||||||
let context: AppContext
|
let context: AppContext
|
||||||
let authContext: AuthContext
|
let authContext: AuthContext
|
||||||
@Published var me: MastodonUser?
|
@Published var me: MastodonUserNxt?
|
||||||
@Published var user: MastodonUser?
|
@Published var user: MastodonUserNxt?
|
||||||
|
|
||||||
let viewDidAppear = PassthroughSubject<Void, Never>()
|
let viewDidAppear = PassthroughSubject<Void, Never>()
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class ProfileViewModel: NSObject {
|
||||||
// @Published var protected: Bool? = nil
|
// @Published var protected: Bool? = nil
|
||||||
// let needsPagePinToTop = CurrentValueSubject<Bool, Never>(false)
|
// let needsPagePinToTop = CurrentValueSubject<Bool, Never>(false)
|
||||||
|
|
||||||
init(context: AppContext, authContext: AuthContext, optionalMastodonUser mastodonUser: MastodonUser?) {
|
init(context: AppContext, authContext: AuthContext, optionalMastodonUser mastodonUser: MastodonUserNxt?) {
|
||||||
self.context = context
|
self.context = context
|
||||||
self.authContext = authContext
|
self.authContext = authContext
|
||||||
self.user = mastodonUser
|
self.user = mastodonUser
|
||||||
|
|
|
@ -16,9 +16,10 @@ import MastodonAsset
|
||||||
import MastodonCore
|
import MastodonCore
|
||||||
import MastodonLocalization
|
import MastodonLocalization
|
||||||
import class CoreDataStack.Notification
|
import class CoreDataStack.Notification
|
||||||
|
import MastodonSDK
|
||||||
|
|
||||||
extension NotificationView {
|
extension NotificationView {
|
||||||
public func configure(feed: Feed) {
|
public func configure(feed: FeedNxt) {
|
||||||
guard let notification = feed.notification else {
|
guard let notification = feed.notification else {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
return
|
return
|
||||||
|
@ -29,7 +30,7 @@ extension NotificationView {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NotificationView {
|
extension NotificationView {
|
||||||
public func configure(notification: Notification) {
|
public func configure(notification: NotificationNxt) {
|
||||||
viewModel.objects.insert(notification)
|
viewModel.objects.insert(notification)
|
||||||
|
|
||||||
configureAuthor(notification: notification)
|
configureAuthor(notification: notification)
|
||||||
|
@ -63,7 +64,7 @@ extension NotificationView {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NotificationView {
|
extension NotificationView {
|
||||||
private func configureAuthor(notification: Notification) {
|
private func configureAuthor(notification: NotificationNxt) {
|
||||||
let author = notification.account
|
let author = notification.account
|
||||||
// author avatar
|
// author avatar
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ import MastodonCore
|
||||||
|
|
||||||
final class CachedThreadViewModel: ThreadViewModel {
|
final class CachedThreadViewModel: ThreadViewModel {
|
||||||
init(context: AppContext, authContext: AuthContext, status: Status) {
|
init(context: AppContext, authContext: AuthContext, status: Status) {
|
||||||
let threadContext = StatusItem.Thread.Context(status: .init(objectID: status.objectID))
|
let threadContext = StatusItem.Thread.Context(status: .from(status: status))
|
||||||
super.init(
|
super.init(
|
||||||
context: context,
|
context: context,
|
||||||
authContext: authContext,
|
authContext: authContext,
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class FeedNxt: Equatable {
|
||||||
|
public enum Kind: String, CaseIterable, Hashable {
|
||||||
|
case none
|
||||||
|
case home
|
||||||
|
case notificationAll
|
||||||
|
case notificationMentions
|
||||||
|
}
|
||||||
|
|
||||||
|
public let kind: Kind
|
||||||
|
public let notification: NotificationNxt?
|
||||||
|
|
||||||
|
init(kind: Kind, notification: NotificationNxt?) {
|
||||||
|
self.kind = kind
|
||||||
|
self.notification = notification
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func == (lhs: FeedNxt, rhs: FeedNxt) -> Bool {
|
||||||
|
lhs.kind == rhs.kind && lhs.notification == rhs.notification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension FeedNxt {
|
||||||
|
static func from(notification: Mastodon.Entity.Notification, as kind: Kind) -> FeedNxt {
|
||||||
|
FeedNxt(kind: kind, notification: .from(notification: notification))
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreDataStack
|
||||||
|
|
||||||
|
public class MastodonUserNxt: ObservableObject, Hashable {
|
||||||
|
public let id: String
|
||||||
|
|
||||||
|
init(id: String) {
|
||||||
|
self.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func == (lhs: MastodonUserNxt, rhs: MastodonUserNxt) -> Bool {
|
||||||
|
lhs.id == rhs.id
|
||||||
|
}
|
||||||
|
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension MastodonUserNxt {
|
||||||
|
static func from(account: Mastodon.Entity.Account) -> MastodonUserNxt {
|
||||||
|
MastodonUserNxt(id: account.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension MastodonUserNxt {
|
||||||
|
static func from(user: MastodonUser) -> MastodonUserNxt {
|
||||||
|
MastodonUserNxt(id: user.id)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
public class NotificationNxt: Hashable {
|
||||||
|
public let id: String
|
||||||
|
public let status: StatusNxt?
|
||||||
|
public let account: MastodonUserNxt
|
||||||
|
public let typeRaw: String
|
||||||
|
|
||||||
|
init(id: String, status: StatusNxt?, account: MastodonUserNxt, typeRaw: String) {
|
||||||
|
self.id = id
|
||||||
|
self.status = status
|
||||||
|
self.account = account
|
||||||
|
self.typeRaw = typeRaw
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func == (lhs: NotificationNxt, rhs: NotificationNxt) -> Bool {
|
||||||
|
lhs.id == rhs.id
|
||||||
|
}
|
||||||
|
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension NotificationNxt {
|
||||||
|
static func from(notification: Mastodon.Entity.Notification) -> NotificationNxt {
|
||||||
|
NotificationNxt(
|
||||||
|
id: notification.id,
|
||||||
|
status: notification.status != nil ? StatusNxt.from(status: notification.status!) : nil,
|
||||||
|
account: MastodonUserNxt.from(account: notification.account),
|
||||||
|
typeRaw: notification.type.rawValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright © 2023 Mastodon gGmbH. All rights reserved.
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CoreDataStack
|
||||||
|
|
||||||
|
public class StatusNxt: Hashable {
|
||||||
|
public let id: String
|
||||||
|
public let reblog: StatusNxt?
|
||||||
|
public let author: MastodonUserNxt
|
||||||
|
public let createdAt: Date
|
||||||
|
|
||||||
|
init(id: String, reblog: StatusNxt?, author: MastodonUserNxt, createdAt: Date) {
|
||||||
|
self.id = id
|
||||||
|
self.reblog = reblog
|
||||||
|
self.author = author
|
||||||
|
self.createdAt = createdAt
|
||||||
|
}
|
||||||
|
|
||||||
|
public static func == (lhs: StatusNxt, rhs: StatusNxt) -> Bool {
|
||||||
|
lhs.id == rhs.id && lhs.author.id == rhs.author.id
|
||||||
|
}
|
||||||
|
|
||||||
|
public func hash(into hasher: inout Hasher) {
|
||||||
|
hasher.combine(id)
|
||||||
|
hasher.combine(createdAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public extension StatusNxt {
|
||||||
|
static func from(status: Mastodon.Entity.Status) -> StatusNxt {
|
||||||
|
StatusNxt(
|
||||||
|
id: status.id,
|
||||||
|
reblog: status.reblog != nil ? .from(status: status.reblog!) : nil,
|
||||||
|
author: .from(account: status.account),
|
||||||
|
createdAt: status.createdAt
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func from(status: Status) -> StatusNxt {
|
||||||
|
StatusNxt(
|
||||||
|
id: status.id,
|
||||||
|
reblog: status.reblog != nil ? .from(status: status.reblog!) : nil,
|
||||||
|
author: .from(user: status.author),
|
||||||
|
createdAt: status.createdAt
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,7 +62,7 @@ extension ComposeContentViewModel {
|
||||||
// configure status
|
// configure status
|
||||||
context.managedObjectContext.performAndWait {
|
context.managedObjectContext.performAndWait {
|
||||||
guard let replyTo = status.object(in: context.managedObjectContext) else { return }
|
guard let replyTo = status.object(in: context.managedObjectContext) else { return }
|
||||||
cell.statusView.configure(status: replyTo)
|
cell.statusView.configure(status: .from(status: replyTo))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ import CoreDataStack
|
||||||
extension NotificationView {
|
extension NotificationView {
|
||||||
public final class ViewModel: ObservableObject {
|
public final class ViewModel: ObservableObject {
|
||||||
public var disposeBag = Set<AnyCancellable>()
|
public var disposeBag = Set<AnyCancellable>()
|
||||||
public var objects = Set<NSManagedObject>()
|
public var objects = Set<NotificationNxt>()
|
||||||
|
|
||||||
@Published public var context: AppContext?
|
@Published public var context: AppContext?
|
||||||
@Published public var authContext: AuthContext?
|
@Published public var authContext: AuthContext?
|
||||||
|
|
|
@ -26,7 +26,7 @@ extension StatusView {
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
configure(status: status)
|
configure(status: .from(status: status))
|
||||||
case .notificationAll:
|
case .notificationAll:
|
||||||
assertionFailure("TODO")
|
assertionFailure("TODO")
|
||||||
case .notificationMentions:
|
case .notificationMentions:
|
||||||
|
@ -40,7 +40,7 @@ extension StatusView {
|
||||||
|
|
||||||
extension StatusView {
|
extension StatusView {
|
||||||
|
|
||||||
public func configure(status: Status, statusEdit: StatusEdit) {
|
public func configure(status: StatusNxt, statusEdit: StatusEdit) {
|
||||||
viewModel.objects.insert(status)
|
viewModel.objects.insert(status)
|
||||||
if let reblog = status.reblog {
|
if let reblog = status.reblog {
|
||||||
viewModel.objects.insert(reblog)
|
viewModel.objects.insert(reblog)
|
||||||
|
@ -66,7 +66,7 @@ extension StatusView {
|
||||||
viewModel.isContentReveal = true
|
viewModel.isContentReveal = true
|
||||||
}
|
}
|
||||||
|
|
||||||
public func configure(status: Status) {
|
public func configure(status: StatusNxt) {
|
||||||
viewModel.objects.insert(status)
|
viewModel.objects.insert(status)
|
||||||
if let reblog = status.reblog {
|
if let reblog = status.reblog {
|
||||||
viewModel.objects.insert(reblog)
|
viewModel.objects.insert(reblog)
|
||||||
|
@ -99,7 +99,7 @@ extension StatusView {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StatusView {
|
extension StatusView {
|
||||||
private func configureHeader(status: Status) {
|
private func configureHeader(status: StatusNxt) {
|
||||||
if let _ = status.reblog {
|
if let _ = status.reblog {
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
status.author.publisher(for: \.displayName),
|
status.author.publisher(for: \.displayName),
|
||||||
|
@ -189,7 +189,7 @@ extension StatusView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func configureAuthor(author: MastodonUser) {
|
public func configureAuthor(author: MastodonUserNxt) {
|
||||||
// author avatar
|
// author avatar
|
||||||
Publishers.CombineLatest(
|
Publishers.CombineLatest(
|
||||||
author.publisher(for: \.avatar),
|
author.publisher(for: \.avatar),
|
||||||
|
|
|
@ -22,7 +22,7 @@ extension StatusView {
|
||||||
public final class ViewModel: ObservableObject {
|
public final class ViewModel: ObservableObject {
|
||||||
var disposeBag = Set<AnyCancellable>()
|
var disposeBag = Set<AnyCancellable>()
|
||||||
var observations = Set<NSKeyValueObservation>()
|
var observations = Set<NSKeyValueObservation>()
|
||||||
public var objects = Set<NSManagedObject>()
|
public var objects = Set<StatusNxt>()
|
||||||
|
|
||||||
public var context: AppContext?
|
public var context: AppContext?
|
||||||
public var authContext: AuthContext?
|
public var authContext: AuthContext?
|
||||||
|
|
Loading…
Reference in New Issue