Compare commits

...

4 Commits

Author SHA1 Message Date
daeed139be Kamera funktionalität
-AlbumAndroidViewModel
-AlbumEvents
-AlbumViewModel
-AlbumViewState
-file_patchs
SelectPictureScree.kt wurde gelöscht. Die Funktionanlität  AlbumScreen() wurde in ReportOverlay neu aufgebaut.
2025-12-17 22:57:08 +01:00
1070adf13f Merge remote-tracking branch 'origin/main'
# Conflicts:
#	app/src/main/AndroidManifest.xml
#	app/src/main/java/com/example/snapandsolve/MainScreen.kt
2025-12-17 22:31:23 +01:00
c644361ab8 Kamera funktionalität
-AlbumAndroidViewModel
-AlbumEvents
-AlbumViewModel
-AlbumViewState
-file_patchs
SelectPictureScree.kt wurde gelöscht. Die Funktionanlität  AlbumScreen() wurde in ReportOverlay neu aufgebaut.
2025-12-17 22:26:26 +01:00
9048d31413 Kamera funktionalität
-AlbumAndroidViewModel
-AlbumEvents
-AlbumViewModel
-AlbumViewState
2025-12-17 19:28:59 +01:00
10 changed files with 464 additions and 41 deletions

View File

@@ -18,6 +18,13 @@ android {
versionName = "1.0" versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
val properties = org.jetbrains.kotlin.konan.properties.Properties()
val propertiesFile = rootProject.file("local.properties")
if (propertiesFile.exists()){
propertiesFile.inputStream().use { properties.load(it) }
}
buildConfigField("String", "ARCGIS_TOKEN","\"${properties.getProperty("arcgis.token","")}\"")
} }
buildTypes { buildTypes {
@@ -38,6 +45,7 @@ android {
} }
buildFeatures { buildFeatures {
compose = true compose = true
buildConfig = true
} }
} }

View File

@@ -1,13 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<!-- Kamera Zugriffsberechtigung -->
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
@@ -18,7 +14,7 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.SnapAndSolve"> android:theme="@style/Theme.SnapAndSolve">
<activity <activity
android:name=".MainActivity" android:name="com.example.snapandsolve.MainActivity"
android:exported="true" android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/Theme.SnapAndSolve"> android:theme="@style/Theme.SnapAndSolve">
@@ -28,6 +24,15 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application> </application>
</manifest> </manifest>

View File

