Improved loading of feature filters
This commit is contained in:
@ -39,7 +39,7 @@ struct LibraryFeatureCardNavigationLink: View {
|
||||
}
|
||||
)
|
||||
.confirmationDialog("", isPresented: $showFeatureActions) {
|
||||
if FeaturedItemFilter(rawValue: viewModel.featureFilter) == .pinned {
|
||||
if FeaturedItemFilter(rawValue: viewModel.fetcher.featureFilter) == .pinned {
|
||||
Button("Unpin", action: {
|
||||
viewModel.unpinItem(dataService: dataService, item: item)
|
||||
})
|
||||
@ -53,7 +53,7 @@ struct LibraryFeatureCardNavigationLink: View {
|
||||
Button("Remove", action: {
|
||||
viewModel.removeLibraryItem(dataService: dataService, objectID: item.objectID)
|
||||
})
|
||||
if FeaturedItemFilter(rawValue: viewModel.featureFilter) != .pinned {
|
||||
if FeaturedItemFilter(rawValue: viewModel.fetcher.featureFilter) != .pinned {
|
||||
Button("Mark Read", action: {
|
||||
viewModel.markRead(dataService: dataService, item: item)
|
||||
})
|
||||
|
||||
@ -9,13 +9,13 @@ import Utils
|
||||
import Views
|
||||
|
||||
@MainActor final class LibraryItemFetcher: NSObject, ObservableObject {
|
||||
let folder = "inbox"
|
||||
|
||||
@Published var items = [Models.LibraryItem]()
|
||||
var itemsPublisher: Published<[Models.LibraryItem]>.Publisher { $items }
|
||||
@Published var featureItems = [Models.LibraryItem]()
|
||||
|
||||
private var fetchedResultsController: NSFetchedResultsController<Models.LibraryItem>?
|
||||
|
||||
@AppStorage(UserDefaultKey.lastSelectedFeaturedItemFilter.rawValue) var featureFilter = FeaturedItemFilter.continueReading.rawValue
|
||||
|
||||
var cursor: String?
|
||||
|
||||
// These are used to make sure we handle search result
|
||||
@ -23,8 +23,11 @@ import Views
|
||||
var searchIdx = 0
|
||||
var receivedIdx = 0
|
||||
|
||||
func setItems(_: NSManagedObjectContext, _ items: [Models.LibraryItem]) {
|
||||
func setItems(_ context: NSManagedObjectContext, _ items: [Models.LibraryItem]) {
|
||||
self.items = items
|
||||
if let filter = FeaturedItemFilter(rawValue: featureFilter) {
|
||||
updateFeatureFilter(context: context, filter: filter)
|
||||
}
|
||||
}
|
||||
|
||||
func loadCurrentViewer(dataService: DataService) async {
|
||||
@ -226,6 +229,29 @@ import Views
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
func refreshFeatureItems(dataService: DataService) {
|
||||
if let featureFilter = FeaturedItemFilter(rawValue: self.featureFilter) {
|
||||
updateFeatureFilter(context: dataService.viewContext, filter: featureFilter)
|
||||
}
|
||||
}
|
||||
|
||||
func updateFeatureFilter(context: NSManagedObjectContext, filter: FeaturedItemFilter?) {
|
||||
if let filter = filter {
|
||||
Task {
|
||||
featureFilter = filter.rawValue
|
||||
|
||||
featureItems = await loadFeatureItems(
|
||||
context: context,
|
||||
predicate: filter.predicate,
|
||||
sort: filter.sortDescriptor
|
||||
)
|
||||
print("FEATURE ITEMS: ", featureItems)
|
||||
}
|
||||
} else {
|
||||
featureItems = []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension LibraryItemFetcher: NSFetchedResultsControllerDelegate {
|
||||
|
||||
@ -131,15 +131,9 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
.fullScreenCover(isPresented: $showExpandedAudioPlayer) {
|
||||
ExpandedAudioPlayer()
|
||||
}
|
||||
.sheet(isPresented: $showOpenAIVoices) {
|
||||
OpenAIVoicesModal(audioController: audioController)
|
||||
}
|
||||
.onAppear {
|
||||
if !openAIPrimerDisplayed, !Voices.isOpenAIVoice(self.audioController.currentVoice) {
|
||||
showOpenAIVoices = true
|
||||
openAIPrimerDisplayed = true
|
||||
}
|
||||
}
|
||||
// .onAppear {
|
||||
// viewModel.refreshFeatureItems(dataService: dataService)
|
||||
// }
|
||||
.toolbar {
|
||||
toolbarItems
|
||||
}
|
||||
@ -182,6 +176,9 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
LibraryAddLinkView()
|
||||
}
|
||||
}
|
||||
.introspectNavigationController { nav in
|
||||
nav.delegate = viewModel
|
||||
}
|
||||
.task {
|
||||
await viewModel.loadFilters(dataService: dataService)
|
||||
}
|
||||
@ -469,17 +466,17 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
HStack {
|
||||
Menu(content: {
|
||||
Button(action: {
|
||||
viewModel.updateFeatureFilter(context: dataService.viewContext, filter: .continueReading)
|
||||
viewModel.fetcher.updateFeatureFilter(context: dataService.viewContext, filter: .continueReading)
|
||||
}, label: {
|
||||
Text("Continue Reading")
|
||||
})
|
||||
Button(action: {
|
||||
viewModel.updateFeatureFilter(context: dataService.viewContext, filter: .pinned)
|
||||
viewModel.fetcher.updateFeatureFilter(context: dataService.viewContext, filter: .pinned)
|
||||
}, label: {
|
||||
Text("Pinned")
|
||||
})
|
||||
Button(action: {
|
||||
viewModel.updateFeatureFilter(context: dataService.viewContext, filter: .newsletters)
|
||||
viewModel.fetcher.updateFeatureFilter(context: dataService.viewContext, filter: .newsletters)
|
||||
}, label: {
|
||||
Text("Newsletters")
|
||||
})
|
||||
@ -493,7 +490,7 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
HStack(alignment: .center) {
|
||||
Image(systemName: "line.3.horizontal.decrease")
|
||||
.font(Font.system(size: 13, weight: .regular))
|
||||
Text((FeaturedItemFilter(rawValue: viewModel.featureFilter) ?? .continueReading).title)
|
||||
Text((FeaturedItemFilter(rawValue: viewModel.fetcher.featureFilter) ?? .continueReading).title)
|
||||
.font(Font.system(size: 13, weight: .medium))
|
||||
}
|
||||
.tint(Color(hex: "#007AFF"))
|
||||
@ -510,17 +507,17 @@ struct AnimatingCellHeight: AnimatableModifier {
|
||||
|
||||
GeometryReader { geo in
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
if viewModel.featureItems.count > 0 {
|
||||
if viewModel.fetcher.featureItems.count > 0 {
|
||||
HStack(alignment: .top, spacing: 15) {
|
||||
Spacer(minLength: 1).frame(width: 1)
|
||||
ForEach(viewModel.featureItems) { item in
|
||||
ForEach(viewModel.fetcher.featureItems) { item in
|
||||
LibraryFeatureCardNavigationLink(item: item, viewModel: viewModel)
|
||||
}
|
||||
Spacer(minLength: 1).frame(width: 1)
|
||||
}
|
||||
.padding(.top, 0)
|
||||
} else {
|
||||
Text((FeaturedItemFilter(rawValue: viewModel.featureFilter) ?? .continueReading).emptyMessage)
|
||||
Text((FeaturedItemFilter(rawValue: viewModel.fetcher.featureFilter) ?? .continueReading).emptyMessage)
|
||||
.padding(.horizontal, UIDevice.isIPad ? 20 : 10)
|
||||
.font(Font.system(size: 14, weight: .regular))
|
||||
.foregroundColor(Color(hex: "#898989"))
|
||||
@ -1034,10 +1031,10 @@ struct LinkDestination: View {
|
||||
|
||||
func fakeLibraryItems(dataService _: DataService) -> [LibraryItemData] {
|
||||
Array(
|
||||
repeatElement(UUID().uuidString, count: 20)
|
||||
.map { uuid in
|
||||
repeatElement(0, count: 20)
|
||||
.map { _ in
|
||||
LibraryItemData(
|
||||
id: uuid,
|
||||
id: UUID().uuidString,
|
||||
title: "fake title that is kind of long so it looks better",
|
||||
pageURLString: "",
|
||||
isArchived: false,
|
||||
|
||||
@ -5,7 +5,7 @@ import SwiftUI
|
||||
import Utils
|
||||
import Views
|
||||
|
||||
@MainActor final class HomeFeedViewModel: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
|
||||
@MainActor final class HomeFeedViewModel: NSObject, ObservableObject, UINavigationControllerDelegate {
|
||||
let folder: String
|
||||
let fetcher: LibraryItemFetcher
|
||||
let listConfig: LibraryListConfig
|
||||
@ -21,15 +21,10 @@ import Views
|
||||
@Published var presentWebContainer = false
|
||||
@Published var showLoadingBar = true
|
||||
|
||||
@Published var selectedLinkItem: NSManagedObjectID? // used by mac app only
|
||||
@Published var selectedItem: Models.LibraryItem?
|
||||
@Published var linkIsActive = false
|
||||
|
||||
@Published var showLabelsSheet = false
|
||||
@Published var showFiltersModal = false
|
||||
@Published var showCommunityModal = false
|
||||
@Published var featureItems = [Models.LibraryItem]()
|
||||
|
||||
@Published var showSnackbar = false
|
||||
@Published var snackbarOperation: SnackbarOperation?
|
||||
|
||||
@ -41,8 +36,6 @@ import Views
|
||||
@Published var appliedSort = LinkedItemSort.newest.rawValue
|
||||
|
||||
@AppStorage(UserDefaultKey.hideFeatureSection.rawValue) var hideFeatureSection = false
|
||||
@AppStorage(UserDefaultKey.lastSelectedFeaturedItemFilter.rawValue) var featureFilter = FeaturedItemFilter.continueReading.rawValue
|
||||
|
||||
@AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false
|
||||
|
||||
@Published var appliedFilter: InternalFilter? {
|
||||
@ -63,22 +56,6 @@ import Views
|
||||
super.init()
|
||||
}
|
||||
|
||||
func updateFeatureFilter(context: NSManagedObjectContext, filter: FeaturedItemFilter?) {
|
||||
if let filter = filter {
|
||||
Task {
|
||||
featureFilter = filter.rawValue
|
||||
|
||||
featureItems = await loadFeatureItems(
|
||||
context: context,
|
||||
predicate: filter.predicate,
|
||||
sort: filter.sortDescriptor
|
||||
)
|
||||
}
|
||||
} else {
|
||||
featureItems = []
|
||||
}
|
||||
}
|
||||
|
||||
func loadFilters(dataService: DataService) async {
|
||||
switch folder {
|
||||
case "following":
|
||||
@ -156,7 +133,6 @@ import Views
|
||||
showLoadingBar = isRefresh
|
||||
|
||||
await fetcher.loadItems(dataService: dataService, filterState: filterState, isRefresh: isRefresh)
|
||||
updateFeatureFilter(context: dataService.viewContext, filter: FeaturedItemFilter(rawValue: featureFilter))
|
||||
|
||||
isLoading = false
|
||||
showLoadingBar = false
|
||||
@ -176,7 +152,16 @@ import Views
|
||||
fetchRequest.predicate = predicate
|
||||
fetchRequest.sortDescriptors = [sort]
|
||||
|
||||
return (try? context.fetch(fetchRequest)) ?? []
|
||||
print("using predicate to load feature items: ", predicate)
|
||||
|
||||
do {
|
||||
let fetched = try context.fetch(fetchRequest)
|
||||
return fetched
|
||||
} catch {
|
||||
print("ERROR FETCHING: ", error)
|
||||
}
|
||||
return []
|
||||
// return (try? context.fetch(fetchRequest)) ?? []
|
||||
}
|
||||
|
||||
func snackbar(_ message: String, undoAction: SnackbarUndoAction? = nil) {
|
||||
@ -220,7 +205,7 @@ import Views
|
||||
dataService.setItemLabels(itemID: item.unwrappedID, labels: InternalLinkedItemLabel.make(Set(existingLabels + [label]) as NSSet))
|
||||
|
||||
item.update(inContext: dataService.viewContext)
|
||||
updateFeatureFilter(context: dataService.viewContext, filter: FeaturedItemFilter(rawValue: featureFilter))
|
||||
fetcher.refreshFeatureItems(dataService: dataService)
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,16 +219,12 @@ import Views
|
||||
|
||||
func pinItem(dataService: DataService, item: Models.LibraryItem) {
|
||||
addLabel(dataService: dataService, item: item, label: "Pinned", color: "#0A84FF")
|
||||
if featureFilter == FeaturedItemFilter.pinned.rawValue {
|
||||
updateFeatureFilter(context: dataService.viewContext, filter: .pinned)
|
||||
}
|
||||
fetcher.refreshFeatureItems(dataService: dataService)
|
||||
}
|
||||
|
||||
func unpinItem(dataService: DataService, item: Models.LibraryItem) {
|
||||
removeLabel(dataService: dataService, item: item, named: "Pinned")
|
||||
if featureFilter == FeaturedItemFilter.pinned.rawValue {
|
||||
updateFeatureFilter(context: dataService.viewContext, filter: .pinned)
|
||||
}
|
||||
fetcher.refreshFeatureItems(dataService: dataService)
|
||||
}
|
||||
|
||||
func markRead(dataService: DataService, item: Models.LibraryItem) {
|
||||
|
||||
@ -17,33 +17,6 @@ import Views
|
||||
action: { viewModel.itemUnderLabelEdit = item },
|
||||
label: { Label(item.labels?.count == 0 ? "Add Labels" : "Edit Labels", systemImage: "tag") }
|
||||
)
|
||||
// Button(action: {
|
||||
// withAnimation(.linear(duration: 0.4)) {
|
||||
// viewModel.setLinkArchived(
|
||||
// dataService: dataService,
|
||||
// objectID: item.objectID,
|
||||
// archived: !item.isArchived
|
||||
// )
|
||||
// }
|
||||
// }, label: {
|
||||
// Label(
|
||||
// item.isArchived ? "Unarchive" : "Archive",
|
||||
// systemImage: item.isArchived ? "tray.and.arrow.down.fill" : "archivebox"
|
||||
// )
|
||||
// })
|
||||
// Button("Remove Item", role: .destructive) {
|
||||
// viewModel.removeLink(dataService: dataService, objectID: item.objectID)
|
||||
// }
|
||||
// if let author = item.author {
|
||||
// Button(
|
||||
// action: {
|
||||
// viewModel.filterState.searchTerm = "author:\"\(author)\""
|
||||
// },
|
||||
// label: {
|
||||
// Label(String("More by \(author)"), systemImage: "person")
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
} else {
|
||||
Button(
|
||||
action: { viewModel.recoverItem(dataService: dataService, itemID: item.unwrappedID) },
|
||||
|
||||
@ -21,36 +21,6 @@ import SwiftUI
|
||||
@AppStorage("inboxMenuState") var inboxMenuState = "open"
|
||||
@AppStorage("followingMenuState") var followingMenuState = "open"
|
||||
|
||||
func createInboxViewModel(_ filter: InternalFilter) -> HomeFeedViewModel {
|
||||
let result = HomeFeedViewModel(
|
||||
folder: "inbox",
|
||||
fetcher: LibraryItemFetcher(),
|
||||
listConfig: LibraryListConfig(
|
||||
hasFeatureCards: true,
|
||||
leadingSwipeActions: [.pin],
|
||||
trailingSwipeActions: [.archive, .delete],
|
||||
cardStyle: .library
|
||||
)
|
||||
)
|
||||
result.appliedFilter = filter
|
||||
return result
|
||||
}
|
||||
|
||||
func createFollowingViewModel(_ filter: InternalFilter) -> HomeFeedViewModel {
|
||||
let result = HomeFeedViewModel(
|
||||
folder: "following",
|
||||
fetcher: LibraryItemFetcher(),
|
||||
listConfig: LibraryListConfig(
|
||||
hasFeatureCards: false,
|
||||
leadingSwipeActions: [.moveToInbox],
|
||||
trailingSwipeActions: [.archive, .delete],
|
||||
cardStyle: .library
|
||||
)
|
||||
)
|
||||
result.appliedFilter = filter
|
||||
return result
|
||||
}
|
||||
|
||||
var innerBody: some View {
|
||||
ZStack {
|
||||
NavigationLink("", destination: HomeView(viewModel: inboxViewModel), isActive: $inboxActive)
|
||||
|
||||
@ -22,28 +22,6 @@ import Views
|
||||
trackReadEvent()
|
||||
}
|
||||
|
||||
func handleArchiveAction(dataService: DataService) {
|
||||
guard let objectID = item?.objectID ?? pdfItem?.objectID else { return }
|
||||
dataService.archiveLink(objectID: objectID, archived: !isItemArchived)
|
||||
showInLibrarySnackbar(!isItemArchived ? "Link archived" : "Link unarchived")
|
||||
}
|
||||
|
||||
func handleDeleteAction(dataService: DataService) {
|
||||
guard let objectID = item?.objectID ?? pdfItem?.objectID else { return }
|
||||
removeLibraryItemAction(dataService: dataService, objectID: objectID)
|
||||
}
|
||||
|
||||
func updateItemReadStatus(dataService: DataService) {
|
||||
guard let itemID = item?.unwrappedID ?? pdfItem?.itemID else { return }
|
||||
|
||||
dataService.updateLinkReadingProgress(
|
||||
itemID: itemID,
|
||||
readingProgress: isItemRead ? 0 : 100,
|
||||
anchorIndex: 0,
|
||||
force: false
|
||||
)
|
||||
}
|
||||
|
||||
private func trackReadEvent() {
|
||||
guard let itemID = item?.unwrappedID ?? pdfItem?.itemID else { return }
|
||||
guard let slug = item?.unwrappedSlug ?? pdfItem?.slug else { return }
|
||||
|
||||
Reference in New Issue
Block a user