()
- init(item: FeedItem, homeFeedViewModel: HomeFeedViewModel) {
+ init(item: LinkedItem, homeFeedViewModel: HomeFeedViewModel) {
self.item = item
self.homeFeedViewModel = homeFeedViewModel
}
func handleArchiveAction(dataService: DataService) {
- homeFeedViewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: !item.isArchived)
+ homeFeedViewModel.setLinkArchived(
+ dataService: dataService,
+ objectID: item.objectID,
+ archived: !item.isArchived
+ )
}
func handleDeleteAction(dataService: DataService) {
- homeFeedViewModel.removeLink(dataService: dataService, linkId: item.id)
+ homeFeedViewModel.removeLink(dataService: dataService, objectID: item.objectID)
}
func updateItemReadStatus(dataService: DataService) {
- dataService
- .updateArticleReadingProgressPublisher(
- itemID: item.id,
- readingProgress: item.isRead ? 0 : 100,
- anchorIndex: 0
- )
- .sink { completion in
- guard case let .failure(error) = completion else { return }
- print(error)
- } receiveValue: { [weak self] feedItem in
- self?.item.readingProgress = feedItem.readingProgress
- }
- .store(in: &subscriptions)
+ dataService.updateLinkReadingProgress(
+ itemID: item.unwrappedID,
+ readingProgress: item.isRead ? 0 : 100,
+ anchorIndex: 0
+ )
}
func loadWebAppWrapper(dataService: DataService, rawAuthCookie: String?) async {
@@ -51,12 +47,20 @@ enum PDFProvider {
return currentViewer
}
- return try? await dataService.fetchViewer()
+ guard let viewerObjectID = try? await dataService.fetchViewer() else { return nil }
+
+ var result: Viewer?
+
+ await dataService.viewContext.perform {
+ result = dataService.viewContext.object(with: viewerObjectID) as? Viewer
+ }
+
+ return result
}()
if let viewer = viewer {
createWebAppWrapperViewModel(
- username: viewer.username,
+ username: viewer.unwrappedUsername,
dataService: dataService,
rawAuthCookie: rawAuthCookie
)
@@ -68,7 +72,7 @@ enum PDFProvider {
let urlRequest = URLRequest.webRequest(
baseURL: dataService.appEnvironment.webAppBaseURL,
- urlPath: "/app/\(username)/\(item.slug)",
+ urlPath: "/app/\(username)/\(item.unwrappedSlug)",
queryParams: ["isAppEmbedView": "true", "highlightBarDisabled": isMacApp ? "false" : "true"]
)
@@ -78,12 +82,10 @@ enum PDFProvider {
rawAuthCookie: rawAuthCookie
)
- newWebAppWrapperViewModel.performActionSubject.sink { [weak self] action in
+ newWebAppWrapperViewModel.performActionSubject.sink { action in
switch action {
case let .shareHighlight(highlightID):
print("show share modal for highlight with id: \(highlightID)")
- case let .updateReadingProgess(progress: progress):
- self?.homeFeedViewModel.uncommittedReadingProgressUpdates[self?.item.id ?? ""] = Double(progress)
}
}
.store(in: &newWebAppWrapperViewModel.subscriptions)
diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift
index 5f171b349..ae9660ffc 100644
--- a/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift
+++ b/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift
@@ -16,9 +16,11 @@ final class NewsletterEmailsViewModel: ObservableObject {
dataService.newsletterEmailsPublisher().sink(
receiveCompletion: { _ in },
- receiveValue: { [weak self] result in
+ receiveValue: { [weak self] objectIDs in
self?.isLoading = false
- self?.emails = result
+ dataService.viewContext.perform {
+ self?.emails = objectIDs.compactMap { dataService.viewContext.object(with: $0) as? NewsletterEmail }
+ }
self?.hasLoadedInitialEmails = true
}
)
@@ -32,9 +34,13 @@ final class NewsletterEmailsViewModel: ObservableObject {
receiveCompletion: { [weak self] _ in
self?.isLoading = false
},
- receiveValue: { [weak self] result in
+ receiveValue: { [weak self] objectID in
self?.isLoading = false
- self?.emails.insert(result, at: 0)
+ dataService.viewContext.perform {
+ if let item = dataService.viewContext.object(with: objectID) as? NewsletterEmail {
+ self?.emails.insert(item, at: 0)
+ }
+ }
}
)
.store(in: &subscriptions)
@@ -97,7 +103,7 @@ struct NewsletterEmailsView: View {
Snackbar.show(message: "Email copied")
},
- label: { Text(newsletterEmail.email) }
+ label: { Text(newsletterEmail.unwrappedEmail) }
)
}
}
diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift
index aab2f65ab..07d967767 100644
--- a/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift
+++ b/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift
@@ -18,11 +18,24 @@ import Views
}
func loadProfileData(dataService: DataService) async {
- guard let viewer = try? await dataService.fetchViewer() else { return }
+ if let currentViewer = dataService.currentViewer {
+ loadProfileCardData(viewer: currentViewer)
+ return
+ }
+ guard let viewerObjectID = try? await dataService.fetchViewer() else { return }
+
+ await dataService.viewContext.perform {
+ if let viewer = dataService.viewContext.object(with: viewerObjectID) as? Viewer {
+ self.loadProfileCardData(viewer: viewer)
+ }
+ }
+ }
+
+ private func loadProfileCardData(viewer: Viewer) {
profileCardData = ProfileCardData(
- name: viewer.name,
- username: viewer.username,
+ name: viewer.unwrappedName,
+ username: viewer.unwrappedUsername,
imageURL: viewer.profileImageURL.flatMap { URL(string: $0) }
)
}
diff --git a/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift b/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift
index 135be02c1..596ff4d69 100644
--- a/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift
+++ b/apple/OmnivoreKit/Sources/App/Views/RootView/RootView.swift
@@ -29,6 +29,7 @@ public struct RootView: View {
InnerRootView(viewModel: viewModel)
.environmentObject(viewModel.services.authenticator)
.environmentObject(viewModel.services.dataService)
+ .environment(\.managedObjectContext, viewModel.services.dataService.viewContext)
.onAppear {
if let pdfViewerProvider = pdfViewerProvider {
viewModel.configurePDFProvider(pdfViewerProvider: pdfViewerProvider)
@@ -59,10 +60,17 @@ struct InnerRootView: View {
}
})
#endif
- .snackBar(
- isShowing: $viewModel.showSnackbar,
- text: Text(viewModel.snackbarMessage ?? "")
- )
+ .snackBar(isShowing: $viewModel.showSnackbar, message: viewModel.snackbarMessage)
+ // Schedule the dismissal every time we present the snackbar.
+ .onChange(of: viewModel.showSnackbar) { newValue in
+ if newValue {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
+ withAnimation {
+ viewModel.showSnackbar = false
+ }
+ }
+ }
+ }
#if os(iOS)
.customAlert(isPresented: $viewModel.showPushNotificationPrimer) {
pushNotificationPrimerView
diff --git a/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift
index a8afdf799..3ed6b0654 100644
--- a/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift
+++ b/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift
@@ -33,9 +33,9 @@ public final class RootViewModel: ObservableObject {
func configurePDFProvider(pdfViewerProvider: @escaping (URL, PDFViewerViewModel) -> AnyView) {
guard PDFProvider.pdfViewerProvider == nil else { return }
- PDFProvider.pdfViewerProvider = { [weak self] url, feedItem in
+ PDFProvider.pdfViewerProvider = { [weak self] url, linkedItem in
guard let self = self else { return AnyView(Text("")) }
- return pdfViewerProvider(url, PDFViewerViewModel(services: self.services, feedItem: feedItem))
+ return pdfViewerProvider(url, PDFViewerViewModel(services: self.services, linkedItem: linkedItem))
}
}
@@ -64,9 +64,11 @@ public final class RootViewModel: ObservableObject {
return
}
- if let viewer = try? await services.dataService.fetchViewer() {
- let path = linkRequestPath(username: viewer.username, requestID: linkRequestID)
- webLinkPath = SafariWebLinkPath(id: UUID(), path: path)
+ if let viewerObjectID = try? await services.dataService.fetchViewer() {
+ if let viewer = services.dataService.viewContext.object(with: viewerObjectID) as? Viewer {
+ let path = linkRequestPath(username: viewer.unwrappedUsername, requestID: linkRequestID)
+ webLinkPath = SafariWebLinkPath(id: UUID(), path: path)
+ }
}
}
diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift
index a4544fafb..7e219b424 100644
--- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift
+++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift
@@ -6,8 +6,9 @@ import WebKit
#if os(iOS)
struct WebReader: UIViewRepresentable {
- let articleContent: ArticleContent
- let item: FeedItem
+ let htmlContent: String
+ let highlightsJSONString: String
+ let item: LinkedItem
let openLinkAction: (URL) -> Void
let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void
let navBarVisibilityRatioUpdater: (Double) -> Void
@@ -99,7 +100,8 @@ import WebKit
func loadContent(webView: WKWebView) {
webView.loadHTMLString(
WebReaderContent(
- articleContent: articleContent,
+ htmlContent: htmlContent,
+ highlightsJSONString: highlightsJSONString,
item: item,
isDark: UITraitCollection.current.userInterfaceStyle == .dark,
fontSize: fontSize()
diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift
index d0c8cf42d..67a45339b 100644
--- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift
+++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift
@@ -7,7 +7,7 @@ import WebKit
#if os(iOS)
struct WebReaderContainerView: View {
- let item: FeedItem
+ let item: LinkedItem
let homeFeedViewModel: HomeFeedViewModel
@State private var showFontSizePopover = false
@@ -33,14 +33,6 @@ import WebKit
}
func webViewActionHandler(message: WKScriptMessage, replyHandler: WKScriptMessageReplyHandler?) {
- if message.name == WebViewAction.readingProgressUpdate.rawValue {
- let messageBody = message.body as? [String: Double]
-
- if let messageBody = messageBody, let progress = messageBody["progress"] {
- homeFeedViewModel.uncommittedReadingProgressUpdates[item.id] = Double(progress)
- }
- }
-
if let replyHandler = replyHandler {
viewModel.webViewActionWithReplyHandler(
message: message,
@@ -53,12 +45,6 @@ import WebKit
if message.name == WebViewAction.highlightAction.rawValue {
handleHighlightAction(message: message)
}
-
- if message.name == WebViewAction.readingProgressUpdate.rawValue {
- guard let messageBody = message.body as? [String: Double] else { return }
- guard let progress = messageBody["progress"] else { return }
- homeFeedViewModel.uncommittedReadingProgressUpdates[item.id] = Double(progress)
- }
}
private func handleHighlightAction(message: WKScriptMessage) {
@@ -107,7 +93,7 @@ import WebKit
action: {
homeFeedViewModel.setLinkArchived(
dataService: dataService,
- linkId: item.id,
+ objectID: item.objectID,
archived: !item.isArchived
)
},
@@ -139,7 +125,7 @@ import WebKit
}
.alert("Are you sure?", isPresented: $showDeleteConfirmation) {
Button("Remove Link", role: .destructive) {
- homeFeedViewModel.removeLink(dataService: dataService, linkId: item.id)
+ homeFeedViewModel.removeLink(dataService: dataService, objectID: item.objectID)
}
Button("Cancel", role: .cancel, action: {})
}
@@ -149,7 +135,8 @@ import WebKit
ZStack {
if let articleContent = viewModel.articleContent {
WebReader(
- articleContent: articleContent,
+ htmlContent: articleContent.htmlContent,
+ highlightsJSONString: articleContent.highlightsJSONString,
item: item,
openLinkAction: {
#if os(macOS)
@@ -205,7 +192,7 @@ import WebKit
.contentShape(Rectangle())
.onAppear {
if !viewModel.isLoading {
- viewModel.loadContent(dataService: dataService, slug: item.slug)
+ viewModel.loadContent(dataService: dataService, slug: item.unwrappedSlug)
}
}
}
diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift
index 1f0b813ea..779079c38 100644
--- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift
+++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift
@@ -4,26 +4,29 @@ import Utils
struct WebReaderContent {
let textFontSize: Int
- let articleContent: ArticleContent
- let item: FeedItem
+ let htmlContent: String
+ let highlightsJSONString: String
+ let item: LinkedItem
let themeKey: String
init(
- articleContent: ArticleContent,
- item: FeedItem,
+ htmlContent: String,
+ highlightsJSONString: String,
+ item: LinkedItem,
isDark: Bool,
fontSize: Int
) {
self.textFontSize = fontSize
- self.articleContent = articleContent
+ self.htmlContent = htmlContent
+ self.highlightsJSONString = highlightsJSONString
self.item = item
self.themeKey = isDark ? "Gray" : "LightGray"
}
// swiftlint:disable line_length
var styledContent: String {
- let savedAt = "new Date(\(item.savedAt.timeIntervalSince1970 * 1000)).toISOString()"
- let createdAt = "new Date(\(item.createdAt.timeIntervalSince1970 * 1000)).toISOString()"
+ let savedAt = "new Date(\(item.unwrappedSavedAt.timeIntervalSince1970 * 1000)).toISOString()"
+ let createdAt = "new Date(\(item.unwrappedCreatedAt.timeIntervalSince1970 * 1000)).toISOString()"
let publishedAt = item.publishDate != nil ? "new Date(\(item.publishDate!.timeIntervalSince1970 * 1000)).toISOString()" : "undefined"
return """
@@ -39,7 +42,7 @@ struct WebReaderContent {
- \(articleContent.htmlContent)
+ \(htmlContent)