From bcae91e76c7d0f4350eac706cb23032a365716ab Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Fri, 10 May 2024 13:30:42 +0800 Subject: [PATCH] Start adding a view to configure digest --- .../App/Views/AI/DigestConfigView.swift | 161 ++++++++++++++++++ .../App/Views/Home/HomeFeedViewIOS.swift | 19 +++ .../App/Views/Home/HomeFeedViewModel.swift | 1 + 3 files changed, 181 insertions(+) create mode 100644 apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift diff --git a/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift b/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift new file mode 100644 index 0000000000..934d13be17 --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift @@ -0,0 +1,161 @@ +import SwiftUI +import Models +import Services +import Views +import MarkdownUI +import Utils +import Transmission + +@MainActor +public class DigestConfigViewModel: ObservableObject { + @Published var isLoading = false + @Published var digest: DigestResult? + @Published var chapterInfo: [(DigestChapter, DigestChapterData)]? + @Published var presentedLibraryItem: String? + @Published var presentWebContainer = false + + @AppStorage(UserDefaultKey.lastVisitedDigestId.rawValue) var lastVisitedDigestId = "" + + func load(dataService: DataService) async { + isLoading = true + if !digestNeedsRefresh() { + if let digest = dataService.loadStoredDigest() { + self.digest = digest + } + } else { + do { + if let digest = try await dataService.getLatestDigest(timeoutInterval: 10) { + self.digest = digest + } + } catch { + print("ERROR WITH DIGEST: ", error) + self.digest = nil + } + } + + isLoading = false + } + + func refreshDigest(dataService: DataService) async { + do { + try await dataService.refreshDigest() + } catch { + print("ERROR WITH DIGEST: ", error) + } + } + + func digestNeedsRefresh() -> Bool { + let fileManager = FileManager.default + let localURL = URL.om_cachesDirectory.appendingPathComponent("digest.json") + do { + let attributes = try fileManager.attributesOfItem(atPath: localURL.path) + if let modificationDate = attributes[.modificationDate] as? Date { + // Two hours ago + let twoHoursAgo = Date().addingTimeInterval(-2 * 60 * 60) + return modificationDate < twoHoursAgo + } + } catch { + print("Error: \(error)") + } + return true + } +} + +@available(iOS 17.0, *) +@MainActor +struct DigestConfigView: View { + @StateObject var viewModel = DigestConfigViewModel() + let dataService: DataService + + @Environment(\.dismiss) private var dismiss + + public init(dataService: DataService) { + self.dataService = dataService + } + + var titleBlock: some View { + HStack { + Text("Omnivore Digest") + .font(Font.system(size: 18, weight: .semibold)) + Image.tabDigestSelected + Spacer() + closeButton + } + .padding(.top, 20) + .padding(.horizontal, 20) + } + + var body: some View { + VStack { + titleBlock + .padding(.top, 10) + itemBody + .padding(15) + + Spacer() + }.task { + await viewModel.load(dataService: dataService) + } + } + + var closeButton: some View { + Button(action: { + dismiss() + }, label: { + Text("Close") + .foregroundColor(Color.blue) + }) + .buttonStyle(.plain) + } + + var logoBlock: some View { + HStack { + Image.coloredSmallOmnivoreLogo + .resizable() + .frame(width: 20, height: 20) + Text("Omnivore.app") + .font(Font.system(size: 14)) + .foregroundColor(Color.themeLibraryItemSubtle) + Spacer() + } + } + + @available(iOS 17.0, *) + var itemBody: some View { + VStack(alignment: .leading, spacing: 20) { + logoBlock + + let description1 = + """ + Omnivore Digest is a free daily digest of your best recent library items. Omnivore + filters and ranks all the items recently added to your library, uses AI to summarize them, + and creates a short library item, email, or a daily podcast you can listen to in our iOS app. + + Note that if you sign up for Digest, your recent library items will be processed by an AI + service (Anthropic, or OpenAI). Your highlights, notes, and labels will not be sent to the AI + service. + + Digest is available to all users that have saved at least ten items and added two subscriptions. + """ + Markdown(description1) + .lineSpacing(10) + .accentColor(.appGraySolid) + .font(.appSubheadline) + .padding(5) + .frame(maxWidth: .infinity, alignment: .leading) + + HStack { + Spacer() + + Button(action: {}, label: { Text("Hide digest") }) + .buttonStyle(RoundedRectButtonStyle()) + + Button(action: {}, label: { Text("Enable digest") }) + .buttonStyle(RoundedRectButtonStyle(color: Color.blue, textColor: Color.white)) + } + } + .padding(15) + .background(Color.themeLabelBackground.opacity(0.6)) + .cornerRadius(5) + } +} diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 1f35cd9225..c19e91c628 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -192,6 +192,7 @@ struct AnimatingCellHeight: AnimatableModifier { @State var listTitle = "" @State var showExpandedAudioPlayer = false @State var showLibraryDigest = false + @State var showDigestConfig = false @Binding var isEditMode: EditMode @@ -330,6 +331,15 @@ struct AnimatingCellHeight: AnimatableModifier { Text("Sorry digest is only available on iOS 17 and above") } } + .sheet(isPresented: $showDigestConfig) { + if #available(iOS 17.0, *) { + NavigationView { + DigestConfigView(dataService: dataService) + } + } else { + Text("Sorry digest is only available on iOS 17 and above") + } + } .toolbar { toolbarItems } @@ -405,6 +415,15 @@ struct AnimatingCellHeight: AnimatableModifier { .buttonStyle(.plain) .padding(.trailing, 4) } + if #available(iOS 17.0, *), !dataService.featureFlags.digestEnabled, !viewModel.digestHidden { + // Give the user an opportunity to enable digest + Button( + action: { showDigestConfig = true }, + label: { Image.tabDigestSelected } + ) + .buttonStyle(.plain) + .padding(.trailing, 4) + } if prefersListLayout { Button( action: { isEditMode = isEditMode == .active ? .inactive : .active }, diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index bdda13bda8..03622c7329 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -50,6 +50,7 @@ enum LoadingBarStyle { @AppStorage(UserDefaultKey.stopUsingFollowingPrimer.rawValue) var stopUsingFollowingPrimer = false @AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false @AppStorage(UserDefaultKey.lastVisitedDigestId.rawValue) var lastVisitedDigestId = "" + @AppStorage("LibraryTabView::digestHidden") var digestHidden = false @AppStorage(UserDefaultKey.lastSelectedFeaturedItemFilter.rawValue) var featureFilter = FeaturedItemFilter.continueReading.rawValue