112 lines
4.0 KiB
Swift
112 lines
4.0 KiB
Swift
//
|
|
// OppositeCollapseKeywordsView.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 11/1/20.
|
|
// Copyright © 2020 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
struct OppositeCollapseKeywordsView: View {
|
|
@ObservedObject private var preferences = Preferences.shared
|
|
// Can't use the raw [String] for keywords, because we need a fixed ID (within the lifetime of this view) for ForEach
|
|
@State private var keywords: [Keyword] = Preferences.shared.oppositeCollapseKeywords.map(Keyword.init) {
|
|
didSet {
|
|
preferences.oppositeCollapseKeywords = keywords.map(\.value)
|
|
}
|
|
}
|
|
@State private var valueToAdd = ""
|
|
@State private var makeAddFieldFirstResponder = false
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
// the background from the grouped ListStyle clips to the safe area, so when the keyboard is hiding/showing
|
|
// the color behind it can be seen, which looks odd
|
|
// Color(UIColor.secondarySystemBackground)
|
|
// .edgesIgnoringSafeArea(.bottom)
|
|
|
|
List {
|
|
Section(footer: Text("A post matches if its content warning contains the text of a keyword, ignoring case.")) {
|
|
ForEach(keywords) { (keyword) in
|
|
Row(keyword: keyword) {
|
|
keywords.removeAll(where: { $0.id == keyword.id })
|
|
}
|
|
}
|
|
.onDelete(perform: self.removeKeywords)
|
|
|
|
FocusableTextField(placeholder: "Add Keyword", text: $valueToAdd, becomeFirstResponder: $makeAddFieldFirstResponder, onCommit: self.addKeyword)
|
|
}
|
|
.appGroupedListRowBackground()
|
|
}
|
|
.animation(.default, value: keywords.map(\.id))
|
|
.listStyle(.grouped)
|
|
.appGroupedListBackground(container: PreferencesNavigationController.self)
|
|
}
|
|
.onAppear(perform: updateAppearance)
|
|
.navigationBarTitle(preferences.expandAllContentWarnings ? "Collapse Post CW Keywords" : "Expand Post CW Keywords")
|
|
}
|
|
|
|
private func updateAppearance() {
|
|
if #available(iOS 16.0, *) {
|
|
// no longer necessary
|
|
} else {
|
|
UIScrollView.appearance(whenContainedInInstancesOf: [PreferencesNavigationController.self]).keyboardDismissMode = .interactive
|
|
}
|
|
}
|
|
|
|
private func commitExisting(at index: Int) -> () -> Void {
|
|
return {
|
|
if keywords[index].value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
keywords.remove(at: index)
|
|
}
|
|
|
|
makeAddFieldFirstResponder = true
|
|
}
|
|
}
|
|
|
|
private func removeKeywords(_ indices: IndexSet) {
|
|
keywords.remove(atOffsets: indices)
|
|
}
|
|
|
|
private func addKeyword() {
|
|
guard !valueToAdd.isEmpty else { return }
|
|
keywords.append(Keyword(valueToAdd))
|
|
valueToAdd = ""
|
|
}
|
|
}
|
|
|
|
fileprivate extension OppositeCollapseKeywordsView {
|
|
// Class for wrapping keywords that provides a fixed id SwiftUI's ForEach can use
|
|
class Keyword: ObservableObject, Identifiable {
|
|
let id = UUID()
|
|
@Published var value: String
|
|
|
|
init(_ value: String) {
|
|
self.value = value
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate extension OppositeCollapseKeywordsView {
|
|
// Use a separate View for the row so it can use @ObservableObject to get a binding for the keyword's value
|
|
struct Row: View {
|
|
@ObservedObject var keyword: Keyword
|
|
let removeKeyword: () -> Void
|
|
|
|
var body: some View {
|
|
FocusableTextField(placeholder: "Keyword", text: $keyword.value) {
|
|
if keyword.value.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
removeKeyword()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct OppositeCollapseKeywordsView_Previews: PreviewProvider {
|
|
static var previews: some View {
|
|
OppositeCollapseKeywordsView()
|
|
}
|
|
}
|