crossmate

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

commit 86820a2aa18c3039a80113013f43a638e7c2758d
parent 8062849feb3bcefef09c9dcf3122da39e22b1755
Author: Michael Camilleri <[email protected]>
Date:   Thu,  7 May 2026 18:49:13 +0900

Move debug puzzles to new top-level directory

Diffstat:
MCrossmate.xcodeproj/project.pbxproj | 24++++--------------------
MCrossmate/Models/PuzzleCatalog.swift | 14+++++++++++---
MCrossmate/Models/PuzzleSource.swift | 2++
MCrossmate/Views/BundledBrowseView.swift | 19+++++++++++++++++++
MCrossmate/Views/NewGameSheet.swift | 13++++++++++++-
MCrossmate/Views/SettingsView.swift | 57++++++++++++++++++++++++++++++++++-----------------------
RCrossmate/Resources/garden.xd -> Puzzles/Debug/garden.xd | 0
RCrossmate/Resources/morning.xd -> Puzzles/Debug/morning.xd | 0
RCrossmate/Resources/sample.xd -> Puzzles/Debug/sample.xd | 0
Mproject.yml | 3+++
10 files changed, 85 insertions(+), 47 deletions(-)

diff --git a/Crossmate.xcodeproj/project.pbxproj b/Crossmate.xcodeproj/project.pbxproj @@ -51,19 +51,17 @@ 82918A74836E5076CBFA1592 /* SyncEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73DDDED719CFFDD6035C3B48 /* SyncEngine.swift */; }; 8478F0BC0CA624C78DC0A3B5 /* ImportedBrowseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87B1BB8AB6309AF111671CB5 /* ImportedBrowseView.swift */; }; 849970A21D62C34EC382A27E /* GameShareItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5ABB557BA10CBE9909056882 /* GameShareItem.swift */; }; + 8B356C953DA0FAF149C3391A /* Puzzles in Resources */ = {isa = PBXBuildFile; fileRef = BA67C509B467132D1B7510A4 /* Puzzles */; }; 8F5CB2F94E083D06D7E04280 /* PlayerSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 20B331CC55827FEF3420ABCE /* PlayerSession.swift */; }; 9789150602A3321D2E1E7E81 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0BF60C84D92A9024AC1A53FC /* Media.xcassets */; }; 978F91DBAE94BC5DA1D94705 /* DriveMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70AD1A006E6D03E4429E3BF0 /* DriveMonitor.swift */; }; - 97D77230A98330DCB757FA81 /* sample.xd in Resources */ = {isa = PBXBuildFile; fileRef = 5C63A148D98E2D37EABF2CF5 /* sample.xd */; }; 98F8FBF324ED00D53FEBB1DB /* Game.swift in Sources */ = {isa = PBXBuildFile; fileRef = 465F2BB469EFE84CF3733398 /* Game.swift */; }; 9CB8808193A4A106D721D767 /* XDFileType.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC61E2582D94B1E6EC67136 /* XDFileType.swift */; }; A98382E7659991FAF0F4ED0A /* AuthorIdentityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 457B06DBFDC358D213A7CE54 /* AuthorIdentityTests.swift */; }; AA28425BD26F72A9E2B58742 /* BundledBrowseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4B7C6A8A23C6E4CCEC759F /* BundledBrowseView.swift */; }; AA38A51862FC0AB8F7D34899 /* NYTToXDConverterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C54223FED97577A593B7964E /* NYTToXDConverterTests.swift */; }; - AA992F67F509EC8EFDDFC7CB /* morning.xd in Resources */ = {isa = PBXBuildFile; fileRef = 0B73A791FD061430AE286E11 /* morning.xd */; }; AB05765D2C3F4841026344E5 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4AF633D73818BD59F759FAC4 /* AboutView.swift */; }; 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 */; }; BCB9A4D5E06EE5006186465D /* ShareController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C74683332956B0D1CA37589 /* ShareController.swift */; }; @@ -108,7 +106,6 @@ /* Begin PBXFileReference section */ 07C57DEE9E0EFA684D8BD00B /* NYTLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NYTLoginView.swift; sourceTree = "<group>"; }; - 0B73A791FD061430AE286E11 /* morning.xd */ = {isa = PBXFileReference; path = morning.xd; sourceTree = "<group>"; }; 0BF60C84D92A9024AC1A53FC /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = "<group>"; }; 0C0A7348E1283E7CD2486E2A /* RecordSerializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordSerializer.swift; sourceTree = "<group>"; }; 14F2AC5C3B50F4178859E9AC /* CrossmateApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrossmateApp.swift; sourceTree = "<group>"; }; @@ -135,12 +132,10 @@ 4AF633D73818BD59F759FAC4 /* AboutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = "<group>"; }; 4DC7784917397BCD6B8D679D /* PuzzleCatalog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PuzzleCatalog.swift; sourceTree = "<group>"; }; 4F4EBC0F07FF815274C028CA /* XDAcceptTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDAcceptTests.swift; sourceTree = "<group>"; }; - 50992CDA4082429EBB17F65C /* garden.xd */ = {isa = PBXFileReference; path = garden.xd; sourceTree = "<group>"; }; 52B8E26067849A63758DDEA4 /* MoveBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveBuffer.swift; sourceTree = "<group>"; }; 543481AA9FA32BF14076EB1C /* MoveLogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveLogTests.swift; sourceTree = "<group>"; }; 56BC76178319D0D669CD50FF /* CloudService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudService.swift; sourceTree = "<group>"; }; 5ABB557BA10CBE9909056882 /* GameShareItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameShareItem.swift; sourceTree = "<group>"; }; - 5C63A148D98E2D37EABF2CF5 /* sample.xd */ = {isa = PBXFileReference; path = sample.xd; sourceTree = "<group>"; }; 5C74683332956B0D1CA37589 /* ShareController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareController.swift; sourceTree = "<group>"; }; 64C8064F04FC6177D987ACA2 /* Puzzle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Puzzle.swift; sourceTree = "<group>"; }; 666155B0C17A8CED11C45A80 /* GamePlayerColorStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GamePlayerColorStore.swift; sourceTree = "<group>"; }; @@ -174,6 +169,7 @@ B369788E0FEA0DCE1B125816 /* PUZToXDConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PUZToXDConverter.swift; sourceTree = "<group>"; }; B689A7138429641E61E9E558 /* Crossmate.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Crossmate.app; sourceTree = BUILT_PRODUCTS_DIR; }; B9031A1574C21866940F6A2C /* XD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XD.swift; sourceTree = "<group>"; }; + BA67C509B467132D1B7510A4 /* Puzzles */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Puzzles; sourceTree = SOURCE_ROOT; }; BAC1B64755AE15CF45350DBB /* MoveBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoveBufferTests.swift; sourceTree = "<group>"; }; BF6F111BE8750697C4BC7A17 /* NYTToXDConverter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NYTToXDConverter.swift; sourceTree = "<group>"; }; BFC1C59A30FB2571598273E4 /* GameMutatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameMutatorTests.swift; sourceTree = "<group>"; }; @@ -303,7 +299,6 @@ 0BF60C84D92A9024AC1A53FC /* Media.xcassets */, 41DB2417FF67A47FE6890256 /* Models */, 565DBAFC8DB2589B3F0AF90E /* Persistence */, - F53443E4827221C62DB7AA36 /* Resources */, D8F0E3376B2616B4E917129C /* Services */, 074C2962E79CAE6C0EA6431A /* Sync */, 84445EA9CACB6AAAEDE6965F /* Views */, @@ -368,6 +363,7 @@ C5342A31D253372339517EEE = { isa = PBXGroup; children = ( + BA67C509B467132D1B7510A4 /* Puzzles */, 5770CE69DB2B0B7462FACE53 /* Crossmate */, 6F470E54D9E6E99FCEA893D1 /* Generated */, 9BF7383FE2AB07F12434C013 /* Shared */, @@ -396,16 +392,6 @@ path = Services; sourceTree = "<group>"; }; - F53443E4827221C62DB7AA36 /* Resources */ = { - isa = PBXGroup; - children = ( - 50992CDA4082429EBB17F65C /* garden.xd */, - 0B73A791FD061430AE286E11 /* morning.xd */, - 5C63A148D98E2D37EABF2CF5 /* sample.xd */, - ); - path = Resources; - sourceTree = "<group>"; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -490,9 +476,7 @@ buildActionMask = 2147483647; files = ( 9789150602A3321D2E1E7E81 /* Media.xcassets in Resources */, - B42454D72FAA219D60DEA334 /* garden.xd in Resources */, - AA992F67F509EC8EFDDFC7CB /* morning.xd in Resources */, - 97D77230A98330DCB757FA81 /* sample.xd in Resources */, + 8B356C953DA0FAF149C3391A /* Puzzles in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Crossmate/Models/PuzzleCatalog.swift b/Crossmate/Models/PuzzleCatalog.swift @@ -1,7 +1,7 @@ import Foundation -/// Knows about all bundled `.xd` puzzle resources. Used by the new-game -/// picker to list available puzzles. +/// Knows about packaged `.xd` puzzle resources. Used by the new-game picker +/// to list available puzzles. struct PuzzleCatalog { struct Entry: Identifiable { let id: String // resource name (e.g. "sample") @@ -10,7 +10,15 @@ struct PuzzleCatalog { } static func bundledPuzzles() -> [Entry] { - guard let urls = Bundle.main.urls(forResourcesWithExtension: "xd", subdirectory: nil) else { + puzzles(in: "Puzzles/Bundled") + } + + static func debugPuzzles() -> [Entry] { + puzzles(in: "Puzzles/Debug") + } + + private static func puzzles(in subdirectory: String) -> [Entry] { + guard let urls = Bundle.main.urls(forResourcesWithExtension: "xd", subdirectory: subdirectory) else { return [] } return urls.compactMap { url in diff --git a/Crossmate/Models/PuzzleSource.swift b/Crossmate/Models/PuzzleSource.swift @@ -2,6 +2,7 @@ import Foundation enum PuzzleSource: String, CaseIterable, Identifiable { case bundled + case debug case imported case nyt @@ -10,6 +11,7 @@ enum PuzzleSource: String, CaseIterable, Identifiable { var title: String { switch self { case .bundled: "Bundled" + case .debug: "Debug" case .imported: "Imported" case .nyt: "NYT" } diff --git a/Crossmate/Views/BundledBrowseView.swift b/Crossmate/Views/BundledBrowseView.swift @@ -18,3 +18,22 @@ struct BundledBrowseView: View { } } } + +struct DebugBrowseView: View { + let onSelected: (String) -> Void + + private var puzzles: [PuzzleCatalog.Entry] { + PuzzleCatalog.debugPuzzles() + } + + var body: some View { + List(puzzles) { entry in + Button { + onSelected(entry.source) + } label: { + Text(entry.title) + .foregroundStyle(.primary) + } + } + } +} diff --git a/Crossmate/Views/NewGameSheet.swift b/Crossmate/Views/NewGameSheet.swift @@ -6,12 +6,21 @@ struct NewGameSheet: View { @Environment(\.dismiss) private var dismiss @Environment(NYTAuthService.self) private var nytAuth @AppStorage("lastPuzzleSource") private var storedSource: PuzzleSource = .bundled + @AppStorage("debugMode") private var debugMode = false @State private var selection: PuzzleSource = .bundled @State private var duplicateSource: String? @State private var createError: String? private var availableSources: [PuzzleSource] { - nytAuth.isSignedIn ? [.bundled, .imported, .nyt] : [.bundled, .imported] + var sources: [PuzzleSource] = [.bundled] + if debugMode { + sources.append(.debug) + } + sources.append(.imported) + if nytAuth.isSignedIn { + sources.append(.nyt) + } + return sources } var body: some View { @@ -29,6 +38,8 @@ struct NewGameSheet: View { switch selection { case .bundled: BundledBrowseView(onSelected: handleSelected) + case .debug: + DebugBrowseView(onSelected: handleSelected) case .imported: ImportedBrowseView(onSelected: handleSelected) case .nyt: diff --git a/Crossmate/Views/SettingsView.swift b/Crossmate/Views/SettingsView.swift @@ -6,9 +6,11 @@ struct SettingsView: View { @Environment(\.dismiss) private var dismiss @Environment(\.resetDatabase) private var resetDatabase + @AppStorage("debugMode") private var debugMode = false @State private var showingNYTLogin = false @State private var showResetConfirmation = false @State private var externalSource: ExternalSource? + @State private var easterEggTaps = 0 var body: some View { @Bindable var preferences = preferences @@ -35,32 +37,34 @@ struct SettingsView: View { } } - Section("Debugging") { - Toggle("Enable iCloud Sync", isOn: $preferences.isICloudSyncEnabled) + if debugMode { + Section("Debugging") { + Toggle("Enable iCloud Sync", isOn: $preferences.isICloudSyncEnabled) - NavigationLink("iCloud Diagnostics") { - DiagnosticsView() - } - NavigationLink("Share Diagnostics") { - ShareDiagnosticsView() - } - NavigationLink("Record Editor") { - RecordEditorView() - } + NavigationLink("iCloud Diagnostics") { + DiagnosticsView() + } + NavigationLink("Share Diagnostics") { + ShareDiagnosticsView() + } + NavigationLink("Record Editor") { + RecordEditorView() + } - Button("Reset Database", role: .destructive) { - showResetConfirmation = true - } - .alert( - "Delete all games?", - isPresented: $showResetConfirmation - ) { - Button("Delete All Games", role: .destructive) { - Task { await resetDatabase?() } + Button("Reset Database", role: .destructive) { + showResetConfirmation = true + } + .alert( + "Delete all games?", + isPresented: $showResetConfirmation + ) { + Button("Delete All Games", role: .destructive) { + Task { await resetDatabase?() } + } + Button("Cancel", role: .cancel) {} + } message: { + Text("This removes every game and clears the sync state on this device. Games on other devices and in iCloud are not affected.") } - Button("Cancel", role: .cancel) {} - } message: { - Text("This removes every game and clears the sync state on this device. Games on other devices and in iCloud are not affected.") } } @@ -73,6 +77,13 @@ struct SettingsView: View { .frame(maxWidth: .infinity) .multilineTextAlignment(.center) .padding(.top, 24) + .onTapGesture { + easterEggTaps += 1 + if easterEggTaps >= 4 { + debugMode.toggle() + easterEggTaps = 0 + } + } } } .navigationTitle("Settings") diff --git a/Crossmate/Resources/garden.xd b/Puzzles/Debug/garden.xd diff --git a/Crossmate/Resources/morning.xd b/Puzzles/Debug/morning.xd diff --git a/Crossmate/Resources/sample.xd b/Puzzles/Debug/sample.xd diff --git a/project.yml b/project.yml @@ -26,6 +26,9 @@ targets: sources: - Crossmate - Shared + - path: Puzzles + type: folder + buildPhase: resources info: path: Crossmate/Info.plist properties: