commit 7f29b31e961964c461d10298b8877c8d16b0083f
parent cceaeae51810ad4f7a86b326597ebe3d3507c6ec
Author: Michael Camilleri <[email protected]>
Date: Sat, 13 Jun 2026 13:54:39 +0900
Suggest user set profile name on first launch
Diffstat:
2 files changed, 50 insertions(+), 7 deletions(-)
diff --git a/Crossmate/Models/PlayerPreferences.swift b/Crossmate/Models/PlayerPreferences.swift
@@ -32,9 +32,14 @@ final class PlayerPreferences {
}
var name: String {
- didSet { write(Keys.name, name) }
+ didSet {
+ hasName = Self.isUsableName(name)
+ write(Keys.name, name)
+ }
}
+ var hasName: Bool
+
/// Debugging switch for comparing on-device responsiveness with CloudKit
/// work paused. Stored locally only so disabling sync on one device does
/// not silently disable it everywhere.
@@ -78,9 +83,10 @@ final class PlayerPreferences {
self.colorID = cloud.string(forKey: Keys.colorID)
?? local.string(forKey: Keys.colorID)
?? PlayerColor.blue.id
- self.name = cloud.string(forKey: Keys.name)
+ let storedName = cloud.string(forKey: Keys.name)
?? local.string(forKey: Keys.name)
- ?? "Player"
+ self.name = storedName ?? "Player"
+ self.hasName = storedName.map(Self.isUsableName) ?? false
self.isICloudSyncEnabled = local.object(forKey: Keys.isICloudSyncEnabled) as? Bool ?? true
self.notifiesSessionBegin = local.object(forKey: Keys.notifiesSessionBegin) as? Bool ?? true
self.notifiesSessionEnd = local.object(forKey: Keys.notifiesSessionEnd) as? Bool ?? true
@@ -111,4 +117,8 @@ final class PlayerPreferences {
name = newName
}
}
+
+ private static func isUsableName(_ name: String) -> Bool {
+ !name.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
+ }
}
diff --git a/Crossmate/Views/GameListView.swift b/Crossmate/Views/GameListView.swift
@@ -35,6 +35,7 @@ struct GameListView: View {
@Environment(\.declineInvite) private var declineInvite
@Environment(\.blockFriend) private var blockFriend
@Environment(\.sendResignPings) private var sendResignPings
+ @Environment(PlayerPreferences.self) private var preferences
@Environment(AnnouncementCenter.self) private var announcements
@State private var acceptingInviteID: NSManagedObjectID?
@State private var blockTarget: InviteEntity?
@@ -46,6 +47,8 @@ struct GameListView: View {
@State private var resignTarget: GameSummary?
@State private var leaveTarget: GameSummary?
@State private var leaveError: Error?
+ @State private var showingNamePrompt = false
+ @State private var nameDraft = ""
@State private var summaryCache = GameSummaryCache()
@State private var completedVisibleCount = completedPageSize
@@ -196,6 +199,22 @@ struct GameListView: View {
let name = blockTarget?.resolvedInviterName ?? "this player"
Text("You won't receive further invites from \(name), and any games they currently share with you will be removed from this device.")
}
+ .alert("Set Profile Name", isPresented: $showingNamePrompt) {
+ TextField("Name", text: $nameDraft)
+ .textInputAutocapitalization(.never)
+ .autocorrectionDisabled()
+ Button("Cancel", role: .cancel) {}
+ Button("Save") {
+ let trimmed = nameDraft.trimmingCharacters(in: .whitespacesAndNewlines)
+ if !trimmed.isEmpty {
+ preferences.name = trimmed
+ nameDraft = trimmed
+ }
+ }
+ .keyboardShortcut(.defaultAction)
+ } message: {
+ Text("Enter the name other players will see.")
+ }
}
@ViewBuilder
@@ -243,10 +262,24 @@ struct GameListView: View {
}
.overlay {
if games.isEmpty {
- ContentUnavailableView {
- Label("No Puzzles", systemImage: "square.grid.3x3")
- } description: {
- Text("Tap the + button to start a new puzzle, or pull down to refresh.")
+ if preferences.hasName {
+ ContentUnavailableView {
+ Label("No Puzzles", systemImage: "square.grid.3x3")
+ } description: {
+ Text("Tap the + button to start a new puzzle, or pull down to refresh.")
+ }
+ } else {
+ ContentUnavailableView {
+ Label("Set Your Profile Name", systemImage: "person.text.rectangle")
+ } description: {
+ Text("Choose the name other players will see.")
+ } actions: {
+ Button {
+ nameDraft = ""
+ showingNamePrompt = true
+ } label: { Text("Set Profile Name") }
+ .buttonStyle(.borderedProminent)
+ }
}
}
}