Skip to content

Commit

Permalink
Merge 6158790 into 4c9a5f5
Browse files Browse the repository at this point in the history
  • Loading branch information
shobhitagarwal1612 authored Jan 6, 2025
2 parents 4c9a5f5 + 6158790 commit 4c5d13a
Show file tree
Hide file tree
Showing 21 changed files with 136 additions and 230 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ abstract class AbstractMapContainerFragment : AbstractFragment() {

private fun applyMapConfig(map: MapFragment) {
val viewModel = getMapViewModel()
val config = viewModel.mapConfig
val config = getMapConfig()

// Map type
if (config.overrideMapType != null) {
Expand Down Expand Up @@ -166,4 +166,11 @@ abstract class AbstractMapContainerFragment : AbstractFragment() {

/** Provides an implementation of [BaseMapViewModel]. */
protected abstract fun getMapViewModel(): BaseMapViewModel

/** Configuration to enable/disable base map features. */
open fun getMapConfig() = DEFAULT_MAP_CONFIG

companion object {
private val DEFAULT_MAP_CONFIG: MapConfig = MapConfig(showOfflineImagery = true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,6 @@ constructor(
}
.stateIn(viewModelScope, SharingStarted.Lazily, null)

/** Configuration to enable/disable base map features. */
open val mapConfig: MapConfig = DEFAULT_MAP_CONFIG

/** Flow of current position of camera. */
var currentCameraPosition = MutableStateFlow<CameraPosition?>(null)
private set
Expand Down Expand Up @@ -275,8 +272,4 @@ constructor(
currentCameraPosition.value = newCameraPosition
mapStateRepository.setCameraPosition(newCameraPosition)
}

companion object {
private val DEFAULT_MAP_CONFIG: MapConfig = MapConfig(showOfflineImagery = true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.google.android.ground.MainViewModel
import com.google.android.ground.ui.datacollection.tasks.date.DateTaskViewModel
import com.google.android.ground.ui.datacollection.tasks.location.CaptureLocationTaskMapViewModel
import com.google.android.ground.ui.datacollection.tasks.location.CaptureLocationTaskViewModel
import com.google.android.ground.ui.datacollection.tasks.multiplechoice.MultipleChoiceTaskViewModel
import com.google.android.ground.ui.datacollection.tasks.number.NumberTaskViewModel
Expand Down Expand Up @@ -153,13 +152,6 @@ abstract class ViewModelModule {
@ViewModelKey(MapTypeViewModel::class)
abstract fun bindMapTypeViewModel(viewModel: MapTypeViewModel): ViewModel

@Binds
@IntoMap
@ViewModelKey(CaptureLocationTaskMapViewModel::class)
abstract fun bindCaptureLocationTaskMapViewModel(
viewModel: CaptureLocationTaskMapViewModel
): ViewModel

@Binds
@IntoMap
@ViewModelKey(StartupViewModel::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.ground.ui.common
package com.google.android.ground.ui.datacollection.tasks

import android.os.Bundle
import android.view.LayoutInflater
Expand All @@ -22,12 +22,17 @@ import android.view.ViewGroup
import androidx.annotation.StringRes
import androidx.hilt.navigation.fragment.hiltNavGraphViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.ground.R
import com.google.android.ground.databinding.MapTaskFragBinding
import com.google.android.ground.ui.common.AbstractMapContainerFragment
import com.google.android.ground.ui.common.BaseMapViewModel
import com.google.android.ground.ui.datacollection.DataCollectionViewModel
import com.google.android.ground.ui.map.CameraPosition
import com.google.android.ground.ui.map.Feature
import com.google.android.ground.ui.map.MapFragment
import com.google.android.ground.ui.map.gms.getAccuracyOrNull
import com.google.android.ground.ui.map.gms.toCoordinates
Expand All @@ -37,21 +42,32 @@ import java.text.DecimalFormat
import kotlinx.coroutines.launch
import org.jetbrains.annotations.MustBeInvokedByOverriders

/**
* Injects a [MapFragment] in the container with id "map" and provides shared map functionality
* including map controls like "MapType" button, "CurrentLocation" button and info card.
*/
abstract class AbstractMapFragmentWithControls : AbstractMapContainerFragment() {
abstract class AbstractTaskMapFragment<TVM : AbstractTaskViewModel> :
AbstractMapContainerFragment() {

protected lateinit var binding: MapTaskFragBinding

protected val dataCollectionViewModel: DataCollectionViewModel by
hiltNavGraphViewModels(R.id.data_collection)

protected val taskViewModel: TVM by lazy {
// Access to this viewModel is lazy for testing. This is because the NavHostController could
// not be initialized before the Fragment under test is created, leading to
// hiltNavGraphViewModels() to fail when called on launch.
dataCollectionViewModel.getTaskViewModel(taskId) as TVM
}

private lateinit var viewModel: BaseMapViewModel

protected val taskId: String by lazy {
arguments?.getString(TASK_ID_FRAGMENT_ARG_KEY) ?: error("null taskId fragment arg")
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = getViewModel(BaseMapViewModel::class.java)
}

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
Expand Down Expand Up @@ -81,15 +97,31 @@ abstract class AbstractMapFragmentWithControls : AbstractMapContainerFragment()
return binding.root
}

override fun getMapViewModel(): BaseMapViewModel = viewModel

@MustBeInvokedByOverriders
override fun onMapReady(map: MapFragment) {
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
getMapViewModel().getCurrentCameraPosition().collect { onMapCameraMoved(it) }
}
}

renderFeatures().observe(this) { map.setFeatures(it) }

// Allow the fragment to restore map viewport to previously drawn feature.
setDefaultViewPort()
}

/** Must be overridden by subclasses. */
open fun renderFeatures(): LiveData<Set<Feature>> = MutableLiveData(setOf())

/**
* This should be overridden if the fragment wants to set a custom map camera position. Default
* behavior is to move the camera to the last known position.
*/
open fun setDefaultViewPort() {}

private fun updateLocationInfoCard(
@StringRes title: Int,
locationText: String?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ import androidx.lifecycle.lifecycleScope
import com.google.android.ground.R
import com.google.android.ground.model.submission.isNotNullOrEmpty
import com.google.android.ground.model.submission.isNullOrEmpty
import com.google.android.ground.ui.common.AbstractMapFragmentWithControls.Companion.TASK_ID_FRAGMENT_ARG_KEY
import com.google.android.ground.ui.datacollection.components.ButtonAction
import com.google.android.ground.ui.datacollection.components.TaskView
import com.google.android.ground.ui.datacollection.components.TaskViewFactory
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskFragment
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskMapFragment.Companion.TASK_ID_FRAGMENT_ARG_KEY
import com.google.android.ground.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,16 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import com.google.android.ground.ui.common.AbstractMapFragmentWithControls
import com.google.android.ground.ui.common.BaseMapViewModel
import com.google.android.ground.ui.common.MapConfig
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskMapFragment
import com.google.android.ground.ui.map.MapFragment
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.launch

@AndroidEntryPoint
class CaptureLocationTaskMapFragment @Inject constructor() : AbstractMapFragmentWithControls() {

private lateinit var mapViewModel: CaptureLocationTaskMapViewModel
private val viewModel: CaptureLocationTaskViewModel by lazy {
// Access to this viewModel is lazy for testing. This is because the NavHostController could
// not be initialized before the Fragment under test is created, leading to
// hiltNavGraphViewModels() to fail when called on launch.
dataCollectionViewModel.getTaskViewModel(taskId) as CaptureLocationTaskViewModel
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapViewModel = getViewModel(CaptureLocationTaskMapViewModel::class.java)
}

override fun getMapViewModel(): BaseMapViewModel = mapViewModel
class CaptureLocationTaskMapFragment @Inject constructor() :
AbstractTaskMapFragment<CaptureLocationTaskViewModel>() {

override fun onCreateView(
inflater: LayoutInflater,
Expand All @@ -52,13 +38,17 @@ class CaptureLocationTaskMapFragment @Inject constructor() : AbstractMapFragment
): View {
val root = super.onCreateView(inflater, container, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
getMapViewModel().getLocationUpdates().collect { viewModel.updateLocation(it) }
getMapViewModel().getLocationUpdates().collect { taskViewModel.updateLocation(it) }
}
return root
}

override fun getMapConfig(): MapConfig = super.getMapConfig().copy(allowGestures = false)

override fun onMapReady(map: MapFragment) {
super.onMapReady(map)
viewLifecycleOwner.lifecycleScope.launch { viewModel.onMapReady(mapViewModel) }
viewLifecycleOwner.lifecycleScope.launch {
taskViewModel.initLocationUpdates(getMapViewModel())
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class CaptureLocationTaskViewModel @Inject constructor() : AbstractTaskViewModel
private val _enableLocationLockFlow = MutableStateFlow(LocationLockEnabledState.UNKNOWN)
val enableLocationLockFlow = _enableLocationLockFlow.asStateFlow()

suspend fun updateLocation(location: Location) {
fun updateLocation(location: Location) {
_lastLocation.update { location }
}

Expand Down Expand Up @@ -78,7 +78,10 @@ class CaptureLocationTaskViewModel @Inject constructor() : AbstractTaskViewModel
}
}

suspend fun onMapReady(mapViewModel: BaseMapViewModel) {
// TODO: Investigate if this method be pulled to BasemapViewModel since location lock is available
// Issue URL: https://github.com/google/ground-android/issues/2985
// for all map tasks.
suspend fun initLocationUpdates(mapViewModel: BaseMapViewModel) {
val locationLockEnabledState =
if (mapViewModel.hasLocationPermission()) {
// User has permission to enable location updates, enable it now.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ import androidx.compose.ui.platform.ComposeView
import com.google.android.ground.R
import com.google.android.ground.model.submission.isNotNullOrEmpty
import com.google.android.ground.model.submission.isNullOrEmpty
import com.google.android.ground.ui.common.AbstractMapFragmentWithControls.Companion.TASK_ID_FRAGMENT_ARG_KEY
import com.google.android.ground.ui.datacollection.components.ButtonAction
import com.google.android.ground.ui.datacollection.components.InstructionsDialog
import com.google.android.ground.ui.datacollection.components.TaskView
import com.google.android.ground.ui.datacollection.components.TaskViewFactory
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskFragment
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskMapFragment.Companion.TASK_ID_FRAGMENT_ARG_KEY
import com.google.android.ground.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,39 +15,27 @@
*/
package com.google.android.ground.ui.datacollection.tasks.point

import android.os.Bundle
import com.google.android.ground.ui.common.AbstractMapFragmentWithControls
import com.google.android.ground.ui.common.BaseMapViewModel
import androidx.lifecycle.LiveData
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskMapFragment
import com.google.android.ground.ui.map.CameraPosition
import com.google.android.ground.ui.map.MapFragment
import com.google.android.ground.ui.map.Feature
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject

@AndroidEntryPoint
class DropPinTaskMapFragment @Inject constructor() : AbstractMapFragmentWithControls() {
class DropPinTaskMapFragment @Inject constructor() :
AbstractTaskMapFragment<DropPinTaskViewModel>() {

private lateinit var mapViewModel: BaseMapViewModel
private val viewModel: DropPinTaskViewModel by lazy {
// Access to this viewModel is lazy for testing. This is because the NavHostController could
// not be initialized before the Fragment under test is created, leading to
// hiltNavGraphViewModels() to fail when called on launch.
dataCollectionViewModel.getTaskViewModel(taskId) as DropPinTaskViewModel
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mapViewModel = getViewModel(BaseMapViewModel::class.java)
override fun onMapCameraMoved(position: CameraPosition) {
super.onMapCameraMoved(position)
taskViewModel.updateCameraPosition(position)
}

override fun getMapViewModel(): BaseMapViewModel = mapViewModel
override fun renderFeatures(): LiveData<Set<Feature>> = taskViewModel.features

override fun onMapReady(map: MapFragment) {
super.onMapReady(map)
viewModel.features.observe(this) { map.setFeatures(it) }
}

override fun onMapCameraMoved(position: CameraPosition) {
super.onMapCameraMoved(position)
viewModel.updateCameraPosition(position)
override fun setDefaultViewPort() {
val feature = taskViewModel.features.value?.firstOrNull() ?: return
val coordinates = feature.geometry.center()
moveToPosition(coordinates)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ constructor(
features.postValue(setOf())
}

fun updateResponse(point: Point) {
private fun updateResponse(point: Point) {
setValue(DropPinTaskData(point))
dropMarker(point)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ import com.google.android.ground.R
import com.google.android.ground.databinding.FragmentDrawAreaTaskBinding
import com.google.android.ground.model.geometry.LineString
import com.google.android.ground.model.geometry.LineString.Companion.lineStringOf
import com.google.android.ground.ui.common.AbstractMapFragmentWithControls.Companion.TASK_ID_FRAGMENT_ARG_KEY
import com.google.android.ground.ui.datacollection.components.ButtonAction
import com.google.android.ground.ui.datacollection.components.InstructionsDialog
import com.google.android.ground.ui.datacollection.components.TaskButton
import com.google.android.ground.ui.datacollection.components.TaskView
import com.google.android.ground.ui.datacollection.components.TaskViewFactory
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskFragment
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskMapFragment.Companion.TASK_ID_FRAGMENT_ARG_KEY
import com.google.android.ground.ui.map.Feature
import com.google.android.ground.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint
Expand Down
Loading

0 comments on commit 4c5d13a

Please sign in to comment.