diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 2337f76bb..448271929 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -215,56 +215,118 @@ import Views @ObservedObject var viewModel: HomeFeedViewModel var filtersHeader: some View { - ScrollView(.horizontal, showsIndicators: false) { - HStack { - if viewModel.searchTerm.count > 0 { - TextChipButton.makeSearchFilterButton(title: viewModel.searchTerm) { - viewModel.searchTerm = "" + GeometryReader { reader in + ScrollView(.horizontal, showsIndicators: false) { + HStack { + if viewModel.searchTerm.count > 0 { + TextChipButton.makeSearchFilterButton(title: viewModel.searchTerm) { + viewModel.searchTerm = "" + }.frame(maxWidth: reader.size.width * 0.66) + } else { + Menu( + content: { + ForEach(LinkedItemFilter.allCases, id: \.self) { filter in + Button(filter.displayName, action: { viewModel.appliedFilter = filter.rawValue }) + } + }, + label: { + TextChipButton.makeMenuButton( + title: LinkedItemFilter(rawValue: viewModel.appliedFilter)?.displayName ?? "Filter" + ) + } + ) } - } else { Menu( content: { - ForEach(LinkedItemFilter.allCases, id: \.self) { filter in - Button(filter.displayName, action: { viewModel.appliedFilter = filter.rawValue }) + ForEach(LinkedItemSort.allCases, id: \.self) { sort in + Button(sort.displayName, action: { viewModel.appliedSort = sort.rawValue }) } }, label: { TextChipButton.makeMenuButton( - title: LinkedItemFilter(rawValue: viewModel.appliedFilter)?.displayName ?? "Filter" + title: LinkedItemSort(rawValue: viewModel.appliedSort)?.displayName ?? "Sort" ) } ) - } - Menu( - content: { - ForEach(LinkedItemSort.allCases, id: \.self) { sort in - Button(sort.displayName, action: { viewModel.appliedSort = sort.rawValue }) + TextChipButton.makeAddLabelButton { + viewModel.showLabelsSheet = true + } + ForEach(viewModel.selectedLabels, id: \.self) { label in + TextChipButton.makeRemovableLabelButton(feedItemLabel: label, negated: false) { + viewModel.selectedLabels.removeAll { $0.id == label.id } } + } + ForEach(viewModel.negatedLabels, id: \.self) { label in + TextChipButton.makeRemovableLabelButton(feedItemLabel: label, negated: true) { + viewModel.negatedLabels.removeAll { $0.id == label.id } + } + } + Spacer() + } + .padding(0) + } + .listRowSeparator(.hidden) + } + } + + func menuItems(for item: LinkedItem) -> some View { + Group { + if (item.highlights?.count ?? 0) > 0 { + Button( + action: { viewModel.itemForHighlightsView = item }, + label: { Label("View Highlights & Notes", systemImage: "highlighter") } + ) + } + Button( + action: { viewModel.itemUnderTitleEdit = item }, + label: { Label("Edit Title/Description", systemImage: "textbox") } + ) + Button( + 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( + action: { + itemToRemove = item + confirmationShown = true + }, + label: { + Label("Remove Item", systemImage: "trash") + } + ).tint(.red) + if FeatureFlag.enableSnooze { + Button { + viewModel.itemToSnoozeID = item.id + viewModel.snoozePresented = true + } label: { + Label { Text("Snooze") } icon: { Image.moon } + } + } + if let author = item.author { + Button( + action: { + viewModel.searchTerm = "author:\"\(author)\"" }, label: { - TextChipButton.makeMenuButton( - title: LinkedItemSort(rawValue: viewModel.appliedSort)?.displayName ?? "Sort" - ) + Label(String("More by \(author)"), systemImage: "person") } ) - TextChipButton.makeAddLabelButton { - viewModel.showLabelsSheet = true - } - ForEach(viewModel.selectedLabels, id: \.self) { label in - TextChipButton.makeRemovableLabelButton(feedItemLabel: label, negated: false) { - viewModel.selectedLabels.removeAll { $0.id == label.id } - } - } - ForEach(viewModel.negatedLabels, id: \.self) { label in - TextChipButton.makeRemovableLabelButton(feedItemLabel: label, negated: true) { - viewModel.negatedLabels.removeAll { $0.id == label.id } - } - } - Spacer() } - .padding(0) } - .listRowSeparator(.hidden) } var body: some View { @@ -292,59 +354,7 @@ import Views viewModel: viewModel ) .contextMenu { - Button( - action: { viewModel.itemForHighlightsView = item }, - label: { Label("View Highlights & Notes", systemImage: "highlighter") } - ) - Button( - action: { viewModel.itemUnderTitleEdit = item }, - label: { Label("Edit Title/Description", systemImage: "textbox") } - ) - Button( - 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( - action: { - itemToRemove = item - confirmationShown = true - }, - label: { - Label("Remove Item", systemImage: "trash") - } - ).tint(.red) - if FeatureFlag.enableSnooze { - Button { - viewModel.itemToSnoozeID = item.id - viewModel.snoozePresented = true - } label: { - Label { Text("Snooze") } icon: { Image.moon } - } - } - if let author = item.author { - Button( - action: { - viewModel.searchTerm = "author:\"\(author)\"" - }, - label: { - Label(String("More by \(author)"), systemImage: "person") - } - ) - } + menuItems(for: item) } .swipeActions(edge: .trailing, allowsFullSwipe: true) { if !item.isArchived { @@ -428,8 +438,6 @@ import Views viewModel.itemUnderLabelEdit = item case .editTitle: viewModel.itemUnderTitleEdit = item - case .downloadAudio: - viewModel.downloadAudio(audioController: audioController, item: item) } } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift index c2b824287..7a58090ba 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift @@ -50,7 +50,7 @@ import Views ) Button( action: { viewModel.itemUnderLabelEdit = item }, - label: { Label("Edit Labels", systemImage: "tag") } + label: { Label(item.labels?.count == 0 ? "Add Labels" : "Edit Labels", systemImage: "tag") } ) Button(action: { withAnimation(.linear(duration: 0.4)) { diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 8b3b1dda2..55ff72e95 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -175,14 +175,6 @@ import Views showLoadingBar = false } - func downloadAudio(audioController: AudioController, item: LinkedItem) { - Snackbar.show(message: "Downloading Offline Audio") - Task { - let downloaded = await audioController.downloadForOffline(itemID: item.unwrappedID) - Snackbar.show(message: downloaded ? "Audio file downloaded" : "Error downloading audio") - } - } - private var fetchRequest: NSFetchRequest { let fetchRequest: NSFetchRequest = LinkedItem.fetchRequest() diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index 4837d8860..74f1238e6 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -153,6 +153,60 @@ struct WebReaderContainerView: View { }.foregroundColor(.appGrayTextContrast) } + func menuItems(for item: LinkedItem) -> some View { + let hasLabels = item.labels?.count == 0 + let hasHighlights = (item.highlights?.count ?? 0) > 0 + return Group { + if hasHighlights { + Button( + action: { showHighlightsView = true }, + label: { Label("View Highlights & Notes", systemImage: "highlighter") } + ) + } + Button( + action: { showTitleEdit = true }, + label: { Label("Edit Title/Description", systemImage: "textbox") } + ) + Button( + action: editLabels, + label: { Label(hasLabels ? "Edit Labels" : "Add Labels", systemImage: "tag") } + ) + Button( + action: { + archive() + }, + label: { + Label( + item.isArchived ? "Unarchive" : "Archive", + systemImage: item.isArchived ? "tray.and.arrow.down.fill" : "archivebox" + ) + } + ) + Button( + action: { + dataService.updateLinkReadingProgress(itemID: item.unwrappedID, readingProgress: 0, anchorIndex: 0) + }, + label: { Label("Reset Read Location", systemImage: "arrow.counterclockwise.circle") } + ) + Button( + action: { + viewModel.downloadAudio(audioController: audioController, item: item) + }, + label: { Label("Download Audio", systemImage: "icloud.and.arrow.down") } + ) + if viewModel.hasOriginalUrl(item) { + Button( + action: share, + label: { Label("Share Original", systemImage: "square.and.arrow.up") } + ) + } + Button( + action: delete, + label: { Label("Delete", systemImage: "trash") } + ) + } + } + var navBar: some View { HStack(alignment: .center) { #if os(iOS) @@ -183,55 +237,7 @@ struct WebReaderContainerView: View { #endif Menu( content: { - Group { - Button( - action: { showHighlightsView = true }, - label: { Label("View Highlights & Notes", systemImage: "highlighter") } - ) - Button( - action: { showTitleEdit = true }, - label: { Label("Edit Title/Description", systemImage: "textbox") } - ) - Button( - action: editLabels, - label: { Label("Edit Labels", systemImage: "tag") } - ) - Button( - action: { - archive() - }, - label: { - Label( - item.isArchived ? "Unarchive" : "Archive", - systemImage: item.isArchived ? "tray.and.arrow.down.fill" : "archivebox" - ) - } - ) - Button( - action: { - dataService.updateLinkReadingProgress(itemID: item.unwrappedID, readingProgress: 0, anchorIndex: 0) - }, - label: { Label("Reset Read Location", systemImage: "arrow.counterclockwise.circle") } - ) - Button( - action: { /* viewModel.downloadAudio(audioController: audioController, item: item) */ }, - label: { Label("Download Audio", systemImage: "icloud.and.arrow.down") } - ) - Button( - action: share, - label: { Label("Share Original", systemImage: "square.and.arrow.up") } - ) - if viewModel.hasOriginalUrl(item) { - Button( - action: share, - label: { Label("Share Original", systemImage: "square.and.arrow.up") } - ) - } - Button( - action: delete, - label: { Label("Delete", systemImage: "trash") } - ) - } + menuItems(for: item) }, label: { #if os(iOS) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift index 6f7952c41..8cf73084e 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift @@ -1,6 +1,7 @@ import Models import Services import SwiftUI +import Views import WebKit struct SafariWebLink: Identifiable { @@ -22,6 +23,14 @@ struct SafariWebLink: Identifiable { return false } + func downloadAudio(audioController: AudioController, item: LinkedItem) { + Snackbar.show(message: "Downloading Offline Audio") + Task { + let downloaded = await audioController.downloadForOffline(itemID: item.unwrappedID) + Snackbar.show(message: downloaded ? "Audio file downloaded" : "Error downloading audio") + } + } + func loadContent(dataService: DataService, username: String, itemID: String, retryCount: Int = 0) async { errorMessage = nil diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift index b3ac2411e..c30663b1b 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift @@ -7,7 +7,6 @@ public enum GridCardAction { case delete case editLabels case editTitle - case downloadAudio case viewHighlights } @@ -46,17 +45,19 @@ public struct GridCard: View { var contextMenuView: some View { Group { - Button( - action: { menuActionHandler(.viewHighlights) }, - label: { Label("View Highlights & Notes", systemImage: "highlighter") } - ) + if (item.highlights?.count ?? 0) > 0 { + Button( + action: { menuActionHandler(.viewHighlights) }, + label: { Label("View Highlights & Notes", systemImage: "highlighter") } + ) + } Button( action: { menuActionHandler(.editTitle) }, label: { Label("Edit Title/Description", systemImage: "textbox") } ) Button( action: { menuActionHandler(.editLabels) }, - label: { Label("Edit Labels", systemImage: "tag") } + label: { Label(item.labels?.count == 0 ? "Add Labels" : "Edit Labels", systemImage: "tag") } ) Button( action: { menuActionHandler(.toggleArchiveStatus) }, diff --git a/apple/OmnivoreKit/Sources/Views/TextChip.swift b/apple/OmnivoreKit/Sources/Views/TextChip.swift index 61858a519..bfd56db27 100644 --- a/apple/OmnivoreKit/Sources/Views/TextChip.swift +++ b/apple/OmnivoreKit/Sources/Views/TextChip.swift @@ -110,7 +110,7 @@ public struct TextChipButton: View { } public static func makeSearchFilterButton(title: String, onTap: @escaping () -> Void) -> TextChipButton { - TextChipButton(title: "Search: \(title)", color: .appCtaYellow, actionType: .clear, negated: false, onTap: onTap) + TextChipButton(title: title, color: .appCtaYellow, actionType: .filter, negated: false, onTap: onTap) } public static func makeShowOptionsButton(title: String, onTap: @escaping () -> Void) -> TextChipButton { @@ -135,11 +135,11 @@ public struct TextChipButton: View { case remove case add case show - case clear + case filter var systemIconName: String { switch self { - case .clear, .remove: + case .filter, .remove: return "xmark" case .add: return "plus" @@ -173,6 +173,10 @@ public struct TextChipButton: View { public var body: some View { VStack(spacing: 0) { HStack { + if actionType == .filter { + Image(systemName: "line.3.horizontal.decrease") + } + Text(text) .strikethrough(color: negated ? foregroundColor : .clear) .padding(.leading, 3)