@@ -7,15 +7,20 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import com.example.snapandsolve.camera.AlbumViewModel
import com.example.snapandsolve.ui.theme.SnapAndSolveTheme import com.example.snapandsolve.ui.theme.SnapAndSolveTheme
import kotlinx.coroutines.Dispatchers
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private lateinit var viewModel: AlbumViewModel
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModel = AlbumViewModel(coroutineContext = Dispatchers.Default)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {

View File

@@ -1,6 +1,11 @@
package com.example.snapandsolve package com.example.snapandsolve
import android.Manifest
import android.app.Application import android.app.Application
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.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -13,35 +18,34 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.CornerSize 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.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.BottomAppBar import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardColors import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition import androidx.compose.material3.FabPosition
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.FloatingActionButtonDefaults
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeFloatingActionButton import androidx.compose.material3.LargeFloatingActionButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MediumTopAppBar import androidx.compose.material3.MediumTopAppBar
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
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarColors
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
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
@@ -50,10 +54,16 @@ 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
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
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.AppColor
import com.example.snapandsolve.ui.theme.ButtonColor import com.example.snapandsolve.ui.theme.ButtonColor
import com.example.snapandsolve.ui.theme.WidgetColor import com.example.snapandsolve.ui.theme.WidgetColor
import kotlinx.coroutines.Dispatchers
@Composable @Composable
@@ -69,7 +79,6 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) {
BottomAppBar( BottomAppBar(
modifier = Modifier.height(120.dp), modifier = Modifier.height(120.dp),
containerColor = AppColor, containerColor = AppColor,
// contentColor = AppColor
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -81,12 +90,11 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) {
onClick = { onClick = {
/*TODO*/ /*TODO*/
}, },
modifier = Modifier.padding(bottom = 8.dp) // Abstand "in" der Bar modifier = Modifier.padding(bottom = 8.dp)
) { ) {
Icon( Icon(
Icons.Default.Menu, Icons.Default.Menu,
contentDescription = "Menu", contentDescription = "Menu",
) )
} }
Spacer(Modifier.weight(1f)) Spacer(Modifier.weight(1f))
@@ -105,8 +113,7 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) {
} }
}, },
floatingActionButtonPosition = FabPosition.Center, floatingActionButtonPosition = FabPosition.Center,
) { ) { innerPadding ->
innerPadding ->
ContentScreen( ContentScreen(
modifier = Modifier.padding(innerPadding), modifier = Modifier.padding(innerPadding),
application, application,
@@ -123,13 +130,15 @@ fun ContentScreen(
onDismissReport: () -> Unit onDismissReport: () -> Unit
) { ) {
val mapViewModel = remember { MapViewModel(application) } val mapViewModel = remember { MapViewModel(application) }
Box(modifier = modifier.fillMaxSize()) { // ViewModel für die Kamera-Funktionalität
val albumViewModel = remember { AlbumViewModel(Dispatchers.Default) }
// 2) Overlay Box(modifier = modifier.fillMaxSize()) {
if (showReport) { if (showReport) {
ReportOverlay( ReportOverlay(
onCancel = onDismissReport, onCancel = onDismissReport,
onAdd = { /* später */ } onAdd = { /* später */ },
viewModel = albumViewModel
) )
} }
} }
@@ -148,15 +157,71 @@ fun AppTopBar(
containerColor = AppColor, containerColor = AppColor,
titleContentColor = Color.White titleContentColor = Color.White
) )
) )
} }
@Composable @Composable
fun ReportOverlay( fun ReportOverlay(
onCancel: () -> Unit, onCancel: () -> Unit,
onAdd: () -> Unit onAdd: () -> Unit,
viewModel: AlbumViewModel
) { ) {
val viewState: AlbumViewState by viewModel.viewStateFlow.collectAsState()
val currentContext = LocalContext.current
// Launcher für Bildauswahl aus Galerie
val pickImageFromAlbumLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(20)
) { urls ->
viewModel.onReceive(Intent.OnFinishPickingImagesWith(currentContext, urls))
}
// Launcher für Kamera
val cameraLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.TakePicture()
) { isImageSaved ->
if (isImageSaved) {
viewModel.onReceive(Intent.OnImageSavedWith(currentContext))
} else {
viewModel.onReceive(Intent.OnImageSavingCanceled)
}
}
// Launcher für Kamera-Berechtigung
val permissionLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { permissionGranted ->
if (permissionGranted) {
viewModel.onReceive(Intent.OnPermissionGrantedWith(currentContext))
} else {
viewModel.onReceive(Intent.OnPermissionDenied)
}
}
// Funktion zum Starten der Kamera (prüft Berechtigung)
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)
}
}
// Kamera starten, wenn tempFileUrl gesetzt ist
LaunchedEffect(key1 = viewState.tempFileUrl) {
viewState.tempFileUrl?.let {
cameraLauncher.launch(it)
}
}
// leichter Dim-Hintergrund // leichter Dim-Hintergrund
Box( Box(
modifier = Modifier modifier = Modifier
@@ -179,14 +244,41 @@ fun ReportOverlay(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(20.dp), .padding(20.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
Text("Schadensbeschreibung:", Text(
"Schadensbeschreibung:",
color = Color.Black color = Color.Black
) )
// Platzhalter fürs Textfeld / Icons etc. // Kamera-Buttons (über der weißen Box)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button(
onClick = {
startCamera()
},
modifier = Modifier.weight(1f)
) {
Text(text = "Foto aufnehmen")
}
Button(
onClick = {
pickImageFromAlbumLauncher.launch(
PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)
)
},
modifier = Modifier.weight(1f)
) {
Text(text = "Aus Galerie")
}
}
// Die weiße Textfeld Box
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -194,6 +286,30 @@ fun ReportOverlay(
.background(Color.White, RoundedCornerShape(12.dp)) .background(Color.White, RoundedCornerShape(12.dp))
) )
// Grid für ausgewählte Bilder (außerhalb der Box, aber in der Card)
if (viewState.selectedPictures.isNotEmpty()) {
Text(
text = "Ausgewählte Bilder (${viewState.selectedPictures.size})",
color = Color.Black
)
LazyVerticalGrid(
columns = GridCells.Fixed(3),
userScrollEnabled = false,
modifier = Modifier
.fillMaxWidth()
.heightIn(0.dp, 200.dp)
) {
itemsIndexed(viewState.selectedPictures) { index, picture ->
Image(
modifier = Modifier.padding(4.dp),
bitmap = picture,
contentDescription = "Bild ${index + 1}",
contentScale = ContentScale.Crop
)
}
}
}
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
@@ -206,16 +322,15 @@ fun ReportOverlay(
disabledContainerColor = Color.White, disabledContainerColor = Color.White,
disabledContentColor = Color.White disabledContentColor = Color.White
) )
) { Text( ) {
Text(
"Abbrechen", "Abbrechen",
color = Color.Black color = Color.Black
) } )
}
Button(onClick = onAdd) { Text("Hinzufügen") } Button(onClick = onAdd) { Text("Hinzufügen") }
} }
} }
} }
} }
} }

