crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

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:
MCrossmate/Views/Components/FriendAvatarView.swift | 11+++++------
MCrossmate/Views/Friends/FriendPickerView.swift | 2++
MCrossmate/Views/Friends/FriendsView.swift | 6+++++-
MCrossmate/Views/GameList/GameShareItem.swift | 2++
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)