fix(notif): deep-link results-ready pushes to per-session results/replay screen (E-003)

This commit is contained in:
null 2026-06-25 12:34:43 -05:00
parent d91aac5ffb
commit bf3de8137b
9 changed files with 51 additions and 11 deletions

View File

@ -274,11 +274,10 @@ enum class PartnerNotificationType(
PARTNER_STARTED_GAME -> gameRouteForType(payload.gameType) ?: AppRoute.PLAY
PARTNER_COMPLETED_PART -> gameRouteForType(payload.gameType) ?: AppRoute.PLAY
// Results-ready means the session is COMPLETED, so the plain game route would show "start a
// new game" (getActiveSession returns only active sessions). The correct target is the
// per-session results/replay route — but that needs the server to also send game_session_id
// in the FCM data (currently it sends only game_type). Until that server change ships, route
// to the Play hub rather than a misleading setup screen. E-003 (results-ready follow-up).
GAME_RESULTS_READY -> AppRoute.PLAY
// new game" (getActiveSession returns only active sessions). Deep link to the per-session
// results/replay route instead, using game_session_id + game_type from the FCM data. Falls
// back to the hub only if the server didn't send the session id. E-003 (results-ready).
GAME_RESULTS_READY -> gameResultsRouteFor(payload.gameType, payload.gameSessionId) ?: AppRoute.PLAY
CHALLENGE_WAITING -> AppRoute.CONNECTION_CHALLENGES
CAPSULE_UNLOCKED -> AppRoute.MEMORY_LANE
GENTLE_REMINDER -> AppRoute.DAILY_QUESTION
@ -355,3 +354,18 @@ private fun gameRouteForType(gameType: String?): String? = when (gameType) {
GameType.DESIRE_SYNC -> AppRoute.DESIRE_SYNC
else -> null
}
/**
* The per-session results/replay route for a COMPLETED game, so a "results ready" push opens the
* actual results (not the game's setup screen). Needs both the game type and the session id. E-003.
*/
private fun gameResultsRouteFor(gameType: String?, sessionId: String?): String? {
if (sessionId.isNullOrBlank()) return null
return when (gameType) {
GameType.WHEEL -> AppRoute.wheelComplete(sessionId)
GameType.THIS_OR_THAT -> AppRoute.thisOrThatReplay(sessionId)
GameType.HOW_WELL -> AppRoute.howWellReplay(sessionId)
GameType.DESIRE_SYNC -> AppRoute.desireSyncReplay(sessionId)
else -> null
}
}

View File

@ -196,6 +196,7 @@ async function notifyPartnerJoined(
title: 'Your partner joined!',
body: "You're connected. Time to answer tonight's question together.",
},
android: { notification: { channelId: 'partner_activity' } }, // E-OBS
data: {
type: 'partner_joined',
couple_id: coupleId,

View File

@ -104,6 +104,7 @@ export const onCoupleLeave = functions.firestore
title: notificationPayload.title,
body: notificationPayload.body,
},
android: { notification: { channelId: 'partner_activity' } }, // E-OBS
data: {
type: notificationPayload.type,
},

View File

@ -68,6 +68,7 @@ async function notifyDateMatch(
title: "It's a match!",
body: "You both want to go on this date. Time to make it happen.",
},
android: { notification: { channelId: 'partner_activity' } }, // E-OBS
data: {
type: 'date_match',
couple_id: coupleId,

View File

@ -65,7 +65,7 @@ export const onGameSessionUpdate = functions.firestore
await notifyPartner(
db, messaging, recipientId, starterName, gameType,
'partner_started_game', `${starterName} has started a game. Tap to join!`, coupleId,
starterAvatar
starterAvatar, sessionId
)
return
}
@ -81,12 +81,12 @@ export const onGameSessionUpdate = functions.firestore
await notifyPartner(
db, messaging, partnerA, partnerBName, gt,
'partner_finished_game', `${partnerBName} finished — tap to see your results!`, coupleId,
avatarB
avatarB, sessionId
)
await notifyPartner(
db, messaging, partnerB, partnerAName, gt,
'partner_finished_game', `${partnerAName} finished — tap to see your results!`, coupleId,
avatarA
avatarA, sessionId
)
return
}
@ -104,7 +104,8 @@ async function notifyPartner(
notificationType: string,
body: string,
coupleId: string,
senderAvatarUrl?: string
senderAvatarUrl?: string,
sessionId?: string
): Promise<void> {
const title =
notificationType === 'partner_finished_game'
@ -161,10 +162,16 @@ async function notifyPartner(
title: notificationPayload.title,
body: notificationPayload.body,
},
// Put backgrounded notifications on the Games channel instead of the FCM fallback channel,
// so importance/sound and the per-category toggle apply. E-OBS.
android: { notification: { channelId: 'game_activity' } },
data: {
type: notificationPayload.type,
couple_id: coupleId,
game_type: gameType,
// Lets the client deep link a results-ready push to the per-session results/replay screen
// (a completed session isn't returned by getActiveSession). E-003 results-ready.
...(sessionId ? { game_session_id: sessionId } : {}),
...(senderAvatarUrl && senderAvatarUrl.length > 0
? { sender_avatar_url: senderAvatarUrl }
: {}),

View File

@ -191,6 +191,12 @@ async function sendNotification(
title: notification.title,
body: notification.body,
},
// E-OBS: challenge reminders → Reminders channel; capsule-unlocked → partner-activity channel.
android: {
notification: {
channelId: notification.type === 'challenge_day_ready' ? 'reminders' : 'partner_activity',
},
},
data: {
type: notification.type,
...notification.data,

View File

@ -103,7 +103,11 @@ export const onAnswerWritten = functions.firestore
const sendResults = await Promise.allSettled(
tokens.map((token) =>
admin.messaging().send({ ...payload, token } as admin.messaging.Message)
admin.messaging().send({
...payload,
token,
android: { notification: { channelId: 'partner_activity' } }, // E-OBS
} as admin.messaging.Message)
)
)

View File

@ -94,7 +94,12 @@ export const onMessageWritten = functions.firestore
const sendResults = await Promise.allSettled(
tokens.map((token) =>
admin.messaging().send({ ...payload, token } as admin.messaging.Message)
admin.messaging().send({
...payload,
token,
// E-OBS: backgrounded delivery on the Chat/partner channel, not the FCM fallback channel.
android: { notification: { channelId: 'partner_activity' } },
} as admin.messaging.Message)
)
)

View File

@ -118,6 +118,7 @@ async function notifyPartner(
title: 'Your partner deleted their account',
body: 'You are no longer paired. Tap to create a new invite.',
},
android: { notification: { channelId: 'partner_activity' } }, // E-OBS
data: { type: 'partner_deleted_account' },
})
)