From 2919b7d7ce1f1fa09fa178a81f07ab5907447f58 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 9 Jan 2024 10:24:25 +0800 Subject: [PATCH 1/3] Improve the iOS filters when following tab is disabled --- .../Home/Components/LibraryItemFetcher.swift | 17 +++++++---- .../App/Views/Home/HomeFeedViewIOS.swift | 4 +-- .../InternalModels/InternalFilter.swift | 30 ++++++++----------- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryItemFetcher.swift b/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryItemFetcher.swift index 23027e2b8..76189ca1b 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryItemFetcher.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/Components/LibraryItemFetcher.swift @@ -164,10 +164,13 @@ import Views var subPredicates = [NSPredicate]() - let folderPredicate = NSPredicate( - format: "%K == %@", #keyPath(Models.LibraryItem.folder), filterState.folder - ) - subPredicates.append(folderPredicate) + // TODO: FOLLOWING MIGRATION: invert this once the following migration has completed + if !UserDefaults.standard.bool(forKey: "LibraryTabView::hideFollowingTab") { + let folderPredicate = NSPredicate( + format: "%K == %@", #keyPath(Models.LibraryItem.folder), filterState.folder + ) + subPredicates.append(folderPredicate) + } if let predicate = filterState.appliedFilter?.predicate { subPredicates.append(predicate) @@ -255,7 +258,11 @@ import Views }.joined(separator: ",")) } - query.append(" use:folders") + // TODO: FOLLOWING MIGRATION: invert this once the following migration has completed + if !UserDefaults.standard.bool(forKey: "LibraryTabView::hideFollowingTab") { + query.append(" use:folders") + } + print("QUERY: `\(query)`") return query diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 991879fee..52995797c 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -19,10 +19,10 @@ struct FiltersHeader: View { viewModel.searchTerm = "" }.frame(maxWidth: reader.size.width * 0.66) } else { - // if UIDevice.isIPhone { + let hideFollowingTab = UserDefaults.standard.bool(forKey: "LibraryTabView::hideFollowingTab") Menu( content: { - ForEach(viewModel.filters.filter { $0.folder == viewModel.currentFolder }) { filter in + ForEach(viewModel.filters.filter { hideFollowingTab || $0.folder == viewModel.currentFolder }) { filter in Button(filter.name, action: { viewModel.appliedFilter = filter }) diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalFilter.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalFilter.swift index 0b3576e6d..a49d9d2d9 100644 --- a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalFilter.swift +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalFilter.swift @@ -209,9 +209,6 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { } public var predicate: NSPredicate? { - let folderPredicate = NSPredicate( - format: "%K == %@", #keyPath(Models.LibraryItem.folder), folder - ) let undeletedPredicate = NSPredicate( format: "%K != %i AND %K != \"DELETED\"", #keyPath(Models.LibraryItem.serverSyncStatus), Int64(ServerSyncStatus.needsDeletion.rawValue), @@ -226,16 +223,16 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { let feedLabelPredicate = NSPredicate( format: "SUBQUERY(labels, $label, $label.name == \"RSS\").@count > 0" ) - return NSCompoundPredicate(andPredicateWithSubpredicates: [folderPredicate, notInArchivePredicate, undeletedPredicate, feedLabelPredicate]) + return NSCompoundPredicate(andPredicateWithSubpredicates: [notInArchivePredicate, undeletedPredicate, feedLabelPredicate]) case "Following": - return NSCompoundPredicate(andPredicateWithSubpredicates: [folderPredicate, notInArchivePredicate, undeletedPredicate]) + return NSCompoundPredicate(andPredicateWithSubpredicates: [notInArchivePredicate, undeletedPredicate]) case "Inbox": - return NSCompoundPredicate(andPredicateWithSubpredicates: [folderPredicate, undeletedPredicate, notInArchivePredicate]) + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, notInArchivePredicate]) case "Unread": let isUnread = NSPredicate( format: "readAt == nil" ) - return NSCompoundPredicate(andPredicateWithSubpredicates: [folderPredicate, undeletedPredicate, notInArchivePredicate, isUnread]) + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, notInArchivePredicate, isUnread]) case "Non-Feed Items": // non-archived or deleted items without the Newsletter label let nonNewsletterLabelPredicate = NSPredicate( @@ -245,7 +242,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { format: "NOT SUBQUERY(labels, $label, $label.name == \"RSS\") .@count > 0" ) return NSCompoundPredicate(andPredicateWithSubpredicates: [ - folderPredicate, undeletedPredicate, notInArchivePredicate, nonNewsletterLabelPredicate, nonRSSPredicate + undeletedPredicate, notInArchivePredicate, nonNewsletterLabelPredicate, nonRSSPredicate ]) case "Downloaded": // include pdf only @@ -259,51 +256,50 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { format: "localPDF.length > 0" ) let downloadedPDF = NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, isPDFPredicate, localPDFURL]) - return NSCompoundPredicate(orPredicateWithSubpredicates: [folderPredicate, hasHTMLContent, downloadedPDF]) + return NSCompoundPredicate(orPredicateWithSubpredicates: [hasHTMLContent, downloadedPDF]) case "Newsletters": // non-archived or deleted items with the Newsletter label let newsletterLabelPredicate = NSPredicate( format: "SUBQUERY(labels, $label, $label.name == \"Newsletter\").@count > 0" ) - return NSCompoundPredicate(andPredicateWithSubpredicates: [folderPredicate, undeletedPredicate, notInArchivePredicate, newsletterLabelPredicate]) + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, notInArchivePredicate, newsletterLabelPredicate]) case "Feeds": let feedLabelPredicate = NSPredicate( format: "SUBQUERY(labels, $label, $label.name == \"RSS\").@count > 0" ) - return NSCompoundPredicate(andPredicateWithSubpredicates: [folderPredicate, undeletedPredicate, notInArchivePredicate, feedLabelPredicate]) + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, notInArchivePredicate, feedLabelPredicate]) case "Recommended": // non-archived or deleted items with the Newsletter label let recommendedPredicate = NSPredicate( format: "recommendations.@count > 0" ) - return NSCompoundPredicate(andPredicateWithSubpredicates: [folderPredicate, undeletedPredicate, notInArchivePredicate, recommendedPredicate]) + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, notInArchivePredicate, recommendedPredicate]) case "All": // include everything undeleted - return NSCompoundPredicate(andPredicateWithSubpredicates: [folderPredicate, undeletedPredicate]) + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate]) case "Archived": let inArchivePredicate = NSPredicate( format: "%K == %@", #keyPath(Models.LibraryItem.isArchived), Int(truncating: true) as NSNumber ) - return NSCompoundPredicate(andPredicateWithSubpredicates: [folderPredicate, undeletedPredicate, inArchivePredicate]) + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, inArchivePredicate]) case "Deleted": let deletedPredicate = NSPredicate( format: "%K == %i OR %K == \"DELETED\"", #keyPath(Models.LibraryItem.serverSyncStatus), Int64(ServerSyncStatus.needsDeletion.rawValue), #keyPath(Models.LibraryItem.state) ) - return NSCompoundPredicate(andPredicateWithSubpredicates: [folderPredicate, deletedPredicate]) + return NSCompoundPredicate(andPredicateWithSubpredicates: [deletedPredicate]) case "Files": // include pdf only let isPDFPredicate = NSPredicate( format: "%K == %@", #keyPath(Models.LibraryItem.contentReader), "PDF" ) - return NSCompoundPredicate(andPredicateWithSubpredicates: [folderPredicate, undeletedPredicate, isPDFPredicate]) + return NSCompoundPredicate(andPredicateWithSubpredicates: [undeletedPredicate, isPDFPredicate]) case "Highlights": let hasHighlightsPredicate = NSPredicate( format: "highlights.@count > 0" ) return NSCompoundPredicate(andPredicateWithSubpredicates: [ - folderPredicate, undeletedPredicate, hasHighlightsPredicate ]) From ecb5fbf1124b0ff9272246840bfac2be8399a3e8 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 9 Jan 2024 12:35:07 +0800 Subject: [PATCH 2/3] Make filters work a bit better with non-following --- .../App/Views/Home/HomeFeedViewModel.swift | 3 +- .../InternalModels/InternalFilter.swift | 34 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 9f89b307b..0000ce363 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -167,10 +167,9 @@ enum LoadingBarStyle { let availableFolders = folderConfigs.keys let appliedFilterName = UserDefaults.standard.string(forKey: filterKey) - filters = newFilters + filters = (defaultFilters + newFilters) .filter { availableFolders.contains($0.folder) } .sorted(by: { $0.position < $1.position }) - + defaultFilters if let newFilter = filters.first(where: { $0.name.lowercased() == appliedFilterName }), newFilter.id != appliedFilter?.id { appliedFilter = newFilter diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalFilter.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalFilter.swift index a49d9d2d9..7830a6433 100644 --- a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalFilter.swift +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalFilter.swift @@ -22,7 +22,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "", visible: true, - position: -1, + position: 11, defaultFilter: true ) } @@ -34,7 +34,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "in:trash", visible: true, - position: -1, + position: 12, defaultFilter: true ) } @@ -46,7 +46,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "in:inbox is:unread", visible: true, - position: -1, + position: 10, defaultFilter: true ) } @@ -58,7 +58,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "following", filter: "", visible: true, - position: -1, + position: 11, defaultFilter: true ) } @@ -70,7 +70,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "following", filter: "in:trash", visible: true, - position: -1, + position: 12, defaultFilter: true ) } @@ -82,7 +82,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "in:inbox is:unread", visible: true, - position: -1, + position: 10, defaultFilter: true ) } @@ -95,7 +95,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "", visible: true, - position: 0, + position: 10, defaultFilter: true ), InternalFilter( @@ -104,7 +104,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "", visible: true, - position: 1, + position: 11, defaultFilter: true ), InternalFilter( @@ -113,7 +113,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "", visible: true, - position: 2, + position: 12, defaultFilter: true ), InternalFilter( @@ -122,7 +122,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "", visible: true, - position: 3, + position: 13, defaultFilter: true ), InternalFilter( @@ -131,7 +131,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "is:archived", visible: true, - position: 4, + position: 14, defaultFilter: true ), InternalFilter( @@ -140,7 +140,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "type:file", visible: true, - position: 5, + position: 15, defaultFilter: true ), InternalFilter( @@ -149,7 +149,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "has:highlights", visible: true, - position: 6, + position: 16, defaultFilter: true ), InternalFilter( @@ -158,7 +158,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "inbox", filter: "in:all", visible: true, - position: 7, + position: 17, defaultFilter: true ) ] @@ -172,7 +172,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "following", filter: "in:following", visible: true, - position: 1, + position: 10, defaultFilter: true ), InternalFilter( @@ -181,7 +181,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "following", filter: "in:following label:RSS", visible: true, - position: 2, + position: 12, defaultFilter: true ), InternalFilter( @@ -190,7 +190,7 @@ public struct InternalFilter: Encodable, Identifiable, Hashable, Equatable { folder: "following", filter: "in:following label:Newsletter", visible: true, - position: 3, + position: 13, defaultFilter: true ) ] From 80f015051298d7c1c85739cc18326d266c9f95c4 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Tue, 9 Jan 2024 14:20:04 +0800 Subject: [PATCH 3/3] Dont show the iPhone tab bar in edit mode --- .../App/Views/Home/HomeFeedViewIOS.swift | 6 ++-- .../Sources/App/Views/Home/HomeView.swift | 34 ------------------- .../Sources/App/Views/LibrarySplitView.swift | 3 +- .../Sources/App/Views/LibraryTabView.swift | 12 ++++--- .../App/Views/Profile/FiltersView.swift | 5 +++ 5 files changed, 19 insertions(+), 41 deletions(-) delete mode 100644 apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 52995797c..939bf0502 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -188,9 +188,10 @@ struct AnimatingCellHeight: AnimatableModifier { @State var showAddLinkView = false @State var isListScrolled = false @State var listTitle = "" - @State var isEditMode: EditMode = .inactive @State var showExpandedAudioPlayer = false + @Binding var isEditMode: EditMode + @EnvironmentObject var dataService: DataService @EnvironmentObject var audioController: AudioController @Environment(\.horizontalSizeClass) var horizontalSizeClass @@ -200,8 +201,9 @@ struct AnimatingCellHeight: AnimatableModifier { @ObservedObject var viewModel: HomeFeedViewModel @State private var selection = Set() - init(viewModel: HomeFeedViewModel) { + init(viewModel: HomeFeedViewModel, isEditMode: Binding) { _viewModel = ObservedObject(wrappedValue: viewModel) + _isEditMode = isEditMode } func loadItems(isRefresh: Bool) { diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift deleted file mode 100644 index 5fa1372c1..000000000 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeView.swift +++ /dev/null @@ -1,34 +0,0 @@ -import SwiftUI -import Utils -import Views - -@MainActor -struct HomeView: View { - @State private var viewModel: HomeFeedViewModel - - init(viewModel: HomeFeedViewModel) { - self.viewModel = viewModel - } - - var body: some View { - #if os(iOS) - HomeFeedContainerView(viewModel: viewModel) - #elseif os(macOS) - HomeFeedView(viewModel: viewModel) - .frame(minWidth: 320) - .toolbar { - ToolbarItem { - Button( - action: { - NSApp.keyWindow?.firstResponder?.tryToPerform( - #selector(NSSplitViewController.toggleSidebar(_:)), with: nil - ) - }, - label: { Label(LocalText.navigationSelectSidebarToggle, systemImage: "sidebar.left") } - ) - } - } - - #endif - } -} diff --git a/apple/OmnivoreKit/Sources/App/Views/LibrarySplitView.swift b/apple/OmnivoreKit/Sources/App/Views/LibrarySplitView.swift index 12ff7edb6..9287b8b6c 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LibrarySplitView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LibrarySplitView.swift @@ -6,6 +6,7 @@ import SwiftUI @MainActor public struct LibrarySplitView: View { @EnvironmentObject var dataService: DataService + @State var isEditMode: EditMode = .inactive @StateObject private var viewModel = HomeFeedViewModel( filterKey: "lastSelected", @@ -37,7 +38,7 @@ public struct LibrarySplitView: View { .navigationBarTitleDisplayMode(.inline) .navigationTitle("") - HomeFeedContainerView(viewModel: viewModel) + HomeFeedContainerView(viewModel: viewModel, isEditMode: $isEditMode) .navigationViewStyle(.stack) .navigationBarTitleDisplayMode(.inline) } diff --git a/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift b/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift index 6fa328866..078a326f0 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LibraryTabView.swift @@ -21,6 +21,8 @@ struct LibraryTabView: View { @AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false @AppStorage(UserDefaultKey.lastSelectedTabItem.rawValue) var selectedTab = "inbox" + + @State var isEditMode: EditMode = .inactive @State var showExpandedAudioPlayer = false private let syncManager = LibrarySyncManager() @@ -74,14 +76,14 @@ struct LibraryTabView: View { TabView(selection: $selectedTab) { if !hideFollowingTab { NavigationView { - HomeFeedContainerView(viewModel: followingViewModel) + HomeFeedContainerView(viewModel: followingViewModel, isEditMode: $isEditMode) .navigationBarTitleDisplayMode(.inline) .navigationViewStyle(.stack) }.tag("following") } NavigationView { - HomeFeedContainerView(viewModel: inboxViewModel) + HomeFeedContainerView(viewModel: inboxViewModel, isEditMode: $isEditMode) .navigationBarTitleDisplayMode(.inline) .navigationViewStyle(.stack) }.tag("inbox") @@ -101,8 +103,10 @@ struct LibraryTabView: View { .frame(height: 1) .frame(maxWidth: .infinity) } - CustomTabBar(selectedTab: $selectedTab, hideFollowingTab: hideFollowingTab) - .padding(0) + if isEditMode != .active { + CustomTabBar(selectedTab: $selectedTab, hideFollowingTab: hideFollowingTab) + .padding(0) + } } .fullScreenCover(isPresented: $showExpandedAudioPlayer) { ExpandedAudioPlayer( diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/FiltersView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/FiltersView.swift index bd02c3ceb..9ee16acdc 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/FiltersView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/FiltersView.swift @@ -84,6 +84,11 @@ struct FiltersView: View { .onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in dismiss() } + .onChange(of: viewModel.hideFollowingTab) { _ in + UserDefaults.standard.setValue(nil, forKey: "lastSelected") + UserDefaults.standard.setValue(nil, forKey: "lastSelectedFilter-inbox") + UserDefaults.standard.setValue(nil, forKey: "lastSelectedFilter-following") + } } private var innerBody: some View {