From 2b8e05b29bbad9b6deb7e67ad9e26b8648027f61 Mon Sep 17 00:00:00 2001 From: null Date: Thu, 18 Jun 2026 01:28:43 -0500 Subject: [PATCH] =?UTF-8?q?fix:=20address=20Neo=20review=20=E2=80=94=20fix?= =?UTF-8?q?=20cloud=20function=20partnerName=20scope,=20WheelCompleteScree?= =?UTF-8?q?n=20session=20ID,=20polling=20isActive,=20navigateTo=20reset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../closer/ui/desiresync/DesireSyncScreen.kt | 9 +- .../ui/games/WaitingForPartnerScreen.kt | 11 +- .../app/closer/ui/howwell/HowWellScreen.kt | 9 +- .../closer/ui/thisorthat/ThisOrThatScreen.kt | 9 +- .../closer/ui/wheel/LocalWheelSessionStore.kt | 1 + .../app/closer/ui/wheel/SpinWheelViewModel.kt | 1 + .../closer/ui/wheel/WheelCompleteScreen.kt | 15 +- functions/dist/games/onGameSessionUpdate.js | 180 ++++++++++++++++++ .../dist/games/onGameSessionUpdate.js.map | 1 + functions/dist/index.js | 4 +- functions/dist/index.js.map | 2 +- functions/src/games/onGameSessionUpdate.ts | 5 +- 12 files changed, 226 insertions(+), 21 deletions(-) create mode 100644 functions/dist/games/onGameSessionUpdate.js create mode 100644 functions/dist/games/onGameSessionUpdate.js.map diff --git a/app/src/main/java/app/closer/ui/desiresync/DesireSyncScreen.kt b/app/src/main/java/app/closer/ui/desiresync/DesireSyncScreen.kt index faa571bb..37eeb486 100644 --- a/app/src/main/java/app/closer/ui/desiresync/DesireSyncScreen.kt +++ b/app/src/main/java/app/closer/ui/desiresync/DesireSyncScreen.kt @@ -248,6 +248,10 @@ class DesireSyncViewModel @Inject constructor( } else null } + fun onNavigated() { + _uiState.update { it.copy(navigateTo = null) } + } + companion object { private const val SESSION_SIZE = 10 private const val ADVANCE_DELAY_MS = 380L @@ -265,7 +269,10 @@ fun DesireSyncScreen( val state by viewModel.uiState.collectAsState() LaunchedEffect(state.navigateTo) { - state.navigateTo?.let { onNavigate(it) } + state.navigateTo?.let { + onNavigate(it) + viewModel.onNavigated() + } } Box( diff --git a/app/src/main/java/app/closer/ui/games/WaitingForPartnerScreen.kt b/app/src/main/java/app/closer/ui/games/WaitingForPartnerScreen.kt index 5a7bf479..404ef7f6 100644 --- a/app/src/main/java/app/closer/ui/games/WaitingForPartnerScreen.kt +++ b/app/src/main/java/app/closer/ui/games/WaitingForPartnerScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment @@ -37,6 +38,7 @@ import app.closer.domain.usecase.GameSessionManager import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -63,7 +65,7 @@ class WaitingForPartnerViewModel @Inject constructor( viewModelScope.launch { loadGameInfo() // Poll for partner's session completion - while (_uiState.value.navigateTo == null) { + while (isActive && _uiState.value.navigateTo == null) { delay(5000) // Check every 5 seconds val userId = gameSessionManager.currentUserId ?: "" val couple = gameSessionManager.getCoupleForUser(userId) @@ -110,6 +112,13 @@ fun WaitingForPartnerScreen( ) { val state by viewModel.uiState.collectAsState() + LaunchedEffect(state.navigateTo) { + state.navigateTo?.let { + onNavigate(it) + viewModel.onNavigated() + } + } + Box( modifier = Modifier .fillMaxSize() diff --git a/app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt b/app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt index 9d905c15..f3890a36 100644 --- a/app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt +++ b/app/src/main/java/app/closer/ui/howwell/HowWellScreen.kt @@ -235,6 +235,10 @@ class HowWellViewModel @Inject constructor( load() } + fun onNavigated() { + _uiState.update { it.copy(navigateTo = null) } + } + companion object { const val SESSION_SIZE = 10 private const val TAG = "HowWellViewModel" @@ -251,7 +255,10 @@ fun HowWellScreen( val state by viewModel.uiState.collectAsState() LaunchedEffect(state.navigateTo) { - state.navigateTo?.let { onNavigate(it) } + state.navigateTo?.let { + onNavigate(it) + viewModel.onNavigated() + } } Box( diff --git a/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt b/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt index 5ae168cb..84e3976e 100644 --- a/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt +++ b/app/src/main/java/app/closer/ui/thisorthat/ThisOrThatScreen.kt @@ -162,6 +162,10 @@ class ThisOrThatViewModel @Inject constructor( load() } + fun onNavigated() { + _uiState.update { it.copy(navigateTo = null) } + } + companion object { const val SESSION_SIZE = 10 private const val TAG = "ThisOrThatViewModel" @@ -178,7 +182,10 @@ fun ThisOrThatScreen( val state by viewModel.uiState.collectAsState() LaunchedEffect(state.navigateTo) { - state.navigateTo?.let { onNavigate(it) } + state.navigateTo?.let { + onNavigate(it) + viewModel.onNavigated() + } } Box( diff --git a/app/src/main/java/app/closer/ui/wheel/LocalWheelSessionStore.kt b/app/src/main/java/app/closer/ui/wheel/LocalWheelSessionStore.kt index d89c300b..4b94fbaf 100644 --- a/app/src/main/java/app/closer/ui/wheel/LocalWheelSessionStore.kt +++ b/app/src/main/java/app/closer/ui/wheel/LocalWheelSessionStore.kt @@ -13,6 +13,7 @@ data class LocalWheelSession( @Singleton class LocalWheelSessionStore @Inject constructor() { var activeSession: LocalWheelSession? = null + var sessionId: String? = null var lastAnswered: Int = 0 var lastTotal: Int = 0 } diff --git a/app/src/main/java/app/closer/ui/wheel/SpinWheelViewModel.kt b/app/src/main/java/app/closer/ui/wheel/SpinWheelViewModel.kt index 0f8e7932..c103e9d0 100644 --- a/app/src/main/java/app/closer/ui/wheel/SpinWheelViewModel.kt +++ b/app/src/main/java/app/closer/ui/wheel/SpinWheelViewModel.kt @@ -125,6 +125,7 @@ class SpinWheelViewModel @Inject constructor( return@launch } + sessionStore.sessionId = startResult _uiState.update { it.copy(navigateTo = AppRoute.wheelSession(startResult)) } } } diff --git a/app/src/main/java/app/closer/ui/wheel/WheelCompleteScreen.kt b/app/src/main/java/app/closer/ui/wheel/WheelCompleteScreen.kt index 73d87224..3ccfee5a 100644 --- a/app/src/main/java/app/closer/ui/wheel/WheelCompleteScreen.kt +++ b/app/src/main/java/app/closer/ui/wheel/WheelCompleteScreen.kt @@ -5,10 +5,8 @@ import app.closer.ui.theme.closerBackgroundBrush import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import app.closer.core.navigation.AppRoute -import app.closer.domain.model.QuestionSession import app.closer.domain.repository.AuthRepository import app.closer.domain.repository.CoupleRepository -import app.closer.domain.repository.QuestionSessionRepository import app.closer.domain.usecase.GameSessionManager import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch @@ -51,7 +49,6 @@ import app.closer.ui.theme.CloserPalette @HiltViewModel class WheelCompleteViewModel @Inject constructor( private val sessionStore: LocalWheelSessionStore, - private val sessionRepository: QuestionSessionRepository, private val authRepository: AuthRepository, private val coupleRepository: CoupleRepository, private val gameSessionManager: GameSessionManager @@ -66,20 +63,12 @@ class WheelCompleteViewModel @Inject constructor( private fun saveSession() { val session = sessionStore.activeSession ?: return + val sessionId = sessionStore.sessionId ?: return val uid = authRepository.currentUserId ?: return viewModelScope.launch { val couple = coupleRepository.getCoupleForUser(uid) ?: return@launch - val savedSession = QuestionSession( - coupleId = couple.id, - categoryId = session.categoryId, - questionIds = session.questions.map { it.id }, - startedByUserId = uid, - completedAt = System.currentTimeMillis(), - status = "completed" - ) - sessionRepository.saveSession(savedSession) gameSessionManager.finishGame( - sessionId = savedSession.id, + sessionId = sessionId, coupleId = couple.id, userId = uid ) diff --git a/functions/dist/games/onGameSessionUpdate.js b/functions/dist/games/onGameSessionUpdate.js new file mode 100644 index 00000000..18235eae --- /dev/null +++ b/functions/dist/games/onGameSessionUpdate.js @@ -0,0 +1,180 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.onGameSessionUpdate = void 0; +const functions = __importStar(require("firebase-functions")); +const admin = __importStar(require("firebase-admin")); +/** + * Firestore trigger that notifies partners when a game session is created or completed. + * + * Path: couples/{coupleId}/sessions/{sessionId} + * Condition: onWrite (create, update, delete) + */ +exports.onGameSessionUpdate = functions.firestore + .document('couples/{coupleId}/sessions/{sessionId}') + .onWrite(async (change, context) => { + var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p; + const { coupleId, sessionId } = context.params; + const db = admin.firestore(); + const messaging = admin.messaging(); + // Get the session document + const sessionDoc = await db.collection('couples').doc(coupleId).collection('sessions').doc(sessionId).get(); + const session = sessionDoc.data(); + if (!session) { + console.log(`[onGameSessionUpdate] session ${sessionId} not found, skipping`); + return; + } + // Get couple info + const coupleDoc = await db.collection('couples').doc(coupleId).get(); + if (!coupleDoc.exists) { + console.warn(`[onGameSessionUpdate] couple ${coupleId} not found`); + return; + } + const coupleData = (_a = coupleDoc.data()) !== null && _a !== void 0 ? _a : {}; + const userIds = ((_b = coupleData.userIds) !== null && _b !== void 0 ? _b : []); + if (userIds.length !== 2) { + console.warn(`[onGameSessionUpdate] invalid couple ${coupleId}: expected 2 users, got ${userIds.length}`); + return; + } + const partnerA = userIds[0]; + const partnerB = userIds[1]; + // Get user display names for notifications + const userA = await db.collection('users').doc(partnerA).get(); + const userB = await db.collection('users').doc(partnerB).get(); + const partnerAName = (_d = (_c = userA.data()) === null || _c === void 0 ? void 0 : _c.displayName) !== null && _d !== void 0 ? _d : 'Partner A'; + const partnerBName = (_f = (_e = userB.data()) === null || _e === void 0 ? void 0 : _e.displayName) !== null && _f !== void 0 ? _f : 'Partner B'; + // Check if session was just created (status = "active") + const previousData = (_g = change.before.data()) !== null && _g !== void 0 ? _g : {}; + const currentData = (_h = change.after.data()) !== null && _h !== void 0 ? _h : {}; + const wasInactive = ((_j = previousData.status) !== null && _j !== void 0 ? _j : '') !== 'active'; + const isActiveNow = currentData.status === 'active'; + if (wasInactive && isActiveNow) { + // New session started - notify the other partner + const startedBy = currentData.startedByUserId; + const gameType = (_k = currentData.gameType) !== null && _k !== void 0 ? _k : 'wheel'; + const partnerId = startedBy === partnerA ? partnerB : partnerA; + const partnerName = startedBy === partnerA ? partnerBName : partnerAName; + await notifyPartner(db, messaging, partnerId, partnerName, gameType, 'partner_started_game', `${partnerName} has started a game. Tap to join!`); + return; + } + // Check if session was completed + const wasActive = ((_l = previousData.status) !== null && _l !== void 0 ? _l : '') === 'active'; + const isCompletedNow = currentData.status === 'completed'; + if (wasActive && isCompletedNow) { + const completedBy = currentData.startedByUserId; + const partnerId = completedBy === partnerA ? partnerB : partnerA; + const completingPartnerName = completedBy === partnerA ? partnerAName : partnerBName; + // Check if partner has also completed + const partnerCompletedAt = currentData.partnerCompletedAt; + if (partnerCompletedAt) { + // Both completed - notify both + await notifyPartner(db, messaging, partnerA, partnerAName, (_m = currentData.gameType) !== null && _m !== void 0 ? _m : 'wheel', 'partner_finished_game', `${partnerBName} has finished the game. Tap to see the results!`); + await notifyPartner(db, messaging, partnerB, partnerBName, (_o = currentData.gameType) !== null && _o !== void 0 ? _o : 'wheel', 'partner_finished_game', `${partnerAName} has finished the game. Tap to see the results!`); + } + else { + // Only one completed - notify the other to continue + await notifyPartner(db, messaging, partnerId, completingPartnerName, (_p = currentData.gameType) !== null && _p !== void 0 ? _p : 'wheel', 'partner_finished_game', `${completingPartnerName} has finished. Tap to continue playing!`); + } + return; + } +}); +/** + * Send notification to partner via FCM and write to notification_queue. + */ +async function notifyPartner(db, messaging, partnerId, partnerName, gameType, notificationType, body) { + var _a; + const notificationPayload = { + type: notificationType, + title: `${partnerName} is playing`, + body: body, + }; + // Write an in-app notification record for the partner + await db + .collection('users') + .doc(partnerId) + .collection('notification_queue') + .add(Object.assign(Object.assign({}, notificationPayload), { read: false, createdAt: admin.firestore.FieldValue.serverTimestamp() })); + // Collect the partner's FCM tokens + const tokens = []; + const partnerUserDoc = await db.collection('users').doc(partnerId).get(); + if (partnerUserDoc.exists) { + const legacyToken = (_a = partnerUserDoc.data()) === null || _a === void 0 ? void 0 : _a.fcmToken; + if (typeof legacyToken === 'string' && legacyToken.length > 0) { + tokens.push(legacyToken); + } + } + const tokenSnapshot = await db + .collection('users') + .doc(partnerId) + .collection('fcmTokens') + .get(); + tokenSnapshot.docs.forEach((doc) => { + var _a; + const t = (_a = doc.data()) === null || _a === void 0 ? void 0 : _a.token; + if (typeof t === 'string' && t.length > 0 && !tokens.includes(t)) { + tokens.push(t); + } + }); + if (tokens.length === 0) { + console.log(`[notifyPartner] no FCM tokens for ${partnerId}`); + return; + } + const fcmMessage = { + token: tokens[0], + notification: { + title: notificationPayload.title, + body: notificationPayload.body, + }, + data: { + type: notificationPayload.type, + gameType: gameType, + partnerId: partnerId, + }, + }; + const sendResults = await Promise.allSettled(tokens.map((token) => messaging.send(Object.assign(Object.assign({}, fcmMessage), { token })))); + const failures = []; + sendResults.forEach((result, index) => { + if (result.status === 'rejected') { + failures.push(`${tokens[index]}: ${String(result.reason)}`); + } + }); + if (failures.length > 0) { + console.error(`[notifyPartner] some notifications failed:`, failures); + } + else { + console.log(`[notifyPartner] notified ${partnerId} (${notificationType})`); + } +} +//# sourceMappingURL=onGameSessionUpdate.js.map \ No newline at end of file diff --git a/functions/dist/games/onGameSessionUpdate.js.map b/functions/dist/games/onGameSessionUpdate.js.map new file mode 100644 index 00000000..797fa861 --- /dev/null +++ b/functions/dist/games/onGameSessionUpdate.js.map @@ -0,0 +1 @@ +{"version":3,"file":"onGameSessionUpdate.js","sourceRoot":"","sources":["../../src/games/onGameSessionUpdate.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/functions/dist/index.js b/functions/dist/index.js index 563b64aa..5a785117 100644 --- a/functions/dist/index.js +++ b/functions/dist/index.js @@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -exports.health = exports.onCoupleLeave = exports.onAnswerWritten = exports.assignDailyQuestionCallable = exports.assignDailyQuestion = exports.createDateMatchOnMutualLove = exports.checkDeviceIntegrity = exports.sendPartnerAnsweredNotification = exports.sendDailyQuestionReminder = exports.syncEntitlement = exports.revenueCatWebhook = void 0; +exports.health = exports.onGameSessionUpdate = exports.onCoupleLeave = exports.onAnswerWritten = exports.assignDailyQuestionCallable = exports.assignDailyQuestion = exports.createDateMatchOnMutualLove = exports.checkDeviceIntegrity = exports.sendPartnerAnsweredNotification = exports.sendDailyQuestionReminder = exports.syncEntitlement = exports.revenueCatWebhook = void 0; const functions = __importStar(require("firebase-functions")); const admin = __importStar(require("firebase-admin")); // Initialize the Admin SDK once for every function in this codebase. @@ -60,6 +60,8 @@ var onAnswerWritten_1 = require("./questions/onAnswerWritten"); Object.defineProperty(exports, "onAnswerWritten", { enumerable: true, get: function () { return onAnswerWritten_1.onAnswerWritten; } }); var onCoupleLeave_1 = require("./couples/onCoupleLeave"); Object.defineProperty(exports, "onCoupleLeave", { enumerable: true, get: function () { return onCoupleLeave_1.onCoupleLeave; } }); +var onGameSessionUpdate_1 = require("./games/onGameSessionUpdate"); +Object.defineProperty(exports, "onGameSessionUpdate", { enumerable: true, get: function () { return onGameSessionUpdate_1.onGameSessionUpdate; } }); /** * Basic health check callable. * Useful for verifying function deployment and firebase-tools wiring. diff --git a/functions/dist/index.js.map b/functions/dist/index.js.map index 50fa23ba..0c60bb53 100644 --- a/functions/dist/index.js.map +++ b/functions/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8DAA+C;AAC/C,sDAAuC;AAEvC,qEAAqE;AACrE,8EAA8E;AAC9E,gFAAgF;AAChF,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IAC5B,KAAK,CAAC,aAAa,EAAE,CAAA;AACvB,CAAC;AAED,iEAA+D;AAAtD,sHAAA,iBAAiB,OAAA;AAC1B,6DAA2D;AAAlD,kHAAA,eAAe,OAAA;AACxB,uDAGkC;AAFhC,sHAAA,yBAAyB,OAAA;AACzB,4HAAA,+BAA+B,OAAA;AAEjC,wEAAsE;AAA7D,4HAAA,oBAAoB,OAAA;AAC7B,2DAAqE;AAA5D,8HAAA,2BAA2B,OAAA;AACpC,uEAGwC;AAFtC,0HAAA,mBAAmB,OAAA;AACnB,kIAAA,2BAA2B,OAAA;AAE7B,+DAA6D;AAApD,kHAAA,eAAe,OAAA;AACxB,yDAAuD;AAA9C,8GAAA,aAAa,OAAA;AAEtB;;;GAGG;AACU,QAAA,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IAC3D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;AACxC,CAAC,CAAC,CAAA"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,8DAA+C;AAC/C,sDAAuC;AAEvC,qEAAqE;AACrE,8EAA8E;AAC9E,gFAAgF;AAChF,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;IAC5B,KAAK,CAAC,aAAa,EAAE,CAAA;AACvB,CAAC;AAED,iEAA+D;AAAtD,sHAAA,iBAAiB,OAAA;AAC1B,6DAA2D;AAAlD,kHAAA,eAAe,OAAA;AACxB,uDAGkC;AAFhC,sHAAA,yBAAyB,OAAA;AACzB,4HAAA,+BAA+B,OAAA;AAEjC,wEAAsE;AAA7D,4HAAA,oBAAoB,OAAA;AAC7B,2DAAqE;AAA5D,8HAAA,2BAA2B,OAAA;AACpC,uEAGwC;AAFtC,0HAAA,mBAAmB,OAAA;AACnB,kIAAA,2BAA2B,OAAA;AAE7B,+DAA6D;AAApD,kHAAA,eAAe,OAAA;AACxB,yDAAuD;AAA9C,8GAAA,aAAa,OAAA;AACtB,mEAAiE;AAAxD,0HAAA,mBAAmB,OAAA;AAE5B;;;GAGG;AACU,QAAA,MAAM,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;IAC3D,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;AACxC,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/functions/src/games/onGameSessionUpdate.ts b/functions/src/games/onGameSessionUpdate.ts index 27508801..575e65c7 100644 --- a/functions/src/games/onGameSessionUpdate.ts +++ b/functions/src/games/onGameSessionUpdate.ts @@ -79,6 +79,7 @@ export const onGameSessionUpdate = functions.firestore if (wasActive && isCompletedNow) { const completedBy = currentData.startedByUserId const partnerId = completedBy === partnerA ? partnerB : partnerA + const completingPartnerName = completedBy === partnerA ? partnerAName : partnerBName // Check if partner has also completed const partnerCompletedAt = currentData.partnerCompletedAt @@ -108,10 +109,10 @@ export const onGameSessionUpdate = functions.firestore db, messaging, partnerId, - partnerName, + completingPartnerName, currentData.gameType ?? 'wheel', 'partner_finished_game', - `${partnerName} has finished. Tap to continue playing!` + `${completingPartnerName} has finished. Tap to continue playing!` ) } return