Kamera funktionalität

-AlbumAndroidViewModel
-AlbumEvents
-AlbumViewModel
-AlbumViewState
-file_patchs
SelectPictureScree.kt wurde gelöscht. Die Funktionanlität  AlbumScreen() wurde in ReportOverlay neu aufgebaut.
This commit is contained in:
2025-12-17 22:26:26 +01:00
parent 9048d31413
commit c644361ab8
12 changed files with 431 additions and 34 deletions

View File

@@ -0,0 +1,336 @@
package com.example.snapandsolve
import android.Manifest
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.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.offset
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.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.Card
import androidx.compose.material3.CardColors
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FabPosition
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LargeFloatingActionButton
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
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.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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 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.ButtonColor
import com.example.snapandsolve.ui.theme.WidgetColor
import kotlinx.coroutines.Dispatchers
@Composable
fun MainScreen(modifier: Modifier = Modifier, application: Application) {
var showReport by rememberSaveable { mutableStateOf(false) }
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
AppTopBar()
},
bottomBar = {
BottomAppBar(
modifier = Modifier.height(120.dp),
containerColor = AppColor,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
IconButton(
onClick = {
/*TODO*/
},
modifier = Modifier.padding(bottom = 8.dp)
) {
Icon(
Icons.Default.Menu,
contentDescription = "Menu",
)
}
Spacer(Modifier.weight(1f))
}
}
},
floatingActionButton = {
LargeFloatingActionButton(
onClick = {
showReport = true
},
modifier = Modifier.offset(y = 64.dp),
containerColor = ButtonColor
) {
Icon(Icons.Default.Add, contentDescription = "Add")
}
},
floatingActionButtonPosition = FabPosition.Center,
) { innerPadding ->
ContentScreen(
modifier = Modifier.padding(innerPadding),
application,
showReport = showReport,
onDismissReport = { showReport = false })
}
}
@Composable
fun ContentScreen(
modifier: Modifier = Modifier,
application: Application,
showReport: Boolean,
onDismissReport: () -> Unit
) {
val mapViewModel = remember { MapViewModel(application) }
// ViewModel für die Kamera-Funktionalität
val albumViewModel = remember { AlbumViewModel(Dispatchers.Default) }
Box(modifier = modifier.fillMaxSize()) {
if (showReport) {
ReportOverlay(
onCancel = onDismissReport,
onAdd = { /* später */ },
viewModel = albumViewModel
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AppTopBar(
modifier: Modifier = Modifier
) {
MediumTopAppBar(
title = {
Text("Scan And Solve")
},
colors = TopAppBarDefaults.mediumTopAppBarColors(
containerColor = AppColor,
titleContentColor = Color.White
)
)
}
@Composable
fun ReportOverlay(
onCancel: () -> 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
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.25f)),
contentAlignment = Alignment.Center
) {
Card(
modifier = Modifier
.fillMaxWidth(0.9f)
.heightIn(min = 400.dp),
shape = RoundedCornerShape(24.dp),
colors = CardColors(
containerColor = WidgetColor,
contentColor = ButtonColor,
disabledContainerColor = Color.White,
disabledContentColor = Color.White
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(
"Schadensbeschreibung:",
color = Color.Black
)
// Deine 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 Box vom Kommilitonen (Textfeld-Platzhalter)
Box(
modifier = Modifier
.fillMaxWidth()
.height(220.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(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
OutlinedButton(
onClick = onCancel,
colors = ButtonColors(
containerColor = ButtonColor,
contentColor = Color.White,
disabledContainerColor = Color.White,
disabledContentColor = Color.White
)
) {
Text(
"Abbrechen",
color = Color.Black
)
}
Button(onClick = onAdd) { Text("Hinzufügen") }
}
}
}
}
}