Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ dependencies {

implementation(projects.features.setting.alerts)
implementation(projects.features.setting.appearance)
implementation(projects.features.setting.debugTools)
implementation(projects.features.setting.faq)
implementation(projects.features.setting.map)
implementation(projects.features.setting.whatsnew)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface RoadsRootComponent {
sealed class Child {
data class Main(val component: MainComponent) : Child()
data class Appearance(val appearanceComponent: AppearanceComponent) : Child()
data object DebugTools : Child()
data class Map(val mapSettingsComponent: MapSettingsComponent) : Child()
data class Alerts(val alertsComponent: AlertsComponent) : Child()
data class WhatsNew(val whatsNewComponent: WhatsNewComponent) : Child()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class RoadsRootComponentImpl(
override fun open(page: Page) {
when (page) {
Page.Appearance -> navigation.push(Config.Appearance)
Page.DebugTools -> navigation.push(Config.DebugTools)
Page.Map -> navigation.push(Config.MapSettings)
Page.Alerts -> navigation.push(Config.Alerts)
Page.WhatsNew -> navigation.push(Config.WhatsNew)
Expand All @@ -77,21 +78,18 @@ class RoadsRootComponentImpl(
onOpen = ::open
)
)

is Config.Appearance -> Child.Appearance(
appearanceComponent = buildAppearanceComponent(componentContext)
)

is Config.DebugTools -> Child.DebugTools
is Config.Alerts -> Child.Alerts(alertsComponent = buildAlertsComponent(componentContext))
is Config.MapSettings -> Child.Map(
mapSettingsComponent = buildMapSettingsComponent(componentContext)
)

is Config.NextFeatures -> TODO()
is Config.WhatsNew -> Child.WhatsNew(
whatsNewComponent = buildWhatsNewComponent(componentContext)
)

is Config.FAQ -> Child.FAQ(faqComponent = buildFaqComponent(componentContext))
}

Expand All @@ -102,6 +100,9 @@ class RoadsRootComponentImpl(
@Parcelize
data object Appearance : Config()

@Parcelize
data object DebugTools : Config()

@Parcelize
data object MapSettings : Config()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.egoriku.grodnoroads.screen.root.store.headlamp.HeadLampType
import com.egoriku.grodnoroads.screen.root.ui.HeadLampDialog
import com.egoriku.grodnoroads.setting.alerts.AlertsScreen
import com.egoriku.grodnoroads.setting.appearance.screen.AppearanceScreen
import com.egoriku.grodnoroads.setting.debugtools.DebugToolsScreen
import com.egoriku.grodnoroads.setting.faq.screen.FaqScreen
import com.egoriku.grodnoroads.setting.map.MapSettingsScreen
import com.egoriku.grodnoroads.setting.whatsnew.screen.WhatsNewScreen
Expand Down Expand Up @@ -44,6 +45,9 @@ fun RootContent(roadsRootComponent: RoadsRootComponent) {
) {
when (val child = it.instance) {
is Child.Main -> MainUi(component = child.component)
is Child.DebugTools -> DebugToolsScreen(
onBack = roadsRootComponent::onBack
)
is Child.Appearance -> AppearanceScreen(
appearanceComponent = child.appearanceComponent,
onBack = roadsRootComponent::onBack
Expand Down
1 change: 1 addition & 0 deletions features/setting/debugTools/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
26 changes: 26 additions & 0 deletions features/setting/debugTools/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
plugins {
id("grodnoroads.library")
id("grodnoroads.library.compose")
}

android {
namespace = "com.egoriku.grodnoroads.setting.debugtools"
}

dependencies {
implementation(projects.libraries.foundation)
implementation(projects.libraries.maps.core)
implementation(projects.libraries.maps.compose)

implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.icons)
implementation(libs.androidx.compose.ui.tooling.preview)
debugImplementation(libs.androidx.compose.ui.tooling)

implementation(libs.immutable.collections)
implementation(libs.google.maps)
implementation(libs.google.maps)

}
Empty file.
2 changes: 2 additions & 0 deletions features/setting/debugTools/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
package com.egoriku.grodnoroads.setting.debugtools

import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Remove
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.egoriku.grodnoroads.foundation.core.rememberMutableState
import com.egoriku.grodnoroads.maps.compose.GoogleMap
import com.egoriku.grodnoroads.maps.compose.MapUpdater
import com.egoriku.grodnoroads.maps.compose.impl.onMapScope
import com.egoriku.grodnoroads.maps.core.StableLatLng
import com.egoriku.grodnoroads.setting.debugtools.data.PolylineRepository
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds
import com.google.maps.android.ktx.model.cameraPosition
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf

internal sealed interface State {
data object None : State
data class Loaded(val polyline: PersistentList<Polyline>) : State
}

internal data class Polyline(
val name: String,
val points: PersistentList<LatLng>,
val bounds: PersistentList<StableLatLng> = calculateBounds(points)
) {
fun copy(points: PersistentList<LatLng>) = Polyline(
name = name,
points = points,
bounds = calculateBounds(points)
)
}

private fun calculateBounds(points: PersistentList<LatLng>): PersistentList<StableLatLng> {
val builder = LatLngBounds.builder()
for (latLng in points) {
builder.include(latLng)
}
val pointsBounds = builder.build()

return persistentListOf(
StableLatLng(
pointsBounds.northeast.latitude,
pointsBounds.northeast.longitude
),
StableLatLng(
pointsBounds.southwest.latitude,
pointsBounds.northeast.longitude
),
StableLatLng(
pointsBounds.southwest.latitude,
pointsBounds.southwest.longitude
),
StableLatLng(
pointsBounds.northeast.latitude,
pointsBounds.southwest.longitude
)
)
}

@Composable
fun DebugToolsScreen(onBack: () -> Unit) {
val repository = remember { PolylineRepository() }

Surface {
val state by repository.polylines.collectAsState()

var isMapLoaded by rememberMutableState { false }
var mapUpdater by rememberMutableState<MapUpdater?> { null }

Box(modifier = Modifier.fillMaxSize()) {
GoogleMap(
contentPadding = WindowInsets.navigationBars.asPaddingValues(),
cameraPositionProvider = {
cameraPosition {
target(LatLng(53.6687765, 23.8212226))
zoom(12.5f)
}
},
onMapLoaded = { isMapLoaded = true },
onMapUpdaterChanged = { mapUpdater = it }
)
TopActions(
modifier = Modifier.align(Alignment.TopStart),
onBack = onBack,
zoomIn = {
mapUpdater.onMapScope {
zoomIn()
}
},
zoomOut = {
mapUpdater.onMapScope {
zoomOut()
}
}
)
}

mapUpdater.onMapScope {
when (val state = state) {
is State.Loaded -> {
state.polyline.forEach {
PolygonMarker(
polyline = it,
onPointsChanged = repository::onPointsChanged
)
}
}
State.None -> {}
}
}
}
}

@Composable
private fun TopActions(
modifier: Modifier,
onBack: () -> Unit,
zoomIn: () -> Unit,
zoomOut: () -> Unit,
) {
Column(
modifier = modifier
.fillMaxWidth()
.statusBarsPadding()
.padding(horizontal = 16.dp)
) {
Row {
FilledIconButton(
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = MaterialTheme.colorScheme.surface
),
onClick = onBack
) {
Icon(
imageVector = Icons.Default.Close,
contentDescription = null
)
}
Spacer(modifier = Modifier.weight(1f))
}
FilledIconButton(
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = MaterialTheme.colorScheme.surface
),
onClick = zoomIn
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = null
)
}
FilledIconButton(
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = MaterialTheme.colorScheme.surface
),
onClick = zoomOut
) {
Icon(
imageVector = Icons.Default.Remove,
contentDescription = null
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.egoriku.grodnoroads.setting.debugtools

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import com.egoriku.grodnoroads.foundation.core.rememberMutableState
import com.egoriku.grodnoroads.maps.compose.MapUpdater
import com.egoriku.grodnoroads.maps.compose.rememberDraggableMarker
import com.egoriku.grodnoroads.maps.compose.rememberPolygon
import com.egoriku.grodnoroads.maps.core.asStable
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.ktx.model.markerOptions
import com.google.maps.android.ktx.model.polygonOptions
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate

context(MapUpdater)
@Composable
internal fun PolygonMarker(
polyline: Polyline,
onPointsChanged: (String, PersistentList<LatLng>) -> Unit
) {
var points by rememberMutableState { polyline.points }
val boundsPolygon = rememberPolygon(
tag = "bounds_${polyline.name}",
polygonOptions = {
polygonOptions {
addAll(polyline.bounds.map { it.value })
strokeColor(Color.Red.toArgb())
}
}
)

val polygon = rememberPolygon(
tag = polyline.name,
polygonOptions = {
polygonOptions {
clickable(true)
addAll(polyline.points)
}
}
)

LaunchedEffect(points) {
polygon?.points = points
}

LaunchedEffect(polyline) {
boundsPolygon?.points = polyline.bounds.map { it.value }
}

polyline.points.forEachIndexed { index, it ->
var position by rememberMutableState(it) { it.asStable() }
val marker = rememberDraggableMarker(
tag = "${polyline.name}_bounds_$index",
markerOptions = {
markerOptions {
position(it)
draggable(true)
}
},
onPositionChange = { newPosition ->
val newPoints = polyline.points.mutate {
it[index] = newPosition.value
}
onPointsChanged(polyline.name, newPoints)
points = newPoints
position = newPosition
}
)

LaunchedEffect(position) {
marker?.position = position.value
}
}
}
Loading