- Featurelayer ArcGIS hinzugefügt

- Textbox jetzt nutzbar
- Schadenauswahl Dropdown Menü
- Featureerstellung nimmt eigene Position und wird hinzugefügt.
This commit is contained in:
2026-01-07 18:03:50 +01:00
parent 91b885f67c
commit f5ac96807c
4 changed files with 245 additions and 164 deletions

View File

@@ -1,5 +1,6 @@
package com.example.snapandsolve
import MapViewModel
import android.Manifest
import android.app.Application
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -7,17 +8,7 @@ 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.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
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
@@ -26,110 +17,62 @@ 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.Filter
import androidx.compose.material.icons.filled.FilterAlt
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeFloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
// 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.AppColor
import com.example.snapandsolve.ui.theme.ButtonColor
import com.example.snapandsolve.ui.theme.SideSlider
import com.example.snapandsolve.ui.theme.SliderMenuItem
import com.example.snapandsolve.ui.theme.WidgetColor
import com.example.snapandsolve.ui.theme.setupLocationDisplay
import com.example.snapandsolve.ui.theme.*
import kotlinx.coroutines.Dispatchers
@Composable
fun MainScreen(modifier: Modifier = Modifier, application: Application) {
var showReport by rememberSaveable { mutableStateOf(false) }
var sliderOpen by remember { mutableStateOf(false) }
// ViewModel Initialisierung
val mapViewModel = remember { MapViewModel(application) }
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
AppTopBar()
},
topBar = { AppTopBar() },
bottomBar = {
BottomAppBar(
modifier = Modifier.height(120.dp),
containerColor = AppColor,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = {
sliderOpen = !sliderOpen
showReport = false
},
modifier = Modifier.padding(bottom = 8.dp)
) {
Icon(
Icons.Default.Menu,
contentDescription = "Menu",
)
}
Spacer(Modifier.weight(1f))
IconButton(onClick = { sliderOpen = !sliderOpen; showReport = false }) {
Icon(Icons.Default.Menu, contentDescription = "Menu")
}
}
},
floatingActionButton = {
LargeFloatingActionButton(
onClick = {
showReport = true
sliderOpen = false
},
onClick = { showReport = true; sliderOpen = false },
modifier = Modifier.offset(y = 64.dp),
containerColor = ButtonColor
) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
) { Icon(Icons.Default.Add, contentDescription = "Add") }
},
floatingActionButtonPosition = FabPosition.Center,
) { innerPadding ->
ContentScreen(
modifier = Modifier.padding(innerPadding),
application,
mapViewModel = mapViewModel,
showReport = showReport,
sliderOpen = sliderOpen,
onDismissReport = { showReport = false })
@@ -139,54 +82,54 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) {
@Composable
fun ContentScreen(
modifier: Modifier = Modifier,
application: Application,
mapViewModel: MapViewModel,
showReport: Boolean,
sliderOpen: Boolean,
onDismissReport: () -> Unit
) {
val mapViewModel = remember { MapViewModel(application) }
val albumViewModel = remember { AlbumViewModel(Dispatchers.Default) }
// ArcGIS Map erstellen
val map = remember {
createMap() //Funktion zur Erstellung der Map
}
//Standortbestimmung aus locationHelper.kt
val locationDisplay = setupLocationDisplay()
// VERBINDUNG ZUM VIEWMODEL (Hier wird das GPS-Werkzeug übergeben)
LaunchedEffect(locationDisplay) {
mapViewModel.locationDisplay = locationDisplay
}
Box(modifier = modifier.fillMaxSize()) {
// HINTERGRUND: Die Map
MapView(
modifier = Modifier.fillMaxSize(),
arcGISMap = map,
arcGISMap = mapViewModel.map,
mapViewProxy = mapViewModel.mapViewProxy,
locationDisplay = locationDisplay
)
// VORDERGRUND: Das Overlay (wenn showReport = true)
if (showReport) {
ReportOverlay(
onCancel = onDismissReport,
onAdd = { /* später */ },
onAdd = { beschreibung, typ ->
mapViewModel.createFeatureAtCurrentLocation(
beschreibung = beschreibung,
typ = typ,
photos = albumViewModel.viewStateFlow.value.selectedPictures
)
onDismissReport()
},
viewModel = albumViewModel
)
}
// RECHTSGRUND: Das Slider
SideSlider(visible = sliderOpen) {
Text(
"Menü",
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 12.dp)
)
SliderMenuItem(text = "Schäden filtern", icon = Icons.Default.FilterAlt, onClick = {})
}
SliderMenuItem(
text = "Schäden filtern",
icon = Icons.Default.FilterAlt,
onClick = {
/* TODO */
}
)
if (mapViewModel.snackBarMessage.isNotEmpty()) {
Snackbar(modifier = Modifier.align(Alignment.BottomCenter).padding(bottom = 80.dp)) {
Text(mapViewModel.snackBarMessage)
}
LaunchedEffect(mapViewModel.snackBarMessage) {
kotlinx.coroutines.delay(4000)
mapViewModel.snackBarMessage = ""
}
}
}
}
@@ -210,12 +153,17 @@ fun AppTopBar(
@Composable
fun ReportOverlay(
onCancel: () -> Unit,
onAdd: () -> Unit,
onAdd: (beschreibung: String, typ: String) -> Unit,
viewModel: AlbumViewModel
) {
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...") }
var dropdownExpanded by remember { mutableStateOf(false) }
// Launcher für Bildauswahl aus Galerie
val pickImageFromAlbumLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(20)
@@ -245,19 +193,13 @@ fun ReportOverlay(
}
}
// Funktion zum Starten der Kamera (prüft Berechtigung)
// Funktion zum Starten der Kamera
fun startCamera() {
println("DEBUG: startCamera() aufgerufen")
val hasPermission = currentContext.checkSelfPermission(Manifest.permission.CAMERA) ==
android.content.pm.PackageManager.PERMISSION_GRANTED
println("DEBUG: Hat Berechtigung? $hasPermission")
if (hasPermission) {
println("DEBUG: Erstelle tempFileUrl")
// Berechtigung bereits erteilt -> direkt tempFileUrl erstellen
viewModel.onReceive(Intent.OnPermissionGrantedWith(currentContext))
} else {
println("DEBUG: Frage Berechtigung an")
// Berechtigung anfragen
permissionLauncher.launch(Manifest.permission.CAMERA)
}
}
@@ -269,7 +211,6 @@ fun ReportOverlay(
}
}
// leichter Dim-Hintergrund
Box(
modifier = Modifier
.fillMaxSize()
@@ -295,20 +236,39 @@ fun ReportOverlay(
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
"Schadensbeschreibung:",
color = Color.Black
)
Text("Schadensbeschreibung:", color = Color.Black)
// Kamera-Buttons (über der weißen Box)
// 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()
},
onClick = { startCamera() },
modifier = Modifier.weight(1f)
) {
Text(text = "Foto aufnehmen")
@@ -325,15 +285,21 @@ fun ReportOverlay(
}
}
// Die weiße Textfeld Box
Box(
// Textfeld für Beschreibung
TextField(
value = beschreibung,
onValueChange = { beschreibung = it },
modifier = Modifier
.fillMaxWidth()
.height(220.dp)
.background(Color.White, RoundedCornerShape(12.dp))
.height(220.dp),
placeholder = { Text("Beschreibung eingeben...") },
colors = TextFieldDefaults.colors(
focusedContainerColor = Color.White,
unfocusedContainerColor = Color.White
)
)
// Grid für ausgewählte Bilder (außerhalb der Box, aber in der Card)
// Bilder-Grid
if (viewState.selectedPictures.isNotEmpty()) {
Text(
text = "Ausgewählte Bilder (${viewState.selectedPictures.size})",
@@ -357,6 +323,7 @@ fun ReportOverlay(
}
}
// Buttons
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
@@ -370,31 +337,22 @@ fun ReportOverlay(
disabledContentColor = Color.White
)
) {
Text(
"Abbrechen",
color = Color.Black
)
Text("Abbrechen", color = Color.Black)
}
Button(
onClick = { onAdd(beschreibung, selectedTyp) },
enabled = beschreibung.isNotBlank()
) {
Text("Hinzufügen")
}
Button(onClick = onAdd) { Text("Hinzufügen") }
}
}
}
}
}
fun createMap(): ArcGISMap {
return ArcGISMap(BasemapStyle.ArcGISTopographic).apply {
initialViewpoint = Viewpoint(
53.14,
8.20,
20000.0)
initialViewpoint = Viewpoint(53.14, 8.20, 20000.0)
}
}