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? var body: some View { List { ForEach(viewModel.labels, id: \.id) { label in HStack { TextChip(feedItemLabel: label).allowsHitTesting(false) Spacer() Button( action: { labelToRemove = label showDeleteConfirmation = true }, label: { Image(systemName: "trash") } ) } } createLabelButton } .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 } } .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: { HStack { Image(systemName: "tag").foregroundColor(.blue) Text( viewModel.labelSearchFilter.count > 0 ? "Create: \"\(viewModel.labelSearchFilter)\" label" : LocalText.createLabelMessage ).foregroundColor(.blue) .font(Font.system(size: 14)) Spacer() } } ) .buttonStyle(PlainButtonStyle()) .disabled(viewModel.isLoading) #if os(iOS) .listRowSeparator(.hidden, edges: .bottom) #endif .padding(.vertical, 10) } } 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 { HStack { if !newLabelName.isEmpty, newLabelColor != .clear { TextChip(text: newLabelName, color: newLabelColor) } else { Text(LocalText.labelsViewAssignNameColor).font(.appBody) } Spacer() } .padding(.bottom, 8) TextField("Label Name", text: $newLabelName) .textFieldStyle(StandardTextFieldStyle()) 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" ]