From 495227bb2c778d3dfc2da1de93ec807ff182e7a3 Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Tue, 24 Jan 2023 19:13:47 -0600 Subject: [PATCH] Trigger a notifee card on new notifications --- __mocks__/state-mock.ts | 2 +- __tests__/state/models/me.test.ts | 2 +- src/App.native.tsx | 8 +++++ src/state/models/me.ts | 14 ++++++-- src/state/models/notifications-view.ts | 50 ++++++++++++++++++++++++++ src/state/models/root-store.ts | 2 +- 6 files changed, 73 insertions(+), 5 deletions(-) diff --git a/__mocks__/state-mock.ts b/__mocks__/state-mock.ts index 129f9c8591..8a6c322f3c 100644 --- a/__mocks__/state-mock.ts +++ b/__mocks__/state-mock.ts @@ -587,7 +587,7 @@ export const mockedMeStore = { clear: jest.fn(), load: jest.fn(), clearNotificationCount: jest.fn(), - fetchStateUpdate: jest.fn(), + fetchNotifications: jest.fn(), refreshMemberships: jest.fn(), } as MeModel diff --git a/__tests__/state/models/me.test.ts b/__tests__/state/models/me.test.ts index fa8d496018..adf22f9422 100644 --- a/__tests__/state/models/me.test.ts +++ b/__tests__/state/models/me.test.ts @@ -173,7 +173,7 @@ describe('MeModel', () => { }) }) - await meModel.fetchStateUpdate() + await meModel.fetchNotifications() expect(meModel.notificationCount).toBe(1) expect(meModel.notifications.refresh).toHaveBeenCalled() }) diff --git a/src/App.native.tsx b/src/App.native.tsx index 30747dbfc9..f00e3cad1c 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -16,6 +16,7 @@ import * as view from './view/index' import {RootStoreModel, setupState, RootStoreProvider} from './state' import {MobileShell} from './view/shell/mobile' import {s} from './view/lib/styles' +import notifee, {EventType} from '@notifee/react-native' const App = observer(() => { const [rootStore, setRootStore] = useState( @@ -43,6 +44,13 @@ const App = observer(() => { Linking.addEventListener('url', ({url}) => { store.nav.handleLink(url) }) + notifee.onForegroundEvent(async ({type}: {type: EventType}) => { + store.log.debug('Notifee foreground event', {type}) + if (type === EventType.PRESS) { + store.log.debug('User pressed a notifee, opening notifications') + store.nav.switchTo(1, true) + } + }) }) }, []) diff --git a/src/state/models/me.ts b/src/state/models/me.ts index 3c3bd3d9d3..da46c60cfd 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -118,7 +118,7 @@ export class MeModel { notifee.setBadgeCount(0) } - async fetchStateUpdate() { + async fetchNotifications() { const res = await this.rootStore.api.app.bsky.notification.getCount() runInAction(() => { const newNotifications = this.notificationCount !== res.data.count @@ -126,7 +126,17 @@ export class MeModel { notifee.setBadgeCount(this.notificationCount) if (newNotifications) { // trigger pre-emptive fetch on new notifications - this.notifications.refresh() + let oldMostRecent = this.notifications.mostRecentNotification + this.notifications.refresh().then(() => { + // if a new most recent notification is found, trigger a notification card + const mostRecent = this.notifications.mostRecentNotification + if (mostRecent && oldMostRecent?.uri !== mostRecent?.uri) { + const notifeeOpts = mostRecent.toNotifeeOpts() + if (notifeeOpts) { + notifee.displayNotification(notifeeOpts) + } + } + }) } }) } diff --git a/src/state/models/notifications-view.ts b/src/state/models/notifications-view.ts index 32294ef334..34bb57f6e9 100644 --- a/src/state/models/notifications-view.ts +++ b/src/state/models/notifications-view.ts @@ -7,6 +7,7 @@ import { AppBskyFeedVote, AppBskyGraphAssertion, AppBskyGraphFollow, + AppBskyEmbedImages, } from '@atproto/api' import {RootStoreModel} from './root-store' import {PostThreadViewModel} from './post-thread-view' @@ -179,6 +180,42 @@ export class NotificationsViewItemModel { }) } } + + toNotifeeOpts() { + let author = this.author.displayName || this.author.handle + let title: string + let body: string = '' + if (this.isUpvote) { + title = `${author} liked your post` + body = this.additionalPost?.thread?.postRecord?.text || '' + } else if (this.isRepost) { + title = `${author} reposted your post` + body = this.additionalPost?.thread?.postRecord?.text || '' + } else if (this.isReply) { + title = `${author} replied to your post` + body = this.additionalPost?.thread?.postRecord?.text || '' + } else if (this.isFollow) { + title = `${author} followed you` + } else { + return undefined + } + let ios + if ( + AppBskyEmbedImages.isPresented(this.additionalPost?.thread?.post.embed) && + this.additionalPost?.thread?.post.embed.images[0]?.thumb + ) { + ios = { + attachments: [ + {url: this.additionalPost.thread.post.embed.images[0].thumb}, + ], + } + } + return { + title, + body, + ios, + } + } } export class NotificationsViewModel { @@ -197,6 +234,9 @@ export class NotificationsViewModel { // data notifications: NotificationsViewItemModel[] = [] + // this is used to trigger push notifications + mostRecentNotification: NotificationsViewItemModel | undefined + constructor( public rootStore: RootStoreModel, params: ListNotifications.QueryParams, @@ -388,6 +428,16 @@ export class NotificationsViewModel { } private async _replaceAll(res: ListNotifications.Response) { + if (res.data.notifications[0]) { + this.mostRecentNotification = new NotificationsViewItemModel( + this.rootStore, + 'mostRecent', + res.data.notifications[0], + ) + await this.mostRecentNotification.fetchAdditionalData() + } else { + this.mostRecentNotification = undefined + } return this._appendAll(res, true) } diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts index 73f1c452f7..98a17faaa1 100644 --- a/src/state/models/root-store.ts +++ b/src/state/models/root-store.ts @@ -55,7 +55,7 @@ export class RootStoreModel { if (!this.session.online) { await this.session.connect() } - await this.me.fetchStateUpdate() + await this.me.fetchNotifications() } catch (e: any) { if (isNetworkError(e)) { this.session.setOnline(false) // connection lost