From a0347718482c1d216e24be00076e2dd3d14b83d2 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 14 May 2024 18:03:05 +0800 Subject: [PATCH] Allog config of digest --- .../App/Views/AI/DigestConfigView.swift | 14 ++- .../App/Views/AI/FullScreenDigestView.swift | 19 ++++ .../Services/DataService/GQLSchema.swift | 94 +++++++++++++++++-- apple/Sources/AppIntents.swift | 1 - .../mutations/updateDigestConfigMutation.ts | 4 +- .../queries/useGetUserPersonalization.tsx | 4 +- 6 files changed, 120 insertions(+), 16 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift b/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift index 26e6bcfd8..7536b0073 100644 --- a/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift @@ -9,7 +9,7 @@ import Transmission @MainActor public class DigestConfigViewModel: ObservableObject { @Published var isLoading = false - @Published var alreadyGranted = false + @Published var digestEnabled = false @Published var isIneligible = false @Published var hasOptInError = false @@ -23,7 +23,7 @@ public class DigestConfigViewModel: ObservableObject { func checkAlreadyOptedIn(dataService: DataService) async { isLoading = true if let user = try? await dataService.fetchViewer() { - alreadyGranted = user.hasFeatureGranted("ai-digest") + digestEnabled = user.hasFeatureGranted("ai-digest") } isLoading = false } @@ -35,6 +35,8 @@ public class DigestConfigViewModel: ObservableObject { throw BasicError.message(messageText: "Could not opt into feature") } try await dataService.setupUserDigestConfig() + try await dataService.refreshDigest() + digestEnabled = true } catch { if error is IneligibleError { isIneligible = true @@ -42,7 +44,6 @@ public class DigestConfigViewModel: ObservableObject { hasOptInError = true } } - isLoading = false } } @@ -84,7 +85,8 @@ struct DigestConfigView: View { ProgressView() Spacer() } - } else if viewModel.alreadyGranted { + .padding(.top, 50) + } else if viewModel.digestEnabled { Text("You've been added to the AI Digest demo. You first issue should be ready soon.") .padding(15) } else if viewModel.isIneligible { @@ -160,7 +162,9 @@ struct DigestConfigView: View { .buttonStyle(RoundedRectButtonStyle()) Button(action: { - // viewModel.en + Task { + await viewModel.enableDigest(dataService: dataService) + } }, label: { Text("Enable digest") }) .buttonStyle(RoundedRectButtonStyle(color: Color.blue, textColor: Color.white)) } diff --git a/apple/OmnivoreKit/Sources/App/Views/AI/FullScreenDigestView.swift b/apple/OmnivoreKit/Sources/App/Views/AI/FullScreenDigestView.swift index 5b92ca42a..dbaf2bd2a 100644 --- a/apple/OmnivoreKit/Sources/App/Views/AI/FullScreenDigestView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/AI/FullScreenDigestView.swift @@ -38,6 +38,7 @@ func formatTimeInterval(_ time: TimeInterval) -> String? { @MainActor public class FullScreenDigestViewModel: ObservableObject { @Published var isLoading = false + @Published var hasError = false @Published var digest: DigestResult? @Published var chapterInfo: [(DigestChapter, DigestChapterData)]? @Published var presentedLibraryItem: String? @@ -46,6 +47,9 @@ public class FullScreenDigestViewModel: ObservableObject { @AppStorage(UserDefaultKey.lastVisitedDigestId.rawValue) var lastVisitedDigestId = "" func load(dataService: DataService, audioController: AudioController) async { + hasError = false + isLoading = true + if !dataService.digestNeedsRefresh() { if let digest = dataService.loadStoredDigest() { self.digest = digest @@ -72,6 +76,8 @@ public class FullScreenDigestViewModel: ObservableObject { let chapterData = self.chapterInfo?.map { $0.1 } audioController.play(itemAudioProperties: DigestAudioItem(digest: digest, chapters: chapterData ?? [])) } + } else { + hasError = true } isLoading = false @@ -164,6 +170,19 @@ struct FullScreenDigestView: View { ProgressView() Spacer() } + } else if viewModel.hasError { + VStack { + Spacer() + Text("There was an error loading your digest.") + Button(action: { + Task { + await viewModel.load(dataService: dataService, audioController: audioController) + } + }, label: { Text("Try again") }) + .buttonStyle(RoundedRectButtonStyle(color: Color.blue, textColor: Color.white)) + + Spacer() + } } else { itemBody } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift b/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift index 56e42410c..0a76114df 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/GQLSchema.swift @@ -5866,6 +5866,68 @@ extension Selection where TypeLock == Never, Type == Never { typealias DeviceTokensSuccess = Selection } +extension Objects { + struct DigestConfig { + let __typename: TypeName = .digestConfig + let channels: [String: [String?]] + + enum TypeName: String, Codable { + case digestConfig = "DigestConfig" + } + } +} + +extension Objects.DigestConfig: Decodable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: DynamicCodingKeys.self) + + var map = HashMap() + for codingKey in container.allKeys { + if codingKey.isTypenameKey { continue } + + let alias = codingKey.stringValue + let field = GraphQLField.getFieldNameFromAlias(alias) + + switch field { + case "channels": + if let value = try container.decode([String?]?.self, forKey: codingKey) { + map.set(key: field, hash: alias, value: value as Any) + } + default: + throw DecodingError.dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unknown key \(field)." + ) + ) + } + } + + channels = map["channels"] + } +} + +extension Fields where TypeLock == Objects.DigestConfig { + func channels() throws -> [String?]? { + let field = GraphQLField.leaf( + name: "channels", + arguments: [] + ) + select(field) + + switch response { + case let .decoding(data): + return data.channels[field.alias!] + case .mocking: + return nil + } + } +} + +extension Selection where TypeLock == Never, Type == Never { + typealias DigestConfig = Selection +} + extension Objects { struct DiscoverFeed { let __typename: TypeName = .discoverFeed @@ -28003,7 +28065,7 @@ extension Selection where TypeLock == Never, Type == Never { extension Objects { struct UserPersonalization { let __typename: TypeName = .userPersonalization - let digestConfig: [String: String] + let digestConfig: [String: Objects.DigestConfig] let fields: [String: String] let fontFamily: [String: String] let fontSize: [String: Int] @@ -28036,7 +28098,7 @@ extension Objects.UserPersonalization: Decodable { switch field { case "digestConfig": - if let value = try container.decode(String?.self, forKey: codingKey) { + if let value = try container.decode(Objects.DigestConfig?.self, forKey: codingKey) { map.set(key: field, hash: alias, value: value as Any) } case "fields": @@ -28114,18 +28176,19 @@ extension Objects.UserPersonalization: Decodable { } extension Fields where TypeLock == Objects.UserPersonalization { - func digestConfig() throws -> String? { - let field = GraphQLField.leaf( + func digestConfig(selection: Selection) throws -> Type { + let field = GraphQLField.composite( name: "digestConfig", - arguments: [] + arguments: [], + selection: selection.selection ) select(field) switch response { case let .decoding(data): - return data.digestConfig[field.alias!] + return try selection.decode(data: data.digestConfig[field.alias!]) case .mocking: - return nil + return selection.mock() } } @@ -39401,6 +39464,21 @@ extension InputObjects { } } +extension InputObjects { + struct DigestConfigInput: Encodable, Hashable { + var channels: OptionalArgument<[OptionalArgument]> = .absent() + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + if channels.hasValue { try container.encode(channels, forKey: .channels) } + } + + enum CodingKeys: String, CodingKey { + case channels + } + } +} + extension InputObjects { struct EditDiscoverFeedInput: Encodable, Hashable { var feedId: String @@ -40379,7 +40457,7 @@ extension InputObjects { extension InputObjects { struct SetUserPersonalizationInput: Encodable, Hashable { - var digestConfig: OptionalArgument = .absent() + var digestConfig: OptionalArgument = .absent() var fields: OptionalArgument = .absent() diff --git a/apple/Sources/AppIntents.swift b/apple/Sources/AppIntents.swift index e842bd299..2ca436735 100644 --- a/apple/Sources/AppIntents.swift +++ b/apple/Sources/AppIntents.swift @@ -40,7 +40,6 @@ } } - @available(iOS 16.0, *) struct LibraryItemEntity: AppEntity { static var defaultQuery = LibraryItemQuery() diff --git a/packages/web/lib/networking/mutations/updateDigestConfigMutation.ts b/packages/web/lib/networking/mutations/updateDigestConfigMutation.ts index 87983ebc7..c2f70e4e6 100644 --- a/packages/web/lib/networking/mutations/updateDigestConfigMutation.ts +++ b/packages/web/lib/networking/mutations/updateDigestConfigMutation.ts @@ -27,7 +27,9 @@ export async function updateDigestConfigMutation( } ... on SetUserPersonalizationSuccess { updatedUserPersonalization { - digestConfig + digestConfig { + channels + } } } } diff --git a/packages/web/lib/networking/queries/useGetUserPersonalization.tsx b/packages/web/lib/networking/queries/useGetUserPersonalization.tsx index bbcf1f958..e688a1231 100644 --- a/packages/web/lib/networking/queries/useGetUserPersonalization.tsx +++ b/packages/web/lib/networking/queries/useGetUserPersonalization.tsx @@ -58,7 +58,9 @@ export function useGetUserPersonalization(): UserPersonalizationResult { getUserPersonalization { ... on GetUserPersonalizationSuccess { userPersonalization { - digestConfig + digestConfig { + channels + } } } ... on GetUserPersonalizationError {