- Code bereinigt

- MapView in MapSegment ausgelagert
- Punkt kann jetzt manuell gesetzt werden
- Eingaben werden erstmal im ReportDraft gespeichert
This commit is contained in:
2026-01-18 16:49:03 +01:00
parent 97d86523ab
commit 30d5a17e6e
4 changed files with 455 additions and 133 deletions

View File

@@ -2,6 +2,7 @@ package com.example.snapandsolve
import MapViewModel import MapViewModel
import android.Manifest import android.Manifest
import android.R.attr.enabled
import android.app.Application import android.app.Application
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.PickVisualMediaRequest
@@ -17,6 +18,8 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll 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.AddLocation
import androidx.compose.material.icons.filled.AddLocationAlt
import androidx.compose.material.icons.filled.FilterAlt import androidx.compose.material.icons.filled.FilterAlt
import androidx.compose.material.icons.filled.FormatListNumbered import androidx.compose.material.icons.filled.FormatListNumbered
import androidx.compose.material.icons.filled.Menu import androidx.compose.material.icons.filled.Menu
@@ -25,6 +28,7 @@ import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -44,10 +48,30 @@ import kotlinx.coroutines.Dispatchers
@Composable @Composable
fun MainScreen(modifier: Modifier = Modifier, application: Application) { fun MainScreen(modifier: Modifier = Modifier, application: Application) {
var showReport by rememberSaveable { mutableStateOf(false) } var showReport by rememberSaveable { mutableStateOf(false) }
var sliderOpen by remember { mutableStateOf(false) } var sliderOpen by rememberSaveable { mutableStateOf(false) }
val mapViewModel = remember { MapViewModel(application) } val mapViewModel = remember { MapViewModel(application) }
val albumViewModel = remember { AlbumViewModel(Dispatchers.Default) } val albumViewModel = remember { AlbumViewModel(Dispatchers.Default) }
// test
fun openReport() {
showReport = true
sliderOpen = false
}
// test
fun closeReport() {
showReport = false
}
LaunchedEffect(mapViewModel.reopenReport) {
if (mapViewModel.reopenReport) {
showReport = true
mapViewModel.consumeReopenReport()
}
}
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
topBar = { AppTopBar() }, topBar = { AppTopBar() },
@@ -64,8 +88,7 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) {
floatingActionButton = { floatingActionButton = {
LargeFloatingActionButton( LargeFloatingActionButton(
onClick = { onClick = {
showReport = true openReport()
sliderOpen = false
}, },
modifier = Modifier.offset(y = 64.dp), modifier = Modifier.offset(y = 64.dp),
containerColor = ButtonColor containerColor = ButtonColor
@@ -77,11 +100,11 @@ fun MainScreen(modifier: Modifier = Modifier, application: Application) {
) { innerPadding -> ) { innerPadding ->
ContentScreen( ContentScreen(
modifier = Modifier.padding(innerPadding), modifier = Modifier.padding(innerPadding),
mapViewModel, mapViewModel = mapViewModel,
albumViewModel, albumViewModel = albumViewModel,
showReport = showReport, showReport = showReport,
sliderOpen = sliderOpen, sliderOpen = sliderOpen,
onDismissReport = { showReport = false } onDismissReport = ::closeReport
) )
} }
} }
@@ -115,7 +138,9 @@ fun ContentScreen(
) )
onDismissReport() onDismissReport()
}, },
viewModel = albumViewModel onClose = onDismissReport,
viewModel = albumViewModel,
mapViewModel = mapViewModel
) )
} }
@@ -166,16 +191,21 @@ fun AppTopBar(
fun ReportOverlay( fun ReportOverlay(
onCancel: () -> Unit, onCancel: () -> Unit,
onAdd: (beschreibung: String, typ: String) -> Unit, onAdd: (beschreibung: String, typ: String) -> Unit,
viewModel: AlbumViewModel onClose: () -> Unit,
viewModel: AlbumViewModel,
mapViewModel: MapViewModel
) { ) {
val viewState: AlbumViewState by viewModel.viewStateFlow.collectAsState() val viewState: AlbumViewState by viewModel.viewStateFlow.collectAsState()
val currentContext = LocalContext.current val currentContext = LocalContext.current
val hasPoint = mapViewModel.reportDraft.point != null
// State für Beschreibung und Typ
var beschreibung by remember { mutableStateOf("") }
var selectedTyp by remember { mutableStateOf("Schadenstyp wählen...") }
var dropdownExpanded by remember { mutableStateOf(false) } var dropdownExpanded by remember { mutableStateOf(false) }
LaunchedEffect(viewState.selectedPictures) {
mapViewModel.updateReportDraft {
copy(photos = viewState.selectedPictures)
}
}
// Launcher für Bildauswahl aus Galerie // Launcher für Bildauswahl aus Galerie
val pickImageFromAlbumLauncher = rememberLauncherForActivityResult( val pickImageFromAlbumLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.PickMultipleVisualMedia(20) ActivityResultContracts.PickMultipleVisualMedia(20)
@@ -229,10 +259,17 @@ fun ReportOverlay(
.background(Color.Black.copy(alpha = 0.25f)), .background(Color.Black.copy(alpha = 0.25f)),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
BoxWithConstraints {
val verticalMargin = 50.dp // <-- dein gewünschter Mindestabstand oben + unten
val maxCardHeight = maxHeight - verticalMargin * 2
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxWidth(0.9f) .fillMaxWidth(0.9f)
.heightIn(min = 400.dp), .heightIn(
min = 400.dp,
max = maxCardHeight
),
shape = RoundedCornerShape(24.dp), shape = RoundedCornerShape(24.dp),
colors = CardColors( colors = CardColors(
containerColor = WidgetColor, containerColor = WidgetColor,
@@ -256,17 +293,27 @@ fun ReportOverlay(
onClick = { dropdownExpanded = true }, onClick = { dropdownExpanded = true },
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Text("Typ: $selectedTyp") Text("Typ: ${mapViewModel.reportDraft.typ}")
} }
DropdownMenu( DropdownMenu(
expanded = dropdownExpanded, expanded = dropdownExpanded,
onDismissRequest = { dropdownExpanded = false } onDismissRequest = { dropdownExpanded = false }
) { ) {
listOf("Straße", "Gehweg", "Fahrradweg", "Beleuchtung","Sonstiges").forEach { typ -> listOf(
"Straße",
"Gehweg",
"Fahrradweg",
"Beleuchtung",
"Sonstiges"
).forEach { typ ->
DropdownMenuItem( DropdownMenuItem(
text = { Text(typ) }, text = {
Text(typ)
},
onClick = { onClick = {
selectedTyp = typ mapViewModel.updateReportDraft {
copy(typ = typ)
}
dropdownExpanded = false dropdownExpanded = false
} }
) )
@@ -299,8 +346,12 @@ fun ReportOverlay(
// Textfeld für Beschreibung // Textfeld für Beschreibung
TextField( TextField(
value = beschreibung, value = mapViewModel.reportDraft.beschreibung,
onValueChange = { beschreibung = it }, onValueChange = {
mapViewModel.updateReportDraft {
copy(beschreibung = it)
}
},
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(220.dp), .height(220.dp),
@@ -335,13 +386,38 @@ fun ReportOverlay(
} }
} }
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Button(
onClick = {
},
) {
Text("Position aus Standort")
}
Button(
onClick = {
mapViewModel.startPickReportLocation()
onClose()
},
) {
Text(if (hasPoint) "Position neu setzen" else "Position manuell setzen")
}
}
// Buttons // Buttons
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
OutlinedButton( OutlinedButton(
onClick = onCancel, onClick = {
mapViewModel.resetDraft()
viewModel.clearSelection()
onCancel()
},
colors = ButtonColors( colors = ButtonColors(
containerColor = ButtonColor, containerColor = ButtonColor,
contentColor = Color.White, contentColor = Color.White,
@@ -352,8 +428,12 @@ fun ReportOverlay(
Text("Abbrechen", color = Color.Black) Text("Abbrechen", color = Color.Black)
} }
Button( Button(
onClick = { onAdd(beschreibung, selectedTyp) }, onClick = {
enabled = beschreibung.isNotBlank() mapViewModel.submitDraftToLayer()
viewModel.clearSelection()
onCancel()
},
enabled = mapViewModel.reportDraft.isValid
) { ) {
Text("Hinzufügen") Text("Hinzufügen")
} }
@@ -361,4 +441,5 @@ fun ReportOverlay(
} }
} }
} }
}
} }

View File

@@ -20,7 +20,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.arcgismaps.ApiKey import com.arcgismaps.ApiKey
import com.arcgismaps.ArcGISEnvironment import com.arcgismaps.ArcGISEnvironment
import com.arcgismaps.Color
import com.arcgismaps.location.LocationDisplayAutoPanMode import com.arcgismaps.location.LocationDisplayAutoPanMode
import com.arcgismaps.mapping.symbology.SimpleMarkerSymbol
import com.arcgismaps.mapping.symbology.SimpleMarkerSymbolStyle
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.toolkit.geoviewcompose.MapView import com.arcgismaps.toolkit.geoviewcompose.MapView
import com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplay import com.arcgismaps.toolkit.geoviewcompose.rememberLocationDisplay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -68,7 +72,8 @@ fun MapSegment(
arcGISMap = mapViewModel.map, arcGISMap = mapViewModel.map,
locationDisplay = locationDisplay, locationDisplay = locationDisplay,
mapViewProxy = mapViewModel.mapViewProxy, mapViewProxy = mapViewModel.mapViewProxy,
onSingleTapConfirmed = {}//mapViewModel::onTap, onSingleTapConfirmed = mapViewModel::onTap,
graphicsOverlays = listOf(mapViewModel.tempOverlay)
) { ) {
/* TODO */ /* TODO */
} }

View File

@@ -1,14 +1,20 @@
import android.app.Application import android.app.Application
import android.graphics.Bitmap
import androidx.compose.runtime.Composable
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.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asAndroidBitmap import androidx.compose.ui.graphics.asAndroidBitmap
import androidx.compose.ui.unit.dp
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.arcgismaps.LoadStatus import com.arcgismaps.LoadStatus
import com.arcgismaps.data.ArcGISFeature import com.arcgismaps.data.ArcGISFeature
import com.arcgismaps.data.CodedValueDomain import com.arcgismaps.data.CodedValueDomain
import com.arcgismaps.data.QueryParameters
import com.arcgismaps.data.ServiceFeatureTable import com.arcgismaps.data.ServiceFeatureTable
import com.arcgismaps.geometry.GeometryEngine import com.arcgismaps.geometry.GeometryEngine
import com.arcgismaps.geometry.Point import com.arcgismaps.geometry.Point
@@ -17,25 +23,46 @@ import com.arcgismaps.mapping.ArcGISMap
import com.arcgismaps.mapping.BasemapStyle import com.arcgismaps.mapping.BasemapStyle
import com.arcgismaps.mapping.Viewpoint import com.arcgismaps.mapping.Viewpoint
import com.arcgismaps.mapping.layers.FeatureLayer import com.arcgismaps.mapping.layers.FeatureLayer
import com.arcgismaps.mapping.symbology.SimpleMarkerSymbol
import com.arcgismaps.mapping.symbology.SimpleMarkerSymbolStyle
import com.arcgismaps.mapping.symbology.SimpleRenderer
import com.arcgismaps.mapping.view.Graphic
import com.arcgismaps.mapping.view.GraphicsOverlay
import com.arcgismaps.mapping.view.LocationDisplay import com.arcgismaps.mapping.view.LocationDisplay
import com.arcgismaps.mapping.view.ScreenCoordinate
import com.arcgismaps.mapping.view.SingleTapConfirmedEvent
import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy import com.arcgismaps.toolkit.geoviewcompose.MapViewProxy
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
class MapViewModel(application: Application) : AndroidViewModel(application) { class MapViewModel(application: Application) : AndroidViewModel(application) {
val map: ArcGISMap = ArcGISMap(BasemapStyle.OpenOsmStyle).apply { val map: ArcGISMap = ArcGISMap(BasemapStyle.OpenOsmStyle).apply {
initialViewpoint = Viewpoint(53.14, 8.20, 20000.0) initialViewpoint = Viewpoint(53.14, 8.20, 20000.0)
} }
var selectedOperation by mutableStateOf(FeatureOperationType.DEFAULT)
private set
var reopenReport by mutableStateOf(false)
private set
var selectedFeature: ArcGISFeature? by mutableStateOf(null) var selectedFeature: ArcGISFeature? by mutableStateOf(null)
val mapViewProxy = MapViewProxy() val mapViewProxy = MapViewProxy()
var reportDraft by mutableStateOf(ReportDraft())
private set
lateinit var featureLayer: FeatureLayer lateinit var featureLayer: FeatureLayer
var snackBarMessage: String by mutableStateOf("") var snackBarMessage: String by mutableStateOf("")
lateinit var serviceFeatureTable: ServiceFeatureTable lateinit var serviceFeatureTable: ServiceFeatureTable
var currentDamageType by mutableStateOf("") var currentDamageType by mutableStateOf("")
var damageTypeList: List<String> = mutableListOf() var damageTypeList: List<String> = mutableListOf()
var locationDisplay: LocationDisplay? = null var locationDisplay: LocationDisplay? = null
// Create a red circle simple marker symbol.
val redCircleSymbol = SimpleMarkerSymbol(
style = SimpleMarkerSymbolStyle.Circle,
color = com.arcgismaps.Color.red,
size = 10.0f
)
var pointGraphic = Graphic(null, redCircleSymbol)
val tempOverlay = GraphicsOverlay()
init { init {
viewModelScope.launch { viewModelScope.launch {
@@ -113,7 +140,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
if (photos.isNotEmpty()) { if (photos.isNotEmpty()) {
// Fix: erstellen eine Abfrage für die neue ID // Fix: erstellen eine Abfrage für die neue ID
val queryParameters = com.arcgismaps.data.QueryParameters().apply { val queryParameters = QueryParameters().apply {
objectIds.add(serverObjectId) objectIds.add(serverObjectId)
} }
@@ -176,7 +203,213 @@ class MapViewModel(application: Application) : AndroidViewModel(application) {
private fun imageBitmapToByteArray(imageBitmap: ImageBitmap): ByteArray { private fun imageBitmapToByteArray(imageBitmap: ImageBitmap): ByteArray {
val stream = ByteArrayOutputStream() val stream = ByteArrayOutputStream()
imageBitmap.asAndroidBitmap().compress(android.graphics.Bitmap.CompressFormat.JPEG, 80, stream) imageBitmap.asAndroidBitmap().compress(Bitmap.CompressFormat.JPEG, 80, stream)
return stream.toByteArray() return stream.toByteArray()
} }
fun onTap(singleTapConfirmedEvent: SingleTapConfirmedEvent) {
if (featureLayer.loadStatus.value != LoadStatus.Loaded) {
snackBarMessage = "Layer not loaded!"
return
}
when (selectedOperation) {
FeatureOperationType.DEFAULT -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
FeatureOperationType.DELETE -> deleteFeatureAt(singleTapConfirmedEvent.screenCoordinate)
FeatureOperationType.UPDATE_ATTRIBUTE -> selectFeatureForAttributeEditAt(singleTapConfirmedEvent.screenCoordinate)
FeatureOperationType.UPDATE_GEOMETRY -> updateFeatureGeometryAt(singleTapConfirmedEvent.screenCoordinate)
FeatureOperationType.PICK_REPORT_LOCATION -> pickReportLocation(singleTapConfirmedEvent.screenCoordinate)
else -> {}
}
}
private fun deleteFeatureAt(screenCoordinate: ScreenCoordinate) {
featureLayer?.let { featureLayer ->
// Clear any existing selection.
featureLayer.clearSelection()
selectedFeature = null
viewModelScope.launch {
// Determine if a user tapped on a feature.
mapViewProxy.identify(featureLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
selectedFeature = (identifyResult.geoElements.firstOrNull() as? ArcGISFeature)?.also {
featureLayer.selectFeature(it)
}
}
}
}
}
private fun selectFeatureForAttributeEditAt(screenCoordinate: ScreenCoordinate) {
featureLayer?.let { featureLayer ->
// Clear any existing selection.
featureLayer.clearSelection()
selectedFeature = null
viewModelScope.launch {
// Determine if a user tapped on a feature.
mapViewProxy.identify(featureLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
// Get the identified feature.
val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
identifiedFeature?.let {
val currentAttributeValue = it.attributes["typ"] as String
currentDamageType = currentAttributeValue
selectedFeature = it.also {
featureLayer.selectFeature(it)
}
} ?: run {
// Reset damage type if no feature identified.
currentDamageType = ""
}
}
}
}
}
private fun updateFeatureGeometryAt(screenCoordinate: ScreenCoordinate) {
featureLayer?.let { featureLayer ->
when (selectedFeature) {
// When no feature is selected.
null -> {
viewModelScope.launch {
// Determine if a user tapped on a feature.
mapViewProxy.identify(featureLayer, screenCoordinate, 10.dp).onSuccess { identifyResult ->
// Get the identified feature.
val identifiedFeature = identifyResult.geoElements.firstOrNull() as? ArcGISFeature
identifiedFeature?.let {
selectedFeature = it.also {
featureLayer.selectFeature(it)
}
}
}
}
}
// When a feature is selected, update its geometry to the tapped location.
else -> {
mapViewProxy.screenToLocationOrNull(screenCoordinate)?.let { mapPoint ->
// Normalize the point - needed when the tapped location is over the international date line.
val destinationPoint = GeometryEngine.normalizeCentralMeridian(mapPoint)
viewModelScope.launch {
selectedFeature?.let { selectedFeature ->
// Load the feature.
selectedFeature.load().onSuccess {
// Update the geometry of the selected feature.
selectedFeature.geometry = destinationPoint
// Apply the edit to the feature table.
serviceFeatureTable?.updateFeature(selectedFeature)
// Push the update to the service with the service geodatabase.
serviceFeatureTable?.applyEdits()?.onSuccess {
snackBarMessage = "Moved feature ${selectedFeature.attributes["objectid"]}"
}?.onFailure {
snackBarMessage =
"Failed to move feature ${selectedFeature.attributes["objectid"]}"
}
}
}
}
}
}
}
}
}
private fun pickReportLocation(screen: ScreenCoordinate) {
val mapPoint = mapViewProxy.screenToLocationOrNull(screen)
val p = mapPoint?.let { GeometryEngine.normalizeCentralMeridian(it) as? Point }
if (p != null) {
reportDraft = reportDraft.copy(point = p)
tempOverlay.graphics.clear()
pointGraphic.geometry = p
tempOverlay.graphics.add(pointGraphic)
reopenReport = true
snackBarMessage = "Position gesetzt."
} else {
snackBarMessage = "Position konnte nicht gesetzt werden."
}
selectedOperation = FeatureOperationType.DEFAULT
}
fun startPickReportLocation() {
selectedOperation = FeatureOperationType.PICK_REPORT_LOCATION
snackBarMessage = "Tippe auf die Karte, um die Position zu setzen."
}
fun consumeReopenReport() {
reopenReport = false
}
fun resetDraft() {
reportDraft = ReportDraft()
pointGraphic.geometry = null
selectedOperation = FeatureOperationType.DEFAULT
}
fun updateReportDraft(update: ReportDraft.() -> ReportDraft) {
reportDraft = reportDraft.update()
}
fun submitDraftToLayer() {
val draft = reportDraft
if (!draft.isValid) {
snackBarMessage = "Bitte Beschreibung, Typ, Position und Fotos setzen."
return
}
viewModelScope.launch {
try {
// 1) Feature lokal erstellen
val feature = serviceFeatureTable.createFeature().apply {
geometry = draft.point
attributes["Beschreibung"] = draft.beschreibung
attributes["Typ"] = draft.typ
}
// 2) Erst addFeature + applyEdits => Feature existiert am Server (ObjectID)
serviceFeatureTable.addFeature(feature).onSuccess {
// applyEditsWithPhotos macht bei dir: applyEdits -> ObjectID holen -> feature neu queryn -> attachments adden
applyEditsWithPhotos(feature as ArcGISFeature, draft.photos)
// Draft/Preview zurücksetzen (am besten nach Erfolg; fürs Erste hier ok)
resetDraft()
}.onFailure {
snackBarMessage = "Fehler beim Hinzufügen: ${it.message}"
}
} catch (e: Exception) {
snackBarMessage = "Fehler: ${e.message}"
}
}
}
}
enum class FeatureOperationType(val operationName: String, val instruction: String) {
DEFAULT("Default", ""),
DELETE("Delete feature", "Select an existing feature to delete it."),
UPDATE_ATTRIBUTE("Update attribute", "Select an existing feature to edit its attribute."),
UPDATE_GEOMETRY("Update geometry", "Select an existing feature and tap the map to move it to a new position."),
PICK_REPORT_LOCATION("Pick report location", "Tippe auf die Karte, um die Position zu setzen."),
}
data class ReportDraft(
val beschreibung: String = "",
val typ: String = "Schadenstyp wählen...",
val photos: List<ImageBitmap> = emptyList(),
val point: Point? = null
) {
val isValid: Boolean
get() =
beschreibung.isNotBlank() &&
typ != "Schadenstyp wählen..." &&
point != null &&
photos.isNotEmpty()
} }

View File

@@ -116,4 +116,7 @@ class AlbumViewModel(private val coroutineContext: CoroutineContext
} }
} }
// endregion // endregion
fun clearSelection() {
_albumViewState.value = _albumViewState.value.copy(selectedPictures = emptyList())
}
} }