127 lines
4.0 KiB
Swift
127 lines
4.0 KiB
Swift
//
|
|
// DocuframeView.swift
|
|
// GasTankAR
|
|
//
|
|
// Created by Dennis Nemec on 28.08.26.
|
|
//
|
|
|
|
import SwiftUI
|
|
import SwiftData
|
|
|
|
/// DOCUframe-Tab: Auswahl aufgenommener Fotos und Übergabe an DOCUframe (Upload) bzw. Mailversand.
|
|
struct DocuframeView: View {
|
|
let project: Project
|
|
|
|
@State private var viewModel = DocuframeViewModel()
|
|
|
|
private let columns = [GridItem(.adaptive(minimum: 110), spacing: 2)]
|
|
|
|
private var photos: [ProjectPhoto] {
|
|
project.photos.sorted { $0.creationDate > $1.creationDate }
|
|
}
|
|
|
|
var body: some View {
|
|
Group {
|
|
if photos.isEmpty {
|
|
ContentUnavailableView(
|
|
"Keine Fotos",
|
|
systemImage: "photo.on.rectangle.angled",
|
|
description: Text("Nimm im AR-Modus Fotos auf, um sie an DOCUframe zu übergeben.")
|
|
)
|
|
} else {
|
|
ScrollView {
|
|
LazyVGrid(columns: columns, spacing: 2) {
|
|
ForEach(photos) { photo in
|
|
selectableThumbnail(photo)
|
|
}
|
|
}
|
|
.padding(2)
|
|
}
|
|
}
|
|
}
|
|
.safeAreaInset(edge: .bottom) {
|
|
if !photos.isEmpty { actionBar }
|
|
}
|
|
.alert(
|
|
viewModel.result?.title ?? "",
|
|
isPresented: resultBinding,
|
|
presenting: viewModel.result
|
|
) { _ in
|
|
Button("OK", role: .cancel) {}
|
|
} message: { result in
|
|
Text(result.message)
|
|
}
|
|
}
|
|
|
|
private func selectableThumbnail(_ photo: ProjectPhoto) -> some View {
|
|
let selected = viewModel.isSelected(photo)
|
|
return PhotoThumbnail(imageData: photo.imageData)
|
|
.aspectRatio(1, contentMode: .fit)
|
|
.clipped()
|
|
.overlay(alignment: .topTrailing) {
|
|
Image(systemName: selected ? "checkmark.circle.fill" : "circle")
|
|
.font(.title3)
|
|
.symbolRenderingMode(.palette)
|
|
.foregroundStyle(.white, selected ? Color.accentColor : Color.black.opacity(0.35))
|
|
.padding(6)
|
|
.shadow(radius: 2)
|
|
}
|
|
.overlay {
|
|
if selected {
|
|
Rectangle().strokeBorder(Color.accentColor, lineWidth: 3)
|
|
}
|
|
}
|
|
.contentShape(.rect)
|
|
.onTapGesture { viewModel.toggleSelection(photo) }
|
|
}
|
|
|
|
private var actionBar: some View {
|
|
VStack(spacing: 10) {
|
|
Text(viewModel.hasSelection ? "\(viewModel.selectionCount) ausgewählt" : "Fotos zum Übertragen auswählen")
|
|
.font(.footnote)
|
|
.foregroundStyle(.secondary)
|
|
|
|
VStack(spacing: 8) {
|
|
Button {
|
|
Task { await viewModel.upload(from: photos, leadID: project.leadID) }
|
|
} label: {
|
|
Label("In DOCUframe hochladen", systemImage: "arrow.up.doc")
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
|
|
Button {
|
|
Task { await viewModel.sendEmail(from: photos, leadID: project.leadID) }
|
|
} label: {
|
|
Label("E-Mail senden", systemImage: "envelope")
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.bordered)
|
|
}
|
|
.controlSize(.large)
|
|
.disabled(!viewModel.hasSelection || viewModel.isProcessing)
|
|
|
|
if viewModel.isProcessing {
|
|
ProgressView()
|
|
}
|
|
}
|
|
.padding()
|
|
.background(.bar)
|
|
}
|
|
|
|
private var resultBinding: Binding<Bool> {
|
|
Binding(
|
|
get: { viewModel.result != nil },
|
|
set: { if !$0 { viewModel.result = nil } }
|
|
)
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
let sample = PreviewSupport.galleryProjectContainer()
|
|
return NavigationStack {
|
|
DocuframeView(project: sample.project)
|
|
}
|
|
.modelContainer(sample.container)
|
|
}
|