fix iOS build blockers and add project scaffold
This commit is contained in:
@@ -13,6 +13,7 @@ This is the canonical working plan and progress log for the project. Use this fi
|
||||
- Backend smoke coverage was added for `server_time` and audit logging.
|
||||
- iOS now follows the backend-backed flow, syncs clock from `/health`, flushes analytics, and uses localized language labels instead of hardcoded `EN` and `SV`.
|
||||
- iOS source was updated for the backend-backed session and round flow, including the real preview cue points and localized session strings.
|
||||
- iOS settings now uses localized copy instead of a scaffold placeholder, and the screen is wired into the root scroll flow.
|
||||
- Android debug build passes with `./gradlew :app:assembleDebug`.
|
||||
- Backend tests pass with `cargo test`.
|
||||
- Backend localization bundles now have a contract test, and the localization catalog matches the shipped English and Swedish keys.
|
||||
@@ -20,11 +21,13 @@ This is the canonical working plan and progress log for the project. Use this fi
|
||||
- Backend bet coverage now includes a direct regression for late-lock rejection.
|
||||
- Backend bet coverage now includes invalid-session and invalid-market rejection regressions, plus settlement override coverage.
|
||||
- Backend analytics coverage now includes a representative key user flow batch.
|
||||
- iOS project scaffolding now exists as `mobile/ios-app/HermesApp.xcodeproj` with a shared scheme and XCTest target.
|
||||
- iOS sources and tests typecheck against the iPhone simulator SDK, and a standalone localization/error-mapping harness passes.
|
||||
|
||||
### Still Open
|
||||
|
||||
- Continue through the remaining plan phases and finish any leftover localization and polish work.
|
||||
- Add iOS build validation once an Xcode project is available in `mobile/ios-app`.
|
||||
- Run Xcode build/test validation once the local Xcode license is accepted; `xcodebuild` is currently blocked by the license gate in this environment.
|
||||
- Keep expanding tests around session, odds, settlement, and analytics behavior.
|
||||
|
||||
## 1. Purpose
|
||||
@@ -1310,6 +1313,8 @@ Must support flags for:
|
||||
|
||||
The AI agent must work in this order.
|
||||
|
||||
After each major completed change set, create a git commit and push it before starting the next major change.
|
||||
|
||||
### Phase 1: Documents
|
||||
|
||||
1. Write root README
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
"result.outcome": "Outcome",
|
||||
"result.next_round": "Next round",
|
||||
"settings.title": "Settings",
|
||||
"settings.subtitle": "Language, haptics and analytics preferences.",
|
||||
"settings.language": "Language",
|
||||
"settings.enabled": "Enabled",
|
||||
"settings.haptics": "Haptics",
|
||||
"settings.analytics": "Analytics",
|
||||
"errors.generic": "Please try again.",
|
||||
|
||||
@@ -30,7 +30,9 @@
|
||||
"result.outcome": "Utfall",
|
||||
"result.next_round": "Nästa runda",
|
||||
"settings.title": "Inställningar",
|
||||
"settings.subtitle": "Språk-, haptik- och analysinställningar.",
|
||||
"settings.language": "Språk",
|
||||
"settings.enabled": "Aktiverad",
|
||||
"settings.haptics": "Haptik",
|
||||
"settings.analytics": "Analys",
|
||||
"errors.generic": "Försök igen.",
|
||||
|
||||
@@ -43,7 +43,9 @@
|
||||
| `result.outcome` | Result outcome label |
|
||||
| `result.next_round` | Next round CTA |
|
||||
| `settings.title` | Settings title |
|
||||
| `settings.subtitle` | Settings subtitle |
|
||||
| `settings.language` | Language setting |
|
||||
| `settings.enabled` | Enabled status |
|
||||
| `settings.haptics` | Haptics setting |
|
||||
| `settings.analytics` | Analytics setting |
|
||||
| `errors.generic` | Generic error copy |
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
func hermesUserFacingErrorMessage(localization: LocalizationStore, localeCode: String, error: Error?) -> String? {
|
||||
guard let error else {
|
||||
return nil
|
||||
|
||||
@@ -13,9 +13,10 @@ struct RootView: View {
|
||||
VStack(alignment: .leading, spacing: 24) {
|
||||
header
|
||||
OnboardingView(onStartSession: { onStartSession(localization.localeCode) })
|
||||
FeedView(onWatchPreview: {}, onRetry: { onStartSession(localization.localeCode) })
|
||||
FeedView(onRetry: { onStartSession(localization.localeCode) })
|
||||
RoundView(onRetry: { onStartSession(localization.localeCode) })
|
||||
SessionView(onRetry: { onStartSession(localization.localeCode) })
|
||||
SettingsView()
|
||||
}
|
||||
.padding(.horizontal, HermesTheme.screenPadding)
|
||||
.padding(.vertical, 24)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol AnalyticsTracking {
|
||||
func track(_ event: String, attributes: [String: String])
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ final class LocalizationStore: ObservableObject {
|
||||
private static let supportedLocaleCodes = ["en", "sv"]
|
||||
private static let fallbackLocaleCode = "en"
|
||||
|
||||
init(bundle: Bundle = .main, localeCode: String = Locale.preferredLanguages.first.map { String($0.prefix(2)) } ?? "en") {
|
||||
self.bundle = bundle
|
||||
init(bundle: Bundle? = nil, localeCode: String = Locale.preferredLanguages.first.map { String($0.prefix(2)) } ?? "en") {
|
||||
self.bundle = bundle ?? Self.defaultBundle
|
||||
self.localeCode = Self.normalize(localeCode)
|
||||
}
|
||||
|
||||
@@ -64,4 +64,12 @@ final class LocalizationStore: ObservableObject {
|
||||
|
||||
return localeCode
|
||||
}
|
||||
|
||||
private static var defaultBundle: Bundle {
|
||||
#if SWIFT_PACKAGE
|
||||
.module
|
||||
#else
|
||||
.main
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,8 @@ struct HermesAPIClient {
|
||||
}
|
||||
|
||||
func submitAnalyticsBatch(_ payload: HermesAnalyticsBatchRequest) async throws {
|
||||
try await perform(path: "api/v1/analytics/batch", method: "POST", body: payload)
|
||||
let encodedBody = try encoder.encode(payload)
|
||||
_ = try await perform(path: "api/v1/analytics/batch", method: "POST", body: encodedBody)
|
||||
}
|
||||
|
||||
private func send<Response: Decodable>(path: String, method: String = "GET") async throws -> Response {
|
||||
|
||||
@@ -113,14 +113,6 @@ struct HermesRound: Codable {
|
||||
var settlement: HermesSettlement
|
||||
}
|
||||
|
||||
struct HermesRound: Codable {
|
||||
var event: HermesEvent
|
||||
var media: HermesEventMedia
|
||||
var market: HermesMarket
|
||||
var oddsVersion: HermesOddsVersion
|
||||
var settlement: HermesSettlement
|
||||
}
|
||||
|
||||
struct HermesBetIntentRequest: Codable {
|
||||
var sessionId: UUID
|
||||
var eventId: UUID
|
||||
|
||||
@@ -92,7 +92,7 @@ struct FeedView: View {
|
||||
.font(.title2.weight(.bold))
|
||||
.foregroundStyle(HermesTheme.textPrimary)
|
||||
|
||||
Text(round.map { localization.string(for: "feed.hero_subtitle") } ?? localization.string(for: "feed.hero_subtitle"))
|
||||
Text(localization.string(for: "feed.hero_subtitle"))
|
||||
.font(.callout)
|
||||
.foregroundStyle(HermesTheme.textSecondary)
|
||||
.frame(maxWidth: 260, alignment: .leading)
|
||||
|
||||
@@ -23,8 +23,7 @@ struct SessionView: View {
|
||||
statusText = localization.string(for: "session.status_loading")
|
||||
}
|
||||
|
||||
return HermesCard {
|
||||
VStack(alignment: .leading, spacing: HermesTheme.sectionSpacing) {
|
||||
return VStack(alignment: .leading, spacing: HermesTheme.sectionSpacing) {
|
||||
HermesSectionHeader(
|
||||
title: localization.string(for: "session.title"),
|
||||
subtitle: localization.string(for: "session.subtitle")
|
||||
@@ -67,7 +66,7 @@ struct SessionView: View {
|
||||
.foregroundStyle(HermesTheme.textSecondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.hermesCard()
|
||||
.onAppear {
|
||||
analytics.track("screen_viewed", attributes: ["screen_name": "session"])
|
||||
}
|
||||
|
||||
@@ -1,7 +1,45 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
@EnvironmentObject private var localization: LocalizationStore
|
||||
|
||||
var body: some View {
|
||||
Text("Settings scaffold")
|
||||
VStack(alignment: .leading, spacing: HermesTheme.sectionSpacing) {
|
||||
HermesSectionHeader(
|
||||
title: localization.string(for: "settings.title"),
|
||||
subtitle: localization.string(for: "settings.subtitle")
|
||||
)
|
||||
|
||||
VStack(spacing: 12) {
|
||||
settingRow(
|
||||
label: localization.string(for: "settings.language"),
|
||||
value: localization.localeName(for: localization.localeCode)
|
||||
)
|
||||
|
||||
settingRow(
|
||||
label: localization.string(for: "settings.haptics"),
|
||||
value: localization.string(for: "settings.enabled")
|
||||
)
|
||||
|
||||
settingRow(
|
||||
label: localization.string(for: "settings.analytics"),
|
||||
value: localization.string(for: "settings.enabled")
|
||||
)
|
||||
}
|
||||
}
|
||||
.hermesCard()
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func settingRow(label: String, value: String) -> some View {
|
||||
HStack {
|
||||
Text(label)
|
||||
.font(.callout)
|
||||
.foregroundStyle(HermesTheme.textSecondary)
|
||||
Spacer(minLength: 12)
|
||||
Text(value)
|
||||
.font(.callout.weight(.semibold))
|
||||
.foregroundStyle(HermesTheme.textPrimary)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {};
|
||||
objectVersion = 56;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
3E1C98162835584FF4929E35 = {isa = PBXBuildFile; fileRef = AFDCE723CFA31DF2A91DA374; };
|
||||
706106BBB737DEF1952FED6F = {isa = PBXBuildFile; fileRef = 3686935FA33F0278440C2137; };
|
||||
594A855A0BE13416458DA3A6 = {isa = PBXBuildFile; fileRef = DA1E3F27959B70E2FA168E86; };
|
||||
D3B7534C86065FFF51A5C55D = {isa = PBXBuildFile; fileRef = 6DE0108803DE783A7D5D528C; };
|
||||
2D43EFE45D26F6A8E55BF3AC = {isa = PBXBuildFile; fileRef = 895DC0E4CCA3A72E5897D850; };
|
||||
F9006995CFE1CCF0D38AD3AA = {isa = PBXBuildFile; fileRef = A24F440D4AC95A5E5A294BB5; };
|
||||
913CFC8BADBC80B024D2C856 = {isa = PBXBuildFile; fileRef = E727CD23C8DFCD3FFA972118; };
|
||||
AD46D096136FB1F118855D68 = {isa = PBXBuildFile; fileRef = 09BEEF1EACD1683731EA8B13; };
|
||||
B3C9966AC80EBB9B64A07C99 = {isa = PBXBuildFile; fileRef = 618EEBF90A2888756D2A0D3D; };
|
||||
BFC8A4A81EE80733E4AA7323 = {isa = PBXBuildFile; fileRef = 3C969AD72CFEAAFD87172BC5; };
|
||||
2C7F98F2653480078868DBF4 = {isa = PBXBuildFile; fileRef = 06986680EEC52DA119F3C422; };
|
||||
5384DC2D3DC830A67B2A1054 = {isa = PBXBuildFile; fileRef = 154F342749182EA88D7D3EA2; };
|
||||
1E2027DC632793AF7E962016 = {isa = PBXBuildFile; fileRef = 2927929745A862DD7DB483BE; };
|
||||
C1DB325D4807CDE56F820173 = {isa = PBXBuildFile; fileRef = 738C6476A9F7A2BBCA018EC7; };
|
||||
1A95F35F994CB9F20DF09556 = {isa = PBXBuildFile; fileRef = A6E7323ECDEAA449DD2925F0; };
|
||||
9BA60A645924EEE65B60E9C1 = {isa = PBXBuildFile; fileRef = 29BBCB2FC555E6C905C380A7; };
|
||||
F2FA36C9D20CD77F0045E16E = {isa = PBXBuildFile; fileRef = D34D3C5C813EAB2AD265F9CE; };
|
||||
72B9487B80FFA7D58F0CF66A = {isa = PBXBuildFile; fileRef = 16E0F69DFE5FC784FB2BBECA; };
|
||||
739163E3DBD5F43A84C49818 = {isa = PBXBuildFile; fileRef = 78ADAFCECDB66900EB275E3D; };
|
||||
A99DDCF27A5B1368CCA1EFB5 = {isa = PBXBuildFile; fileRef = B9C59E00C07DD533A7DC8126; };
|
||||
C8B084764EFF305D4AD0ADDF = {isa = PBXBuildFile; fileRef = 55B391E8147E161F76672032; };
|
||||
FCF2BB724CCC6B6974C426F8 = {isa = PBXBuildFile; fileRef = 36A67B6FF0A52868D30E3AFF; };
|
||||
7E17EFEA80A73B8C3179D66C = {isa = PBXBuildFile; fileRef = 5E777846BC9ED35ED93034AA; };
|
||||
9FE2C1B4C0DA9FE3AEF298AF = {isa = PBXBuildFile; fileRef = 9710CA4A388CBE265276E667; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
CCF863E2FFE2061635563672 = {isa = PBXContainerItemProxy; containerPortal = 63E87276D8BD98B1C9DA3FF1; proxyType = 1; remoteGlobalIDString = E9FB55C6F47F1FFA66FE0D94; remoteInfo = HermesApp; };
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
AFDCE723CFA31DF2A91DA374 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/HermesApp.swift; sourceTree = "<group>"; };
|
||||
3686935FA33F0278440C2137 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/HermesErrorMapper.swift; sourceTree = "<group>"; };
|
||||
DA1E3F27959B70E2FA168E86 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/HermesRepository.swift; sourceTree = "<group>"; };
|
||||
6DE0108803DE783A7D5D528C = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App/RootView.swift; sourceTree = "<group>"; };
|
||||
895DC0E4CCA3A72E5897D850 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core/Analytics/AnalyticsClient.swift; sourceTree = "<group>"; };
|
||||
A24F440D4AC95A5E5A294BB5 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core/DesignSystem/Theme.swift; sourceTree = "<group>"; };
|
||||
E727CD23C8DFCD3FFA972118 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core/Gestures/GestureHandlers.swift; sourceTree = "<group>"; };
|
||||
09BEEF1EACD1683731EA8B13 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core/Haptics/HapticsController.swift; sourceTree = "<group>"; };
|
||||
618EEBF90A2888756D2A0D3D = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core/Localization/LocalizationStore.swift; sourceTree = "<group>"; };
|
||||
3C969AD72CFEAAFD87172BC5 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core/Media/HermesVideoPlayerView.swift; sourceTree = "<group>"; };
|
||||
06986680EEC52DA119F3C422 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core/Media/PlayerCoordinator.swift; sourceTree = "<group>"; };
|
||||
154F342749182EA88D7D3EA2 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core/Networking/APIClient.swift; sourceTree = "<group>"; };
|
||||
2927929745A862DD7DB483BE = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Core/Networking/APIModels.swift; sourceTree = "<group>"; };
|
||||
738C6476A9F7A2BBCA018EC7 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features/Feed/FeedView.swift; sourceTree = "<group>"; };
|
||||
A6E7323ECDEAA449DD2925F0 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features/Onboarding/OnboardingView.swift; sourceTree = "<group>"; };
|
||||
29BBCB2FC555E6C905C380A7 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features/Result/ResultView.swift; sourceTree = "<group>"; };
|
||||
D34D3C5C813EAB2AD265F9CE = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features/Reveal/RevealView.swift; sourceTree = "<group>"; };
|
||||
16E0F69DFE5FC784FB2BBECA = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features/Round/RoundView.swift; sourceTree = "<group>"; };
|
||||
78ADAFCECDB66900EB275E3D = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features/Selection/SelectionView.swift; sourceTree = "<group>"; };
|
||||
B9C59E00C07DD533A7DC8126 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features/Session/SessionView.swift; sourceTree = "<group>"; };
|
||||
55B391E8147E161F76672032 = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Features/Settings/SettingsView.swift; sourceTree = "<group>"; };
|
||||
36A67B6FF0A52868D30E3AFF = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests/HermesAppTests/LocalizationStoreTests.swift; sourceTree = "<group>"; };
|
||||
5E777846BC9ED35ED93034AA = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Resources/Localization/en.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
9710CA4A388CBE265276E667 = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Resources/Localization/sv.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
3A8D22D1A79B415AD4892F84 = {isa = PBXFileReference; explicitFileType = wrapper.application; path = HermesApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
C1368E6B7C6BAAA50C8B38A8 = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = HermesAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
5151B46607EBC8890D8DFCC2 = {isa = PBXGroup; children = (
|
||||
3A8D22D1A79B415AD4892F84,
|
||||
C1368E6B7C6BAAA50C8B38A8,
|
||||
); sourceTree = "<group>"; };
|
||||
1C48CE29E4E135EE62951B22 = {isa = PBXGroup; children = (
|
||||
AFDCE723CFA31DF2A91DA374,
|
||||
3686935FA33F0278440C2137,
|
||||
DA1E3F27959B70E2FA168E86,
|
||||
6DE0108803DE783A7D5D528C,
|
||||
); path = App; sourceTree = "<group>"; };
|
||||
410188F9DC6E499FEC68BCF6 = {isa = PBXGroup; children = (
|
||||
895DC0E4CCA3A72E5897D850,
|
||||
A24F440D4AC95A5E5A294BB5,
|
||||
E727CD23C8DFCD3FFA972118,
|
||||
09BEEF1EACD1683731EA8B13,
|
||||
618EEBF90A2888756D2A0D3D,
|
||||
3C969AD72CFEAAFD87172BC5,
|
||||
06986680EEC52DA119F3C422,
|
||||
154F342749182EA88D7D3EA2,
|
||||
2927929745A862DD7DB483BE,
|
||||
); path = Core; sourceTree = "<group>"; };
|
||||
1F26A8704D8CDB03D5D3D4BB = {isa = PBXGroup; children = (
|
||||
738C6476A9F7A2BBCA018EC7,
|
||||
A6E7323ECDEAA449DD2925F0,
|
||||
29BBCB2FC555E6C905C380A7,
|
||||
D34D3C5C813EAB2AD265F9CE,
|
||||
16E0F69DFE5FC784FB2BBECA,
|
||||
78ADAFCECDB66900EB275E3D,
|
||||
B9C59E00C07DD533A7DC8126,
|
||||
55B391E8147E161F76672032,
|
||||
); path = Features; sourceTree = "<group>"; };
|
||||
A024F06580C79B7582C3806A = {isa = PBXGroup; children = (
|
||||
5E777846BC9ED35ED93034AA,
|
||||
9710CA4A388CBE265276E667,
|
||||
); path = Resources; sourceTree = "<group>"; };
|
||||
156052F04525D0F710673CF0 = {isa = PBXGroup; children = (
|
||||
36A67B6FF0A52868D30E3AFF,
|
||||
); path = Tests; sourceTree = "<group>"; };
|
||||
E461FA3D1EC1D072C116F41B = {isa = PBXGroup; children = (
|
||||
1C48CE29E4E135EE62951B22,
|
||||
410188F9DC6E499FEC68BCF6,
|
||||
1F26A8704D8CDB03D5D3D4BB,
|
||||
A024F06580C79B7582C3806A,
|
||||
156052F04525D0F710673CF0,
|
||||
5151B46607EBC8890D8DFCC2,
|
||||
); sourceTree = "<group>"; };
|
||||
/* End PBXGroup section */
|
||||
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
E9FB55C6F47F1FFA66FE0D94 = {isa = PBXNativeTarget; buildConfigurationList = 6999223B144664B5601B7EE0; buildPhases = (
|
||||
B70F6135A878AF9C52651F99,
|
||||
42F0FBEA30B4CA87317E015D,
|
||||
DFA634CB8CCD163EA3930D97,
|
||||
); buildRules = (); dependencies = (); name = HermesApp; productName = HermesApp; productReference = 3A8D22D1A79B415AD4892F84; productType = "com.apple.product-type.application"; };
|
||||
32127D1229D93E1AD0281CF5 = {isa = PBXNativeTarget; buildConfigurationList = 82A4B6F3DEB17576E838F4C6; buildPhases = (
|
||||
0A58D7436E03725AA2604F91,
|
||||
4F6B784AA6FDB9607A3034BB,
|
||||
); buildRules = (); dependencies = (
|
||||
54AF1B5F109B8B15F06D32EB,
|
||||
); name = HermesAppTests; productName = HermesAppTests; productReference = C1368E6B7C6BAAA50C8B38A8; productType = "com.apple.product-type.bundle.unit-test"; };
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
|
||||
/* Begin PBXProject section */
|
||||
63E87276D8BD98B1C9DA3FF1 = {isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1600; LastUpgradeCheck = 1600; }; buildConfigurationList = FCBE72429C0B1B8B87DC8CC9; compatibilityVersion = "Xcode 16.0"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = (
|
||||
en,
|
||||
sv,
|
||||
); mainGroup = E461FA3D1EC1D072C116F41B; packageReferences = (); productRefGroup = 5151B46607EBC8890D8DFCC2; projectDirPath = ""; projectRoot = ""; targets = (
|
||||
E9FB55C6F47F1FFA66FE0D94,
|
||||
32127D1229D93E1AD0281CF5,
|
||||
); };
|
||||
/* End PBXProject section */
|
||||
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
54AF1B5F109B8B15F06D32EB = {isa = PBXTargetDependency; target = E9FB55C6F47F1FFA66FE0D94; targetProxy = CCF863E2FFE2061635563672; };
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
B70F6135A878AF9C52651F99 = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; };
|
||||
0A58D7436E03725AA2604F91 = {isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = (); runOnlyForDeploymentPostprocessing = 0; };
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
42F0FBEA30B4CA87317E015D = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (
|
||||
3E1C98162835584FF4929E35,
|
||||
706106BBB737DEF1952FED6F,
|
||||
594A855A0BE13416458DA3A6,
|
||||
D3B7534C86065FFF51A5C55D,
|
||||
2D43EFE45D26F6A8E55BF3AC,
|
||||
F9006995CFE1CCF0D38AD3AA,
|
||||
913CFC8BADBC80B024D2C856,
|
||||
AD46D096136FB1F118855D68,
|
||||
B3C9966AC80EBB9B64A07C99,
|
||||
BFC8A4A81EE80733E4AA7323,
|
||||
2C7F98F2653480078868DBF4,
|
||||
5384DC2D3DC830A67B2A1054,
|
||||
1E2027DC632793AF7E962016,
|
||||
C1DB325D4807CDE56F820173,
|
||||
1A95F35F994CB9F20DF09556,
|
||||
9BA60A645924EEE65B60E9C1,
|
||||
F2FA36C9D20CD77F0045E16E,
|
||||
72B9487B80FFA7D58F0CF66A,
|
||||
739163E3DBD5F43A84C49818,
|
||||
A99DDCF27A5B1368CCA1EFB5,
|
||||
C8B084764EFF305D4AD0ADDF,
|
||||
); runOnlyForDeploymentPostprocessing = 0; };
|
||||
4F6B784AA6FDB9607A3034BB = {isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = (
|
||||
FCF2BB724CCC6B6974C426F8,
|
||||
); runOnlyForDeploymentPostprocessing = 0; };
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
DFA634CB8CCD163EA3930D97 = {isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = (
|
||||
7E17EFEA80A73B8C3179D66C,
|
||||
9FE2C1B4C0DA9FE3AEF298AF,
|
||||
); runOnlyForDeploymentPostprocessing = 0; };
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
C761B7D49C3C32C5F60E8010 = {isa = XCBuildConfiguration; buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
DEVELOPMENT_REGION = en;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SWIFT_VERSION = 5.0;
|
||||
}; name = Debug; };
|
||||
ACCCD85F61979F8ECCA377BC = {isa = XCBuildConfiguration; buildSettings = {
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
DEVELOPMENT_REGION = en;
|
||||
SDKROOT = iphoneos;
|
||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
||||
SWIFT_VERSION = 5.0;
|
||||
}; name = Release; };
|
||||
A393E557BBA174C592175785 = {isa = XCBuildConfiguration; buildSettings = {
|
||||
CODE_SIGNING_ALLOWED = NO;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.hermes.app;
|
||||
PRODUCT_NAME = HermesApp;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
SWIFT_OPTIMIZATION_LEVEL = -Onone;
|
||||
}; name = Debug; };
|
||||
4848DE5FB3FF960B8E5B63DC = {isa = XCBuildConfiguration; buildSettings = {
|
||||
CODE_SIGNING_ALLOWED = NO;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.hermes.app;
|
||||
PRODUCT_NAME = HermesApp;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
SWIFT_OPTIMIZATION_LEVEL = -O;
|
||||
}; name = Release; };
|
||||
0D633BD545D878A6647A1EBE = {isa = XCBuildConfiguration; buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGNING_ALLOWED = NO;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_TESTING_SEARCH_PATHS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.hermes.appTests;
|
||||
PRODUCT_NAME = HermesAppTests;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HermesApp.app/HermesApp";
|
||||
SWIFT_OPTIMIZATION_LEVEL = -Onone;
|
||||
}; name = Debug; };
|
||||
43ABF9DDFB26DCC1D7D88137 = {isa = XCBuildConfiguration; buildSettings = {
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGNING_ALLOWED = NO;
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_TESTING_SEARCH_PATHS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.hermes.appTests;
|
||||
PRODUCT_NAME = HermesAppTests;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = 1;
|
||||
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/HermesApp.app/HermesApp";
|
||||
SWIFT_OPTIMIZATION_LEVEL = -O;
|
||||
}; name = Release; };
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
FCBE72429C0B1B8B87DC8CC9 = {isa = XCConfigurationList; buildConfigurations = (
|
||||
C761B7D49C3C32C5F60E8010,
|
||||
ACCCD85F61979F8ECCA377BC,
|
||||
); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; };
|
||||
6999223B144664B5601B7EE0 = {isa = XCConfigurationList; buildConfigurations = (
|
||||
A393E557BBA174C592175785,
|
||||
4848DE5FB3FF960B8E5B63DC,
|
||||
); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; };
|
||||
82A4B6F3DEB17576E838F4C6 = {isa = XCConfigurationList; buildConfigurations = (
|
||||
0D633BD545D878A6647A1EBE,
|
||||
43ABF9DDFB26DCC1D7D88137,
|
||||
); defaultConfigurationIsVisible = 0; defaultConfigurationName = Debug; };
|
||||
/* End XCConfigurationList section */
|
||||
|
||||
};
|
||||
rootObject = 63E87276D8BD98B1C9DA3FF1;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme LastUpgradeVersion="1600" version="1.7">
|
||||
<BuildAction parallelizeBuildables="YES" buildImplicitDependencies="YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry buildForTesting="YES" buildForRunning="YES" buildForProfiling="YES" buildForArchiving="YES" buildForAnalyzing="YES">
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="E9FB55C6F47F1FFA66FE0D94" BuildableName="HermesApp.app" BlueprintName="HermesApp" ReferencedContainer="container:HermesApp.xcodeproj"/>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry buildForTesting="YES" buildForRunning="NO" buildForProfiling="NO" buildForArchiving="NO" buildForAnalyzing="NO">
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="32127D1229D93E1AD0281CF5" BuildableName="HermesAppTests.xctest" BlueprintName="HermesAppTests" ReferencedContainer="container:HermesApp.xcodeproj"/>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv="YES">
|
||||
<Testables>
|
||||
<TestableReference skipped="NO">
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="32127D1229D93E1AD0281CF5" BuildableName="HermesAppTests.xctest" BlueprintName="HermesAppTests" ReferencedContainer="container:HermesApp.xcodeproj"/>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction buildConfiguration="Debug" selectedDebuggerIdentifier="Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier="Xcode.DebuggerFoundation.Launcher.LLDB" launchStyle="0" useCustomWorkingDirectory="NO" ignoresPersistentStateOnLaunch="NO" debugDocumentVersioning="YES" debugServiceExtension="internal" allowLocationSimulation="YES">
|
||||
<BuildableProductRunnable runnableDebuggingMode="0">
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="E9FB55C6F47F1FFA66FE0D94" BuildableName="HermesApp.app" BlueprintName="HermesApp" ReferencedContainer="container:HermesApp.xcodeproj"/>
|
||||
</BuildableProductRunnable>
|
||||
</LaunchAction>
|
||||
<ProfileAction buildConfiguration="Release" shouldUseLaunchSchemeArgsEnv="YES" savedToolIdentifier="" useCustomWorkingDirectory="NO" debugDocumentVersioning="YES">
|
||||
<BuildableProductRunnable runnableDebuggingMode="0">
|
||||
<BuildableReference BuildableIdentifier="primary" BlueprintIdentifier="E9FB55C6F47F1FFA66FE0D94" BuildableName="HermesApp.app" BlueprintName="HermesApp" ReferencedContainer="container:HermesApp.xcodeproj"/>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction buildConfiguration="Debug"/>
|
||||
<ArchiveAction buildConfiguration="Release" revealArchiveInOrganizer="YES"/>
|
||||
</Scheme>
|
||||
@@ -56,3 +56,9 @@
|
||||
"session.app_version_label" = "App version";
|
||||
"session.device_model_label" = "Device model";
|
||||
"session.os_version_label" = "OS version";
|
||||
"settings.title" = "Settings";
|
||||
"settings.subtitle" = "Language, haptics and analytics preferences.";
|
||||
"settings.language" = "Language";
|
||||
"settings.enabled" = "Enabled";
|
||||
"settings.haptics" = "Haptics";
|
||||
"settings.analytics" = "Analytics";
|
||||
|
||||
@@ -56,3 +56,9 @@
|
||||
"session.app_version_label" = "Appversion";
|
||||
"session.device_model_label" = "Enhetsmodell";
|
||||
"session.os_version_label" = "OS-version";
|
||||
"settings.title" = "Inställningar";
|
||||
"settings.subtitle" = "Språk-, haptik- och analysinställningar.";
|
||||
"settings.language" = "Språk";
|
||||
"settings.enabled" = "Aktiverad";
|
||||
"settings.haptics" = "Haptik";
|
||||
"settings.analytics" = "Analys";
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import XCTest
|
||||
@testable import HermesApp
|
||||
|
||||
private struct TestFailure: Error, CustomStringConvertible {
|
||||
let description: String
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class LocalizationStoreTests: XCTestCase {
|
||||
func testEnglishBundleStringsLoad() throws {
|
||||
let store = LocalizationStore(localeCode: "en")
|
||||
|
||||
try expectEqual(store.string(for: "app.name"), "Hermes")
|
||||
try expectEqual(store.string(for: "settings.title"), "Settings")
|
||||
try expectEqual(store.string(for: "settings.enabled"), "Enabled")
|
||||
}
|
||||
|
||||
func testSwedishBundleStringsLoad() throws {
|
||||
let store = LocalizationStore(localeCode: "sv")
|
||||
|
||||
try expectEqual(store.string(for: "app.name"), "Hermes")
|
||||
try expectEqual(store.string(for: "settings.title"), "Inställningar")
|
||||
try expectEqual(store.string(for: "settings.enabled"), "Aktiverad")
|
||||
}
|
||||
|
||||
func testUnsupportedLocaleFallsBackToEnglish() throws {
|
||||
let store = LocalizationStore(localeCode: "fr")
|
||||
|
||||
try expectEqual(store.string(for: "settings.analytics"), "Analytics")
|
||||
try expectEqual(store.localeName(for: "sv", displayLocaleCode: "fr"), "Swedish")
|
||||
}
|
||||
|
||||
private func expectEqual(_ actual: String, _ expected: String) throws {
|
||||
guard actual == expected else {
|
||||
throw TestFailure(description: "Expected \(expected), got \(actual)")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user