Files
omnivore/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsView.swift
2023-12-23 22:25:20 +08:00

262 lines
6.7 KiB
Swift

import Models
import Services
import SwiftUI
import Utils
import Views
struct LabelsView: View {
@EnvironmentObject var dataService: DataService
@StateObject var viewModel = LabelsViewModel()
@State private var showDeleteConfirmation = false
@State private var labelToRemove: LinkedItemLabel?
@Environment(\.dismiss) private var dismiss
@AppStorage(UserDefaultKey.hideSystemLabels.rawValue) var hideSystemLabels = false
var body: some View {
List {
Section {
ForEach(viewModel.labels, id: \.id) { label in
HStack {
TextChip(feedItemLabel: label).allowsHitTesting(false)
Spacer()
if !isSystemLabel(label) {
Button(
action: {
labelToRemove = label
showDeleteConfirmation = true
},
label: { Image(systemName: "trash") }
)
}
}
}
createLabelButton
}
Section("Label settings") {
Toggle("Hide system labels", isOn: $hideSystemLabels)
}
}
.navigationTitle(LocalText.labelsGeneric)
.alert("Are you sure you want to delete this label?", isPresented: $showDeleteConfirmation) {
Button("Delete Label", role: .destructive) {
if let label = labelToRemove {
withAnimation {
viewModel.deleteLabel(
dataService: dataService,
labelID: label.unwrappedID,
name: label.unwrappedName
)
}
}
self.labelToRemove = nil
}
Button(LocalText.cancelGeneric, role: .cancel) { self.labelToRemove = nil }
}
.onChange(of: hideSystemLabels) { newValue in
PublicValet.hideLabels = newValue
Task {
await viewModel.loadLabels(dataService: dataService, item: nil)
}
}
.onReceive(NotificationCenter.default.publisher(for: Notification.Name("ScrollToTop"))) { _ in
dismiss()
}
.sheet(isPresented: $viewModel.showCreateLabelModal) {
CreateLabelView(viewModel: viewModel, newLabelName: viewModel.labelSearchFilter)
}
.task { await viewModel.loadLabels(dataService: dataService, item: nil) }
}
var createLabelButton: some View {
Button(
action: { viewModel.showCreateLabelModal = true },
label: {
Label(title: {
let trimmedLabelName = viewModel.labelSearchFilter.trimmingCharacters(in: .whitespacesAndNewlines)
Text(
viewModel.labelSearchFilter.count > 0 && viewModel.labelSearchFilter != ZWSP ?
"Create: \"\(trimmedLabelName)\" label" :
LocalText.createLabelMessage
)
}, icon: {
Image.addLink
})
}
)
.disabled(viewModel.isLoading)
}
}
struct CreateLabelView: View {
@EnvironmentObject var dataService: DataService
@ObservedObject var viewModel: LabelsViewModel
@State private var newLabelName: String
@State private var newLabelColor = Color.clear
init(viewModel: LabelsViewModel, newLabelName: String = "") {
self.viewModel = viewModel
self.newLabelName = newLabelName
}
var shouldDisableCreateButton: Bool {
viewModel.isLoading || newLabelName.isEmpty || newLabelColor == .clear
}
let rows = [
GridItem(.fixed(60)),
GridItem(.fixed(60)),
GridItem(.fixed(70))
]
let swatches: [Color] = {
let webSwatches = webSwatchHexes.map { Color(hex: $0) ?? .clear }
var additionalSwatches = swatchHexes.map { Color(hex: $0) ?? .clear }.shuffled()
let firstSwatch = additionalSwatches.remove(at: 0)
return [firstSwatch] + webSwatches + additionalSwatches
}()
var innerBody: some View {
VStack {
TextField(LocalText.labelNamePlaceholder, text: $newLabelName)
.textFieldStyle(StandardTextFieldStyle())
.onChange(of: newLabelName) { inputLabelName in
newLabelName = String(inputLabelName.prefix(viewModel.labelNameMaxLength))
}
Text("\(newLabelName.count)/\(viewModel.labelNameMaxLength)")
.font(.caption)
.frame(maxWidth: .infinity, alignment: .trailing)
.foregroundColor(newLabelName.count < viewModel.labelNameMaxLength ? .gray : .red)
ScrollView(.horizontal, showsIndicators: false) {
LazyHGrid(rows: rows, alignment: .top, spacing: 20) {
ForEach(swatches, id: \.self) { swatch in
ZStack {
Circle()
.fill(swatch)
.frame(width: 50, height: 50)
.onTapGesture {
newLabelColor = swatch
}
.padding(10)
if newLabelColor == swatch {
Circle()
.stroke(swatch, lineWidth: 5)
.frame(width: 60, height: 60)
}
}
}
}
}
Spacer()
}
.padding()
.toolbar {
#if os(iOS)
ToolbarItem(placement: .barLeading) {
cancelCreateLabelButton
}
ToolbarItem(placement: .barTrailing) {
createLabelButton
}
#else
ToolbarItemGroup {
createLabelButton
cancelCreateLabelButton
}
#endif
}
}
var cancelCreateLabelButton: some View {
Button(
action: { viewModel.showCreateLabelModal = false },
label: { Text(LocalText.cancelGeneric).foregroundColor(.appGrayTextContrast) }
)
}
var createLabelButton: some View {
Button(
action: {
viewModel.createLabel(
dataService: dataService,
name: newLabelName,
color: newLabelColor,
description: nil
)
},
label: { Text(LocalText.genericCreate).foregroundColor(.appGrayTextContrast) }
)
.opacity(shouldDisableCreateButton ? 0.2 : 1)
.disabled(shouldDisableCreateButton)
}
var body: some View {
#if os(iOS)
NavigationView {
innerBody
.navigationTitle("Create New Label")
.navigationBarTitleDisplayMode(.inline)
.task {
newLabelColor = swatches.first ?? .clear
}
}
#else
innerBody
.task {
newLabelColor = swatches.first ?? .clear
}
#endif
}
}
private let webSwatchHexes = [
"#FF5D99",
"#7CFF7B",
"#FFD234",
"#7BE4FF",
"#CE88EF",
"#EF8C43"
]
private let swatchHexes = [
"#fff034",
"#efff34",
"#d1ff34",
"#b2ff34",
"#94ff34",
"#75ff34",
"#57ff34",
"#38ff34",
"#34ff4e",
"#34ff6d",
"#34ff8b",
"#34ffa9",
"#34ffc8",
"#34ffe6",
"#34f9ff",
"#34dbff",
"#34bcff",
"#349eff",
"#347fff",
"#3461ff",
"#3443ff",
"#4434ff",
"#6234ff",
"#8134ff",
"#9f34ff",
"#be34ff",
"#dc34ff",
"#fb34ff",
"#ff34e5",
"#ff34c7",
"#ff34a8",
"#ff348a",
"#ff346b"
]