[WIP] Add relationships/user to notifications (IOS-192)
This commit is contained in:
parent
127c3167b8
commit
35c017986a
|
@ -31,6 +31,7 @@ extension NotificationTableViewCellDelegate where Self: DataSourceProvider & Aut
|
|||
return
|
||||
}
|
||||
|
||||
//TODO: Update Relationship
|
||||
_ = try await DataSourceFacade.responseToMenuAction(
|
||||
dependency: self,
|
||||
action: action,
|
||||
|
|
|
@ -57,7 +57,7 @@ extension NotificationTableViewCell {
|
|||
|
||||
UIView.performWithoutAnimation {
|
||||
tableView.beginUpdates()
|
||||
tableView.endUpdates()
|
||||
tableView.endUpdates()
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
|
|
@ -22,10 +22,12 @@ extension NotificationTimelineViewController: DataSourceProvider {
|
|||
|
||||
switch item {
|
||||
case .feed(let feed):
|
||||
let managedObjectContext = context.managedObjectContext
|
||||
let item: DataSourceItem? = {
|
||||
guard feed.kind == .notificationAll || feed.kind == .notificationMentions else { return nil }
|
||||
if let notification = feed.notification, let mastodonNotification = MastodonNotification.fromEntity(notification, using: managedObjectContext, domain: authContext.mastodonAuthenticationBox.domain) {
|
||||
|
||||
//TODO: Get relationship
|
||||
if let notification = feed.notification,
|
||||
let mastodonNotification = MastodonNotification.fromEntity(notification, relationship: nil, domain: authContext.mastodonAuthenticationBox.domain) {
|
||||
return .notification(record: mastodonNotification)
|
||||
} else {
|
||||
return nil
|
||||
|
|
|
@ -53,18 +53,19 @@ final class NotificationTimelineViewModel {
|
|||
self.authContext = authContext
|
||||
self.scope = scope
|
||||
self.dataController = FeedDataController(context: context, authContext: authContext)
|
||||
|
||||
|
||||
switch scope {
|
||||
case .everything:
|
||||
//TODO: I need the relationship here, too
|
||||
self.dataController.records = (try? FileManager.default.cachedNotificationsAll(for: authContext.mastodonAuthenticationBox))?.map({ notification in
|
||||
MastodonFeed.fromNotification(notification, kind: .notificationAll)
|
||||
MastodonFeed.fromNotification(notification, relationship: nil, kind: .notificationAll)
|
||||
}) ?? []
|
||||
case .mentions:
|
||||
self.dataController.records = (try? FileManager.default.cachedNotificationsMentions(for: authContext.mastodonAuthenticationBox))?.map({ notification in
|
||||
MastodonFeed.fromNotification(notification, kind: .notificationMentions)
|
||||
MastodonFeed.fromNotification(notification, relationship: nil, kind: .notificationMentions)
|
||||
}) ?? []
|
||||
}
|
||||
|
||||
|
||||
self.dataController.$records
|
||||
.removeDuplicates()
|
||||
.receive(on: DispatchQueue.main)
|
||||
|
|
|
@ -19,17 +19,14 @@ import MastodonSDK
|
|||
|
||||
extension NotificationView {
|
||||
public func configure(feed: MastodonFeed) {
|
||||
guard
|
||||
let notification = feed.notification,
|
||||
let managedObjectContext = viewModel.context?.managedObjectContext
|
||||
else {
|
||||
guard let notification = feed.notification else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
MastodonNotification.fromEntity(
|
||||
notification,
|
||||
using: managedObjectContext,
|
||||
relationship: feed.relationship,
|
||||
domain: viewModel.authContext?.mastodonAuthenticationBox.domain ?? ""
|
||||
).map(configure(notification:))
|
||||
}
|
||||
|
@ -65,56 +62,29 @@ extension NotificationView {
|
|||
extension NotificationView {
|
||||
private func configureAuthor(notification: MastodonNotification) {
|
||||
let author = notification.account
|
||||
|
||||
// author avatar
|
||||
|
||||
Publishers.CombineLatest(
|
||||
author.publisher(for: \.avatar),
|
||||
UserDefaults.shared.publisher(for: \.preferredStaticAvatar)
|
||||
)
|
||||
.map { _ in author.avatarImageURL() }
|
||||
.assign(to: \.authorAvatarImageURL, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
viewModel.authorAvatarImageURL = author.avatarImageURL()
|
||||
|
||||
// author name
|
||||
Publishers.CombineLatest(
|
||||
author.publisher(for: \.displayName),
|
||||
author.publisher(for: \.emojis)
|
||||
)
|
||||
.map { _, emojis in
|
||||
do {
|
||||
let content = MastodonContent(content: author.displayNameWithFallback, emojis: emojis.asDictionary)
|
||||
let metaContent = try MastodonMetaContent.convert(document: content)
|
||||
return metaContent
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
return PlaintextMetaContent(string: author.displayNameWithFallback)
|
||||
}
|
||||
do {
|
||||
let content = MastodonContent(content: author.displayNameWithFallback, emojis: author.emojis.asDictionary)
|
||||
viewModel.authorName = try MastodonMetaContent.convert(document: content)
|
||||
} catch {
|
||||
assertionFailure(error.localizedDescription)
|
||||
viewModel.authorName = PlaintextMetaContent(string: author.displayNameWithFallback)
|
||||
}
|
||||
.assign(to: \.authorName, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
// author username
|
||||
author.publisher(for: \.acct)
|
||||
.map { $0 as String? }
|
||||
.assign(to: \.authorUsername, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
// timestamp
|
||||
|
||||
viewModel.authorUsername = author.acct
|
||||
viewModel.timestamp = notification.entity.createdAt
|
||||
|
||||
viewModel.visibility = notification.entity.status?.mastodonVisibility ?? ._other("")
|
||||
|
||||
// notification type indicator
|
||||
Publishers.CombineLatest(
|
||||
author.publisher(for: \.displayName),
|
||||
author.publisher(for: \.emojis)
|
||||
)
|
||||
.sink { [weak self] _, emojis in
|
||||
guard let self = self else { return }
|
||||
guard let type = MastodonNotificationType(rawValue: notification.entity.type.rawValue) else {
|
||||
self.viewModel.notificationIndicatorText = nil
|
||||
return
|
||||
}
|
||||
if let type = MastodonNotificationType(rawValue: notification.entity.type.rawValue) {
|
||||
self.viewModel.type = type
|
||||
|
||||
// TODO: fix the i18n. The subject should assert place at the string beginning
|
||||
func createMetaContent(text: String, emojis: MastodonContent.Emojis) -> MetaContent {
|
||||
let content = MastodonContent(content: text, emojis: emojis)
|
||||
guard let metaContent = try? MastodonMetaContent.convert(document: content) else {
|
||||
|
@ -122,102 +92,65 @@ extension NotificationView {
|
|||
}
|
||||
return metaContent
|
||||
}
|
||||
|
||||
// TODO: fix the i18n. The subject should assert place at the string beginning
|
||||
|
||||
switch type {
|
||||
case .follow:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.followedYou,
|
||||
emojis: emojis.asDictionary
|
||||
emojis: author.emojis.asDictionary
|
||||
)
|
||||
case .followRequest:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.requestToFollowYou,
|
||||
emojis: emojis.asDictionary
|
||||
emojis: author.emojis.asDictionary
|
||||
)
|
||||
case .mention:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.mentionedYou,
|
||||
emojis: emojis.asDictionary
|
||||
emojis: author.emojis.asDictionary
|
||||
)
|
||||
case .reblog:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.rebloggedYourPost,
|
||||
emojis: emojis.asDictionary
|
||||
emojis: author.emojis.asDictionary
|
||||
)
|
||||
case .favourite:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.favoritedYourPost,
|
||||
emojis: emojis.asDictionary
|
||||
emojis: author.emojis.asDictionary
|
||||
)
|
||||
case .poll:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: L10n.Scene.Notification.NotificationDescription.pollHasEnded,
|
||||
emojis: emojis.asDictionary
|
||||
emojis: author.emojis.asDictionary
|
||||
)
|
||||
case .status:
|
||||
self.viewModel.notificationIndicatorText = createMetaContent(
|
||||
text: .empty,
|
||||
emojis: emojis.asDictionary
|
||||
emojis: author.emojis.asDictionary
|
||||
)
|
||||
case ._other:
|
||||
self.viewModel.notificationIndicatorText = nil
|
||||
}
|
||||
} else {
|
||||
self.viewModel.notificationIndicatorText = nil
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
let authContext = viewModel.authContext
|
||||
// isMuting
|
||||
author.publisher(for: \.mutingBy)
|
||||
.map { mutingBy in
|
||||
guard let authContext = authContext else { return false }
|
||||
return mutingBy.contains(where: {
|
||||
$0.id == authContext.mastodonAuthenticationBox.userID
|
||||
&& $0.domain == authContext.mastodonAuthenticationBox.domain
|
||||
})
|
||||
if let me = viewModel.authContext?.mastodonAuthenticationBox.authentication.account() {
|
||||
viewModel.isMyself = (author == me)
|
||||
|
||||
if let relationship = notification.relationship {
|
||||
viewModel.isMuting = relationship.muting
|
||||
viewModel.isBlocking = relationship.blocking || relationship.domainBlocking
|
||||
viewModel.isFollowed = relationship.following
|
||||
} else {
|
||||
viewModel.isMuting = false
|
||||
viewModel.isBlocking = false
|
||||
viewModel.isFollowed = false
|
||||
}
|
||||
.assign(to: \.isMuting, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
// isBlocking
|
||||
author.publisher(for: \.blockingBy)
|
||||
.map { blockingBy in
|
||||
guard let authContext = authContext else { return false }
|
||||
return blockingBy.contains(where: {
|
||||
$0.id == authContext.mastodonAuthenticationBox.userID
|
||||
&& $0.domain == authContext.mastodonAuthenticationBox.domain
|
||||
})
|
||||
}
|
||||
.assign(to: \.isBlocking, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// isMyself
|
||||
Publishers.CombineLatest(
|
||||
author.publisher(for: \.domain),
|
||||
author.publisher(for: \.id)
|
||||
)
|
||||
.map { domain, id in
|
||||
guard let authContext = authContext else { return false }
|
||||
return authContext.mastodonAuthenticationBox.domain == domain
|
||||
&& authContext.mastodonAuthenticationBox.userID == id
|
||||
}
|
||||
.assign(to: \.isMyself, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
// follow request state
|
||||
viewModel.followRequestState = notification.followRequestState
|
||||
viewModel.transientFollowRequestState = notification.transientFollowRequestState
|
||||
|
||||
// Following
|
||||
author.publisher(for: \.followingBy)
|
||||
.map { [weak viewModel] followingBy in
|
||||
guard let viewModel = viewModel else { return false }
|
||||
guard let authContext = viewModel.authContext else { return false }
|
||||
return followingBy.contains(where: {
|
||||
$0.id == authContext.mastodonAuthenticationBox.userID && $0.domain == authContext.mastodonAuthenticationBox.domain
|
||||
})
|
||||
}
|
||||
.assign(to: \.isFollowed, on: viewModel)
|
||||
.store(in: &disposeBag)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,16 +76,36 @@ final public class FeedDataController {
|
|||
private extension FeedDataController {
|
||||
func load(kind: MastodonFeed.Kind, sinceId: MastodonStatus.ID?) async throws -> [MastodonFeed] {
|
||||
switch kind {
|
||||
case .home:
|
||||
await context.authenticationService.authenticationServiceProvider.fetchAccounts(apiService: context.apiService)
|
||||
return try await context.apiService.homeTimeline(sinceID: sinceId, authenticationBox: authContext.mastodonAuthenticationBox)
|
||||
.value.map { .fromStatus(.fromEntity($0), kind: .home) }
|
||||
case .notificationAll:
|
||||
return try await context.apiService.notifications(maxID: nil, scope: .everything, authenticationBox: authContext.mastodonAuthenticationBox)
|
||||
.value.map { .fromNotification($0, kind: .notificationAll) }
|
||||
case .notificationMentions:
|
||||
return try await context.apiService.notifications(maxID: nil, scope: .mentions, authenticationBox: authContext.mastodonAuthenticationBox)
|
||||
.value.map { .fromNotification($0, kind: .notificationMentions) }
|
||||
case .home:
|
||||
await context.authenticationService.authenticationServiceProvider.fetchAccounts(apiService: context.apiService)
|
||||
return try await context.apiService.homeTimeline(sinceID: sinceId, authenticationBox: authContext.mastodonAuthenticationBox)
|
||||
.value.map { .fromStatus(.fromEntity($0), kind: .home) }
|
||||
case .notificationAll:
|
||||
return try await getFeeds(with: .everything)
|
||||
case .notificationMentions:
|
||||
return try await getFeeds(with: .mentions)
|
||||
}
|
||||
}
|
||||
|
||||
private func getFeeds(with scope: APIService.MastodonNotificationScope) async throws -> [MastodonFeed] {
|
||||
|
||||
let notifications = try await context.apiService.notifications(maxID: nil, scope: scope, authenticationBox: authContext.mastodonAuthenticationBox).value
|
||||
|
||||
let accounts = notifications.map { $0.account }
|
||||
let relationships = try await context.apiService.relationship(forAccounts: accounts, authenticationBox: authContext.mastodonAuthenticationBox).value
|
||||
|
||||
let notificationsWithRelationship: [(notification: Mastodon.Entity.Notification, relationship: Mastodon.Entity.Relationship?)] = notifications.compactMap { notification in
|
||||
guard let relationship = relationships.first(where: {$0.id == notification.account.id }) else { return (notification: notification, relationship: nil)}
|
||||
|
||||
return (notification: notification, relationship: relationship)
|
||||
}
|
||||
|
||||
let feeds = notificationsWithRelationship.compactMap({ (notification: Mastodon.Entity.Notification, relationship: Mastodon.Entity.Relationship?) in
|
||||
MastodonFeed.fromNotification(notification, relationship: relationship, kind: .notificationAll)
|
||||
})
|
||||
|
||||
return feeds
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -16,16 +16,18 @@ public final class MastodonFeed {
|
|||
public var isLoadingMore: Bool = false
|
||||
|
||||
public let status: MastodonStatus?
|
||||
public let relationship: Mastodon.Entity.Relationship?
|
||||
public let notification: Mastodon.Entity.Notification?
|
||||
|
||||
public let kind: Feed.Kind
|
||||
|
||||
init(hasMore: Bool, isLoadingMore: Bool, status: MastodonStatus?, notification: Mastodon.Entity.Notification?, kind: Feed.Kind) {
|
||||
init(hasMore: Bool, isLoadingMore: Bool, status: MastodonStatus?, notification: Mastodon.Entity.Notification?, relationship: Mastodon.Entity.Relationship?, kind: Feed.Kind) {
|
||||
self.id = notification?.id ?? status?.id ?? UUID().uuidString
|
||||
self.hasMore = hasMore
|
||||
self.isLoadingMore = isLoadingMore
|
||||
self.status = status
|
||||
self.notification = notification
|
||||
self.relationship = relationship
|
||||
self.kind = kind
|
||||
}
|
||||
}
|
||||
|
@ -37,11 +39,12 @@ public extension MastodonFeed {
|
|||
isLoadingMore: false,
|
||||
status: status,
|
||||
notification: nil,
|
||||
relationship: nil,
|
||||
kind: kind
|
||||
)
|
||||
}
|
||||
|
||||
static func fromNotification(_ notification: Mastodon.Entity.Notification, kind: Feed.Kind) -> MastodonFeed {
|
||||
static func fromNotification(_ notification: Mastodon.Entity.Notification, relationship: Mastodon.Entity.Relationship?, kind: Feed.Kind) -> MastodonFeed {
|
||||
MastodonFeed(
|
||||
hasMore: false,
|
||||
isLoadingMore: false,
|
||||
|
@ -52,6 +55,7 @@ public extension MastodonFeed {
|
|||
return .fromEntity(status)
|
||||
}(),
|
||||
notification: notification,
|
||||
relationship: relationship,
|
||||
kind: kind
|
||||
)
|
||||
}
|
||||
|
|
|
@ -10,30 +10,26 @@ public final class MastodonNotification {
|
|||
entity.id
|
||||
}
|
||||
|
||||
public let account: MastodonUser
|
||||
public let account: Mastodon.Entity.Account
|
||||
public let relationship: Mastodon.Entity.Relationship?
|
||||
public let status: MastodonStatus?
|
||||
public let feeds: [MastodonFeed]
|
||||
|
||||
public var followRequestState: MastodonFollowRequestState = .init(state: .none)
|
||||
public var transientFollowRequestState: MastodonFollowRequestState = .init(state: .none)
|
||||
|
||||
public init(entity: Mastodon.Entity.Notification, account: MastodonUser, status: MastodonStatus?, feeds: [MastodonFeed]) {
|
||||
public init(entity: Mastodon.Entity.Notification, account: Mastodon.Entity.Account, relationship: Mastodon.Entity.Relationship?, status: MastodonStatus?, feeds: [MastodonFeed]) {
|
||||
self.entity = entity
|
||||
self.account = account
|
||||
self.relationship = relationship
|
||||
self.status = status
|
||||
self.feeds = feeds
|
||||
}
|
||||
}
|
||||
|
||||
public extension MastodonNotification {
|
||||
static func fromEntity(_ entity: Mastodon.Entity.Notification, using managedObjectContext: NSManagedObjectContext, domain: String) -> MastodonNotification? {
|
||||
guard let user = MastodonUser.fetch(in: managedObjectContext, configurationBlock: { request in
|
||||
request.predicate = MastodonUser.predicate(domain: domain, id: entity.account.id)
|
||||
}).first else {
|
||||
assertionFailure()
|
||||
return nil
|
||||
}
|
||||
return MastodonNotification(entity: entity, account: user, status: entity.status.map(MastodonStatus.fromEntity), feeds: [])
|
||||
static func fromEntity(_ entity: Mastodon.Entity.Notification, relationship: Mastodon.Entity.Relationship?, domain: String) -> MastodonNotification? {
|
||||
return MastodonNotification(entity: entity, account: entity.account, relationship: relationship, status: entity.status.map(MastodonStatus.fromEntity), feeds: [])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue