Skip to content

Commit ed684e4

Browse files
committed
Result Screen - Advanced options (#613)
* WIP * WIP * WIP * WIP * more changes * remove string * don't throw exceptions * add copyright * update formatting * WIP * WIP * WIP * WIP * fix to show multiple results * address code review comments * address comments * add backhandler * add string resource * WIP * update deletion logic * update logic * changes for color picker * refactor colorpicker * add newline * Save selected color in options screen * remove padding changes * revert changes to navigation * add copyright * update indentation * clear all the starting points * address code review comments * address code review comments * address code review feedback
1 parent 0fab6c0 commit ed684e4

File tree

8 files changed

+439
-169
lines changed

8 files changed

+439
-169
lines changed

toolkit/utilitynetworks/src/main/java/com/arcgismaps/toolkit/utilitynetworks/TraceState.kt

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,6 @@ public class TraceState(
135135

136136
private val currentTraceGeometryResultsGraphics: MutableList<Graphic> = mutableListOf()
137137

138-
private val _currentScreen: MutableState<TraceNavRoute> = mutableStateOf(TraceNavRoute.TraceOptions)
139-
internal var currentScreen: State<TraceNavRoute> = _currentScreen
140-
141138
private val _completedTraces: SnapshotStateList<TraceRun> = mutableStateListOf()
142139
internal val completedTraces: List<TraceRun> = _completedTraces
143140

@@ -164,27 +161,6 @@ public class TraceState(
164161
private var _currentTraceZoomToResults: MutableState<Boolean> = mutableStateOf(true)
165162
internal var currentTraceZoomToResults: State<Boolean> = _currentTraceZoomToResults
166163

167-
private val currentTraceResultGeometriesExtent: Envelope?
168-
get() {
169-
val utilityGeometryTraceResult = currentTraceRun?.geometryTraceResult ?: return null
170-
171-
val geometries = listOf(
172-
utilityGeometryTraceResult.polygon,
173-
utilityGeometryTraceResult.polyline,
174-
utilityGeometryTraceResult.multipoint
175-
).mapNotNull { geometry ->
176-
if (geometry != null && !geometry.isEmpty) {
177-
geometry
178-
} else {
179-
null
180-
}
181-
}
182-
val combinedExtents = GeometryEngine.combineExtentsOrNull(geometries) ?: return null
183-
val expandedEnvelope = GeometryEngine.bufferOrNull(combinedExtents, 200.0) ?: return null
184-
185-
return expandedEnvelope.extent
186-
}
187-
188164
private var navigateToRoute: ((TraceNavRoute) -> Unit)? = null
189165

190166
internal fun setNavigationCallback(navigateToRoute: (TraceNavRoute) -> Unit) {
@@ -360,11 +336,18 @@ public class TraceState(
360336
}
361337
}
362338
}
339+
340+
val currentTraceResultGeometriesExtent = currentTraceGeometryResults?.let {
341+
getResultGeometriesExtent(it)
342+
}
343+
363344
currentTraceRun = TraceRun(
364345
name = _currentTraceName.value,
365346
configuration = traceConfiguration,
366347
startingPoints = _currentTraceStartingPoints.toList(),
367348
geometryResultsGraphics = currentTraceGeometryResultsGraphics.toList(),
349+
resultsGraphicExtent = currentTraceResultGeometriesExtent,
350+
resultGraphicColor = currentTraceGraphicsColorAsComposeColor,
368351
featureResults = currentTraceElementResults,
369352
functionResults = currentTraceFunctionResults,
370353
geometryTraceResult = currentTraceGeometryResults
@@ -393,6 +376,24 @@ public class TraceState(
393376
_currentTraceZoomToResults.value = true
394377
}
395378

379+
private fun getResultGeometriesExtent(utilityGeometryTraceResult: UtilityGeometryTraceResult): Envelope? {
380+
val geometries = listOf(
381+
utilityGeometryTraceResult.polygon,
382+
utilityGeometryTraceResult.polyline,
383+
utilityGeometryTraceResult.multipoint
384+
).mapNotNull { geometry ->
385+
if (geometry != null && !geometry.isEmpty) {
386+
geometry
387+
} else {
388+
null
389+
}
390+
}
391+
val combinedExtents = GeometryEngine.combineExtentsOrNull(geometries) ?: return null
392+
val expandedEnvelope = GeometryEngine.bufferOrNull(combinedExtents, 200.0) ?: return null
393+
394+
return expandedEnvelope.extent
395+
}
396+
396397
private fun createGraphicForSimpleLineSymbol(geometry: Geometry, style: SimpleLineSymbolStyle, color: Color) =
397398
Graphic(
398399
geometry = geometry,
@@ -573,6 +574,30 @@ public class TraceState(
573574
}
574575
}
575576

577+
internal fun setGraphicsColorForSelectedTraceRun(color: androidx.compose.ui.graphics.Color) {
578+
val arcgisColor = Color.fromRgba(
579+
color.red.toInt() * 255,
580+
color.green.toInt() * 255,
581+
color.blue.toInt() * 255,
582+
color.alpha.toInt() * 255
583+
)
584+
val selectedTraceRun = completedTraces[_selectedCompletedTraceIndex.value]
585+
selectedTraceRun.resultGraphicColor = color
586+
587+
// update the color of the starting points
588+
selectedTraceRun.startingPoints.forEach { startingPoint ->
589+
val symbol = startingPoint.graphic.symbol as SimpleMarkerSymbol
590+
symbol.color = arcgisColor
591+
}
592+
// update the color of the trace results graphics
593+
selectedTraceRun.geometryResultsGraphics.forEach { graphic ->
594+
if (graphic.symbol is SimpleLineSymbol) {
595+
val symbol = graphic.symbol as SimpleLineSymbol
596+
symbol.color = arcgisColor
597+
}
598+
}
599+
}
600+
576601
/**
577602
* Set whether to zoom to the results.
578603
*
@@ -597,6 +622,35 @@ public class TraceState(
597622
return completedTraces[_selectedCompletedTraceIndex.value].featureResults.filter { it.assetGroup.name == selectedAssetGroupName }
598623
}
599624

625+
internal fun clearAllResults() {
626+
_completedTraces.clear()
627+
_selectedCompletedTraceIndex.value = 0
628+
currentTraceGeometryResultsGraphics.clear()
629+
_currentTraceStartingPoints.clear()
630+
graphicsOverlay.graphics.clear()
631+
}
632+
633+
internal fun clearSelectedTraceResult() {
634+
val selectedTrace = _completedTraces[_selectedCompletedTraceIndex.value]
635+
selectedTrace.geometryResultsGraphics.forEach { graphicsOverlay.graphics.remove(it) }
636+
selectedTrace.startingPoints.forEach { it.graphic.isSelected = false }
637+
_completedTraces.removeAt(_selectedCompletedTraceIndex.value)
638+
if (_selectedCompletedTraceIndex.value - 1 >= 0) {
639+
_selectedCompletedTraceIndex.value -= 1
640+
updateSelectedStateForTraceResultsGraphics(_selectedCompletedTraceIndex.value, true)
641+
}
642+
}
643+
644+
internal suspend fun zoomToSelectedTrace() {
645+
val currentTrace = completedTraces[_selectedCompletedTraceIndex.value]
646+
val extent = currentTrace.resultsGraphicExtent ?: return
647+
mapViewProxy.setViewpointAnimated(
648+
Viewpoint(extent.extent),
649+
1.0.seconds,
650+
AnimationCurve.EaseInOutCubic
651+
)
652+
}
653+
600654
/**
601655
* Set the [error] that occurred during the trace.
602656
*
@@ -681,7 +735,8 @@ internal enum class TraceNavRoute {
681735
TraceResults,
682736
FeatureResultsDetails,
683737
StartingPointDetails,
684-
TraceError
738+
TraceError,
739+
ClearResults
685740
}
686741

687742
@Immutable
@@ -703,6 +758,8 @@ internal data class TraceRun(
703758
val configuration: UtilityNamedTraceConfiguration,
704759
val startingPoints: List<StartingPoint>,
705760
val geometryResultsGraphics: List<Graphic>,
761+
val resultsGraphicExtent: Envelope? = null,
762+
var resultGraphicColor: androidx.compose.ui.graphics.Color,
706763
val featureResults: List<UtilityElement>,
707764
val functionResults: List<UtilityTraceFunctionOutput>,
708765
val geometryTraceResult: UtilityGeometryTraceResult?
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/*
2+
* Copyright 2024 Esri
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.arcgismaps.toolkit.utilitynetworks.internal.util
18+
19+
import androidx.compose.foundation.background
20+
import androidx.compose.foundation.clickable
21+
import androidx.compose.foundation.layout.Arrangement
22+
import androidx.compose.foundation.layout.Box
23+
import androidx.compose.foundation.layout.PaddingValues
24+
import androidx.compose.foundation.layout.Row
25+
import androidx.compose.foundation.layout.fillMaxWidth
26+
import androidx.compose.foundation.layout.height
27+
import androidx.compose.foundation.layout.padding
28+
import androidx.compose.foundation.layout.size
29+
import androidx.compose.foundation.shape.CircleShape
30+
import androidx.compose.foundation.shape.RoundedCornerShape
31+
import androidx.compose.material3.DropdownMenu
32+
import androidx.compose.material3.DropdownMenuItem
33+
import androidx.compose.material3.MaterialTheme
34+
import androidx.compose.material3.Switch
35+
import androidx.compose.runtime.Composable
36+
import androidx.compose.runtime.LaunchedEffect
37+
import androidx.compose.runtime.MutableState
38+
import androidx.compose.runtime.getValue
39+
import androidx.compose.runtime.mutableStateOf
40+
import androidx.compose.runtime.remember
41+
import androidx.compose.runtime.saveable.Saver
42+
import androidx.compose.runtime.saveable.listSaver
43+
import androidx.compose.runtime.saveable.rememberSaveable
44+
import androidx.compose.runtime.setValue
45+
import androidx.compose.ui.Alignment
46+
import androidx.compose.ui.Modifier
47+
import androidx.compose.ui.draw.clip
48+
import androidx.compose.ui.graphics.Color
49+
import androidx.compose.ui.res.stringResource
50+
import androidx.compose.ui.semantics.contentDescription
51+
import androidx.compose.ui.semantics.semantics
52+
import androidx.compose.ui.tooling.preview.Preview
53+
import androidx.compose.ui.unit.DpOffset
54+
import androidx.compose.ui.unit.dp
55+
import com.arcgismaps.toolkit.utilitynetworks.R
56+
import com.arcgismaps.toolkit.utilitynetworks.ui.ReadOnlyTextField
57+
import com.arcgismaps.toolkit.utilitynetworks.ui.TraceColors
58+
59+
@Composable
60+
internal fun AdvancedOptionsRow(name: String, modifier: Modifier = Modifier, trailingTool: @Composable () -> Unit) {
61+
Row(
62+
modifier = modifier
63+
.fillMaxWidth()
64+
.height(80.dp)
65+
.padding(10.dp),
66+
verticalAlignment = Alignment.CenterVertically,
67+
horizontalArrangement = Arrangement.SpaceBetween
68+
) {
69+
ReadOnlyTextField(
70+
text = name,
71+
modifier = Modifier
72+
.padding(horizontal = 2.dp)
73+
.weight(1f)
74+
.align(Alignment.CenterVertically),
75+
)
76+
77+
trailingTool()
78+
}
79+
}
80+
81+
/**
82+
* A simple ColorPicker which spans the colors defined in [TraceColors.colors].
83+
*
84+
* @since 200.6.0
85+
*/
86+
@Composable
87+
internal fun ColorPicker(selectedColor: Color, onColorChanged: (Color) -> Unit = {}) {
88+
var currentSelectedColor by rememberSaveable(saver = ColorSaver.Saver()) { mutableStateOf(selectedColor) }
89+
LaunchedEffect(selectedColor) {
90+
currentSelectedColor = selectedColor
91+
}
92+
var displayPicker by rememberSaveable { mutableStateOf(false) }
93+
Box {
94+
TraceColors.SpectralRing(
95+
currentSelectedColor,
96+
modifier = Modifier
97+
.padding(4.dp)
98+
.size(36.dp)
99+
.clip(CircleShape)
100+
.clickable {
101+
displayPicker = true
102+
}
103+
)
104+
105+
MaterialTheme(shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(16.dp))) {
106+
DropdownMenu(
107+
expanded = displayPicker,
108+
offset = DpOffset.Zero,
109+
onDismissRequest = { displayPicker = false },
110+
) {
111+
DropdownMenuItem(
112+
text = {
113+
Row(
114+
verticalAlignment = Alignment.CenterVertically,
115+
horizontalArrangement = Arrangement.SpaceBetween
116+
) {
117+
TraceColors.colors.forEach {
118+
Box(modifier = Modifier
119+
.size(40.dp)
120+
.padding(8.dp)
121+
.clip(CircleShape)
122+
.background(it)
123+
.clickable {
124+
currentSelectedColor = it
125+
displayPicker = false
126+
onColorChanged(currentSelectedColor)
127+
}
128+
)
129+
}
130+
131+
}
132+
},
133+
onClick = { /* No action needed here */ },
134+
contentPadding = PaddingValues(vertical = 0.dp, horizontal = 10.dp)
135+
)
136+
}
137+
}
138+
}
139+
}
140+
141+
private object ColorSaver {
142+
fun Saver(): Saver<MutableState<Color>, Any> = listSaver(
143+
save = {
144+
listOf(
145+
it.value.component1(),
146+
it.value.component2(),
147+
it.value.component3(),
148+
it.value.component4()
149+
)
150+
},
151+
restore = {
152+
mutableStateOf(Color(red = it[0], green = it[1], blue = it[2], alpha = it[3]))
153+
}
154+
)
155+
}
156+
157+
@Preview(showBackground = true)
158+
@Composable
159+
private fun AdvancedOptionsRowPreview() {
160+
var isEnabled by remember { mutableStateOf(false) }
161+
AdvancedOptionsRow(name = stringResource(id = R.string.zoom_to_result)) {
162+
Switch(
163+
checked = isEnabled,
164+
onCheckedChange = { newState ->
165+
isEnabled = newState
166+
},
167+
modifier = Modifier
168+
.semantics { contentDescription = "switch" }
169+
.padding(horizontal = 4.dp),
170+
enabled = isEnabled
171+
)
172+
}
173+
}

toolkit/utilitynetworks/src/main/java/com/arcgismaps/toolkit/utilitynetworks/internal/util/TitleRow.kt

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@ import androidx.compose.ui.unit.dp
4444
import com.arcgismaps.toolkit.utilitynetworks.R
4545

4646
@Composable
47-
internal fun Title(name: String, onZoomTo: () -> Unit, onDelete: () -> Unit) {
47+
internal fun Title(
48+
name: String,
49+
onZoomTo: () -> Unit,
50+
onDelete: () -> Unit,
51+
showZoomToOption: Boolean = true,
52+
) {
4853
var expanded by rememberSaveable { mutableStateOf(false) }
4954
Row(
5055
modifier = Modifier
@@ -83,20 +88,22 @@ internal fun Title(name: String, onZoomTo: () -> Unit, onDelete: () -> Unit) {
8388
onDismissRequest = {
8489
expanded = false
8590
}) {
86-
DropdownMenuItem(
87-
text = { Text(stringResource(R.string.zoom_to)) },
88-
onClick = {
89-
expanded = false
90-
onZoomTo()
91-
},
92-
leadingIcon = {
93-
Icon(
94-
Icons.Outlined.LocationOn, contentDescription = stringResource(
95-
R.string.zoom_to
91+
if (showZoomToOption) {
92+
DropdownMenuItem(
93+
text = { Text(stringResource(R.string.zoom_to)) },
94+
onClick = {
95+
expanded = false
96+
onZoomTo()
97+
},
98+
leadingIcon = {
99+
Icon(
100+
Icons.Outlined.LocationOn, contentDescription = stringResource(
101+
R.string.zoom_to
102+
)
96103
)
97-
)
98-
}
99-
)
104+
}
105+
)
106+
}
100107
DropdownMenuItem(
101108
text = { Text(stringResource(R.string.delete)) },
102109
onClick = {

0 commit comments

Comments
 (0)