diff --git a/apple/OmnivoreKit/Sources/App/Views/AI/LibraryDigestView.swift b/apple/OmnivoreKit/Sources/App/Views/AI/LibraryDigestView.swift new file mode 100644 index 000000000..e6f4f91d7 --- /dev/null +++ b/apple/OmnivoreKit/Sources/App/Views/AI/LibraryDigestView.swift @@ -0,0 +1,267 @@ +import SwiftUI +import Models +import Services + + +struct DigestItem { + let id: String + let site: String + let siteIcon: URL? + let author: String + let title: String + let summaryText: String + let keyPointsText: String + let highlightsText: String +} + +@available(iOS 17.0, *) +@MainActor +struct LibraryDigestView: View { + let dataService: DataService + // @State private var currentIndex = 0 + @State private var items: [DigestItem] + // @State private var preloadedItems: [Int: String] = [:] + @Environment(\.dismiss) private var dismiss + + // let itemCount = 10 // Number of items to initially load + // let prefetchCount = 2 // Number of items to prefetch + + public init(dataService: DataService) { + self.dataService = dataService + self.items = [ + DigestItem( + id: "1468AFAA-88sdfsdfC-4546-BE02-EACF385288FC", + site: "CNBC.com", + siteIcon: URL(string: "https://www.cnbc.com/favicon.ico"), + author: "Kif Leswing", + title: "Apple shares just had their best day since last May", + summaryText: "In a significant political turn, the SOTU response faces unexpected collapse, marking a stark contrast to Trump's latest" + + " downturn, alongside an unprecedented surge in Biden's fundraising efforts as of 3/11/24, according to the TDPS Podcast. " + + "The analysis provides insights into the shifting dynamics of political support and the potential implications for future " + + "electoral strategies. ", + keyPointsText: "Key points from the article:", + highlightsText: "Highlights from the article:" + ), + DigestItem( + id: "1468AFAA-8sdfsdffsdf-4546-BE02-EACF385288FC", + site: "CNBC.com", + siteIcon: URL(string: "https://www.cnbc.com/favicon.ico"), + author: "Kif Leswing", + title: "Apple shares just had their best day since last May", + summaryText: "In a significant political turn, the SOTU response faces unexpected collapse, marking a stark contrast to Trump's latest" + + " downturn, alongside an unprecedented surge in Biden's fundraising efforts as of 3/11/24, according to the TDPS Podcast. " + + "The analysis provides insights into the shifting dynamics of political support and the potential implications for future " + + "electoral strategies. ", + keyPointsText: "Key points from the article:", + highlightsText: "Highlights from the article:" + ), + DigestItem( + id: "1468AFAA-882C-asdadfsa85288FC", + site: "CNBC.com", + siteIcon: URL(string: "https://www.cnbc.com/favicon.ico"), + author: "Kif Leswing", + title: "Apple shares just had their best day since last May", + summaryText: "In a significant political turn, the SOTU response faces unexpected collapse, marking a stark contrast to Trump's latest" + + " downturn, alongside an unprecedented surge in Biden's fundraising efforts as of 3/11/24, according to the TDPS Podcast. " + + "The analysis provides insights into the shifting dynamics of political support and the potential implications for future " + + "electoral strategies. ", + keyPointsText: "Key points from the article:", + highlightsText: "Highlights from the article:" + ) + ] + // currentIndex = 0 + // _preloadedItems = [Int:String] + } + + var body: some View { + itemBody + } + + @available(iOS 17.0, *) + var itemBody: some View { + ScrollView(.vertical) { + LazyVStack(spacing: 0) { + ForEach(Array(self.items.enumerated()), id: \.1.id) { idx, item in + PreviewItemView( + viewModel: PreviewItemViewModel(dataService: dataService, item: item, showSwipeHint: idx == 0) + ) + .containerRelativeFrame([.horizontal, .vertical]) + } + } + .scrollTargetLayout() + } + .scrollTargetBehavior(.paging) + .ignoresSafeArea() + + + // ScrollView(.horizontal, showsIndicators: false) { + // HStack(spacing: 0) { + // ForEach(items.indices, id: \.self) { index in + // if let item = preloadedItems[index] { + // ItemView(content: item) + // .onAppear { + // if index == items.count - prefetchCount { + // fetchItems() + // } + // } + // } + // } + // } + // } + // .frame(maxWidth: .infinity, maxHeight: .infinity) + .onAppear { + // fetchItems() + } + .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in + // Pause any background tasks if necessary + } + + } + +// private func fetchItems() { +// // Simulate fetching items from an API +// for idx in currentIndex.. Void let tapHandler: () -> Void + let explainHandler: ((String) -> Void)? let scrollPercentHandler: (Int) -> Void let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void let navBarVisibilityUpdater: (Bool) -> Void @@ -51,6 +52,7 @@ struct WebReader: PlatformViewRepresentable { let contentController = WKUserContentController() webView.tapHandler = tapHandler + webView.explainHandler = explainHandler webView.navigationDelegate = context.coordinator webView.configuration.userContentController = contentController webView.configuration.userContentController.removeAllScriptMessageHandlers() diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 1cb4aab3d..5eebee5ee 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -41,6 +41,8 @@ struct WebReaderContainerView: View { @State var displayLinkSheet = false @State var linkToOpen: URL? + @State var showExplainSheet = false + @EnvironmentObject var dataService: DataService @EnvironmentObject var audioController: AudioController @Environment(\.openURL) var openURL @@ -92,6 +94,11 @@ struct WebReaderContainerView: View { } } + private func explainHandler(text: String) { + viewModel.explainText = String(text) + showExplainSheet = true + } + private func handleHighlightAction(message: WKScriptMessage) { guard let messageBody = message.body as? [String: String] else { return } guard let actionID = messageBody["actionID"] else { return } @@ -271,6 +278,13 @@ struct WebReaderContainerView: View { Spacer() #endif +// Button( +// action: { showExplainSheet = true }, +// label: { Image(systemName: "sparkles") } +// ) +// .buttonStyle(.plain) +// .padding(.trailing, 4) + Button( action: { showLabelsModal = true }, label: { @@ -378,6 +392,7 @@ struct WebReaderContainerView: View { #endif }, tapHandler: tapHandler, + explainHandler: explainHandler, scrollPercentHandler: scrollPercentHandler, webViewActionHandler: webViewActionHandler, navBarVisibilityUpdater: { visible in @@ -485,6 +500,20 @@ struct WebReaderContainerView: View { .formSheet(isPresented: $showOpenArchiveSheet) { OpenArchiveTodayView(item: item) } + .formSheet(isPresented: $showExplainSheet) { + ExplainView( + viewModel: ExplainViewModel( + dataService: dataService, + item: self.item, + promptName: "explain-text-001", + extraText: viewModel.explainText + ), + dismissAction: { + viewModel.explainText = nil + showExplainSheet = false + } + ) + } #endif .sheet(isPresented: $showHighlightAnnotationModal) { NavigationView { @@ -633,7 +662,6 @@ struct WebReaderContainerView: View { try? WebViewManager.shared().dispatchEvent(.saveReadPosition) } .onDisappear { - // WebViewManager.shared().loadHTMLString("", baseURL: nil) WebViewManager.shared().loadHTMLString(WebReaderContent.emptyContent(isDark: Color.isDarkMode), baseURL: nil) } .onReceive(NotificationCenter.default.publisher(for: Notification.Name("PopToRoot"))) { _ in @@ -674,7 +702,7 @@ struct WebReaderContainerView: View { shareActionID = UUID() } - func print() { + func printReader() { shareActionID = UUID() } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift index 1a70626cf..7d5071194 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift @@ -21,6 +21,8 @@ struct SafariWebLink: Identifiable { @Published var showOperationToast: Bool = false @Published var operationStatus: OperationStatus = .none + @Published var explainText: String? + func hasOriginalUrl(_ item: Models.LibraryItem) -> Bool { if let pageURLString = item.pageURLString, let host = URL(string: pageURLString)?.host { if host == "omnivore.app" { diff --git a/apple/OmnivoreKit/Sources/Services/Authentication/AuthModels.swift b/apple/OmnivoreKit/Sources/Services/Authentication/AuthModels.swift index 550389c56..fb450b0d6 100644 --- a/apple/OmnivoreKit/Sources/Services/Authentication/AuthModels.swift +++ b/apple/OmnivoreKit/Sources/Services/Authentication/AuthModels.swift @@ -75,7 +75,7 @@ extension LoginError { return .unauthorized case .unknown: return .unknown - case .pendingEmailVerification: + case .pendingEmailVerification, .stillProcessing: return .pendingEmailVerification } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Errors/ServerError.swift b/apple/OmnivoreKit/Sources/Services/DataService/Errors/ServerError.swift index 601b2e4cf..7e51b2a1a 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Errors/ServerError.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Errors/ServerError.swift @@ -10,6 +10,7 @@ public enum ServerError: String, Error { case unauthenticated case timeout case unknown + case stillProcessing case pendingEmailVerification } @@ -22,6 +23,8 @@ extension ServerError { case 401?, 403?: self = .unauthenticated return + case 202: + self = .stillProcessing default: break } diff --git a/apple/OmnivoreKit/Sources/Views/Article/OmnivoreWebView.swift b/apple/OmnivoreKit/Sources/Views/Article/OmnivoreWebView.swift index af128dd9b..05dcfb44d 100644 --- a/apple/OmnivoreKit/Sources/Views/Article/OmnivoreWebView.swift +++ b/apple/OmnivoreKit/Sources/Views/Article/OmnivoreWebView.swift @@ -23,6 +23,7 @@ public final class OmnivoreWebView: WKWebView { #endif public var tapHandler: (() -> Void)? + public var explainHandler: ((String) -> Void)? private var currentMenu: ContextMenu = .defaultMenu @@ -298,6 +299,7 @@ public final class OmnivoreWebView: WKWebView { case #selector(removeSelection): return true case #selector(copy(_:)): return true case #selector(setLabels(_:)): return true + case #selector(explainSelection): return true case Selector(("_lookup:")): return (currentMenu == .defaultMenu) case Selector(("_define:")): return (currentMenu == .defaultMenu) @@ -334,6 +336,18 @@ public final class OmnivoreWebView: WKWebView { hideMenu() } + @objc private func explainSelection() { + Task { + let selection = try? await self.evaluateJavaScript("window.getSelection().toString()") + if let selection = selection as? String, let explainHandler = explainHandler { + print("Explaining \(selection)") + explainHandler(selection) + } else { + showInReaderSnackbar("Error getting text to explain") + } + } + } + @objc private func shareSelection() { do { try dispatchEvent(.share) @@ -386,7 +400,8 @@ public final class OmnivoreWebView: WKWebView { return } let highlight = UICommand(title: LocalText.genericHighlight, action: #selector(highlightSelection)) - items = [highlight, annotate] + // let explain = UICommand(title: "Explain", action: #selector(explainSelection)) + items = [highlight, /* explain, */ annotate] } else { let remove = UICommand(title: "Remove", action: #selector(removeSelection)) let setLabels = UICommand(title: LocalText.labelsGeneric, action: #selector(setLabels)) diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.swift b/apple/OmnivoreKit/Sources/Views/Images/Images.swift index f90d34ede..9b684783e 100644 --- a/apple/OmnivoreKit/Sources/Views/Images/Images.swift +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.swift @@ -9,6 +9,9 @@ public extension Image { static var tabFollowing: Image { Image("_tab_following", bundle: .module).renderingMode(.template) } static var tabLibrary: Image { Image("_tab_library", bundle: .module).renderingMode(.template) } + static var tabDigest: Image { Image("_tab_digest", bundle: .module).renderingMode(.template) } + static var tabDigestSelected: Image { Image("_tab_digest_selected", bundle: .module) } + static var tabSearch: Image { Image("_tab_search", bundle: .module).renderingMode(.template) } static var tabHighlights: Image { Image("_tab_highlights", bundle: .module).renderingMode(.template) } static var tabProfile: Image { Image("_tab_profile", bundle: .module).renderingMode(.template) } @@ -49,4 +52,7 @@ 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 doubleChevronUp: Image { Image("double_chevron_up", bundle: .module) } + } diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest.imageset/Contents.json b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest.imageset/Contents.json new file mode 100644 index 000000000..83aca2101 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "_tab_digest.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest.imageset/_tab_digest.svg b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest.imageset/_tab_digest.svg new file mode 100644 index 000000000..ccf49775d --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest.imageset/_tab_digest.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest_selected.imageset/Contents.json b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest_selected.imageset/Contents.json new file mode 100644 index 000000000..e5fed4197 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest_selected.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "_tab_digest_selected.svg", + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest_selected.imageset/_tab_digest_selected.svg b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest_selected.imageset/_tab_digest_selected.svg new file mode 100644 index 000000000..dfc6d541d --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/_tab_digest_selected.imageset/_tab_digest_selected.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-archive-button.imageset/Contents.json b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-archive-button.imageset/Contents.json new file mode 100644 index 000000000..340a487c3 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-archive-button.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "digest-archive-button.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-archive-button.imageset/digest-archive-button.svg b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-archive-button.imageset/digest-archive-button.svg new file mode 100644 index 000000000..ecdbd0d54 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-archive-button.imageset/digest-archive-button.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-dots-button.imageset/Contents.json b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-dots-button.imageset/Contents.json new file mode 100644 index 000000000..d8d84c76c --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-dots-button.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "digest-dots-button.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-dots-button.imageset/digest-dots-button.svg b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-dots-button.imageset/digest-dots-button.svg new file mode 100644 index 000000000..41032d9ee --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-dots-button.imageset/digest-dots-button.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-play-button.imageset/Contents.json b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-play-button.imageset/Contents.json new file mode 100644 index 000000000..04686abac --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-play-button.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "digest-play-button.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-play-button.imageset/digest-play-button.svg b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-play-button.imageset/digest-play-button.svg new file mode 100644 index 000000000..45875dbb5 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-play-button.imageset/digest-play-button.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-trash-button.imageset/Contents.json b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-trash-button.imageset/Contents.json new file mode 100644 index 000000000..fa3f18ec0 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-trash-button.imageset/Contents.json @@ -0,0 +1,24 @@ +{ + "images" : [ + { + "filename" : "digest-trash-button.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-trash-button.imageset/digest-trash-button.svg b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-trash-button.imageset/digest-trash-button.svg new file mode 100644 index 000000000..23cef56cb --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/digest-trash-button.imageset/digest-trash-button.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/double_chevron_up.imageset/Contents.json b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/double_chevron_up.imageset/Contents.json new file mode 100644 index 000000000..6a11721b2 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/double_chevron_up.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "double_chevron_up.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/double_chevron_up.imageset/double_chevron_up.svg b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/double_chevron_up.imageset/double_chevron_up.svg new file mode 100644 index 000000000..7ac7be15d --- /dev/null +++ b/apple/OmnivoreKit/Sources/Views/Images/Images.xcassets/double_chevron_up.imageset/double_chevron_up.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apple/OmnivoreKit/Sources/Views/Theme.swift b/apple/OmnivoreKit/Sources/Views/Theme.swift index e4b9147a9..69ebecf70 100644 --- a/apple/OmnivoreKit/Sources/Views/Theme.swift +++ b/apple/OmnivoreKit/Sources/Views/Theme.swift @@ -31,6 +31,22 @@ public enum Theme: String, CaseIterable { } } + public var fgColor: Color { + let prefersHighContrastText = UserDefaults.standard.bool(forKey: UserDefaultKey.prefersHighContrastWebFont.rawValue) + switch self { + case .system: + return Color.isDarkMode ? .white : .black + case .light: + return .black + case .dark: + return Color.white + case .sepia: + return prefersHighContrastText ? Color.black : (Color(hex: "#5F4B32") ?? Color.black) + case .apollo: + return prefersHighContrastText ? Color.white : (Color(hex: "#F3F3F3") ?? Color.white) + } + } + public var toolbarColor: Color { ThemeManager.currentTheme.isDark ? Color.themeDarkWhiteGray : Color.themeMiddleGray } @@ -98,6 +114,10 @@ public enum ThemeManager { currentTheme.bgColor } + public static var currentFgColor: Color { + currentTheme.fgColor + } + public static var currentHighlightColor: Color { currentTheme.highlightColor }