diff --git a/app/src/main/java/com/example/snapandsolve/MainActivity.kt b/app/src/main/java/com/example/snapandsolve/MainActivity.kt index 9dd3cab..f22ac84 100644 --- a/app/src/main/java/com/example/snapandsolve/MainActivity.kt +++ b/app/src/main/java/com/example/snapandsolve/MainActivity.kt @@ -26,7 +26,7 @@ class MainActivity : ComponentActivity() { setContent { SnapAndSolveTheme { - MainScreen(application=application) + MainScreen(application=application, context=this) } } } diff --git a/app/src/main/java/com/example/snapandsolve/MainScreen.kt b/app/src/main/java/com/example/snapandsolve/MainScreen.kt index f5dbe77..e335cb4 100644 --- a/app/src/main/java/com/example/snapandsolve/MainScreen.kt +++ b/app/src/main/java/com/example/snapandsolve/MainScreen.kt @@ -2,6 +2,7 @@ import DamageFilterDialog import DamageListDialog import MapViewModel import android.app.Application +import android.content.Context import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add @@ -37,12 +38,12 @@ import kotlinx.coroutines.launch @Composable -fun MainScreen(modifier: Modifier = Modifier, application: Application) { +fun MainScreen(modifier: Modifier = Modifier, application: Application, context: Context) { var showReport by rememberSaveable { mutableStateOf(false) } var sliderOpen by rememberSaveable { mutableStateOf(false) } var showSettings by rememberSaveable { mutableStateOf(false) } - val mapViewModel = remember { MapViewModel(application) } + val mapViewModel = remember { MapViewModel(application, context) } val albumViewModel = remember { AlbumViewModel(Dispatchers.Default) } fun openReport() { @@ -196,19 +197,16 @@ fun ContentScreen( style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(bottom = 12.dp) ) - SliderMenuItem( text = "Einstellungen", icon = Icons.Default.Settings, onClick = onOpenSettings ) - SliderMenuItem( text = "Schäden filtern", icon = Icons.Default.FilterAlt, onClick = { showFilterDialog = true } ) - SliderMenuItem( text = "Schadensliste", icon = Icons.Default.FormatListNumbered, @@ -233,24 +231,3 @@ fun AppTopBar( ) ) } - -/* -suspend fun loadFirstAttachmentBitmap( - feature: ArcGISFeature -): ImageBitmap? { - - // Feature muss geladen sein - feature.load().getOrThrow() - - // Attachments abrufen - val attachments = feature.fetchAttachments().getOrThrow() - - val first = attachments.firstOrNull() ?: return null - - // Attachment-Daten laden - val data = first.fetchData().getOrThrow() - - val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size) - return bitmap.asImageBitmap() -} - */ \ No newline at end of file diff --git a/app/src/main/java/com/example/snapandsolve/MapViewModel.kt b/app/src/main/java/com/example/snapandsolve/MapViewModel.kt index 6dda7a1..f893dbf 100644 --- a/app/src/main/java/com/example/snapandsolve/MapViewModel.kt +++ b/app/src/main/java/com/example/snapandsolve/MapViewModel.kt @@ -1,4 +1,5 @@ import android.app.Application +import android.content.Context import android.graphics.Bitmap import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -35,7 +36,7 @@ import java.io.ByteArrayOutputStream -class MapViewModel(application: Application) : AndroidViewModel(application) { +class MapViewModel(application: Application, context: Context) : AndroidViewModel(application) { companion object { // Zentrale Definition der Schadenstypen @@ -99,7 +100,7 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { println("DEBUG: Fehler beim Laden der Tabelle: ${it.message}") } featureLayer = FeatureLayer.createWithFeatureTable(serviceFeatureTable) - featureLayer.renderer = createTypStatusRenderer() + featureLayer.renderer = createTypStatusRenderer(context) map.operationalLayers.add(featureLayer) // ===== DEBUG: Felder nach dem Hinzufügen zur Map ===== @@ -219,27 +220,9 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { when (selectedOperation) { FeatureOperationType.DEFAULT -> selectFeatureAt(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) - // RATE_FEATURE wird nicht mehr gebraucht - wird im DEFAULT mit behandelt! - } - } - - 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) - } - } - } } } @@ -431,7 +414,6 @@ class MapViewModel(application: Application) : AndroidViewModel(application) { 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."), diff --git a/app/src/main/java/com/example/snapandsolve/ui/theme/DamageFilterSystem.kt b/app/src/main/java/com/example/snapandsolve/ui/theme/DamageFilterSystem.kt index 6c01648..8681d2d 100644 --- a/app/src/main/java/com/example/snapandsolve/ui/theme/DamageFilterSystem.kt +++ b/app/src/main/java/com/example/snapandsolve/ui/theme/DamageFilterSystem.kt @@ -1,4 +1,3 @@ -import MapViewModel import MapViewModel.Companion.DAMAGE_TYPES import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState @@ -148,14 +147,7 @@ fun DamageFilterDialog( selectedFilters - type } }, - emoji = when (type) { - "Straße" -> "🛣️" - "Gehweg" -> "🚶" - "Fahrradweg" -> "🚴" - "Beleuchtung" -> "💡" - "Sonstiges" -> "📍" - else -> "•" - } + emoji = getEmojiForType(type) ) } diff --git a/app/src/main/java/com/example/snapandsolve/ui/theme/DamageListSystem.kt b/app/src/main/java/com/example/snapandsolve/ui/theme/DamageListSystem.kt index cb7f69e..837d981 100644 --- a/app/src/main/java/com/example/snapandsolve/ui/theme/DamageListSystem.kt +++ b/app/src/main/java/com/example/snapandsolve/ui/theme/DamageListSystem.kt @@ -645,11 +645,11 @@ fun formatDistance(meters: Double?): String { fun getEmojiForType(typ: String): String { return when (typ) { - "Straße" -> "🛣️" - "Gehweg" -> "🚶" - "Fahrradweg" -> "🚴" - "Beleuchtung" -> "💡" - "Sonstiges" -> "📍" + "Straße" -> String(Character.toChars(0x1F6E3)) + "Gehweg" -> String(Character.toChars(0x1F6B5)) + "Fahrradweg" -> String(Character.toChars(0x1F6B2)) + "Beleuchtung" -> String(Character.toChars(0x1F4A1)) + "Sonstiges" -> String(Character.toChars(0x1F4CC)) else -> "•" } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/snapandsolve/view/StatusSymbolRenderer.kt b/app/src/main/java/com/example/snapandsolve/view/StatusSymbolRenderer.kt index 2575d35..e8fa7aa 100644 --- a/app/src/main/java/com/example/snapandsolve/view/StatusSymbolRenderer.kt +++ b/app/src/main/java/com/example/snapandsolve/view/StatusSymbolRenderer.kt @@ -1,45 +1,108 @@ package com.example.snapandsolve.view +import android.content.Context import com.arcgismaps.Color import com.arcgismaps.mapping.symbology.* -import kotlin.collections.iterator +import com.example.snapandsolve.R -fun createTypStatusRenderer(): UniqueValueRenderer { - // Status -> Hintergrundfarbe - val statusColor = mapOf( +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.Drawable +import androidx.core.content.ContextCompat + +fun makeStatusTypeSymbol( + context: Context, + foregroundDrawableRes: Int, + statusColor: Color +): Symbol { + val white = Color.fromRgba(255, 255, 255, 255) + + // Hintergrund: weißer Kreis + Outline in Statusfarbe + val background = SimpleMarkerSymbol( + style = SimpleMarkerSymbolStyle.Circle, + color = white, + size = 20f + ).apply { + outline = SimpleLineSymbol( + style = SimpleLineSymbolStyle.Solid, + color = statusColor, + width = 2.5f + ) + } + + // Drawable aus Ressourcen laden + val drawable = ContextCompat.getDrawable(context, foregroundDrawableRes) + ?: throw IllegalArgumentException("Drawable resource not found: $foregroundDrawableRes") + + // Drawable zu Bitmap konvertieren + val bitmap = drawableToBitmap(drawable) + + // Bitmap zu BitmapDrawable konvertieren + val bitmapDrawable = BitmapDrawable(context.resources, bitmap) + + // Vordergrund: BitmapDrawable als PictureMarkerSymbol + val foreground = PictureMarkerSymbol.createWithImage(bitmapDrawable).apply { + width = 16f + height = 16f + } + + return CompositeSymbol(listOf(background, foreground)) +} + +// Hilfsfunktion: Drawable zu Bitmap konvertieren +private fun drawableToBitmap(drawable: Drawable): Bitmap { + if (drawable is BitmapDrawable) { + return drawable.bitmap + } + + // Für VectorDrawables und andere Drawable-Typen + val bitmap = Bitmap.createBitmap( + drawable.intrinsicWidth.takeIf { it > 0 } ?: 1, + drawable.intrinsicHeight.takeIf { it > 0 } ?: 1, + Bitmap.Config.ARGB_8888 + ) + + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, canvas.width, canvas.height) + drawable.draw(canvas) + + return bitmap +} + +fun createTypStatusRenderer(context: Context): UniqueValueRenderer { + val statusColorByName = mapOf( "neu" to Color.fromRgba(220, 50, 50, 255), "in Bearbeitung" to Color.fromRgba(255, 180, 0, 255), "Schaden behoben" to Color.fromRgba(60, 180, 75, 255) ) - // Typ -> Icon-Style (aus Standardbibliothek) - val typeIcon = mapOf( - "Straße" to SimpleMarkerSymbolStyle.Square, - "Gehweg" to SimpleMarkerSymbolStyle.Triangle, - "Fahrradweg" to SimpleMarkerSymbolStyle.Diamond, - "Beleuchtung" to SimpleMarkerSymbolStyle.X, - "Sonstiges" to SimpleMarkerSymbolStyle.Cross + val typeIconRes = mapOf( + "Straße" to R.drawable.motorway, + "Gehweg" to R.drawable.pedestrian, + "Fahrradweg" to R.drawable.bike, + "Beleuchtung" to R.drawable.lightbulb, + "Sonstiges" to R.drawable.pin ) val renderer = UniqueValueRenderer().apply { fieldNames.addAll(listOf("typ", "status")) - - defaultLabel = "Sonstige" - defaultSymbol = makeStatusTypeSymbol( - iconStyle = SimpleMarkerSymbolStyle.Circle, - backgroundFill = Color.fromRgba(180, 180, 180, 255) - ) } - for ((typ, iconStyle) in typeIcon) { - for ((status, bgColor) in statusColor) { + for ((typ, iconRes) in typeIconRes) { + for ((status, outlineColor) in statusColorByName) { val label = "$typ • $status" + renderer.uniqueValues.add( UniqueValue( label = label, description = label, - symbol = makeStatusTypeSymbol(iconStyle, bgColor), + symbol = makeStatusTypeSymbol( + context = context, + foregroundDrawableRes = iconRes, + statusColor = outlineColor + ), values = listOf(typ, status) ) ) @@ -49,38 +112,3 @@ fun createTypStatusRenderer(): UniqueValueRenderer { return renderer } -fun makeStatusTypeSymbol( - iconStyle: SimpleMarkerSymbolStyle, - backgroundFill: Color -): Symbol { - val white = Color.fromRgba(255, 255, 255, 255) - - // Hintergrund: Kreis in Statusfarbe - val background = SimpleMarkerSymbol( - style = SimpleMarkerSymbolStyle.Circle, - color = backgroundFill, - size = 18f - ).apply { - outline = SimpleLineSymbol( - style = SimpleLineSymbolStyle.Solid, - color = white, - width = 1.5f - ) - } - - // Vordergrund: "Icon" als Outline (weiß), ohne Füllung - val foreground = SimpleMarkerSymbol( - style = iconStyle, - color = Color.fromRgba(0, 0, 0, 0), // transparent => nur Outline sichtbar - size = 10f - ).apply { - outline = SimpleLineSymbol( - style = SimpleLineSymbolStyle.Solid, - color = white, - width = 2.0f - ) - } - - return CompositeSymbol(listOf(background, foreground)) -} - diff --git a/app/src/main/java/com/example/snapandsolve/viewmodel/NearbyDamageCheck.kt b/app/src/main/java/com/example/snapandsolve/viewmodel/NearbyDamageCheck.kt index 0f263db..3d397f7 100644 --- a/app/src/main/java/com/example/snapandsolve/viewmodel/NearbyDamageCheck.kt +++ b/app/src/main/java/com/example/snapandsolve/viewmodel/NearbyDamageCheck.kt @@ -40,7 +40,6 @@ suspend fun findNearbyDamageOfSameType( for (feature in result) { val p = feature.geometry as? Point ?: continue - // ✅ Distanz korrekt val dist = GeometryEngine.distanceOrNull(pointInLayerSR, p) ?: Double.POSITIVE_INFINITY val typ = (feature.attributes["typ"] as? String).orEmpty() diff --git a/app/src/main/res/drawable/bike.png b/app/src/main/res/drawable/bike.png new file mode 100644 index 0000000..a015a8f Binary files /dev/null and b/app/src/main/res/drawable/bike.png differ diff --git a/app/src/main/res/drawable/lightbulb.png b/app/src/main/res/drawable/lightbulb.png new file mode 100644 index 0000000..d16679b Binary files /dev/null and b/app/src/main/res/drawable/lightbulb.png differ diff --git a/app/src/main/res/drawable/motorway.png b/app/src/main/res/drawable/motorway.png new file mode 100644 index 0000000..927ba36 Binary files /dev/null and b/app/src/main/res/drawable/motorway.png differ diff --git a/app/src/main/res/drawable/pedestrian.png b/app/src/main/res/drawable/pedestrian.png new file mode 100644 index 0000000..41c5362 Binary files /dev/null and b/app/src/main/res/drawable/pedestrian.png differ diff --git a/app/src/main/res/drawable/pin.png b/app/src/main/res/drawable/pin.png new file mode 100644 index 0000000..ef6372f Binary files /dev/null and b/app/src/main/res/drawable/pin.png differ