From 200404483c4cb0e0320ec589d27b00134cd92c6b Mon Sep 17 00:00:00 2001 From: arnauda-gh <59512940+arnauda-gh@users.noreply.github.com> Date: Fri, 9 Oct 2026 09:01:28 +0200 Subject: [PATCH] fix(notifications): ensure notification tap always opens target email --- App.tsx | 1 + .../java/com/anonymous/bulwarkmobile/BulwarkFcmModule.kt | 9 ++++++++- src/lib/__tests__/viewer-pages.test.ts | 8 ++++++++ src/screens/EmailThreadScreen.tsx | 7 ++++++- 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/App.tsx b/App.tsx index 5e152ebf..4622f730 100644 --- a/App.tsx +++ b/App.tsx @@ -119,6 +119,7 @@ async function navigateToNotificationTap(payload: NotificationTapPayload): Promi navigationRef.navigate('EmailThread', { emailId: payload.emailId, threadId: payload.threadId, + emailIds: [payload.emailId], subject: payload.subject, // A group mailbox's message lives under another JMAP account (#839). jmapAccountId: notificationTapJmapAccountId(payload), diff --git a/android/app/src/main/java/com/anonymous/bulwarkmobile/BulwarkFcmModule.kt b/android/app/src/main/java/com/anonymous/bulwarkmobile/BulwarkFcmModule.kt index 6922adbe..c69d042c 100644 --- a/android/app/src/main/java/com/anonymous/bulwarkmobile/BulwarkFcmModule.kt +++ b/android/app/src/main/java/com/anonymous/bulwarkmobile/BulwarkFcmModule.kt @@ -4,6 +4,7 @@ import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.net.Uri import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.Canvas @@ -114,6 +115,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext) val ctx = reactApplicationContext val intent = Intent(ctx, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP + data = Uri.parse("bulwark-notification://$notificationId") if (emailId != null) putExtra(NotificationTapStore.EXTRA_EMAIL_ID, emailId) if (threadId != null) putExtra(NotificationTapStore.EXTRA_THREAD_ID, threadId) if (subject != null) putExtra(NotificationTapStore.EXTRA_SUBJECT, subject) @@ -157,7 +159,11 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext) it.groupKey?.endsWith(groupKey) == true && (it.notification.flags and android.app.Notification.FLAG_GROUP_SUMMARY) == 0 } - val count = maxOf(children.size, 1) + if (children.size < 2) { + manager.cancel(groupKey, groupKey.hashCode()) + return + } + val count = children.size val inbox = NotificationCompat.InboxStyle() children.sortedByDescending { it.postTime }.take(5).forEach { sbn -> val extras = sbn.notification.extras @@ -172,6 +178,7 @@ class BulwarkFcmModule(reactContext: ReactApplicationContext) val intent = Intent(ctx, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP + data = Uri.parse("bulwark-group-summary://$groupKey") if (accountId != null) putExtra(NotificationTapStore.EXTRA_ACCOUNT_ID, accountId) } val pending = PendingIntent.getActivity( diff --git a/src/lib/__tests__/viewer-pages.test.ts b/src/lib/__tests__/viewer-pages.test.ts index bad52442..1120ad41 100644 --- a/src/lib/__tests__/viewer-pages.test.ts +++ b/src/lib/__tests__/viewer-pages.test.ts @@ -41,6 +41,14 @@ describe('viewerPages', () => { expect(pages).toEqual([{ id: 'u1', threadId: 't-u1' }, LIST[3]]); }); + it('is a single page when opened with a single emailId from notification tap', () => { + const pages = viewerPages({ + emailId: 'q2', threadId: 'tq', emailIds: ['q2'], + list: LIST, threading: true, + }); + expect(pages).toEqual([LIST[1]]); + }); + it('is a single page for a message the list does not hold', () => { const pages = viewerPages({ emailId: 'm9', threadId: 't9', list: LIST, threading: true, diff --git a/src/screens/EmailThreadScreen.tsx b/src/screens/EmailThreadScreen.tsx index aad8f3f9..851d965b 100644 --- a/src/screens/EmailThreadScreen.tsx +++ b/src/screens/EmailThreadScreen.tsx @@ -735,12 +735,17 @@ function EmailViewer({ route, navigation }: Props) { initialScrollIndex={initialIndexRef.current} getItemLayout={(_, index) => ({ length: windowWidth, offset: windowWidth * index, index })} windowSize={3} - initialNumToRender={1} + initialNumToRender={Math.max(initialIndexRef.current + 1, 1)} maxToRenderPerBatch={2} removeClippedSubviews scrollEnabled={!pagerLocked} onScrollBeginDrag={releaseNeighbours} onMomentumScrollEnd={onMomentumEnd} + onScrollToIndexFailed={(info) => { + setTimeout(() => { + listRef.current?.scrollToOffset({ offset: info.offset, animated: false }); + }, 50); + }} renderItem={({ item, index }) => (