diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ac7f6f2..53ab920 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,17 @@ on: - feature/** jobs: + content-validation: + name: Validate bundled content + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Validate bundled content + run: python3 Scripts/validate_content.py + snapshot-tests: name: Snapshot tests runs-on: macos-26 @@ -58,3 +69,78 @@ jobs: -scheme 'Klepon Mac' \ -destination 'platform=macOS' \ CODE_SIGNING_ALLOWED=NO + + tvos-build: + name: tvOS app build + runs-on: macos-26 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install XcodeGen + run: brew install xcodegen + + - name: Generate project + run: xcodegen generate + + - name: Resolve Swift packages + run: xcodebuild -project Klepon.xcodeproj -scheme 'Klepon TV' -resolvePackageDependencies + + - name: Build tvOS app + run: | + xcodebuild build \ + -project Klepon.xcodeproj \ + -scheme 'Klepon TV' \ + -destination 'generic/platform=tvOS Simulator' \ + CODE_SIGNING_ALLOWED=NO + + visionos-build: + name: visionOS app build + runs-on: macos-26 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install XcodeGen + run: brew install xcodegen + + - name: Generate project + run: xcodegen generate + + - name: Resolve Swift packages + run: xcodebuild -project Klepon.xcodeproj -scheme 'Klepon Vision' -resolvePackageDependencies + + - name: Build visionOS app + run: | + xcodebuild build \ + -project Klepon.xcodeproj \ + -scheme 'Klepon Vision' \ + -destination 'generic/platform=visionOS Simulator' \ + CODE_SIGNING_ALLOWED=NO + + watchos-build: + name: watchOS app build + runs-on: macos-26 + + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Install XcodeGen + run: brew install xcodegen + + - name: Generate project + run: xcodegen generate + + - name: Resolve Swift packages + run: xcodebuild -project Klepon.xcodeproj -scheme 'Klepon Watch' -resolvePackageDependencies + + - name: Build watchOS app + run: | + xcodebuild build \ + -project Klepon.xcodeproj \ + -scheme 'Klepon Watch' \ + -destination 'generic/platform=watchOS Simulator' \ + CODE_SIGNING_ALLOWED=NO diff --git a/.gitignore b/.gitignore index a4c4cf0..e9558cd 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist # Xcode build artifacts build/ DerivedData/ +.worktrees/ *.moved-aside *.xccheckout *.xcscmblueprint diff --git a/AppStore/README.md b/AppStore/README.md index f9553d0..240cc13 100644 --- a/AppStore/README.md +++ b/AppStore/README.md @@ -6,12 +6,18 @@ This folder keeps the App Store-facing copy and planning docs for Klepon. - `metadata/en-US/` — English metadata copy for the iPhone app - `metadata-macos/en-US/` — English metadata copy for the macOS app +- `metadata-tvos/en-US/` — English metadata copy for the tvOS app +- `metadata-visionos/en-US/` — English metadata copy for the visionOS app - `screenshots/iphone/` — iPhone screenshot exports - `screenshots/macos/` — macOS screenshot exports +- `screenshots/tvos/` — tvOS screenshot exports +- `screenshots/visionos/` — visionOS screenshot exports - `screenshots/watch/` — watchOS screenshot exports - `screenshots/README.md` — screenshot shot list and titles - `testflight-checklist.md` — pre-TestFlight checklist - `release-checklist.md` — broader release checklist - `macos-appstore-notes.md` — macOS-specific submission notes +- `tvos-appstore-notes.md` — tvOS-specific submission notes +- `visionos-appstore-notes.md` — visionOS-specific submission notes The screenshot folders are intentionally empty in Git until real exports are added. diff --git a/AppStore/macos-appstore-notes.md b/AppStore/macos-appstore-notes.md index d58bfe6..253c958 100644 --- a/AppStore/macos-appstore-notes.md +++ b/AppStore/macos-appstore-notes.md @@ -19,8 +19,9 @@ Required keys for the shipping App Store build: - `com.apple.security.app-sandbox = true` - `com.apple.security.network.client = true` - `com.apple.security.application-groups = group.com.ondeinference.apps` +- `ITSAppUsesNonExemptEncryption = false` in `KleponMac/Info.plist` -The sandbox entitlement is mandatory for App Store validation. The network entitlement is needed because Onde can download the optional on-device guide. The app group keeps local model storage aligned with the other Apple targets. +The sandbox entitlement is mandatory for App Store validation. The network entitlement is needed because Onde can download the optional on-device guide. `ITSAppUsesNonExemptEncryption` is an Info.plist declaration, not an entitlement. The app group keeps local model storage aligned with the other Apple targets. ## Xcode project notes diff --git a/AppStore/metadata-tvos/en-US/description.txt b/AppStore/metadata-tvos/en-US/description.txt new file mode 100644 index 0000000..413b9d9 --- /dev/null +++ b/AppStore/metadata-tvos/en-US/description.txt @@ -0,0 +1,16 @@ +Discover Indonesian food on Apple TV with warmth, clarity, and privacy. + +Klepon helps you explore dishes, ingredients, and food traditions from across Indonesia in a simple living-room experience that feels calm, readable, and easy to browse. + +Browse curated guides to iconic dishes, regional favorites, sweet treats, and essential ingredients. Learn what a dish is, what it tastes like, what usually goes into it, and what to try next. + +When you want a little more context, Klepon can answer short follow-up questions privately on device with an optional local guide. + +What you can do in Klepon: +- Explore curated Indonesian food guides +- Learn dishes, ingredients, and traditions +- Search quickly when you spot an unfamiliar name +- Save dishes you want to remember +- Ask short follow-up questions privately on device + +Klepon is for curious eaters, travelers, diaspora families, and anyone who wants a more thoughtful way to discover Indonesian food. diff --git a/AppStore/metadata-tvos/en-US/keywords.txt b/AppStore/metadata-tvos/en-US/keywords.txt new file mode 100644 index 0000000..cd69355 --- /dev/null +++ b/AppStore/metadata-tvos/en-US/keywords.txt @@ -0,0 +1 @@ +indonesian food,dishes,ingredients,cuisine,guide,travel diff --git a/AppStore/metadata-tvos/en-US/name.txt b/AppStore/metadata-tvos/en-US/name.txt new file mode 100644 index 0000000..9ac5fc8 --- /dev/null +++ b/AppStore/metadata-tvos/en-US/name.txt @@ -0,0 +1 @@ +Klepon: Taste of Indonesia diff --git a/AppStore/metadata-tvos/en-US/promotional_text.txt b/AppStore/metadata-tvos/en-US/promotional_text.txt new file mode 100644 index 0000000..876383e --- /dev/null +++ b/AppStore/metadata-tvos/en-US/promotional_text.txt @@ -0,0 +1 @@ +A calm Apple TV guide to Indonesian dishes, ingredients, and food traditions. diff --git a/AppStore/metadata-tvos/en-US/review_notes.txt b/AppStore/metadata-tvos/en-US/review_notes.txt new file mode 100644 index 0000000..84e4436 --- /dev/null +++ b/AppStore/metadata-tvos/en-US/review_notes.txt @@ -0,0 +1,5 @@ +This tvOS app is a browse-first guide and is fully usable without enabling the private guide. + +The private guide is optional. If the user chooses to enable it, the app downloads a local on-device model and uses it for short follow-up answers inside the guide. + +The tvOS target uses the same main app bundle identifier as the iPhone app and uses the shared app group container for local model storage when available. diff --git a/AppStore/metadata-tvos/en-US/subtitle.txt b/AppStore/metadata-tvos/en-US/subtitle.txt new file mode 100644 index 0000000..8752f41 --- /dev/null +++ b/AppStore/metadata-tvos/en-US/subtitle.txt @@ -0,0 +1 @@ +Indonesian food guide diff --git a/AppStore/metadata-visionos/en-US/description.txt b/AppStore/metadata-visionos/en-US/description.txt new file mode 100644 index 0000000..8afeed4 --- /dev/null +++ b/AppStore/metadata-visionos/en-US/description.txt @@ -0,0 +1,16 @@ +Discover Indonesian food on Apple Vision Pro with warmth, clarity, and privacy. + +Klepon helps you explore dishes, ingredients, and food traditions from across Indonesia in a simple windowed app that feels calm, readable, and easy to browse. + +Browse curated guides to iconic dishes, regional favorites, sweet treats, and essential ingredients. Learn what a dish is, what it tastes like, what usually goes into it, and what to try next. + +When you want a little more context, Klepon can answer short follow-up questions privately on device with an optional local guide. + +What you can do in Klepon: +- Explore curated Indonesian food guides +- Learn dishes, ingredients, and traditions +- Search quickly when you spot an unfamiliar name +- Save dishes you want to remember +- Ask short follow-up questions privately on device + +Klepon is for curious eaters, travelers, diaspora families, and anyone who wants a more thoughtful way to discover Indonesian food. diff --git a/AppStore/metadata-visionos/en-US/keywords.txt b/AppStore/metadata-visionos/en-US/keywords.txt new file mode 100644 index 0000000..cd69355 --- /dev/null +++ b/AppStore/metadata-visionos/en-US/keywords.txt @@ -0,0 +1 @@ +indonesian food,dishes,ingredients,cuisine,guide,travel diff --git a/AppStore/metadata-visionos/en-US/name.txt b/AppStore/metadata-visionos/en-US/name.txt new file mode 100644 index 0000000..9ac5fc8 --- /dev/null +++ b/AppStore/metadata-visionos/en-US/name.txt @@ -0,0 +1 @@ +Klepon: Taste of Indonesia diff --git a/AppStore/metadata-visionos/en-US/promotional_text.txt b/AppStore/metadata-visionos/en-US/promotional_text.txt new file mode 100644 index 0000000..1e679dc --- /dev/null +++ b/AppStore/metadata-visionos/en-US/promotional_text.txt @@ -0,0 +1 @@ +A calm windowed guide to Indonesian dishes, ingredients, and food traditions on Apple Vision Pro. diff --git a/AppStore/metadata-visionos/en-US/review_notes.txt b/AppStore/metadata-visionos/en-US/review_notes.txt new file mode 100644 index 0000000..bc148ae --- /dev/null +++ b/AppStore/metadata-visionos/en-US/review_notes.txt @@ -0,0 +1,5 @@ +This visionOS app is a simple windowed guide and is fully usable without enabling the private guide. + +The private guide is optional. If the user chooses to enable it, the app downloads a local on-device model and uses it for short follow-up answers inside the guide. + +The visionOS target uses the same main app bundle identifier as the iPhone app and uses the shared app group container for local model storage when available. diff --git a/AppStore/metadata-visionos/en-US/subtitle.txt b/AppStore/metadata-visionos/en-US/subtitle.txt new file mode 100644 index 0000000..8752f41 --- /dev/null +++ b/AppStore/metadata-visionos/en-US/subtitle.txt @@ -0,0 +1 @@ +Indonesian food guide diff --git a/AppStore/release-checklist.md b/AppStore/release-checklist.md index e147b77..28ce0a8 100644 --- a/AppStore/release-checklist.md +++ b/AppStore/release-checklist.md @@ -4,6 +4,8 @@ - [ ] iPhone app builds cleanly - [ ] macOS app builds cleanly +- [ ] tvOS app builds cleanly +- [ ] visionOS app builds cleanly - [ ] watchOS companion builds cleanly - [ ] README is current - [ ] LICENSE files are present @@ -17,21 +19,28 @@ - [ ] Ask flow works on device with acceptable latency - [ ] Watch app content is readable and useful at a glance - [ ] macOS window flows feel native and polished enough for v1 +- [ ] tvOS focus and remote navigation feel polished enough for v1 +- [ ] visionOS window flows feel polished enough for a simple v1 - [ ] No obvious sample-app copy remains ## Signing and distribution - [ ] iPhone bundle identifier is correct - [ ] macOS bundle identifier is correct +- [ ] tvOS bundle identifier is correct +- [ ] visionOS bundle identifier is correct - [ ] watch bundle identifier is correct - [ ] App Group entitlement is correct for the shipping account - [ ] macOS App Sandbox entitlement is enabled for the shipping build +- [ ] Apple platform Info.plists set `ITSAppUsesNonExemptEncryption = false` - [ ] Provisioning profiles and signing identities are valid ## Final gate - [ ] TestFlight build installed on at least one real iPhone - [ ] macOS archive validates cleanly for App Store submission +- [ ] tvOS archive validates cleanly for App Store submission +- [ ] visionOS archive validates cleanly for App Store submission - [ ] Watch companion installed and tested on at least one real Apple Watch - [ ] Release notes are written - [ ] Git status is clean before tagging diff --git a/AppStore/screenshots/README.md b/AppStore/screenshots/README.md index ea306a6..217e5b5 100644 --- a/AppStore/screenshots/README.md +++ b/AppStore/screenshots/README.md @@ -24,6 +24,24 @@ 3. `Ask privately on device` - Show the ask flow with a grounded answer card and source chips. +## tvOS screenshots + +1. `A calm guide on Apple TV` + - Show the Discover screen with focus on the featured dish and one visible collection rail. +2. `Browse and save from the couch` + - Show a readable Search or Saved view with a realistic shortlist. +3. `Ask privately on device` + - Show the ask flow with a grounded answer card and focus-friendly suggestions. + +## visionOS screenshots + +1. `A calm guide in your space` + - Show the Discover screen in the visionOS window with the featured dish and collections visible. +2. `Search and save dishes naturally` + - Show a readable Search or Saved view with a realistic shortlist. +3. `Ask privately on device` + - Show the ask flow with a grounded answer card and source chips. + ## Apple Watch screenshots 1. `Browse on your wrist` diff --git a/AppStore/screenshots/tvos/.gitkeep b/AppStore/screenshots/tvos/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/AppStore/screenshots/visionos/.gitkeep b/AppStore/screenshots/visionos/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/AppStore/testflight-checklist.md b/AppStore/testflight-checklist.md index 9919340..9fae3a8 100644 --- a/AppStore/testflight-checklist.md +++ b/AppStore/testflight-checklist.md @@ -13,6 +13,8 @@ - [ ] iPhone target builds on simulator - [ ] iPhone target runs on a real device - [ ] macOS target builds on the release Xcode version +- [ ] tvOS target builds on the release Xcode version +- [ ] visionOS target builds on the release Xcode version - [ ] Watch target builds on simulator - [ ] Watch target runs with the paired iPhone app - [ ] Private guide setup succeeds on a real device @@ -38,3 +40,5 @@ - [ ] No local machine junk files are staged - [ ] Bundle identifiers and App Group values are intentional for the release build - [ ] macOS release signing uses the sandboxed entitlements file +- [ ] tvOS release signing uses the shared app identifier and App Group entitlement +- [ ] visionOS release signing uses the shared app identifier and App Group entitlement diff --git a/AppStore/tvos-appstore-notes.md b/AppStore/tvos-appstore-notes.md new file mode 100644 index 0000000..4cc3e71 --- /dev/null +++ b/AppStore/tvos-appstore-notes.md @@ -0,0 +1,62 @@ +# tvOS App Store notes + +## Bundle identifier + +The tvOS target uses the same bundle identifier as the iPhone app: + +- `ai.splitfire.klepon` + +That keeps the main Apple-platform app identity aligned across iPhone, Apple TV, Mac, and visionOS. The watch companion still uses its own watch bundle identifier, which Apple requires for the companion app target. + +## App shape + +The current tvOS target is intentionally simple for v1. + +- It is a focus-friendly SwiftUI app +- It uses tabs and navigation stacks +- It keeps the private guide inside the existing browse-first experience + +This is deliberate. The goal is a clean, stable App Store MVP for Apple TV without overbuilding the first release. + +## Required configuration + +The tvOS target uses: + +- `KleponTV/Info.plist` +- `KleponTV/KleponTV.entitlements` + +Important settings: + +- `ITSAppUsesNonExemptEncryption = false` +- `com.apple.security.application-groups = group.com.ondeinference.apps` + +`ITSAppUsesNonExemptEncryption` is an Info.plist declaration, not an entitlement. The App Group entitlement keeps local model storage aligned with the other Apple targets. + +## Build and archive + +Generate the project: + +```/dev/null/sh#L1-1 +xcodegen generate +``` + +Build the tvOS target locally: + +```/dev/null/sh#L1-1 +xcodebuild build -project Klepon.xcodeproj -scheme 'Klepon TV' -destination 'generic/platform=tvOS Simulator' +``` + +Archive the tvOS target: + +```/dev/null/sh#L1-1 +xcodebuild archive -project Klepon.xcodeproj -scheme 'Klepon TV' -destination 'generic/platform=tvOS' -archivePath build/KleponTV.xcarchive +``` + +## Submission checklist + +Before upload, confirm: + +- the signing profile is for `ai.splitfire.klepon` +- the App Group capability exists for the shipping team +- the app feels clean and navigable with the Siri Remote focus model +- tvOS screenshots and metadata are ready in `AppStore/screenshots/tvos/` and `AppStore/metadata-tvos/en-US/` diff --git a/AppStore/visionos-appstore-notes.md b/AppStore/visionos-appstore-notes.md new file mode 100644 index 0000000..463ccf1 --- /dev/null +++ b/AppStore/visionos-appstore-notes.md @@ -0,0 +1,62 @@ +# visionOS App Store notes + +## Bundle identifier + +The visionOS target uses the same bundle identifier as the iPhone app: + +- `ai.splitfire.klepon` + +That keeps the main Apple-platform app identity aligned across iPhone, Mac, and visionOS. The watch companion still uses its own watch bundle identifier, which Apple requires for the companion app target. + +## App shape + +The current visionOS target is intentionally simple for v1. + +- It is a windowed SwiftUI app +- It does not use immersive spaces +- It reuses the shared browse-first Klepon experience + +This is deliberate. The goal is a clean, stable App Store MVP that showcases Onde on Apple Vision Pro without forcing a spatial-first redesign yet. + +## Required configuration + +The visionOS target uses: + +- `KleponVision/Info.plist` +- `KleponVision/KleponVision.entitlements` + +Important settings: + +- `ITSAppUsesNonExemptEncryption = false` +- `com.apple.security.application-groups = group.com.ondeinference.apps` + +`ITSAppUsesNonExemptEncryption` is an Info.plist declaration, not an entitlement. The App Group entitlement keeps local model storage aligned with the other Apple targets. + +## Build and archive + +Generate the project: + +```/dev/null/sh#L1-1 +xcodegen generate +``` + +Build the visionOS target locally: + +```/dev/null/sh#L1-1 +xcodebuild build -project Klepon.xcodeproj -scheme 'Klepon Vision' -destination 'generic/platform=visionOS Simulator' +``` + +Archive the visionOS target: + +```/dev/null/sh#L1-1 +xcodebuild archive -project Klepon.xcodeproj -scheme 'Klepon Vision' -destination 'generic/platform=visionOS' -archivePath build/KleponVision.xcarchive +``` + +## Submission checklist + +Before upload, confirm: + +- the signing profile is for `ai.splitfire.klepon` +- the App Group capability exists for the shipping team +- the app opens as a clean windowed experience on Apple Vision Pro +- visionOS screenshots and metadata are ready in `AppStore/screenshots/visionos/` and `AppStore/metadata-visionos/en-US/` diff --git a/Klepon.xcodeproj/project.pbxproj b/Klepon.xcodeproj/project.pbxproj index 16fd94d..b828586 100644 --- a/Klepon.xcodeproj/project.pbxproj +++ b/Klepon.xcodeproj/project.pbxproj @@ -10,88 +10,158 @@ 019B658F26ABE2706231EC9E /* GuideEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A0935AE4B398B281758834 /* GuideEntry.swift */; }; 032AFB30CAB4E8A31CDC897D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E886BAB66E828950DBED52 /* SearchView.swift */; }; 03D16A2386EB68FED8D92D52 /* GuideEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A0935AE4B398B281758834 /* GuideEntry.swift */; }; + 0481C8A85FED1E35C3734816 /* AppRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C82874C90A5FAA32D1B52EB7 /* AppRootView.swift */; }; 05BF0404B7030EDCD3D8C357 /* GuideArtworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2850226B4B05FF758C1F93 /* GuideArtworkView.swift */; }; 0709F0B92923D97F6859275A /* SearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6235AD6792F25476E2A508F0 /* SearchService.swift */; }; 0977CD6BFFDBDBE3B7795924 /* collections.json in Resources */ = {isa = PBXBuildFile; fileRef = 1FDDB0F88FF430ABD679AD89 /* collections.json */; }; + 0B94C64018A573AD679106E5 /* KleponCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 868C58098969278A542893F3 /* KleponCard.swift */; }; 0CE2632DF6FB02163D55BB65 /* Klepon Watch.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 9E76510CC840CE068B0FC381 /* Klepon Watch.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 0D685E35EE50C926DC6FBE4C /* KleponChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83C4F3C8688072191AA16C6 /* KleponChip.swift */; }; + 0E350E68B43067698FE3B05C /* KleponChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83C4F3C8688072191AA16C6 /* KleponChip.swift */; }; 0E69E79C5FADF1633FE3D63F /* KleponColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C675ABBBA25BBA25A8DE29BE /* KleponColor.swift */; }; 14B7CE6E6A0EB12B709F7B55 /* KleponTypography.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD520C870387E82E9A1533C2 /* KleponTypography.swift */; }; + 14C796CE6777B6013910831C /* SavedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AA2E425D5454602F78D628 /* SavedView.swift */; }; + 16330914DF2EE212C91BC0CA /* KleponCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 868C58098969278A542893F3 /* KleponCard.swift */; }; 1704DDB60B1E23057C0E1094 /* GuideAnswerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3719031080B59898133810F7 /* GuideAnswerService.swift */; }; 1879460ECC35F3386E8E6109 /* KleponChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83C4F3C8688072191AA16C6 /* KleponChip.swift */; }; 19E282A6347C588E17778195 /* DiscoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4545DC84863788D55B94D31A /* DiscoverView.swift */; }; + 1C2B6971843620B876F5B0C3 /* SavedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AA2E425D5454602F78D628 /* SavedView.swift */; }; + 1E042510F7F75D392A4AC6E7 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF291E9C2E66209377138E6 /* AppState.swift */; }; 1E7AAEA8007BFE4ED6FE4892 /* WatchContentLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C77AF5EB4B17D087FA522F6 /* WatchContentLoader.swift */; }; 1F95A454A641C829BD304A89 /* GuideCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17C0DB3BD620AD9F55B7631 /* GuideCollection.swift */; }; 1FF490E985C31BB08DA29AFF /* guide_entries.json in Resources */ = {isa = PBXBuildFile; fileRef = 248F359109A08A60F792C42D /* guide_entries.json */; }; 200AE94E7E7E73B5C83869DD /* FavoritesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC0C4C22A5134DB058E41F4 /* FavoritesStore.swift */; }; + 20CDEBB4B6C8AAE007854102 /* ContentRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2891A1AFBB59928FC5B1E66 /* ContentRepository.swift */; }; 215C4D8A2B2ABF2C3C11C163 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8870F09A11EA6D3F0108DA4D /* SystemConfiguration.framework */; }; 23E22EC56BE48A20BD1229F7 /* AnswerCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02B0EA4B8BA09604D62A62B /* AnswerCard.swift */; }; + 26FA5D01FED96591E78A2293 /* AskSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC6E7DC56DD8E1459EFC892 /* AskSheetView.swift */; }; 2AB57009245E1BABD1156AFA /* GuideDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7541983FF7CE190C0663294D /* GuideDetailView.swift */; }; 2AD05B0B7ED109134F013862 /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E7B5E3D9A1906C2FED3032 /* EmptyStateView.swift */; }; 2F3047F5078C4526133CB848 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13418896CA28250CEEE7592E /* OnboardingView.swift */; }; + 2F74C3B8D4A1A38DEF50E39B /* AskSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC6E7DC56DD8E1459EFC892 /* AskSheetView.swift */; }; 3349FB46691C944112401D6E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E152344B39D1D77F1B3943 /* SettingsView.swift */; }; + 3568D735D790330031DCF8F9 /* SectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC438ECC335175B18778C682 /* SectionHeader.swift */; }; 35B5508906432B3EFE26F4FF /* PlatformViewSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51D6F97615398D511673B98 /* PlatformViewSupport.swift */; }; 35D43509610D0E2CB3DBD2BB /* testEmptyStateView.saved-empty-state.png in Resources */ = {isa = PBXBuildFile; fileRef = 943670F01925DE3065B92636 /* testEmptyStateView.saved-empty-state.png */; }; + 367BECAC66674A040D99C0DF /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13418896CA28250CEEE7592E /* OnboardingView.swift */; }; 36AA193A3B7EF7CB25B6E0BE /* KleponWatchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFBF98118F066D3C7FA14B2 /* KleponWatchApp.swift */; }; 37A723A15D19B7DE3DD92B25 /* PlatformViewSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51D6F97615398D511673B98 /* PlatformViewSupport.swift */; }; 37BD5B2A275426143819B70D /* OndeGuideEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0D53A5F9D5897E15506C20 /* OndeGuideEngine.swift */; }; + 387EE903EFA0D820C646B4DB /* GuideArtworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2850226B4B05FF758C1F93 /* GuideArtworkView.swift */; }; 3A6F67743964C4D04893C321 /* RecentlyViewedStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45070309D2C7058BAA44CD2 /* RecentlyViewedStore.swift */; }; + 3C4C5EEBF766D6E8A5D240DA /* PlatformViewSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51D6F97615398D511673B98 /* PlatformViewSupport.swift */; }; + 3EB900CB86D2B32F8DAB434D /* GuideAnswerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3719031080B59898133810F7 /* GuideAnswerService.swift */; }; 3F999E04650931C2019BDA17 /* KleponColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C675ABBBA25BBA25A8DE29BE /* KleponColor.swift */; }; 41F8E173B82809AF87E1B297 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13418896CA28250CEEE7592E /* OnboardingView.swift */; }; 43ABAA3C08695B7C8FEE7324 /* KleponMetadataRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E1C50BFBE2EF5F37941590 /* KleponMetadataRow.swift */; }; 4609C70D9FB74C55B9DBE832 /* KleponActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DD909BB2EF68103CB46EDA /* KleponActionButton.swift */; }; 478FFEBDAA7729189B429A6D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E886BAB66E828950DBED52 /* SearchView.swift */; }; 47B632AF6BF3E25AF628CC01 /* Onde in Frameworks */ = {isa = PBXBuildFile; productRef = 35BDA28E3F43D5441C5FE932 /* Onde */; }; + 4B6E0CF8EAB6FF1D0160E3E6 /* KleponColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C675ABBBA25BBA25A8DE29BE /* KleponColor.swift */; }; 4D63E231AC9630B2C225DF22 /* WatchHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EDBB4F5D4C98D036AC52E193 /* WatchHomeView.swift */; }; 5692BBCE950885C5C1193DDF /* collections.json in Resources */ = {isa = PBXBuildFile; fileRef = 1FDDB0F88FF430ABD679AD89 /* collections.json */; }; 5808C4539EBB98D822BDE99F /* KleponActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DD909BB2EF68103CB46EDA /* KleponActionButton.swift */; }; + 587F0BB61966B2BB048013DE /* KleponChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = B83C4F3C8688072191AA16C6 /* KleponChip.swift */; }; + 5C3590F8B1A8255C5E175A03 /* PlatformViewSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = F51D6F97615398D511673B98 /* PlatformViewSupport.swift */; }; + 5D3BDDCB5CA8A8DC32E8ABEA /* Onde in Frameworks */ = {isa = PBXBuildFile; productRef = CECDF5959E6652B49DCF1E18 /* Onde */; }; + 6117CD728CC7FF8D6EB0A8C8 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13418896CA28250CEEE7592E /* OnboardingView.swift */; }; 67368394088AA3045EBA14A5 /* OndeEnvironmentBootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B499D3BF7AC80D31907D87F /* OndeEnvironmentBootstrap.swift */; }; + 68479892D13825C6C9977DF9 /* FavoritesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC0C4C22A5134DB058E41F4 /* FavoritesStore.swift */; }; 687418866BCC5554A0F4BFDC /* AnswerCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02B0EA4B8BA09604D62A62B /* AnswerCard.swift */; }; + 6B7AE8B1C6D3AD6F973F5757 /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E7B5E3D9A1906C2FED3032 /* EmptyStateView.swift */; }; + 6FCE6EBC39C7F229A5F52745 /* OndeEnvironmentBootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B499D3BF7AC80D31907D87F /* OndeEnvironmentBootstrap.swift */; }; 72A88C81BE80344130E0EC0C /* GuideAnswerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3719031080B59898133810F7 /* GuideAnswerService.swift */; }; + 74A9016453B9820FC486B014 /* SearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6235AD6792F25476E2A508F0 /* SearchService.swift */; }; + 76EE3B0D899987557B6741DF /* collections.json in Resources */ = {isa = PBXBuildFile; fileRef = 1FDDB0F88FF430ABD679AD89 /* collections.json */; }; + 78142FDCFE7EF00A3D81A067 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E152344B39D1D77F1B3943 /* SettingsView.swift */; }; + 7A9B7E0858E7095E18EC7AC6 /* KleponApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEDE2A1E73EB15970A17A894 /* KleponApp.swift */; }; 7C90532B70C1C4840E49A92B /* GuideDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7541983FF7CE190C0663294D /* GuideDetailView.swift */; }; 7F1E744AAE60BF1FB132D66C /* KleponCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 868C58098969278A542893F3 /* KleponCard.swift */; }; + 7F726FA99DBDC0155E99E098 /* SectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC438ECC335175B18778C682 /* SectionHeader.swift */; }; + 8189E2DF13801C2B7162AD45 /* AnswerCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02B0EA4B8BA09604D62A62B /* AnswerCard.swift */; }; + 819489D60D833AE2AD9185A6 /* RecentSearchStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643D52BCEDF66CC8FE8A1234 /* RecentSearchStore.swift */; }; 8401F9C0FA140DC2CF8BA248 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF291E9C2E66209377138E6 /* AppState.swift */; }; + 841F3B272D0FD82365DE48BD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AF79D8DD27B9CA485140C934 /* Assets.xcassets */; }; + 854C10AE603DCCC521D93BC2 /* ContentRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2891A1AFBB59928FC5B1E66 /* ContentRepository.swift */; }; + 85FC2D60857424B39654DE08 /* RecentSearchStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643D52BCEDF66CC8FE8A1234 /* RecentSearchStore.swift */; }; + 87E259162B03252EA23A32CF /* GuideEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A0935AE4B398B281758834 /* GuideEntry.swift */; }; 8804CFBAD707E1FED63BF214 /* SearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6235AD6792F25476E2A508F0 /* SearchService.swift */; }; 88984B6A8AA504AD200A7F39 /* collections.json in Resources */ = {isa = PBXBuildFile; fileRef = 1FDDB0F88FF430ABD679AD89 /* collections.json */; }; 889CBF2AF84AABF8012EE737 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AF79D8DD27B9CA485140C934 /* Assets.xcassets */; }; 895B5369E9CF1E1B26CDF40E /* KleponTypography.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD520C870387E82E9A1533C2 /* KleponTypography.swift */; }; + 8C6D6EC32870A45E021BF3EB /* KleponMetadataRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E1C50BFBE2EF5F37941590 /* KleponMetadataRow.swift */; }; 8EBCC9FA074785FDD82B02E5 /* KleponApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEDE2A1E73EB15970A17A894 /* KleponApp.swift */; }; + 906E12C00728B15B676A23DB /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF291E9C2E66209377138E6 /* AppState.swift */; }; 910A84B3F28B3F79871FF60E /* SectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC438ECC335175B18778C682 /* SectionHeader.swift */; }; 926AB007EBCD5315EE77E2A6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AF79D8DD27B9CA485140C934 /* Assets.xcassets */; }; + 935A2F7D98CCC0DEA5EA2782 /* AppRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C82874C90A5FAA32D1B52EB7 /* AppRootView.swift */; }; 93E938AA978ABF5665737887 /* GuideEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A0935AE4B398B281758834 /* GuideEntry.swift */; }; 955D039F4B5D14FCA4E7F2B2 /* GuideArtworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2850226B4B05FF758C1F93 /* GuideArtworkView.swift */; }; 99071E0CB9F44857C0BFC8C9 /* SavedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AA2E425D5454602F78D628 /* SavedView.swift */; }; + 995C5FDC790E09B87CB5F1C4 /* KleponTypography.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD520C870387E82E9A1533C2 /* KleponTypography.swift */; }; + 997D63C43B61CB342C586FB8 /* DiscoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4545DC84863788D55B94D31A /* DiscoverView.swift */; }; + 9BF8C40A4C12038A9DD2D0D7 /* collections.json in Resources */ = {isa = PBXBuildFile; fileRef = 1FDDB0F88FF430ABD679AD89 /* collections.json */; }; 9E16F5C37F30B58441780260 /* DiscoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4545DC84863788D55B94D31A /* DiscoverView.swift */; }; + 9E42E7DE70F8582B66C895F3 /* SearchService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6235AD6792F25476E2A508F0 /* SearchService.swift */; }; 9ECA989E9DCDF08A2D7C29A3 /* AppRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C82874C90A5FAA32D1B52EB7 /* AppRootView.swift */; }; 9ECEB59572D3B9D08D1DA896 /* GuideCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17C0DB3BD620AD9F55B7631 /* GuideCollection.swift */; }; + 9F79023304A3FD651D05BB54 /* OndeGuideEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0D53A5F9D5897E15506C20 /* OndeGuideEngine.swift */; }; + A001621A9DB5FF868217D410 /* DiscoverView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4545DC84863788D55B94D31A /* DiscoverView.swift */; }; + A0EF936D6223961D25CBC9F6 /* RecentlyViewedStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45070309D2C7058BAA44CD2 /* RecentlyViewedStore.swift */; }; + A0F94006F95F2F671D00EEFB /* KleponColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C675ABBBA25BBA25A8DE29BE /* KleponColor.swift */; }; + A18C34880D04EAB4C889BC22 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E152344B39D1D77F1B3943 /* SettingsView.swift */; }; A54D028D8F35FE002B0ACA90 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 054656155630B064F785B0D7 /* Assets.xcassets */; }; + A571A7E1FA449D257951367C /* guide_entries.json in Resources */ = {isa = PBXBuildFile; fileRef = 248F359109A08A60F792C42D /* guide_entries.json */; }; + A5E339F380247E90AEF56DC6 /* GuideCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17C0DB3BD620AD9F55B7631 /* GuideCollection.swift */; }; + A69F302A8F11969D9CD197EE /* guide_entries.json in Resources */ = {isa = PBXBuildFile; fileRef = 248F359109A08A60F792C42D /* guide_entries.json */; }; A7001D58E0F2B1BD5F52FF8A /* Onde in Frameworks */ = {isa = PBXBuildFile; productRef = 855EE8DF62A7820E77C05D1A /* Onde */; }; + A76C56EF8CFB6D723B55EE74 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E886BAB66E828950DBED52 /* SearchView.swift */; }; B1F1D1A453150D3BBB0146EF /* RecentlyViewedStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45070309D2C7058BAA44CD2 /* RecentlyViewedStore.swift */; }; B34377720179454F9B9184C5 /* OndeGuideEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0D53A5F9D5897E15506C20 /* OndeGuideEngine.swift */; }; B4467AA7214B4990D2FC19DA /* KleponCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 868C58098969278A542893F3 /* KleponCard.swift */; }; + B5B076B218DF86269DD7F80D /* GuideArtworkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7F2850226B4B05FF758C1F93 /* GuideArtworkView.swift */; }; B768150B02B19417B81FE651 /* guide_entries.json in Resources */ = {isa = PBXBuildFile; fileRef = 248F359109A08A60F792C42D /* guide_entries.json */; }; B7DE9EC8BE998E6719C88E83 /* guide_entries.json in Resources */ = {isa = PBXBuildFile; fileRef = 248F359109A08A60F792C42D /* guide_entries.json */; }; + B97D6D5885E0B6CFB358B1BC /* KleponActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DD909BB2EF68103CB46EDA /* KleponActionButton.swift */; }; BB70FAD944544125AF261F7C /* SavedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98AA2E425D5454602F78D628 /* SavedView.swift */; }; + BC8102C70075D3DEDE537CDD /* KleponActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DD909BB2EF68103CB46EDA /* KleponActionButton.swift */; }; C15131992039B3AC7DB74DAB /* testGuideArtworkView.guide-artwork.png in Resources */ = {isa = PBXBuildFile; fileRef = 9D6EFA021D4524508DA8E78C /* testGuideArtworkView.guide-artwork.png */; }; + C1DF8DB1F063D1C5258602D6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AF79D8DD27B9CA485140C934 /* Assets.xcassets */; }; + C2A06A0802694BD0C1F39B85 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7E886BAB66E828950DBED52 /* SearchView.swift */; }; C2EA2682A6545802C06E00CC /* WatchBrandSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 217E7D4050D471955FF3AB13 /* WatchBrandSystem.swift */; }; + C62C1ECC5068A50E5C09CEC5 /* Onde in Frameworks */ = {isa = PBXBuildFile; productRef = D977F065ACEF38E106A8FB5C /* Onde */; }; + C6532365C518962FC265ABCD /* GuideCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17C0DB3BD620AD9F55B7631 /* GuideCollection.swift */; }; C709C82E7F0BCF398BCD9C2F /* ContentRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2891A1AFBB59928FC5B1E66 /* ContentRepository.swift */; }; + C88013421E507AEDCB41BE96 /* OndeGuideEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0D53A5F9D5897E15506C20 /* OndeGuideEngine.swift */; }; + C8ECF8664277103582D96B45 /* FavoritesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC0C4C22A5134DB058E41F4 /* FavoritesStore.swift */; }; + CE5C9946E68E1F7A781F1628 /* AnswerCard.swift in Sources */ = {isa = PBXBuildFile; fileRef = B02B0EA4B8BA09604D62A62B /* AnswerCard.swift */; }; + CE6824D3D8261BF0463F39D8 /* KleponMetadataRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E1C50BFBE2EF5F37941590 /* KleponMetadataRow.swift */; }; + D2D49509E9CB5CF2BA4EBF6D /* GuideAnswerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3719031080B59898133810F7 /* GuideAnswerService.swift */; }; D6D8A42930EA9A0EA6622108 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AF291E9C2E66209377138E6 /* AppState.swift */; }; D99AAE1CDE51139840A52607 /* KleponMetadataRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7E1C50BFBE2EF5F37941590 /* KleponMetadataRow.swift */; }; DA915BA8B2F732E7D62CF936 /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E7B5E3D9A1906C2FED3032 /* EmptyStateView.swift */; }; + DBD7CE5D9AB42EA74CC9F7A1 /* KleponApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEDE2A1E73EB15970A17A894 /* KleponApp.swift */; }; + DDD3529FAEA0D105EBD5C168 /* KleponTypography.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD520C870387E82E9A1533C2 /* KleponTypography.swift */; }; + E17595636392EDF3D6EEE895 /* OndeEnvironmentBootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B499D3BF7AC80D31907D87F /* OndeEnvironmentBootstrap.swift */; }; + E25DE24C663A9579D28F21D4 /* GuideEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A0935AE4B398B281758834 /* GuideEntry.swift */; }; E45F257E3D35DC565B21B299 /* KleponColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = C675ABBBA25BBA25A8DE29BE /* KleponColor.swift */; }; E599CCDB1FE5331C4A1EB6C6 /* KleponSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 759E92BBAF958F5882F17C37 /* KleponSnapshotTests.swift */; }; E87F8A08D6922557A5DBD198 /* testOnboardingView_defaultState.default.png in Resources */ = {isa = PBXBuildFile; fileRef = 414FD1A8945B820DCC4ED192 /* testOnboardingView_defaultState.default.png */; }; ECDD606D8AFA776765AA9A31 /* SectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC438ECC335175B18778C682 /* SectionHeader.swift */; }; + EDD126593FECA8D9EB7DBC4A /* EmptyStateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E7B5E3D9A1906C2FED3032 /* EmptyStateView.swift */; }; EE2103530984735318E469FD /* AskSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC6E7DC56DD8E1459EFC892 /* AskSheetView.swift */; }; EE88AEC7059BB2639687FECF /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = 35214C3FF433B08C1C06C3F7 /* SnapshotTesting */; }; F0694A813D1CB57F0B5C0F65 /* FavoritesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC0C4C22A5134DB058E41F4 /* FavoritesStore.swift */; }; F1431179B54CEB23FFFCB2AE /* KleponApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEDE2A1E73EB15970A17A894 /* KleponApp.swift */; }; F2D905B433B0B76CE6B4001F /* OndeEnvironmentBootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B499D3BF7AC80D31907D87F /* OndeEnvironmentBootstrap.swift */; }; F5B35C6C771073CE32B4BFD1 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 42E152344B39D1D77F1B3943 /* SettingsView.swift */; }; + F64259FB47704FC3E2F15FF7 /* RecentlyViewedStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = E45070309D2C7058BAA44CD2 /* RecentlyViewedStore.swift */; }; F87EF3C291B7D3FB9BE8E43A /* ContentRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2891A1AFBB59928FC5B1E66 /* ContentRepository.swift */; }; F88972525510344E63D2983C /* AppRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C82874C90A5FAA32D1B52EB7 /* AppRootView.swift */; }; + F9983D842EB95B43620CA52B /* GuideDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7541983FF7CE190C0663294D /* GuideDetailView.swift */; }; F9D6FC75A11BE98BD28BBDC2 /* RecentSearchStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643D52BCEDF66CC8FE8A1234 /* RecentSearchStore.swift */; }; FA423A76BE098A9911044246 /* AskSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEC6E7DC56DD8E1459EFC892 /* AskSheetView.swift */; }; + FA44F0883B69820D526BF6E4 /* GuideDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7541983FF7CE190C0663294D /* GuideDetailView.swift */; }; FD0E1BA888E3CE73875D58FF /* RecentSearchStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 643D52BCEDF66CC8FE8A1234 /* RecentSearchStore.swift */; }; /* End PBXBuildFile section */ @@ -149,6 +219,7 @@ 6B67BCC5A459288FB240C6E7 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 6EFBF98118F066D3C7FA14B2 /* KleponWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KleponWatchApp.swift; sourceTree = ""; }; 7541983FF7CE190C0663294D /* GuideDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideDetailView.swift; sourceTree = ""; }; + 754A7FAB8C92112B8B6228B7 /* Klepon TV.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = "Klepon TV.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 759E92BBAF958F5882F17C37 /* KleponSnapshotTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KleponSnapshotTests.swift; sourceTree = ""; }; 7F2850226B4B05FF758C1F93 /* GuideArtworkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuideArtworkView.swift; sourceTree = ""; }; 85E7B5E3D9A1906C2FED3032 /* EmptyStateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStateView.swift; sourceTree = ""; }; @@ -171,6 +242,7 @@ CD520C870387E82E9A1533C2 /* KleponTypography.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KleponTypography.swift; sourceTree = ""; }; D53EA049112C7D59177F9878 /* Klepon Mac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Klepon Mac.app"; sourceTree = BUILT_PRODUCTS_DIR; }; DEDE2A1E73EB15970A17A894 /* KleponApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KleponApp.swift; sourceTree = ""; }; + E041DB1D0DAA58746AE6F9EA /* Klepon Vision.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = "Klepon Vision.app"; sourceTree = BUILT_PRODUCTS_DIR; }; E45070309D2C7058BAA44CD2 /* RecentlyViewedStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentlyViewedStore.swift; sourceTree = ""; }; E7E886BAB66E828950DBED52 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = ""; }; EDBB4F5D4C98D036AC52E193 /* WatchHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchHomeView.swift; sourceTree = ""; }; @@ -182,6 +254,22 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 2D72F61FB9AD47BC2682FB3F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5D3BDDCB5CA8A8DC32E8ABEA /* Onde in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 614F30649198DB2180250038 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C62C1ECC5068A50E5C09CEC5 /* Onde in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; B0052635A882F0E996B81154 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -238,6 +326,8 @@ isa = PBXGroup; children = ( D53EA049112C7D59177F9878 /* Klepon Mac.app */, + 754A7FAB8C92112B8B6228B7 /* Klepon TV.app */, + E041DB1D0DAA58746AE6F9EA /* Klepon Vision.app */, 9E76510CC840CE068B0FC381 /* Klepon Watch.app */, 5DE91827A635C0B0B72690EC /* Klepon.app */, A48AC0E39277DEFCE5D103CC /* KleponSnapshotTests.xctest */, @@ -455,6 +545,27 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 0322696BE3965C4C0F0E5322 /* Klepon Vision */ = { + isa = PBXNativeTarget; + buildConfigurationList = A7E925A77873EC80DF7D2840 /* Build configuration list for PBXNativeTarget "Klepon Vision" */; + buildPhases = ( + BFE208AF41AAF322DFAB7EED /* Sources */, + 63B62007E78131109B2F8902 /* Resources */, + 2D72F61FB9AD47BC2682FB3F /* Frameworks */, + 201C2DC58537433FDB96C0C5 /* Update CFBundleVersion */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Klepon Vision"; + packageProductDependencies = ( + CECDF5959E6652B49DCF1E18 /* Onde */, + ); + productName = "Klepon Vision"; + productReference = E041DB1D0DAA58746AE6F9EA /* Klepon Vision.app */; + productType = "com.apple.product-type.application"; + }; 2ADB11F6C1D51B9F9A9DB15F /* Klepon */ = { isa = PBXNativeTarget; buildConfigurationList = E1E5A3DF58C9552EE814CC23 /* Build configuration list for PBXNativeTarget "Klepon" */; @@ -499,6 +610,27 @@ productReference = D53EA049112C7D59177F9878 /* Klepon Mac.app */; productType = "com.apple.product-type.application"; }; + E153DCB2727ADB891CF09A25 /* Klepon TV */ = { + isa = PBXNativeTarget; + buildConfigurationList = 14FFFB40F48F5625253F44DE /* Build configuration list for PBXNativeTarget "Klepon TV" */; + buildPhases = ( + 00C4D42D71379B10BB8DAD32 /* Sources */, + 13483CEA676E76809EF16344 /* Resources */, + 614F30649198DB2180250038 /* Frameworks */, + 2444E8602E844C7B97E84E9D /* Update CFBundleVersion */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "Klepon TV"; + packageProductDependencies = ( + D977F065ACEF38E106A8FB5C /* Onde */, + ); + productName = "Klepon TV"; + productReference = 754A7FAB8C92112B8B6228B7 /* Klepon TV.app */; + productType = "com.apple.product-type.application"; + }; E8CEBF52617721AEE450A7CF /* Klepon Watch */ = { isa = PBXNativeTarget; buildConfigurationList = 07A758477E97CF9254DDBA82 /* Build configuration list for PBXNativeTarget "Klepon Watch" */; @@ -548,12 +680,18 @@ BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 2600; TargetAttributes = { + 0322696BE3965C4C0F0E5322 = { + ProvisioningStyle = Automatic; + }; 2ADB11F6C1D51B9F9A9DB15F = { ProvisioningStyle = Automatic; }; 676CA86C78E7DB801DEEAF12 = { ProvisioningStyle = Automatic; }; + E153DCB2727ADB891CF09A25 = { + ProvisioningStyle = Automatic; + }; E8CEBF52617721AEE450A7CF = { ProvisioningStyle = Automatic; }; @@ -582,6 +720,8 @@ targets = ( 2ADB11F6C1D51B9F9A9DB15F /* Klepon */, 676CA86C78E7DB801DEEAF12 /* Klepon Mac */, + E153DCB2727ADB891CF09A25 /* Klepon TV */, + 0322696BE3965C4C0F0E5322 /* Klepon Vision */, E8CEBF52617721AEE450A7CF /* Klepon Watch */, F10E9562BA0BE60A7E000DB0 /* KleponSnapshotTests */, ); @@ -599,6 +739,26 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 13483CEA676E76809EF16344 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C1DF8DB1F063D1C5258602D6 /* Assets.xcassets in Resources */, + 9BF8C40A4C12038A9DD2D0D7 /* collections.json in Resources */, + A571A7E1FA449D257951367C /* guide_entries.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 63B62007E78131109B2F8902 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 841F3B272D0FD82365DE48BD /* Assets.xcassets in Resources */, + 76EE3B0D899987557B6741DF /* collections.json in Resources */, + A69F302A8F11969D9CD197EE /* guide_entries.json in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 667705EEBEDEBBCA43E94EAC /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -652,6 +812,46 @@ shellScript = "set -eu\n\nSHARED_BUNDLE_VERSION_FILE=\"${BUILD_DIR}/klepon-cfbundleversion.txt\"\nif [ -f \"${SHARED_BUNDLE_VERSION_FILE}\" ]; then\n BUNDLE_VERSION=\"$(cat \"${SHARED_BUNDLE_VERSION_FILE}\")\"\nelse\n BUNDLE_VERSION=\"$(date -u +\"%Y%m%d.%H%M\")\"\n printf '%s' \"${BUNDLE_VERSION}\" > \"${SHARED_BUNDLE_VERSION_FILE}\"\nfi\n\nINFO_PLIST=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n\nif [ ! -f \"${INFO_PLIST}\" ]; then\n echo \"error: Info.plist not found at ${INFO_PLIST}\"\n exit 1\nfi\n\nif ! /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion ${BUNDLE_VERSION}\" \"${INFO_PLIST}\" >/dev/null 2>&1; then\n /usr/libexec/PlistBuddy -c \"Add :CFBundleVersion string ${BUNDLE_VERSION}\" \"${INFO_PLIST}\"\nfi\n\necho \"Updated CFBundleVersion to ${BUNDLE_VERSION}\"\n"; showEnvVarsInLog = 0; }; + 201C2DC58537433FDB96C0C5 /* Update CFBundleVersion */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Update CFBundleVersion"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -eu\n\nSHARED_BUNDLE_VERSION_FILE=\"${BUILD_DIR}/klepon-cfbundleversion.txt\"\nif [ -f \"${SHARED_BUNDLE_VERSION_FILE}\" ]; then\n BUNDLE_VERSION=\"$(cat \"${SHARED_BUNDLE_VERSION_FILE}\")\"\nelse\n BUNDLE_VERSION=\"$(date -u +\"%Y%m%d.%H%M\")\"\n printf '%s' \"${BUNDLE_VERSION}\" > \"${SHARED_BUNDLE_VERSION_FILE}\"\nfi\n\nINFO_PLIST=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n\nif [ ! -f \"${INFO_PLIST}\" ]; then\n echo \"error: Info.plist not found at ${INFO_PLIST}\"\n exit 1\nfi\n\nif ! /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion ${BUNDLE_VERSION}\" \"${INFO_PLIST}\" >/dev/null 2>&1; then\n /usr/libexec/PlistBuddy -c \"Add :CFBundleVersion string ${BUNDLE_VERSION}\" \"${INFO_PLIST}\"\nfi\n\necho \"Updated CFBundleVersion to ${BUNDLE_VERSION}\"\n"; + showEnvVarsInLog = 0; + }; + 2444E8602E844C7B97E84E9D /* Update CFBundleVersion */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Update CFBundleVersion"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -eu\n\nSHARED_BUNDLE_VERSION_FILE=\"${BUILD_DIR}/klepon-cfbundleversion.txt\"\nif [ -f \"${SHARED_BUNDLE_VERSION_FILE}\" ]; then\n BUNDLE_VERSION=\"$(cat \"${SHARED_BUNDLE_VERSION_FILE}\")\"\nelse\n BUNDLE_VERSION=\"$(date -u +\"%Y%m%d.%H%M\")\"\n printf '%s' \"${BUNDLE_VERSION}\" > \"${SHARED_BUNDLE_VERSION_FILE}\"\nfi\n\nINFO_PLIST=\"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}\"\n\nif [ ! -f \"${INFO_PLIST}\" ]; then\n echo \"error: Info.plist not found at ${INFO_PLIST}\"\n exit 1\nfi\n\nif ! /usr/libexec/PlistBuddy -c \"Set :CFBundleVersion ${BUNDLE_VERSION}\" \"${INFO_PLIST}\" >/dev/null 2>&1; then\n /usr/libexec/PlistBuddy -c \"Add :CFBundleVersion string ${BUNDLE_VERSION}\" \"${INFO_PLIST}\"\nfi\n\necho \"Updated CFBundleVersion to ${BUNDLE_VERSION}\"\n"; + showEnvVarsInLog = 0; + }; 38259E5F7F0F32D684AE509A /* Update CFBundleVersion */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -695,6 +895,44 @@ /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 00C4D42D71379B10BB8DAD32 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8189E2DF13801C2B7162AD45 /* AnswerCard.swift in Sources */, + 935A2F7D98CCC0DEA5EA2782 /* AppRootView.swift in Sources */, + 1E042510F7F75D392A4AC6E7 /* AppState.swift in Sources */, + 26FA5D01FED96591E78A2293 /* AskSheetView.swift in Sources */, + 854C10AE603DCCC521D93BC2 /* ContentRepository.swift in Sources */, + 997D63C43B61CB342C586FB8 /* DiscoverView.swift in Sources */, + 6B7AE8B1C6D3AD6F973F5757 /* EmptyStateView.swift in Sources */, + C8ECF8664277103582D96B45 /* FavoritesStore.swift in Sources */, + 3EB900CB86D2B32F8DAB434D /* GuideAnswerService.swift in Sources */, + B5B076B218DF86269DD7F80D /* GuideArtworkView.swift in Sources */, + C6532365C518962FC265ABCD /* GuideCollection.swift in Sources */, + F9983D842EB95B43620CA52B /* GuideDetailView.swift in Sources */, + E25DE24C663A9579D28F21D4 /* GuideEntry.swift in Sources */, + B97D6D5885E0B6CFB358B1BC /* KleponActionButton.swift in Sources */, + DBD7CE5D9AB42EA74CC9F7A1 /* KleponApp.swift in Sources */, + 16330914DF2EE212C91BC0CA /* KleponCard.swift in Sources */, + 587F0BB61966B2BB048013DE /* KleponChip.swift in Sources */, + 4B6E0CF8EAB6FF1D0160E3E6 /* KleponColor.swift in Sources */, + CE6824D3D8261BF0463F39D8 /* KleponMetadataRow.swift in Sources */, + DDD3529FAEA0D105EBD5C168 /* KleponTypography.swift in Sources */, + 6117CD728CC7FF8D6EB0A8C8 /* OnboardingView.swift in Sources */, + E17595636392EDF3D6EEE895 /* OndeEnvironmentBootstrap.swift in Sources */, + 9F79023304A3FD651D05BB54 /* OndeGuideEngine.swift in Sources */, + 3C4C5EEBF766D6E8A5D240DA /* PlatformViewSupport.swift in Sources */, + 819489D60D833AE2AD9185A6 /* RecentSearchStore.swift in Sources */, + A0EF936D6223961D25CBC9F6 /* RecentlyViewedStore.swift in Sources */, + 14C796CE6777B6013910831C /* SavedView.swift in Sources */, + 74A9016453B9820FC486B014 /* SearchService.swift in Sources */, + C2A06A0802694BD0C1F39B85 /* SearchView.swift in Sources */, + 7F726FA99DBDC0155E99E098 /* SectionHeader.swift in Sources */, + 78142FDCFE7EF00A3D81A067 /* SettingsView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 142193A63A26ACC180E1EB37 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -784,6 +1022,44 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + BFE208AF41AAF322DFAB7EED /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + CE5C9946E68E1F7A781F1628 /* AnswerCard.swift in Sources */, + 0481C8A85FED1E35C3734816 /* AppRootView.swift in Sources */, + 906E12C00728B15B676A23DB /* AppState.swift in Sources */, + 2F74C3B8D4A1A38DEF50E39B /* AskSheetView.swift in Sources */, + 20CDEBB4B6C8AAE007854102 /* ContentRepository.swift in Sources */, + A001621A9DB5FF868217D410 /* DiscoverView.swift in Sources */, + EDD126593FECA8D9EB7DBC4A /* EmptyStateView.swift in Sources */, + 68479892D13825C6C9977DF9 /* FavoritesStore.swift in Sources */, + D2D49509E9CB5CF2BA4EBF6D /* GuideAnswerService.swift in Sources */, + 387EE903EFA0D820C646B4DB /* GuideArtworkView.swift in Sources */, + A5E339F380247E90AEF56DC6 /* GuideCollection.swift in Sources */, + FA44F0883B69820D526BF6E4 /* GuideDetailView.swift in Sources */, + 87E259162B03252EA23A32CF /* GuideEntry.swift in Sources */, + BC8102C70075D3DEDE537CDD /* KleponActionButton.swift in Sources */, + 7A9B7E0858E7095E18EC7AC6 /* KleponApp.swift in Sources */, + 0B94C64018A573AD679106E5 /* KleponCard.swift in Sources */, + 0E350E68B43067698FE3B05C /* KleponChip.swift in Sources */, + A0F94006F95F2F671D00EEFB /* KleponColor.swift in Sources */, + 8C6D6EC32870A45E021BF3EB /* KleponMetadataRow.swift in Sources */, + 995C5FDC790E09B87CB5F1C4 /* KleponTypography.swift in Sources */, + 367BECAC66674A040D99C0DF /* OnboardingView.swift in Sources */, + 6FCE6EBC39C7F229A5F52745 /* OndeEnvironmentBootstrap.swift in Sources */, + C88013421E507AEDCB41BE96 /* OndeGuideEngine.swift in Sources */, + 5C3590F8B1A8255C5E175A03 /* PlatformViewSupport.swift in Sources */, + 85FC2D60857424B39654DE08 /* RecentSearchStore.swift in Sources */, + F64259FB47704FC3E2F15FF7 /* RecentlyViewedStore.swift in Sources */, + 1C2B6971843620B876F5B0C3 /* SavedView.swift in Sources */, + 9E42E7DE70F8582B66C895F3 /* SearchService.swift in Sources */, + A76C56EF8CFB6D723B55EE74 /* SearchView.swift in Sources */, + 3568D735D790330031DCF8F9 /* SectionHeader.swift in Sources */, + A18C34880D04EAB4C889BC22 /* SettingsView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; C2B1B8ADEE7B8E2628B90D1F /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -977,10 +1253,37 @@ SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 18.5; WATCHOS_DEPLOYMENT_TARGET = 9.0; + XROS_DEPLOYMENT_TARGET = 1.0; }; name = Release; }; + 9415BE3B8DBFC6BE334F60B6 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 13B09D700AA047E07E91F6A2 /* Signing.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconTV; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_ENTITLEMENTS = KleponTV/KleponTV.entitlements; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=appletvsimulator*]" = x86_64; + INFOPLIST_FILE = KleponTV/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = ai.splitfire.klepon; + PRODUCT_NAME = Klepon; + SDKROOT = appletvos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = 3; + }; + name = Debug; + }; 97B8585F2E74C6C870509FE9 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 13B09D700AA047E07E91F6A2 /* Signing.xcconfig */; @@ -1005,6 +1308,30 @@ }; name = Release; }; + 999F5F439FDE6D455B425C69 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 13B09D700AA047E07E91F6A2 /* Signing.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconVision; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = KleponVision/KleponVision.entitlements; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=xrsimulator*]" = x86_64; + INFOPLIST_FILE = KleponVision/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = ai.splitfire.klepon; + PRODUCT_NAME = Klepon; + SDKROOT = xros; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Release; + }; A3B469B57456DA7C46537A7E /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 13B09D700AA047E07E91F6A2 /* Signing.xcconfig */; @@ -1091,10 +1418,37 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + TVOS_DEPLOYMENT_TARGET = 18.5; WATCHOS_DEPLOYMENT_TARGET = 9.0; + XROS_DEPLOYMENT_TARGET = 1.0; }; name = Debug; }; + B88CFA8CA520549B8189AABE /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 13B09D700AA047E07E91F6A2 /* Signing.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconTV; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; + CODE_SIGN_ENTITLEMENTS = KleponTV/KleponTV.entitlements; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=appletvsimulator*]" = x86_64; + INFOPLIST_FILE = KleponTV/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = ai.splitfire.klepon; + PRODUCT_NAME = Klepon; + SDKROOT = appletvos; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = 3; + }; + name = Release; + }; D2D1439E7A622A6F8019887F /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 13B09D700AA047E07E91F6A2 /* Signing.xcconfig */; @@ -1119,6 +1473,30 @@ }; name = Debug; }; + F007CBD9F576E3B4C7163B40 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 13B09D700AA047E07E91F6A2 /* Signing.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIconVision; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = KleponVision/KleponVision.entitlements; + CURRENT_PROJECT_VERSION = 1; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + "EXCLUDED_ARCHS[sdk=xrsimulator*]" = x86_64; + INFOPLIST_FILE = KleponVision/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0.0; + PRODUCT_BUNDLE_IDENTIFIER = ai.splitfire.klepon; + PRODUCT_NAME = Klepon; + SDKROOT = xros; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = 7; + }; + name = Debug; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1140,6 +1518,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + 14FFFB40F48F5625253F44DE /* Build configuration list for PBXNativeTarget "Klepon TV" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 9415BE3B8DBFC6BE334F60B6 /* Debug */, + B88CFA8CA520549B8189AABE /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; 78899F7C62A9675700D42C5A /* Build configuration list for PBXNativeTarget "Klepon Mac" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1158,6 +1545,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; }; + A7E925A77873EC80DF7D2840 /* Build configuration list for PBXNativeTarget "Klepon Vision" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + F007CBD9F576E3B4C7163B40 /* Debug */, + 999F5F439FDE6D455B425C69 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; E1E5A3DF58C9552EE814CC23 /* Build configuration list for PBXNativeTarget "Klepon" */ = { isa = XCConfigurationList; buildConfigurations = ( @@ -1204,6 +1600,16 @@ package = AD8C734FB461604D5793769C /* XCRemoteSwiftPackageReference "onde-swift" */; productName = Onde; }; + CECDF5959E6652B49DCF1E18 /* Onde */ = { + isa = XCSwiftPackageProductDependency; + package = AD8C734FB461604D5793769C /* XCRemoteSwiftPackageReference "onde-swift" */; + productName = Onde; + }; + D977F065ACEF38E106A8FB5C /* Onde */ = { + isa = XCSwiftPackageProductDependency; + package = AD8C734FB461604D5793769C /* XCRemoteSwiftPackageReference "onde-swift" */; + productName = Onde; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 05A5707BDA883282DEB132FF /* Project object */; diff --git a/Klepon.xcodeproj/xcshareddata/xcschemes/Klepon TV.xcscheme b/Klepon.xcodeproj/xcshareddata/xcschemes/Klepon TV.xcscheme new file mode 100644 index 0000000..1a01fb5 --- /dev/null +++ b/Klepon.xcodeproj/xcshareddata/xcschemes/Klepon TV.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Klepon.xcodeproj/xcshareddata/xcschemes/Klepon Vision.xcscheme b/Klepon.xcodeproj/xcshareddata/xcschemes/Klepon Vision.xcscheme new file mode 100644 index 0000000..c2bc568 --- /dev/null +++ b/Klepon.xcodeproj/xcshareddata/xcschemes/Klepon Vision.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Klepon/App/AppRootView.swift b/Klepon/App/AppRootView.swift index f18348d..537ad9e 100644 --- a/Klepon/App/AppRootView.swift +++ b/Klepon/App/AppRootView.swift @@ -5,6 +5,9 @@ struct AppRootView: View { case discover case search case saved + #if os(tvOS) + case settings + #endif } @EnvironmentObject private var appState: AppState @@ -38,17 +41,29 @@ struct AppRootView: View { Label("Saved", systemImage: "heart") } .tag(Tab.saved) + + #if os(tvOS) + NavigationStack { + SettingsView() + } + .tabItem { + Label("Settings", systemImage: "gearshape.2") + } + .tag(Tab.settings) + #endif } .tint(KleponColor.accent) .background(KleponColor.background.ignoresSafeArea()) - .sheet(isPresented: $showingSettings) { - NavigationStack { - SettingsView() + #if !os(tvOS) + .sheet(isPresented: $showingSettings) { + NavigationStack { + SettingsView() + } + #if os(macOS) || os(visionOS) + .frame(minWidth: 480, minHeight: 620) + #endif } - #if os(macOS) - .frame(minWidth: 480, minHeight: 620) - #endif - } + #endif .kleponOnboardingPresentation(isPresented: $showingOnboarding) { OnboardingView( onBrowseFirst: { @@ -58,7 +73,7 @@ struct AppRootView: View { appState.completeOnboarding() } ) - #if os(macOS) + #if os(macOS) || os(visionOS) .frame(minWidth: 640, minHeight: 760) #endif } @@ -69,7 +84,7 @@ struct AppRootView: View { .onChange(of: appState.hasCompletedOnboarding) { _, newValue in showingOnboarding = !newValue } - #if os(macOS) + #if os(macOS) || os(visionOS) .frame(minWidth: 980, minHeight: 720) #endif } diff --git a/Klepon/App/KleponApp.swift b/Klepon/App/KleponApp.swift index e3401f0..7be83c4 100644 --- a/Klepon/App/KleponApp.swift +++ b/Klepon/App/KleponApp.swift @@ -16,7 +16,7 @@ struct KleponApp: App { .preferredColorScheme(.light) #endif } - #if os(macOS) + #if os(macOS) || os(visionOS) .defaultSize(width: 1100, height: 760) #endif } diff --git a/Klepon/App/PlatformViewSupport.swift b/Klepon/App/PlatformViewSupport.swift index 041dd62..2395023 100644 --- a/Klepon/App/PlatformViewSupport.swift +++ b/Klepon/App/PlatformViewSupport.swift @@ -4,6 +4,8 @@ extension ToolbarItemPlacement { static var kleponPrimaryAction: ToolbarItemPlacement { #if os(macOS) return .primaryAction + #elseif os(tvOS) || os(visionOS) + return .automatic #else return .topBarTrailing #endif @@ -11,6 +13,15 @@ extension ToolbarItemPlacement { } extension View { + @ViewBuilder + func kleponInteractiveButtonStyle() -> some View { + #if os(tvOS) || os(visionOS) + self.buttonStyle(.automatic) + #else + self.buttonStyle(.plain) + #endif + } + @ViewBuilder func kleponInlineNavigationTitle() -> some View { #if os(iOS) @@ -34,7 +45,7 @@ extension View { isPresented: Binding, @ViewBuilder content: @escaping () -> Content ) -> some View { - #if os(macOS) + #if os(macOS) || os(tvOS) || os(visionOS) self.sheet(isPresented: isPresented, content: content) #else self.fullScreenCover(isPresented: isPresented, content: content) diff --git a/Klepon/Assets.xcassets/AppIconMac.appiconset/Icon-1024.png b/Klepon/Assets.xcassets/AppIconMac.appiconset/Icon-1024.png deleted file mode 100644 index 10d3e93..0000000 Binary files a/Klepon/Assets.xcassets/AppIconMac.appiconset/Icon-1024.png and /dev/null differ diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Back.imagestacklayer/Content.imageset/Back.png b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Back.imagestacklayer/Content.imageset/Back.png new file mode 100644 index 0000000..1a0158e Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Back.imagestacklayer/Content.imageset/Back.png differ diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..1542e46 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Back.png", + "idiom" : "tv" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Back.imagestacklayer/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Contents.json new file mode 100644 index 0000000..3d73e5f --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Contents.json @@ -0,0 +1,14 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ] +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..d128032 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Front.png", + "idiom" : "tv" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Front.imagestacklayer/Content.imageset/Front.png b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Front.imagestacklayer/Content.imageset/Front.png new file mode 100644 index 0000000..252d186 Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Front.imagestacklayer/Content.imageset/Front.png differ diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Front.imagestacklayer/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS - App Store.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Content.imageset/Back.png b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Content.imageset/Back.png new file mode 100644 index 0000000..a66f8ba Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Content.imageset/Back.png differ diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Content.imageset/Back@2x.png b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Content.imageset/Back@2x.png new file mode 100644 index 0000000..bfd1ed1 Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Content.imageset/Back@2x.png differ diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Content.imageset/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..0b2a6fe --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "filename" : "Back.png", + "idiom" : "tv", + "scale" : "1x" + }, + { + "filename" : "Back@2x.png", + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Contents.json new file mode 100644 index 0000000..d3e30a3 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Back.imagestacklayer/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "frame-size" : { + "height" : 240, + "width" : 400 + } + } +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Contents.json new file mode 100644 index 0000000..3d73e5f --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Contents.json @@ -0,0 +1,14 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.imagestacklayer" + }, + { + "filename" : "Back.imagestacklayer" + } + ] +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Content.imageset/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..39d6030 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "filename" : "Front.png", + "idiom" : "tv", + "scale" : "1x" + }, + { + "filename" : "Front@2x.png", + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Content.imageset/Front.png b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Content.imageset/Front.png new file mode 100644 index 0000000..f046a02 Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Content.imageset/Front.png differ diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Content.imageset/Front@2x.png b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Content.imageset/Front@2x.png new file mode 100644 index 0000000..fb050ae Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Content.imageset/Front@2x.png differ diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Contents.json new file mode 100644 index 0000000..d3e30a3 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/App Icon - tvOS.imagestack/Front.imagestacklayer/Contents.json @@ -0,0 +1,12 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "frame-size" : { + "height" : 240, + "width" : 400 + } + } +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/Contents.json new file mode 100644 index 0000000..c0f9968 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/Contents.json @@ -0,0 +1,32 @@ +{ + "assets" : [ + { + "filename" : "App Icon - tvOS - App Store.imagestack", + "idiom" : "tv", + "role" : "primary-app-icon", + "size" : "1280x768" + }, + { + "filename" : "App Icon - tvOS.imagestack", + "idiom" : "tv", + "role" : "primary-app-icon", + "size" : "400x240" + }, + { + "filename" : "Top Shelf Image.imageset", + "idiom" : "tv", + "role" : "top-shelf-image", + "size" : "1920x720" + }, + { + "filename" : "Top Shelf Image Wide.imageset", + "idiom" : "tv", + "role" : "top-shelf-image-wide", + "size" : "2320x720" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image Wide.imageset/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image Wide.imageset/Contents.json new file mode 100644 index 0000000..2a4b468 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image Wide.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "filename" : "TopShelfImageWide.png", + "idiom" : "tv", + "scale" : "1x" + }, + { + "filename" : "TopShelfImageWide@2x.png", + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image Wide.imageset/TopShelfImageWide.png b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image Wide.imageset/TopShelfImageWide.png new file mode 100644 index 0000000..0a7fcb7 Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image Wide.imageset/TopShelfImageWide.png differ diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image Wide.imageset/TopShelfImageWide@2x.png b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image Wide.imageset/TopShelfImageWide@2x.png new file mode 100644 index 0000000..92dde9e Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image Wide.imageset/TopShelfImageWide@2x.png differ diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image.imageset/Contents.json b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image.imageset/Contents.json new file mode 100644 index 0000000..ece2e3c --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image.imageset/Contents.json @@ -0,0 +1,18 @@ +{ + "images" : [ + { + "filename" : "TopShelfImage.png", + "idiom" : "tv", + "scale" : "1x" + }, + { + "filename" : "TopShelfImage@2x.png", + "idiom" : "tv", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image.imageset/TopShelfImage.png b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image.imageset/TopShelfImage.png new file mode 100644 index 0000000..a474819 Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image.imageset/TopShelfImage.png differ diff --git a/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image.imageset/TopShelfImage@2x.png b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image.imageset/TopShelfImage@2x.png new file mode 100644 index 0000000..edfc02d Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconTV.brandassets/Top Shelf Image.imageset/TopShelfImage@2x.png differ diff --git a/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Back.solidimagestacklayer/Content.imageset/Back.png b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Back.solidimagestacklayer/Content.imageset/Back.png new file mode 100644 index 0000000..475177f Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Back.solidimagestacklayer/Content.imageset/Back.png differ diff --git a/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..5979065 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Back.solidimagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "filename" : "Back.png", + "idiom" : "vision", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Back.solidimagestacklayer/Contents.json b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Back.solidimagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Back.solidimagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Contents.json b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Contents.json new file mode 100644 index 0000000..2c99a09 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Contents.json @@ -0,0 +1,23 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + }, + "layers" : [ + { + "filename" : "Front.solidimagestacklayer" + }, + { + "filename" : "Middle.solidimagestacklayer" + }, + { + "filename" : "Back.solidimagestacklayer" + } + ], + "properties" : { + "canvasSize" : { + "height" : 512, + "width" : 512 + } + } +} diff --git a/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..e3b0a77 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Front.solidimagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "filename" : "Front.png", + "idiom" : "vision", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Front.solidimagestacklayer/Content.imageset/Front.png b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Front.solidimagestacklayer/Content.imageset/Front.png new file mode 100644 index 0000000..2a3b56f Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Front.solidimagestacklayer/Content.imageset/Front.png differ diff --git a/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Front.solidimagestacklayer/Contents.json b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Front.solidimagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Front.solidimagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json new file mode 100644 index 0000000..8c9879e --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Contents.json @@ -0,0 +1,13 @@ +{ + "images" : [ + { + "filename" : "Middle.png", + "idiom" : "vision", + "scale" : "2x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Middle.png b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Middle.png new file mode 100644 index 0000000..e281745 Binary files /dev/null and b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Middle.solidimagestacklayer/Content.imageset/Middle.png differ diff --git a/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Middle.solidimagestacklayer/Contents.json b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Middle.solidimagestacklayer/Contents.json new file mode 100644 index 0000000..73c0059 --- /dev/null +++ b/Klepon/Assets.xcassets/AppIconVision.solidimagestack/Middle.solidimagestacklayer/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Klepon/DesignSystem/KleponActionButton.swift b/Klepon/DesignSystem/KleponActionButton.swift index fb0851b..7fe43e7 100644 --- a/Klepon/DesignSystem/KleponActionButton.swift +++ b/Klepon/DesignSystem/KleponActionButton.swift @@ -34,7 +34,7 @@ struct KleponActionButton: View { ) .foregroundStyle(foregroundColor) } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() .disabled(isDisabled || isLoading) .opacity((isDisabled || isLoading) && tone == .primary ? 0.85 : 1) } diff --git a/Klepon/Features/Ask/AskSheetView.swift b/Klepon/Features/Ask/AskSheetView.swift index 466e169..aa64c95 100644 --- a/Klepon/Features/Ask/AskSheetView.swift +++ b/Klepon/Features/Ask/AskSheetView.swift @@ -34,7 +34,9 @@ struct AskSheetView: View { Button("Close") { dismiss() } - .keyboardShortcut(.cancelAction) + #if os(macOS) + .keyboardShortcut(.cancelAction) + #endif } #endif @@ -84,7 +86,7 @@ struct AskSheetView: View { ) .foregroundStyle(KleponColor.textPrimary) } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() .disabled(isLoading) } } @@ -93,12 +95,24 @@ struct AskSheetView: View { VStack(alignment: .leading, spacing: 12) { SectionHeader("Your question") - TextField( - "Ask about taste, ingredients, or what to try next", text: $question, - axis: .vertical - ) - .textFieldStyle(.roundedBorder) - .lineLimit(2...5) + #if os(tvOS) + TextField( + "Ask about taste, ingredients, or what to try next", text: $question + ) + .padding(.horizontal, 14) + .padding(.vertical, 12) + .background( + RoundedRectangle(cornerRadius: 18, style: .continuous) + .fill(KleponColor.surfaceSecondary) + ) + #else + TextField( + "Ask about taste, ingredients, or what to try next", text: $question, + axis: .vertical + ) + .textFieldStyle(.roundedBorder) + .lineLimit(2...5) + #endif KleponActionButton( title: isLoading ? "Thinking" : "Get answer", @@ -175,7 +189,9 @@ struct AskSheetView: View { Button("Close") { dismiss() } - .keyboardShortcut(.cancelAction) + #if os(macOS) + .keyboardShortcut(.cancelAction) + #endif .tint(KleponColor.accent) } } @@ -278,7 +294,7 @@ private struct AnswerCardView: View { ) .foregroundStyle(KleponColor.textPrimary) } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() .disabled(isInteractionDisabled) } } diff --git a/Klepon/Features/Detail/GuideDetailView.swift b/Klepon/Features/Detail/GuideDetailView.swift index e301777..3b5176e 100644 --- a/Klepon/Features/Detail/GuideDetailView.swift +++ b/Klepon/Features/Detail/GuideDetailView.swift @@ -80,7 +80,7 @@ struct GuideDetailView: View { } } } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() } } } @@ -184,7 +184,7 @@ struct GuideDetailView: View { ) .foregroundStyle(KleponColor.textPrimary) } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() } } } diff --git a/Klepon/Features/Discover/DiscoverView.swift b/Klepon/Features/Discover/DiscoverView.swift index 7ecdbf2..ab251cc 100644 --- a/Klepon/Features/Discover/DiscoverView.swift +++ b/Klepon/Features/Discover/DiscoverView.swift @@ -39,7 +39,7 @@ struct DiscoverView: View { } label: { FeaturedEntryCard(entry: featuredEntry) } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() } else { EmptyStateView( title: "Curated dishes are on the way", @@ -75,7 +75,7 @@ struct DiscoverView: View { } label: { EntryListCard(entry: entry) } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() } } } @@ -92,7 +92,7 @@ struct DiscoverView: View { } label: { EntryListCard(entry: entry) } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() } } } @@ -105,16 +105,18 @@ struct DiscoverView: View { .background(KleponColor.background.ignoresSafeArea()) .navigationTitle("Klepon") .kleponLargeNavigationTitle() - .toolbar { - ToolbarItem(placement: .kleponPrimaryAction) { - Button { - showingSettings = true - } label: { - Image(systemName: "gearshape.2") + #if !os(tvOS) + .toolbar { + ToolbarItem(placement: .kleponPrimaryAction) { + Button { + showingSettings = true + } label: { + Image(systemName: "gearshape.2") + } + .tint(KleponColor.accent) } - .tint(KleponColor.accent) } - } + #endif } private var introSection: some View { @@ -302,7 +304,7 @@ private struct CollectionRail: View { .fill(KleponColor.surfaceSecondary) ) } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() } } } diff --git a/Klepon/Features/Saved/SavedView.swift b/Klepon/Features/Saved/SavedView.swift index 26768dd..364870c 100644 --- a/Klepon/Features/Saved/SavedView.swift +++ b/Klepon/Features/Saved/SavedView.swift @@ -40,7 +40,7 @@ struct SavedView: View { } .frame(maxWidth: .infinity, alignment: .leading) } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() Button { favoritesStore.toggle(entry.id) @@ -50,7 +50,7 @@ struct SavedView: View { .padding(10) .background(Circle().fill(KleponColor.surfaceSecondary)) } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() } } } diff --git a/Klepon/Features/Search/SearchView.swift b/Klepon/Features/Search/SearchView.swift index c3ddb0b..386ac76 100644 --- a/Klepon/Features/Search/SearchView.swift +++ b/Klepon/Features/Search/SearchView.swift @@ -35,7 +35,7 @@ struct SearchView: View { } label: { KleponChip(title: recentQuery, icon: "clock") } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() } } } @@ -56,7 +56,7 @@ struct SearchView: View { appState.recentSearchStore.record(entry.title) } ) - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() } if !recentEntries.isEmpty { @@ -69,7 +69,7 @@ struct SearchView: View { } label: { SearchResultCard(entry: entry) } - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() } } } @@ -94,7 +94,7 @@ struct SearchView: View { appState.recentSearchStore.record(query) } ) - .buttonStyle(.plain) + .kleponInteractiveButtonStyle() } } } diff --git a/Klepon/Features/Settings/SettingsView.swift b/Klepon/Features/Settings/SettingsView.swift index 0403377..10a92b7 100644 --- a/Klepon/Features/Settings/SettingsView.swift +++ b/Klepon/Features/Settings/SettingsView.swift @@ -144,7 +144,9 @@ struct SettingsView: View { KleponActionButton(title: "Show welcome again", tone: .secondary) { appState.hasCompletedOnboarding = false - dismiss() + #if !os(tvOS) + dismiss() + #endif } } } @@ -168,13 +170,15 @@ struct SettingsView: View { } .background(KleponColor.background.ignoresSafeArea()) .navigationTitle("Settings") - .toolbar { - ToolbarItem(placement: .kleponPrimaryAction) { - Button("Done") { - dismiss() + #if !os(tvOS) + .toolbar { + ToolbarItem(placement: .kleponPrimaryAction) { + Button("Done") { + dismiss() + } + .tint(KleponColor.accent) } - .tint(KleponColor.accent) } - } + #endif } } diff --git a/KleponTV/Info.plist b/KleponTV/Info.plist new file mode 100644 index 0000000..35449f3 --- /dev/null +++ b/KleponTV/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption + + UILaunchScreen + + + diff --git a/KleponTV/KleponTV.entitlements b/KleponTV/KleponTV.entitlements new file mode 100644 index 0000000..38c10cb --- /dev/null +++ b/KleponTV/KleponTV.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.ondeinference.apps + + + diff --git a/KleponVision/Info.plist b/KleponVision/Info.plist new file mode 100644 index 0000000..35449f3 --- /dev/null +++ b/KleponVision/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption + + UILaunchScreen + + + diff --git a/KleponVision/KleponVision.entitlements b/KleponVision/KleponVision.entitlements new file mode 100644 index 0000000..38c10cb --- /dev/null +++ b/KleponVision/KleponVision.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.application-groups + + group.com.ondeinference.apps + + + diff --git a/README.md b/README.md index cfc26c9..a51c07a 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,16 @@ Klepon is a small SwiftUI guide to Indonesian dishes, ingredients, and food trad - curated local content - private on-device follow-up answers with [Onde Inference](https://ondeinference.com) - search, saved items, and recently viewed +- Apple TV support with tvOS focus-friendly navigation - native macOS app support +- visionOS window support, kept intentionally simple for v1 - watchOS companion support ## Repo -- `Klepon/` app source +- `Klepon/` shared iOS, macOS, tvOS, and visionOS app source +- `KleponTV/` tvOS target configuration +- `KleponVision/` visionOS target configuration - `Klepon.xcodeproj/` Xcode project - `Scripts/validate_content.py` content validation - `AppStore/` store assets and release notes @@ -40,6 +44,16 @@ macOS: xcodebuild -project Klepon.xcodeproj -scheme 'Klepon Mac' -destination 'platform=macOS' CODE_SIGNING_ALLOWED=NO build ``` +tvOS simulator: +```/dev/null/sh#L1-1 +xcodebuild -project Klepon.xcodeproj -scheme 'Klepon TV' -destination 'generic/platform=tvOS Simulator' CODE_SIGNING_ALLOWED=NO build +``` + +visionOS simulator: +```/dev/null/sh#L1-1 +xcodebuild -project Klepon.xcodeproj -scheme 'Klepon Vision' -destination 'generic/platform=visionOS Simulator' CODE_SIGNING_ALLOWED=NO build +``` + watchOS simulator: ```/dev/null/sh#L1-1 xcodebuild -project Klepon.xcodeproj -scheme 'Klepon Watch' -destination 'generic/platform=watchOS Simulator' CODE_SIGNING_ALLOWED=NO build @@ -54,7 +68,7 @@ python3 Scripts/validate_content.py If you run your own fork, change the bundle identifier and App Group to values you control. -The macOS target uses the same main app bundle identifier as iPhone and a sandboxed entitlements file for App Store distribution. +The macOS, tvOS, and visionOS targets use the same main app bundle identifier as iPhone. The macOS target also uses a sandboxed entitlements file for App Store distribution. ## License diff --git a/project.yml b/project.yml index bfb3e71..00882f8 100644 --- a/project.yml +++ b/project.yml @@ -3,6 +3,8 @@ options: deploymentTarget: iOS: "18.5" macOS: "15.0" + tvOS: "18.5" + visionOS: "1.0" watchOS: "9.0" xcodeVersion: "26.0" generateEmptyDirectories: false @@ -61,6 +63,30 @@ schemes: config: Release profile: config: Release + Klepon TV: + build: + targets: + Klepon TV: all + run: + config: Debug + analyze: + config: Debug + archive: + config: Release + profile: + config: Release + Klepon Vision: + build: + targets: + Klepon Vision: all + run: + config: Debug + analyze: + config: Debug + archive: + config: Release + profile: + config: Release targets: Klepon: @@ -239,6 +265,150 @@ targets: Release: SWIFT_OPTIMIZATION_LEVEL: -O + KleponTV: + name: Klepon TV + type: application + platform: tvOS + sources: + - path: Klepon + excludes: + - Info.plist + - Klepon.entitlements + info: + path: KleponTV/Info.plist + properties: + CFBundleShortVersionString: $(MARKETING_VERSION) + CFBundleVersion: $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption: false + UILaunchScreen: {} + entitlements: + path: KleponTV/KleponTV.entitlements + properties: + com.apple.security.application-groups: + - group.com.ondeinference.apps + configFiles: + Debug: Configs/Signing.xcconfig + Release: Configs/Signing.xcconfig + dependencies: + - package: Onde + product: Onde + postBuildScripts: + - name: Update CFBundleVersion + shell: /bin/sh + basedOnDependencyAnalysis: false + showEnvVars: false + script: | + set -eu + + SHARED_BUNDLE_VERSION_FILE="${BUILD_DIR}/klepon-cfbundleversion.txt" + if [ -f "${SHARED_BUNDLE_VERSION_FILE}" ]; then + BUNDLE_VERSION="$(cat "${SHARED_BUNDLE_VERSION_FILE}")" + else + BUNDLE_VERSION="$(date -u +"%Y%m%d.%H%M")" + printf '%s' "${BUNDLE_VERSION}" > "${SHARED_BUNDLE_VERSION_FILE}" + fi + + INFO_PLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}" + + if [ ! -f "${INFO_PLIST}" ]; then + echo "error: Info.plist not found at ${INFO_PLIST}" + exit 1 + fi + + if ! /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${BUNDLE_VERSION}" "${INFO_PLIST}" >/dev/null 2>&1; then + /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string ${BUNDLE_VERSION}" "${INFO_PLIST}" + fi + + echo "Updated CFBundleVersion to ${BUNDLE_VERSION}" + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: ai.splitfire.klepon + PRODUCT_NAME: Klepon + MARKETING_VERSION: "1.0.0" + CURRENT_PROJECT_VERSION: 1 + ASSETCATALOG_COMPILER_APPICON_NAME: AppIconTV + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor + ENABLE_USER_SCRIPT_SANDBOXING: NO + TARGETED_DEVICE_FAMILY: 3 + "EXCLUDED_ARCHS[sdk=appletvsimulator*]": x86_64 + configs: + Debug: + SWIFT_OPTIMIZATION_LEVEL: -Onone + Release: + SWIFT_OPTIMIZATION_LEVEL: -O + + KleponVision: + name: Klepon Vision + type: application + platform: visionOS + sources: + - path: Klepon + excludes: + - Info.plist + - Klepon.entitlements + info: + path: KleponVision/Info.plist + properties: + CFBundleShortVersionString: $(MARKETING_VERSION) + CFBundleVersion: $(CURRENT_PROJECT_VERSION) + ITSAppUsesNonExemptEncryption: false + UILaunchScreen: {} + entitlements: + path: KleponVision/KleponVision.entitlements + properties: + com.apple.security.application-groups: + - group.com.ondeinference.apps + configFiles: + Debug: Configs/Signing.xcconfig + Release: Configs/Signing.xcconfig + dependencies: + - package: Onde + product: Onde + postBuildScripts: + - name: Update CFBundleVersion + shell: /bin/sh + basedOnDependencyAnalysis: false + showEnvVars: false + script: | + set -eu + + SHARED_BUNDLE_VERSION_FILE="${BUILD_DIR}/klepon-cfbundleversion.txt" + if [ -f "${SHARED_BUNDLE_VERSION_FILE}" ]; then + BUNDLE_VERSION="$(cat "${SHARED_BUNDLE_VERSION_FILE}")" + else + BUNDLE_VERSION="$(date -u +"%Y%m%d.%H%M")" + printf '%s' "${BUNDLE_VERSION}" > "${SHARED_BUNDLE_VERSION_FILE}" + fi + + INFO_PLIST="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}" + + if [ ! -f "${INFO_PLIST}" ]; then + echo "error: Info.plist not found at ${INFO_PLIST}" + exit 1 + fi + + if ! /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${BUNDLE_VERSION}" "${INFO_PLIST}" >/dev/null 2>&1; then + /usr/libexec/PlistBuddy -c "Add :CFBundleVersion string ${BUNDLE_VERSION}" "${INFO_PLIST}" + fi + + echo "Updated CFBundleVersion to ${BUNDLE_VERSION}" + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: ai.splitfire.klepon + PRODUCT_NAME: Klepon + MARKETING_VERSION: "1.0.0" + CURRENT_PROJECT_VERSION: 1 + TARGETED_DEVICE_FAMILY: 7 + ASSETCATALOG_COMPILER_APPICON_NAME: AppIconVision + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME: AccentColor + ENABLE_USER_SCRIPT_SANDBOXING: NO + "EXCLUDED_ARCHS[sdk=xrsimulator*]": x86_64 + configs: + Debug: + SWIFT_OPTIMIZATION_LEVEL: -Onone + Release: + SWIFT_OPTIMIZATION_LEVEL: -O + KleponWatch: name: Klepon Watch type: application