commit c532223bd9035bbe06ca0c8eacfda3dd95dbbc72
parent e7db7db51c539719ffc93e2bb546d23e5ae1398e
Author: Michael Camilleri <[email protected]>
Date: Wed, 22 Apr 2026 06:48:40 +0900
Use clue list to display long clues
Prior to this commit, if a clue was too long to display in the Clue Bar,
it would simply be truncated. This commit adds a clue list that a user
can bring up by tapping on the clue in the Clue Bar (this means that a
previous commit that allowed a user to toggle the solving direction had
to be reverted).
Co-Authored-By: Claude Opus 4.7 <[email protected]>
Diffstat:
4 files changed, 94 insertions(+), 1 deletion(-)
diff --git a/Crossmate.xcodeproj/project.pbxproj b/Crossmate.xcodeproj/project.pbxproj
@@ -41,6 +41,7 @@
AF4F1AE2A1F94E92C785C524 /* Square.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB851649DE78AAAC5A928C52 /* Square.swift */; };
B42454D72FAA219D60DEA334 /* garden.xd in Resources */ = {isa = PBXBuildFile; fileRef = 50992CDA4082429EBB17F65C /* garden.xd */; };
B762200F54C52E8377A80D15 /* NYTToXDConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF6F111BE8750697C4BC7A17 /* NYTToXDConverter.swift */; };
+ B94919176DEC6EC31637B037 /* ClueList.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9BD3F7EAFD344D8E10E8C3B /* ClueList.swift */; };
C30C0C4E54E4209A22843872 /* CrossmateModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F93AC31640C40FCC039570A3 /* CrossmateModel.xcdatamodeld */; };
C6E0E5128565D3B822A41605 /* PendingChangePayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = E512D95B4518EE3DE6E350C0 /* PendingChangePayload.swift */; };
C7370BCAD585EEFD366204E3 /* GridThumbnailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D9BB7D9759D27F7BA6734FDE /* GridThumbnailView.swift */; };
@@ -124,6 +125,7 @@
E512D95B4518EE3DE6E350C0 /* PendingChangePayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PendingChangePayload.swift; sourceTree = "<group>"; };
E524780E360E008FACE4F213 /* PendingChange+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PendingChange+Helpers.swift"; sourceTree = "<group>"; };
E7AFD37B03A1C2E23E5766E6 /* PuzzleSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PuzzleSource.swift; sourceTree = "<group>"; };
+ E9BD3F7EAFD344D8E10E8C3B /* ClueList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClueList.swift; sourceTree = "<group>"; };
EAC61E2582D94B1E6EC67136 /* XDFileType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDFileType.swift; sourceTree = "<group>"; };
F13AB28AA016F8A3DF53E6AA /* OutboxRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OutboxRecorder.swift; sourceTree = "<group>"; };
F2F7D62E5E9EE2AEFC8940F4 /* NewGameSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGameSheet.swift; sourceTree = "<group>"; };
@@ -236,6 +238,7 @@
9A4B7C6A8A23C6E4CCEC759F /* BundledBrowseView.swift */,
C0CAA5E17BD406AFEEF96196 /* CalendarDayCell.swift */,
F8E50E7BA98C88B4CAB39DC1 /* CellView.swift */,
+ E9BD3F7EAFD344D8E10E8C3B /* ClueList.swift */,
38DDAD9D6470A894C3FD6F90 /* GameListView.swift */,
D9BB7D9759D27F7BA6734FDE /* GridThumbnailView.swift */,
CAB4BB9E160C3A59C653E7A9 /* GridView.swift */,
@@ -399,6 +402,7 @@
C944A5BD871C6ECC64DE8A5B /* CalendarDayCell.swift in Sources */,
6BE7E91158F4DF1F71247C6D /* CellMark.swift in Sources */,
CFCA3C2C3CF6D88AE844D7AD /* CellView.swift in Sources */,
+ B94919176DEC6EC31637B037 /* ClueList.swift in Sources */,
DE9E4FAB098731A650F2D306 /* CrossmateApp.swift in Sources */,
C30C0C4E54E4209A22843872 /* CrossmateModel.xcdatamodeld in Sources */,
978F91DBAE94BC5DA1D94705 /* DriveMonitor.swift in Sources */,
diff --git a/Crossmate/Models/PlayerSession.swift b/Crossmate/Models/PlayerSession.swift
@@ -80,6 +80,13 @@ final class PlayerSession {
}
}
+ func selectClue(direction: Puzzle.Direction, number: Int) {
+ guard let cell = puzzle.cell(numbered: number) else { return }
+ self.direction = direction
+ selectedRow = cell.row
+ selectedCol = cell.col
+ }
+
// MARK: - Clue navigation
func goToNextClue() {
diff --git a/Crossmate/Views/ClueList.swift b/Crossmate/Views/ClueList.swift
@@ -0,0 +1,76 @@
+import SwiftUI
+
+struct ClueList: View {
+ @Bindable var session: PlayerSession
+ @Environment(PlayerPreferences.self) private var preferences
+ @Environment(\.dismiss) private var dismiss
+
+ var body: some View {
+ let current = session.currentClue()
+ let currentDirection = session.direction
+ let currentID = current.map { rowID(direction: currentDirection, number: $0.number) }
+
+ NavigationStack {
+ ScrollViewReader { proxy in
+ List {
+ Section("Across") {
+ ForEach(session.puzzle.acrossClues) { clue in
+ row(for: clue, direction: .across, current: current, currentDirection: currentDirection)
+ .id(rowID(direction: .across, number: clue.number))
+ }
+ }
+ Section("Down") {
+ ForEach(session.puzzle.downClues) { clue in
+ row(for: clue, direction: .down, current: current, currentDirection: currentDirection)
+ .id(rowID(direction: .down, number: clue.number))
+ }
+ }
+ }
+ .listStyle(.insetGrouped)
+ .onAppear {
+ guard let currentID else { return }
+ proxy.scrollTo(currentID, anchor: .center)
+ }
+ }
+ .navigationTitle("Clues")
+ .navigationBarTitleDisplayMode(.inline)
+ .toolbar {
+ ToolbarItem(placement: .topBarTrailing) {
+ Button("Done") { dismiss() }
+ }
+ }
+ }
+ }
+
+ private func rowID(direction: Puzzle.Direction, number: Int) -> String {
+ "\(direction == .across ? "A" : "D")-\(number)"
+ }
+
+ @ViewBuilder
+ private func row(
+ for clue: Puzzle.Clue,
+ direction: Puzzle.Direction,
+ current: Puzzle.Clue?,
+ currentDirection: Puzzle.Direction
+ ) -> some View {
+ let isCurrent = current?.number == clue.number && currentDirection == direction
+ Button {
+ session.selectClue(direction: direction, number: clue.number)
+ dismiss()
+ } label: {
+ HStack(alignment: .firstTextBaseline, spacing: 10) {
+ Text("\(clue.number)")
+ .font(.subheadline.weight(.semibold))
+ .foregroundStyle(.secondary)
+ .frame(minWidth: 28, alignment: .trailing)
+ Text(clue.text)
+ .font(.body)
+ .foregroundStyle(.primary)
+ .frame(maxWidth: .infinity, alignment: .leading)
+ }
+ .contentShape(Rectangle())
+ }
+ .buttonStyle(.plain)
+ .listRowBackground(isCurrent ? preferences.color.highlightFill : Color.clear)
+ }
+}
diff --git a/Crossmate/Views/PuzzleView.swift b/Crossmate/Views/PuzzleView.swift
@@ -213,6 +213,7 @@ private struct ClueBar: View {
@Bindable var session: PlayerSession
@Environment(PlayerPreferences.self) private var preferences
@State private var slideEdge: Edge = .trailing
+ @State private var isShowingClueList = false
private var playerColor: PlayerColor { preferences.color }
@@ -254,7 +255,7 @@ private struct ClueBar: View {
}
.contentShape(Rectangle())
.onTapGesture {
- session.toggleDirection()
+ isShowingClueList = true
}
Button {
@@ -271,6 +272,11 @@ private struct ClueBar: View {
.padding(.vertical, 12)
.background(playerColor.highlightFill)
.animation(.smooth(duration: 0.22), value: currentKey)
+ .sheet(isPresented: $isShowingClueList) {
+ ClueList(session: session)
+ .presentationDetents([.medium, .large])
+ .presentationDragIndicator(.visible)
+ }
}
private func label(for clue: Puzzle.Clue?) -> String {