View File

@@ -0,0 +1,119 @@
package com.example.snapandsolve.camera
import android.app.Application
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.core.content.FileProvider
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.example.snapandsolve.BuildConfig
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import java.io.File
import kotlin.coroutines.CoroutineContext
/**
* This variant inherits from [AndroidViewModel] and has access to the application context
*/
class AlbumAndroidViewModel(private val appContext: Application,
private val coroutineContext: CoroutineContext
): AndroidViewModel(appContext) {
//region View State
private val _albumViewState: MutableStateFlow<AlbumViewState> = MutableStateFlow(
AlbumViewState()
)
val viewStateFlow: StateFlow<AlbumViewState>
get() = _albumViewState
//endregion
fun onEvent(intent: Intent) = viewModelScope.launch(coroutineContext) {
when(intent) {
is Intent.OnPermissionGranted -> {
// Create an empty image file in the app's cache directory
val file = File.createTempFile(
"temp_image_file_", /* prefix */
".jpg", /* suffix */
appContext.cacheDir /* cache directory */
)
// Create sandboxed url for this temp file - needed for the camera API
val uri = FileProvider.getUriForFile(appContext,
"${BuildConfig.APPLICATION_ID}.provider",
file
)
_albumViewState.value = _albumViewState.value.copy(tempFileUrl = uri)
}
is Intent.OnPermissionDenied -> {
// maybe log the permission denial event
println("User did not grant permission to use the camera")
}
is Intent.OnFinishPickingImages -> {
if (intent.imageUrls.isNotEmpty()) {
// Handle picked images
val newImages = mutableListOf<ImageBitmap>()
for (eachImageUrl in intent.imageUrls) {
val inputStream = appContext.contentResolver.openInputStream(eachImageUrl)
val bytes = inputStream?.readBytes()
inputStream?.close()
if (bytes != null) {
val bitmapOptions = BitmapFactory.Options()
bitmapOptions.inMutable = true
val bitmap: Bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, bitmapOptions)
val imageBitmap = bitmap.asImageBitmap()
newImages.add(imageBitmap)
} else {
// error reading the bytes from the image url
println("The image that was picked could not be read from the device at this url: $eachImageUrl")
}
}
val currentViewState = _albumViewState.value
val newCopy = currentViewState.copy(
selectedPictures = (currentViewState.selectedPictures + newImages),
tempFileUrl = null
)
_albumViewState.value = newCopy
} else {
// user did not pick anything
}
}
is Intent.OnImageSaved -> {
val tempImageUrl = _albumViewState.value.tempFileUrl
if (tempImageUrl != null) {
val source: ImageDecoder.Source = ImageDecoder.createSource(appContext.contentResolver, tempImageUrl)
val currentPictures = _albumViewState.value.selectedPictures.toMutableList()
currentPictures.add(ImageDecoder.decodeBitmap(source).asImageBitmap())
_albumViewState.value = _albumViewState.value.copy(tempFileUrl = null,
selectedPictures = currentPictures)
}
}
is Intent.OnImageSavingCanceled -> {
_albumViewState.value = _albumViewState.value.copy(tempFileUrl = null)
}
is Intent.OnFinishPickingImagesWith -> {
// unnecessary in this viewmodel variant
}
is Intent.OnPermissionGrantedWith -> {
// unnecessary in this viewmodel variant
}
is Intent.OnImageSavedWith -> {
// unnecessary in this viewmodel variant
}
}
}
}

View File

@@ -0,0 +1,28 @@
package com.example.snapandsolve.camera
import android.content.Context
import android.net.Uri
/*
Das sind die Funktionen beim Drücken der Knöpfe. Übersichtlicher wäre es sie direkt mit den
Knöpfen in der ViewModel zu platzieren. AlbumEvents als name ist vielleicht unglücklich gewählt.
*/
/**
* User generated events that can be triggered from the UI.
*/
sealed class Intent {
data object OnPermissionGranted: Intent()
data class OnPermissionGrantedWith(val compositionContext: Context): Intent()
data object OnPermissionDenied: Intent()
data object OnImageSaved: Intent()
data class OnImageSavedWith (val compositionContext: Context): Intent()
data object OnImageSavingCanceled: Intent()
data class OnFinishPickingImages(val imageUrls: List<Uri>): Intent()
data class OnFinishPickingImagesWith(val compositionContext: Context, val imageUrls: List<Uri>): Intent()
}

