commit 42bda51e5e7ff94433e73f5ea70e923c624af903
parent c943ffca7090dbdf1cb1336ba68f66165134b594
Author: Michael Camilleri <[email protected]>
Date: Thu, 18 Jun 2026 04:15:19 +0900
Derive friend avatar colours in consistent manner
This commit ensures that all friend avatar colours are derived in a
manner that exvludes the user's own colour, matching the one-player game
participant path. A friend whose base avatar colour collides with the
local player no longer appears under one colour in the Friends List and
another colour once invited into a puzzle.
To achieve this, the shared avatar view now accepts reserved colour
identifiers and keeps the previous empty-reservation default for other
callers. The friends list, full invite picker and compact share-sheet
invite buttons pass the current player colour through that hook.
Co-Authored-By: Codex GPT 5.5 <[email protected]>
Diffstat:
4 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/Crossmate/Views/Components/FriendAvatarView.swift b/Crossmate/Views/Components/FriendAvatarView.swift
@@ -14,6 +14,7 @@ struct FriendAvatarView: View {
let authorID: String
var size: CGFloat = 34
+ var reservedColorIDs: Set<String> = []
var invitePhase: InvitePhase? = nil
/// Spin shared by the avatar → paper plane and paper plane → checkmark
@@ -25,7 +26,7 @@ struct FriendAvatarView: View {
@State private var ringsTask: Task<Void, Never>?
private var avatar: FriendAvatar {
- FriendAvatar.avatar(for: authorID)
+ FriendAvatar.avatar(for: authorID, reservedColorIDs: reservedColorIDs)
}
/// The glyph shown in the avatar. While sending it is a paper plane; on
@@ -131,14 +132,12 @@ private struct FriendAvatar {
let symbolName: String
let background: Color
- static func avatar(for authorID: String) -> FriendAvatar {
+ static func avatar(for authorID: String, reservedColorIDs: Set<String>) -> FriendAvatar {
// The symbol is seeded with a distinct string from the colour (which
// `stableColor` hashes off the raw authorID) so a friend's symbol and
- // colour vary independently rather than always pairing up. The avatar
- // is global to the friend list, so no colours are reserved here — this
- // is the friend's base colour, which a busy game may probe off.
+ // colour vary independently rather than always pairing up.
let symbol = symbols[PlayerColor.stableIndex(for: "symbol-\(authorID)", count: symbols.count)]
- let color = PlayerColor.stableColor(forAuthorID: authorID, reserved: [])
+ let color = PlayerColor.stableColor(forAuthorID: authorID, reserved: reservedColorIDs)
return FriendAvatar(symbolName: symbol, background: color.tint)
}
diff --git a/Crossmate/Views/Friends/FriendPickerView.swift b/Crossmate/Views/Friends/FriendPickerView.swift
@@ -7,6 +7,7 @@ import SwiftUI
struct FriendPickerView: View {
let gameID: UUID
+ @Environment(PlayerPreferences.self) private var preferences
@Environment(\.inviteFriend) private var inviteFriend
@Environment(\.dismiss) private var dismiss
@@ -71,6 +72,7 @@ struct FriendPickerView: View {
HStack {
FriendAvatarView(
authorID: authorID,
+ reservedColorIDs: [preferences.color.id],
invitePhase: invitePhase(authorID: authorID, invited: invited)
)
.padding(.trailing, 8)
diff --git a/Crossmate/Views/Friends/FriendsView.swift b/Crossmate/Views/Friends/FriendsView.swift
@@ -6,6 +6,7 @@ import SwiftUI
/// nickname via `\.renameFriend`) and blocking, which tears down the pairwise
/// channel via `\.blockFriend`.
struct FriendsView: View {
+ @Environment(PlayerPreferences.self) private var preferences
@Environment(\.blockFriend) private var blockFriend
@Environment(\.renameFriend) private var renameFriend
@Environment(\.dismiss) private var dismiss
@@ -83,7 +84,10 @@ struct FriendsView: View {
@ViewBuilder
private func friendRow(for friend: FriendEntity) -> some View {
HStack {
- FriendAvatarView(authorID: friend.authorID ?? "")
+ FriendAvatarView(
+ authorID: friend.authorID ?? "",
+ reservedColorIDs: [preferences.color.id]
+ )
.padding(.trailing, 8)
Text(friend.resolvedDisplayName)
Spacer()
diff --git a/Crossmate/Views/GameList/GameShareItem.swift b/Crossmate/Views/GameList/GameShareItem.swift
@@ -6,6 +6,7 @@ struct GameShareSheet: View {
let title: String
let shareController: ShareController
+ @Environment(PlayerPreferences.self) private var preferences
@Environment(\.inviteFriend) private var inviteFriend
@Environment(\.dismiss) private var dismiss
@FetchRequest(
@@ -203,6 +204,7 @@ struct GameShareSheet: View {
FriendAvatarView(
authorID: authorID,
size: 40,
+ reservedColorIDs: [preferences.color.id],
invitePhase: invitePhase(authorID: authorID, invited: wasInvited)
)
Text(friend.resolvedDisplayName)