305 lines
11 KiB
Kotlin
305 lines
11 KiB
Kotlin
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<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
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|