203 lines
8.3 KiB
Swift
203 lines
8.3 KiB
Swift
import SwiftUI
|
|
|
|
struct ARProjectsSheet: View {
|
|
let onOpen: (UUID) -> Void
|
|
@State private var model: ARProjectsViewModel
|
|
@State private var creating = false
|
|
@State private var editing: ARProject?
|
|
@Environment(\.dismiss) private var dismiss
|
|
|
|
init(service: any ARProjectsProviding, onOpen: @escaping (UUID) -> Void) {
|
|
self.onOpen = onOpen
|
|
_model = State(initialValue: ARProjectsViewModel(service: service))
|
|
}
|
|
|
|
var body: some View {
|
|
NavigationStack {
|
|
ARProjectListView(supplied: model.supplied,
|
|
custom: model.custom, onOpen: onOpen, onDelete: model.delete, onEdit: { editing = $0 }, onCreate: { creating = true })
|
|
.overlay { if model.isLoading { ProgressView() } }
|
|
.navigationTitle("AR-Projekte")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.toolbar {
|
|
ToolbarItem(placement: .cancellationAction) {
|
|
Button("Abbrechen", systemImage: "xmark") { dismiss() }
|
|
.labelStyle(.iconOnly)
|
|
}
|
|
ToolbarItem(placement: .primaryAction) {
|
|
Button("Neues Projekt", systemImage: "plus") { creating = true }
|
|
.labelStyle(.iconOnly)
|
|
.buttonStyle(.borderedProminent)
|
|
}
|
|
}
|
|
.navigationDestination(isPresented: $creating) {
|
|
ARProjectCreateView { name, tank, start in
|
|
guard let project = model.create(name: name, tank: tank) else { return }
|
|
creating = false
|
|
if start { onOpen(project.id) }
|
|
}
|
|
}
|
|
.navigationDestination(isPresented: Binding(
|
|
get: { editing != nil }, set: { if !$0 { editing = nil } }
|
|
)) {
|
|
if let project = editing {
|
|
ARProjectCreateView(project: project) { name, tank, start in
|
|
guard let updated = model.update(id: project.id, name: name, tank: tank) else { return }
|
|
editing = nil
|
|
if start { onOpen(updated.id) }
|
|
}
|
|
.id(project.id)
|
|
}
|
|
}
|
|
}
|
|
.task { model.load() }
|
|
.alert("Projektaktion fehlgeschlagen",
|
|
isPresented: Binding(get: { model.errorMessage != nil },
|
|
set: { if !$0 { model.errorMessage = nil } })) {
|
|
Button("OK", role: .cancel) { model.errorMessage = nil }
|
|
} message: { Text(model.errorMessage ?? "") }
|
|
}
|
|
}
|
|
|
|
struct ARProjectListView: View {
|
|
let supplied: [ARProject]
|
|
let custom: [ARProject]
|
|
let onOpen: (UUID) -> Void
|
|
let onDelete: (UUID) -> Void
|
|
let onEdit: (ARProject) -> Void
|
|
let onCreate: () -> Void
|
|
|
|
var body: some View {
|
|
List {
|
|
if !supplied.isEmpty {
|
|
Section {
|
|
ForEach(supplied) { project in
|
|
HStack(spacing: 12) {
|
|
ARProjectRow(project: project)
|
|
Button { onOpen(project.id) } label: {
|
|
Label("AR", systemImage: "arkit")
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.accessibilityLabel("AR-Modus starten: " + project.name)
|
|
}
|
|
|
|
}
|
|
} header: { Text("Bereitgestellte Projekte") }
|
|
footer: { Text("Diese Projekte werden automatisch anhand der Angebote bereitgestellt.") }
|
|
}
|
|
|
|
Section("Eigene Projekte") {
|
|
ForEach(custom) { project in
|
|
HStack(spacing: 12) {
|
|
Button { onEdit(project) } label: {
|
|
ARProjectRow(project: project).contentShape(Rectangle())
|
|
}
|
|
.buttonStyle(.plain)
|
|
.accessibilityHint("Projektkonfiguration bearbeiten")
|
|
Button { onOpen(project.id) } label: {
|
|
Label("AR", systemImage: "arkit")
|
|
}
|
|
.buttonStyle(.bordered)
|
|
.accessibilityLabel("AR-Modus starten: " + project.name)
|
|
Divider()
|
|
.frame(height: 24)
|
|
.accessibilityHidden(true)
|
|
Button { onEdit(project) } label: {
|
|
Image(systemName: "chevron.right")
|
|
.font(.body.weight(.semibold))
|
|
.foregroundStyle(.secondary)
|
|
.frame(minWidth: 44, minHeight: 44)
|
|
.contentShape(Rectangle())
|
|
}
|
|
.buttonStyle(.plain)
|
|
.accessibilityLabel("Projekt bearbeiten: " + project.name)
|
|
}
|
|
.swipeActions(edge: .trailing) {
|
|
Button("Löschen", systemImage: "trash", role: .destructive) {
|
|
onDelete(project.id)
|
|
}
|
|
.tint(.red)
|
|
}
|
|
}
|
|
Button(action: onCreate) {
|
|
Label("Neues Projekt", systemImage: "plus.circle.fill")
|
|
.padding(.vertical, 8)
|
|
}
|
|
}
|
|
}
|
|
.listStyle(.insetGrouped)
|
|
}
|
|
}
|
|
|
|
struct ARProjectRow: View {
|
|
let project: ARProject
|
|
@Environment(\.tankCatalog) private var catalog
|
|
@Environment(\.dynamicTypeSize) private var typeSize
|
|
|
|
var body: some View {
|
|
HStack(spacing: 12) {
|
|
ARProjectTankImage(tank: project.tank)
|
|
.frame(width: 76, height: 60)
|
|
.accessibilityHidden(true)
|
|
VStack(alignment: .leading, spacing: 5) {
|
|
Text(project.name).font(.headline)
|
|
if typeSize.isAccessibilitySize {
|
|
configuration
|
|
reference
|
|
} else {
|
|
HStack(alignment: .firstTextBaseline) {
|
|
configuration
|
|
Spacer(minLength: 4)
|
|
reference
|
|
}
|
|
}
|
|
}
|
|
Spacer(minLength: 0)
|
|
}
|
|
.padding(.vertical, 6)
|
|
.accessibilityElement(children: .combine)
|
|
}
|
|
|
|
private var configuration: some View {
|
|
Text("\(project.tank.size.volumeTitle) · \(project.tank.placement.displayName) · \(catalog.displayName(for: project.tank.color))")
|
|
.font(.caption).foregroundStyle(.secondary)
|
|
}
|
|
|
|
@ViewBuilder
|
|
private var reference: some View {
|
|
if let reference = project.reference {
|
|
Text(reference).font(.caption2).foregroundStyle(.secondary)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#Preview("AR-Projekte") {
|
|
ARProjectsSheet(service: ARProjectsPreviewService()) { _ in }
|
|
.frame(maxWidth: 600, maxHeight: 700)
|
|
}
|
|
|
|
private struct ARProjectsPreviewService: ARProjectsProviding {
|
|
func updateProject(id: UUID, name: String, tank: Tank) throws -> ARProject {
|
|
ARProject(id: id, name: name, tank: tank, createdAt: .now, source: .custom, reference: nil)
|
|
}
|
|
|
|
func deleteProject(id: UUID) throws {}
|
|
|
|
func loadProjects() throws -> [ARProject] {
|
|
[
|
|
ARProject(id: UUID(), name: "Flüssiggastank 2.700 l oberirdisch", tank: Tank(),
|
|
createdAt: .now, source: .supplied, reference: "A-2026-1182"),
|
|
ARProject(id: UUID(), name: "Flüssiggastank 4.850 l unterirdisch",
|
|
tank: Tank(size: .t2_1, color: .olive, placement: .belowGround),
|
|
createdAt: .now, source: .supplied, reference: "A-2026-1197"),
|
|
ARProject(id: UUID(), name: "Variante hinter dem Haus", tank: Tank(color: .green),
|
|
createdAt: .now, source: .custom, reference: nil)
|
|
]
|
|
}
|
|
|
|
func createProject(name: String, tank: Tank) throws -> ARProject {
|
|
ARProject(id: UUID(), name: name, tank: tank, createdAt: .now, source: .custom, reference: nil)
|
|
}
|
|
}
|