Merge pull request #3933 from omnivore-app/feat/ios-enable-digest
Start adding a view to configure digest
This commit is contained in:
161
apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift
Normal file
161
apple/OmnivoreKit/Sources/App/Views/AI/DigestConfigView.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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 },
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user