Better empty state handling for RSS

This commit is contained in:
Jackson Harper
2023-12-08 18:31:09 +08:00
parent 0c82710a5f
commit adefd1b2eb
6 changed files with 234 additions and 9 deletions

View File

@ -357,7 +357,9 @@ struct AnimatingCellHeight: AnimatableModifier {
@Binding var isListScrolled: Bool
@Binding var prefersListLayout: Bool
@Binding var isEditMode: EditMode
@State private var showAddFeedView = false
@State private var showHideFeatureAlert = false
@State private var showHideFollowingAlert = false
@Binding var selection: Set<String>
@ObservedObject var viewModel: HomeFeedViewModel
@ -584,6 +586,46 @@ struct AnimatingCellHeight: AnimatableModifier {
}.redacted(reason: .placeholder)
}
var emptyState: some View {
if viewModel.folder == "following" {
return AnyView(
VStack(alignment: .center, spacing: 20) {
Text("You don't have any Feed items.")
.font(Font.system(size: 18, weight: .bold))
Text("Add an RSS/Atom feed")
.foregroundColor(Color.blue)
.onTapGesture {
showAddFeedView = true
}
Text("Hide the Following tab")
.foregroundColor(Color.blue)
.onTapGesture {
showHideFollowingAlert = true
}
}
.frame(minHeight: 400)
.frame(maxWidth: .infinity)
.padding()
)
} else {
return AnyView(Group {
Spacer()
VStack(alignment: .center, spacing: 20) {
Text("No results found for this query")
.font(Font.system(size: 18, weight: .bold))
}
.frame(minHeight: 400)
.frame(maxWidth: .infinity)
.padding()
Spacer()
})
}
}
var listItems: some View {
ForEach(Array(viewModel.fetcher.items.enumerated()), id: \.1.unwrappedID) { _, item in
let horizontalInset = CGFloat(UIDevice.isIPad ? 20 : 10)
@ -663,6 +705,9 @@ struct AnimatingCellHeight: AnimatableModifier {
if viewModel.showLoadingBar {
redactedItems
} else if viewModel.fetcher.items.isEmpty {
emptyState
.listRowSeparator(.hidden, edges: .all)
} else {
listItems
}
@ -688,13 +733,26 @@ struct AnimatingCellHeight: AnimatableModifier {
shouldScrollToTop = true
}
}
.sheet(isPresented: $showAddFeedView) {
NavigationView {
LibraryAddFeedView()
}
}
.alert("The Feature Section will be removed from your library. You can add it back from the filter settings in your profile.",
isPresented: $showHideFeatureAlert) {
Button("OK", role: .destructive) {
viewModel.hideFeatureSection = true
}
Button(LocalText.cancelGeneric, role: .cancel) { self.showHideFeatureAlert = false }
}.introspectNavigationController { nav in
}
.alert("The Following tab will be hidden. You can add it back from the filter settings in your profile.",
isPresented: $showHideFollowingAlert) {
Button("OK", role: .destructive) {
viewModel.hideFollowingTab = true
}
Button(LocalText.cancelGeneric, role: .cancel) { self.showHideFollowingAlert = false }
}
.introspectNavigationController { nav in
nav.navigationBar.shadowImage = UIImage()
nav.navigationBar.setBackgroundImage(UIImage(), for: .default)
}

View File

@ -44,6 +44,8 @@ import Views
@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? {
didSet {
let filterKey = UserDefaults.standard.string(forKey: "lastSelected-\(folder)-filter") ?? folder

View File

@ -0,0 +1,156 @@
import Introspect
import Models
import Services
import SwiftUI
import Views
@MainActor final class LibraryAddFeedViewModel: NSObject, ObservableObject {
@Published var isLoading = false
@Published var errorMessage: String = ""
@Published var showErrorMessage: Bool = false
@Environment(\.dismiss) private var dismiss
func addLink(dataService: DataService, newLinkURL: String, dismiss: DismissAction) {
isLoading = true
Task {
if URL(string: newLinkURL) == nil {
error("Invalid link")
} else {
let result = try? await dataService.saveURL(id: UUID().uuidString, url: newLinkURL)
if result == nil {
error("Error adding link")
} else {
dismiss()
}
}
isLoading = false
}
}
func error(_ msg: String) {
errorMessage = msg
showErrorMessage = true
isLoading = false
}
}
struct LibraryAddFeedView: View {
@StateObject var viewModel = LibraryAddFeedViewModel()
@State var newLinkURL: String = ""
@EnvironmentObject var dataService: DataService
@Environment(\.dismiss) private var dismiss
enum FocusField: Hashable {
case addLinkEditor
}
@FocusState private var focusedField: FocusField?
var body: some View {
Group {
#if os(iOS)
Form {
innerBody
.navigationTitle("Add Link")
.navigationBarTitleDisplayMode(.inline)
}
#else
innerBody
#endif
}
#if os(macOS)
.padding()
#endif
.onAppear {
focusedField = .addLinkEditor
}
.navigationTitle("Add Link")
#if os(iOS)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
dismissButton
}
ToolbarItem(placement: .navigationBarTrailing) {
viewModel.isLoading ? AnyView(ProgressView()) : AnyView(addButton)
}
}
#endif
.alert(viewModel.errorMessage,
isPresented: $viewModel.showErrorMessage) {
Button(LocalText.genericOk, role: .cancel) { viewModel.showErrorMessage = false }
}
}
var cancelButton: some View {
Button(
action: { dismiss() },
label: { Text(LocalText.cancelGeneric).foregroundColor(.appGrayTextContrast) }
)
}
var pasteboardString: String? {
#if os(iOS)
UIPasteboard.general.url?.absoluteString
#else
NSPasteboard.general.string(forType: NSPasteboard.PasteboardType.URL)
#endif
}
var innerBody: some View {
Group {
TextField("Add Link", text: $newLinkURL)
#if os(iOS)
.keyboardType(.URL)
#endif
.autocorrectionDisabled(true)
.textFieldStyle(StandardTextFieldStyle())
.focused($focusedField, equals: .addLinkEditor)
Button(action: {
if let url = pasteboardString {
newLinkURL = url
} else {
viewModel.error("No URL on pasteboard")
}
}, label: {
Text("Get from pasteboard")
})
#if os(macOS)
Spacer()
HStack {
cancelButton
Spacer()
addButton
}
.frame(maxWidth: .infinity)
#endif
}
}
var addButton: some View {
Button(
action: {
viewModel.addLink(dataService: dataService, newLinkURL: newLinkURL, dismiss: dismiss)
},
label: { Text("Add").bold() }
)
.keyboardShortcut(.defaultAction)
.onSubmit {
viewModel.addLink(dataService: dataService, newLinkURL: newLinkURL, dismiss: dismiss)
}
.disabled(viewModel.isLoading)
}
var dismissButton: some View {
Button(
action: { dismiss() },
label: { Text(LocalText.genericClose) }
)
.disabled(viewModel.isLoading)
}
}

View File

@ -19,6 +19,7 @@ struct LibraryTabView: View {
@EnvironmentObject var dataService: DataService
@EnvironmentObject var audioController: AudioController
@AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false
@AppStorage(UserDefaultKey.lastSelectedTabItem.rawValue) var selectedTab = "inbox"
@State var showExpandedAudioPlayer = false
@ -53,11 +54,13 @@ struct LibraryTabView: View {
var body: some View {
VStack(spacing: 0) {
TabView(selection: $selectedTab) {
NavigationView {
HomeFeedContainerView(viewModel: followingViewModel)
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.stack)
}.tag("following")
if !hideFollowingTab {
NavigationView {
HomeFeedContainerView(viewModel: followingViewModel)
.navigationBarTitleDisplayMode(.inline)
.navigationViewStyle(.stack)
}.tag("following")
}
NavigationView {
HomeFeedContainerView(viewModel: libraryViewModel)
@ -80,7 +83,7 @@ struct LibraryTabView: View {
.frame(height: 1)
.frame(maxWidth: .infinity)
}
CustomTabBar(selectedTab: $selectedTab)
CustomTabBar(selectedTab: $selectedTab, hideFollowingTab: hideFollowingTab)
.padding(0)
}
.fullScreenCover(isPresented: $showExpandedAudioPlayer) {

View File

@ -11,6 +11,7 @@ import Views
@Published var networkError = false
@Published var libraryFilters = [InternalFilter]()
@AppStorage("LibraryTabView::hideFollowingTab") var hideFollowingTab = false
@AppStorage(UserDefaultKey.hideFeatureSection.rawValue) var hideFeatureSection = false
func loadFilters(dataService: DataService) async {
@ -50,7 +51,8 @@ struct FiltersView: View {
private var innerBody: some View {
List {
Section {
Toggle("Hide Feature Section", isOn: $viewModel.hideFeatureSection)
Toggle("Hide following tab", isOn: $viewModel.hideFollowingTab)
Toggle("Hide feature section", isOn: $viewModel.hideFeatureSection)
}
Section(header: Text("Saved Searches")) {

View File

@ -3,9 +3,13 @@ import SwiftUI
struct CustomTabBar: View {
@Binding var selectedTab: String
let hideFollowingTab: Bool
var body: some View {
HStack(spacing: 0) {
TabBarButton(key: "following", image: Image.tabFollowing, selectedTab: $selectedTab)
if !hideFollowingTab {
TabBarButton(key: "following", image: Image.tabFollowing, selectedTab: $selectedTab)
}
TabBarButton(key: "inbox", image: Image.tabLibrary, selectedTab: $selectedTab)
TabBarButton(key: "profile", image: Image.tabProfile, selectedTab: $selectedTab)
}