View File

@@ -0,0 +1,119 @@
package com.example.snapandsolve.camera
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.ImageDecoder
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.core.content.FileProvider
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.snapandsolve.BuildConfig
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import java.io.File
import kotlin.coroutines.CoroutineContext
class AlbumViewModel(private val coroutineContext: CoroutineContext
): ViewModel() {
//region View State
private val _albumViewState: MutableStateFlow<AlbumViewState> = MutableStateFlow(
AlbumViewState()
)
val viewStateFlow: StateFlow<AlbumViewState>
get() = _albumViewState
//endregion
// region Intents
fun onReceive(intent: Intent) = viewModelScope.launch(coroutineContext) {
when(intent) {
is Intent.OnPermissionGrantedWith -> {
println("DEBUG: OnPermissionGrantedWith empfangen")
val tempFile = File.createTempFile(
"temp_image_file_",
".jpg",
intent.compositionContext.cacheDir
)
println("DEBUG: TempFile erstellt: ${tempFile.absolutePath}")
val uri = FileProvider.getUriForFile(intent.compositionContext,
"${BuildConfig.APPLICATION_ID}.provider",
tempFile
)
println("DEBUG: URI erstellt: $uri")
_albumViewState.value = _albumViewState.value.copy(tempFileUrl = uri)
println("DEBUG: tempFileUrl gesetzt in ViewState")
}
is Intent.OnPermissionDenied -> {
// maybe log the permission denial event
println("User did not grant permission to use the camera")
}
is Intent.OnFinishPickingImagesWith -> {
if (intent.imageUrls.isNotEmpty()) {
// Handle picked images
val newImages = mutableListOf<ImageBitmap>()
for (eachImageUrl in intent.imageUrls) {
val inputStream = intent.compositionContext.contentResolver.openInputStream(eachImageUrl)
val bytes = inputStream?.readBytes()
inputStream?.close()
if (bytes != null) {
val bitmapOptions = BitmapFactory.Options()
bitmapOptions.inMutable = true
val bitmap: Bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, bitmapOptions)
newImages.add(bitmap.asImageBitmap())
} else {
// error reading the bytes from the image url
println("The image that was picked could not be read from the device at this url: $eachImageUrl")
}
}
val currentViewState = _albumViewState.value
val newCopy = currentViewState.copy(
selectedPictures = (currentViewState.selectedPictures + newImages),
tempFileUrl = null
)
_albumViewState.value = newCopy
} else {
// user did not pick anything
}
}
is Intent.OnImageSavedWith -> {
val tempImageUrl = _albumViewState.value.tempFileUrl
if (tempImageUrl != null) {
val source = ImageDecoder.createSource(intent.compositionContext.contentResolver, tempImageUrl)
val currentPictures = _albumViewState.value.selectedPictures.toMutableList()
currentPictures.add(ImageDecoder.decodeBitmap(source).asImageBitmap())
_albumViewState.value = _albumViewState.value.copy(tempFileUrl = null,
selectedPictures = currentPictures)
}
}
is Intent.OnImageSavingCanceled -> {
_albumViewState.value = _albumViewState.value.copy(tempFileUrl = null)
}
is Intent.OnPermissionGranted -> {
// unnecessary in this viewmodel variant
}
is Intent.OnFinishPickingImages -> {
// unnecessary in this viewmodel variant
}
is Intent.OnImageSaved -> {
// unnecessary in this viewmodel variant
}
}
}
// endregion
}

View File

@@ -0,0 +1,19 @@
package com.example.snapandsolve.camera
import android.net.Uri
import androidx.compose.ui.graphics.ImageBitmap
/**
* Holds state data for the MainScreen composable.
*/
data class AlbumViewState(
/**
* holds the URL of the temporary file which stores the image taken by the camera.
*/
val tempFileUrl: Uri? = null,
/**
* holds the list of images taken by camera or selected pictures from the gallery.
*/
val selectedPictures: List<ImageBitmap> = emptyList(),
)

View File

@@ -0,0 +1,4 @@
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- creates a reference to the cache folder that the system maintains -->
<cache-path name="temporary_camera_images" path="/" />
</paths>

View File

@@ -16,6 +16,7 @@ dependencyResolutionManagement {
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
maven { url = uri("https://esri.jfrog.io/artifactory/arcgis") } // <-- NEU
} }
} }