Skip to content

Commit

Permalink
Merge pull request #165 from Semper-Viventem/apply-filters-for-the-cu…
Browse files Browse the repository at this point in the history
…rrent-batch

Patch 0.27.0-beta
  • Loading branch information
Semper-Viventem authored Jan 25, 2025
2 parents 10b3901 + 2f7990f commit d2e25ee
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 140 deletions.
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ android {
minSdk = 29
targetSdk = 35

versionCode = 1708536362
versionName = "0.27.0-beta"
versionCode = 1708536363
versionName = "0.27.1-beta"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package f.cking.software.ui.devicedetails

import android.graphics.Paint
import android.view.MotionEvent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
Expand All @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
Expand All @@ -23,14 +22,14 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SuggestionChip
import androidx.compose.material3.Text
Expand All @@ -49,12 +48,13 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.TextStyle
Expand All @@ -73,6 +73,7 @@ import f.cking.software.ui.AsyncBatchProcessor
import f.cking.software.ui.map.MapView
import f.cking.software.ui.tagdialog.TagDialog
import f.cking.software.utils.graphic.GlassSystemNavbar
import f.cking.software.utils.graphic.ListItem
import f.cking.software.utils.graphic.RadarIcon
import f.cking.software.utils.graphic.RoundedBox
import f.cking.software.utils.graphic.SignalData
Expand All @@ -89,6 +90,10 @@ import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.Marker
import org.osmdroid.views.overlay.Polyline
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlay
import org.osmdroid.views.overlay.simplefastpoint.SimpleFastPointOverlayOptions
import org.osmdroid.views.overlay.simplefastpoint.SimplePointTheme
import timber.log.Timber

@OptIn(ExperimentalMaterial3Api::class)
Expand Down Expand Up @@ -367,8 +372,7 @@ object DeviceDetailsScreen {
}

@Composable
private fun HistoryPeriod(
deviceData: DeviceData,
private fun PointsStyle(
viewModel: DeviceDetailsViewModel,
) {
val dialog = rememberMaterialDialogState()
Expand All @@ -384,60 +388,80 @@ object DeviceDetailsScreen {
Column(
modifier = Modifier.padding(16.dp)
) {
Text(stringResource(R.string.change_history_period_dialog), fontSize = 20.sp, fontWeight = FontWeight.Black)
Text(stringResource(R.string.device_history_pint_style), fontSize = 20.sp, fontWeight = FontWeight.Black)
Spacer(Modifier.height(8.dp))
DeviceDetailsViewModel.HistoryPeriod.entries.forEach { period ->
val isSelected = viewModel.historyPeriod == period
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
viewModel.selectHistoryPeriodSelected(period, deviceData.address, autotunePeriod = false)
dialog.hide()
},
enabled = !isSelected,
DeviceDetailsViewModel.PointsStyle.entries.forEach { pointStyle ->
val isSelected = viewModel.pointsStyle == pointStyle

val onClick = {
viewModel.pointsStyle = pointStyle
dialog.hide()
}
Row(
modifier = Modifier.fillMaxWidth()
.clickable(onClick = onClick),
verticalAlignment = Alignment.CenterVertically,
) {
val periodDisplayName = stringResource(period.displayNameRes)
val text = if (isSelected) {
stringResource(R.string.device_details_dialog_time_period_selected, periodDisplayName)
} else {
periodDisplayName
}
Text(text = text)
RadioButton(selected = isSelected, onClick = onClick)
Spacer(Modifier.width(8.dp))
Text(text = stringResource(pointStyle.displayNameRes), color = MaterialTheme.colorScheme.onSurface)
}
}
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.clickable { dialog.show() },
ListItem(
icon = painterResource(R.drawable.ic_style),
title = stringResource(R.string.device_history_pint_style),
subtitle = stringResource(viewModel.pointsStyle.displayNameRes),
onClick = { dialog.show() }
)
}

@Composable
private fun HistoryPeriod(
deviceData: DeviceData,
viewModel: DeviceDetailsViewModel,
) {
val dialog = rememberMaterialDialogState()
ThemedDialog(
dialogState = dialog,
buttons = {
negativeButton(
stringResource(R.string.cancel),
textStyle = TextStyle(color = MaterialTheme.colorScheme.onSurface)
) { dialog.hide() }
},
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
Column(
modifier = Modifier.padding(16.dp)
) {
Column(modifier = Modifier.weight(1f)) {
Row {
Text(text = stringResource(R.string.device_details_history_period), fontSize = 18.sp)
Text(text = stringResource(viewModel.historyPeriod.displayNameRes), fontWeight = FontWeight.Bold, fontSize = 18.sp)
Text(stringResource(R.string.change_history_period_dialog), fontSize = 20.sp, fontWeight = FontWeight.Black)
Spacer(Modifier.height(8.dp))
DeviceDetailsViewModel.HistoryPeriod.entries.forEach { period ->
val isSelected = viewModel.historyPeriod == period

val onClick = {
viewModel.selectHistoryPeriodSelected(period, deviceData.address, autotunePeriod = false)
dialog.hide()
}
Row(
modifier = Modifier.fillMaxWidth()
.clickable(onClick = onClick),
verticalAlignment = Alignment.CenterVertically,
) {
RadioButton(selected = isSelected, onClick = onClick)
Spacer(Modifier.width(8.dp))
Text(text = stringResource(period.displayNameRes), color = MaterialTheme.colorScheme.onSurface)
}
Spacer(modifier = Modifier.height(4.dp))
Text(
text = stringResource(R.string.device_details_history_period_subtitle),
fontWeight = FontWeight.Light,
)
}
Spacer(modifier = Modifier.width(8.dp))
Image(
modifier = Modifier.size(24.dp),
imageVector = Icons.Default.Edit,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurface),
contentDescription = stringResource(R.string.change)
)
}
}
ListItem(
icon = painterResource(R.drawable.ic_time),
title = stringResource(R.string.device_details_history_period, stringResource(viewModel.historyPeriod.displayNameRes)),
subtitle = stringResource(R.string.device_details_history_period_subtitle),
onClick = { dialog.show() }
)
}

@Composable
Expand Down Expand Up @@ -467,6 +491,7 @@ object DeviceDetailsScreen {
}
}
if (mapIsReady) {
PointsStyle(viewModel)
HistoryPeriod(deviceData = deviceData, viewModel = viewModel)
}
}
Expand Down Expand Up @@ -555,6 +580,7 @@ object DeviceDetailsScreen {
}

var mapView: MapView? by remember { mutableStateOf(null) }
val colorScheme = MaterialTheme.colorScheme

MapView(
modifier = modifier.pointerInteropFilter { event ->
Expand Down Expand Up @@ -582,7 +608,7 @@ object DeviceDetailsScreen {
}
},
onLoad = { map ->
initMapState(map)
initMapState(map, colorScheme)
mapIsReadyToUse.invoke()
map.addMapListener(object : MapListener {
override fun onScroll(event: ScrollEvent?): Boolean {
Expand All @@ -598,17 +624,19 @@ object DeviceDetailsScreen {
},
onUpdate = { map -> mapView = map }
)
val mapColorScheme = remember { MapColorScheme(colorScheme.scrim.copy(alpha = 0.6f,), Color.Red) }

LaunchedEffect(mapView, viewModel.pointsState, viewModel.pointsState) {
LaunchedEffect(mapView, viewModel.pointsState, viewModel.pointsStyle) {
if (mapView != null) {
val mapUpdate = MapUpdate(viewModel.pointsState, viewModel.cameraState, mapView!!)
refreshMap(mapUpdate, batchProcessor)
refreshMap(mapUpdate, batchProcessor, mapColorScheme, viewModel.pointsStyle)
}
}
}

private fun initMapState(map: MapView) {
private fun initMapState(map: MapView, colorScheme: ColorScheme) {
map.setMultiTouchControls(true)
map.setBackgroundColor(colorScheme.surface.toArgb())
map.minZoomLevel = MapConfig.MIN_MAP_ZOOM
map.maxZoomLevel = MapConfig.MAX_MAP_ZOOM
map.controller.setZoom(MapConfig.MIN_MAP_ZOOM)
Expand All @@ -620,12 +648,53 @@ object DeviceDetailsScreen {
val map: MapView,
)

private data class MapColorScheme(
val lineColor: Color,
val pointColor: Color,
)

private fun refreshMap(
mapUpdate: MapUpdate,
batchProcessor: AsyncBatchProcessor<LocationModel, MapView>,
mapColorScheme: MapColorScheme,
pointsStyle: DeviceDetailsViewModel.PointsStyle,
) {

batchProcessor.process(mapUpdate.points, mapUpdate.map)
when (pointsStyle) {
DeviceDetailsViewModel.PointsStyle.MARKERS -> {
batchProcessor.process(mapUpdate.points, mapUpdate.map)
}
DeviceDetailsViewModel.PointsStyle.PATH -> {
batchProcessor.cancel()
mapUpdate.map.overlays.clear()
val points = mapUpdate.points.map { GeoPoint(it.lat, it.lng) }
val polyline = Polyline(mapUpdate.map).apply {
this.setPoints(points)
this.outlinePaint.apply {
color = mapColorScheme.lineColor.toArgb()
}
}

mapUpdate.map.overlays.add(polyline)

val pt = SimplePointTheme(points)

val paint = Paint().apply {
style = Paint.Style.FILL
setColor(mapColorScheme.pointColor.toArgb())
}

val fastPointOverlayOptions = SimpleFastPointOverlayOptions.getDefaultStyle()
.setAlgorithm(SimpleFastPointOverlayOptions.RenderingAlgorithm.MAXIMUM_OPTIMIZATION)
.setPointStyle(paint)
.setRadius(5f)

val fastPointOverlay = SimpleFastPointOverlay(pt, fastPointOverlayOptions)
mapUpdate.map.overlays.add(fastPointOverlay)
mapUpdate.map.invalidate()
}
}


when (val cameraConfig = mapUpdate.cameraState) {
is DeviceDetailsViewModel.MapCameraState.SinglePoint -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class DeviceDetailsViewModel(
var historyPeriod by mutableStateOf(DEFAULT_HISTORY_PERIOD)
var markersInLoadingState by mutableStateOf(false)
var onlineStatusData: OnlineStatus? by mutableStateOf(null)
var pointsStyle: PointsStyle by mutableStateOf(DEFAULT_POINTS_STYLE)

private var currentLocation: LocationModel? = null

Expand Down Expand Up @@ -114,18 +115,15 @@ class DeviceDetailsViewModel(
val fromTime = System.currentTimeMillis() - historyPeriod.periodMills
val fetched = locationRepository.getAllLocationsByAddress(address, fromTime = fromTime)
val nextStep = historyPeriod.next()
val prev = historyPeriod.previous()

val shouldStepBack = autotunePeriod
&& fetched.size > MAX_POINTS_FOR_AUTO_UPGRADE_PERIOD
&& prev != null

val shouldStepNext = autotunePeriod && fetched.isEmpty() && nextStep != null

if (shouldStepBack) {
selectHistoryPeriodSelected(prev!!, address, autotunePeriod = false)
} else if (shouldStepNext) {
selectHistoryPeriodSelected(nextStep!!, address, autotunePeriod)
if (shouldStepNext) {
selectHistoryPeriodSelected(nextStep, address, autotunePeriod)
}

if (fetched.size > MAX_POINTS_FOR_MARKERS) {
pointsStyle = PointsStyle.PATH
}

pointsState = fetched
Expand Down Expand Up @@ -202,6 +200,11 @@ class DeviceDetailsViewModel(
}
}

enum class PointsStyle(@StringRes val displayNameRes: Int) {
MARKERS(R.string.device_history_pint_style_markers),
PATH(R.string.device_history_pint_style_path),
}

sealed interface MapCameraState {
data class SinglePoint(
val location: LocationModel,
Expand All @@ -221,14 +224,14 @@ class DeviceDetailsViewModel(
)

companion object {
private const val MAX_POINTS_FOR_MARKERS = 5_000
private const val HISTORY_PERIOD_DAY = 24 * 60 * 60 * 1000L // 24 hours
private const val HISTORY_PERIOD_WEEK = 7 * 24 * 60 * 60 * 1000L // 1 week
private const val HISTORY_PERIOD_MONTH = 31 * 24 * 60 * 60 * 1000L // 1 month
private const val HISTORY_PERIOD_LONG = Long.MAX_VALUE
private const val MAX_POINTS_FOR_AUTO_UPGRADE_PERIOD = 20_000
private val DEFAULT_HISTORY_PERIOD = HistoryPeriod.DAY
private val ONLINE_THRESHOLD_MS =
PowerModeHelper.PowerMode.POWER_SAVING.scanDuration + PowerModeHelper.PowerMode.POWER_SAVING.scanDuration + 3000L
private val ONLINE_THRESHOLD_MS = PowerModeHelper.PowerMode.POWER_SAVING.scanDuration + PowerModeHelper.PowerMode.POWER_SAVING.scanDuration + 3000L
private val DEFAULT_POINTS_STYLE = PointsStyle.MARKERS

private val DEFAULT_MAP_CAMERA_STATE = MapCameraState.SinglePoint(
location = LocationModel(0.0, 0.0, 0),
Expand Down
Loading

0 comments on commit d2e25ee

Please sign in to comment.