IceCubesApp/Packages/Account/Sources/Account/Filters/EditFilterView.swift
Thomas Ricouard da0f87be8e WIP
2023-09-12 12:40:37 +02:00

298 lines
8.5 KiB
Swift

import DesignSystem
import Env
import Models
import Network
import SwiftUI
struct EditFilterView: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject private var theme: Theme
@EnvironmentObject private var account: CurrentAccount
@EnvironmentObject private var client: Client
@State private var isSavingFilter: Bool = false
@State private var filter: ServerFilter?
@State private var title: String
@State private var keywords: [ServerFilter.Keyword]
@State private var newKeyword: String = ""
@State private var contexts: [ServerFilter.Context]
@State private var filterAction: ServerFilter.Action
@State private var expiresAt: Date?
@State private var expirySelection: Duration
enum Fields {
case title, newKeyword
}
@FocusState private var focusedField: Fields?
private var data: ServerFilterData {
var expiresIn: String?
// we add 50 seconds, otherwise we immediately show 6d for a 7d filter (6d, 23h, 59s)
switch expirySelection {
case .infinite:
expiresIn = "" // need to send an empty value in order for the server to clear this field in the filter
case .custom:
expiresIn = String(Int(expiresAt?.timeIntervalSince(Date()) ?? 0) + 50)
default:
expiresIn = String(expirySelection.rawValue + 50)
}
return ServerFilterData(title: title,
context: contexts,
filterAction: filterAction,
expiresIn: expiresIn)
}
private var canSave: Bool {
!title.isEmpty
}
init(filter: ServerFilter?) {
_filter = .init(initialValue: filter)
_title = .init(initialValue: filter?.title ?? "")
_keywords = .init(initialValue: filter?.keywords ?? [])
_contexts = .init(initialValue: filter?.context ?? [.home])
_filterAction = .init(initialValue: filter?.filterAction ?? .warn)
_expiresAt = .init(initialValue: filter?.expiresAt?.asDate)
_expirySelection = .init(initialValue: filter?.expiresAt == nil ? .infinite : .custom)
}
var body: some View {
Form {
titleSection
if filter != nil {
expirySection
keywordsSection
contextsSection
filterActionView
}
}
.navigationTitle(filter?.title ?? NSLocalizedString("filter.new", comment: ""))
.navigationBarTitleDisplayMode(.inline)
.scrollContentBackground(.hidden)
.background(theme.secondaryBackgroundColor)
.onAppear {
if filter == nil {
focusedField = .title
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
saveButton
}
}
}
private var expirySection: some View {
Section("filter.edit.expiry") {
Picker(selection: $expirySelection, label: Text("filter.edit.expiry.duration")) {
ForEach(Duration.filterDurations(), id: \.rawValue) { duration in
Text(duration.description).tag(duration)
}
}
.onChange(of: expirySelection) { duration in
if duration != .custom {
expiresAt = Date(timeIntervalSinceNow: TimeInterval(duration.rawValue))
}
}
if expirySelection != .infinite {
DatePicker("filter.edit.expiry.date-time",
selection: Binding<Date>(get: { self.expiresAt ?? Date() }, set: { self.expiresAt = $0 }),
displayedComponents: [.date, .hourAndMinute])
.disabled(expirySelection != .custom)
}
}
.listRowBackground(theme.primaryBackgroundColor)
}
@ViewBuilder
private var titleSection: some View {
Section("filter.edit.title") {
TextField("filter.edit.title", text: $title)
.focused($focusedField, equals: .title)
.onSubmit {
Task {
await saveFilter()
}
}
}
.listRowBackground(theme.primaryBackgroundColor)
if filter == nil, !title.isEmpty {
Section {
Button {
Task {
await saveFilter()
}
} label: {
if isSavingFilter {
ProgressView()
.frame(maxWidth: .infinity)
} else {
Text("action.save")
.frame(maxWidth: .infinity)
}
}
.buttonStyle(.borderedProminent)
.transition(.opacity)
}
.listRowBackground(theme.secondaryBackgroundColor)
}
}
private var keywordsSection: some View {
Section("filter.edit.keywords") {
ForEach(keywords) { keyword in
HStack {
Text(keyword.keyword)
Spacer()
Button {
Task {
await deleteKeyword(keyword: keyword)
}
} label: {
Image(systemName: "trash")
.tint(.red)
}
}
}
.onDelete { indexes in
if let index = indexes.first {
let keyword = keywords[index]
Task {
await deleteKeyword(keyword: keyword)
}
}
}
HStack {
TextField("filter.edit.keywords.add", text: $newKeyword, axis: .horizontal)
.focused($focusedField, equals: .newKeyword)
.onSubmit {
Task {
await addKeyword(name: newKeyword)
newKeyword = ""
focusedField = .newKeyword
}
}
Spacer()
if !newKeyword.isEmpty {
Button {
Task {
Task {
await addKeyword(name: newKeyword)
newKeyword = ""
}
}
} label: {
Image(systemName: "checkmark.circle.fill")
.tint(.green)
}
}
}
}
.listRowBackground(theme.primaryBackgroundColor)
}
private var contextsSection: some View {
Section("filter.edit.contexts") {
ForEach(ServerFilter.Context.allCases, id: \.self) { context in
Toggle(isOn: .init(get: {
contexts.contains(where: { $0 == context })
}, set: { _ in
if let index = contexts.firstIndex(of: context) {
contexts.remove(at: index)
} else {
contexts.append(context)
}
Task {
await saveFilter()
}
})) {
Label(context.name, systemImage: context.iconName)
}
.disabled(isSavingFilter)
}
.listRowBackground(theme.primaryBackgroundColor)
}
}
private var filterActionView: some View {
Section("filter.edit.action") {
Picker(selection: $filterAction) {
ForEach(ServerFilter.Action.allCases, id: \.self) { filter in
Text(filter.label)
.id(filter)
}
} label: {
EmptyView()
}
.onChange(of: filterAction) { _ in
Task {
await saveFilter()
}
}
.pickerStyle(.inline)
}
.listRowBackground(theme.primaryBackgroundColor)
}
private var saveButton: some View {
Button {
Task {
await saveFilter()
dismiss()
}
} label: {
if isSavingFilter {
ProgressView()
} else {
Text("action.save").bold()
}
}
.disabled(!canSave)
}
private func saveFilter() async {
do {
isSavingFilter = true
if let filter {
self.filter = try await client.put(endpoint: ServerFilters.editFilter(id: filter.id, json: data),
forceVersion: .v2)
} else {
let newFilter: ServerFilter = try await client.post(endpoint: ServerFilters.createFilter(json: data),
forceVersion: .v2)
filter = newFilter
}
} catch {}
isSavingFilter = false
}
private func addKeyword(name: String) async {
guard let filterId = filter?.id else { return }
isSavingFilter = true
do {
let keyword: ServerFilter.Keyword = try await
client.post(endpoint: ServerFilters.addKeyword(filter: filterId,
keyword: name,
wholeWord: true),
forceVersion: .v2)
keywords.append(keyword)
} catch {}
isSavingFilter = false
}
private func deleteKeyword(keyword: ServerFilter.Keyword) async {
isSavingFilter = true
do {
let response = try await client.delete(endpoint: ServerFilters.removeKeyword(id: keyword.id),
forceVersion: .v2)
if response?.statusCode == 200 {
keywords.removeAll(where: { $0.id == keyword.id })
}
} catch {}
isSavingFilter = false
}
}