commit 07283e311d3bde56a9c45d9a34a3187d18473c38
parent 0400549e98857de1490a84f322cd8e8a3e7e1044
Author: Michael Camilleri <[email protected]>
Date: Wed, 8 Jul 2026 19:21:08 +0900
Show foreground invite notifications while rows sync
Invite pushes could disappear while the app was already on the Game
List. The foreground notification delegate suppressed invite banners on
the assumption that the 'Invited' section would show the row
immediately, but that row is created by the follow-up friend-invite sync
and can lag behind the push.
This commit lets foreground invite notifications present like other
visible pushes, so the user still sees the invitation while the durable
row catches up. The Game List also folds invite count, blocked-friend
count, name, and colour into one section refresh token, so the cached
sections rebuild when a new invite arrives or a block changes which
games and invitations are visible.
Co-Authored-By: Codex GPT 5.5 <[email protected]>
Diffstat:
2 files changed, 21 insertions(+), 16 deletions(-)
diff --git a/Crossmate/CrossmateApp.swift b/Crossmate/CrossmateApp.swift
@@ -141,15 +141,9 @@ final class AppDelegate: UIResponder, UIApplicationDelegate, @preconcurrency UNU
completionHandler([.banner, .list, .sound])
return
}
- // An invite banner is redundant on the Game List: the invite already
- // shows in the "Invited" section there. Suppress it while no puzzle is
- // active (the user is on the list), but still present it when the user
- // is inside a different puzzle and wouldn't see that row.
- let isInvite = Self.isInviteNotification(userInfo)
- if isInvite, NotificationState.activePuzzleID() == nil {
- completionHandler([])
- return
- }
+ // Invite rows are populated by an async sync pass after the push
+ // arrives. Present the foreground notification so the invite is still
+ // visible while that row catches up.
if NotificationState.isSuppressed(gameID: gameID) {
completionHandler([])
} else {
diff --git a/Crossmate/Views/GameList/GameListView.swift b/Crossmate/Views/GameList/GameListView.swift
@@ -82,6 +82,13 @@ struct GameListView: View {
var hasMoreCompleted = false
}
+ private struct SectionRefreshToken: Equatable {
+ var pendingInviteCount: Int
+ var blockedFriendCount: Int
+ var name: String
+ var colorID: String
+ }
+
var body: some View {
VStack(spacing: 0) {
accessibilityHeading
@@ -190,14 +197,9 @@ struct GameListView: View {
.onChange(of: pendingInviteNotificationGameID) { _, _ in
reconcilePendingInviteNotification()
}
- .onChange(of: pendingInvites.count) { _, _ in
- reconcilePendingInviteNotification()
- }
- .onChange(of: preferences.name) { _, _ in
- rebuildGameListSections()
- }
- .onChange(of: preferences.colorID) { _, _ in
+ .onChange(of: sectionRefreshToken) { _, _ in
rebuildGameListSections()
+ reconcilePendingInviteNotification()
}
.onChange(of: isVoiceOverEnabled) { _, enabled in
if enabled {
@@ -372,6 +374,15 @@ struct GameListView: View {
showingNewGame = true
}
+ private var sectionRefreshToken: SectionRefreshToken {
+ SectionRefreshToken(
+ pendingInviteCount: pendingInvites.count,
+ blockedFriendCount: blockedFriends.count,
+ name: preferences.name,
+ colorID: preferences.colorID
+ )
+ }
+
private func inviteNewGame(_ gameID: UUID, to target: FriendNewGameTarget) async {
guard let appActions else { return }
announcements.dismiss(id: Self.newGameInviteErrorID)