- Code bereinigt

- MapView in MapSegment ausgelagert
- Punkt kann jetzt manuell gesetzt werden
- Eingaben werden erstmal im ReportDraft gespeichert
This commit is contained in:
2026-01-18 16:49:03 +01:00
parent 97d86523ab
commit 30d5a17e6e
4 changed files with 455 additions and 133 deletions

View File

@@ -2,6 +2,7 @@ package com.example.snapandsolve
import MapViewModel
import android.Manifest
import android.R.attr.enabled
import android.app.Application
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
@@ -17,6 +18,8 @@ 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
@@ -25,6 +28,7 @@ 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.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
@@ -44,10 +48,30 @@ import kotlinx.coroutines.Dispatchers
@Composable
fun MainScreen(modifier: Modifier = Modifier, application: Application) {
var showReport by rememberSaveable { mutableStateOf(false) }
var sliderOpen by remember { 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
}
LaunchedEffect(mapViewModel.reopenReport) {
if (mapViewModel.reopenReport) {
showReport = true
mapViewModel.consumeReopenReport()
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = { AppTopBar() },
@@ -64,8 +88,7 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) {
floatingActionButton = {
LargeFloatingActionButton(
onClick = {
showReport = true
sliderOpen = false
openReport()
},
modifier = Modifier.offset(y = 64.dp),
containerColor = ButtonColor
@@ -77,11 +100,11 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) {
) { innerPadding ->
ContentScreen(
modifier = Modifier.padding(innerPadding),
mapViewModel,
albumViewModel,
mapViewModel = mapViewModel,
albumViewModel = albumViewModel,
showReport = showReport,
sliderOpen = sliderOpen,
onDismissReport = { showReport = false }
onDismissReport = ::closeReport
)
}
}
@@ -115,7 +138,9 @@ fun ContentScreen(
)
onDismissReport()
},
viewModel = albumViewModel
onClose = onDismissReport,
viewModel = albumViewModel,
mapViewModel = mapViewModel
)
}
@@ -166,16 +191,21 @@ fun AppTopBar(
fun ReportOverlay(
onCancel: () -> Unit,
onAdd: (beschreibung: String, typ: String) -> Unit,
viewModel: AlbumViewModel
onClose: () -> Unit,
viewModel: AlbumViewModel,
mapViewModel: MapViewModel
) {
val viewState: AlbumViewState by viewModel.viewStateFlow.collectAsState()
val currentContext = LocalContext.current
// State für Beschreibung und Typ
var beschreibung by remember { mutableStateOf("") }
var selectedTyp by remember { mutableStateOf("Schadenstyp wählen...") }
val hasPoint = mapViewModel.reportDraft.point != null
var dropdownExpanded by remember { mutableStateOf(false) }
LaunchedEffect(viewState.selectedPictures) {
mapViewModel.updateReportDraft {
copy(photos = viewState.selectedPictures)
}
}
// Launcher für Bildauswahl aus Galerie
val pickImageFromAlbumLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(20)
@@ -229,136 +259,187 @@ fun ReportOverlay(
.background(Color.Black.copy(alpha = 0.25f)),
contentAlignment = Alignment.Center
) {
Card(
modifier = Modifier
.fillMaxWidth(0.9f)
.heightIn(min = 400.dp),
shape = RoundedCornerShape(24.dp),
colors = CardColors(
containerColor = WidgetColor,
contentColor = ButtonColor,
disabledContainerColor = Color.White,
disabledContentColor = Color.White
)
) {
Column(
BoxWithConstraints {
val verticalMargin = 50.dp // <-- dein gewünschter Mindestabstand oben + unten
val maxCardHeight = maxHeight - verticalMargin * 2
Card(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
.fillMaxWidth(0.9f)
.heightIn(
min = 400.dp,
max = maxCardHeight
),
shape = RoundedCornerShape(24.dp),
colors = CardColors(
containerColor = WidgetColor,
contentColor = ButtonColor,
disabledContainerColor = Color.White,
disabledContentColor = Color.White
)
) {
Text("Schadensbeschreibung:", color = Color.Black)
// Typ-Auswahl (Dropdown)
Box {
OutlinedButton(
onClick = { dropdownExpanded = true },
modifier = Modifier.fillMaxWidth()
) {
Text("Typ: $selectedTyp")
}
DropdownMenu(
expanded = dropdownExpanded,
onDismissRequest = { dropdownExpanded = false }
) {
listOf("Straße", "Gehweg", "Fahrradweg", "Beleuchtung","Sonstiges").forEach { typ ->
DropdownMenuItem(
text = { Text(typ) },
onClick = {
selectedTyp = typ
dropdownExpanded = false
}
)
}
}
}
// Kamera-Buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = { startCamera() },
modifier = Modifier.weight(1f)
) {
Text(text = "Foto aufnehmen")
}
Button(
onClick = {
pickImageFromAlbumLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
},
modifier = Modifier.weight(1f)
) {
Text(text = "Aus Galerie")
}
}
// Textfeld für Beschreibung
TextField(
value = beschreibung,
onValueChange = { beschreibung = it },
Column(
modifier = Modifier
.fillMaxWidth()
.height(220.dp),
placeholder = { Text("Beschreibung eingeben...") },
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.White,
unfocusedContainerColor = Color.White
)
)
.padding(20.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text("Schadensbeschreibung:", color = Color.Black)
// Bilder-Grid
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
)
// Typ-Auswahl (Dropdown)
Box {
OutlinedButton(
onClick = { dropdownExpanded = true },
modifier = Modifier.fillMaxWidth()
) {
Text("Typ: ${mapViewModel.reportDraft.typ}")
}
DropdownMenu(
expanded = dropdownExpanded,
onDismissRequest = { dropdownExpanded = false }
) {
listOf(
"Straße",
"Gehweg",
"Fahrradweg",
"Beleuchtung",
"Sonstiges"
).forEach { typ ->
DropdownMenuItem(
text = {
Text(typ)
},
onClick = {
mapViewModel.updateReportDraft {
copy(typ = typ)
}
dropdownExpanded = false
}
)
}
}
}
}
// Buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
OutlinedButton(
onClick = onCancel,
colors = ButtonColors(
containerColor = ButtonColor,
contentColor = Color.White,
disabledContainerColor = Color.White,
disabledContentColor = Color.White
)
// Kamera-Buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text("Abbrechen", color = Color.Black)
Button(
onClick = { startCamera() },
modifier = Modifier.weight(1f)
) {
Text(text = "Foto aufnehmen")
}
Button(
onClick = {
pickImageFromAlbumLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
},
modifier = Modifier.weight(1f)
) {
Text(text = "Aus Galerie")
}
}
Button(
onClick = { onAdd(beschreibung, selectedTyp) },
enabled = beschreibung.isNotBlank()
// Textfeld für Beschreibung
TextField(
value = mapViewModel.reportDraft.beschreibung,
onValueChange = {
mapViewModel.updateReportDraft {
copy(beschreibung = it)
}
},
modifier = Modifier
.fillMaxWidth()
.height(220.dp),
placeholder = { Text("Beschreibung eingeben...") },
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.White,
unfocusedContainerColor = Color.White
)
)
// Bilder-Grid
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
)
}
}
}
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text("Hinzufügen")
Button(
onClick = {
},
) {
Text("Position aus Standort")
}
Button(
onClick = {
mapViewModel.startPickReportLocation()
onClose()
},
) {
Text(if (hasPoint) "Position neu setzen" else "Position manuell setzen")
}
}
// Buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
OutlinedButton(
onClick = {
mapViewModel.resetDraft()
viewModel.clearSelection()
onCancel()
},
colors = ButtonColors(
containerColor = ButtonColor,
contentColor = Color.White,
disabledContainerColor = Color.White,
disabledContentColor = Color.White
)
) {
Text("Abbrechen", color = Color.Black)
}
Button(
onClick = {
mapViewModel.submitDraftToLayer()
viewModel.clearSelection()
onCancel()
},
enabled = mapViewModel.reportDraft.isValid
) {
Text("Hinzufügen")
}
}
}
}
}
}
}
}