validation
This commit is contained in:
parent
bcb4ee1a39
commit
d45093a355
|
|
@ -0,0 +1,114 @@
|
||||||
|
package com.example.livingai.domain.ml
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import com.google.mlkit.vision.common.InputImage
|
||||||
|
import com.google.mlkit.vision.segmentation.subject.SubjectSegmentation
|
||||||
|
import com.google.mlkit.vision.segmentation.subject.SubjectSegmenterOptions
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import java.io.OutputStream
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
|
||||||
|
class SubjectSegmenterHelper(private val context: Context) {
|
||||||
|
|
||||||
|
suspend fun segmentAndSave(inputUri: Uri): Uri? {
|
||||||
|
return suspendCancellableCoroutine { continuation ->
|
||||||
|
try {
|
||||||
|
val image = InputImage.fromFilePath(context, inputUri)
|
||||||
|
val options = SubjectSegmenterOptions.Builder()
|
||||||
|
.enableForegroundBitmap()
|
||||||
|
.build()
|
||||||
|
val segmenter = SubjectSegmentation.getClient(options)
|
||||||
|
|
||||||
|
segmenter.process(image)
|
||||||
|
.addOnSuccessListener { result ->
|
||||||
|
val foreground = result.foregroundBitmap
|
||||||
|
if (foreground != null) {
|
||||||
|
try {
|
||||||
|
val resultBitmap = Bitmap.createBitmap(
|
||||||
|
foreground.width,
|
||||||
|
foreground.height,
|
||||||
|
Bitmap.Config.ARGB_8888
|
||||||
|
)
|
||||||
|
val canvas = Canvas(resultBitmap)
|
||||||
|
canvas.drawColor(Color.BLACK)
|
||||||
|
canvas.drawBitmap(foreground, 0f, 0f, null)
|
||||||
|
|
||||||
|
val originalName = getFileName(inputUri) ?: "image"
|
||||||
|
val nameWithoutExt = originalName.substringBeforeLast('.')
|
||||||
|
val baseName = nameWithoutExt.replace(Regex("_segmented.*"), "")
|
||||||
|
val filename = "${baseName}_segmented_${System.currentTimeMillis()}.jpg"
|
||||||
|
|
||||||
|
val contentValues = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/LivingAI/Segmented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val uri = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||||
|
|
||||||
|
if (uri != null) {
|
||||||
|
val outputStream: OutputStream? = context.contentResolver.openOutputStream(uri)
|
||||||
|
outputStream?.use { out ->
|
||||||
|
resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out)
|
||||||
|
}
|
||||||
|
continuation.resume(uri)
|
||||||
|
} else {
|
||||||
|
continuation.resume(null)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
continuation.resumeWithException(e)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continuation.resume(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.addOnFailureListener { e ->
|
||||||
|
continuation.resumeWithException(e)
|
||||||
|
}
|
||||||
|
.addOnCompleteListener {
|
||||||
|
segmenter.close()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
continuation.resumeWithException(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFileName(uri: Uri): String? {
|
||||||
|
var result: String? = null
|
||||||
|
if (uri.scheme == "content") {
|
||||||
|
val cursor = context.contentResolver.query(uri, null, null, null, null)
|
||||||
|
try {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
||||||
|
if (index >= 0) {
|
||||||
|
result = cursor.getString(index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// ignore
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = uri.path
|
||||||
|
val cut = result?.lastIndexOf('/')
|
||||||
|
if (cut != null && cut != -1) {
|
||||||
|
result = result?.substring(cut + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ import androidx.lifecycle.viewModelScope
|
||||||
import com.example.livingai.domain.model.AnimalDetails
|
import com.example.livingai.domain.model.AnimalDetails
|
||||||
import com.example.livingai.domain.usecases.GetAnimalDetails
|
import com.example.livingai.domain.usecases.GetAnimalDetails
|
||||||
import com.example.livingai.domain.usecases.ProfileEntry.ProfileEntryUseCase
|
import com.example.livingai.domain.usecases.ProfileEntry.ProfileEntryUseCase
|
||||||
|
import com.example.livingai.utils.Constants
|
||||||
import com.example.livingai.utils.CoroutineDispatchers
|
import com.example.livingai.utils.CoroutineDispatchers
|
||||||
import com.example.livingai.utils.IdGenerator
|
import com.example.livingai.utils.IdGenerator
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
|
@ -102,10 +103,45 @@ class AddProfileViewModel(
|
||||||
val uri = Uri.parse(path)
|
val uri = Uri.parse(path)
|
||||||
val filename = getFileName(uri) ?: path.substringAfterLast('/')
|
val filename = getFileName(uri) ?: path.substringAfterLast('/')
|
||||||
val nameWithoutExt = filename.substringBeforeLast('.')
|
val nameWithoutExt = filename.substringBeforeLast('.')
|
||||||
|
|
||||||
|
// Find orientation in filename
|
||||||
|
var foundOrientation: String? = null
|
||||||
|
for (o in Constants.silhouetteList) {
|
||||||
|
// Check if filename contains the orientation string
|
||||||
|
// We use ignoreCase just in case, though Constants are lowercase
|
||||||
|
if (nameWithoutExt.contains(o, ignoreCase = true)) {
|
||||||
|
// If we found a match, we verify it's not a substring of another word if possible,
|
||||||
|
// but here the orientations are quite distinct (front, back, left, right, angleview).
|
||||||
|
// To be safer, we could check for delimiters, but usually containment is enough for now.
|
||||||
|
foundOrientation = o
|
||||||
|
// Prioritize exact matches or longer matches if necessary?
|
||||||
|
// "left" is in "leftangle". "leftangle" should be matched first if we iterate in order?
|
||||||
|
// Constants list: front, back, left, right, leftangle...
|
||||||
|
// If file is "leftangle", it matches "left".
|
||||||
|
// We should probably check longer keys first or exact match between delimiters.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Better approach: Split by underscore and check exact match against list
|
||||||
val parts = nameWithoutExt.split('_')
|
val parts = nameWithoutExt.split('_')
|
||||||
if (parts.size >= 2) {
|
val matchingPart = parts.find { part ->
|
||||||
val orientation = parts.last()
|
Constants.silhouetteList.any { it.equals(part, ignoreCase = true) }
|
||||||
photoMap[orientation] = path
|
}
|
||||||
|
|
||||||
|
if (matchingPart != null) {
|
||||||
|
// Normalize to the key in Constants (lowercase)
|
||||||
|
val key = Constants.silhouetteList.find { it.equals(matchingPart, ignoreCase = true) }
|
||||||
|
if (key != null) {
|
||||||
|
photoMap[key] = path
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback to substring search if underscore splitting fails (e.g. if naming changed)
|
||||||
|
// We sort by length descending so "leftangle" is checked before "left"
|
||||||
|
val sortedOrientations = Constants.silhouetteList.sortedByDescending { it.length }
|
||||||
|
val match = sortedOrientations.find { nameWithoutExt.contains(it, ignoreCase = true) }
|
||||||
|
if (match != null) {
|
||||||
|
photoMap[match] = path
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
withContext(dispatchers.main) {
|
withContext(dispatchers.main) {
|
||||||
|
|
@ -187,7 +223,7 @@ class AddProfileViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
val ageInt = age.value.toIntOrNull()
|
val ageInt = age.value.toIntOrNull()
|
||||||
if (ageInt == null || ageInt < 0) {
|
if (ageInt == null || ageInt <= 0 || ageInt > 20) {
|
||||||
ageError.value = "Invalid age"
|
ageError.value = "Invalid age"
|
||||||
isValid = false
|
isValid = false
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -195,7 +231,7 @@ class AddProfileViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
val milkInt = milkYield.value.toIntOrNull()
|
val milkInt = milkYield.value.toIntOrNull()
|
||||||
if (milkInt == null || milkInt < 0) {
|
if (milkInt == null || milkInt <= 0 || milkInt > 75) {
|
||||||
milkYieldError.value = "Invalid milk yield"
|
milkYieldError.value = "Invalid milk yield"
|
||||||
isValid = false
|
isValid = false
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -203,7 +239,7 @@ class AddProfileViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
val calvingInt = calvingNumber.value.toIntOrNull()
|
val calvingInt = calvingNumber.value.toIntOrNull()
|
||||||
if (calvingInt == null || calvingInt < 0) {
|
if (calvingInt == null || calvingInt < 0 || calvingInt > 12) {
|
||||||
calvingNumberError.value = "Invalid calving number"
|
calvingNumberError.value = "Invalid calving number"
|
||||||
isValid = false
|
isValid = false
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -213,8 +249,8 @@ class AddProfileViewModel(
|
||||||
return isValid
|
return isValid
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveAnimalDetails() {
|
fun saveAnimalDetails(): Boolean {
|
||||||
if (!validateInputs()) return
|
if (!validateInputs()) return false
|
||||||
|
|
||||||
val id = _currentAnimalId.value ?: IdGenerator.generateAnimalId().also { _currentAnimalId.value = it }
|
val id = _currentAnimalId.value ?: IdGenerator.generateAnimalId().also { _currentAnimalId.value = it }
|
||||||
|
|
||||||
|
|
@ -236,6 +272,8 @@ class AddProfileViewModel(
|
||||||
profileEntryUseCase.setAnimalDetails(details)
|
profileEntryUseCase.setAnimalDetails(details)
|
||||||
_saveSuccess.value = true
|
_saveSuccess.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onSaveComplete() {
|
fun onSaveComplete() {
|
||||||
|
|
@ -243,9 +281,7 @@ class AddProfileViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Try to auto-load when editing via saved state handle
|
|
||||||
val animalId: String? = savedStateHandle?.get<String>("animalId")
|
val animalId: String? = savedStateHandle?.get<String>("animalId")
|
||||||
// Call loadAnimal unconditionally to ensure ID generation for new profiles
|
|
||||||
loadAnimal(animalId)
|
loadAnimal(animalId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,8 @@ import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
|
@ -22,6 +19,7 @@ import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -34,9 +32,9 @@ import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import coil.compose.rememberAsyncImagePainter
|
import coil.compose.rememberAsyncImagePainter
|
||||||
|
import com.example.livingai.domain.ml.SubjectSegmenterHelper
|
||||||
import com.example.livingai.ui.theme.LivingAITheme
|
import com.example.livingai.ui.theme.LivingAITheme
|
||||||
import java.io.InputStream
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -45,17 +43,25 @@ fun ViewImageScreen(
|
||||||
shouldAllowRetake: Boolean,
|
shouldAllowRetake: Boolean,
|
||||||
showAccept: Boolean,
|
showAccept: Boolean,
|
||||||
showBack: Boolean,
|
showBack: Boolean,
|
||||||
|
showSegment: Boolean = false,
|
||||||
onRetake: () -> Unit,
|
onRetake: () -> Unit,
|
||||||
onAccept: () -> Unit,
|
onAccept: (String) -> Unit,
|
||||||
|
onSegmented: (String) -> Unit = {},
|
||||||
onBack: () -> Unit
|
onBack: () -> Unit
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
var boundingBox by remember { mutableStateOf<String?>(null) }
|
var boundingBox by remember { mutableStateOf<String?>(null) }
|
||||||
var imageWidth by remember { mutableStateOf(0f) }
|
var imageWidth by remember { mutableStateOf(0f) }
|
||||||
var imageHeight by remember { mutableStateOf(0f) }
|
var imageHeight by remember { mutableStateOf(0f) }
|
||||||
|
|
||||||
LaunchedEffect(imageUri) {
|
val displayedUri = Uri.parse(imageUri)
|
||||||
val uri = Uri.parse(imageUri)
|
var isSegmenting by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val segmenterHelper = remember { SubjectSegmenterHelper(context) }
|
||||||
|
|
||||||
|
LaunchedEffect(displayedUri) {
|
||||||
|
val uri = displayedUri
|
||||||
try {
|
try {
|
||||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||||
val exif = ExifInterface(inputStream)
|
val exif = ExifInterface(inputStream)
|
||||||
|
|
@ -79,9 +85,13 @@ fun ViewImageScreen(
|
||||||
Box(modifier = Modifier
|
Box(modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(it)) {
|
.padding(it)) {
|
||||||
|
|
||||||
|
if (isSegmenting) {
|
||||||
|
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
|
||||||
|
} else {
|
||||||
Image(
|
Image(
|
||||||
painter = rememberAsyncImagePainter(
|
painter = rememberAsyncImagePainter(
|
||||||
model = Uri.parse(imageUri),
|
model = displayedUri,
|
||||||
onSuccess = { state ->
|
onSuccess = { state ->
|
||||||
if (imageWidth == 0f || imageHeight == 0f) {
|
if (imageWidth == 0f || imageHeight == 0f) {
|
||||||
imageWidth = state.result.drawable.intrinsicWidth.toFloat()
|
imageWidth = state.result.drawable.intrinsicWidth.toFloat()
|
||||||
|
|
@ -110,7 +120,6 @@ fun ViewImageScreen(
|
||||||
val bottom = parts[3].toFloatOrNull() ?: 0f
|
val bottom = parts[3].toFloatOrNull() ?: 0f
|
||||||
|
|
||||||
// Calculate scale and offset (CenterInside/Fit logic)
|
// Calculate scale and offset (CenterInside/Fit logic)
|
||||||
// Image composable uses 'Fit' by default which scales to fit within bounds while maintaining aspect ratio
|
|
||||||
val widthRatio = canvasWidth / imageWidth
|
val widthRatio = canvasWidth / imageWidth
|
||||||
val heightRatio = canvasHeight / imageHeight
|
val heightRatio = canvasHeight / imageHeight
|
||||||
val scale = min(widthRatio, heightRatio)
|
val scale = min(widthRatio, heightRatio)
|
||||||
|
|
@ -136,6 +145,7 @@ fun ViewImageScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -144,16 +154,36 @@ fun ViewImageScreen(
|
||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
) {
|
) {
|
||||||
if (shouldAllowRetake) {
|
if (shouldAllowRetake && !isSegmenting) {
|
||||||
OutlinedButton(onClick = onRetake) {
|
OutlinedButton(onClick = onRetake) {
|
||||||
Text("Retake")
|
Text("Retake")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isSegmenting) {
|
||||||
|
if (showSegment) {
|
||||||
|
Button(onClick = {
|
||||||
|
scope.launch {
|
||||||
|
isSegmenting = true
|
||||||
|
val resultUri = segmenterHelper.segmentAndSave(displayedUri)
|
||||||
|
if (resultUri != null) {
|
||||||
|
onSegmented(resultUri.toString())
|
||||||
|
}
|
||||||
|
isSegmenting = false
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text("Segment")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showAccept) {
|
if (showAccept) {
|
||||||
Button(onClick = onAccept) {
|
Button(onClick = { onAccept(displayedUri.toString()) }) {
|
||||||
Text("Accept")
|
Text("Accept")
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// Show Back if explicitly requested OR if Accept is not shown (to avoid empty state or getting stuck)
|
||||||
|
if (showBack || !showAccept) {
|
||||||
Button(onClick = onBack) {
|
Button(onClick = onBack) {
|
||||||
Text("Back")
|
Text("Back")
|
||||||
}
|
}
|
||||||
|
|
@ -162,4 +192,5 @@ fun ViewImageScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,8 @@ fun NavGraph(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
onSave = {
|
onSave = {
|
||||||
viewModel.saveAnimalDetails()
|
val isSaved = viewModel.saveAnimalDetails()
|
||||||
|
if (isSaved)
|
||||||
navController.popBackStack(Route.HomeScreen, inclusive = false)
|
navController.popBackStack(Route.HomeScreen, inclusive = false)
|
||||||
},
|
},
|
||||||
onCancel = { navController.popBackStack() },
|
onCancel = { navController.popBackStack() },
|
||||||
|
|
@ -95,6 +96,8 @@ fun NavGraph(
|
||||||
shouldAllowRetake = true,
|
shouldAllowRetake = true,
|
||||||
orientation = orientation,
|
orientation = orientation,
|
||||||
showAccept = false,
|
showAccept = false,
|
||||||
|
showBack = true,
|
||||||
|
showSegment = true,
|
||||||
animalId = currentId ?: "unknown"
|
animalId = currentId ?: "unknown"
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -141,15 +144,27 @@ fun NavGraph(
|
||||||
shouldAllowRetake = args.shouldAllowRetake,
|
shouldAllowRetake = args.shouldAllowRetake,
|
||||||
showAccept = args.showAccept,
|
showAccept = args.showAccept,
|
||||||
showBack = args.showBack,
|
showBack = args.showBack,
|
||||||
|
showSegment = args.showSegment,
|
||||||
onRetake = {
|
onRetake = {
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
// navController.navigate(Route.CameraScreen(...))
|
// navController.navigate(Route.CameraScreen(...))
|
||||||
},
|
},
|
||||||
onAccept = {
|
onAccept = { uri ->
|
||||||
navController.getBackStackEntry<Route.AddProfileScreen>().savedStateHandle["newImageUri"] = args.imageUri
|
navController.getBackStackEntry<Route.AddProfileScreen>().savedStateHandle["newImageUri"] = uri
|
||||||
navController.getBackStackEntry<Route.AddProfileScreen>().savedStateHandle["newImageOrientation"] = args.orientation
|
navController.getBackStackEntry<Route.AddProfileScreen>().savedStateHandle["newImageOrientation"] = args.orientation
|
||||||
navController.popBackStack<Route.AddProfileScreen>(inclusive = false)
|
navController.popBackStack<Route.AddProfileScreen>(inclusive = false)
|
||||||
},
|
},
|
||||||
|
onSegmented = { segmentedUri ->
|
||||||
|
navController.navigate(Route.ViewImageScreen(
|
||||||
|
imageUri = segmentedUri,
|
||||||
|
shouldAllowRetake = false,
|
||||||
|
orientation = args.orientation,
|
||||||
|
showAccept = true,
|
||||||
|
showBack = true,
|
||||||
|
showSegment = false,
|
||||||
|
animalId = args.animalId
|
||||||
|
))
|
||||||
|
},
|
||||||
onBack = { navController.popBackStack() }
|
onBack = { navController.popBackStack() }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,15 @@ sealed class Route {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class VideoRecordScreen(val animalId: String) : Route()
|
data class VideoRecordScreen(val animalId: String) : Route()
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ViewImageScreen(val imageUri: String, val shouldAllowRetake: Boolean, val orientation: String? = null, val showAccept: Boolean = false, val showBack: Boolean = false, val animalId: String) : Route()
|
data class ViewImageScreen(
|
||||||
|
val imageUri: String,
|
||||||
|
val shouldAllowRetake: Boolean,
|
||||||
|
val orientation: String? = null,
|
||||||
|
val showAccept: Boolean = false,
|
||||||
|
val showBack: Boolean = false,
|
||||||
|
val showSegment: Boolean = false,
|
||||||
|
val animalId: String
|
||||||
|
) : Route()
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ViewVideoScreen(val videoUri: String, val shouldAllowRetake: Boolean, val animalId: String) : Route()
|
data class ViewVideoScreen(val videoUri: String, val shouldAllowRetake: Boolean, val animalId: String) : Route()
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue