package de.jadehs.strassenschadenpro2.pages 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 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 import com.arcgismaps.geometry.Point import com.arcgismaps.geometry.SpatialReference 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) @Composable fun CreatePage(modifier: Modifier = Modifier, mapViewModel: MapViewModel) { var beschreibungTextFieldValue = remember { mutableStateOf(TextFieldValue("")) } var currentDamageType = remember { mutableStateOf("")} var showModalBottomSheet = remember { mutableStateOf(false) } 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 if (checkPermissions(context)) { // Permissions are already granted. LaunchedEffect(Unit) { locationDisplay.dataSource.start() } } else { RequestPermissions( context = context, onPermissionsGranted = { coroutineScope.launch { locationDisplay.dataSource.start() } } ) } 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) { OutlinedTextField( modifier = Modifier.fillMaxWidth(), value = beschreibungTextFieldValue.value, onValueChange = { beschreibungTextFieldValue.value = it enableSaveButton.value = beschreibungTextFieldValue.value.text.isNotEmpty() && currentDamageType.value.isNotEmpty() }, minLines = 5, label = { Text("Beschreibung")} ) DropDownMenuBox( modifier = Modifier .padding(8.dp), textFieldLabel = "Select damage type", textFieldValue = currentDamageType.value, dropDownItemList = mapViewModel.damageTypeList, onIndexSelected = { index -> currentDamageType.value = mapViewModel.damageTypeList[index] 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, onClick = { fuesedLocationClient.lastLocation.addOnSuccessListener { location -> location?.let { location-> val beschreibung = beschreibungTextFieldValue.value.text val typ = currentDamageType.value var point = Point(location.longitude, location.latitude, SpatialReference.wgs84()) val photoDataList = getPhotosFromAlbum(context,albumViewModel) mapViewModel.createFeatureWithAttributesAtPoint( point, beschreibung, typ, photoDataList) } } }){ Text("GPS") } Button(enabled = enableSaveButton.value, onClick = { showModalBottomSheet.value = !showModalBottomSheet.value }){ Text("Karte") } } } if(showModalBottomSheet.value) { ModalBottomSheet( sheetGesturesEnabled = false, onDismissRequest = { showModalBottomSheet.value = false } ) { MapView( modifier = Modifier, arcGISMap = mapViewModel.map, locationDisplay = locationDisplay, mapViewProxy = mapViewModel.mapViewProxy, onSingleTapConfirmed = { singleTapConfirmedEvent -> val screenCoordinate = singleTapConfirmedEvent.screenCoordinate val beschreibung = beschreibungTextFieldValue.value.text val typ = currentDamageType.value 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{ var photoDataList = mutableListOf() 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 }