Implement legacy-style map markers with UI model mapping.
Replace default OSM pins with round value-based icons, add DTO-to-domain-to-UI marker mapping, and normalize no-value/offline styling while keeping ownership icon behavior stubbed for future auth integration. Made-with: Cursor
This commit is contained in:
15
app/src/main/kotlin/org/db3/airmq/features/map/MapMarker.kt
Normal file
15
app/src/main/kotlin/org/db3/airmq/features/map/MapMarker.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
package org.db3.airmq.features.map
|
||||
|
||||
import org.db3.airmq.features.map.MapScreenContract.SensorType
|
||||
|
||||
data class MapMarker(
|
||||
val id: String,
|
||||
val title: String,
|
||||
val city: String?,
|
||||
val latitude: Double,
|
||||
val longitude: Double,
|
||||
val isOnline: Boolean,
|
||||
val sensorType: SensorType,
|
||||
val value: Double?,
|
||||
val isOwned: Boolean
|
||||
)
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.db3.airmq.features.map
|
||||
|
||||
import androidx.annotation.ColorRes
|
||||
import org.db3.airmq.R
|
||||
import org.db3.airmq.features.map.MapScreenContract.SensorType
|
||||
import java.util.Locale
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
object MapMarkerStyle {
|
||||
|
||||
fun formatValue(value: Double?, sensorType: SensorType): String {
|
||||
if (value == null) return "--"
|
||||
return when (sensorType) {
|
||||
SensorType.DUST -> value.roundToInt().toString()
|
||||
SensorType.RADIOACTIVITY -> formatAdaptive(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ColorRes
|
||||
fun valueColorRes(value: Double?, sensorType: SensorType): Int {
|
||||
if (value == null) return R.color.colorGrey
|
||||
return when (sensorType) {
|
||||
SensorType.DUST -> when {
|
||||
value <= 12.0 -> R.color.sensorGreen
|
||||
value <= 35.4 -> R.color.sensorYellow
|
||||
value <= 55.4 -> R.color.sensorOrange
|
||||
value <= 150.4 -> R.color.sensorRed
|
||||
value <= 250.4 -> R.color.sensorPink
|
||||
else -> R.color.sensorPurple
|
||||
}
|
||||
SensorType.RADIOACTIVITY -> R.color.sensorGreen
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatAdaptive(value: Double): String {
|
||||
val decimals = when {
|
||||
value > 10.0 -> 0
|
||||
value > 1.0 -> 1
|
||||
else -> 2
|
||||
}
|
||||
return if (decimals == 0) {
|
||||
value.roundToInt().toString()
|
||||
} else {
|
||||
String.format(Locale.US, "%.${decimals}f", value)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
package org.db3.airmq.features.map
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.Toast
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -16,8 +22,10 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
@@ -25,14 +33,23 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.db3.airmq.R
|
||||
import org.db3.airmq.features.map.MapScreenContract.Action
|
||||
import org.db3.airmq.features.map.MapScreenContract.DevicePanelState
|
||||
import org.db3.airmq.features.map.MapScreenContract.DeviceSensorType
|
||||
import org.db3.airmq.features.map.MapScreenContract.Event
|
||||
import org.db3.airmq.sdk.map.domain.MapItem
|
||||
import org.db3.airmq.features.map.MapScreenContract.SearchPanelState
|
||||
import org.db3.airmq.features.map.MapScreenContract.SearchResult
|
||||
import org.db3.airmq.features.map.MapScreenContract.SensorType
|
||||
import org.db3.airmq.features.map.MapScreenContract.State
|
||||
import org.db3.airmq.features.map.MapScreenContract.TimeRange
|
||||
import org.db3.airmq.ui.theme.AirMQTheme
|
||||
import org.osmdroid.config.Configuration
|
||||
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import org.osmdroid.views.MapView
|
||||
import org.osmdroid.views.overlay.Marker
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.core.graphics.createBitmap
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
|
||||
@Composable
|
||||
fun MapScreen(
|
||||
@@ -58,15 +75,38 @@ fun MapScreen(
|
||||
}
|
||||
}
|
||||
|
||||
MapScreenContent(
|
||||
uiState = uiState,
|
||||
onEvent = viewModel::onEvent,
|
||||
showMap = true
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MapScreenContent(
|
||||
uiState: State,
|
||||
onEvent: (Event) -> Unit,
|
||||
showMap: Boolean
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
AirMQMap(
|
||||
items = uiState.items,
|
||||
onMarkerClick = { viewModel.onEvent(Event.MarkerClicked(it)) }
|
||||
)
|
||||
if (showMap) {
|
||||
AirMQMap(
|
||||
items = uiState.items,
|
||||
onMarkerClick = { onEvent(Event.MarkerClicked(it)) }
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color(0xFFE8F1E5))
|
||||
)
|
||||
}
|
||||
|
||||
MapTopControls(
|
||||
selectedSensor = uiState.selectedTopSensor,
|
||||
onSensorSelected = { viewModel.onEvent(Event.TopSensorSelected(it)) },
|
||||
onSensorSelected = { onEvent(Event.TopSensorSelected(it)) },
|
||||
onHelpClick = {
|
||||
Toast.makeText(
|
||||
context,
|
||||
@@ -81,8 +121,8 @@ fun MapScreen(
|
||||
|
||||
if (uiState.searchPanelState == null && uiState.devicePanelState == null) {
|
||||
MapFloatingActions(
|
||||
onSearchClick = { viewModel.onEvent(Event.SearchButtonClicked) },
|
||||
onMyLocationClick = { viewModel.onEvent(Event.MyLocationClicked) },
|
||||
onSearchClick = { onEvent(Event.SearchButtonClicked) },
|
||||
onMyLocationClick = { onEvent(Event.MyLocationClicked) },
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(bottom = 92.dp, end = 16.dp)
|
||||
@@ -93,9 +133,9 @@ fun MapScreen(
|
||||
MapSearchOverlay(
|
||||
query = searchPanelState.query,
|
||||
results = searchPanelState.results,
|
||||
onQueryChanged = { viewModel.onEvent(Event.SearchQueryChanged(it)) },
|
||||
onClose = { viewModel.onEvent(Event.SearchClosed) },
|
||||
onResultClick = { viewModel.onEvent(Event.SearchResultClicked(it.id)) },
|
||||
onQueryChanged = { onEvent(Event.SearchQueryChanged(it)) },
|
||||
onClose = { onEvent(Event.SearchClosed) },
|
||||
onResultClick = { onEvent(Event.SearchResultClicked(it.id)) },
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
@@ -103,12 +143,12 @@ fun MapScreen(
|
||||
uiState.devicePanelState?.let { panelData ->
|
||||
MapDevicePanel(
|
||||
data = panelData,
|
||||
onClose = { viewModel.onEvent(Event.DevicePanelClosed) },
|
||||
onOpenDevice = { viewModel.onEvent(Event.DeviceOpenClicked) },
|
||||
onRangeSelected = { viewModel.onEvent(Event.TimeRangeSelected(it)) },
|
||||
onDateBack = { viewModel.onEvent(Event.DateBackClicked) },
|
||||
onDateForward = { viewModel.onEvent(Event.DateForwardClicked) },
|
||||
onSensorSelected = { viewModel.onEvent(Event.DeviceSensorSelected(it)) },
|
||||
onClose = { onEvent(Event.DevicePanelClosed) },
|
||||
onOpenDevice = { onEvent(Event.DeviceOpenClicked) },
|
||||
onRangeSelected = { onEvent(Event.TimeRangeSelected(it)) },
|
||||
onDateBack = { onEvent(Event.DateBackClicked) },
|
||||
onDateForward = { onEvent(Event.DateForwardClicked) },
|
||||
onSensorSelected = { onEvent(Event.DeviceSensorSelected(it)) },
|
||||
modifier = Modifier.align(Alignment.BottomCenter)
|
||||
)
|
||||
}
|
||||
@@ -129,7 +169,7 @@ fun MapScreen(
|
||||
|
||||
@Composable
|
||||
private fun AirMQMap(
|
||||
items: List<MapItem>,
|
||||
items: List<MapMarker>,
|
||||
onMarkerClick: (String) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
@@ -171,6 +211,7 @@ private fun AirMQMap(
|
||||
title = listOfNotNull(item.title, item.city).joinToString(" - ")
|
||||
subDescription = if (item.isOnline) "Online" else "Offline"
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||
icon = createMarkerIcon(map, item)
|
||||
setOnMarkerClickListener { _, _ ->
|
||||
onMarkerClick(item.id)
|
||||
true
|
||||
@@ -187,3 +228,122 @@ private fun AirMQMap(
|
||||
)
|
||||
}
|
||||
|
||||
private fun createMarkerIcon(mapView: MapView, item: MapMarker): BitmapDrawable {
|
||||
val context = mapView.context
|
||||
val iconView = LayoutInflater.from(context).inflate(R.layout.view_map_marker, null, false)
|
||||
val markerBorder = iconView.findViewById<android.view.View>(R.id.marker_border)
|
||||
val markerCenter = iconView.findViewById<android.view.View>(R.id.marker_center)
|
||||
val markerText = iconView.findViewById<TextView>(R.id.marker_text)
|
||||
val markerShade = iconView.findViewById<android.view.View>(R.id.marker_image_shade)
|
||||
val markerImage = iconView.findViewById<ImageView>(R.id.marker_image)
|
||||
|
||||
val hasValue = item.value != null
|
||||
val isNoValueStyled = !hasValue
|
||||
val colorRes = if (isNoValueStyled) {
|
||||
R.color.colorGrey
|
||||
} else {
|
||||
MapMarkerStyle.valueColorRes(item.value, item.sensorType)
|
||||
}
|
||||
val markerColor = ContextCompat.getColor(context, colorRes)
|
||||
val centerColor = if (isNoValueStyled) R.color.colorGrey else R.color.white
|
||||
markerBorder.backgroundTintList = android.content.res.ColorStateList.valueOf(markerColor)
|
||||
markerCenter.backgroundTintList =
|
||||
android.content.res.ColorStateList.valueOf(ContextCompat.getColor(context, centerColor))
|
||||
|
||||
markerText.text = MapMarkerStyle.formatValue(item.value, item.sensorType)
|
||||
if (item.isOwned && !isNoValueStyled) {
|
||||
markerImage.setImageResource(R.drawable.ic_marker_user)
|
||||
markerImage.visibility = android.view.View.VISIBLE
|
||||
markerShade.visibility = android.view.View.VISIBLE
|
||||
markerText.setTextColor(ContextCompat.getColor(context, R.color.white))
|
||||
} else {
|
||||
markerImage.visibility = android.view.View.GONE
|
||||
markerShade.visibility = android.view.View.GONE
|
||||
markerText.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
context,
|
||||
if (isNoValueStyled) R.color.white else colorRes
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
iconView.measure(
|
||||
android.view.View.MeasureSpec.makeMeasureSpec(0, android.view.View.MeasureSpec.UNSPECIFIED),
|
||||
android.view.View.MeasureSpec.makeMeasureSpec(0, android.view.View.MeasureSpec.UNSPECIFIED)
|
||||
)
|
||||
iconView.layout(0, 0, iconView.measuredWidth, iconView.measuredHeight)
|
||||
val bitmap = createBitmap(iconView.measuredWidth, iconView.measuredHeight)
|
||||
iconView.draw(Canvas(bitmap))
|
||||
return bitmap.toDrawable(context.resources)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewMapScreenDefault() {
|
||||
AirMQTheme {
|
||||
MapScreenContent(
|
||||
uiState = State(
|
||||
selectedTopSensor = SensorType.DUST,
|
||||
items = listOf(
|
||||
MapMarker(
|
||||
id = "1",
|
||||
title = "AirMQ #1",
|
||||
city = "Minsk",
|
||||
latitude = 53.9,
|
||||
longitude = 27.56,
|
||||
isOnline = true,
|
||||
sensorType = SensorType.DUST,
|
||||
value = 12.0,
|
||||
isOwned = false
|
||||
)
|
||||
)
|
||||
),
|
||||
onEvent = {},
|
||||
showMap = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewMapScreenSearch() {
|
||||
AirMQTheme {
|
||||
MapScreenContent(
|
||||
uiState = State(
|
||||
selectedTopSensor = SensorType.RADIOACTIVITY,
|
||||
searchPanelState = SearchPanelState(
|
||||
query = "Minsk",
|
||||
results = listOf(
|
||||
SearchResult(id = "1", title = "AirMQ #42", subtitle = "Minsk"),
|
||||
SearchResult(id = "2", title = "AirMQ #91", subtitle = "Salihorsk")
|
||||
)
|
||||
)
|
||||
),
|
||||
onEvent = {},
|
||||
showMap = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, showSystemUi = true)
|
||||
@Composable
|
||||
private fun PreviewMapScreenDevicePanel() {
|
||||
AirMQTheme {
|
||||
MapScreenContent(
|
||||
uiState = State(
|
||||
selectedTopSensor = SensorType.DUST,
|
||||
devicePanelState = DevicePanelState(
|
||||
id = "42",
|
||||
name = "AirMQ #42",
|
||||
status = "Online",
|
||||
selectedRange = TimeRange.DAY,
|
||||
displayedDateRange = "Today",
|
||||
selectedSensor = DeviceSensorType.DUST
|
||||
)
|
||||
),
|
||||
onEvent = {},
|
||||
showMap = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package org.db3.airmq.features.map
|
||||
|
||||
import org.db3.airmq.sdk.map.domain.MapItem
|
||||
|
||||
object MapScreenContract {
|
||||
|
||||
enum class SensorType {
|
||||
@@ -44,7 +42,7 @@ object MapScreenContract {
|
||||
|
||||
data class State(
|
||||
val isLoading: Boolean = false,
|
||||
val items: List<MapItem> = emptyList(),
|
||||
val items: List<MapMarker> = emptyList(),
|
||||
val selectedTopSensor: SensorType = SensorType.DUST,
|
||||
val searchPanelState: SearchPanelState? = null,
|
||||
val devicePanelState: DevicePanelState? = null
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.db3.airmq.features.map.MapScreenContract.SensorType
|
||||
import org.db3.airmq.features.map.MapScreenContract.State
|
||||
import org.db3.airmq.features.map.MapScreenContract.TimeRange
|
||||
import org.db3.airmq.sdk.map.MapService
|
||||
import org.db3.airmq.sdk.map.domain.MapItem
|
||||
|
||||
@HiltViewModel
|
||||
class MapViewModel @Inject constructor(
|
||||
@@ -30,6 +31,7 @@ class MapViewModel @Inject constructor(
|
||||
|
||||
private val _uiState = MutableStateFlow(State(isLoading = true))
|
||||
val uiState: StateFlow<State> = _uiState.asStateFlow()
|
||||
private var domainItems: List<MapItem> = emptyList()
|
||||
|
||||
private val _actions = MutableSharedFlow<Action>(extraBufferCapacity = 1)
|
||||
val actions: SharedFlow<Action> = _actions.asSharedFlow()
|
||||
@@ -82,6 +84,7 @@ class MapViewModel @Inject constructor(
|
||||
|
||||
is Event.TopSensorSelected -> {
|
||||
_uiState.value = _uiState.value.copy(selectedTopSensor = event.sensor)
|
||||
remapMarkers()
|
||||
}
|
||||
|
||||
is Event.MarkerClicked -> {
|
||||
@@ -141,16 +144,19 @@ class MapViewModel @Inject constructor(
|
||||
val result = runCatching { mapService.fetchMapItems() }
|
||||
_uiState.value = result.fold(
|
||||
onSuccess = { items ->
|
||||
domainItems = items
|
||||
val searchPanelState = _uiState.value.searchPanelState
|
||||
val markers = items.toMarkers(_uiState.value.selectedTopSensor)
|
||||
_uiState.value.copy(
|
||||
isLoading = false,
|
||||
items = items,
|
||||
items = markers,
|
||||
searchPanelState = searchPanelState?.copy(
|
||||
results = resolveSearchResults(searchPanelState.query)
|
||||
)
|
||||
)
|
||||
},
|
||||
onFailure = { throwable ->
|
||||
domainItems = emptyList()
|
||||
_actions.tryEmit(Action.ShowToast(throwable.message ?: "Failed to load map items"))
|
||||
val searchPanelState = _uiState.value.searchPanelState
|
||||
_uiState.value.copy(
|
||||
@@ -187,11 +193,35 @@ class MapViewModel @Inject constructor(
|
||||
TimeRange.MONTH -> "This month"
|
||||
}
|
||||
|
||||
private fun org.db3.airmq.sdk.map.domain.MapItem.toDevicePanelState(): DevicePanelState {
|
||||
val defaultSensor = when {
|
||||
title.contains("radiation", ignoreCase = true) -> DeviceSensorType.RADIOACTIVITY
|
||||
title.contains("dust", ignoreCase = true) -> DeviceSensorType.DUST
|
||||
else -> DeviceSensorType.TEMPERATURE
|
||||
private fun remapMarkers() {
|
||||
_uiState.value = _uiState.value.copy(
|
||||
items = domainItems.toMarkers(_uiState.value.selectedTopSensor)
|
||||
)
|
||||
}
|
||||
|
||||
private fun List<MapItem>.toMarkers(sensorType: SensorType): List<MapMarker> {
|
||||
return map { item ->
|
||||
MapMarker(
|
||||
id = item.id,
|
||||
title = item.title,
|
||||
city = item.city,
|
||||
latitude = item.latitude,
|
||||
longitude = item.longitude,
|
||||
isOnline = item.isOnline,
|
||||
sensorType = sensorType,
|
||||
value = when (sensorType) {
|
||||
SensorType.DUST -> item.dustValue
|
||||
SensorType.RADIOACTIVITY -> item.radioactivityValue
|
||||
},
|
||||
isOwned = false // TODO: derive from authenticated user when ownership/auth is implemented.
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MapMarker.toDevicePanelState(): DevicePanelState {
|
||||
val defaultSensor = when (sensorType) {
|
||||
SensorType.DUST -> DeviceSensorType.DUST
|
||||
SensorType.RADIOACTIVITY -> DeviceSensorType.RADIOACTIVITY
|
||||
}
|
||||
return DevicePanelState(
|
||||
id = id,
|
||||
|
||||
4
app/src/main/res/drawable/circle_marker.xml
Normal file
4
app/src/main/res/drawable/circle_marker.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="@color/white" />
|
||||
</shape>
|
||||
4
app/src/main/res/drawable/circle_marker_shade.xml
Normal file
4
app/src/main/res/drawable/circle_marker_shade.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
|
||||
<solid android:color="#66000000" />
|
||||
</shape>
|
||||
10
app/src/main/res/drawable/ic_marker_user.xml
Normal file
10
app/src/main/res/drawable/ic_marker_user.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z" />
|
||||
</vector>
|
||||
50
app/src/main/res/layout/view_map_marker.xml
Normal file
50
app/src/main/res/layout/view_map_marker.xml
Normal file
@@ -0,0 +1,50 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:padding="4dp">
|
||||
|
||||
<View
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_marker" />
|
||||
|
||||
<View
|
||||
android:id="@+id/marker_border"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_marker" />
|
||||
|
||||
<View
|
||||
android:id="@+id/marker_center"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_marker" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/marker_image"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<View
|
||||
android:id="@+id/marker_image_shade"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_marker_shade"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/marker_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -7,4 +7,11 @@
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="colorGrey">#FF9E9E9E</color>
|
||||
<color name="sensorGreen">#FF00C853</color>
|
||||
<color name="sensorYellow">#FFFFD54F</color>
|
||||
<color name="sensorOrange">#FFFF9800</color>
|
||||
<color name="sensorRed">#FFF44336</color>
|
||||
<color name="sensorPink">#FFEC407A</color>
|
||||
<color name="sensorPurple">#FF8E24AA</color>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user