commit 412e38cdf3a44e290a3e4ab8e510af2cf6cd95f3
parent 23dcb635efd09a516f60aecebcb9856a42ec58f2
Author: Michael Camilleri <[email protected]>
Date: Thu, 25 Jun 2026 00:43:26 +0900
Seed the share-screen checkmarks before the first frame
Reflecting already-invited friends through a CloudKit fetch meant the
checkmark animated after a brief delay whenever the share screen
re-opened, as though the invite had only just been sent — a confusing
flourish for an invitation that is known to have been sent.
This commit seeds each invite surface synchronously at construction from
ShareController.invitedAuthorIDsKnownThisSession(for:), a plain read of
the in-session invitee set. A friend invited this session now wears a
static checkmark on the first frame, since FriendAvatarView animates
only a transition into sent, not an initial value. The async fetch stays
as a backfill for invites made in a prior session or on another device,
where the delay — and the animation — is legitimate.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Diffstat:
3 files changed, 30 insertions(+), 3 deletions(-)
diff --git a/Crossmate/Sync/ShareController.swift b/Crossmate/Sync/ShareController.swift
@@ -374,7 +374,7 @@ final class ShareController {
/// earlier. Best-effort: an unshared game or a transient fetch failure
/// falls back to the session set alone.
func invitedAuthorIDs(for gameID: UUID) async -> Set<String> {
- var invited = sessionInvitedAuthorIDs[gameID] ?? []
+ var invited = invitedAuthorIDsKnownThisSession(for: gameID)
let ctx = persistence.viewContext
let request = NSFetchRequest<GameEntity>(entityName: "GameEntity")
request.predicate = NSPredicate(format: "id == %@", gameID as CVarArg)
@@ -388,6 +388,15 @@ final class ShareController {
return invited
}
+ /// The author IDs added as invitees during this app session, readable
+ /// synchronously so a re-presented share screen can render their checkmark
+ /// on the first frame — no await, no animated transition — before the
+ /// async invitedAuthorIDs(for:) backfills anyone invited on another device
+ /// or in a prior session.
+ func invitedAuthorIDsKnownThisSession(for gameID: UUID) -> Set<String> {
+ sessionInvitedAuthorIDs[gameID] ?? []
+ }
+
/// The game's grid silhouette for share-link previews, read from the
/// cached block layout so it costs nothing at link-creation time. Returns
/// `nil` when the cache hasn't been populated, in which case the link simply
diff --git a/Crossmate/Views/Friends/FriendPickerView.swift b/Crossmate/Views/Friends/FriendPickerView.swift
@@ -19,13 +19,19 @@ struct FriendPickerView: View {
private var friends: FetchedResults<FriendEntity>
@State private var invitingAuthorID: String?
- @State private var invitedAuthorIDs: Set<String> = []
+ @State private var invitedAuthorIDs: Set<String>
@State private var isInviteLimitReached: Bool
@State private var errorMessage: String?
init(gameID: UUID, shareController: ShareController, isInviteLimitReached: Bool = false) {
self.gameID = gameID
self.shareController = shareController
+ // Seed from the in-memory session set so friends invited a moment ago
+ // already wear their checkmark on the first frame; the .task below
+ // backfills anyone invited in a prior session or on another device.
+ _invitedAuthorIDs = State(
+ initialValue: shareController.invitedAuthorIDsKnownThisSession(for: gameID)
+ )
_isInviteLimitReached = State(initialValue: isInviteLimitReached)
}
diff --git a/Crossmate/Views/GameList/GameShareItem.swift b/Crossmate/Views/GameList/GameShareItem.swift
@@ -22,9 +22,21 @@ struct GameShareSheet: View {
@State private var didLoadExistingLink = false
@State private var didCopy = false
@State private var invitingAuthorID: String?
- @State private var invitedAuthorIDs: Set<String> = []
+ @State private var invitedAuthorIDs: Set<String>
@State private var isInviteLimitReached = false
+ init(gameID: UUID, title: String, shareController: ShareController) {
+ self.gameID = gameID
+ self.title = title
+ self.shareController = shareController
+ // Seed from the in-memory session set so friends invited a moment ago
+ // already wear their checkmark on the first frame; the .task below
+ // backfills anyone invited in a prior session or on another device.
+ _invitedAuthorIDs = State(
+ initialValue: shareController.invitedAuthorIDsKnownThisSession(for: gameID)
+ )
+ }
+
private var visibleFriends: Array<FetchedResults<FriendEntity>.Element> {
Array(friends.prefix(4))
}