diff --git a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/point/DropPinTaskMapFragment.kt b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/point/DropPinTaskMapFragment.kt index ae24394dd2..e83a5bfbbc 100644 --- a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/point/DropPinTaskMapFragment.kt +++ b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/point/DropPinTaskMapFragment.kt @@ -18,6 +18,7 @@ package org.groundplatform.android.ui.datacollection.tasks.point import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import org.groundplatform.android.ui.datacollection.tasks.AbstractTaskMapFragment import org.groundplatform.android.ui.datacollection.tasks.launchWhenTaskVisible import org.groundplatform.android.ui.map.Feature @@ -50,7 +51,10 @@ class DropPinTaskMapFragment @Inject constructor() : taskViewModel.updateCameraPosition(position) } - override fun renderFeatures(): Flow> = taskViewModel.features + override fun renderFeatures(): Flow> = + combine(taskViewModel.features, taskViewModel.existingLoiFeatures) { pinFeatures, existing -> + pinFeatures + existing + } override fun setDefaultViewPort() { val feature = taskViewModel.features.value?.firstOrNull() ?: return diff --git a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/point/DropPinTaskViewModel.kt b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/point/DropPinTaskViewModel.kt index 27dae065e3..14df8bab2f 100644 --- a/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/point/DropPinTaskViewModel.kt +++ b/app/src/main/java/org/groundplatform/android/ui/datacollection/tasks/point/DropPinTaskViewModel.kt @@ -18,8 +18,13 @@ package org.groundplatform.android.ui.datacollection.tasks.point import androidx.lifecycle.viewModelScope import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import org.groundplatform.android.data.local.LocalValueStore import org.groundplatform.android.data.uuid.OfflineUuidGenerator @@ -32,22 +37,48 @@ import org.groundplatform.android.ui.map.Feature import org.groundplatform.android.ui.util.getDefaultColor import org.groundplatform.domain.model.geometry.Point import org.groundplatform.domain.model.job.Job +import org.groundplatform.domain.model.locationofinterest.LocationOfInterest import org.groundplatform.domain.model.submission.DropPinTaskData import org.groundplatform.domain.model.submission.TaskData import org.groundplatform.domain.model.submission.isNullOrEmpty import org.groundplatform.domain.model.task.Task +import org.groundplatform.domain.repository.LocationOfInterestRepositoryInterface +import org.groundplatform.domain.repository.SurveyRepositoryInterface class DropPinTaskViewModel @Inject constructor( private val uuidGenerator: OfflineUuidGenerator, private val localValueStore: LocalValueStore, + private val loiRepository: LocationOfInterestRepositoryInterface, + private val surveyRepository: SurveyRepositoryInterface, ) : AbstractMapTaskViewModel() { private var pinColor: Int = 0 private val _features = MutableStateFlow>(emptySet()) val features: StateFlow> = _features.asStateFlow() + /** + * Features representing LOIs that already exist in the survey, shown as background context while + * the user drops a new pin. + */ + val existingLoiFeatures: StateFlow> = + surveyRepository.activeSurveyFlow + .filterNotNull() + .flatMapLatest { survey -> loiRepository.getValidLois(survey) } + .map { lois -> lois.map { loi -> loi.toExistingFeature() }.toSet() } + .stateIn(viewModelScope, WhileSubscribed(5_000), emptySet()) + + private fun LocationOfInterest.toExistingFeature(): Feature = + Feature( + id = id, + type = Feature.Type.LOCATION_OF_INTEREST, + geometry = geometry, + style = Feature.Style(job.getDefaultColor()), + clusterable = false, + selected = false, + ) + /** Whether the instructions dialog has been shown or not. */ internal var instructionsDialogShown: Boolean by localValueStore::dropPinInstructionsShown 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 93c537b4aa..81a7036bfb 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,6 +20,7 @@ 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.ui.datacollection.tasks.AbstractTaskMapFragment @@ -66,8 +67,8 @@ class DrawAreaTaskMapFragment @Inject constructor() : } override fun renderFeatures(): Flow> = - taskViewModel.draftArea.map { feature: Feature? -> - if (feature == null) setOf() else setOf(feature) + combine(taskViewModel.draftArea, taskViewModel.existingLoiFeatures) { draft, existing -> + setOfNotNull(draft) + existing } override fun onMapCameraMoved(position: CameraPosition) { 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 d13a5248ce..b0f041a375 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 @@ -27,6 +27,9 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -50,11 +53,14 @@ import org.groundplatform.domain.model.geometry.LineString import org.groundplatform.domain.model.geometry.LinearRing import org.groundplatform.domain.model.geometry.Polygon import org.groundplatform.domain.model.job.Job +import org.groundplatform.domain.model.locationofinterest.LocationOfInterest import org.groundplatform.domain.model.settings.MeasurementUnits import org.groundplatform.domain.model.submission.DrawAreaTaskData import org.groundplatform.domain.model.submission.DrawAreaTaskIncompleteData import org.groundplatform.domain.model.submission.TaskData import org.groundplatform.domain.model.task.Task +import org.groundplatform.domain.repository.LocationOfInterestRepositoryInterface +import org.groundplatform.domain.repository.SurveyRepositoryInterface import org.groundplatform.domain.usecases.user.GetUserSettingsUseCase import org.groundplatform.domain.util.calculateShoelacePolygonArea import org.groundplatform.ui.util.getFormattedArea @@ -73,12 +79,35 @@ internal constructor( private val vibrationHelper: VibrationHelper, private val localeAwareMeasureFormatter: LocaleAwareMeasureFormatter, private val getUserSettingsUseCase: GetUserSettingsUseCase, + private val loiRepository: LocationOfInterestRepositoryInterface, + private val surveyRepository: SurveyRepositoryInterface, ) : AbstractMapTaskViewModel() { /** Polygon [Feature] being drawn by the user. */ private val _draftArea: MutableStateFlow = MutableStateFlow(null) val draftArea: StateFlow = _draftArea.asStateFlow() + /** + * Features representing LOIs that already exist in the survey, shown as background context while + * the user draws a new polygon. + */ + val existingLoiFeatures: StateFlow> = + surveyRepository.activeSurveyFlow + .filterNotNull() + .flatMapLatest { survey -> loiRepository.getValidLois(survey) } + .map { lois -> lois.map { loi -> loi.toExistingFeature() }.toSet() } + .stateIn(viewModelScope, WhileSubscribed(5_000), emptySet()) + + private fun LocationOfInterest.toExistingFeature(): Feature = + Feature( + id = id, + type = Feature.Type.LOCATION_OF_INTEREST, + geometry = geometry, + style = Feature.Style(job.getDefaultColor()), + clusterable = false, + selected = false, + ) + /** * Unique identifier for the currently active draft polygon or line being drawn. *