From abe8593ed7cf6da10b0e3e5288ea669e7ed3afe7 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 15 May 2024 20:11:40 +0800 Subject: [PATCH 1/3] Add UI for digest --- .../App/Views/AI/DigestConfigView.swift | 44 ++++++- .../App/Views/AI/FullScreenDigestView.swift | 108 +++++++++--------- .../Services/DataService/AI/AITasks.swift | 4 +- .../Mutations/UpdateUserPersonalization.swift | 70 ++++++++++++ 4 files changed, 172 insertions(+), 54 deletions(-) create mode 100644 apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateUserPersonalization.swift diff --git a/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift b/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift index 7536b0073..b9acf8784 100644 --- a/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift @@ -18,6 +18,9 @@ public class DigestConfigViewModel: ObservableObject { @Published var presentedLibraryItem: String? @Published var presentWebContainer = false + @Published var notificationsEnabled = false + @Published var isTryingToEnableNotifications = false + @AppStorage(UserDefaultKey.lastVisitedDigestId.rawValue) var lastVisitedDigestId = "" func checkAlreadyOptedIn(dataService: DataService) async { @@ -46,6 +49,25 @@ public class DigestConfigViewModel: ObservableObject { } isLoading = false } + + func tryEnableNotifications(dataService: DataService) { + isTryingToEnableNotifications = true + UNUserNotificationCenter.current().requestAuthorization(options: [.alert]) { granted, _ in + DispatchQueue.main.async { + self.notificationsEnabled = granted + UserDefaults.standard.set(granted, forKey: UserDefaultKey.notificationsEnabled.rawValue) + + Task { + if let savedToken = UserDefaults.standard.string(forKey: UserDefaultKey.firebasePushToken.rawValue) { + _ = try? await dataService.syncDeviceToken( + deviceTokenOperation: DeviceTokenOperation.addToken(token: savedToken)) + } + NotificationCenter.default.post(name: Notification.Name("ReconfigurePushNotifications"), object: nil) + self.isTryingToEnableNotifications = false + } + } + } + } } @available(iOS 17.0, *) @@ -87,8 +109,26 @@ struct DigestConfigView: View { } .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) + VStack(spacing: 15) { + Spacer() + Text(""" + You've been added to the AI Digest demo. Your first issue should be ready soon. + When a new digest is ready the icon in the library header will change color. + You can close this window now. + """) + if !viewModel.notificationsEnabled { + if viewModel.isTryingToEnableNotifications { + ProgressView() + } else { + Button(action: { + viewModel.tryEnableNotifications(dataService: dataService) + }, label: { Text("Notify me when its ready") }) + .buttonStyle(RoundedRectButtonStyle(color: Color.blue, textColor: Color.white)) + } + } + Spacer() + } + .padding(20) } else if viewModel.isIneligible { Text("To enable digest you need to have saved at least ten library items and have two active subscriptions.") .padding(15) diff --git a/apple/OmnivoreKit/Sources/App/Views/AI/FullScreenDigestView.swift b/apple/OmnivoreKit/Sources/App/Views/AI/FullScreenDigestView.swift index dbaf2bd2a..fc92a1db9 100644 --- a/apple/OmnivoreKit/Sources/App/Views/AI/FullScreenDigestView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/AI/FullScreenDigestView.swift @@ -180,7 +180,6 @@ struct FullScreenDigestView: View { } }, label: { Text("Try again") }) .buttonStyle(RoundedRectButtonStyle(color: Color.blue, textColor: Color.white)) - Spacer() } } else { @@ -252,7 +251,8 @@ struct FullScreenDigestView: View { ChapterView( startTime: chapterData.time, skipIndex: chapterData.start, - chapter: chapter + chapter: chapter, + isCurrentChapter: currentChapter ) .onTapGesture { audioController.seek(toIdx: chapterData.start) @@ -264,8 +264,9 @@ struct FullScreenDigestView: View { viewModel.presentedLibraryItem = chapter.id viewModel.presentWebContainer = true } + .contentShape(Rectangle()) .background( - currentChapter ? Color.themeLabelBackground.opacity(0.6) : Color.clear + currentChapter ? Color.blue.opacity(0.2) : Color.clear ) .cornerRadius(5) } @@ -285,7 +286,6 @@ struct FullScreenDigestView: View { } .padding(15) .background(Color.themeLabelBackground.opacity(0.6)) - .cornerRadius(5) } Spacer(minLength: 60) @@ -300,27 +300,6 @@ struct FullScreenDigestView: View { RatingWidget() Spacer(minLength: 60) } -// -// VStack(alignment: .leading, spacing: 20) { -// Text("If you didn't like today's digest or would like another one you can create another one. The process takes a few minutes") -// Button(action: { -// Task { -// await viewModel.refreshDigest(dataService: dataService) -// } -// }, label: { -// Text("Create new digest") -// .font(Font.system(size: 13, weight: .medium)) -// .padding(.horizontal, 8) -// .padding(.vertical, 5) -// .tint(Color.blue) -// .background(Color.themeLabelBackground) -// .cornerRadius(5) -// }) -// } -// .padding(15) -// .background(Color.themeLabelBackground.opacity(0.6)) -// .cornerRadius(5) -// }.contentMargins(10, for: .scrollContent) Spacer() @@ -340,41 +319,70 @@ struct ChapterView: View { let startTime: String let skipIndex: Int let chapter: DigestChapter + let isCurrentChapter: Bool var body: some View { - HStack(spacing: 15) { + HStack { + VStack(spacing: 5) { + HStack { + Text(startTime) + .padding(4) + .padding(.horizontal, 4) + .foregroundColor(.blue) + .font(Font.system(size: 13)) + .background(Color.themeLabelBackground.opacity(0.6)) + .cornerRadius(5) + + if let author = chapter.author { + Text(author) + .font(Font.system(size: 14)) + .foregroundColor(Color.themeLibraryItemSubtle) + .lineLimit(1) + .padding(.trailing, 10) + } + + Spacer() + } + Text(chapter.title) + .foregroundColor(isCurrentChapter ? .primary :Color.themeLibraryItemSubtle.opacity(0.60)) + .font(Font.system(size: 14)) + .lineLimit(4) + .frame(maxWidth: .infinity, alignment: .topLeading) + } + .padding(.leading, 10) + Spacer() if let thumbnail = chapter.thumbnail, let thumbnailURL = URL(string: thumbnail) { AsyncImage(url: thumbnailURL) { image in image .resizable() .aspectRatio(contentMode: .fill) - .frame(width: 90, height: 50) + .frame(width: 65, height: 65) + .cornerRadius(5) .clipped() - } placeholder: { - Rectangle() - .foregroundColor(.gray) - .frame(width: 90, height: 50) - } - .cornerRadius(8) - } else { - Rectangle() - .foregroundColor(.gray) - .frame(width: 90, height: 50) - .cornerRadius(8) - } - VStack(alignment: .leading) { - (Text(startTime) - .foregroundColor(.blue) - .font(.caption) + .padding(.trailing, 10) - + - Text(" - " + chapter.title) - .foregroundColor(.primary) - .font(.caption)) - .lineLimit(2) + } placeholder: { + Rectangle() + .foregroundColor(.clear) + .frame(width: 65, height: 65) + .cornerRadius(5) + .padding(.trailing, 10) + } + } else { + ZStack { + Rectangle() + .foregroundColor(.thLibrarySeparator) + .frame(width: 65, height: 65) + .cornerRadius(5) + Image(systemName: "photo") + .foregroundColor(.thBorderColor) + .frame(width: 65, height: 65) + .cornerRadius(5) + } + .padding(.trailing, 10) } - Spacer() } + .frame(maxWidth: .infinity, alignment: .topLeading) .padding(.leading, 4) .padding(.vertical, 15) } @@ -424,8 +432,6 @@ struct PreviewItemView: View { } .padding(.top, 10) Text(viewModel.item.title) - // .font(.body) - // .fontWeight(.semibold) .font(Font.system(size: 18, weight: .semibold)) .frame(maxWidth: .infinity, alignment: .topLeading) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/AI/AITasks.swift b/apple/OmnivoreKit/Sources/Services/DataService/AI/AITasks.swift index 91d877499..b529929f4 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/AI/AITasks.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/AI/AITasks.swift @@ -26,11 +26,13 @@ public struct DigestChapter: Codable { public let id: String public let url: String public let wordCount: Double + public let author: String? public let thumbnail: String? - public init(title: String, id: String, url: String, wordCount: Double, thumbnail: String?) { + public init(title: String, id: String, url: String, wordCount: Double, author: String?, thumbnail: String?) { self.title = title self.id = id self.url = url + self.author = author self.wordCount = wordCount self.thumbnail = thumbnail } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateUserPersonalization.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateUserPersonalization.swift new file mode 100644 index 000000000..4f6f98e4d --- /dev/null +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateUserPersonalization.swift @@ -0,0 +1,70 @@ +import CoreData +import Foundation +import Models +import SwiftGraphQL + +struct DigestConfig { + let channels: [String] +} + +struct UserPersonalization { + let digestConfig: DigestConfig +} + +let digestConfigSelection = Selection.DigestConfig { + DigestConfig(channels: (try? $0.channels())?.compactMap { $0 } ?? []) +} + +let channelSelection = Selection.UserPersonalization { + try $0.digestConfig(selection: digestConfigSelection.nullable)?.channels ?? [] +} + + +public extension DataService { + func setupUserDigestConfig() async throws { + enum MutationResult { + case success(channels: [String]) + case error(errorMessage: String) + } + + let selection = Selection { + try $0.on( + setUserPersonalizationError: .init { + .error(errorMessage: try $0.errorCodes().first?.rawValue ?? "Unknown Error") + }, + setUserPersonalizationSuccess: .init { .success(channels: try $0.updatedUserPersonalization(selection: channelSelection)) } + ) + } + + let mutation = Selection.Mutation { + try $0.setUserPersonalization( + input: InputObjects.SetUserPersonalizationInput( + digestConfig: OptionalArgument( + InputObjects.DigestConfigInput(channels: OptionalArgument([OptionalArgument("email")])) + ) + ), + selection: selection + ) + } + + let path = appEnvironment.graphqlPath + let headers = networker.defaultHeaders + + return try await withCheckedThrowingContinuation { continuation in + send(mutation, to: path, headers: headers) { queryResult in + guard let payload = try? queryResult.get() else { + print("network error setting up user digest config") + continuation.resume(throwing: BasicError.message(messageText: "network error")) + return + } + switch payload.data { + case .success: + continuation.resume() + case let .error(errorMessage: errorMessage): + continuation.resume(throwing: BasicError.message(messageText: errorMessage)) + } + } + } + } +} + From 373d3975398844b80f85e59f3a219b212e29e933 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 15 May 2024 20:31:16 +0800 Subject: [PATCH 2/3] Update digest flair icon --- .../Views/FeedItem/LibraryItemCard.swift | 2 +- .../Sources/Views/Images/Images.swift | 3 ++- .../flair-digest.imageset/Contents.json | 23 ++++++++++++++++++ .../flair-digest.imageset/flair-digest@1x.png | Bin 0 -> 369 bytes .../flair-digest.imageset/flair-digest@2x.png | Bin 0 -> 553 bytes .../flair-digest.imageset/flair-digest@3x.png | Bin 0 -> 467 bytes 6 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/Contents.json create mode 100644 apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/flair-digest@1x.png create mode 100644 apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/flair-digest@2x.png create mode 100644 apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/flair-digest@3x.png diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift index b6997b3db..54e3e54b8 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/LibraryItemCard.swift @@ -18,7 +18,7 @@ enum FlairLabels: String { case .recommended: return Image.flairRecommended case .newsletter: return Image.flairNewsletter case .feed, .rss: return Image.flairFeed - case .digest: return Image.tabDigestSelected + case .digest: return Image.flairDigest } } diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.swift b/apple/OmnivoreKit/Sources/Views/Images/Images.swift index ccfb85f88..93e2c07a1 100644 --- a/apple/OmnivoreKit/Sources/Views/Images/Images.swift +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.swift @@ -53,7 +53,8 @@ public extension Image { static var flairNewsletter: Image { Image("flair-newsletter", bundle: .module) } static var flairPinned: Image { Image("flair-pinned", bundle: .module) } static var flairRecommended: Image { Image("flair-recommended", bundle: .module) } - + static var flairDigest: Image { Image("flair-digest", bundle: .module) } + static var doubleChevronUp: Image { Image("double_chevron_up", bundle: .module) } } diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/Contents.json b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/Contents.json new file mode 100644 index 000000000..85fade8b6 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "flair-digest@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "flair-digest@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "flair-digest@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/flair-digest@1x.png b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/flair-digest@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..2f16d8e92e00b29ec6e4d07e00a60c4c2d841e3d GIT binary patch literal 369 zcmV-%0gnEOP)@vgK9yN8sJbU+D|uvD;=uoph!LbxHgqy##E4rl@H2vdSDC0 z8hLCzKhK*nz&kLn$2A@r?-qx_9egnS11ZZ7i||(LCsCnXYduSr<*};5B3eT&iOQbv z?S?_mU6L?E>=YKsAzqJodsEB~|2N*EDJNEzRvt)4ATwRslC7+@Qzee{z<>`|5JV`H zI^>ApQ(TJV$}{5p?BSjS{ZhnAP6P$18|&Y!m6u@$NCatfl=3Rm P00000NkvXXu0mjf++dG| literal 0 HcmV?d00001 diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/flair-digest@2x.png b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/flair-digest@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..384b495f9fe8cfe827043ee753716f5822949b33 GIT binary patch literal 553 zcmV+^0@nSBP)Lf{nRS`o^};kn!xIpZ@>Kv_ic6}lAolNeRViLwYnhyjZHDZ*<{?sIQ* zqz{3BkB#}doc?G>7oIb|S}jj)OyPb!No+KtWBmCHlCb`Ca@cA<=3rxco4EcXyRG~4 z{Pvoa@Cm$6KHr?k&Qf32PMGlP6D5hz^#BS)$}tJ$4GiYovNs}P*3mu`O!{$1&Usd_ zM|`pF@^}hTcS2^)@lp%WH9@N+YmbQC>5a6!)N!Oizw0hkCj)kKAZ==eiJ0wLZUF-I zs~n9@qNsX|S2;mdrTlR^xtDNa`|hpfsOg r-CE{8QGe7>7+?rAt#}jLu@Qa%7pbeOmy~Ze00000NkvXXu0mjfDFf|! literal 0 HcmV?d00001 diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/flair-digest@3x.png b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/flair-digest.imageset/flair-digest@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..93195403dbd13c0ba7d0cc340ddc214c6bc34685 GIT binary patch literal 467 zcmV;^0WAKBP)M~1iL2dv?;3nk=u%UDjyLS4ccF}sOnQPZb6lEU+1(?4sAKTzZ)!ohKq&7dm%oPYTpDiAO4uV3;_uTau$VKGTXY%I0;(w;#!^k+-oVy+a zxrn4bcH=h3)F&3wP}b+rnZTlsjX)iHD4^O&vZpzGBwK`ZPjmQ4wg?U#&q4JO^$9L| z&rVc0X0G>k;_wkY6eW_*Z?BK(yfow@lFl0&rm>@6th=tE2UrICl+KsnGJK-M8JW6& z_DSS=zSDW=bpAc+{4+3vnK{% Date: Fri, 17 May 2024 12:04:29 +0800 Subject: [PATCH 3/3] Clean up messages in digest onboarding --- .../App/Views/AI/DigestConfigView.swift | 12 ++--- .../App/Views/AI/FullScreenDigestView.swift | 47 ++++++++++++++----- .../AudioSession/AudioController.swift | 8 ++-- .../Services/DataService/AI/AITasks.swift | 23 ++++----- .../Mutations/UpdateUserPersonalization.swift | 2 +- 5 files changed, 56 insertions(+), 36 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift b/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift index b9acf8784..98ee89d4e 100644 --- a/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift @@ -40,6 +40,7 @@ public class DigestConfigViewModel: ObservableObject { try await dataService.setupUserDigestConfig() try await dataService.refreshDigest() digestEnabled = true + dataService.featureFlags.digestEnabled = true } catch { if error is IneligibleError { isIneligible = true @@ -109,20 +110,17 @@ struct DigestConfigView: View { } .padding(.top, 50) } else if viewModel.digestEnabled { - VStack(spacing: 15) { + VStack(spacing: 25) { Spacer() - Text(""" - You've been added to the AI Digest demo. Your first issue should be ready soon. - When a new digest is ready the icon in the library header will change color. - You can close this window now. - """) + // swiftlint:disable:next line_length + Text("You've been added to the AI Digest demo. Your first issue should be ready soon. When a new digest is ready the icon in the library header will change color. You can close this window now.") if !viewModel.notificationsEnabled { if viewModel.isTryingToEnableNotifications { ProgressView() } else { Button(action: { viewModel.tryEnableNotifications(dataService: dataService) - }, label: { Text("Notify me when its ready") }) + }, label: { Text("Enable digest notifications") }) .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 fc92a1db9..94ea44818 100644 --- a/apple/OmnivoreKit/Sources/App/Views/AI/FullScreenDigestView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/AI/FullScreenDigestView.swift @@ -12,17 +12,19 @@ func getChapterData(digest: DigestResult) -> [(DigestChapter, DigestChapterData) var currentAudioIndex = 0 var currentWordCount = 0.0 - for (index, speechFile) in digest.speechFiles.enumerated() { - let chapter = digest.chapters[index] + for (index, speechFile) in (digest.speechFiles ?? []).enumerated() { + let chapter = digest.chapters?[index] let duration = currentWordCount / SpeechDocument.averageWPM / speed * 60.0 - chapterData.append((chapter, DigestChapterData( - time: formatTimeInterval(duration) ?? "00:00", - start: Int(currentAudioIndex), - end: currentAudioIndex + Int(speechFile.utterances.count) - ))) - currentAudioIndex += Int(speechFile.utterances.count) - currentWordCount += chapter.wordCount + if let chapter = chapter { + chapterData.append((chapter, DigestChapterData( + time: formatTimeInterval(duration) ?? "00:00", + start: Int(currentAudioIndex), + end: currentAudioIndex + Int(speechFile.utterances.count) + ))) + currentAudioIndex += Int(speechFile.utterances.count) + currentWordCount += chapter.wordCount + } } return chapterData } @@ -39,6 +41,7 @@ func formatTimeInterval(_ time: TimeInterval) -> String? { public class FullScreenDigestViewModel: ObservableObject { @Published var isLoading = false @Published var hasError = false + @Published var isRunning = false @Published var digest: DigestResult? @Published var chapterInfo: [(DigestChapter, DigestChapterData)]? @Published var presentedLibraryItem: String? @@ -49,6 +52,7 @@ public class FullScreenDigestViewModel: ObservableObject { func load(dataService: DataService, audioController: AudioController) async { hasError = false isLoading = true + isRunning = false if !dataService.digestNeedsRefresh() { if let digest = dataService.loadStoredDigest() { @@ -69,6 +73,8 @@ public class FullScreenDigestViewModel: ObservableObject { self.digest = digest self.chapterInfo = getChapterData(digest: digest) self.lastVisitedDigestId = digest.id + self.isRunning = digest.jobState == "RUNNING" || digest.jobState == "PENDING" + self.hasError = digest.jobState == "FAILED" if let playingDigest = audioController.itemAudioProperties as? DigestAudioItem, playingDigest.digest.id == digest.id { // Don't think we need to do anything here @@ -179,9 +185,11 @@ struct FullScreenDigestView: View { await viewModel.load(dataService: dataService, audioController: audioController) } }, label: { Text("Try again") }) - .buttonStyle(RoundedRectButtonStyle(color: Color.blue, textColor: Color.white)) + .buttonStyle(RoundedRectButtonStyle(color: Color.blue, textColor: Color.white)) Spacer() } + } else if viewModel.isRunning { + jobRunningText } else { itemBody } @@ -192,6 +200,19 @@ struct FullScreenDigestView: View { await viewModel.load(dataService: dataService, audioController: audioController) } } + + var jobRunningText: some View { + VStack { + Spacer() + Text(""" + You've been added to the AI Digest demo. Your first issue should be ready soon. + When a new digest is ready the icon in the library header will change color. + You can close this window now. + """) + .padding(20) + Spacer() + } + } var closeButton: some View { Button(action: { @@ -218,7 +239,7 @@ struct FullScreenDigestView: View { Spacer() } if let digest = viewModel.digest { - Text(digest.title) + Text(digest.title ?? "") .font(Font.system(size: 17, weight: .semibold)) .lineSpacing(5) .lineLimit(3) @@ -274,14 +295,14 @@ struct FullScreenDigestView: View { .padding(.top, 20) } - if let digest = viewModel.digest { + if let digest = viewModel.digest, let content = digest.content { Text("Transcript") .font(Font.system(size: 17, weight: .semibold)) .frame(maxWidth: .infinity, alignment: .leading) .padding(.top, 20) VStack { - Markdown(digest.content) + Markdown(content) .foregroundColor(Color.appGrayTextContrast) } .padding(15) diff --git a/apple/OmnivoreKit/Sources/Services/AudioSession/AudioController.swift b/apple/OmnivoreKit/Sources/Services/AudioSession/AudioController.swift index 21b10a54b..ce28e4536 100644 --- a/apple/OmnivoreKit/Sources/Services/AudioSession/AudioController.swift +++ b/apple/OmnivoreKit/Sources/Services/AudioSession/AudioController.swift @@ -44,7 +44,7 @@ public struct DigestAudioItem: AudioItemProperties { public init(digest: DigestResult, chapters: [DigestChapterData]) { self.digest = digest self.itemID = digest.id - self.title = digest.title + self.title = digest.title ?? "Omnivore digest" self.chapters = chapters self.startIndex = 0 @@ -52,7 +52,7 @@ public struct DigestAudioItem: AudioItemProperties { self.imageURL = nil - if let first = digest.speechFiles.first { + if let first = digest.speechFiles?.first { self.language = first.language self.byline = digest.byline } @@ -1033,7 +1033,7 @@ public struct DigestAudioItem: AudioItemProperties { } func combineSpeechFiles(from digest: DigestResult) -> ([Utterance], Double) { - let allUtterances = digest.speechFiles.flatMap { $0.utterances } + let allUtterances = digest.speechFiles?.flatMap { $0.utterances } ?? [] var updatedUtterances: [Utterance] = [] var currentWordOffset = 0.0 @@ -1053,7 +1053,7 @@ public struct DigestAudioItem: AudioItemProperties { } func downloadDigestItemSpeechFile(itemID: String, priority: DownloadPriority) async throws -> SpeechDocument? { - if let digestItem = itemAudioProperties as? DigestAudioItem, let firstFile = digestItem.digest.speechFiles.first { + if let digestItem = itemAudioProperties as? DigestAudioItem, let firstFile = digestItem.digest.speechFiles?.first { let (utterances, wordCount) = combineSpeechFiles(from: digestItem.digest) let document = SpeechDocument( diff --git a/apple/OmnivoreKit/Sources/Services/DataService/AI/AITasks.swift b/apple/OmnivoreKit/Sources/Services/DataService/AI/AITasks.swift index b529929f4..088e83b5e 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/AI/AITasks.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/AI/AITasks.swift @@ -9,16 +9,16 @@ struct AITaskRequest: Decodable { public struct DigestResult: Codable { public let id: String - public let title: String - public let byline: String - public let content: String - public let description: String - public let urlsToAudio: [String] - public let chapters: [DigestChapter] - public let speechFiles: [SpeechDocument] + public let title: String? + public let byline: String? + public let content: String? + public let description: String? + public let urlsToAudio: [String]? + public let chapters: [DigestChapter]? + public let speechFiles: [SpeechDocument]? - public let jobState: String - public let createdAt: String + public let jobState: String? + public let createdAt: String? } public struct DigestChapter: Codable { @@ -169,9 +169,10 @@ extension DataService { do { let digest = try await networker.urlSession.performRequest(resource: resource) - let oldDigest = loadStoredDigest() - saveDigest(digest) + if digest.jobState == "SUCCEEDED" { + saveDigest(digest) + } return digest } catch { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateUserPersonalization.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateUserPersonalization.swift index 4f6f98e4d..25508cb7b 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateUserPersonalization.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateUserPersonalization.swift @@ -40,7 +40,7 @@ public extension DataService { try $0.setUserPersonalization( input: InputObjects.SetUserPersonalizationInput( digestConfig: OptionalArgument( - InputObjects.DigestConfigInput(channels: OptionalArgument([OptionalArgument("email")])) + InputObjects.DigestConfigInput(channels: OptionalArgument([OptionalArgument("push")])) ) ), selection: selection