Attachments hinzufügen

This commit is contained in:
2025-12-11 10:43:06 +01:00
parent bd66f298e0
commit 0c045c8c91
8 changed files with 359 additions and 8 deletions

View File

@@ -1,12 +1,26 @@
package de.jadehs.strassenschadenpro2.pages
import android.util.Log
import android.Manifest
import android.content.Context
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.layout.Arrangement
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.padding
import androidx.compose.foundation.layout.width
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.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
@@ -14,11 +28,14 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
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.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
@@ -28,8 +45,12 @@ import com.arcgismaps.location.LocationDisplayAutoPanMode
import com.arcgismaps.toolkit.geoviewcompose.MapView
import com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplay
import com.google.android.gms.location.LocationServices
import de.jadehs.strassenschadenpro2.AlbumViewModel
import de.jadehs.strassenschadenpro2.MapViewModel
import de.jadehs.strassenschadenpro2.components.DropDownMenuBox
import de.jadehs.strassenschadenpro2.components.album.AlbumViewState
import de.jadehs.strassenschadenpro2.components.album.Intent
import de.jadehs.strassenschadenpro2.components.album.PhotoData
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@@ -43,12 +64,16 @@ fun CreatePage(modifier: Modifier = Modifier, mapViewModel: MapViewModel) {
var enableSaveButton = remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
var albumViewModel = remember { AlbumViewModel(coroutineScope.coroutineContext) }
val locationDisplay = rememberLocationDisplay().apply {
setAutoPanMode(LocationDisplayAutoPanMode.Off)
}
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
if (checkPermissions(context)) {
// Permissions are already granted.
LaunchedEffect(Unit) {
@@ -68,6 +93,7 @@ fun CreatePage(modifier: Modifier = Modifier, mapViewModel: MapViewModel) {
val fuesedLocationClient = remember { LocationServices.getFusedLocationProviderClient(context) }
Column(modifier = modifier.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(top = 16.dp, start = 16.dp, end = 16.dp, bottom = 16.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally) {
@@ -92,6 +118,7 @@ fun CreatePage(modifier: Modifier = Modifier, mapViewModel: MapViewModel) {
enableSaveButton.value = beschreibungTextFieldValue.value.text.isNotEmpty() && currentDamageType.value.isNotEmpty()
}
)
AlbumScreen(modifier= Modifier, viewModel = albumViewModel)
Row(modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly){
Button(enabled = enableSaveButton.value,
@@ -103,7 +130,8 @@ fun CreatePage(modifier: Modifier = Modifier, mapViewModel: MapViewModel) {
var point = Point(location.longitude,
location.latitude,
SpatialReference.wgs84())
mapViewModel.createFeatureWithAttributesAtPoint(point,beschreibung,typ)
val photoDataList = getPhotosFromAlbum(context,albumViewModel)
mapViewModel.createFeatureWithAttributesAtPoint(point,beschreibung,typ,photoDataList)
}
}
}){
@@ -133,10 +161,140 @@ fun CreatePage(modifier: Modifier = Modifier, mapViewModel: MapViewModel) {
val screenCoordinate = singleTapConfirmedEvent.screenCoordinate
val beschreibung = beschreibungTextFieldValue.value.text
val typ = currentDamageType.value
mapViewModel.createFeatureWithAttributesAt(screenCoordinate,beschreibung,typ)
val photoDataList=getPhotosFromAlbum(context,albumViewModel)
mapViewModel.createFeatureWithAttributesAt(
screenCoordinate,
beschreibung,
typ,
photoDataList
)
},
)
}
}
}
}
@Composable
fun AlbumScreen(modifier: Modifier = Modifier, viewModel: AlbumViewModel) {
// collecting the flow from the view model as a state allows our ViewModel and View
// to be in sync with each other.
val viewState: AlbumViewState by viewModel.viewStateFlow.collectAsState()
val currentContext = LocalContext.current
// launches photo picker
val pickImageFromAlbumLauncher = rememberLauncherForActivityResult(ActivityResultContracts.PickMultipleVisualMedia()) { urls ->
viewModel.onReceive(Intent.OnFinishPickingImagesWith(currentContext, urls))
}
// launches camera
val cameraLauncher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) { isImageSaved ->
if (isImageSaved) {
viewModel.onReceive(Intent.OnImageSavedWith(currentContext))
} else {
// handle image saving error or cancellation
viewModel.onReceive(Intent.OnImageSavingCanceled)
}
}
// launches camera permissions
val permissionLauncher = rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { permissionGranted ->
if (permissionGranted) {
viewModel.onReceive(Intent.OnPermissionGrantedWith(currentContext))
} else {
// handle permission denied such as:
viewModel.onReceive(Intent.OnPermissionDenied)
}
}
// this ensures that the camera is launched only once when the url of the temp file changes
LaunchedEffect(key1 = viewState.tempFileUrl) {
viewState.tempFileUrl?.let {
cameraLauncher.launch(it)
}
}
// basic view that has 2 buttons and a grid for selected pictures
Column(modifier = Modifier.padding(20.dp),
horizontalAlignment = Alignment.CenterHorizontally) {
Row {
Button(onClick = {
// get user's permission first to use camera
permissionLauncher.launch(Manifest.permission.CAMERA)
}) {
Text(text = "Take a photo")
}
Spacer(modifier = Modifier.width(16.dp))
Button(onClick = {
// Image picker does not require special permissions and can be activated right away
val mediaRequest = PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
pickImageFromAlbumLauncher.launch(mediaRequest)
}) {
Text(text = "Pick a picture")
}
}
Spacer(modifier = Modifier.height(16.dp))
Text(text = "Selected Pictures")
LazyVerticalGrid(modifier = Modifier.fillMaxWidth().heightIn(0.dp, 1200.dp),
columns = GridCells.Adaptive(150.dp),
userScrollEnabled = false) {
itemsIndexed(viewState.selectedPictures) { index, picture ->
Image(modifier = Modifier.padding(8.dp),
bitmap = picture,
contentDescription = null,
contentScale = ContentScale.FillWidth
)
}
}
}
}
fun getPhotosFromAlbum(context: Context,albumViewModel: AlbumViewModel): List<PhotoData>{
var photoDataList = mutableListOf<PhotoData>()
albumViewModel.viewStateFlow.value.imageUris.forEach { uri ->
context.contentResolver.openInputStream(uri).use { inputStream ->
try {
val name = "image_${System.currentTimeMillis()}.png"
val contentType = "image/png"
val byteArray = inputStream?.readBytes()
if (byteArray!=null) {
photoDataList.add(
PhotoData(
name,
contentType,
byteArray
)
)
}
}catch (e: Exception){
}
}
}
return photoDataList
}