mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Use the ApplyLabels view when users want to search for labels
This commit is contained in:
parent
1d79690f0d
commit
12f40a61b8
7 changed files with 84 additions and 143 deletions
|
|
@ -4,9 +4,9 @@ import SwiftUI
|
|||
import Utils
|
||||
import Views
|
||||
|
||||
// swiftlint:disable:next type_body_length
|
||||
public struct ShareExtensionView: View {
|
||||
let extensionContext: NSExtensionContext?
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@StateObject var labelsViewModel = LabelsViewModel()
|
||||
@StateObject private var viewModel = ShareExtensionViewModel()
|
||||
|
||||
|
|
@ -14,7 +14,7 @@ public struct ShareExtensionView: View {
|
|||
@State var hideUntilReminded = false
|
||||
@State var previousLabels: [LinkedItemLabel]?
|
||||
@State var messageText: String?
|
||||
@State var searchTerm: String = ""
|
||||
@State var showSearchLabels = false
|
||||
|
||||
@State var viewState = ViewState.mainView
|
||||
@State var showHighlightInstructionAlert = false
|
||||
|
|
@ -129,7 +129,7 @@ public struct ShareExtensionView: View {
|
|||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
} else {}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: 60)
|
||||
.padding()
|
||||
|
|
@ -140,6 +140,28 @@ public struct ShareExtensionView: View {
|
|||
}
|
||||
}
|
||||
|
||||
var searchButton: some View {
|
||||
HStack {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.resizable()
|
||||
.frame(width: 15, height: 15)
|
||||
.foregroundColor(.appGrayText)
|
||||
.padding(.leading, 10)
|
||||
|
||||
Text("Search labels")
|
||||
.font(Font.system(size: 15))
|
||||
.foregroundColor(.appGrayText)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(height: 36)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(
|
||||
Color.appButtonBackground
|
||||
.cornerRadius(8)
|
||||
)
|
||||
}
|
||||
|
||||
var labelsSection: some View {
|
||||
HStack {
|
||||
if viewState != .editingLabels {
|
||||
|
|
@ -174,32 +196,24 @@ public struct ShareExtensionView: View {
|
|||
Image(systemName: "chevron.right")
|
||||
.font(.appCallout)
|
||||
} else {
|
||||
VStack {
|
||||
ScrollView {
|
||||
SearchBar(searchTerm: $searchTerm)
|
||||
VStack(spacing: 15) {
|
||||
searchButton
|
||||
.onTapGesture { showSearchLabels = true }
|
||||
|
||||
VStack {
|
||||
LabelsMasonaryView(labels: labelsViewModel.labels,
|
||||
selectedLabels: labelsViewModel.selectedLabels,
|
||||
onLabelTap: onLabelTap)
|
||||
}.background(Color.appButtonBackground)
|
||||
.cornerRadius(8)
|
||||
|
||||
Button(
|
||||
action: { labelsViewModel.showCreateLabelModal = true },
|
||||
label: {
|
||||
HStack {
|
||||
Spacer()
|
||||
Image(systemName: "plus")
|
||||
Text("Create label")
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
).buttonStyle(RoundedRectButtonStyle(color: .blue, textColor: .white))
|
||||
Spacer()
|
||||
}
|
||||
.background(Color.appButtonBackground)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(16)
|
||||
.padding(viewState == .editingLabels ? 0 : 16)
|
||||
.background(viewState == .editingLabels ? Color.clear : Color.appButtonBackground)
|
||||
.frame(maxWidth: .infinity, maxHeight: viewState == .editingLabels ? .infinity : 60)
|
||||
.background(Color.appButtonBackground)
|
||||
.cornerRadius(8)
|
||||
}
|
||||
|
||||
|
|
@ -492,89 +506,17 @@ public struct ShareExtensionView: View {
|
|||
isPresented: $showHighlightInstructionAlert) {
|
||||
Button("Ok", role: .cancel) { showHighlightInstructionAlert = false }
|
||||
}
|
||||
.environmentObject(viewModel.services.dataService)
|
||||
.task {
|
||||
await labelsViewModel.loadLabelsFromStore(dataService: viewModel.services.dataService)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ApplyLabelsListView: View {
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@StateObject var viewModel = LabelsViewModel()
|
||||
|
||||
let linkedItem: LinkedItem?
|
||||
|
||||
func isSelected(_ label: LinkedItemLabel) -> Bool {
|
||||
viewModel.selectedLabels.contains(where: { $0.id == label.id })
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section(
|
||||
content: {
|
||||
ForEach(viewModel.labels.applySearchFilter(viewModel.labelSearchFilter), id: \.self) { label in
|
||||
Button(
|
||||
action: {
|
||||
if isSelected(label) {
|
||||
viewModel.selectedLabels.removeAll(where: { $0.id == label.id })
|
||||
} else {
|
||||
viewModel.selectedLabels.append(label)
|
||||
}
|
||||
if let linkedItem = linkedItem {
|
||||
viewModel.saveItemLabelChanges(itemID: linkedItem.unwrappedID, dataService: dataService)
|
||||
}
|
||||
},
|
||||
label: {
|
||||
HStack {
|
||||
TextChip(feedItemLabel: label)
|
||||
Spacer()
|
||||
if isSelected(label) {
|
||||
Image(systemName: "checkmark.circle.fill")
|
||||
.foregroundColor(.checkmarkBlue)
|
||||
} else {
|
||||
Image(systemName: "circle")
|
||||
.foregroundColor(.appGraySolid)
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
)
|
||||
#if os(iOS)
|
||||
.listRowSeparator(.hidden)
|
||||
#endif
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
|
||||
},
|
||||
header: {
|
||||
Text(LocalText.labelsGeneric)
|
||||
.font(.appFootnote)
|
||||
.foregroundColor(.appGrayText)
|
||||
}
|
||||
)
|
||||
#if os(iOS)
|
||||
.listRowSeparator(.hidden)
|
||||
#endif
|
||||
Button(
|
||||
action: { viewModel.showCreateLabelModal = true },
|
||||
label: {
|
||||
HStack {
|
||||
Image(systemName: "plus.circle.fill").foregroundColor(.green)
|
||||
Text(LocalText.createLabelMessage).foregroundColor(.appGrayTextContrast)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
)
|
||||
.disabled(viewModel.isLoading)
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
.padding(.vertical, 0)
|
||||
.task {
|
||||
await viewModel.loadLabelsFromStore(dataService: dataService)
|
||||
}
|
||||
.sheet(isPresented: $viewModel.showCreateLabelModal) {
|
||||
CreateLabelView(viewModel: viewModel)
|
||||
}
|
||||
.sheet(isPresented: $showSearchLabels) {
|
||||
ApplyLabelsView(mode: .list(self.labelsViewModel.selectedLabels), isSearchFocused: true) { labels in
|
||||
self.labelsViewModel.selectedLabels = labels
|
||||
if let itemID = self.viewModel.linkedItem?.unwrappedID {
|
||||
self.labelsViewModel.saveItemLabelChanges(itemID: itemID, dataService: self.viewModel.services.dataService)
|
||||
}
|
||||
}
|
||||
}
|
||||
.environmentObject(viewModel.services.dataService)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ struct HighlightsListView: View {
|
|||
}
|
||||
}
|
||||
}.sheet(item: $setLabelsHighlight) { highlight in
|
||||
ApplyLabelsView(mode: .highlight(highlight), onSave: { selectedLabels in
|
||||
ApplyLabelsView(mode: .highlight(highlight), isSearchFocused: false, onSave: { selectedLabels in
|
||||
hasHighlightMutations = true
|
||||
|
||||
viewModel.setLabelsForHighlight(highlightID: highlight.unwrappedID,
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ import Views
|
|||
loadItems(isRefresh: true)
|
||||
}
|
||||
.sheet(item: $viewModel.itemUnderLabelEdit) { item in
|
||||
ApplyLabelsView(mode: .item(item), onSave: nil)
|
||||
ApplyLabelsView(mode: .item(item), isSearchFocused: false, onSave: nil)
|
||||
}
|
||||
.sheet(item: $viewModel.itemUnderTitleEdit) { item in
|
||||
LinkedItemMetadataEditView(item: item)
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ struct ApplyLabelsView: View {
|
|||
var navTitle: String {
|
||||
switch self {
|
||||
case .item, .highlight:
|
||||
return "Assign Labels"
|
||||
return "Set Labels"
|
||||
case .list:
|
||||
return "Apply Label Filters"
|
||||
return "Set Labels"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -23,24 +23,35 @@ struct ApplyLabelsView: View {
|
|||
case .item, .highlight:
|
||||
return "Save"
|
||||
case .list:
|
||||
return "Apply"
|
||||
return "Done"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mode: Mode
|
||||
let isSearchFocused: Bool
|
||||
let onSave: (([LinkedItemLabel]) -> Void)?
|
||||
|
||||
@EnvironmentObject var dataService: DataService
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
@StateObject var viewModel = LabelsViewModel()
|
||||
|
||||
enum ViewState {
|
||||
case mainView
|
||||
case editingTitle
|
||||
case editingLabels
|
||||
case viewingHighlight
|
||||
}
|
||||
|
||||
func isSelected(_ label: LinkedItemLabel) -> Bool {
|
||||
viewModel.selectedLabels.contains(where: { $0.id == label.id })
|
||||
}
|
||||
|
||||
var innerBody: some View {
|
||||
List {
|
||||
Section(header: Spacer(minLength: 0)) {
|
||||
SearchBar(searchTerm: $viewModel.labelSearchFilter, initialFocus: isSearchFocused)
|
||||
}
|
||||
Section {
|
||||
Button(
|
||||
action: { viewModel.showCreateLabelModal = true },
|
||||
|
|
@ -80,6 +91,7 @@ struct ApplyLabelsView: View {
|
|||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 0)
|
||||
.navigationTitle(mode.navTitle)
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
|
@ -136,10 +148,7 @@ struct ApplyLabelsView: View {
|
|||
EmptyView()
|
||||
} else {
|
||||
innerBody
|
||||
.searchable(
|
||||
text: $viewModel.labelSearchFilter,
|
||||
placement: .navigationBarDrawer(displayMode: .always)
|
||||
)
|
||||
.padding(.top, -20) // This is a hack to give us a bit more room on the page
|
||||
}
|
||||
}
|
||||
#elseif os(macOS)
|
||||
|
|
|
|||
|
|
@ -12,12 +12,8 @@ import Models
|
|||
import Views
|
||||
|
||||
struct LabelsMasonaryView: View {
|
||||
// var allLabels: [LinkedItemLabel]
|
||||
// var selectedLabels: [LinkedItemLabel]
|
||||
var onLabelTap: (LinkedItemLabel, TextChip) -> Void
|
||||
|
||||
var iteration = UUID().uuidString
|
||||
|
||||
@State private var totalHeight = CGFloat.zero
|
||||
private var labelItems: [(label: LinkedItemLabel, selected: Bool)]
|
||||
|
||||
|
|
@ -52,7 +48,8 @@ struct LabelsMasonaryView: View {
|
|||
return ZStack(alignment: .topLeading) {
|
||||
ForEach(self.labelItems, id: \.label.self) { label in
|
||||
self.item(for: label)
|
||||
.padding([.horizontal, .vertical], 6)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 5)
|
||||
.alignmentGuide(.leading, computeValue: { dim in
|
||||
if abs(width - dim.width) > geom.size.width {
|
||||
width = 0
|
||||
|
|
|
|||
|
|
@ -295,7 +295,7 @@ struct WebReaderContainerView: View {
|
|||
Button(LocalText.cancelGeneric, role: .cancel, action: {})
|
||||
}
|
||||
.sheet(isPresented: $showLabelsModal) {
|
||||
ApplyLabelsView(mode: .item(item), onSave: { _ in showLabelsModal = false })
|
||||
ApplyLabelsView(mode: .item(item), isSearchFocused: false, onSave: { _ in showLabelsModal = false })
|
||||
}
|
||||
.sheet(isPresented: $showTitleEdit) {
|
||||
LinkedItemMetadataEditView(item: item)
|
||||
|
|
@ -410,7 +410,7 @@ struct WebReaderContainerView: View {
|
|||
}
|
||||
.sheet(isPresented: $showHighlightLabelsModal) {
|
||||
if let highlight = Highlight.lookup(byID: self.annotation, inContext: self.dataService.viewContext) {
|
||||
ApplyLabelsView(mode: .highlight(highlight)) { selectedLabels in
|
||||
ApplyLabelsView(mode: .highlight(highlight), isSearchFocused: false) { selectedLabels in
|
||||
viewModel.setLabelsForHighlight(highlightID: highlight.unwrappedID,
|
||||
labelIDs: selectedLabels.map(\.unwrappedID),
|
||||
dataService: dataService)
|
||||
|
|
|
|||
|
|
@ -1,48 +1,38 @@
|
|||
import SwiftUI
|
||||
|
||||
public struct SearchBar: View {
|
||||
private let horizontalPadding: Double
|
||||
@Binding var searchTerm: String
|
||||
@FocusState private var isFocused: Bool
|
||||
@State private var initialFocus: Bool
|
||||
|
||||
public init(
|
||||
searchTerm: Binding<String>,
|
||||
horizontalPadding: Double = 10
|
||||
initialFocus: Bool
|
||||
) {
|
||||
self._searchTerm = searchTerm
|
||||
self.horizontalPadding = horizontalPadding
|
||||
self.initialFocus = initialFocus
|
||||
}
|
||||
|
||||
public var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
TextField("Search", text: $searchTerm)
|
||||
.padding(7)
|
||||
.padding(.horizontal, 25)
|
||||
.background(Color.systemGray6)
|
||||
.frame(height: 36)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(Color.appButtonBackground)
|
||||
.cornerRadius(8)
|
||||
.focused($isFocused)
|
||||
.padding(.leading, 24)
|
||||
.overlay(
|
||||
HStack {
|
||||
Image(systemName: "magnifyingglass")
|
||||
.foregroundColor(.gray)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, alignment: .leading)
|
||||
.padding(.leading, 10)
|
||||
.resizable()
|
||||
.frame(width: 15, height: 15)
|
||||
.foregroundColor(.appGrayText)
|
||||
.padding(.leading, 2)
|
||||
|
||||
if self.searchTerm != "" {
|
||||
Button(
|
||||
action: {
|
||||
self.searchTerm = ""
|
||||
},
|
||||
label: {
|
||||
Image(systemName: "multiply.circle.fill")
|
||||
.foregroundColor(.gray)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
)
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
|
||||
if isFocused {
|
||||
Button(
|
||||
|
|
@ -51,12 +41,15 @@ public struct SearchBar: View {
|
|||
self.isFocused = false
|
||||
},
|
||||
label: {
|
||||
Text(LocalText.cancelGeneric)
|
||||
Image(systemName: "multiply.circle.fill")
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
)
|
||||
.padding(.trailing, 10)
|
||||
.padding(.trailing, 0)
|
||||
.transition(.move(edge: .trailing))
|
||||
}
|
||||
}.onAppear {
|
||||
self.isFocused = initialFocus
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue