status hinzugefügt

regelbasiertes styling
architektur überarbeitet
This commit is contained in:
2026-02-06 15:34:39 +01:00
parent 8eeeb2ce99
commit 7781551e02
13 changed files with 960 additions and 301 deletions

View File

@@ -3,54 +3,26 @@ package com.example.snapandsolve
import DamageFilterDialog
import DamageListDialog
import MapViewModel
import android.Manifest
import android.R.attr.enabled
import android.app.Application
import android.graphics.BitmapFactory
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.AddLocation
import androidx.compose.material.icons.filled.AddLocationAlt
import androidx.compose.material.icons.filled.FilterAlt
import androidx.compose.material.icons.filled.FormatListNumbered
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.ThumbUp
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import applyDamageFilter
import com.arcgismaps.data.ArcGISFeature
// Hier holen wir die ArcGIS Klassen
import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.toolkit.geoviewcompose.MapView
// Hier deine eigenen Klassen (Pfade prüfen!)
import com.example.snapandsolve.camera.AlbumViewModel
import com.example.snapandsolve.camera.AlbumViewState
import com.example.snapandsolve.camera.Intent
import com.example.snapandsolve.ui.theme.*
import com.example.snapandsolve.ui.theme.composable.SideSlider
import com.example.snapandsolve.ui.theme.composable.SliderMenuItem
import com.example.snapandsolve.view.ReportDialog
import getActiveFilters
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -60,17 +32,14 @@ import kotlinx.coroutines.launch
fun MainScreen(modifier: Modifier = Modifier, application: Application) {
var showReport by rememberSaveable { mutableStateOf(false) }
var sliderOpen by rememberSaveable { mutableStateOf(false) }
val mapViewModel = remember { MapViewModel(application) }
val albumViewModel = remember { AlbumViewModel(Dispatchers.Default) }
// test
fun openReport() {
showReport = true
sliderOpen = false
}
// test
fun closeReport() {
showReport = false
}
@@ -160,7 +129,7 @@ fun ContentScreen(
// Report Overlay
if (showReport) {
ReportOverlay(
ReportDialog(
onCancel = onDismissReport,
onClose = onDismissReport,
viewModel = albumViewModel,
@@ -236,219 +205,7 @@ fun AppTopBar(
)
}
@Composable
fun ReportOverlay(
onCancel: () -> Unit,
onClose: () -> Unit,
viewModel: AlbumViewModel,
mapViewModel: MapViewModel
) {
val viewState: AlbumViewState by viewModel.viewStateFlow.collectAsState()
val currentContext = LocalContext.current
val hasPoint = mapViewModel.reportDraft.point != null
var dropdownExpanded by remember { mutableStateOf(false) }
LaunchedEffect(viewState.selectedPictures) {
mapViewModel.updateReportDraft { copy(photos = viewState.selectedPictures) }
}
val pickImageFromAlbumLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(20)
) { urls ->
viewModel.onReceive(Intent.OnFinishPickingImagesWith(currentContext, urls))
}
val cameraLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.TakePicture()
) { isImageSaved ->
if (isImageSaved) viewModel.onReceive(Intent.OnImageSavedWith(currentContext))
else viewModel.onReceive(Intent.OnImageSavingCanceled)
}
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { permissionGranted ->
if (permissionGranted) viewModel.onReceive(Intent.OnPermissionGrantedWith(currentContext))
else viewModel.onReceive(Intent.OnPermissionDenied)
}
fun startCamera() {
val hasPermission = currentContext.checkSelfPermission(Manifest.permission.CAMERA) ==
android.content.pm.PackageManager.PERMISSION_GRANTED
if (hasPermission) viewModel.onReceive(Intent.OnPermissionGrantedWith(currentContext))
else permissionLauncher.launch(Manifest.permission.CAMERA)
}
LaunchedEffect(viewState.tempFileUrl) {
viewState.tempFileUrl?.let { cameraLauncher.launch(it) }
}
OverlayShell(
title = "Neue Meldung",
footer = {
OutlinedButton(
onClick = {
mapViewModel.resetDraft()
viewModel.clearSelection()
onCancel()
}
) { Text("Abbrechen", color = Color.Black) }
Button(
onClick = {
mapViewModel.submitDraftToLayer()
viewModel.clearSelection()
onCancel()
},
enabled = mapViewModel.reportDraft.isValid
) { Text("Hinzufügen") }
}
) {
Text("Schadensbeschreibung:", color = Color.Black)
// Typ Dropdown
Box {
OutlinedButton(
onClick = { dropdownExpanded = true },
modifier = Modifier.fillMaxWidth()
) {
Text("Typ: ${mapViewModel.reportDraft.typ}")
}
DropdownMenu(
expanded = dropdownExpanded,
onDismissRequest = { dropdownExpanded = false }
) {
MapViewModel.DAMAGE_TYPES.forEach { typ -> // <-- Nutzt zentrale Liste
DropdownMenuItem(
text = { Text(typ) },
onClick = {
mapViewModel.updateReportDraft { copy(typ = typ) }
dropdownExpanded = false
}
)
}
}
}
// Kamera Buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(onClick = { startCamera() }, modifier = Modifier.weight(1f)) {
Text("Foto aufnehmen")
}
Button(
onClick = {
pickImageFromAlbumLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
},
modifier = Modifier.weight(1f)
) {
Text("Aus Galerie")
}
}
// Beschreibung
TextField(
value = mapViewModel.reportDraft.beschreibung,
onValueChange = { text -> mapViewModel.updateReportDraft { copy(beschreibung = text) } },
modifier = Modifier.fillMaxWidth().height(220.dp),
placeholder = { Text("Beschreibung eingeben...") },
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.White,
unfocusedContainerColor = Color.White
)
)
// Bilder Grid (body ist scrollbar, grid selbst nicht)
if (viewState.selectedPictures.isNotEmpty()) {
Text(
text = "Ausgewählte Bilder (${viewState.selectedPictures.size})",
color = Color.Black
)
LazyVerticalGrid(
columns = GridCells.Fixed(3),
userScrollEnabled = false,
modifier = Modifier.fillMaxWidth().heightIn(0.dp, 200.dp)
) {
itemsIndexed(viewState.selectedPictures) { index, picture ->
Image(
modifier = Modifier.padding(4.dp),
bitmap = picture,
contentDescription = "Bild ${index + 1}",
contentScale = ContentScale.Crop
)
}
}
}
// Position Buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Button(
onClick = {
mapViewModel.pickCurrentLocation()
}
) {
Text(if (hasPoint) "Neue Position aus Standort" else "Position aus Standort")
}
Button(
onClick = {
mapViewModel.startPickReportLocation()
onClose()
}
) {
Text(if (hasPoint) "Position neu setzen" else "Position manuell setzen")
}
}
}
}
@Composable
fun FeatureInfoOverlay(
feature: ArcGISFeature,
onClose: () -> Unit
) {
val typ = feature.attributes["Typ"].toString()
val beschreibung = feature.attributes["Beschreibung"].toString()
val id = feature.attributes["OBJECTID"].toString()
var image by remember { mutableStateOf<ImageBitmap?>(null) }
LaunchedEffect(feature) {
image = loadFirstAttachmentBitmap(feature)
}
OverlayShell(
title = "Meldung $id",
footer = {
OutlinedButton(
onClick = {
onClose()
}
) { Text("Schließen", color = Color.Black) }
}
) {
image?.let {
Image(
bitmap = it,
contentDescription = "Feature Bild",
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
contentScale = ContentScale.Crop
)
}
Text("Typ: $typ", color = Color.Black)
Text("Beschreibung:", color = Color.Black)
Text(beschreibung, color = Color.Black)
}
}
/*
suspend fun loadFirstAttachmentBitmap(
feature: ArcGISFeature
): ImageBitmap? {
@@ -467,4 +224,5 @@ suspend fun loadFirstAttachmentBitmap(
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
return bitmap.asImageBitmap()
}
*/