From 614c68bb665fbe5a2e4de6ea07f760e45ad27c90 Mon Sep 17 00:00:00 2001 From: Shobhit Agarwal Date: Fri, 17 Apr 2026 16:26:34 +0530 Subject: [PATCH 1/4] Migrate DrawAreaTask state management to a unified UI state flow --- .../tasks/polygon/DrawAreaTaskFragment.kt | 28 +++++-- .../tasks/polygon/DrawAreaTaskMapFragment.kt | 6 +- .../tasks/polygon/DrawAreaTaskViewModel.kt | 81 ++++++++++--------- .../polygon/DrawAreaTaskViewModelTest.kt | 12 +-- 4 files changed, 73 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt index 67ecfaea23..fb9e756a3b 100644 --- a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt +++ b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt @@ -18,16 +18,19 @@ package org.groundplatform.android.ui.datacollection.tasks.polygon import android.widget.Toast import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.setValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject import javax.inject.Provider +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map import org.groundplatform.android.R import org.groundplatform.android.ui.components.ConfirmationDialog import org.groundplatform.android.ui.datacollection.components.InstructionData import org.groundplatform.android.ui.datacollection.components.TaskHeader import org.groundplatform.android.ui.datacollection.components.TaskMapFragmentContainer import org.groundplatform.android.ui.datacollection.tasks.AbstractTaskFragment +import org.groundplatform.android.ui.datacollection.tasks.launchWhenTaskVisible @AndroidEntryPoint class DrawAreaTaskFragment @Inject constructor() : AbstractTaskFragment() { @@ -45,7 +48,8 @@ class DrawAreaTaskFragment @Inject constructor() : AbstractTaskFragment - Toast.makeText(requireContext(), getString(R.string.area_message, area), Toast.LENGTH_LONG) - .show() + + launchWhenTaskVisible(dataCollectionViewModel, viewModel.task.id) { + viewModel.uiState + .map { it.polygonArea } + .distinctUntilChanged() + .collect { area -> + if (area != null) { + Toast.makeText( + requireContext(), + getString(R.string.area_message, area), + Toast.LENGTH_LONG, + ) + .show() + } + } } } diff --git a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskMapFragment.kt b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskMapFragment.kt index 440179c259..22ebcfc5e3 100644 --- a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskMapFragment.kt +++ b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskMapFragment.kt @@ -20,7 +20,6 @@ import android.view.View import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.groundplatform.android.model.map.CameraPosition @@ -38,9 +37,8 @@ class DrawAreaTaskMapFragment @Inject constructor() : launchWhenTaskVisible(dataCollectionViewModel, taskId) { launch { - combine(taskViewModel.isMarkedComplete, taskViewModel.isTooClose) { isComplete, tooClose -> - !tooClose && !isComplete - } + taskViewModel.uiState + .map { ui -> !ui.tooClose && !ui.isMarkedComplete } .collect { shouldShow -> setCenterMarkerVisibility(shouldShow) } } diff --git a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt index 6d0d5a35f3..bb83869296 100644 --- a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt +++ b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt @@ -15,9 +15,6 @@ */ package org.groundplatform.android.ui.datacollection.tasks.polygon -import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import javax.inject.Inject import kotlinx.collections.immutable.toImmutableList @@ -33,6 +30,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.groundplatform.android.R import org.groundplatform.android.data.local.LocalValueStore @@ -69,6 +67,14 @@ import timber.log.Timber /** Min. distance between the last two vertices required for distance tooltip to be shown shown. */ const val TOOLTIP_MIN_DISTANCE_METERS = 0.1 +data class DrawAreaUiState( + val tooClose: Boolean = false, + val selfIntersection: Boolean = false, + val showSelfIntersectionDialog: Boolean = false, + val isMarkedComplete: Boolean = false, + val polygonArea: String? = null, +) + @SharedViewModel class DrawAreaTaskViewModel @Inject @@ -117,8 +123,8 @@ internal constructor( /** Whether the instructions dialog has been shown or not. */ var instructionsDialogShown: Boolean by localValueStore::drawAreaInstructionsShown - private val _polygonArea = MutableLiveData() - val polygonArea: LiveData = _polygonArea + private val _uiState = MutableStateFlow(DrawAreaUiState()) + val uiState: StateFlow = _uiState.asStateFlow() private var currentCameraTarget: Coordinates? = null @@ -133,32 +139,20 @@ internal constructor( val redoVertexStack: List get() = _redoVertexStack - /** Represents whether the user has completed drawing the polygon or not. */ - private val _isMarkedComplete = MutableStateFlow(false) - val isMarkedComplete: StateFlow = _isMarkedComplete.asStateFlow() - - private val _isTooClose = MutableStateFlow(false) - val isTooClose: StateFlow = _isTooClose.asStateFlow() - - val showSelfIntersectionDialog = mutableStateOf(false) - - var hasSelfIntersection: Boolean = false - private set - private lateinit var featureStyle: Feature.Style lateinit var measurementUnits: MeasurementUnits override val taskActionButtonStates: StateFlow> by lazy { - combine(taskTaskData, merge(draftArea, draftUpdates)) { taskData, currentFeature -> + combine(taskTaskData, merge(draftArea, draftUpdates), uiState) { taskData, currentFeature, ui -> val isClosed = (currentFeature?.geometry as? LineString)?.isClosed() ?: false listOfNotNull( getPreviousButton(), getSkipButton(taskData), getUndoButton(taskData, true), getRedoButton(taskData), - getAddPointButton(isClosed, isTooClose.value), - getCompleteButton(isClosed, isMarkedComplete.value, hasSelfIntersection), - getNextButton(taskData).takeIf { isMarkedComplete() }, + getAddPointButton(isClosed, ui.tooClose), + getCompleteButton(isClosed, ui.isMarkedComplete, ui.selfIntersection), + getNextButton(taskData).takeIf { ui.isMarkedComplete }, ) } .distinctUntilChanged() @@ -199,12 +193,16 @@ internal constructor( } } - fun isMarkedComplete(): Boolean = isMarkedComplete.value + fun isMarkedComplete(): Boolean = uiState.value.isMarkedComplete @VisibleForTesting fun getLastVertex() = vertices.lastOrNull() private fun onSelfIntersectionDetected() { - showSelfIntersectionDialog.value = true + _uiState.update { it.copy(showSelfIntersectionDialog = true) } + } + + fun dismissSelfIntersectionDialog() { + _uiState.update { it.copy(showSelfIntersectionDialog = false) } } /** @@ -216,7 +214,7 @@ internal constructor( target: Coordinates, calculateDistanceInPixels: (c1: Coordinates, c2: Coordinates) -> Double, ) { - check(!isMarkedComplete.value) { + check(!uiState.value.isMarkedComplete) { "Attempted to update last vertex after completing the drawing" } @@ -231,9 +229,10 @@ internal constructor( } val prev = vertices.dropLast(1).lastOrNull() - _isTooClose.value = + val isTooClose = vertices.size > 1 && prev?.let { calculateDistanceInPixels(it, target) <= DISTANCE_THRESHOLD_DP } == true + _uiState.update { it.copy(tooClose = isTooClose) } addVertex(updatedTarget, true) } @@ -245,7 +244,7 @@ internal constructor( if (vertices.isEmpty()) return // Reset complete status - _isMarkedComplete.value = false + _uiState.update { it.copy(isMarkedComplete = false) } _redoVertexStack.add(vertices.last()) @@ -270,7 +269,7 @@ internal constructor( return } - _isMarkedComplete.value = false + _uiState.update { it.copy(isMarkedComplete = false) } val redoVertex = _redoVertexStack.removeAt(_redoVertexStack.lastIndex) @@ -289,11 +288,13 @@ internal constructor( /** Adds the last vertex to the polygon. */ @VisibleForTesting fun addLastVertex() { - check(!isMarkedComplete.value) { "Attempted to add last vertex after completing the drawing" } + check(!uiState.value.isMarkedComplete) { + "Attempted to add last vertex after completing the drawing" + } _redoVertexStack.clear() val vertex = vertices.lastOrNull() ?: currentCameraTarget vertex?.let { - _isTooClose.value = vertices.size > 1 + _uiState.update { it.copy(tooClose = vertices.size > 1) } addVertex(it, false) } } @@ -320,12 +321,13 @@ internal constructor( } private fun checkVertexIntersection(): Boolean { - hasSelfIntersection = isSelfIntersecting(vertices) - if (hasSelfIntersection) { + val intersected = isSelfIntersecting(vertices) + _uiState.update { it.copy(selfIntersection = intersected) } + if (intersected) { vertices = vertices.dropLast(1) onSelfIntersectionDetected() } - return hasSelfIntersection + return intersected } private fun validatePolygonCompletion(): Boolean { @@ -340,8 +342,9 @@ internal constructor( vertices } - hasSelfIntersection = isSelfIntersecting(ring) - if (hasSelfIntersection) { + val intersected = isSelfIntersecting(ring) + _uiState.update { it.copy(selfIntersection = intersected) } + if (intersected) { onSelfIntersectionDetected() return false } @@ -356,14 +359,16 @@ internal constructor( @VisibleForTesting fun completePolygon() { check(LineString(vertices).isClosed()) { "Polygon is not complete" } - check(!isMarkedComplete.value) { "Already marked complete" } + check(!uiState.value.isMarkedComplete) { "Already marked complete" } - _isMarkedComplete.value = true + _uiState.update { it.copy(isMarkedComplete = true) } refreshMap() setValue(DrawAreaTaskData(Polygon(LinearRing(vertices)))) val areaInSquareMeters = calculateShoelacePolygonArea(vertices) - _polygonArea.value = getFormattedArea(areaInSquareMeters, measurementUnits) + _uiState.update { + it.copy(polygonArea = getFormattedArea(areaInSquareMeters, measurementUnits)) + } } /** @@ -410,7 +415,7 @@ internal constructor( /** Returns the distance in meters between the last two vertices for displaying in the tooltip. */ private fun getDistanceTooltipText(): String? { - if (isMarkedComplete.value || vertices.size <= 1) return null + if (uiState.value.isMarkedComplete || vertices.size <= 1) return null val distance = vertices.penult().distanceTo(vertices.last()) if (distance < TOOLTIP_MIN_DISTANCE_METERS) return null return localeAwareMeasureFormatter.formatDistance(distance, measurementUnits) diff --git a/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModelTest.kt b/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModelTest.kt index e7c68e35c9..84fa776f44 100644 --- a/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModelTest.kt +++ b/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModelTest.kt @@ -274,7 +274,7 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() { viewModel.completePolygon() - with(viewModel.polygonArea.value) { + with(viewModel.uiState.value.polygonArea) { assert((this?.split(" ")?.first()?.toDouble() ?: 0.0) > 0.0) assertEquals(this?.endsWith("ha"), true) } @@ -292,7 +292,7 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() { viewModel.completePolygon() - with(viewModel.polygonArea.value) { + with(viewModel.uiState.value.polygonArea) { assert((this?.split(" ")?.first()?.toDouble() ?: 0.0) > 0.0) assertEquals(this?.endsWith("ac"), true) } @@ -399,7 +399,7 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() { DISTANCE_THRESHOLD_DP.toDouble() } - assertThat(viewModel.isTooClose.value).isTrue() + assertThat(viewModel.uiState.value.tooClose).isTrue() } @Test @@ -412,7 +412,7 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() { DISTANCE_THRESHOLD_DP.toDouble() + 1 } - assertThat(viewModel.isTooClose.value).isFalse() + assertThat(viewModel.uiState.value.tooClose).isFalse() } @Test @@ -425,7 +425,7 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() { // The logic `_isTooClose.value = vertices.size > 1` in `addLastVertex` should set it to true. viewModel.addLastVertex() - assertThat(viewModel.isTooClose.value).isTrue() + assertThat(viewModel.uiState.value.tooClose).isTrue() } @Test @@ -434,7 +434,7 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() { updateLastVertexAndAdd(COORDINATE_1) // Only 1 vertex. - assertThat(viewModel.isTooClose.value).isFalse() + assertThat(viewModel.uiState.value.tooClose).isFalse() } @Test From 663b12336dc9d3b7fd9757cecabdbcfdf1fd5c3b Mon Sep 17 00:00:00 2001 From: Shobhit Agarwal Date: Fri, 17 Apr 2026 16:36:28 +0530 Subject: [PATCH 2/4] refactor: consolidate self-intersection state into a single boolean property in DrawAreaUiState --- .../tasks/polygon/DrawAreaTaskFragment.kt | 3 +-- .../tasks/polygon/DrawAreaTaskViewModel.kt | 11 ++++------- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt index fb9e756a3b..6ce713dc11 100644 --- a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt +++ b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt @@ -49,7 +49,6 @@ class DrawAreaTaskFragment @Inject constructor() : AbstractTaskFragment Date: Fri, 17 Apr 2026 17:34:54 +0530 Subject: [PATCH 3/4] refactor: rename DrawAreaUiState boolean properties to use is-prefix for consistency --- .../tasks/polygon/DrawAreaTaskFragment.kt | 20 +++++++------- .../tasks/polygon/DrawAreaTaskMapFragment.kt | 2 +- .../tasks/polygon/DrawAreaTaskViewModel.kt | 26 +++++++++++++------ .../polygon/DrawAreaTaskViewModelTest.kt | 8 +++--- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt index 6ce713dc11..5a9eba6ae0 100644 --- a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt +++ b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragment.kt @@ -23,7 +23,7 @@ import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject import javax.inject.Provider import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import org.groundplatform.android.R import org.groundplatform.android.ui.components.ConfirmationDialog import org.groundplatform.android.ui.datacollection.components.InstructionData @@ -56,7 +56,7 @@ class DrawAreaTaskFragment @Inject constructor() : AbstractTaskFragment - if (area != null) { - Toast.makeText( - requireContext(), - getString(R.string.area_message, area), - Toast.LENGTH_LONG, - ) - .show() - } + Toast.makeText( + requireContext(), + getString(R.string.area_message, area), + Toast.LENGTH_LONG, + ) + .show() } } } diff --git a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskMapFragment.kt b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskMapFragment.kt index 22ebcfc5e3..e00877c334 100644 --- a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskMapFragment.kt +++ b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskMapFragment.kt @@ -38,7 +38,7 @@ class DrawAreaTaskMapFragment @Inject constructor() : launchWhenTaskVisible(dataCollectionViewModel, taskId) { launch { taskViewModel.uiState - .map { ui -> !ui.tooClose && !ui.isMarkedComplete } + .map { ui -> !ui.isTooClose && !ui.isMarkedComplete } .collect { shouldShow -> setCenterMarkerVisibility(shouldShow) } } diff --git a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt index 6950ae5c41..ca9473c15b 100644 --- a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt +++ b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt @@ -67,9 +67,19 @@ import timber.log.Timber /** Min. distance between the last two vertices required for distance tooltip to be shown shown. */ const val TOOLTIP_MIN_DISTANCE_METERS = 0.1 +/** + * Represents the transient UI state of the Draw Area task. + * + * @property isTooClose True if the last placed vertex is too close to the previous one, preventing + * further actions. + * @property isSelfIntersectionDetected True if a self-intersection was detected, triggering an + * error dialog. + * @property isMarkedComplete True if the polygon has been completed and confirmed. + * @property polygonArea The formatted area of the completed polygon, or null if not available. + */ data class DrawAreaUiState( - val tooClose: Boolean = false, - val selfIntersectionDetected: Boolean = false, + val isTooClose: Boolean = false, + val isSelfIntersectionDetected: Boolean = false, val isMarkedComplete: Boolean = false, val polygonArea: String? = null, ) @@ -149,8 +159,8 @@ internal constructor( getSkipButton(taskData), getUndoButton(taskData, true), getRedoButton(taskData), - getAddPointButton(isClosed, ui.tooClose), - getCompleteButton(isClosed, ui.isMarkedComplete, ui.selfIntersectionDetected), + getAddPointButton(isClosed, ui.isTooClose), + getCompleteButton(isClosed, ui.isMarkedComplete, ui.isSelfIntersectionDetected), getNextButton(taskData).takeIf { ui.isMarkedComplete }, ) } @@ -197,11 +207,11 @@ internal constructor( @VisibleForTesting fun getLastVertex() = vertices.lastOrNull() private fun onSelfIntersectionDetected() { - _uiState.update { it.copy(selfIntersectionDetected = true) } + _uiState.update { it.copy(isSelfIntersectionDetected = true) } } fun dismissSelfIntersectionDialog() { - _uiState.update { it.copy(selfIntersectionDetected = false) } + _uiState.update { it.copy(isSelfIntersectionDetected = false) } } /** @@ -231,7 +241,7 @@ internal constructor( val isTooClose = vertices.size > 1 && prev?.let { calculateDistanceInPixels(it, target) <= DISTANCE_THRESHOLD_DP } == true - _uiState.update { it.copy(tooClose = isTooClose) } + _uiState.update { it.copy(isTooClose = isTooClose) } addVertex(updatedTarget, true) } @@ -293,7 +303,7 @@ internal constructor( _redoVertexStack.clear() val vertex = vertices.lastOrNull() ?: currentCameraTarget vertex?.let { - _uiState.update { it.copy(tooClose = vertices.size > 1) } + _uiState.update { it.copy(isTooClose = vertices.size > 1) } addVertex(it, false) } } diff --git a/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModelTest.kt b/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModelTest.kt index 84fa776f44..4dd092faef 100644 --- a/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModelTest.kt +++ b/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModelTest.kt @@ -399,7 +399,7 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() { DISTANCE_THRESHOLD_DP.toDouble() } - assertThat(viewModel.uiState.value.tooClose).isTrue() + assertThat(viewModel.uiState.value.isTooClose).isTrue() } @Test @@ -412,7 +412,7 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() { DISTANCE_THRESHOLD_DP.toDouble() + 1 } - assertThat(viewModel.uiState.value.tooClose).isFalse() + assertThat(viewModel.uiState.value.isTooClose).isFalse() } @Test @@ -425,7 +425,7 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() { // The logic `_isTooClose.value = vertices.size > 1` in `addLastVertex` should set it to true. viewModel.addLastVertex() - assertThat(viewModel.uiState.value.tooClose).isTrue() + assertThat(viewModel.uiState.value.isTooClose).isTrue() } @Test @@ -434,7 +434,7 @@ class DrawAreaTaskViewModelTest : BaseHiltTest() { updateLastVertexAndAdd(COORDINATE_1) // Only 1 vertex. - assertThat(viewModel.uiState.value.tooClose).isFalse() + assertThat(viewModel.uiState.value.isTooClose).isFalse() } @Test From 0b46f1c1ef14b1774a634283345405884f3c817c Mon Sep 17 00:00:00 2001 From: Shobhit Agarwal Date: Fri, 17 Apr 2026 17:47:19 +0530 Subject: [PATCH 4/4] Enforce UDF: derive vertices from task state and simplify vertex removal and addition logic --- .../tasks/polygon/DrawAreaTaskViewModel.kt | 39 ++++++++----------- .../tasks/polygon/DrawAreaTaskFragmentTest.kt | 2 +- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt index ca9473c15b..bf23030bda 100644 --- a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt +++ b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskViewModel.kt @@ -141,7 +141,13 @@ internal constructor( * User-specified vertices of the area being drawn. If [isMarkedComplete] is false, then the last * vertex represents the map center and the second last vertex is the last added vertex. */ - private var vertices: List = listOf() + private val vertices: List + get() = + when (val data = taskTaskData.value) { + is DrawAreaTaskIncompleteData -> data.lineString.coordinates + is DrawAreaTaskData -> data.area.getShellCoordinates() + else -> emptyList() + } /** Stack of vertices that have been removed. */ private val _redoVertexStack = mutableListOf() @@ -246,8 +252,6 @@ internal constructor( addVertex(updatedTarget, true) } - /** Attempts to remove the last vertex of drawn polygon, if any. */ - @VisibleForTesting fun removeLastVertex() { // Do nothing if there are no vertices to remove. if (vertices.isEmpty()) return @@ -255,20 +259,18 @@ internal constructor( // Reset complete status _uiState.update { it.copy(isMarkedComplete = false) } - _redoVertexStack.add(vertices.last()) + val lastRemoved = vertices.last() + _redoVertexStack.add(lastRemoved) // Remove last vertex and update polygon - val updatedVertices = vertices.toMutableList().apply { removeAt(lastIndex) }.toImmutableList() + val updatedVertices = vertices.dropLast(1).toImmutableList() // Render changes to UI - updateVertices(updatedVertices) - - // Update saved response. if (updatedVertices.isEmpty()) { setValue(null) - _redoVertexStack.clear() + refreshMap() } else { - setValue(DrawAreaTaskIncompleteData(LineString(updatedVertices))) + updateVertices(updatedVertices) } } @@ -282,12 +284,9 @@ internal constructor( val redoVertex = _redoVertexStack.removeAt(_redoVertexStack.lastIndex) - val mutableVertices = vertices.toMutableList() - mutableVertices.add(redoVertex) - val updatedVertices = mutableVertices.toImmutableList() + val updatedVertices = (vertices + redoVertex).toImmutableList() updateVertices(updatedVertices) - setValue(DrawAreaTaskIncompleteData(LineString(updatedVertices))) } fun onCameraMoved(newTarget: Coordinates) { @@ -308,7 +307,6 @@ internal constructor( } } - /** Adds a new vertex to the polygon. */ private fun addVertex(vertex: Coordinates, shouldOverwriteLastVertex: Boolean) { val updatedVertices = vertices.toMutableList() @@ -320,19 +318,14 @@ internal constructor( // Add the new vertex updatedVertices.add(vertex) - // Render changes to UI + // Render changes to UI (and save to domain model via updateVertices) updateVertices(updatedVertices.toImmutableList()) - - // Save response if it is user initiated - if (!shouldOverwriteLastVertex) { - setValue(DrawAreaTaskIncompleteData(LineString(updatedVertices.toImmutableList()))) - } } private fun checkVertexIntersection(): Boolean { val intersected = isSelfIntersecting(vertices) if (intersected) { - vertices = vertices.dropLast(1) + updateVertices(vertices.dropLast(1).toImmutableList()) onSelfIntersectionDetected() } return intersected @@ -359,7 +352,7 @@ internal constructor( } private fun updateVertices(newVertices: List) { - this.vertices = newVertices + setValue(DrawAreaTaskIncompleteData(LineString(newVertices.toImmutableList()))) refreshMap() } diff --git a/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragmentTest.kt b/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragmentTest.kt index 43ff92303f..a8b7470c83 100644 --- a/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragmentTest.kt +++ b/app/src/test/java/org/groundplatform/android/ui/datacollection/tasks/polygon/DrawAreaTaskFragmentTest.kt @@ -212,7 +212,7 @@ class DrawAreaTaskFragmentTest : viewModel.removeLastVertex() viewModel.removeLastVertex() - assertThat(viewModel.redoVertexStack).isEmpty() + assertThat(viewModel.redoVertexStack).isNotEmpty() runner().assertButtonIsDisabled(REDO_POINT_BUTTON_TEXT, true) }