Skip to content

Commit

Permalink
[Capture location] Prompt for location permissions when capture locat…
Browse files Browse the repository at this point in the history
…ion task is opened (#2818)

* dialog ui added

* show dialog for the first time when enter

* added permission check

* string and view fix
  • Loading branch information
anandwana001 authored Nov 13, 2024
1 parent a9815b0 commit e2fcc7b
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ constructor(
false
}

/** Returns `true` iff the app has been granted the specified permission. */
/** Returns `true` if the app has been granted the specified permission. */
fun isGranted(permission: String): Boolean =
checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,15 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView
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.map.MapFragment
import com.google.android.ground.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -57,9 +62,46 @@ class CaptureLocationTaskMapFragment @Inject constructor() : AbstractMapFragment
return root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launch {
viewModel.enableLocationLockFlow.collect {
if (it == LocationLockEnabledState.NEEDS_ENABLE) {
showLocationPermissionDialog()
}
}
}
}

override fun onMapReady(map: MapFragment) {
super.onMapReady(map)
binding.locationLockBtn.isClickable = false
viewLifecycleOwner.lifecycleScope.launch { viewModel.onMapReady(mapViewModel) }
}

@Suppress("LabeledExpression")
private fun showLocationPermissionDialog() {
val dialogComposeView =
ComposeView(requireContext()).apply {
setContent {
val openAlertDialog = remember { mutableStateOf(true) }
when {
openAlertDialog.value -> {
AppTheme {
LocationPermissionDialog(
onCancel = {
binding.locationLockBtn.isClickable = false
openAlertDialog.value = false
},
onDismiss = { openAlertDialog.value = false },
)
}
}
}

DisposableEffect(Unit) { onDispose { (parent as? ViewGroup)?.removeView(this@apply) } }
}
}

(view as ViewGroup).addView(dialogComposeView)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ import com.google.android.ground.ui.common.BaseMapViewModel
import com.google.android.ground.ui.datacollection.tasks.AbstractTaskViewModel
import javax.inject.Inject
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

/** Location lock states relevant for attempting to enable it or not. */
private enum class LocationLockEnabledState {
enum class LocationLockEnabledState {
/** The default, unknown state. */
UNKNOWN,

Expand All @@ -44,23 +45,24 @@ class CaptureLocationTaskViewModel @Inject constructor() : AbstractTaskViewModel

private val lastLocation = MutableStateFlow<CaptureLocationTaskData?>(null)
/** Allows control for triggering the location lock programmatically. */
private val enableLocationLockFlow = MutableStateFlow(LocationLockEnabledState.UNKNOWN)
private val _enableLocationLockFlow = MutableStateFlow(LocationLockEnabledState.UNKNOWN)
val enableLocationLockFlow = _enableLocationLockFlow.asStateFlow()

suspend fun updateLocation(location: Location) {
lastLocation.emit(location.toCaptureLocationResult())
}

fun updateResponse() {
if (lastLocation.value == null) {
viewModelScope.launch { enableLocationLockFlow.emit(LocationLockEnabledState.ENABLE) }
viewModelScope.launch { _enableLocationLockFlow.emit(LocationLockEnabledState.ENABLE) }
} else {
setValue(lastLocation.value)
}
}

fun enableLocationLock() {
if (enableLocationLockFlow.value == LocationLockEnabledState.NEEDS_ENABLE) {
viewModelScope.launch { enableLocationLockFlow.emit(LocationLockEnabledState.ENABLE) }
if (_enableLocationLockFlow.value == LocationLockEnabledState.NEEDS_ENABLE) {
viewModelScope.launch { _enableLocationLockFlow.emit(LocationLockEnabledState.ENABLE) }
}
}

Expand All @@ -74,12 +76,12 @@ class CaptureLocationTaskViewModel @Inject constructor() : AbstractTaskViewModel
// Otherwise, wait to enable location lock until later.
LocationLockEnabledState.NEEDS_ENABLE
}
enableLocationLockFlow.value = locationLockEnabledState
enableLocationLockFlow.collect {
_enableLocationLockFlow.value = locationLockEnabledState
_enableLocationLockFlow.collect {
if (it == LocationLockEnabledState.ENABLE) {
// No-op if permission is already granted and location updates are enabled.
mapViewModel.enableLocationLockAndGetUpdates()
enableLocationLockFlow.value = LocationLockEnabledState.ALREADY_ENABLED
_enableLocationLockFlow.value = LocationLockEnabledState.ALREADY_ENABLED
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.ground.ui.datacollection.tasks.location

import android.content.Intent
import android.net.Uri
import android.provider.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.google.android.ground.R
import com.google.android.ground.ui.theme.AppTheme

@Composable
fun LocationPermissionDialog(onDismiss: () -> Unit, onCancel: () -> Unit) {
val context = LocalContext.current
AlertDialog(
containerColor = MaterialTheme.colorScheme.surface,
onDismissRequest = { onDismiss() },
title = {
Text(
stringResource(R.string.allow_location_title),
color = MaterialTheme.colorScheme.onSurface,
)
},
text = {
Text(
stringResource(R.string.allow_location_description),
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
},
dismissButton = {
TextButton(onClick = { onCancel() }) { Text(text = stringResource(R.string.cancel)) }
},
confirmButton = {
TextButton(
onClick = {
// Open the app settings
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", context.packageName, null)
intent.data = uri
context.startActivity(intent)
}
) {
Text(text = stringResource(R.string.allow_location_confirmation))
}
},
)
}

@Composable
@Preview
fun PreviewUserDetailsDialog() {
AppTheme { LocationPermissionDialog({}, {}) }
}
4 changes: 4 additions & 0 deletions ground/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,8 @@
<string name="data_submitted_image">Data submitted image</string>
<string name="data_collection_complete">Data collection complete!</string>
<string name="data_collection_complete_details">Your data has been saved and will be automatically synced when you’re online.</string>

<string name="allow_location_confirmation">Allow location</string>
<string name="allow_location_title">Allow location sharing</string>
<string name="allow_location_description">If you don&#8217;t allow Ground to access this device&#8217;s location, you won&#8217;t be able to continue collecting data for this site.</string>
</resources>

0 comments on commit e2fcc7b

Please sign in to comment.