Add feeds, better delete handling
This commit is contained in:
@ -38,7 +38,7 @@ public struct EditLabelsSheet: View {
|
||||
if let idx = labelsViewModel.selectedLabels.firstIndex(of: label) {
|
||||
labelsViewModel.selectedLabels.remove(at: idx)
|
||||
} else {
|
||||
labelsViewModel.labelSearchFilter = ""
|
||||
labelsViewModel.labelSearchFilter = ZWSP
|
||||
labelsViewModel.selectedLabels.append(label)
|
||||
}
|
||||
|
||||
|
||||
@ -11,6 +11,8 @@ extension LinkedItemFilter {
|
||||
return LocalText.readLaterGeneric
|
||||
case .newsletters:
|
||||
return LocalText.newslettersGeneric
|
||||
case .feeds:
|
||||
return "Feeds"
|
||||
case .recommended:
|
||||
return "Recommended"
|
||||
case .all:
|
||||
|
||||
@ -72,7 +72,7 @@ struct ApplyLabelsView: View {
|
||||
viewModel.selectedLabels.remove(at: idx)
|
||||
}
|
||||
} else {
|
||||
viewModel.labelSearchFilter = ""
|
||||
viewModel.labelSearchFilter = ZWSP
|
||||
viewModel.selectedLabels.append(label)
|
||||
}
|
||||
},
|
||||
@ -204,9 +204,11 @@ struct ApplyLabelsView: View {
|
||||
|
||||
extension Sequence where Element == LinkedItemLabel {
|
||||
func applySearchFilter(_ searchFilter: String) -> [LinkedItemLabel] {
|
||||
if searchFilter.isEmpty {
|
||||
if searchFilter.isEmpty || searchFilter == ZWSP {
|
||||
return map { $0 } // return the identity of the sequence
|
||||
}
|
||||
return filter { ($0.name ?? "").lowercased().contains(searchFilter.lowercased()) }
|
||||
let index = searchFilter.index(searchFilter.startIndex, offsetBy: 1)
|
||||
let trimmed = searchFilter.suffix(from: index).lowercased()
|
||||
return filter { ($0.name ?? "").lowercased().contains(trimmed) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import SwiftUI
|
||||
@Published var unselectedLabels = Set<LinkedItemLabel>()
|
||||
@Published var labels = [LinkedItemLabel]()
|
||||
@Published var showCreateLabelModal = false
|
||||
@Published var labelSearchFilter = ""
|
||||
@Published var labelSearchFilter = ZWSP
|
||||
|
||||
public init() {}
|
||||
|
||||
@ -35,7 +35,9 @@ import SwiftUI
|
||||
await loadLabelsFromStore(dataService: dataService)
|
||||
for label in labels {
|
||||
if selLabels.contains(label) {
|
||||
selectedLabels.append(label)
|
||||
if !selectedLabels.contains(label) {
|
||||
selectedLabels.append(label)
|
||||
}
|
||||
} else {
|
||||
unselectedLabels.insert(label)
|
||||
}
|
||||
@ -49,7 +51,9 @@ import SwiftUI
|
||||
}
|
||||
for label in self.labels {
|
||||
if selLabels.contains(label) {
|
||||
self.selectedLabels.append(label)
|
||||
if !self.selectedLabels.contains(label) {
|
||||
self.selectedLabels.append(label)
|
||||
}
|
||||
} else {
|
||||
self.unselectedLabels.insert(label)
|
||||
}
|
||||
|
||||
@ -3,6 +3,8 @@ import Services
|
||||
import SwiftUI
|
||||
import Views
|
||||
|
||||
let ZWSP = "\u{200B}"
|
||||
|
||||
@MainActor
|
||||
protocol Entry {
|
||||
func item(parent: LabelsEntryView) -> AnyView
|
||||
@ -24,14 +26,11 @@ private struct LabelEntry: Entry {
|
||||
public struct LabelsEntryView: View {
|
||||
@Binding var searchTerm: String
|
||||
@State var viewModel: LabelsViewModel
|
||||
@State var lastSelected = false
|
||||
@State var justInserted = false
|
||||
|
||||
let entries: [Entry]
|
||||
|
||||
@State private var totalHeight = CGFloat.zero
|
||||
@FocusState private var textFieldFocused: Bool
|
||||
@FocusState private var neverFocused: Bool
|
||||
|
||||
public init(
|
||||
searchTerm: Binding<String>,
|
||||
@ -44,22 +43,21 @@ public struct LabelsEntryView: View {
|
||||
}
|
||||
|
||||
func onTextSubmit() {
|
||||
if searchTerm.count < 1 {
|
||||
let index = searchTerm.index(searchTerm.startIndex, offsetBy: 1)
|
||||
let trimmed = searchTerm.suffix(from: index).lowercased()
|
||||
|
||||
if trimmed.count < 1 {
|
||||
return
|
||||
}
|
||||
|
||||
// first see if there is a matching label
|
||||
let term = searchTerm.lowercased()
|
||||
if let label = viewModel.labels.first(where: { $0.name?.lowercased() == term }) {
|
||||
justInserted = true
|
||||
searchTerm = ""
|
||||
if let label = viewModel.labels.first(where: { $0.name?.lowercased() == trimmed }) {
|
||||
if !viewModel.selectedLabels.contains(label) {
|
||||
viewModel.selectedLabels.append(label)
|
||||
}
|
||||
|
||||
searchTerm = ZWSP
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
|
||||
lastSelected = false
|
||||
textFieldFocused = true
|
||||
justInserted = false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -78,32 +76,20 @@ public struct LabelsEntryView: View {
|
||||
.padding(5)
|
||||
.font(Font.system(size: 14))
|
||||
.multilineTextAlignment(.leading)
|
||||
.onChange(of: searchTerm, perform: { newValue in
|
||||
print("NEW VALUE: ", newValue.count)
|
||||
.onChange(of: searchTerm, perform: { _ in
|
||||
if searchTerm.count >= 64 {
|
||||
searchTerm = String(searchTerm.prefix(64))
|
||||
}
|
||||
if searchTerm.isEmpty {
|
||||
// When we insert a new item we set the text to "" so this block is triggered
|
||||
// we need to ignore that special case.
|
||||
if justInserted {
|
||||
justInserted = false
|
||||
return
|
||||
}
|
||||
if lastSelected {
|
||||
if viewModel.selectedLabels.count > 0 {
|
||||
lastSelected = false
|
||||
viewModel.selectedLabels.removeLast()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
|
||||
textFieldFocused = true
|
||||
}
|
||||
if viewModel.selectedLabels.count > 0 {
|
||||
viewModel.selectedLabels.removeLast()
|
||||
searchTerm = ZWSP
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) {
|
||||
textFieldFocused = true
|
||||
}
|
||||
} else {
|
||||
lastSelected = true
|
||||
searchTerm = "\u{200B}"
|
||||
searchTerm = ZWSP
|
||||
}
|
||||
} else if searchTerm != "\u{200B}" {
|
||||
lastSelected = false
|
||||
}
|
||||
})
|
||||
.onSubmit {
|
||||
@ -112,21 +98,21 @@ public struct LabelsEntryView: View {
|
||||
return result
|
||||
}
|
||||
|
||||
func onTextDelete() -> Bool { if searchTerm.isEmpty {
|
||||
if lastSelected {
|
||||
if viewModel.selectedLabels.count > 0 {
|
||||
viewModel.selectedLabels.removeLast()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
|
||||
textFieldFocused = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
lastSelected = true
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
// func onTextDelete() -> Bool { if searchTerm.isEmpty {
|
||||
// if lastSelected {
|
||||
// if viewModel.selectedLabels.count > 0 {
|
||||
// viewModel.selectedLabels.removeLast()
|
||||
// DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
|
||||
// textFieldFocused = true
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
// lastSelected = true
|
||||
// }
|
||||
// return true
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
|
||||
public var body: some View {
|
||||
// HStack(spacing: 0) {
|
||||
@ -2,6 +2,7 @@ import Foundation
|
||||
|
||||
public enum LinkedItemFilter: String, CaseIterable {
|
||||
case inbox
|
||||
case feeds
|
||||
case readlater
|
||||
case newsletters
|
||||
case recommended
|
||||
@ -17,6 +18,8 @@ public extension LinkedItemFilter {
|
||||
switch self {
|
||||
case .inbox:
|
||||
return "in:inbox"
|
||||
case .feeds:
|
||||
return "label:RSS"
|
||||
case .readlater:
|
||||
return "in:library"
|
||||
case .newsletters:
|
||||
@ -76,6 +79,11 @@ public extension LinkedItemFilter {
|
||||
format: "SUBQUERY(labels, $label, $label.name == \"Newsletter\").@count > 0"
|
||||
)
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [notInArchivePredicate, newsletterLabelPredicate])
|
||||
case .feeds:
|
||||
let feedLabelPredicate = NSPredicate(
|
||||
format: "SUBQUERY(labels, $label, $label.name == \"RSS\").@count > 0"
|
||||
)
|
||||
return NSCompoundPredicate(andPredicateWithSubpredicates: [notInArchivePredicate, feedLabelPredicate])
|
||||
case .recommended:
|
||||
// non-archived or deleted items with the Newsletter label
|
||||
let recommendedPredicate = NSPredicate(
|
||||
|
||||
Reference in New Issue
Block a user