Check location is enabled
This commit is contained in:
@@ -10,8 +10,11 @@ package io.element.android.features.location.impl.common.actions
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.location.LocationManager
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.location.LocationManagerCompat
|
||||
import androidx.core.net.toUri
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
@@ -43,6 +46,23 @@ class AndroidLocationActions(
|
||||
override fun openSettings() {
|
||||
context.openAppSettingsPage()
|
||||
}
|
||||
|
||||
override fun isLocationEnabled(): Boolean {
|
||||
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
return LocationManagerCompat.isLocationEnabled(locationManager)
|
||||
}
|
||||
|
||||
override fun openLocationSettings() {
|
||||
runCatchingExceptions {
|
||||
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(intent)
|
||||
}.onSuccess {
|
||||
Timber.v("Open location settings succeed")
|
||||
}.onFailure {
|
||||
Timber.e(it, "Open location settings failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ref: https://developer.android.com/guide/components/intents-common#ViewMap
|
||||
|
||||
@@ -13,4 +13,6 @@ import io.element.android.features.location.api.Location
|
||||
interface LocationActions {
|
||||
fun share(location: Location, label: String?)
|
||||
fun openSettings()
|
||||
fun isLocationEnabled(): Boolean
|
||||
fun openLocationSettings()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.location.impl.common.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@Composable
|
||||
internal fun LocationServiceDisabledDialog(
|
||||
onContinue: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
ConfirmationDialog(
|
||||
content = "Location services are disabled. Please enable them in your device settings to use this feature.",
|
||||
onSubmitClick = onContinue,
|
||||
onDismiss = onDismiss,
|
||||
submitText = stringResource(CommonStrings.action_continue),
|
||||
cancelText = stringResource(CommonStrings.action_cancel),
|
||||
)
|
||||
}
|
||||
@@ -25,4 +25,5 @@ sealed interface ShareLocationEvent {
|
||||
data object DismissDialog : ShareLocationEvent
|
||||
data object RequestPermissions : ShareLocationEvent
|
||||
data object OpenAppSettings : ShareLocationEvent
|
||||
data object OpenLocationSettings : ShareLocationEvent
|
||||
}
|
||||
|
||||
@@ -33,12 +33,10 @@ import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
|
||||
import io.element.android.libraries.matrix.api.room.JoinedRoom
|
||||
import io.element.android.libraries.matrix.api.room.location.AssetType
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -89,7 +87,13 @@ class ShareLocationPresenter(
|
||||
shareStaticLocation(event)
|
||||
}
|
||||
ShareLocationEvent.StartTrackingUserPosition -> when {
|
||||
permissionsState.isAnyGranted -> trackUserPosition = true
|
||||
permissionsState.isAnyGranted -> {
|
||||
if (!locationActions.isLocationEnabled()) {
|
||||
dialogState = ShareLocationState.Dialog.LocationServiceDisabled
|
||||
} else {
|
||||
trackUserPosition = true
|
||||
}
|
||||
}
|
||||
permissionsState.shouldShowRationale -> dialogState = ShareLocationState.Dialog.PermissionRationale
|
||||
else -> dialogState = ShareLocationState.Dialog.PermissionDenied
|
||||
}
|
||||
@@ -99,9 +103,19 @@ class ShareLocationPresenter(
|
||||
locationActions.openSettings()
|
||||
dialogState = ShareLocationState.Dialog.None
|
||||
}
|
||||
ShareLocationEvent.OpenLocationSettings -> {
|
||||
locationActions.openLocationSettings()
|
||||
dialogState = ShareLocationState.Dialog.None
|
||||
}
|
||||
ShareLocationEvent.RequestPermissions -> permissionsState.eventSink(PermissionsEvents.RequestPermissions)
|
||||
ShareLocationEvent.ShowLiveLocationDurationPicker -> dialogState = when {
|
||||
permissionsState.isAnyGranted -> ShareLocationState.Dialog.LiveLocationDuration
|
||||
permissionsState.isAnyGranted -> {
|
||||
if (!locationActions.isLocationEnabled()) {
|
||||
ShareLocationState.Dialog.LocationServiceDisabled
|
||||
} else {
|
||||
ShareLocationState.Dialog.LiveLocationDuration
|
||||
}
|
||||
}
|
||||
permissionsState.shouldShowRationale -> ShareLocationState.Dialog.PermissionRationale
|
||||
else -> ShareLocationState.Dialog.PermissionDenied
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ data class ShareLocationState(
|
||||
data object None : Dialog
|
||||
data object PermissionRationale : Dialog
|
||||
data object PermissionDenied : Dialog
|
||||
data object LocationServiceDisabled : Dialog
|
||||
data object LiveLocationDuration : Dialog
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ class ShareLocationStateProvider : PreviewParameterProvider<ShareLocationState>
|
||||
trackUserPosition = false,
|
||||
hasLocationPermission = false,
|
||||
),
|
||||
aShareLocationState(
|
||||
permissionDialog = ShareLocationState.Dialog.LocationServiceDisabled,
|
||||
trackUserPosition = false,
|
||||
hasLocationPermission = true,
|
||||
),
|
||||
aShareLocationState(
|
||||
permissionDialog = ShareLocationState.Dialog.None,
|
||||
trackUserPosition = false,
|
||||
|
||||
@@ -38,6 +38,7 @@ import io.element.android.features.location.impl.common.MapDefaults
|
||||
import io.element.android.features.location.impl.common.PermissionDeniedDialog
|
||||
import io.element.android.features.location.impl.common.PermissionRationaleDialog
|
||||
import io.element.android.features.location.impl.common.ui.LocationFloatingActionButton
|
||||
import io.element.android.features.location.impl.common.ui.LocationServiceDisabledDialog
|
||||
import io.element.android.features.location.impl.common.ui.MapBottomSheetScaffold
|
||||
import io.element.android.features.location.impl.common.ui.UserLocationPuck
|
||||
import io.element.android.libraries.designsystem.components.LocationPin
|
||||
@@ -90,6 +91,10 @@ fun ShareLocationView(
|
||||
onDismiss = { state.eventSink(ShareLocationEvent.DismissDialog) },
|
||||
appName = state.appName,
|
||||
)
|
||||
ShareLocationState.Dialog.LocationServiceDisabled -> LocationServiceDisabledDialog(
|
||||
onContinue = { state.eventSink(ShareLocationEvent.OpenLocationSettings) },
|
||||
onDismiss = { state.eventSink(ShareLocationEvent.DismissDialog) },
|
||||
)
|
||||
ShareLocationState.Dialog.LiveLocationDuration -> LiveLocationDurationDialog(
|
||||
onSelectDuration = { duration ->
|
||||
state.eventSink(ShareLocationEvent.StartLiveLocationShare(duration))
|
||||
|
||||
@@ -16,4 +16,5 @@ sealed interface ShowLocationEvents {
|
||||
data object DismissDialog : ShowLocationEvents
|
||||
data object RequestPermissions : ShowLocationEvents
|
||||
data object OpenAppSettings : ShowLocationEvents
|
||||
data object OpenLocationSettings : ShowLocationEvents
|
||||
}
|
||||
|
||||
@@ -72,7 +72,13 @@ class ShowLocationPresenter(
|
||||
is ShowLocationEvents.TrackMyLocation -> {
|
||||
if (event.enabled) {
|
||||
when {
|
||||
permissionsState.isAnyGranted -> isTrackMyLocation = true
|
||||
permissionsState.isAnyGranted -> {
|
||||
if (!locationActions.isLocationEnabled()) {
|
||||
permissionDialog = ShowLocationState.Dialog.LocationServiceDisabled
|
||||
} else {
|
||||
isTrackMyLocation = true
|
||||
}
|
||||
}
|
||||
permissionsState.shouldShowRationale -> permissionDialog = ShowLocationState.Dialog.PermissionRationale
|
||||
else -> permissionDialog = ShowLocationState.Dialog.PermissionDenied
|
||||
}
|
||||
@@ -85,6 +91,10 @@ class ShowLocationPresenter(
|
||||
locationActions.openSettings()
|
||||
permissionDialog = ShowLocationState.Dialog.None
|
||||
}
|
||||
ShowLocationEvents.OpenLocationSettings -> {
|
||||
locationActions.openLocationSettings()
|
||||
permissionDialog = ShowLocationState.Dialog.None
|
||||
}
|
||||
ShowLocationEvents.RequestPermissions -> permissionsState.eventSink(PermissionsEvents.RequestPermissions)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ data class ShowLocationState(
|
||||
data object None : Dialog
|
||||
data object PermissionRationale : Dialog
|
||||
data object PermissionDenied : Dialog
|
||||
data object LocationServiceDisabled : Dialog
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
|
||||
aShowLocationState(
|
||||
permissionDialog = ShowLocationState.Dialog.PermissionRationale,
|
||||
),
|
||||
aShowLocationState(
|
||||
permissionDialog = ShowLocationState.Dialog.LocationServiceDisabled,
|
||||
hasLocationPermission = true,
|
||||
),
|
||||
aShowLocationState(
|
||||
hasLocationPermission = true,
|
||||
),
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
package io.element.android.features.location.impl.show
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -23,23 +25,21 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.location.api.ShowLocationMode
|
||||
import io.element.android.features.location.impl.common.ui.LocationServiceDisabledDialog
|
||||
import io.element.android.features.location.impl.common.MapDefaults
|
||||
import io.element.android.features.location.impl.common.PermissionDeniedDialog
|
||||
import io.element.android.features.location.impl.common.PermissionRationaleDialog
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.location.impl.common.ui.LocationFloatingActionButton
|
||||
import io.element.android.features.location.impl.common.ui.LocationPinMarkers
|
||||
import io.element.android.features.location.impl.common.ui.LocationShareRow
|
||||
import io.element.android.features.location.impl.common.ui.MapBottomSheetScaffold
|
||||
import io.element.android.features.location.impl.common.ui.UserLocationPuck
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.IconButton
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -72,6 +72,10 @@ fun ShowLocationView(
|
||||
onDismiss = { state.eventSink(ShowLocationEvents.DismissDialog) },
|
||||
appName = state.appName,
|
||||
)
|
||||
ShowLocationState.Dialog.LocationServiceDisabled -> LocationServiceDisabledDialog(
|
||||
onContinue = { state.eventSink(ShowLocationEvents.OpenLocationSettings) },
|
||||
onDismiss = { state.eventSink(ShowLocationEvents.DismissDialog) },
|
||||
)
|
||||
}
|
||||
|
||||
val initialPosition = when (val mode = state.mode) {
|
||||
@@ -99,18 +103,18 @@ fun ShowLocationView(
|
||||
}
|
||||
|
||||
val scaffoldState = rememberBottomSheetScaffoldState(
|
||||
bottomSheetState = rememberStandardBottomSheetState(initialValue =
|
||||
if(state.isSheetDraggable) {
|
||||
SheetValue.PartiallyExpanded
|
||||
}else {
|
||||
SheetValue.Expanded
|
||||
}
|
||||
bottomSheetState = rememberStandardBottomSheetState(
|
||||
initialValue =
|
||||
if (state.isSheetDraggable) {
|
||||
SheetValue.PartiallyExpanded
|
||||
} else {
|
||||
SheetValue.Expanded
|
||||
}
|
||||
)
|
||||
)
|
||||
MapBottomSheetScaffold(
|
||||
//sheetPeekHeight = 180.dp,
|
||||
sheetDragHandle = if(state.isSheetDraggable) {
|
||||
{BottomSheetDefaults.DragHandle()}
|
||||
sheetDragHandle = if (state.isSheetDraggable) {
|
||||
{ BottomSheetDefaults.DragHandle() }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
@@ -130,8 +134,9 @@ fun ShowLocationView(
|
||||
},
|
||||
sheetContent = { sheetPaddings ->
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
Spacer(Modifier.height(20.dp))
|
||||
Text(
|
||||
text = "On the map",
|
||||
text = stringResource(CommonStrings.screen_static_location_sheet_title),
|
||||
style = ElementTheme.typography.fontBodyLgMedium,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
@@ -142,7 +147,11 @@ fun ShowLocationView(
|
||||
onShareClick = { state.eventSink(ShowLocationEvents.Share(locationShare.location)) },
|
||||
modifier = Modifier.clickable {
|
||||
state.eventSink(ShowLocationEvents.TrackMyLocation(false))
|
||||
val position = CameraPosition(padding = sheetPaddings, target = Position(locationShare.location.lon, locationShare.location.lat), zoom = MapDefaults.DEFAULT_ZOOM)
|
||||
val position = CameraPosition(
|
||||
padding = sheetPaddings,
|
||||
target = Position(locationShare.location.lon, locationShare.location.lat),
|
||||
zoom = MapDefaults.DEFAULT_ZOOM
|
||||
)
|
||||
coroutineScope.launch {
|
||||
cameraState.animateTo(finalPosition = position)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,9 @@ package io.element.android.features.location.impl.common.actions
|
||||
|
||||
import io.element.android.features.location.api.Location
|
||||
|
||||
class FakeLocationActions : LocationActions {
|
||||
class FakeLocationActions(
|
||||
private var isLocationEnabled: Boolean = true,
|
||||
) : LocationActions {
|
||||
var sharedLocation: Location? = null
|
||||
private set
|
||||
|
||||
@@ -20,6 +22,9 @@ class FakeLocationActions : LocationActions {
|
||||
var openSettingsInvocationsCount = 0
|
||||
private set
|
||||
|
||||
var openLocationSettingsInvocationsCount = 0
|
||||
private set
|
||||
|
||||
override fun share(location: Location, label: String?) {
|
||||
sharedLocation = location
|
||||
sharedLabel = label
|
||||
@@ -28,4 +33,16 @@ class FakeLocationActions : LocationActions {
|
||||
override fun openSettings() {
|
||||
openSettingsInvocationsCount++
|
||||
}
|
||||
|
||||
override fun isLocationEnabled(): Boolean {
|
||||
return isLocationEnabled
|
||||
}
|
||||
|
||||
override fun openLocationSettings() {
|
||||
openLocationSettingsInvocationsCount++
|
||||
}
|
||||
|
||||
fun givenLocationEnabled(enabled: Boolean) {
|
||||
isLocationEnabled = enabled
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user