mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Update filters
This commit is contained in:
parent
1bff4cbd18
commit
26d6702c6f
9 changed files with 488 additions and 156 deletions
|
|
@ -12,18 +12,10 @@ class FetcherFilterState: ObservableObject {
|
|||
@Published var selectedLabels = [LinkedItemLabel]()
|
||||
@Published var negatedLabels = [LinkedItemLabel]()
|
||||
|
||||
@Published var appliedFilter: InternalFilter?
|
||||
@Published var appliedSort = LinkedItemSort.newest.rawValue
|
||||
|
||||
@Published var appliedFilter: InternalFilter? {
|
||||
didSet {
|
||||
let newValue = appliedFilter?.name.lowercased()
|
||||
UserDefaults.standard.setValue(newValue, forKey: "lastSelected-\(folder)-filter")
|
||||
}
|
||||
}
|
||||
|
||||
init(folder: String) {
|
||||
self.folder = folder
|
||||
let newValue = appliedFilter?.name.lowercased()
|
||||
let appliedFilterKey = UserDefaults.standard.string(forKey: "lastSelected-\(folder)-filter")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,20 +43,19 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
|
||||
@State private var selection = Set<String>()
|
||||
@ObservedObject var filterState: FetcherFilterState
|
||||
|
||||
func loadItems(isRefresh: Bool) {
|
||||
Task { await viewModel.loadItems(dataService: dataService, filterState: filterState, isRefresh: isRefresh) }
|
||||
Task { await viewModel.loadItems(dataService: dataService, isRefresh: isRefresh) }
|
||||
}
|
||||
|
||||
var showFeatureCards: Bool {
|
||||
viewModel.listConfig.hasFeatureCards &&
|
||||
!viewModel.hideFeatureSection &&
|
||||
viewModel.fetcher.items.count > 0 &&
|
||||
filterState.searchTerm.isEmpty &&
|
||||
filterState.selectedLabels.isEmpty &&
|
||||
filterState.negatedLabels.isEmpty &&
|
||||
filterState.appliedFilter?.name == "inbox"
|
||||
viewModel.filterState.searchTerm.isEmpty &&
|
||||
viewModel.filterState.selectedLabels.isEmpty &&
|
||||
viewModel.filterState.negatedLabels.isEmpty &&
|
||||
viewModel.filterState.appliedFilter?.name == "inbox"
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
|
@ -67,27 +66,26 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
isEditMode: $isEditMode,
|
||||
selection: $selection,
|
||||
viewModel: viewModel,
|
||||
filterState: filterState,
|
||||
showFeatureCards: showFeatureCards
|
||||
)
|
||||
.refreshable {
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
.onChange(of: filterState.searchTerm) { _ in
|
||||
.onChange(of: viewModel.filterState.searchTerm) { _ in
|
||||
// Maybe we should debounce this, but
|
||||
// it feels like it works ok without
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
.onChange(of: filterState.selectedLabels) { _ in
|
||||
.onChange(of: viewModel.filterState.selectedLabels) { _ in
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
.onChange(of: filterState.negatedLabels) { _ in
|
||||
.onChange(of: viewModel.filterState.negatedLabels) { _ in
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
.onChange(of: filterState.appliedFilter) { _ in
|
||||
.onChange(of: viewModel.filterState.appliedFilter) { _ in
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
.onChange(of: filterState.appliedSort) { _ in
|
||||
.onChange(of: viewModel.filterState.appliedSort) { _ in
|
||||
loadItems(isRefresh: true)
|
||||
}
|
||||
.sheet(item: $viewModel.itemUnderLabelEdit) { item in
|
||||
|
|
@ -127,10 +125,10 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
if let deepLink = DeepLink.make(from: url) {
|
||||
switch deepLink {
|
||||
case let .search(query):
|
||||
filterState.searchTerm = query
|
||||
viewModel.filterState.searchTerm = query
|
||||
case let .savedSearch(named):
|
||||
if let filter = viewModel.findFilter(dataService, named: named) {
|
||||
filterState.appliedFilter = filter
|
||||
viewModel.filterState.appliedFilter = filter
|
||||
}
|
||||
case let .webAppLinkRequest(requestID):
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
|
||||
|
|
@ -158,6 +156,7 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
if viewModel.fetcher.items.isEmpty {
|
||||
loadItems(isRefresh: false)
|
||||
}
|
||||
await viewModel.loadFilters(dataService: dataService, filterState: viewModel.filterState)
|
||||
}
|
||||
.environment(\.editMode, self.$isEditMode)
|
||||
}
|
||||
|
|
@ -167,7 +166,7 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
ToolbarItem(placement: .barLeading) {
|
||||
VStack(alignment: .leading) {
|
||||
let showDate = isListScrolled && !listTitle.isEmpty
|
||||
if let title = filterState.appliedFilter?.name {
|
||||
if let title = viewModel.filterState.appliedFilter?.name {
|
||||
Text(title)
|
||||
.font(Font.system(size: showDate ? 10 : 18, weight: .semibold))
|
||||
if showDate, prefersListLayout, isListScrolled || !showFeatureCards {
|
||||
|
|
@ -264,7 +263,6 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
@Binding var isEditMode: EditMode
|
||||
@Binding var selection: Set<String>
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
@ObservedObject var filterState: FetcherFilterState
|
||||
|
||||
let showFeatureCards: Bool
|
||||
|
||||
|
|
@ -294,23 +292,21 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
isEditMode: $isEditMode,
|
||||
selection: $selection,
|
||||
viewModel: viewModel,
|
||||
filterState: filterState,
|
||||
showFeatureCards: showFeatureCards
|
||||
)
|
||||
} else {
|
||||
HomeFeedGridView(
|
||||
viewModel: viewModel,
|
||||
filterState: filterState,
|
||||
isListScrolled: $isListScrolled
|
||||
)
|
||||
}
|
||||
}.sheet(isPresented: $viewModel.showLabelsSheet) {
|
||||
FilterByLabelsView(
|
||||
initiallySelected: filterState.selectedLabels,
|
||||
initiallyNegated: filterState.negatedLabels
|
||||
initiallySelected: viewModel.filterState.selectedLabels,
|
||||
initiallyNegated: viewModel.filterState.negatedLabels
|
||||
) {
|
||||
self.filterState.selectedLabels = $0
|
||||
self.filterState.negatedLabels = $1
|
||||
viewModel.filterState.selectedLabels = $0
|
||||
viewModel.filterState.negatedLabels = $1
|
||||
}
|
||||
}
|
||||
.popup(isPresented: $viewModel.showSnackbar) {
|
||||
|
|
@ -351,7 +347,6 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
|
||||
@Binding var selection: Set<String>
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
@ObservedObject var filterState: FetcherFilterState
|
||||
|
||||
let showFeatureCards: Bool
|
||||
|
||||
|
|
@ -359,20 +354,20 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
GeometryReader { reader in
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack {
|
||||
if filterState.searchTerm.count > 0 {
|
||||
TextChipButton.makeSearchFilterButton(title: filterState.searchTerm) {
|
||||
filterState.searchTerm = ""
|
||||
if viewModel.filterState.searchTerm.count > 0 {
|
||||
TextChipButton.makeSearchFilterButton(title: viewModel.filterState.searchTerm) {
|
||||
viewModel.filterState.searchTerm = ""
|
||||
}.frame(maxWidth: reader.size.width * 0.66)
|
||||
} else {
|
||||
Menu(
|
||||
content: {
|
||||
ForEach(viewModel.filters) { filter in
|
||||
Button(filter.name, action: { filterState.appliedFilter = filter })
|
||||
Button(filter.name, action: { viewModel.filterState.appliedFilter = filter })
|
||||
}
|
||||
},
|
||||
label: {
|
||||
TextChipButton.makeMenuButton(
|
||||
title: filterState.appliedFilter?.name ?? "-",
|
||||
title: viewModel.filterState.appliedFilter?.name ?? "-",
|
||||
color: .systemGray6
|
||||
)
|
||||
}
|
||||
|
|
@ -381,25 +376,25 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
Menu(
|
||||
content: {
|
||||
ForEach(LinkedItemSort.allCases, id: \.self) { sort in
|
||||
Button(sort.displayName, action: { filterState.appliedSort = sort.rawValue })
|
||||
Button(sort.displayName, action: { viewModel.filterState.appliedSort = sort.rawValue })
|
||||
}
|
||||
},
|
||||
label: {
|
||||
TextChipButton.makeMenuButton(
|
||||
title: LinkedItemSort(rawValue: filterState.appliedSort)?.displayName ?? "Sort",
|
||||
title: LinkedItemSort(rawValue: viewModel.filterState.appliedSort)?.displayName ?? "Sort",
|
||||
color: .systemGray6
|
||||
)
|
||||
}
|
||||
)
|
||||
TextChipButton.makeAddLabelButton(color: .systemGray6, onTap: { viewModel.showLabelsSheet = true })
|
||||
ForEach(filterState.selectedLabels, id: \.self) { label in
|
||||
ForEach(viewModel.filterState.selectedLabels, id: \.self) { label in
|
||||
TextChipButton.makeRemovableLabelButton(feedItemLabel: label, negated: false) {
|
||||
filterState.selectedLabels.removeAll { $0.id == label.id }
|
||||
viewModel.filterState.selectedLabels.removeAll { $0.id == label.id }
|
||||
}
|
||||
}
|
||||
ForEach(filterState.negatedLabels, id: \.self) { label in
|
||||
ForEach(viewModel.filterState.negatedLabels, id: \.self) { label in
|
||||
TextChipButton.makeRemovableLabelButton(feedItemLabel: label, negated: true) {
|
||||
filterState.negatedLabels.removeAll { $0.id == label.id }
|
||||
viewModel.filterState.negatedLabels.removeAll { $0.id == label.id }
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
|
|
@ -569,7 +564,7 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
.listRowSeparator(.hidden, edges: .all)
|
||||
.listRowInsets(.init(top: 0, leading: horizontalInset, bottom: 0, trailing: horizontalInset))
|
||||
|
||||
if let appliedFilter = filterState.appliedFilter,
|
||||
if let appliedFilter = viewModel.filterState.appliedFilter,
|
||||
networkMonitor.status == .disconnected,
|
||||
!appliedFilter.allowLocalFetch
|
||||
{
|
||||
|
|
@ -706,7 +701,6 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
@State var isContextMenuOpen = false
|
||||
|
||||
@ObservedObject var viewModel: HomeFeedViewModel
|
||||
@ObservedObject var filterState: FetcherFilterState
|
||||
|
||||
@Binding var isListScrolled: Bool
|
||||
|
||||
|
|
@ -726,27 +720,27 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
}
|
||||
|
||||
func loadItems(isRefresh: Bool) {
|
||||
Task { await viewModel.loadItems(dataService: dataService, filterState: filterState, isRefresh: isRefresh) }
|
||||
Task { await viewModel.loadItems(dataService: dataService, isRefresh: isRefresh) }
|
||||
}
|
||||
|
||||
var filtersHeader: some View {
|
||||
GeometryReader { reader in
|
||||
ScrollView(.horizontal, showsIndicators: false) {
|
||||
HStack {
|
||||
if filterState.searchTerm.count > 0 {
|
||||
TextChipButton.makeSearchFilterButton(title: filterState.searchTerm) {
|
||||
filterState.searchTerm = ""
|
||||
if viewModel.filterState.searchTerm.count > 0 {
|
||||
TextChipButton.makeSearchFilterButton(title: viewModel.filterState.searchTerm) {
|
||||
viewModel.filterState.searchTerm = ""
|
||||
}.frame(maxWidth: reader.size.width * 0.66)
|
||||
} else {
|
||||
Menu(
|
||||
content: {
|
||||
ForEach(viewModel.filters, id: \.self) { filter in
|
||||
Button(filter.name, action: { filterState.appliedFilter = filter })
|
||||
Button(filter.name, action: { viewModel.filterState.appliedFilter = filter })
|
||||
}
|
||||
},
|
||||
label: {
|
||||
TextChipButton.makeMenuButton(
|
||||
title: filterState.appliedFilter?.name ?? "-",
|
||||
title: viewModel.filterState.appliedFilter?.name ?? "-",
|
||||
color: .systemGray6
|
||||
)
|
||||
}
|
||||
|
|
@ -755,25 +749,25 @@ struct AnimatingCellHeight: AnimatableModifier {
|
|||
Menu(
|
||||
content: {
|
||||
ForEach(LinkedItemSort.allCases, id: \.self) { sort in
|
||||
Button(sort.displayName, action: { filterState.appliedSort = sort.rawValue })
|
||||
Button(sort.displayName, action: { viewModel.filterState.appliedSort = sort.rawValue })
|
||||
}
|
||||
},
|
||||
label: {
|
||||
TextChipButton.makeMenuButton(
|
||||
title: LinkedItemSort(rawValue: filterState.appliedSort)?.displayName ?? "Sort",
|
||||
title: LinkedItemSort(rawValue: viewModel.filterState.appliedSort)?.displayName ?? "Sort",
|
||||
color: .systemGray6
|
||||
)
|
||||
}
|
||||
)
|
||||
TextChipButton.makeAddLabelButton(color: .systemGray6, onTap: { viewModel.showLabelsSheet = true })
|
||||
ForEach(filterState.selectedLabels, id: \.self) { label in
|
||||
ForEach(viewModel.filterState.selectedLabels, id: \.self) { label in
|
||||
TextChipButton.makeRemovableLabelButton(feedItemLabel: label, negated: false) {
|
||||
filterState.selectedLabels.removeAll { $0.id == label.id }
|
||||
viewModel.filterState.selectedLabels.removeAll { $0.id == label.id }
|
||||
}
|
||||
}
|
||||
ForEach(filterState.negatedLabels, id: \.self) { label in
|
||||
ForEach(viewModel.filterState.negatedLabels, id: \.self) { label in
|
||||
TextChipButton.makeRemovableLabelButton(feedItemLabel: label, negated: true) {
|
||||
filterState.negatedLabels.removeAll { $0.id == label.id }
|
||||
viewModel.filterState.negatedLabels.removeAll { $0.id == label.id }
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
|
|
|
|||
|
|
@ -37,14 +37,17 @@ import Views
|
|||
|
||||
@Published var filters = [InternalFilter]()
|
||||
|
||||
@ObservedObject var filterState: FetcherFilterState
|
||||
|
||||
@AppStorage(UserDefaultKey.hideFeatureSection.rawValue) var hideFeatureSection = false
|
||||
@AppStorage(UserDefaultKey.lastSelectedFeaturedItemFilter.rawValue) var featureFilter = FeaturedItemFilter.continueReading.rawValue
|
||||
|
||||
let fetcher: LibraryItemFetcher
|
||||
|
||||
init(fetcher: LibraryItemFetcher, listConfig: LibraryListConfig) {
|
||||
init(fetcher: LibraryItemFetcher, filterState: FetcherFilterState, listConfig: LibraryListConfig) {
|
||||
self.fetcher = fetcher
|
||||
self.listConfig = listConfig
|
||||
self.filterState = filterState
|
||||
super.init()
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +67,32 @@ import Views
|
|||
}
|
||||
}
|
||||
|
||||
func itemAppeared(item: Models.LibraryItem, dataService _: DataService) async {
|
||||
func loadFilters(dataService: DataService, filterState: FetcherFilterState) async {
|
||||
switch filterState.folder {
|
||||
case "following":
|
||||
updateFilters(filterState: filterState, newFilters: InternalFilter.DefaultFollowingFilters)
|
||||
default:
|
||||
var hasLocalResults = false
|
||||
let fetchRequest: NSFetchRequest<Models.Filter> = Filter.fetchRequest()
|
||||
|
||||
// Load from disk
|
||||
if let results = try? dataService.viewContext.fetch(fetchRequest) {
|
||||
hasLocalResults = true
|
||||
updateFilters(filterState: filterState, newFilters: InternalFilter.make(from: results))
|
||||
}
|
||||
|
||||
let hasResults = hasLocalResults
|
||||
Task.detached {
|
||||
if let downloadedFilters = try? await dataService.filters() {
|
||||
await self.updateFilters(filterState: filterState, newFilters: downloadedFilters)
|
||||
} else if !hasResults {
|
||||
await self.updateFilters(filterState: filterState, newFilters: InternalFilter.DefaultInboxFilters)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func itemAppeared(item: Models.LibraryItem, dataService: DataService) async {
|
||||
if isLoading { return }
|
||||
let itemIndex = fetcher.items.firstIndex(where: { $0.id == item.id })
|
||||
let thresholdIndex = fetcher.items.index(fetcher.items.endIndex, offsetBy: -5)
|
||||
|
|
@ -72,9 +100,9 @@ import Views
|
|||
// Check if user has scrolled to the last five items in the list
|
||||
// Make sure we aren't currently loading though, as this would get triggered when the first set
|
||||
// of items are presented to the user.
|
||||
// if let itemIndex = itemIndex, itemIndex > thresholdIndex {
|
||||
// await loadMoreItems(dataService: dataService, isRefresh: false)
|
||||
// }
|
||||
if let itemIndex = itemIndex, itemIndex > thresholdIndex {
|
||||
await loadMoreItems(dataService: dataService, filterState: filterState, isRefresh: false)
|
||||
}
|
||||
}
|
||||
|
||||
func pushFeedItem(item _: Models.LibraryItem) {
|
||||
|
|
@ -98,38 +126,23 @@ import Views
|
|||
}
|
||||
}
|
||||
|
||||
func loadFilters(dataService: DataService) async {
|
||||
var hasLocalResults = false
|
||||
let fetchRequest: NSFetchRequest<Models.Filter> = Filter.fetchRequest()
|
||||
func updateFilters(filterState: FetcherFilterState, newFilters: [InternalFilter]) {
|
||||
let appliedFilterName = UserDefaults.standard.string(forKey: "lastSelected-\(filterState.folder)-filter") ?? filterState.folder
|
||||
|
||||
// Load from disk
|
||||
if let results = try? dataService.viewContext.fetch(fetchRequest) {
|
||||
hasLocalResults = true
|
||||
updateFilters(newFilters: InternalFilter.make(from: results))
|
||||
}
|
||||
filters = newFilters
|
||||
.filter { $0.folder == filterState.folder }
|
||||
.sorted(by: { $0.position < $1.position })
|
||||
+ [InternalFilter.DeletedFilter, InternalFilter.DownloadedFilter]
|
||||
|
||||
let hasResults = hasLocalResults
|
||||
Task.detached {
|
||||
if let downloadedFilters = try? await dataService.filters() {
|
||||
await self.updateFilters(newFilters: downloadedFilters)
|
||||
} else if !hasResults {
|
||||
await self.updateFilters(newFilters: InternalFilter.DefaultFilters)
|
||||
}
|
||||
if let newFilter = filters.first(where: { $0.name.lowercased() == appliedFilterName }), newFilter.id != filterState.appliedFilter?.id {
|
||||
filterState.appliedFilter = newFilter
|
||||
}
|
||||
}
|
||||
|
||||
func updateFilters(newFilters _: [InternalFilter]) {
|
||||
// filters = newFilters.sorted(by: { $0.position < $1.position }) + [InternalFilter.DeletedFilter, InternalFilter.DownloadedFilter]
|
||||
// if let newFilter = filters.first(where: { $0.name.lowercased() == appliedFilterName }), newFilter.id != appliedFilter?.id {
|
||||
// appliedFilter = newFilter
|
||||
// }
|
||||
}
|
||||
|
||||
func loadItems(dataService: DataService, filterState: FetcherFilterState, isRefresh: Bool) async {
|
||||
func loadItems(dataService: DataService, isRefresh: Bool) async {
|
||||
isLoading = true
|
||||
showLoadingBar = true
|
||||
|
||||
// group.addTask { await self.loadFilters(dataService: dataService) }
|
||||
await fetcher.loadItems(dataService: dataService, filterState: filterState, isRefresh: isRefresh)
|
||||
|
||||
updateFeatureFilter(context: dataService.viewContext, filter: FeaturedItemFilter(rawValue: featureFilter))
|
||||
|
|
|
|||
|
|
@ -6,17 +6,13 @@ import Views
|
|||
struct HomeView: View {
|
||||
@State private var viewModel: HomeFeedViewModel
|
||||
|
||||
let inboxFilterState = FetcherFilterState(
|
||||
folder: "inbox"
|
||||
)
|
||||
|
||||
init(viewModel: HomeFeedViewModel) {
|
||||
self.viewModel = viewModel
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
#if os(iOS)
|
||||
HomeFeedContainerView(viewModel: viewModel, filterState: inboxFilterState)
|
||||
HomeFeedContainerView(viewModel: viewModel)
|
||||
#elseif os(macOS)
|
||||
HomeFeedView(viewModel: viewModel)
|
||||
.frame(minWidth: 320)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import SwiftUI
|
|||
struct LibraryListView: View {
|
||||
@StateObject private var libraryViewModel = HomeFeedViewModel(
|
||||
fetcher: LibraryItemFetcher(),
|
||||
filterState: FetcherFilterState(folder: "inbox"),
|
||||
listConfig: LibraryListConfig(
|
||||
hasFeatureCards: true,
|
||||
leadingSwipeActions: [.pin],
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ struct LibraryTabView: View {
|
|||
|
||||
@StateObject private var followingViewModel = HomeFeedViewModel(
|
||||
fetcher: LibraryItemFetcher(),
|
||||
filterState: FetcherFilterState(folder: "following"),
|
||||
listConfig: LibraryListConfig(
|
||||
hasFeatureCards: false,
|
||||
leadingSwipeActions: [.moveToInbox],
|
||||
|
|
@ -35,6 +36,7 @@ struct LibraryTabView: View {
|
|||
|
||||
@StateObject private var libraryViewModel = HomeFeedViewModel(
|
||||
fetcher: LibraryItemFetcher(),
|
||||
filterState: FetcherFilterState(folder: "inbox"),
|
||||
listConfig: LibraryListConfig(
|
||||
hasFeatureCards: true,
|
||||
leadingSwipeActions: [.pin],
|
||||
|
|
@ -47,19 +49,13 @@ struct LibraryTabView: View {
|
|||
VStack(spacing: 0) {
|
||||
TabView(selection: $selectedTab) {
|
||||
NavigationView {
|
||||
HomeFeedContainerView(
|
||||
viewModel: followingViewModel,
|
||||
filterState: FetcherFilterState(folder: "following")
|
||||
)
|
||||
.navigationViewStyle(.stack)
|
||||
HomeFeedContainerView(viewModel: followingViewModel)
|
||||
.navigationViewStyle(.stack)
|
||||
}.tag("following")
|
||||
|
||||
NavigationView {
|
||||
HomeFeedContainerView(
|
||||
viewModel: libraryViewModel,
|
||||
filterState: FetcherFilterState(folder: "inbox")
|
||||
)
|
||||
.navigationViewStyle(.stack)
|
||||
HomeFeedContainerView(viewModel: libraryViewModel)
|
||||
.navigationViewStyle(.stack)
|
||||
}.tag("inbox")
|
||||
|
||||
NavigationView {
|
||||
|
|
|
|||
|
|
@ -5643,6 +5643,7 @@ extension Objects {
|
|||
let image: [String: String]
|
||||
let publishedAt: [String: DateTime]
|
||||
let title: [String: String]
|
||||
let type: [String: String]
|
||||
let updatedAt: [String: DateTime]
|
||||
let url: [String: String]
|
||||
|
||||
|
|
@ -5692,6 +5693,10 @@ extension Objects.Feed: Decodable {
|
|||
if let value = try container.decode(String?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
case "type":
|
||||
if let value = try container.decode(String?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
case "updatedAt":
|
||||
if let value = try container.decode(DateTime?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
|
|
@ -5717,6 +5722,7 @@ extension Objects.Feed: Decodable {
|
|||
image = map["image"]
|
||||
publishedAt = map["publishedAt"]
|
||||
title = map["title"]
|
||||
type = map["type"]
|
||||
updatedAt = map["updatedAt"]
|
||||
url = map["url"]
|
||||
}
|
||||
|
|
@ -5738,7 +5744,7 @@ extension Fields where TypeLock == Objects.Feed {
|
|||
}
|
||||
}
|
||||
|
||||
func createdAt() throws -> DateTime {
|
||||
func createdAt() throws -> DateTime? {
|
||||
let field = GraphQLField.leaf(
|
||||
name: "createdAt",
|
||||
arguments: []
|
||||
|
|
@ -5747,12 +5753,9 @@ extension Fields where TypeLock == Objects.Feed {
|
|||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
if let data = data.createdAt[field.alias!] {
|
||||
return data
|
||||
}
|
||||
throw HttpError.badpayload
|
||||
return data.createdAt[field.alias!]
|
||||
case .mocking:
|
||||
return DateTime.mockValue
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5771,7 +5774,7 @@ extension Fields where TypeLock == Objects.Feed {
|
|||
}
|
||||
}
|
||||
|
||||
func id() throws -> String {
|
||||
func id() throws -> String? {
|
||||
let field = GraphQLField.leaf(
|
||||
name: "id",
|
||||
arguments: []
|
||||
|
|
@ -5780,12 +5783,9 @@ extension Fields where TypeLock == Objects.Feed {
|
|||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
if let data = data.id[field.alias!] {
|
||||
return data
|
||||
}
|
||||
throw HttpError.badpayload
|
||||
return data.id[field.alias!]
|
||||
case .mocking:
|
||||
return String.mockValue
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5837,7 +5837,22 @@ extension Fields where TypeLock == Objects.Feed {
|
|||
}
|
||||
}
|
||||
|
||||
func updatedAt() throws -> DateTime {
|
||||
func type() throws -> String? {
|
||||
let field = GraphQLField.leaf(
|
||||
name: "type",
|
||||
arguments: []
|
||||
)
|
||||
select(field)
|
||||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
return data.type[field.alias!]
|
||||
case .mocking:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func updatedAt() throws -> DateTime? {
|
||||
let field = GraphQLField.leaf(
|
||||
name: "updatedAt",
|
||||
arguments: []
|
||||
|
|
@ -5846,12 +5861,9 @@ extension Fields where TypeLock == Objects.Feed {
|
|||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
if let data = data.updatedAt[field.alias!] {
|
||||
return data
|
||||
}
|
||||
throw HttpError.badpayload
|
||||
return data.updatedAt[field.alias!]
|
||||
case .mocking:
|
||||
return DateTime.mockValue
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6643,11 +6655,11 @@ extension Selection where TypeLock == Never, Type == Never {
|
|||
extension Objects {
|
||||
struct Filter {
|
||||
let __typename: TypeName = .filter
|
||||
let category: [String: String]
|
||||
let createdAt: [String: DateTime]
|
||||
let defaultFilter: [String: Bool]
|
||||
let description: [String: String]
|
||||
let filter: [String: String]
|
||||
let folder: [String: String]
|
||||
let id: [String: String]
|
||||
let name: [String: String]
|
||||
let position: [String: Int]
|
||||
|
|
@ -6672,10 +6684,6 @@ extension Objects.Filter: Decodable {
|
|||
let field = GraphQLField.getFieldNameFromAlias(alias)
|
||||
|
||||
switch field {
|
||||
case "category":
|
||||
if let value = try container.decode(String?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
case "createdAt":
|
||||
if let value = try container.decode(DateTime?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
|
|
@ -6692,6 +6700,10 @@ extension Objects.Filter: Decodable {
|
|||
if let value = try container.decode(String?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
case "folder":
|
||||
if let value = try container.decode(String?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
case "id":
|
||||
if let value = try container.decode(String?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
|
|
@ -6722,11 +6734,11 @@ extension Objects.Filter: Decodable {
|
|||
}
|
||||
}
|
||||
|
||||
category = map["category"]
|
||||
createdAt = map["createdAt"]
|
||||
defaultFilter = map["defaultFilter"]
|
||||
description = map["description"]
|
||||
filter = map["filter"]
|
||||
folder = map["folder"]
|
||||
id = map["id"]
|
||||
name = map["name"]
|
||||
position = map["position"]
|
||||
|
|
@ -6736,24 +6748,6 @@ extension Objects.Filter: Decodable {
|
|||
}
|
||||
|
||||
extension Fields where TypeLock == Objects.Filter {
|
||||
func category() throws -> String {
|
||||
let field = GraphQLField.leaf(
|
||||
name: "category",
|
||||
arguments: []
|
||||
)
|
||||
select(field)
|
||||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
if let data = data.category[field.alias!] {
|
||||
return data
|
||||
}
|
||||
throw HttpError.badpayload
|
||||
case .mocking:
|
||||
return String.mockValue
|
||||
}
|
||||
}
|
||||
|
||||
func createdAt() throws -> DateTime {
|
||||
let field = GraphQLField.leaf(
|
||||
name: "createdAt",
|
||||
|
|
@ -6820,6 +6814,24 @@ extension Fields where TypeLock == Objects.Filter {
|
|||
}
|
||||
}
|
||||
|
||||
func folder() throws -> String {
|
||||
let field = GraphQLField.leaf(
|
||||
name: "folder",
|
||||
arguments: []
|
||||
)
|
||||
select(field)
|
||||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
if let data = data.folder[field.alias!] {
|
||||
return data
|
||||
}
|
||||
throw HttpError.badpayload
|
||||
case .mocking:
|
||||
return String.mockValue
|
||||
}
|
||||
}
|
||||
|
||||
func id() throws -> String {
|
||||
let field = GraphQLField.leaf(
|
||||
name: "id",
|
||||
|
|
@ -13695,6 +13707,7 @@ extension Objects {
|
|||
let recentEmails: [String: Unions.RecentEmailsResult]
|
||||
let recentSearches: [String: Unions.RecentSearchesResult]
|
||||
let rules: [String: Unions.RulesResult]
|
||||
let scanFeeds: [String: Unions.ScanFeedsResult]
|
||||
let search: [String: Unions.SearchResult]
|
||||
let sendInstallInstructions: [String: Unions.SendInstallInstructionsResult]
|
||||
let subscriptions: [String: Unions.SubscriptionsResult]
|
||||
|
|
@ -13788,6 +13801,10 @@ extension Objects.Query: Decodable {
|
|||
if let value = try container.decode(Unions.RulesResult?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
case "scanFeeds":
|
||||
if let value = try container.decode(Unions.ScanFeedsResult?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
case "search":
|
||||
if let value = try container.decode(Unions.SearchResult?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
|
|
@ -13854,6 +13871,7 @@ extension Objects.Query: Decodable {
|
|||
recentEmails = map["recentEmails"]
|
||||
recentSearches = map["recentSearches"]
|
||||
rules = map["rules"]
|
||||
scanFeeds = map["scanFeeds"]
|
||||
search = map["search"]
|
||||
sendInstallInstructions = map["sendInstallInstructions"]
|
||||
subscriptions = map["subscriptions"]
|
||||
|
|
@ -14165,6 +14183,25 @@ extension Fields where TypeLock == Objects.Query {
|
|||
}
|
||||
}
|
||||
|
||||
func scanFeeds<Type>(input: InputObjects.ScanFeedsInput, selection: Selection<Type, Unions.ScanFeedsResult>) throws -> Type {
|
||||
let field = GraphQLField.composite(
|
||||
name: "scanFeeds",
|
||||
arguments: [Argument(name: "input", type: "ScanFeedsInput!", value: input)],
|
||||
selection: selection.selection
|
||||
)
|
||||
select(field)
|
||||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
if let data = data.scanFeeds[field.alias!] {
|
||||
return try selection.decode(data: data)
|
||||
}
|
||||
throw HttpError.badpayload
|
||||
case .mocking:
|
||||
return selection.mock()
|
||||
}
|
||||
}
|
||||
|
||||
func search<Type>(after: OptionalArgument<String> = .absent(), first: OptionalArgument<Int> = .absent(), format: OptionalArgument<String> = .absent(), includeContent: OptionalArgument<Bool> = .absent(), query: OptionalArgument<String> = .absent(), selection: Selection<Type, Unions.SearchResult>) throws -> Type {
|
||||
let field = GraphQLField.composite(
|
||||
name: "search",
|
||||
|
|
@ -14241,10 +14278,10 @@ extension Fields where TypeLock == Objects.Query {
|
|||
}
|
||||
}
|
||||
|
||||
func updatesSince<Type>(after: OptionalArgument<String> = .absent(), first: OptionalArgument<Int> = .absent(), since: DateTime, sort: OptionalArgument<InputObjects.SortParams> = .absent(), selection: Selection<Type, Unions.UpdatesSinceResult>) throws -> Type {
|
||||
func updatesSince<Type>(after: OptionalArgument<String> = .absent(), first: OptionalArgument<Int> = .absent(), folder: OptionalArgument<String> = .absent(), since: DateTime, sort: OptionalArgument<InputObjects.SortParams> = .absent(), selection: Selection<Type, Unions.UpdatesSinceResult>) throws -> Type {
|
||||
let field = GraphQLField.composite(
|
||||
name: "updatesSince",
|
||||
arguments: [Argument(name: "after", type: "String", value: after), Argument(name: "first", type: "Int", value: first), Argument(name: "since", type: "Date!", value: since), Argument(name: "sort", type: "SortParams", value: sort)],
|
||||
arguments: [Argument(name: "after", type: "String", value: after), Argument(name: "first", type: "Int", value: first), Argument(name: "folder", type: "String", value: folder), Argument(name: "since", type: "Date!", value: since), Argument(name: "sort", type: "SortParams", value: sort)],
|
||||
selection: selection.selection
|
||||
)
|
||||
select(field)
|
||||
|
|
@ -17454,6 +17491,137 @@ extension Selection where TypeLock == Never, Type == Never {
|
|||
typealias SaveSuccess<T> = Selection<T, Objects.SaveSuccess>
|
||||
}
|
||||
|
||||
extension Objects {
|
||||
struct ScanFeedsError {
|
||||
let __typename: TypeName = .scanFeedsError
|
||||
let errorCodes: [String: [Enums.ScanFeedsErrorCode]]
|
||||
|
||||
enum TypeName: String, Codable {
|
||||
case scanFeedsError = "ScanFeedsError"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Objects.ScanFeedsError: Decodable {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
|
||||
|
||||
var map = HashMap()
|
||||
for codingKey in container.allKeys {
|
||||
if codingKey.isTypenameKey { continue }
|
||||
|
||||
let alias = codingKey.stringValue
|
||||
let field = GraphQLField.getFieldNameFromAlias(alias)
|
||||
|
||||
switch field {
|
||||
case "errorCodes":
|
||||
if let value = try container.decode([Enums.ScanFeedsErrorCode]?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
default:
|
||||
throw DecodingError.dataCorrupted(
|
||||
DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "Unknown key \(field)."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
errorCodes = map["errorCodes"]
|
||||
}
|
||||
}
|
||||
|
||||
extension Fields where TypeLock == Objects.ScanFeedsError {
|
||||
func errorCodes() throws -> [Enums.ScanFeedsErrorCode] {
|
||||
let field = GraphQLField.leaf(
|
||||
name: "errorCodes",
|
||||
arguments: []
|
||||
)
|
||||
select(field)
|
||||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
if let data = data.errorCodes[field.alias!] {
|
||||
return data
|
||||
}
|
||||
throw HttpError.badpayload
|
||||
case .mocking:
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Selection where TypeLock == Never, Type == Never {
|
||||
typealias ScanFeedsError<T> = Selection<T, Objects.ScanFeedsError>
|
||||
}
|
||||
|
||||
extension Objects {
|
||||
struct ScanFeedsSuccess {
|
||||
let __typename: TypeName = .scanFeedsSuccess
|
||||
let feeds: [String: [Objects.Feed]]
|
||||
|
||||
enum TypeName: String, Codable {
|
||||
case scanFeedsSuccess = "ScanFeedsSuccess"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Objects.ScanFeedsSuccess: Decodable {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
|
||||
|
||||
var map = HashMap()
|
||||
for codingKey in container.allKeys {
|
||||
if codingKey.isTypenameKey { continue }
|
||||
|
||||
let alias = codingKey.stringValue
|
||||
let field = GraphQLField.getFieldNameFromAlias(alias)
|
||||
|
||||
switch field {
|
||||
case "feeds":
|
||||
if let value = try container.decode([Objects.Feed]?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
default:
|
||||
throw DecodingError.dataCorrupted(
|
||||
DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "Unknown key \(field)."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
feeds = map["feeds"]
|
||||
}
|
||||
}
|
||||
|
||||
extension Fields where TypeLock == Objects.ScanFeedsSuccess {
|
||||
func feeds<Type>(selection: Selection<Type, [Objects.Feed]>) throws -> Type {
|
||||
let field = GraphQLField.composite(
|
||||
name: "feeds",
|
||||
arguments: [],
|
||||
selection: selection.selection
|
||||
)
|
||||
select(field)
|
||||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
if let data = data.feeds[field.alias!] {
|
||||
return try selection.decode(data: data)
|
||||
}
|
||||
throw HttpError.badpayload
|
||||
case .mocking:
|
||||
return selection.mock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Selection where TypeLock == Never, Type == Never {
|
||||
typealias ScanFeedsSuccess<T> = Selection<T, Objects.ScanFeedsSuccess>
|
||||
}
|
||||
|
||||
extension Objects {
|
||||
struct SearchError {
|
||||
let __typename: TypeName = .searchError
|
||||
|
|
@ -29856,6 +30024,80 @@ extension Selection where TypeLock == Never, Type == Never {
|
|||
typealias SaveResult<T> = Selection<T, Unions.SaveResult>
|
||||
}
|
||||
|
||||
extension Unions {
|
||||
struct ScanFeedsResult {
|
||||
let __typename: TypeName
|
||||
let errorCodes: [String: [Enums.ScanFeedsErrorCode]]
|
||||
let feeds: [String: [Objects.Feed]]
|
||||
|
||||
enum TypeName: String, Codable {
|
||||
case scanFeedsError = "ScanFeedsError"
|
||||
case scanFeedsSuccess = "ScanFeedsSuccess"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Unions.ScanFeedsResult: Decodable {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: DynamicCodingKeys.self)
|
||||
|
||||
var map = HashMap()
|
||||
for codingKey in container.allKeys {
|
||||
if codingKey.isTypenameKey { continue }
|
||||
|
||||
let alias = codingKey.stringValue
|
||||
let field = GraphQLField.getFieldNameFromAlias(alias)
|
||||
|
||||
switch field {
|
||||
case "errorCodes":
|
||||
if let value = try container.decode([Enums.ScanFeedsErrorCode]?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
case "feeds":
|
||||
if let value = try container.decode([Objects.Feed]?.self, forKey: codingKey) {
|
||||
map.set(key: field, hash: alias, value: value as Any)
|
||||
}
|
||||
default:
|
||||
throw DecodingError.dataCorrupted(
|
||||
DecodingError.Context(
|
||||
codingPath: decoder.codingPath,
|
||||
debugDescription: "Unknown key \(field)."
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
__typename = try container.decode(TypeName.self, forKey: DynamicCodingKeys(stringValue: "__typename")!)
|
||||
|
||||
errorCodes = map["errorCodes"]
|
||||
feeds = map["feeds"]
|
||||
}
|
||||
}
|
||||
|
||||
extension Fields where TypeLock == Unions.ScanFeedsResult {
|
||||
func on<Type>(scanFeedsError: Selection<Type, Objects.ScanFeedsError>, scanFeedsSuccess: Selection<Type, Objects.ScanFeedsSuccess>) throws -> Type {
|
||||
select([GraphQLField.fragment(type: "ScanFeedsError", selection: scanFeedsError.selection), GraphQLField.fragment(type: "ScanFeedsSuccess", selection: scanFeedsSuccess.selection)])
|
||||
|
||||
switch response {
|
||||
case let .decoding(data):
|
||||
switch data.__typename {
|
||||
case .scanFeedsError:
|
||||
let data = Objects.ScanFeedsError(errorCodes: data.errorCodes)
|
||||
return try scanFeedsError.decode(data: data)
|
||||
case .scanFeedsSuccess:
|
||||
let data = Objects.ScanFeedsSuccess(feeds: data.feeds)
|
||||
return try scanFeedsSuccess.decode(data: data)
|
||||
}
|
||||
case .mocking:
|
||||
return scanFeedsError.mock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Selection where TypeLock == Never, Type == Never {
|
||||
typealias ScanFeedsResult<T> = Selection<T, Unions.ScanFeedsResult>
|
||||
}
|
||||
|
||||
extension Unions {
|
||||
struct SearchResult {
|
||||
let __typename: TypeName
|
||||
|
|
@ -33434,6 +33676,22 @@ extension Enums {
|
|||
}
|
||||
}
|
||||
|
||||
extension Enums {
|
||||
/// ScanFeedsErrorCode
|
||||
enum ScanFeedsErrorCode: String, CaseIterable, Codable {
|
||||
case badRequest = "BAD_REQUEST"
|
||||
}
|
||||
}
|
||||
|
||||
extension Enums {
|
||||
/// ScanFeedsType
|
||||
enum ScanFeedsType: String, CaseIterable, Codable {
|
||||
case html = "HTML"
|
||||
|
||||
case opml = "OPML"
|
||||
}
|
||||
}
|
||||
|
||||
extension Enums {
|
||||
/// SearchErrorCode
|
||||
enum SearchErrorCode: String, CaseIterable, Codable {
|
||||
|
|
@ -33997,6 +34255,8 @@ extension InputObjects {
|
|||
struct CreateArticleInput: Encodable, Hashable {
|
||||
var articleSavingRequestId: OptionalArgument<String> = .absent()
|
||||
|
||||
var folder: OptionalArgument<String> = .absent()
|
||||
|
||||
var labels: OptionalArgument<[InputObjects.CreateLabelInput]> = .absent()
|
||||
|
||||
var preparedDocument: OptionalArgument<InputObjects.PreparedDocumentInput> = .absent()
|
||||
|
|
@ -34014,6 +34274,7 @@ extension InputObjects {
|
|||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
if articleSavingRequestId.hasValue { try container.encode(articleSavingRequestId, forKey: .articleSavingRequestId) }
|
||||
if folder.hasValue { try container.encode(folder, forKey: .folder) }
|
||||
if labels.hasValue { try container.encode(labels, forKey: .labels) }
|
||||
if preparedDocument.hasValue { try container.encode(preparedDocument, forKey: .preparedDocument) }
|
||||
if skipParsing.hasValue { try container.encode(skipParsing, forKey: .skipParsing) }
|
||||
|
|
@ -34025,6 +34286,7 @@ extension InputObjects {
|
|||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case articleSavingRequestId
|
||||
case folder
|
||||
case labels
|
||||
case preparedDocument
|
||||
case skipParsing
|
||||
|
|
@ -34733,6 +34995,8 @@ extension InputObjects {
|
|||
struct SaveFileInput: Encodable, Hashable {
|
||||
var clientRequestId: String
|
||||
|
||||
var folder: OptionalArgument<String> = .absent()
|
||||
|
||||
var labels: OptionalArgument<[InputObjects.CreateLabelInput]> = .absent()
|
||||
|
||||
var source: String
|
||||
|
|
@ -34746,6 +35010,7 @@ extension InputObjects {
|
|||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(clientRequestId, forKey: .clientRequestId)
|
||||
if folder.hasValue { try container.encode(folder, forKey: .folder) }
|
||||
if labels.hasValue { try container.encode(labels, forKey: .labels) }
|
||||
try container.encode(source, forKey: .source)
|
||||
if state.hasValue { try container.encode(state, forKey: .state) }
|
||||
|
|
@ -34755,6 +35020,7 @@ extension InputObjects {
|
|||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case clientRequestId
|
||||
case folder
|
||||
case labels
|
||||
case source
|
||||
case state
|
||||
|
|
@ -34766,29 +35032,29 @@ extension InputObjects {
|
|||
|
||||
extension InputObjects {
|
||||
struct SaveFilterInput: Encodable, Hashable {
|
||||
var category: OptionalArgument<String> = .absent()
|
||||
|
||||
var description: OptionalArgument<String> = .absent()
|
||||
|
||||
var filter: String
|
||||
|
||||
var folder: OptionalArgument<String> = .absent()
|
||||
|
||||
var name: String
|
||||
|
||||
var position: OptionalArgument<Int> = .absent()
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
if category.hasValue { try container.encode(category, forKey: .category) }
|
||||
if description.hasValue { try container.encode(description, forKey: .description) }
|
||||
try container.encode(filter, forKey: .filter)
|
||||
if folder.hasValue { try container.encode(folder, forKey: .folder) }
|
||||
try container.encode(name, forKey: .name)
|
||||
if position.hasValue { try container.encode(position, forKey: .position) }
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case category
|
||||
case description
|
||||
case filter
|
||||
case folder
|
||||
case name
|
||||
case position
|
||||
}
|
||||
|
|
@ -34799,6 +35065,8 @@ extension InputObjects {
|
|||
struct SavePageInput: Encodable, Hashable {
|
||||
var clientRequestId: String
|
||||
|
||||
var folder: OptionalArgument<String> = .absent()
|
||||
|
||||
var labels: OptionalArgument<[InputObjects.CreateLabelInput]> = .absent()
|
||||
|
||||
var originalContent: String
|
||||
|
|
@ -34822,6 +35090,7 @@ extension InputObjects {
|
|||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(clientRequestId, forKey: .clientRequestId)
|
||||
if folder.hasValue { try container.encode(folder, forKey: .folder) }
|
||||
if labels.hasValue { try container.encode(labels, forKey: .labels) }
|
||||
try container.encode(originalContent, forKey: .originalContent)
|
||||
if parseResult.hasValue { try container.encode(parseResult, forKey: .parseResult) }
|
||||
|
|
@ -34836,6 +35105,7 @@ extension InputObjects {
|
|||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case clientRequestId
|
||||
case folder
|
||||
case labels
|
||||
case originalContent
|
||||
case parseResult
|
||||
|
|
@ -34854,6 +35124,8 @@ extension InputObjects {
|
|||
struct SaveUrlInput: Encodable, Hashable {
|
||||
var clientRequestId: String
|
||||
|
||||
var folder: OptionalArgument<String> = .absent()
|
||||
|
||||
var labels: OptionalArgument<[InputObjects.CreateLabelInput]> = .absent()
|
||||
|
||||
var locale: OptionalArgument<String> = .absent()
|
||||
|
|
@ -34873,6 +35145,7 @@ extension InputObjects {
|
|||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(clientRequestId, forKey: .clientRequestId)
|
||||
if folder.hasValue { try container.encode(folder, forKey: .folder) }
|
||||
if labels.hasValue { try container.encode(labels, forKey: .labels) }
|
||||
if locale.hasValue { try container.encode(locale, forKey: .locale) }
|
||||
if publishedAt.hasValue { try container.encode(publishedAt, forKey: .publishedAt) }
|
||||
|
|
@ -34885,6 +35158,7 @@ extension InputObjects {
|
|||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case clientRequestId
|
||||
case folder
|
||||
case labels
|
||||
case locale
|
||||
case publishedAt
|
||||
|
|
@ -34897,6 +35171,29 @@ extension InputObjects {
|
|||
}
|
||||
}
|
||||
|
||||
extension InputObjects {
|
||||
struct ScanFeedsInput: Encodable, Hashable {
|
||||
var opml: OptionalArgument<String> = .absent()
|
||||
|
||||
var type: Enums.ScanFeedsType
|
||||
|
||||
var url: OptionalArgument<String> = .absent()
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
if opml.hasValue { try container.encode(opml, forKey: .opml) }
|
||||
try container.encode(type, forKey: .type)
|
||||
if url.hasValue { try container.encode(url, forKey: .url) }
|
||||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case opml
|
||||
case type
|
||||
case url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension InputObjects {
|
||||
struct SetBookmarkArticleInput: Encodable, Hashable {
|
||||
var articleId: String
|
||||
|
|
@ -34966,6 +35263,8 @@ extension InputObjects {
|
|||
|
||||
var syncedAt: OptionalArgument<DateTime> = .absent()
|
||||
|
||||
var taskName: OptionalArgument<String> = .absent()
|
||||
|
||||
var token: String
|
||||
|
||||
var type: OptionalArgument<Enums.IntegrationType> = .absent()
|
||||
|
|
@ -34977,6 +35276,7 @@ extension InputObjects {
|
|||
if importItemState.hasValue { try container.encode(importItemState, forKey: .importItemState) }
|
||||
try container.encode(name, forKey: .name)
|
||||
if syncedAt.hasValue { try container.encode(syncedAt, forKey: .syncedAt) }
|
||||
if taskName.hasValue { try container.encode(taskName, forKey: .taskName) }
|
||||
try container.encode(token, forKey: .token)
|
||||
if type.hasValue { try container.encode(type, forKey: .type) }
|
||||
}
|
||||
|
|
@ -34987,6 +35287,7 @@ extension InputObjects {
|
|||
case importItemState
|
||||
case name
|
||||
case syncedAt
|
||||
case taskName
|
||||
case token
|
||||
case type
|
||||
}
|
||||
|
|
@ -35277,12 +35578,12 @@ extension InputObjects {
|
|||
|
||||
extension InputObjects {
|
||||
struct UpdateFilterInput: Encodable, Hashable {
|
||||
var category: OptionalArgument<String> = .absent()
|
||||
|
||||
var description: OptionalArgument<String> = .absent()
|
||||
|
||||
var filter: OptionalArgument<String> = .absent()
|
||||
|
||||
var folder: OptionalArgument<String> = .absent()
|
||||
|
||||
var id: String
|
||||
|
||||
var name: OptionalArgument<String> = .absent()
|
||||
|
|
@ -35293,9 +35594,9 @@ extension InputObjects {
|
|||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
if category.hasValue { try container.encode(category, forKey: .category) }
|
||||
if description.hasValue { try container.encode(description, forKey: .description) }
|
||||
if filter.hasValue { try container.encode(filter, forKey: .filter) }
|
||||
if folder.hasValue { try container.encode(folder, forKey: .folder) }
|
||||
try container.encode(id, forKey: .id)
|
||||
if name.hasValue { try container.encode(name, forKey: .name) }
|
||||
if position.hasValue { try container.encode(position, forKey: .position) }
|
||||
|
|
@ -35303,9 +35604,9 @@ extension InputObjects {
|
|||
}
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case category
|
||||
case description
|
||||
case filter
|
||||
case folder
|
||||
case id
|
||||
case name
|
||||
case position
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ let filterSelection = Selection.Filter {
|
|||
InternalFilter(
|
||||
id: try $0.id(),
|
||||
name: try $0.name(),
|
||||
folder: try $0.folder(),
|
||||
filter: try $0.filter(),
|
||||
visible: try $0.visible() ?? true,
|
||||
position: try $0.position(),
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import Models
|
|||
public struct InternalFilter: Encodable, Identifiable, Hashable {
|
||||
public let id: String
|
||||
public let name: String
|
||||
public let folder: String
|
||||
public let filter: String
|
||||
public let visible: Bool
|
||||
public let position: Int
|
||||
|
|
@ -14,6 +15,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
InternalFilter(
|
||||
id: "downloaded",
|
||||
name: "Downloaded",
|
||||
folder: "inbox",
|
||||
filter: "",
|
||||
visible: true,
|
||||
position: -1,
|
||||
|
|
@ -25,6 +27,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
InternalFilter(
|
||||
id: "deleted",
|
||||
name: "Deleted",
|
||||
folder: "inbox",
|
||||
filter: "in:trash",
|
||||
visible: true,
|
||||
position: -1,
|
||||
|
|
@ -32,11 +35,12 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
)
|
||||
}
|
||||
|
||||
public static var DefaultFilters: [InternalFilter] {
|
||||
public static var DefaultInboxFilters: [InternalFilter] {
|
||||
[
|
||||
InternalFilter(
|
||||
id: "inbox",
|
||||
name: "Inbox",
|
||||
folder: "inbox",
|
||||
filter: "",
|
||||
visible: true,
|
||||
position: 0,
|
||||
|
|
@ -45,6 +49,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
InternalFilter(
|
||||
id: "non-feed-items",
|
||||
name: "Non-Feed Items",
|
||||
folder: "inbox",
|
||||
filter: "",
|
||||
visible: true,
|
||||
position: 1,
|
||||
|
|
@ -53,6 +58,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
InternalFilter(
|
||||
id: "newsletters",
|
||||
name: "Newsletters",
|
||||
folder: "inbox",
|
||||
filter: "",
|
||||
visible: true,
|
||||
position: 2,
|
||||
|
|
@ -61,6 +67,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
InternalFilter(
|
||||
id: "feeds",
|
||||
name: "Feeds",
|
||||
folder: "inbox",
|
||||
filter: "",
|
||||
visible: true,
|
||||
position: 3,
|
||||
|
|
@ -69,6 +76,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
InternalFilter(
|
||||
id: "archived",
|
||||
name: "Archived",
|
||||
folder: "inbox",
|
||||
filter: "is:archived",
|
||||
visible: true,
|
||||
position: 4,
|
||||
|
|
@ -77,6 +85,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
InternalFilter(
|
||||
id: "files",
|
||||
name: "Files",
|
||||
folder: "inbox",
|
||||
filter: "type:file",
|
||||
visible: true,
|
||||
position: 5,
|
||||
|
|
@ -85,6 +94,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
InternalFilter(
|
||||
id: "highlighted",
|
||||
name: "Highlights",
|
||||
folder: "inbox",
|
||||
filter: "has:highlights",
|
||||
visible: true,
|
||||
position: 6,
|
||||
|
|
@ -93,6 +103,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
InternalFilter(
|
||||
id: "all",
|
||||
name: "All",
|
||||
folder: "inbox",
|
||||
filter: "in:all",
|
||||
visible: true,
|
||||
position: 7,
|
||||
|
|
@ -101,6 +112,29 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
]
|
||||
}
|
||||
|
||||
public static var DefaultFollowingFilters: [InternalFilter] {
|
||||
[
|
||||
InternalFilter(
|
||||
id: "following",
|
||||
name: "Following",
|
||||
folder: "following",
|
||||
filter: "in:following",
|
||||
visible: true,
|
||||
position: 0,
|
||||
defaultFilter: true
|
||||
),
|
||||
InternalFilter(
|
||||
id: "rss",
|
||||
name: "RSS",
|
||||
folder: "following",
|
||||
filter: "in:following label:RSS",
|
||||
visible: true,
|
||||
position: 1,
|
||||
defaultFilter: true
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
public var shouldRemoteSearch: Bool {
|
||||
id != "downloaded"
|
||||
}
|
||||
|
|
@ -182,7 +216,9 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, inArchivePredicate])
|
||||
case "Deleted":
|
||||
let deletedPredicate = NSPredicate(
|
||||
format: "%K == %i", #keyPath(Models.LibraryItem.serverSyncStatus), Int64(ServerSyncStatus.needsDeletion.rawValue)
|
||||
format: "%K == %i",
|
||||
#keyPath(Models.LibraryItem.serverSyncStatus),
|
||||
Int64(ServerSyncStatus.needsDeletion.rawValue)
|
||||
)
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [deletedPredicate])
|
||||
case "Files":
|
||||
|
|
@ -227,6 +263,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
let newFilter = existing ?? Filter(entity: Filter.entity(), insertInto: context)
|
||||
newFilter.id = id
|
||||
newFilter.name = name
|
||||
newFilter.folder = folder
|
||||
newFilter.filter = filter
|
||||
newFilter.visible = visible
|
||||
newFilter.position = Int64(position)
|
||||
|
|
@ -238,11 +275,13 @@ public struct InternalFilter: Encodable, Identifiable, Hashable {
|
|||
filters.compactMap { filter in
|
||||
if let id = filter.id,
|
||||
let name = filter.name,
|
||||
let folder = filter.folder,
|
||||
let filterStr = filter.filter
|
||||
{
|
||||
return InternalFilter(
|
||||
id: id,
|
||||
name: name,
|
||||
folder: folder,
|
||||
filter: filterStr,
|
||||
visible: filter.visible,
|
||||
position: Int(filter.position),
|
||||
|
|
@ -264,7 +303,6 @@ public extension Filter {
|
|||
)
|
||||
|
||||
var filter: Filter?
|
||||
|
||||
context.performAndWait {
|
||||
filter = (try? context.fetch(fetchRequest))?.first
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue