diff options
Diffstat (limited to 'src/state')
-rw-r--r-- | src/state/models/me.ts | 15 | ||||
-rw-r--r-- | src/state/models/notifications-view.ts | 103 | ||||
-rw-r--r-- | src/state/models/post-thread-view.ts | 11 |
3 files changed, 97 insertions, 32 deletions
diff --git a/src/state/models/me.ts b/src/state/models/me.ts index 78c6d2e76..e3405b80d 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -1,6 +1,7 @@ import {makeAutoObservable, runInAction} from 'mobx' import {RootStoreModel} from './root-store' import {MembershipsViewModel} from './memberships-view' +import {NotificationsViewModel} from './notifications-view' export class MeModel { did?: string @@ -9,9 +10,11 @@ export class MeModel { description?: string notificationCount: number = 0 memberships?: MembershipsViewModel + notifications: NotificationsViewModel constructor(public rootStore: RootStoreModel) { makeAutoObservable(this, {rootStore: false}, {autoBind: true}) + this.notifications = new NotificationsViewModel(this.rootStore, {}) } clear() { @@ -43,7 +46,12 @@ export class MeModel { this.memberships = new MembershipsViewModel(this.rootStore, { actor: this.did, }) - await this.memberships?.setup() + await this.memberships?.setup().catch(e => { + console.error('Failed to setup memberships model', e) + }) + await this.notifications.setup().catch(e => { + console.error('Failed to setup notifications model', e) + }) } else { this.clear() } @@ -56,7 +64,12 @@ export class MeModel { async fetchStateUpdate() { const res = await this.rootStore.api.app.bsky.notification.getCount() runInAction(() => { + const newNotifications = this.notificationCount !== res.data.count this.notificationCount = res.data.count + if (newNotifications) { + // trigger pre-emptive fetch on new notifications + this.notifications.refresh() + } }) } diff --git a/src/state/models/notifications-view.ts b/src/state/models/notifications-view.ts index 80e5c80c6..e81f31a25 100644 --- a/src/state/models/notifications-view.ts +++ b/src/state/models/notifications-view.ts @@ -1,6 +1,7 @@ -import {makeAutoObservable} from 'mobx' +import {makeAutoObservable, runInAction} from 'mobx' import * as ListNotifications from '../../third-party/api/src/client/types/app/bsky/notification/list' import {RootStoreModel} from './root-store' +import {PostThreadViewModel} from './post-thread-view' import {Declaration} from './_common' import {hasProp} from '../lib/type-guards' import {APP_BSKY_GRAPH} from '../../third-party/api' @@ -34,6 +35,9 @@ export class NotificationsViewItemModel implements GroupedNotification { indexedAt: string = '' additional?: NotificationsViewItemModel[] + // additional data + additionalPost?: PostThreadViewModel + constructor( public rootStore: RootStoreModel, reactKey: string, @@ -89,6 +93,13 @@ export class NotificationsViewItemModel implements GroupedNotification { return this.reason === 'assertion' } + get needsAdditionalData() { + if (this.isUpvote || this.isRepost || this.isTrend || this.isReply) { + return !this.additionalPost + } + return false + } + get isInvite() { return ( this.isAssertion && this.record.assertion === APP_BSKY_GRAPH.AssertMember @@ -107,6 +118,27 @@ export class NotificationsViewItemModel implements GroupedNotification { } return '' } + + async fetchAdditionalData() { + if (!this.needsAdditionalData) { + return + } + let postUri + if (this.isReply) { + postUri = this.uri + } else if (this.isUpvote || this.isRead || this.isTrend) { + postUri = this.subjectUri + } + if (postUri) { + this.additionalPost = new PostThreadViewModel(this.rootStore, { + uri: postUri, + depth: 0, + }) + await this.additionalPost.setup().catch(e => { + console.error('Failed to load post needed by notification', e) + }) + } + } } export class NotificationsViewModel { @@ -171,7 +203,6 @@ export class NotificationsViewModel { await this._pendingWork() this._loadPromise = this._initialLoad(isRefreshing) await this._loadPromise - this._updateReadState() this._loadPromise = undefined } @@ -208,6 +239,20 @@ export class NotificationsViewModel { this._updatePromise = undefined } + /** + * Update read/unread state + */ + async updateReadState() { + try { + await this.rootStore.api.app.bsky.notification.updateSeen({ + seenAt: new Date().toISOString(), + }) + this.rootStore.me.clearNotificationCount() + } catch (e) { + console.log('Failed to update notifications read state', e) + } + } + // state transitions // = @@ -246,7 +291,7 @@ export class NotificationsViewModel { limit: PAGE_SIZE, }) const res = await this.rootStore.api.app.bsky.notification.list(params) - this._replaceAll(res) + await this._replaceAll(res) this._xIdle() } catch (e: any) { this._xIdle(`Failed to load notifications: ${e.toString()}`) @@ -264,7 +309,7 @@ export class NotificationsViewModel { before: this.loadMoreCursor, }) const res = await this.rootStore.api.app.bsky.notification.list(params) - this._appendAll(res) + await this._appendAll(res) this._xIdle() } catch (e: any) { this._xIdle(`Failed to load notifications: ${e.toString()}`) @@ -296,25 +341,40 @@ export class NotificationsViewModel { } } - private _replaceAll(res: ListNotifications.Response) { - this.notifications.length = 0 - this._appendAll(res) + private async _replaceAll(res: ListNotifications.Response) { + return this._appendAll(res, true) } - private _appendAll(res: ListNotifications.Response) { + private async _appendAll(res: ListNotifications.Response, replace = false) { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor let counter = this.notifications.length + const promises = [] + const itemModels: NotificationsViewItemModel[] = [] for (const item of groupNotifications(res.data.notifications)) { - this._append(counter++, item) + const itemModel = new NotificationsViewItemModel( + this.rootStore, + `item-${counter++}`, + item, + ) + if (itemModel.needsAdditionalData) { + promises.push(itemModel.fetchAdditionalData()) + } + itemModels.push(itemModel) } - } - - private _append(keyId: number, item: GroupedNotification) { - // TODO: validate .record - this.notifications.push( - new NotificationsViewItemModel(this.rootStore, `item-${keyId}`, item), - ) + await Promise.all(promises).catch(e => { + console.error( + 'Uncaught failure during notifications-view _appendAll()', + e, + ) + }) + runInAction(() => { + if (replace) { + this.notifications = itemModels + } else { + this.notifications = this.notifications.concat(itemModels) + } + }) } private _updateAll(res: ListNotifications.Response) { @@ -330,17 +390,6 @@ export class NotificationsViewModel { } } } - - private async _updateReadState() { - try { - await this.rootStore.api.app.bsky.notification.updateSeen({ - seenAt: new Date().toISOString(), - }) - this.rootStore.me.clearNotificationCount() - } catch (e) { - console.log('Failed to update notifications read state', e) - } - } } function groupNotifications( diff --git a/src/state/models/post-thread-view.ts b/src/state/models/post-thread-view.ts index 70e34537f..5c0e0a4e8 100644 --- a/src/state/models/post-thread-view.ts +++ b/src/state/models/post-thread-view.ts @@ -1,5 +1,5 @@ import {makeAutoObservable, runInAction} from 'mobx' -import * as GetPostThread from '../../third-party/api/src/client/types/app/bsky/feed/getPostThread' +import {AppBskyFeedGetPostThread as GetPostThread} from '../../third-party/api' import {AtUri} from '../../third-party/uri' import _omit from 'lodash.omit' import {RootStoreModel} from './root-store' @@ -216,6 +216,7 @@ export class PostThreadViewModel { isRefreshing = false hasLoaded = false error = '' + notFound = false resolvedUri = '' params: GetPostThread.QueryParams @@ -286,13 +287,15 @@ export class PostThreadViewModel { this.isLoading = true this.isRefreshing = isRefreshing this.error = '' + this.notFound = false } - private _xIdle(err: string = '') { + private _xIdle(err: any = undefined) { this.isLoading = false this.isRefreshing = false this.hasLoaded = true - this.error = err + this.error = err ? err.toString() : '' + this.notFound = err instanceof GetPostThread.NotFoundError } // loader functions @@ -317,7 +320,7 @@ export class PostThreadViewModel { this._replaceAll(res) this._xIdle() } catch (e: any) { - this._xIdle(`Failed to load thread: ${e.toString()}`) + this._xIdle(e) } } |