Tusker/Tusker/Screens/Preferences/OppositeCollapseKeywordsVie...

112 lines
3.9 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)
}
.listRowBackground(Color.appGroupedCellBackground)
}
.animation(.default, value: keywords.map(\.id))
.listStyle(.grouped)
.appGroupedScrollBackgroundIfAvailable()
}
.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()
}
}