define viewer as a nsmanagedobject
This commit is contained in:
@ -18,9 +18,9 @@ public final class PDFViewerViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
public func loadHighlights(completion onComplete: @escaping ([Highlight]) -> Void) {
|
||||
guard let viewer = services.dataService.currentViewer else { return }
|
||||
guard let username = services.dataService.currentViewer?.username else { return }
|
||||
|
||||
services.dataService.pdfHighlightsPublisher(username: viewer.username, slug: feedItem.slug).sink(
|
||||
services.dataService.pdfHighlightsPublisher(username: username, slug: feedItem.slug).sink(
|
||||
receiveCompletion: { [weak self] completion in
|
||||
guard case .failure = completion else { return }
|
||||
onComplete(self?.allHighlights(fetchedHighlights: []) ?? [])
|
||||
|
||||
@ -56,7 +56,7 @@ enum PDFProvider {
|
||||
|
||||
if let viewer = viewer {
|
||||
createWebAppWrapperViewModel(
|
||||
username: viewer.username,
|
||||
username: viewer.username ?? "",
|
||||
dataService: dataService,
|
||||
rawAuthCookie: rawAuthCookie
|
||||
)
|
||||
|
||||
@ -21,8 +21,8 @@ import Views
|
||||
guard let viewer = try? await dataService.fetchViewer() else { return }
|
||||
|
||||
profileCardData = ProfileCardData(
|
||||
name: viewer.name,
|
||||
username: viewer.username,
|
||||
name: viewer.name ?? "",
|
||||
username: viewer.username ?? "",
|
||||
imageURL: viewer.profileImageURL.flatMap { URL(string: $0) }
|
||||
)
|
||||
}
|
||||
|
||||
@ -64,8 +64,8 @@ public final class RootViewModel: ObservableObject {
|
||||
return
|
||||
}
|
||||
|
||||
if let viewer = try? await services.dataService.fetchViewer() {
|
||||
let path = linkRequestPath(username: viewer.username, requestID: linkRequestID)
|
||||
if let username = try? await services.dataService.fetchViewer().username {
|
||||
let path = linkRequestPath(username: username, requestID: linkRequestID)
|
||||
webLinkPath = SafariWebLinkPath(id: UUID(), path: path)
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,14 +25,14 @@ final class WebReaderViewModel: ObservableObject {
|
||||
self.slug = slug
|
||||
isLoading = true
|
||||
|
||||
guard let viewer = dataService.currentViewer else { return }
|
||||
guard let username = dataService.currentViewer?.username else { return }
|
||||
|
||||
if let content = dataService.pageFromCache(slug: slug) {
|
||||
articleContent = content
|
||||
// continue to load from the web if possible
|
||||
}
|
||||
|
||||
dataService.articleContentPublisher(username: viewer.username, slug: slug).sink(
|
||||
dataService.articleContentPublisher(username: username, slug: slug).sink(
|
||||
receiveCompletion: { [weak self] completion in
|
||||
guard case .failure = completion else { return }
|
||||
self?.isLoading = false
|
||||
|
||||
@ -64,10 +64,22 @@
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<entity name="Viewer" representedClassName="Viewer" syncable="YES" codeGenerationType="class">
|
||||
<attribute name="name" attributeType="String"/>
|
||||
<attribute name="profileImageURL" optional="YES" attributeType="String"/>
|
||||
<attribute name="userID" attributeType="String"/>
|
||||
<attribute name="username" attributeType="String"/>
|
||||
<uniquenessConstraints>
|
||||
<uniquenessConstraint>
|
||||
<constraint value="userID"/>
|
||||
</uniquenessConstraint>
|
||||
</uniquenessConstraints>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="PersistedFeedItemLabel" positionX="-36" positionY="18" width="128" height="104"/>
|
||||
<element name="PersistedFeedItem" positionX="-18" positionY="63" width="128" height="284"/>
|
||||
<element name="PersistedArticleContent" positionX="9" positionY="108" width="128" height="59"/>
|
||||
<element name="PersistedFeedItem" positionX="-18" positionY="63" width="128" height="284"/>
|
||||
<element name="PersistedFeedItemLabel" positionX="-36" positionY="18" width="128" height="104"/>
|
||||
<element name="PersistedHighlight" positionX="27" positionY="225" width="128" height="209"/>
|
||||
<element name="Viewer" positionX="45" positionY="234" width="128" height="89"/>
|
||||
</elements>
|
||||
</model>
|
||||
@ -7,6 +7,14 @@ public class PersistentContainer: NSPersistentContainer {
|
||||
public static func make() -> PersistentContainer {
|
||||
let modelURL = Bundle.module.url(forResource: "CoreDataModel", withExtension: "momd")!
|
||||
let model = NSManagedObjectModel(contentsOf: modelURL)!
|
||||
return PersistentContainer(name: "DataModel", managedObjectModel: model)
|
||||
let container = PersistentContainer(name: "DataModel", managedObjectModel: model)
|
||||
|
||||
container.viewContext.automaticallyMergesChangesFromParent = false
|
||||
container.viewContext.name = "viewContext"
|
||||
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||
container.viewContext.undoManager = nil
|
||||
container.viewContext.shouldDeleteInaccessibleFaults = true
|
||||
|
||||
return container
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,22 +44,3 @@ public extension UserProfile {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public struct Viewer {
|
||||
public let userID: String
|
||||
public let username: String
|
||||
public let name: String
|
||||
public let profileImageURL: String?
|
||||
|
||||
public init(
|
||||
username: String,
|
||||
name: String,
|
||||
profileImageURL: String?,
|
||||
userID: String
|
||||
) {
|
||||
self.username = username
|
||||
self.name = name
|
||||
self.profileImageURL = profileImageURL
|
||||
self.userID = userID
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,14 +2,15 @@ import Combine
|
||||
import CoreData
|
||||
import Foundation
|
||||
import Models
|
||||
import OSLog
|
||||
|
||||
public final class DataService: ObservableObject {
|
||||
public static var registerIntercomUser: ((String) -> Void)?
|
||||
public static var showIntercomMessenger: (() -> Void)?
|
||||
|
||||
public let appEnvironment: AppEnvironment
|
||||
public internal(set) var currentViewer: Viewer?
|
||||
let networker: Networker
|
||||
static let logger = Logger(subsystem: "app.omnivore", category: "data-service")
|
||||
|
||||
let persistentContainer: PersistentContainer
|
||||
var subscriptions = Set<AnyCancellable>()
|
||||
@ -27,6 +28,11 @@ public final class DataService: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
public var currentViewer: Viewer? {
|
||||
let fetchRequest: NSFetchRequest<Models.Viewer> = Viewer.fetchRequest()
|
||||
return try? persistentContainer.viewContext.fetch(fetchRequest).first
|
||||
}
|
||||
|
||||
public func clearHighlights() {
|
||||
deletedHighlightsIDs.removeAll()
|
||||
|
||||
@ -41,7 +47,7 @@ public final class DataService: ObservableObject {
|
||||
do {
|
||||
try persistentContainer.viewContext.save()
|
||||
} catch {
|
||||
print("failed to delete objects")
|
||||
DataService.logger.debug("failed to delete objects")
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,11 +63,11 @@ public final class DataService: ObservableObject {
|
||||
|
||||
public extension DataService {
|
||||
func prefetchPages(items: [FeedItem]) {
|
||||
guard let viewer = currentViewer else { return }
|
||||
guard let username = currentViewer?.username else { return }
|
||||
|
||||
for item in items {
|
||||
let slug = item.slug
|
||||
articleContentPublisher(username: viewer.username, slug: slug).sink(
|
||||
articleContentPublisher(username: username, slug: slug).sink(
|
||||
receiveCompletion: { _ in },
|
||||
receiveValue: { _ in }
|
||||
)
|
||||
|
||||
@ -6,7 +6,7 @@ import SwiftGraphQL
|
||||
public extension DataService {
|
||||
func articlePublisher(slug: String) -> AnyPublisher<FeedItem, BasicError> {
|
||||
internalViewerPublisher()
|
||||
.flatMap { self.internalArticlePublisher(username: $0.username, slug: slug) }
|
||||
.flatMap { self.internalArticlePublisher(username: $0.username ?? "", slug: slug) }
|
||||
.receive(on: DispatchQueue.main)
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import Combine
|
||||
import CoreData
|
||||
import Foundation
|
||||
import Models
|
||||
import SwiftGraphQL
|
||||
@ -6,16 +7,16 @@ import Utils
|
||||
|
||||
public extension DataService {
|
||||
func fetchViewer() async throws -> Viewer {
|
||||
let selection = Selection<Viewer, Objects.User> {
|
||||
Viewer(
|
||||
let selection = Selection<ViewerInternal, Objects.User> {
|
||||
ViewerInternal(
|
||||
userID: try $0.id(),
|
||||
username: try $0.profile(
|
||||
selection: .init { try $0.username() }
|
||||
),
|
||||
name: try $0.name(),
|
||||
profileImageURL: try $0.profile(
|
||||
selection: .init { try $0.pictureUrl() }
|
||||
),
|
||||
userID: try $0.id()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -30,12 +31,16 @@ public extension DataService {
|
||||
send(query, to: path, headers: headers) { [weak self] result in
|
||||
switch result {
|
||||
case let .success(payload):
|
||||
self?.currentViewer = payload.data
|
||||
if UserDefaults.standard.string(forKey: Keys.userIdKey) == nil {
|
||||
UserDefaults.standard.setValue(payload.data.userID, forKey: Keys.userIdKey)
|
||||
DataService.registerIntercomUser?(payload.data.userID)
|
||||
}
|
||||
continuation.resume(returning: payload.data)
|
||||
|
||||
if let self = self, let viewer = payload.data.persist(context: self.persistentContainer.viewContext) {
|
||||
continuation.resume(returning: viewer)
|
||||
} else {
|
||||
continuation.resume(throwing: BasicError.message(messageText: "coredata error"))
|
||||
}
|
||||
case .failure:
|
||||
continuation.resume(throwing: BasicError.message(messageText: "http error"))
|
||||
}
|
||||
@ -47,16 +52,16 @@ public extension DataService {
|
||||
extension DataService {
|
||||
@available(*, deprecated, message: "use async version instead")
|
||||
func internalViewerPublisher() -> AnyPublisher<Viewer, BasicError> {
|
||||
let selection = Selection<Viewer, Objects.User> {
|
||||
Viewer(
|
||||
let selection = Selection<ViewerInternal, Objects.User> {
|
||||
ViewerInternal(
|
||||
userID: try $0.id(),
|
||||
username: try $0.profile(
|
||||
selection: .init { try $0.username() }
|
||||
),
|
||||
name: try $0.name(),
|
||||
profileImageURL: try $0.profile(
|
||||
selection: .init { try $0.pictureUrl() }
|
||||
),
|
||||
userID: try $0.id()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@ -72,8 +77,11 @@ extension DataService {
|
||||
send(query, to: path, headers: headers) { result in
|
||||
switch result {
|
||||
case let .success(payload):
|
||||
self?.currentViewer = payload.data
|
||||
promise(.success(payload.data))
|
||||
if let self = self, let viewer = payload.data.persist(context: self.persistentContainer.viewContext) {
|
||||
promise(.success(viewer))
|
||||
} else {
|
||||
promise(.failure(.message(messageText: "coredata error")))
|
||||
}
|
||||
case .failure:
|
||||
promise(.failure(.message(messageText: "http error")))
|
||||
}
|
||||
@ -83,3 +91,28 @@ extension DataService {
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
||||
private struct ViewerInternal {
|
||||
let userID: String
|
||||
let username: String
|
||||
let name: String
|
||||
let profileImageURL: String?
|
||||
|
||||
func persist(context: NSManagedObjectContext) -> Viewer? {
|
||||
let viewer = Viewer(context: context)
|
||||
viewer.userID = userID
|
||||
viewer.username = username
|
||||
viewer.name = name
|
||||
viewer.profileImageURL = profileImageURL
|
||||
|
||||
do {
|
||||
try context.save()
|
||||
DataService.logger.debug("Viewer saved succesfully")
|
||||
return viewer
|
||||
} catch {
|
||||
context.rollback()
|
||||
DataService.logger.debug("Failed to save Viewer: \(error.localizedDescription)")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user