diff --git a/.gitignore b/.gitignore index 7dd042bb..051c6bb2 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ terraform.tfstate.* # Xcode user state (do not commit) **/xcuserdata/ **/*.xcuserstate + +# iOS app generated token (from apps/cms/.env at build time) +mobile/ios/App/ForgeApp/Generated/ diff --git a/apps/cms/.env.example b/apps/cms/.env.example index cfced4c1..fb798dd8 100644 --- a/apps/cms/.env.example +++ b/apps/cms/.env.example @@ -11,6 +11,9 @@ ENCRYPTION_KEY= DATABASE_CLIENT=sqlite DATABASE_FILENAME=.tmp/data.db +# Full-access API token for local Strapi (clients use as Bearer token; create in Admin → Settings → API Tokens) +STRAPI_FULL_ACCESS_TOKEN= + # Web integration (apps/web expects these; set in apps/web/.env) # Revalidate: web POST /api/revalidate expects x-forge-revalidate-token header (future Strapi webhook on publish) STRAPI_REVALIDATE_TOKEN= diff --git a/mobile/ios/.gitignore b/mobile/ios/.gitignore new file mode 100644 index 00000000..6ee8bae9 --- /dev/null +++ b/mobile/ios/.gitignore @@ -0,0 +1,2 @@ +# Apollo iOS CLI symlink (created by apollo-cli-install) +apollo-ios-cli diff --git a/mobile/ios/.swiftlint.yml b/mobile/ios/.swiftlint.yml index 7be817fa..f493fa6e 100644 --- a/mobile/ios/.swiftlint.yml +++ b/mobile/ios/.swiftlint.yml @@ -1,2 +1,6 @@ excluded: - .build + # Apollo codegen output; do not edit + - Sources/ForgeMobile/Generated + # Build-generated token from apps/cms/.env + - App/ForgeApp/Generated diff --git a/mobile/ios/App/ForgeApp.xcodeproj/project.pbxproj b/mobile/ios/App/ForgeApp.xcodeproj/project.pbxproj index dbca0a3c..6f961e26 100644 --- a/mobile/ios/App/ForgeApp.xcodeproj/project.pbxproj +++ b/mobile/ios/App/ForgeApp.xcodeproj/project.pbxproj @@ -8,11 +8,17 @@ /* Begin PBXBuildFile section */ A1B2C3D4E5F600000001 /* ForgeApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F600000002 /* ForgeApp.swift */; }; + A1B2C3D4E5F60000001C /* AppContentRepositoryFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60000001D /* AppContentRepositoryFactory.swift */; }; + A1B2C3D4E5F600000019 /* StrapiToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F600000017 /* StrapiToken.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + A1B2C3D4E5F60000001D /* AppContentRepositoryFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppContentRepositoryFactory.swift; sourceTree = ""; }; A1B2C3D4E5F600000002 /* ForgeApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForgeApp.swift; sourceTree = ""; }; A1B2C3D4E5F600000003 /* ForgeApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ForgeApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + A1B2C3D4E5F60000001A /* Info-Debug.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-Debug.plist"; sourceTree = ""; }; + A1B2C3D4E5F60000001B /* Info-Release.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Info-Release.plist"; sourceTree = ""; }; + A1B2C3D4E5F600000017 /* StrapiToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrapiToken.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -45,11 +51,23 @@ A1B2C3D4E5F600000006 /* ForgeApp */ = { isa = PBXGroup; children = ( + A1B2C3D4E5F60000001D /* AppContentRepositoryFactory.swift */, + A1B2C3D4E5F600000018 /* Generated */, A1B2C3D4E5F600000002 /* ForgeApp.swift */, + A1B2C3D4E5F60000001A /* Info-Debug.plist */, + A1B2C3D4E5F60000001B /* Info-Release.plist */, ); path = ForgeApp; sourceTree = ""; }; + A1B2C3D4E5F600000018 /* Generated */ = { + isa = PBXGroup; + children = ( + A1B2C3D4E5F600000017 /* StrapiToken.swift */, + ); + path = Generated; + sourceTree = ""; + }; A1B2C3D4E5F600000012 /* Package product dependencies */ = { isa = PBXGroup; children = ( @@ -64,6 +82,7 @@ isa = PBXNativeTarget; buildConfigurationList = A1B2C3D4E5F60000000F /* Build configuration list for PBXNativeTarget "ForgeApp" */; buildPhases = ( + A1B2C3D4E5F600000016 /* Generate Strapi token */, A1B2C3D4E5F600000007 /* Sources */, A1B2C3D4E5F600000008 /* Frameworks */, A1B2C3D4E5F600000009 /* Resources */, @@ -116,6 +135,24 @@ }; /* End PBXProject section */ +/* Begin PBXShellScriptBuildPhase section */ + A1B2C3D4E5F600000016 /* Generate Strapi token */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Generate Strapi token"; + outputPaths = ( + "$(PROJECT_DIR)/ForgeApp/Generated/StrapiToken.swift", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "mkdir -p \"${PROJECT_DIR}/ForgeApp/Generated\"\nOUT=\"${PROJECT_DIR}/ForgeApp/Generated/StrapiToken.swift\"\nif [ \"${CONFIGURATION}\" = \"Release\" ]; then\n echo '// Generated at build time — do not edit. Release: no token in binary.' > \"$OUT\"\n echo 'let kStrapiFullAccessToken: String? = nil' >> \"$OUT\"\nelse\n ENV_FILE=\"${PROJECT_DIR}/../../../apps/cms/.env\"\n if [ -f \"$ENV_FILE\" ]; then\n TOKEN=$(grep '^STRAPI_FULL_ACCESS_TOKEN=' \"$ENV_FILE\" 2>/dev/null | sed 's/^STRAPI_FULL_ACCESS_TOKEN=//' | tr -d '\\n' | sed 's/^ *//;s/ *$//')\n else\n TOKEN=\"\"\n fi\n if [ -z \"$TOKEN\" ]; then\n echo '// Generated at build time — do not edit. Debug: set STRAPI_FULL_ACCESS_TOKEN in apps/cms/.env or scheme.' > \"$OUT\"\n echo 'let kStrapiFullAccessToken: String? = nil' >> \"$OUT\"\n else\n ESCAPED=$(printf '%s' \"$TOKEN\" | sed 's/\\\\/\\\\\\\\/g; s/\"/\\\\\"/g')\n printf '// Generated at build time — do not edit. Debug only.\nlet kStrapiFullAccessToken: String? = \"%s\"\n' \"$ESCAPED\" > \"$OUT\"\n fi\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXResourcesBuildPhase section */ A1B2C3D4E5F600000009 /* Resources */ = { isa = PBXResourcesBuildPhase; @@ -131,7 +168,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A1B2C3D4E5F60000001C /* AppContentRepositoryFactory.swift in Sources */, A1B2C3D4E5F600000001 /* ForgeApp.swift in Sources */, + A1B2C3D4E5F600000019 /* StrapiToken.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -202,6 +241,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ForgeApp/Info-Debug.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; @@ -226,6 +266,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = ForgeApp/Info-Release.plist; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; diff --git a/mobile/ios/App/ForgeApp/AppContentRepositoryFactory.swift b/mobile/ios/App/ForgeApp/AppContentRepositoryFactory.swift new file mode 100644 index 00000000..1e71ec03 --- /dev/null +++ b/mobile/ios/App/ForgeApp/AppContentRepositoryFactory.swift @@ -0,0 +1,28 @@ +import Foundation +import ForgeMobile + +/// Builds the app's `ContentRepository` using GraphQL endpoint from Info.plist and optional bearer token. +enum AppContentRepositoryFactory { + /// GraphQL endpoint from Info.plist (GraphQLEndpoint). Debug falls back to localhost; Release requires a valid value. + private static var graphQLURL: URL { + let key = "GraphQLEndpoint" + let raw = Bundle.main.object(forInfoDictionaryKey: key) as? String + let trimmed = raw?.trimmingCharacters(in: .whitespacesAndNewlines) + if let endpointString = trimmed, !endpointString.isEmpty, let url = URL(string: endpointString) { + return url + } + #if DEBUG + return URL(string: "http://localhost:1337/graphql")! + #else + fatalError("\(key) must be set in Info-Release.plist for production builds.") + #endif + } + + static func makeContentRepository() -> ContentRepository { + let token = ProcessInfo.processInfo.environment["STRAPI_FULL_ACCESS_TOKEN"] + .flatMap { $0.isEmpty ? nil : $0 } + ?? kStrapiFullAccessToken + let client = GraphQLContentClient(endpoint: graphQLURL, bearerToken: token) + return ContentRepository(client: client) + } +} diff --git a/mobile/ios/App/ForgeApp/ForgeApp.swift b/mobile/ios/App/ForgeApp/ForgeApp.swift index 3c08ea22..67437c99 100644 --- a/mobile/ios/App/ForgeApp/ForgeApp.swift +++ b/mobile/ios/App/ForgeApp/ForgeApp.swift @@ -5,7 +5,7 @@ import ForgeMobile struct ForgeApp: App { var body: some Scene { WindowGroup { - ForgeRootView() + ForgeRootView(contentRepository: AppContentRepositoryFactory.makeContentRepository()) } } } diff --git a/mobile/ios/App/ForgeApp/Info-Debug.plist b/mobile/ios/App/ForgeApp/Info-Debug.plist new file mode 100644 index 00000000..9edff843 --- /dev/null +++ b/mobile/ios/App/ForgeApp/Info-Debug.plist @@ -0,0 +1,19 @@ + + + + + GraphQLEndpoint + http://localhost:1337/graphql + NSAppTransportSecurity + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + + diff --git a/mobile/ios/App/ForgeApp/Info-Release.plist b/mobile/ios/App/ForgeApp/Info-Release.plist new file mode 100644 index 00000000..73eab282 --- /dev/null +++ b/mobile/ios/App/ForgeApp/Info-Release.plist @@ -0,0 +1,8 @@ + + + + + GraphQLEndpoint + + + diff --git a/mobile/ios/GraphQL/Operations/GetWatchExperience.graphql b/mobile/ios/GraphQL/Operations/GetWatchExperience.graphql new file mode 100644 index 00000000..a10bf80a --- /dev/null +++ b/mobile/ios/GraphQL/Operations/GetWatchExperience.graphql @@ -0,0 +1,48 @@ +query GetWatchExperience( + $locale: I18NLocaleCode! + $filters: ExperienceFiltersInput! +) { + experiences(filters: $filters, locale: $locale) { + documentId + slug + publishedAt + sections { + ... on ComponentSectionsMediaCollection { + id + title + subtitle + mediaCollectionDescription: description + categoryLabel + mediaCollectionCtaLink: ctaLink + showItemNumbers + variant + } + ... on ComponentSectionsPromoBanner { + id + promoBannerHeading: heading + promoBannerDescription: description + intro + promoBannerCtaLink: ctaLink + } + ... on ComponentSectionsInfoBlocks { + id + infoBlocksHeading: heading + intro + infoBlocksDescription: description + blocks { + id + title + description + icon + } + } + ... on ComponentSectionsCta { + id + ctaHeading: heading + body + buttonLabel + buttonLink + } + } + } +} diff --git a/mobile/ios/Package.resolved b/mobile/ios/Package.resolved new file mode 100644 index 00000000..32bd22ba --- /dev/null +++ b/mobile/ios/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "apollo-ios", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apollographql/apollo-ios.git", + "state" : { + "revision" : "1abd62f6cbc8c1f4405919d7eb6cd8e96967b07c", + "version" : "1.25.3" + } + } + ], + "version" : 2 +} diff --git a/mobile/ios/Package.swift b/mobile/ios/Package.swift index dae61e85..e9ba5cc2 100644 --- a/mobile/ios/Package.swift +++ b/mobile/ios/Package.swift @@ -7,10 +7,15 @@ let package = Package( products: [ .library(name: "ForgeMobile", targets: ["ForgeMobile"]) ], + dependencies: [ + .package(url: "https://github.com/apollographql/apollo-ios.git", from: "1.0.0") + ], targets: [ .target( name: "ForgeMobile", - dependencies: [], + dependencies: [ + .product(name: "Apollo", package: "apollo-ios") + ], path: "Sources/ForgeMobile" ) ] diff --git a/mobile/ios/README.md b/mobile/ios/README.md index 6102bec7..4c4aad6f 100644 --- a/mobile/ios/README.md +++ b/mobile/ios/README.md @@ -2,7 +2,9 @@ Native SwiftUI app. Outside Turborepo graph. -Integrates via ContentClient; implement using `packages/graphql` GraphQL types. +Integrates via **ContentClient**. A GraphQL implementation is provided: **GraphQLContentClient** (endpoint URL + optional bearer token). Construct it with your CMS GraphQL URL (e.g. dev/stage/prod) and pass it to `ContentRepository(client:)`. The app uses **MVVM**: views depend only on **ViewModels**; ViewModels own the repository and expose state and actions (e.g. `WatchHomeViewModel` and `load(locale:)`). + +**ForgeMobile library layout** (under `Sources/ForgeMobile/`): **Data/** — `ContentRepository`, `GraphQLContentClient`, `ContentClient`, `MobileContentItem`; **ViewModels/** — screen ViewModels (e.g. `WatchHomeViewModel`); **Views/** — SwiftUI views (e.g. `ForgeRootView`); **Generated/** — Apollo-generated types and operations (do not edit). ## Building and running @@ -16,6 +18,17 @@ Integrates via ContentClient; implement using `packages/graphql` GraphQL types. App source lives under **App/ForgeApp/**; the **ForgeMobile** library is in **Sources/ForgeMobile**. The app target depends on the local ForgeMobile Swift package (one level up from the project); Xcode resolves it automatically when you open the project. +**Local Strapi with API token:** + +- **Debug (e.g. run from Cursor/SweetPad):** A build-phase script reads `STRAPI_FULL_ACCESS_TOKEN` from `apps/cms/.env` and attaches it to GraphQL requests. Ensure `apps/cms/.env` has that variable set; no Xcode scheme setup needed. +- **Release:** The script never embeds a token (always `nil`), so the shipped binary stays safe. Use a backend or runtime token for production. + +You can still override by setting the **environment variable** `STRAPI_FULL_ACCESS_TOKEN` in the run scheme (Edit Scheme → Run → Environment Variables); the app uses env first, then the generated value. + +If you see **"Error: Forbidden access"**, the request is reaching Strapi without a valid token—for Debug from Cursor, add `STRAPI_FULL_ACCESS_TOKEN` to `apps/cms/.env` and rebuild. + +**Info plists and App Store:** **Debug** builds use `Info-Debug.plist` (allows HTTP to localhost for local Strapi). **Release** builds (including **Archive** for App Store Connect) use `Info-Release.plist`, which has no ATS exception, so the shipped app is ATS-clean for review. + ### Command line From this directory (`mobile/ios`). Requires **Xcode 16+** (Swift 6). Point `xcodebuild` at the app project in `App/`: @@ -30,3 +43,20 @@ xcodebuild -project App/ForgeApp.xcodeproj -scheme ForgeApp -destination 'platfo # Build for a generic iOS Simulator destination xcodebuild -project App/ForgeApp.xcodeproj -scheme ForgeApp -destination 'generic/platform=iOS Simulator' build ``` + +## GraphQL and codegen + +The **ForgeMobile** package uses [Apollo iOS](https://www.apollographql.com/docs/ios/) for the CMS GraphQL client. Schema and operations: + +- **Schema**: `apps/cms/schema.graphql` (repo root). +- **Operations**: `GraphQL/Operations/*.graphql` (e.g. `GetWatchExperience.graphql`). +- **Generated Swift**: `Sources/ForgeMobile/Generated/` (do not edit by hand). + +To regenerate after schema or operation changes, **run from `mobile/ios`** (required: the schema path in config is relative and is resolved from the current working directory): + +1. Install the Apollo CLI (once): + `swift package --allow-writing-to-package-directory --allow-network-connections all apollo-cli-install` +2. Generate: + `./apollo-ios-cli generate -p apollo-codegen-configuration.json` + +Config: `apollo-codegen-configuration.json` (embedded in ForgeMobile target; `schemaSearchPaths` uses `../../apps/cms/schema.graphql`). Any CI or script that runs codegen must use `mobile/ios` as the working directory. diff --git a/mobile/ios/Sources/ForgeMobile/ContentRepository.swift b/mobile/ios/Sources/ForgeMobile/Data/ContentRepository.swift similarity index 90% rename from mobile/ios/Sources/ForgeMobile/ContentRepository.swift rename to mobile/ios/Sources/ForgeMobile/Data/ContentRepository.swift index 81439984..9f09c345 100644 --- a/mobile/ios/Sources/ForgeMobile/ContentRepository.swift +++ b/mobile/ios/Sources/ForgeMobile/Data/ContentRepository.swift @@ -20,6 +20,6 @@ public final class ContentRepository { } public func fetchHome(locale: String) async throws -> MobileContentItem? { - try await client.getContent(locale: locale, slug: "home") + try await client.getContent(locale: locale, slug: "experience") } } diff --git a/mobile/ios/Sources/ForgeMobile/Data/GraphQLContentClient.swift b/mobile/ios/Sources/ForgeMobile/Data/GraphQLContentClient.swift new file mode 100644 index 00000000..67455239 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Data/GraphQLContentClient.swift @@ -0,0 +1,114 @@ +import Apollo +import ApolloAPI +import Foundation + +/// GraphQL client that implements `ContentClient` by calling the CMS GetWatchExperience query. +/// Configure with endpoint URL and optional bearer token (e.g. for dev/stage/prod). +public final class GraphQLContentClient: ContentClient { + private let apollo: ApolloClient + + /// Use this initializer to inject an `ApolloClient` (e.g. for tests with a mock). + public init(apollo: ApolloClient) { + self.apollo = apollo + } + + /// Creates a client that talks to the given endpoint with optional bearer auth. + public convenience init(endpoint: URL, bearerToken: String? = nil) { + let store = ApolloStore(cache: InMemoryNormalizedCache()) + let trimmedToken = bearerToken?.trimmingCharacters(in: .whitespacesAndNewlines) + let headers: [String: String] = if let token = trimmedToken, !token.isEmpty { + ["Authorization": "Bearer \(token)"] + } else { + [:] + } + let transport = RequestChainNetworkTransport( + interceptorProvider: DefaultInterceptorProvider(store: store), + endpointURL: endpoint, + additionalHeaders: headers + ) + let apollo = ApolloClient(networkTransport: transport, store: store) + self.init(apollo: apollo) + } + + public func getContent(locale: String, slug: String) async throws -> MobileContentItem? { + let filters = ForgeSchema.ExperienceFiltersInput( + slug: .some(ForgeSchema.StringFilterInput(eq: .some(slug))) + ) + let query = ForgeSchema.GetWatchExperienceQuery( + locale: locale, + filters: filters + ) + let result = await withCheckedContinuation { continuation in + apollo.fetch(query: query, cachePolicy: .fetchIgnoringCacheData) { result in + continuation.resume(returning: result) + } + } + switch result { + case .success(let graphQLResult): + if let errors = graphQLResult.errors, !errors.isEmpty { + throw GraphQLContentClientError.graphQLErrors(errors) + } + guard let data = graphQLResult.data else { + return nil + } + guard let first = data.experiences.compactMap({ $0 }).first else { + return nil + } + return mapExperienceToContentItem(experience: first, locale: locale) + case .failure(let error): + throw error + } + } + + private func mapExperienceToContentItem( + experience: ForgeSchema.GetWatchExperienceQuery.Data.Experience, + locale: String + ) -> MobileContentItem { + let title = firstSectionTitle(from: experience.sections) ?? experience.slug + let state = experience.publishedAt != nil ? "published" : "draft" + // body: Experience has no root-level body in the schema; not requested in the query. Leave empty. + return MobileContentItem( + id: experience.documentId, + slug: experience.slug, + locale: locale, + title: title, + body: "", + state: state + ) + } + + private func firstSectionTitle( + from sections: [ForgeSchema.GetWatchExperienceQuery.Data.Experience.Section?]? + ) -> String? { + guard let sections = sections else { return nil } + for section in sections.compactMap({ $0 }) { + if let media = section.asComponentSectionsMediaCollection, + let title = media.title, !title.isEmpty { + return title + } + if let promo = section.asComponentSectionsPromoBanner, !promo.promoBannerHeading.isEmpty { + return promo.promoBannerHeading + } + if let info = section.asComponentSectionsInfoBlocks, + let heading = info.infoBlocksHeading, !heading.isEmpty { + return heading + } + if let cta = section.asComponentSectionsCta, !cta.ctaHeading.isEmpty { + return cta.ctaHeading + } + } + return nil + } +} + +public enum GraphQLContentClientError: Error, LocalizedError { + case graphQLErrors([GraphQLError]) + + public var errorDescription: String? { + switch self { + case .graphQLErrors(let errors): + let messages = errors.compactMap(\.message) + return messages.isEmpty ? "GraphQL error" : messages.joined(separator: "; ") + } + } +} diff --git a/mobile/ios/Sources/ForgeMobile/ForgeRootView.swift b/mobile/ios/Sources/ForgeMobile/ForgeRootView.swift deleted file mode 100644 index 408563c1..00000000 --- a/mobile/ios/Sources/ForgeMobile/ForgeRootView.swift +++ /dev/null @@ -1,10 +0,0 @@ -import SwiftUI - -public struct ForgeRootView: View { - public init() {} - - public var body: some View { - Text("Forge iOS") - .accessibilityLabel("Forge iOS") - } -} diff --git a/mobile/ios/Sources/ForgeMobile/Generated/ForgeSchema.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/ForgeSchema.graphql.swift new file mode 100644 index 00000000..401674fb --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/ForgeSchema.graphql.swift @@ -0,0 +1,4 @@ +// @generated +// This file was automatically generated and should not be edited. + +enum ForgeSchema { } diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Operations/Queries/GetWatchExperienceQuery.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Operations/Queries/GetWatchExperienceQuery.graphql.swift new file mode 100644 index 00000000..514ba036 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Operations/Queries/GetWatchExperienceQuery.graphql.swift @@ -0,0 +1,242 @@ +// @generated +// This file was automatically generated and should not be edited. + +@_exported import ApolloAPI + +extension ForgeSchema { + class GetWatchExperienceQuery: GraphQLQuery { + static let operationName: String = "GetWatchExperience" + static let operationDocument: ApolloAPI.OperationDocument = .init( + definition: .init( + #"query GetWatchExperience($locale: I18NLocaleCode!, $filters: ExperienceFiltersInput!) { experiences(filters: $filters, locale: $locale) { __typename documentId slug publishedAt sections { __typename ... on ComponentSectionsMediaCollection { id title subtitle mediaCollectionDescription: description categoryLabel mediaCollectionCtaLink: ctaLink showItemNumbers variant } ... on ComponentSectionsPromoBanner { id promoBannerHeading: heading promoBannerDescription: description intro promoBannerCtaLink: ctaLink } ... on ComponentSectionsInfoBlocks { id infoBlocksHeading: heading intro infoBlocksDescription: description blocks { __typename id title description icon } } ... on ComponentSectionsCta { id ctaHeading: heading body buttonLabel buttonLink } } } }"# + )) + + public var locale: I18NLocaleCode + public var filters: ExperienceFiltersInput + + public init( + locale: I18NLocaleCode, + filters: ExperienceFiltersInput + ) { + self.locale = locale + self.filters = filters + } + + public var __variables: Variables? { [ + "locale": locale, + "filters": filters + ] } + + struct Data: ForgeSchema.SelectionSet { + let __data: DataDict + init(_dataDict: DataDict) { __data = _dataDict } + + static var __parentType: any ApolloAPI.ParentType { ForgeSchema.Objects.Query } + static var __selections: [ApolloAPI.Selection] { [ + .field("experiences", [Experience?].self, arguments: [ + "filters": .variable("filters"), + "locale": .variable("locale") + ]), + ] } + static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [ + GetWatchExperienceQuery.Data.self + ] } + + var experiences: [Experience?] { __data["experiences"] } + + /// Experience + /// + /// Parent Type: `Experience` + struct Experience: ForgeSchema.SelectionSet { + let __data: DataDict + init(_dataDict: DataDict) { __data = _dataDict } + + static var __parentType: any ApolloAPI.ParentType { ForgeSchema.Objects.Experience } + static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("documentId", ForgeSchema.ID.self), + .field("slug", String.self), + .field("publishedAt", ForgeSchema.DateTime?.self), + .field("sections", [Section?]?.self), + ] } + static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [ + GetWatchExperienceQuery.Data.Experience.self + ] } + + var documentId: ForgeSchema.ID { __data["documentId"] } + var slug: String { __data["slug"] } + var publishedAt: ForgeSchema.DateTime? { __data["publishedAt"] } + var sections: [Section?]? { __data["sections"] } + + /// Experience.Section + /// + /// Parent Type: `ExperienceSectionsDynamicZone` + struct Section: ForgeSchema.SelectionSet { + let __data: DataDict + init(_dataDict: DataDict) { __data = _dataDict } + + static var __parentType: any ApolloAPI.ParentType { ForgeSchema.Unions.ExperienceSectionsDynamicZone } + static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .inlineFragment(AsComponentSectionsMediaCollection.self), + .inlineFragment(AsComponentSectionsPromoBanner.self), + .inlineFragment(AsComponentSectionsInfoBlocks.self), + .inlineFragment(AsComponentSectionsCta.self), + ] } + static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [ + GetWatchExperienceQuery.Data.Experience.Section.self + ] } + + var asComponentSectionsMediaCollection: AsComponentSectionsMediaCollection? { _asInlineFragment() } + var asComponentSectionsPromoBanner: AsComponentSectionsPromoBanner? { _asInlineFragment() } + var asComponentSectionsInfoBlocks: AsComponentSectionsInfoBlocks? { _asInlineFragment() } + var asComponentSectionsCta: AsComponentSectionsCta? { _asInlineFragment() } + + /// Experience.Section.AsComponentSectionsMediaCollection + /// + /// Parent Type: `ComponentSectionsMediaCollection` + struct AsComponentSectionsMediaCollection: ForgeSchema.InlineFragment { + let __data: DataDict + init(_dataDict: DataDict) { __data = _dataDict } + + typealias RootEntityType = GetWatchExperienceQuery.Data.Experience.Section + static var __parentType: any ApolloAPI.ParentType { ForgeSchema.Objects.ComponentSectionsMediaCollection } + static var __selections: [ApolloAPI.Selection] { [ + .field("id", ForgeSchema.ID.self), + .field("title", String?.self), + .field("subtitle", String?.self), + .field("description", alias: "mediaCollectionDescription", String?.self), + .field("categoryLabel", String?.self), + .field("ctaLink", alias: "mediaCollectionCtaLink", String?.self), + .field("showItemNumbers", Bool?.self), + .field("variant", GraphQLEnum.self), + ] } + static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [ + GetWatchExperienceQuery.Data.Experience.Section.self, + GetWatchExperienceQuery.Data.Experience.Section.AsComponentSectionsMediaCollection.self + ] } + + var id: ForgeSchema.ID { __data["id"] } + var title: String? { __data["title"] } + var subtitle: String? { __data["subtitle"] } + var mediaCollectionDescription: String? { __data["mediaCollectionDescription"] } + var categoryLabel: String? { __data["categoryLabel"] } + var mediaCollectionCtaLink: String? { __data["mediaCollectionCtaLink"] } + var showItemNumbers: Bool? { __data["showItemNumbers"] } + var variant: GraphQLEnum { __data["variant"] } + } + + /// Experience.Section.AsComponentSectionsPromoBanner + /// + /// Parent Type: `ComponentSectionsPromoBanner` + struct AsComponentSectionsPromoBanner: ForgeSchema.InlineFragment { + let __data: DataDict + init(_dataDict: DataDict) { __data = _dataDict } + + typealias RootEntityType = GetWatchExperienceQuery.Data.Experience.Section + static var __parentType: any ApolloAPI.ParentType { ForgeSchema.Objects.ComponentSectionsPromoBanner } + static var __selections: [ApolloAPI.Selection] { [ + .field("id", ForgeSchema.ID.self), + .field("heading", alias: "promoBannerHeading", String.self), + .field("description", alias: "promoBannerDescription", String.self), + .field("intro", String?.self), + .field("ctaLink", alias: "promoBannerCtaLink", String.self), + ] } + static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [ + GetWatchExperienceQuery.Data.Experience.Section.self, + GetWatchExperienceQuery.Data.Experience.Section.AsComponentSectionsPromoBanner.self + ] } + + var id: ForgeSchema.ID { __data["id"] } + var promoBannerHeading: String { __data["promoBannerHeading"] } + var promoBannerDescription: String { __data["promoBannerDescription"] } + var intro: String? { __data["intro"] } + var promoBannerCtaLink: String { __data["promoBannerCtaLink"] } + } + + /// Experience.Section.AsComponentSectionsInfoBlocks + /// + /// Parent Type: `ComponentSectionsInfoBlocks` + struct AsComponentSectionsInfoBlocks: ForgeSchema.InlineFragment { + let __data: DataDict + init(_dataDict: DataDict) { __data = _dataDict } + + typealias RootEntityType = GetWatchExperienceQuery.Data.Experience.Section + static var __parentType: any ApolloAPI.ParentType { ForgeSchema.Objects.ComponentSectionsInfoBlocks } + static var __selections: [ApolloAPI.Selection] { [ + .field("id", ForgeSchema.ID.self), + .field("heading", alias: "infoBlocksHeading", String?.self), + .field("intro", String?.self), + .field("description", alias: "infoBlocksDescription", String?.self), + .field("blocks", [Block?]?.self), + ] } + static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [ + GetWatchExperienceQuery.Data.Experience.Section.self, + GetWatchExperienceQuery.Data.Experience.Section.AsComponentSectionsInfoBlocks.self + ] } + + var id: ForgeSchema.ID { __data["id"] } + var infoBlocksHeading: String? { __data["infoBlocksHeading"] } + var intro: String? { __data["intro"] } + var infoBlocksDescription: String? { __data["infoBlocksDescription"] } + var blocks: [Block?]? { __data["blocks"] } + + /// Experience.Section.AsComponentSectionsInfoBlocks.Block + /// + /// Parent Type: `ComponentSectionsInfoBlock` + struct Block: ForgeSchema.SelectionSet { + let __data: DataDict + init(_dataDict: DataDict) { __data = _dataDict } + + static var __parentType: any ApolloAPI.ParentType { ForgeSchema.Objects.ComponentSectionsInfoBlock } + static var __selections: [ApolloAPI.Selection] { [ + .field("__typename", String.self), + .field("id", ForgeSchema.ID.self), + .field("title", String.self), + .field("description", String.self), + .field("icon", String.self), + ] } + static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [ + GetWatchExperienceQuery.Data.Experience.Section.AsComponentSectionsInfoBlocks.Block.self + ] } + + var id: ForgeSchema.ID { __data["id"] } + var title: String { __data["title"] } + var description: String { __data["description"] } + var icon: String { __data["icon"] } + } + } + + /// Experience.Section.AsComponentSectionsCta + /// + /// Parent Type: `ComponentSectionsCta` + struct AsComponentSectionsCta: ForgeSchema.InlineFragment { + let __data: DataDict + init(_dataDict: DataDict) { __data = _dataDict } + + typealias RootEntityType = GetWatchExperienceQuery.Data.Experience.Section + static var __parentType: any ApolloAPI.ParentType { ForgeSchema.Objects.ComponentSectionsCta } + static var __selections: [ApolloAPI.Selection] { [ + .field("id", ForgeSchema.ID.self), + .field("heading", alias: "ctaHeading", String.self), + .field("body", String.self), + .field("buttonLabel", String.self), + .field("buttonLink", String.self), + ] } + static var __fulfilledFragments: [any ApolloAPI.SelectionSet.Type] { [ + GetWatchExperienceQuery.Data.Experience.Section.self, + GetWatchExperienceQuery.Data.Experience.Section.AsComponentSectionsCta.self + ] } + + var id: ForgeSchema.ID { __data["id"] } + var ctaHeading: String { __data["ctaHeading"] } + var body: String { __data["body"] } + var buttonLabel: String { __data["buttonLabel"] } + var buttonLink: String { __data["buttonLink"] } + } + } + } + } + } + +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/CustomScalars/DateTime.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/CustomScalars/DateTime.swift new file mode 100644 index 00000000..cf935b50 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/CustomScalars/DateTime.swift @@ -0,0 +1,14 @@ +// @generated +// This file was automatically generated and can be edited to +// implement advanced custom scalar functionality. +// +// Any changes to this file will not be overwritten by future +// code generation execution. + +import ApolloAPI + +extension ForgeSchema { + /// A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. + typealias DateTime = String + +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/CustomScalars/I18NLocaleCode.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/CustomScalars/I18NLocaleCode.swift new file mode 100644 index 00000000..73c2bfcf --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/CustomScalars/I18NLocaleCode.swift @@ -0,0 +1,14 @@ +// @generated +// This file was automatically generated and can be edited to +// implement advanced custom scalar functionality. +// +// Any changes to this file will not be overwritten by future +// code generation execution. + +import ApolloAPI + +extension ForgeSchema { + /// A string used to identify an i18n locale + typealias I18NLocaleCode = String + +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/CustomScalars/ID.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/CustomScalars/ID.swift new file mode 100644 index 00000000..8af7b634 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/CustomScalars/ID.swift @@ -0,0 +1,14 @@ +// @generated +// This file was automatically generated and can be edited to +// implement advanced custom scalar functionality. +// +// Any changes to this file will not be overwritten by future +// code generation execution. + +import ApolloAPI + +extension ForgeSchema { + /// The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID. + typealias ID = String + +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/Enums/ENUM_COMPONENTSECTIONSMEDIACOLLECTION_VARIANT.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Enums/ENUM_COMPONENTSECTIONSMEDIACOLLECTION_VARIANT.graphql.swift new file mode 100644 index 00000000..0513b48f --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Enums/ENUM_COMPONENTSECTIONSMEDIACOLLECTION_VARIANT.graphql.swift @@ -0,0 +1,15 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema { + enum ENUM_COMPONENTSECTIONSMEDIACOLLECTION_VARIANT: String, EnumType { + case carousel = "carousel" + case collection = "collection" + case grid = "grid" + case hero = "hero" + case player = "player" + } + +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/BooleanFilterInput.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/BooleanFilterInput.graphql.swift new file mode 100644 index 00000000..96450669 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/BooleanFilterInput.graphql.swift @@ -0,0 +1,175 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema { + struct BooleanFilterInput: InputObject { + private(set) var __data: InputDict + + init(_ data: InputDict) { + __data = data + } + + init( + and: GraphQLNullable<[Bool?]> = nil, + between: GraphQLNullable<[Bool?]> = nil, + contains: GraphQLNullable = nil, + containsi: GraphQLNullable = nil, + endsWith: GraphQLNullable = nil, + eq: GraphQLNullable = nil, + eqi: GraphQLNullable = nil, + gt: GraphQLNullable = nil, + gte: GraphQLNullable = nil, + `in`: GraphQLNullable<[Bool?]> = nil, + lt: GraphQLNullable = nil, + lte: GraphQLNullable = nil, + ne: GraphQLNullable = nil, + nei: GraphQLNullable = nil, + not: GraphQLNullable = nil, + notContains: GraphQLNullable = nil, + notContainsi: GraphQLNullable = nil, + notIn: GraphQLNullable<[Bool?]> = nil, + notNull: GraphQLNullable = nil, + null: GraphQLNullable = nil, + or: GraphQLNullable<[Bool?]> = nil, + startsWith: GraphQLNullable = nil + ) { + __data = InputDict([ + "and": and, + "between": between, + "contains": contains, + "containsi": containsi, + "endsWith": endsWith, + "eq": eq, + "eqi": eqi, + "gt": gt, + "gte": gte, + "in": `in`, + "lt": lt, + "lte": lte, + "ne": ne, + "nei": nei, + "not": not, + "notContains": notContains, + "notContainsi": notContainsi, + "notIn": notIn, + "notNull": notNull, + "null": null, + "or": or, + "startsWith": startsWith + ]) + } + + var and: GraphQLNullable<[Bool?]> { + get { __data["and"] } + set { __data["and"] = newValue } + } + + var between: GraphQLNullable<[Bool?]> { + get { __data["between"] } + set { __data["between"] = newValue } + } + + var contains: GraphQLNullable { + get { __data["contains"] } + set { __data["contains"] = newValue } + } + + var containsi: GraphQLNullable { + get { __data["containsi"] } + set { __data["containsi"] = newValue } + } + + var endsWith: GraphQLNullable { + get { __data["endsWith"] } + set { __data["endsWith"] = newValue } + } + + var eq: GraphQLNullable { + get { __data["eq"] } + set { __data["eq"] = newValue } + } + + var eqi: GraphQLNullable { + get { __data["eqi"] } + set { __data["eqi"] = newValue } + } + + var gt: GraphQLNullable { + get { __data["gt"] } + set { __data["gt"] = newValue } + } + + var gte: GraphQLNullable { + get { __data["gte"] } + set { __data["gte"] = newValue } + } + + var `in`: GraphQLNullable<[Bool?]> { + get { __data["in"] } + set { __data["in"] = newValue } + } + + var lt: GraphQLNullable { + get { __data["lt"] } + set { __data["lt"] = newValue } + } + + var lte: GraphQLNullable { + get { __data["lte"] } + set { __data["lte"] = newValue } + } + + var ne: GraphQLNullable { + get { __data["ne"] } + set { __data["ne"] = newValue } + } + + var nei: GraphQLNullable { + get { __data["nei"] } + set { __data["nei"] = newValue } + } + + var not: GraphQLNullable { + get { __data["not"] } + set { __data["not"] = newValue } + } + + var notContains: GraphQLNullable { + get { __data["notContains"] } + set { __data["notContains"] = newValue } + } + + var notContainsi: GraphQLNullable { + get { __data["notContainsi"] } + set { __data["notContainsi"] = newValue } + } + + var notIn: GraphQLNullable<[Bool?]> { + get { __data["notIn"] } + set { __data["notIn"] = newValue } + } + + var notNull: GraphQLNullable { + get { __data["notNull"] } + set { __data["notNull"] = newValue } + } + + var null: GraphQLNullable { + get { __data["null"] } + set { __data["null"] = newValue } + } + + var or: GraphQLNullable<[Bool?]> { + get { __data["or"] } + set { __data["or"] = newValue } + } + + var startsWith: GraphQLNullable { + get { __data["startsWith"] } + set { __data["startsWith"] = newValue } + } + } + +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/DateTimeFilterInput.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/DateTimeFilterInput.graphql.swift new file mode 100644 index 00000000..d246323e --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/DateTimeFilterInput.graphql.swift @@ -0,0 +1,175 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema { + struct DateTimeFilterInput: InputObject { + private(set) var __data: InputDict + + init(_ data: InputDict) { + __data = data + } + + init( + and: GraphQLNullable<[DateTime?]> = nil, + between: GraphQLNullable<[DateTime?]> = nil, + contains: GraphQLNullable = nil, + containsi: GraphQLNullable = nil, + endsWith: GraphQLNullable = nil, + eq: GraphQLNullable = nil, + eqi: GraphQLNullable = nil, + gt: GraphQLNullable = nil, + gte: GraphQLNullable = nil, + `in`: GraphQLNullable<[DateTime?]> = nil, + lt: GraphQLNullable = nil, + lte: GraphQLNullable = nil, + ne: GraphQLNullable = nil, + nei: GraphQLNullable = nil, + not: GraphQLNullable = nil, + notContains: GraphQLNullable = nil, + notContainsi: GraphQLNullable = nil, + notIn: GraphQLNullable<[DateTime?]> = nil, + notNull: GraphQLNullable = nil, + null: GraphQLNullable = nil, + or: GraphQLNullable<[DateTime?]> = nil, + startsWith: GraphQLNullable = nil + ) { + __data = InputDict([ + "and": and, + "between": between, + "contains": contains, + "containsi": containsi, + "endsWith": endsWith, + "eq": eq, + "eqi": eqi, + "gt": gt, + "gte": gte, + "in": `in`, + "lt": lt, + "lte": lte, + "ne": ne, + "nei": nei, + "not": not, + "notContains": notContains, + "notContainsi": notContainsi, + "notIn": notIn, + "notNull": notNull, + "null": null, + "or": or, + "startsWith": startsWith + ]) + } + + var and: GraphQLNullable<[DateTime?]> { + get { __data["and"] } + set { __data["and"] = newValue } + } + + var between: GraphQLNullable<[DateTime?]> { + get { __data["between"] } + set { __data["between"] = newValue } + } + + var contains: GraphQLNullable { + get { __data["contains"] } + set { __data["contains"] = newValue } + } + + var containsi: GraphQLNullable { + get { __data["containsi"] } + set { __data["containsi"] = newValue } + } + + var endsWith: GraphQLNullable { + get { __data["endsWith"] } + set { __data["endsWith"] = newValue } + } + + var eq: GraphQLNullable { + get { __data["eq"] } + set { __data["eq"] = newValue } + } + + var eqi: GraphQLNullable { + get { __data["eqi"] } + set { __data["eqi"] = newValue } + } + + var gt: GraphQLNullable { + get { __data["gt"] } + set { __data["gt"] = newValue } + } + + var gte: GraphQLNullable { + get { __data["gte"] } + set { __data["gte"] = newValue } + } + + var `in`: GraphQLNullable<[DateTime?]> { + get { __data["in"] } + set { __data["in"] = newValue } + } + + var lt: GraphQLNullable { + get { __data["lt"] } + set { __data["lt"] = newValue } + } + + var lte: GraphQLNullable { + get { __data["lte"] } + set { __data["lte"] = newValue } + } + + var ne: GraphQLNullable { + get { __data["ne"] } + set { __data["ne"] = newValue } + } + + var nei: GraphQLNullable { + get { __data["nei"] } + set { __data["nei"] = newValue } + } + + var not: GraphQLNullable { + get { __data["not"] } + set { __data["not"] = newValue } + } + + var notContains: GraphQLNullable { + get { __data["notContains"] } + set { __data["notContains"] = newValue } + } + + var notContainsi: GraphQLNullable { + get { __data["notContainsi"] } + set { __data["notContainsi"] = newValue } + } + + var notIn: GraphQLNullable<[DateTime?]> { + get { __data["notIn"] } + set { __data["notIn"] = newValue } + } + + var notNull: GraphQLNullable { + get { __data["notNull"] } + set { __data["notNull"] = newValue } + } + + var null: GraphQLNullable { + get { __data["null"] } + set { __data["null"] = newValue } + } + + var or: GraphQLNullable<[DateTime?]> { + get { __data["or"] } + set { __data["or"] = newValue } + } + + var startsWith: GraphQLNullable { + get { __data["startsWith"] } + set { __data["startsWith"] = newValue } + } + } + +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/ExperienceFiltersInput.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/ExperienceFiltersInput.graphql.swift new file mode 100644 index 00000000..20386856 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/ExperienceFiltersInput.graphql.swift @@ -0,0 +1,98 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema { + struct ExperienceFiltersInput: InputObject { + private(set) var __data: InputDict + + init(_ data: InputDict) { + __data = data + } + + init( + and: GraphQLNullable<[ExperienceFiltersInput?]> = nil, + createdAt: GraphQLNullable = nil, + documentId: GraphQLNullable = nil, + isHomepage: GraphQLNullable = nil, + locale: GraphQLNullable = nil, + localizations: GraphQLNullable = nil, + not: GraphQLNullable = nil, + or: GraphQLNullable<[ExperienceFiltersInput?]> = nil, + publishedAt: GraphQLNullable = nil, + slug: GraphQLNullable = nil, + updatedAt: GraphQLNullable = nil + ) { + __data = InputDict([ + "and": and, + "createdAt": createdAt, + "documentId": documentId, + "isHomepage": isHomepage, + "locale": locale, + "localizations": localizations, + "not": not, + "or": or, + "publishedAt": publishedAt, + "slug": slug, + "updatedAt": updatedAt + ]) + } + + var and: GraphQLNullable<[ExperienceFiltersInput?]> { + get { __data["and"] } + set { __data["and"] = newValue } + } + + var createdAt: GraphQLNullable { + get { __data["createdAt"] } + set { __data["createdAt"] = newValue } + } + + var documentId: GraphQLNullable { + get { __data["documentId"] } + set { __data["documentId"] = newValue } + } + + var isHomepage: GraphQLNullable { + get { __data["isHomepage"] } + set { __data["isHomepage"] = newValue } + } + + var locale: GraphQLNullable { + get { __data["locale"] } + set { __data["locale"] = newValue } + } + + var localizations: GraphQLNullable { + get { __data["localizations"] } + set { __data["localizations"] = newValue } + } + + var not: GraphQLNullable { + get { __data["not"] } + set { __data["not"] = newValue } + } + + var or: GraphQLNullable<[ExperienceFiltersInput?]> { + get { __data["or"] } + set { __data["or"] = newValue } + } + + var publishedAt: GraphQLNullable { + get { __data["publishedAt"] } + set { __data["publishedAt"] = newValue } + } + + var slug: GraphQLNullable { + get { __data["slug"] } + set { __data["slug"] = newValue } + } + + var updatedAt: GraphQLNullable { + get { __data["updatedAt"] } + set { __data["updatedAt"] = newValue } + } + } + +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/IDFilterInput.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/IDFilterInput.graphql.swift new file mode 100644 index 00000000..65c4293d --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/IDFilterInput.graphql.swift @@ -0,0 +1,175 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema { + struct IDFilterInput: InputObject { + private(set) var __data: InputDict + + init(_ data: InputDict) { + __data = data + } + + init( + and: GraphQLNullable<[ID?]> = nil, + between: GraphQLNullable<[ID?]> = nil, + contains: GraphQLNullable = nil, + containsi: GraphQLNullable = nil, + endsWith: GraphQLNullable = nil, + eq: GraphQLNullable = nil, + eqi: GraphQLNullable = nil, + gt: GraphQLNullable = nil, + gte: GraphQLNullable = nil, + `in`: GraphQLNullable<[ID?]> = nil, + lt: GraphQLNullable = nil, + lte: GraphQLNullable = nil, + ne: GraphQLNullable = nil, + nei: GraphQLNullable = nil, + not: GraphQLNullable = nil, + notContains: GraphQLNullable = nil, + notContainsi: GraphQLNullable = nil, + notIn: GraphQLNullable<[ID?]> = nil, + notNull: GraphQLNullable = nil, + null: GraphQLNullable = nil, + or: GraphQLNullable<[ID?]> = nil, + startsWith: GraphQLNullable = nil + ) { + __data = InputDict([ + "and": and, + "between": between, + "contains": contains, + "containsi": containsi, + "endsWith": endsWith, + "eq": eq, + "eqi": eqi, + "gt": gt, + "gte": gte, + "in": `in`, + "lt": lt, + "lte": lte, + "ne": ne, + "nei": nei, + "not": not, + "notContains": notContains, + "notContainsi": notContainsi, + "notIn": notIn, + "notNull": notNull, + "null": null, + "or": or, + "startsWith": startsWith + ]) + } + + var and: GraphQLNullable<[ID?]> { + get { __data["and"] } + set { __data["and"] = newValue } + } + + var between: GraphQLNullable<[ID?]> { + get { __data["between"] } + set { __data["between"] = newValue } + } + + var contains: GraphQLNullable { + get { __data["contains"] } + set { __data["contains"] = newValue } + } + + var containsi: GraphQLNullable { + get { __data["containsi"] } + set { __data["containsi"] = newValue } + } + + var endsWith: GraphQLNullable { + get { __data["endsWith"] } + set { __data["endsWith"] = newValue } + } + + var eq: GraphQLNullable { + get { __data["eq"] } + set { __data["eq"] = newValue } + } + + var eqi: GraphQLNullable { + get { __data["eqi"] } + set { __data["eqi"] = newValue } + } + + var gt: GraphQLNullable { + get { __data["gt"] } + set { __data["gt"] = newValue } + } + + var gte: GraphQLNullable { + get { __data["gte"] } + set { __data["gte"] = newValue } + } + + var `in`: GraphQLNullable<[ID?]> { + get { __data["in"] } + set { __data["in"] = newValue } + } + + var lt: GraphQLNullable { + get { __data["lt"] } + set { __data["lt"] = newValue } + } + + var lte: GraphQLNullable { + get { __data["lte"] } + set { __data["lte"] = newValue } + } + + var ne: GraphQLNullable { + get { __data["ne"] } + set { __data["ne"] = newValue } + } + + var nei: GraphQLNullable { + get { __data["nei"] } + set { __data["nei"] = newValue } + } + + var not: GraphQLNullable { + get { __data["not"] } + set { __data["not"] = newValue } + } + + var notContains: GraphQLNullable { + get { __data["notContains"] } + set { __data["notContains"] = newValue } + } + + var notContainsi: GraphQLNullable { + get { __data["notContainsi"] } + set { __data["notContainsi"] = newValue } + } + + var notIn: GraphQLNullable<[ID?]> { + get { __data["notIn"] } + set { __data["notIn"] = newValue } + } + + var notNull: GraphQLNullable { + get { __data["notNull"] } + set { __data["notNull"] = newValue } + } + + var null: GraphQLNullable { + get { __data["null"] } + set { __data["null"] = newValue } + } + + var or: GraphQLNullable<[ID?]> { + get { __data["or"] } + set { __data["or"] = newValue } + } + + var startsWith: GraphQLNullable { + get { __data["startsWith"] } + set { __data["startsWith"] = newValue } + } + } + +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/StringFilterInput.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/StringFilterInput.graphql.swift new file mode 100644 index 00000000..5425016f --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/InputObjects/StringFilterInput.graphql.swift @@ -0,0 +1,175 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema { + struct StringFilterInput: InputObject { + private(set) var __data: InputDict + + init(_ data: InputDict) { + __data = data + } + + init( + and: GraphQLNullable<[String?]> = nil, + between: GraphQLNullable<[String?]> = nil, + contains: GraphQLNullable = nil, + containsi: GraphQLNullable = nil, + endsWith: GraphQLNullable = nil, + eq: GraphQLNullable = nil, + eqi: GraphQLNullable = nil, + gt: GraphQLNullable = nil, + gte: GraphQLNullable = nil, + `in`: GraphQLNullable<[String?]> = nil, + lt: GraphQLNullable = nil, + lte: GraphQLNullable = nil, + ne: GraphQLNullable = nil, + nei: GraphQLNullable = nil, + not: GraphQLNullable = nil, + notContains: GraphQLNullable = nil, + notContainsi: GraphQLNullable = nil, + notIn: GraphQLNullable<[String?]> = nil, + notNull: GraphQLNullable = nil, + null: GraphQLNullable = nil, + or: GraphQLNullable<[String?]> = nil, + startsWith: GraphQLNullable = nil + ) { + __data = InputDict([ + "and": and, + "between": between, + "contains": contains, + "containsi": containsi, + "endsWith": endsWith, + "eq": eq, + "eqi": eqi, + "gt": gt, + "gte": gte, + "in": `in`, + "lt": lt, + "lte": lte, + "ne": ne, + "nei": nei, + "not": not, + "notContains": notContains, + "notContainsi": notContainsi, + "notIn": notIn, + "notNull": notNull, + "null": null, + "or": or, + "startsWith": startsWith + ]) + } + + var and: GraphQLNullable<[String?]> { + get { __data["and"] } + set { __data["and"] = newValue } + } + + var between: GraphQLNullable<[String?]> { + get { __data["between"] } + set { __data["between"] = newValue } + } + + var contains: GraphQLNullable { + get { __data["contains"] } + set { __data["contains"] = newValue } + } + + var containsi: GraphQLNullable { + get { __data["containsi"] } + set { __data["containsi"] = newValue } + } + + var endsWith: GraphQLNullable { + get { __data["endsWith"] } + set { __data["endsWith"] = newValue } + } + + var eq: GraphQLNullable { + get { __data["eq"] } + set { __data["eq"] = newValue } + } + + var eqi: GraphQLNullable { + get { __data["eqi"] } + set { __data["eqi"] = newValue } + } + + var gt: GraphQLNullable { + get { __data["gt"] } + set { __data["gt"] = newValue } + } + + var gte: GraphQLNullable { + get { __data["gte"] } + set { __data["gte"] = newValue } + } + + var `in`: GraphQLNullable<[String?]> { + get { __data["in"] } + set { __data["in"] = newValue } + } + + var lt: GraphQLNullable { + get { __data["lt"] } + set { __data["lt"] = newValue } + } + + var lte: GraphQLNullable { + get { __data["lte"] } + set { __data["lte"] = newValue } + } + + var ne: GraphQLNullable { + get { __data["ne"] } + set { __data["ne"] = newValue } + } + + var nei: GraphQLNullable { + get { __data["nei"] } + set { __data["nei"] = newValue } + } + + var not: GraphQLNullable { + get { __data["not"] } + set { __data["not"] = newValue } + } + + var notContains: GraphQLNullable { + get { __data["notContains"] } + set { __data["notContains"] = newValue } + } + + var notContainsi: GraphQLNullable { + get { __data["notContainsi"] } + set { __data["notContainsi"] = newValue } + } + + var notIn: GraphQLNullable<[String?]> { + get { __data["notIn"] } + set { __data["notIn"] = newValue } + } + + var notNull: GraphQLNullable { + get { __data["notNull"] } + set { __data["notNull"] = newValue } + } + + var null: GraphQLNullable { + get { __data["null"] } + set { __data["null"] = newValue } + } + + var or: GraphQLNullable<[String?]> { + get { __data["or"] } + set { __data["or"] = newValue } + } + + var startsWith: GraphQLNullable { + get { __data["startsWith"] } + set { __data["startsWith"] = newValue } + } + } + +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsCta.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsCta.graphql.swift new file mode 100644 index 00000000..7469014c --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsCta.graphql.swift @@ -0,0 +1,12 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema.Objects { + static let ComponentSectionsCta = ApolloAPI.Object( + typename: "ComponentSectionsCta", + implementedInterfaces: [], + keyFields: nil + ) +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsInfoBlock.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsInfoBlock.graphql.swift new file mode 100644 index 00000000..e4d70d86 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsInfoBlock.graphql.swift @@ -0,0 +1,12 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema.Objects { + static let ComponentSectionsInfoBlock = ApolloAPI.Object( + typename: "ComponentSectionsInfoBlock", + implementedInterfaces: [], + keyFields: nil + ) +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsInfoBlocks.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsInfoBlocks.graphql.swift new file mode 100644 index 00000000..4bc6a7a0 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsInfoBlocks.graphql.swift @@ -0,0 +1,12 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema.Objects { + static let ComponentSectionsInfoBlocks = ApolloAPI.Object( + typename: "ComponentSectionsInfoBlocks", + implementedInterfaces: [], + keyFields: nil + ) +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsMediaCollection.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsMediaCollection.graphql.swift new file mode 100644 index 00000000..b5e6795d --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsMediaCollection.graphql.swift @@ -0,0 +1,12 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema.Objects { + static let ComponentSectionsMediaCollection = ApolloAPI.Object( + typename: "ComponentSectionsMediaCollection", + implementedInterfaces: [], + keyFields: nil + ) +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsPromoBanner.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsPromoBanner.graphql.swift new file mode 100644 index 00000000..f2abe727 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/ComponentSectionsPromoBanner.graphql.swift @@ -0,0 +1,12 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema.Objects { + static let ComponentSectionsPromoBanner = ApolloAPI.Object( + typename: "ComponentSectionsPromoBanner", + implementedInterfaces: [], + keyFields: nil + ) +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/Error.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/Error.graphql.swift new file mode 100644 index 00000000..a81a8321 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/Error.graphql.swift @@ -0,0 +1,12 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema.Objects { + static let Error_Object = ApolloAPI.Object( + typename: "Error", + implementedInterfaces: [], + keyFields: nil + ) +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/Experience.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/Experience.graphql.swift new file mode 100644 index 00000000..6898e1c3 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/Experience.graphql.swift @@ -0,0 +1,12 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema.Objects { + static let Experience = ApolloAPI.Object( + typename: "Experience", + implementedInterfaces: [], + keyFields: nil + ) +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/Query.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/Query.graphql.swift new file mode 100644 index 00000000..1ab3def7 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Objects/Query.graphql.swift @@ -0,0 +1,12 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema.Objects { + static let Query = ApolloAPI.Object( + typename: "Query", + implementedInterfaces: [], + keyFields: nil + ) +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/SchemaConfiguration.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/SchemaConfiguration.swift new file mode 100644 index 00000000..7afdf3cd --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/SchemaConfiguration.swift @@ -0,0 +1,15 @@ +// @generated +// This file was automatically generated and can be edited to +// provide custom configuration for a generated GraphQL schema. +// +// Any changes to this file will not be overwritten by future +// code generation execution. + +import ApolloAPI + +enum SchemaConfiguration: ApolloAPI.SchemaConfiguration { + static func cacheKeyInfo(for type: ApolloAPI.Object, object: ApolloAPI.ObjectData) -> CacheKeyInfo? { + // Implement this function to configure cache key resolution for your schema types. + return nil + } +} diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/SchemaMetadata.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/SchemaMetadata.graphql.swift new file mode 100644 index 00000000..6a3b0793 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/SchemaMetadata.graphql.swift @@ -0,0 +1,49 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +protocol ForgeSchema_SelectionSet: ApolloAPI.SelectionSet & ApolloAPI.RootSelectionSet +where Schema == ForgeSchema.SchemaMetadata {} + +protocol ForgeSchema_InlineFragment: ApolloAPI.SelectionSet & ApolloAPI.InlineFragment +where Schema == ForgeSchema.SchemaMetadata {} + +protocol ForgeSchema_MutableSelectionSet: ApolloAPI.MutableRootSelectionSet +where Schema == ForgeSchema.SchemaMetadata {} + +protocol ForgeSchema_MutableInlineFragment: ApolloAPI.MutableSelectionSet & ApolloAPI.InlineFragment +where Schema == ForgeSchema.SchemaMetadata {} + +extension ForgeSchema { + typealias SelectionSet = ForgeSchema_SelectionSet + + typealias InlineFragment = ForgeSchema_InlineFragment + + typealias MutableSelectionSet = ForgeSchema_MutableSelectionSet + + typealias MutableInlineFragment = ForgeSchema_MutableInlineFragment + + enum SchemaMetadata: ApolloAPI.SchemaMetadata { + static let configuration: any ApolloAPI.SchemaConfiguration.Type = SchemaConfiguration.self + + static func objectType(forTypename typename: String) -> ApolloAPI.Object? { + switch typename { + case "ComponentSectionsCta": return ForgeSchema.Objects.ComponentSectionsCta + case "ComponentSectionsInfoBlock": return ForgeSchema.Objects.ComponentSectionsInfoBlock + case "ComponentSectionsInfoBlocks": return ForgeSchema.Objects.ComponentSectionsInfoBlocks + case "ComponentSectionsMediaCollection": return ForgeSchema.Objects.ComponentSectionsMediaCollection + case "ComponentSectionsPromoBanner": return ForgeSchema.Objects.ComponentSectionsPromoBanner + case "Error": return ForgeSchema.Objects.Error_Object + case "Experience": return ForgeSchema.Objects.Experience + case "Query": return ForgeSchema.Objects.Query + default: return nil + } + } + } + + enum Objects {} + enum Interfaces {} + enum Unions {} + +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/Generated/Schema/Unions/ExperienceSectionsDynamicZone.graphql.swift b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Unions/ExperienceSectionsDynamicZone.graphql.swift new file mode 100644 index 00000000..6e0a795d --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Generated/Schema/Unions/ExperienceSectionsDynamicZone.graphql.swift @@ -0,0 +1,17 @@ +// @generated +// This file was automatically generated and should not be edited. + +import ApolloAPI + +extension ForgeSchema.Unions { + static let ExperienceSectionsDynamicZone = Union( + name: "ExperienceSectionsDynamicZone", + possibleTypes: [ + ForgeSchema.Objects.ComponentSectionsCta.self, + ForgeSchema.Objects.ComponentSectionsInfoBlocks.self, + ForgeSchema.Objects.ComponentSectionsMediaCollection.self, + ForgeSchema.Objects.ComponentSectionsPromoBanner.self, + ForgeSchema.Objects.Error_Object.self + ] + ) +} \ No newline at end of file diff --git a/mobile/ios/Sources/ForgeMobile/ViewModels/WatchHomeViewModel.swift b/mobile/ios/Sources/ForgeMobile/ViewModels/WatchHomeViewModel.swift new file mode 100644 index 00000000..59e53301 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/ViewModels/WatchHomeViewModel.swift @@ -0,0 +1,31 @@ +import Foundation + +/// ViewModel for the watch/home screen. Owns repository access and exposes loading state and content. +@Observable +public final class WatchHomeViewModel { + public private(set) var isLoading = false + public private(set) var homeItem: MobileContentItem? + public private(set) var homeError: String? + + private let repository: ContentRepository + + public init(repository: ContentRepository) { + self.repository = repository + } + + /// Loads home content for the given locale. Updates `isLoading`, `homeItem`, and `homeError`. + public func load(locale: String = "en") async { + isLoading = true + homeError = nil + homeItem = nil + defer { isLoading = false } + do { + let item = try await repository.fetchHome(locale: locale) + homeItem = item + homeError = nil + } catch { + homeItem = nil + homeError = error.localizedDescription + } + } +} diff --git a/mobile/ios/Sources/ForgeMobile/Views/ForgeRootView.swift b/mobile/ios/Sources/ForgeMobile/Views/ForgeRootView.swift new file mode 100644 index 00000000..e13cd022 --- /dev/null +++ b/mobile/ios/Sources/ForgeMobile/Views/ForgeRootView.swift @@ -0,0 +1,54 @@ +import SwiftUI + +public struct ForgeRootView: View { + private let contentRepository: ContentRepository? + @State private var viewModel: WatchHomeViewModel? + + public init(contentRepository: ContentRepository? = nil) { + self.contentRepository = contentRepository + _viewModel = State(initialValue: contentRepository.map { WatchHomeViewModel(repository: $0) }) + } + + public var body: some View { + #if DEBUG + if let viewModel = viewModel { + GraphQLTestView(viewModel: viewModel) + } else { + Text("Forge iOS") + .accessibilityLabel("Forge iOS") + } + #else + Text("Forge iOS") + .accessibilityLabel("Forge iOS") + #endif + } +} + +private struct GraphQLTestView: View { + let viewModel: WatchHomeViewModel + + var body: some View { + VStack(spacing: 12) { + Text("Forge iOS") + .accessibilityLabel("Forge iOS") + if viewModel.isLoading { + ProgressView("Loading…") + .accessibilityLabel("Loading content") + } else if let item = viewModel.homeItem { + Text("Loaded: \(item.title)") + .accessibilityLabel("Loaded title \(item.title)") + } else if let error = viewModel.homeError { + Text("Error: \(error)") + .foregroundStyle(.red) + .accessibilityLabel("Error \(error)") + } else { + Text("No content") + .accessibilityLabel("No content") + } + } + .padding() + .task { + await viewModel.load(locale: "en") + } + } +} diff --git a/mobile/ios/apollo-codegen-configuration.json b/mobile/ios/apollo-codegen-configuration.json new file mode 100644 index 00000000..180bdf89 --- /dev/null +++ b/mobile/ios/apollo-codegen-configuration.json @@ -0,0 +1,23 @@ +{ + "schemaNamespace": "ForgeSchema", + "input": { + "schemaSearchPaths": ["../../apps/cms/schema.graphql"], + "operationSearchPaths": ["GraphQL/Operations/**/*.graphql"] + }, + "output": { + "schemaTypes": { + "path": "./Sources/ForgeMobile/Generated", + "moduleType": { + "embeddedInTarget": { + "name": "ForgeMobile" + } + } + }, + "operations": { + "inSchemaModule": {} + }, + "testMocks": { + "none": {} + } + } +}