diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt index 46c7ae976d9..37f5badfc80 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerComponent.kt @@ -17,6 +17,7 @@ */ package com.wire.android.ui.home.messagecomposer.location +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope @@ -30,9 +31,14 @@ import androidx.compose.material.icons.filled.Send import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme import androidx.compose.material3.SheetValue +import androidx.compose.material3.SnackbarDuration +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -40,6 +46,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import com.wire.android.R import com.wire.android.ui.common.Icon @@ -58,6 +65,7 @@ import com.wire.android.ui.theme.wireTypography import com.wire.android.util.orDefault import com.wire.android.util.permission.PermissionsDeniedRequestDialog import com.wire.android.util.permission.rememberCurrentLocationFlow +import kotlinx.coroutines.launch /** * Component to pick the current location to send. @@ -104,42 +112,91 @@ fun LocationPickerComponent( } } add { - Column( + Box( modifier = Modifier - .align(alignment = Alignment.Start) - .padding(horizontal = dimensions().spacing16x) .wrapContentHeight() .fillMaxWidth() ) { - WirePrimaryButton( - onClick = { - onLocationPicked(geoLocatedAddress!!) - onLocationClosed() - }, - leadingIcon = Icons.Filled.Send.Icon(Modifier.padding(end = dimensions().spacing8x)), - text = stringResource(id = R.string.content_description_send_button), - state = if (isLocationLoading || geoLocatedAddress == null) { - WireButtonState.Disabled - } else { - WireButtonState.Default + if (showLocationSharingError) { + LocationErrorMessage { + coroutineScope.launch { + sheetState.hide() + viewModel.onLocationSharingErrorDialogDiscarded() + onLocationClosed() + } } + } + SendLocationButton( + isLocationLoading = isLocationLoading, + geoLocatedAddress = geoLocatedAddress, + onLocationPicked = onLocationPicked, + onLocationClosed = onLocationClosed ) - VerticalSpace.x16() } } } ) + + if (showPermissionDeniedDialog) { + PermissionsDeniedRequestDialog( + body = R.string.location_app_permission_dialog_body, + onDismiss = { + viewModel.onPermissionsDialogDiscarded() + onLocationClosed() + } + ) + } } } +} - if (viewModel.state.showPermissionDeniedDialog) { - PermissionsDeniedRequestDialog( - body = R.string.location_app_permission_dialog_body, - onDismiss = { - viewModel.onPermissionsDialogDiscarded() +@Composable +private fun SendLocationButton( + isLocationLoading: Boolean, + geoLocatedAddress: GeoLocatedAddress?, + onLocationPicked: (GeoLocatedAddress) -> Unit, + onLocationClosed: () -> Unit +) { + Column( + modifier = Modifier + .padding(horizontal = dimensions().spacing16x) + .wrapContentHeight() + .fillMaxWidth() + ) { + WirePrimaryButton( + onClick = { + onLocationPicked(geoLocatedAddress!!) onLocationClosed() + }, + leadingIcon = Icons.Filled.Send.Icon(Modifier.padding(end = dimensions().spacing8x)), + text = stringResource(id = R.string.content_description_send_button), + state = if (isLocationLoading || geoLocatedAddress == null) { + WireButtonState.Disabled + } else { + WireButtonState.Default } ) + VerticalSpace.x16() + } +} + +@Composable +private fun LocationErrorMessage( + message: String = stringResource(id = R.string.location_could_not_be_shared), + onLocationClosed: () -> Unit +) { + Box(Modifier.zIndex(Float.MAX_VALUE), contentAlignment = Alignment.BottomCenter) { + val snackbarHostState = remember { SnackbarHostState() } + LaunchedEffect(snackbarHostState) { + val result = snackbarHostState.showSnackbar(message = message, duration = SnackbarDuration.Short) + when (result) { + SnackbarResult.Dismissed -> onLocationClosed() + SnackbarResult.ActionPerformed -> { + /* do nothing */ + } + } + } + SnackbarHost(hostState = snackbarHostState) } } diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerState.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerState.kt index e8d10c30848..d0f2d55d703 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerState.kt @@ -27,5 +27,6 @@ data class LocationPickerState( val isLocationLoading: Boolean = false, val isPermissionDiscarded: Boolean = false, val showPermissionDeniedDialog: Boolean = false, + val showLocationSharingError: Boolean = false, val wireModalSheetState: WireModalSheetState = WireModalSheetState(SheetValue.Hidden) ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerViewModel.kt index fbde6c867d7..23c685054e1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerViewModel.kt @@ -26,6 +26,7 @@ import android.location.LocationManager import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue +import androidx.core.location.LocationManagerCompat import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.android.gms.location.LocationServices @@ -40,6 +41,7 @@ import javax.inject.Inject @HiltViewModel class LocationPickerViewModel @Inject constructor() : ViewModel() { + var state: LocationPickerState by mutableStateOf(LocationPickerState()) private set @@ -47,16 +49,36 @@ class LocationPickerViewModel @Inject constructor() : ViewModel() { state = state.copy(showPermissionDeniedDialog = false) } + fun onLocationSharingErrorDialogDiscarded() { + state = state.copy(showLocationSharingError = false) + } + fun onPermissionsDenied() { state = state.copy(showPermissionDeniedDialog = true) } private fun toStartLoadingLocationState() { - state = state.copy(isLocationLoading = true, geoLocatedAddress = null) + state = state.copy( + showLocationSharingError = false, + isLocationLoading = true, + geoLocatedAddress = null + ) } private fun toLocationLoadedState(geoLocatedAddress: GeoLocatedAddress) { - state = state.copy(isLocationLoading = false, geoLocatedAddress = geoLocatedAddress) + state = state.copy( + showLocationSharingError = false, + isLocationLoading = false, + geoLocatedAddress = geoLocatedAddress + ) + } + + private fun toLocationError() { + state = state.copy( + showLocationSharingError = true, + isLocationLoading = false, + geoLocatedAddress = null, + ) } fun getCurrentLocation(context: Context) { @@ -74,23 +96,36 @@ class LocationPickerViewModel @Inject constructor() : ViewModel() { @SuppressLint("MissingPermission") private fun getLocationWithGms(context: Context) = viewModelScope.launch { appLogger.d("Getting location with GMS") - val locationProvider = LocationServices.getFusedLocationProviderClient(context) - val currentLocation = locationProvider.getCurrentLocation(PRIORITY_HIGH_ACCURACY, CancellationTokenSource().token).await() - val address = Geocoder(context).getFromLocation(currentLocation.latitude, currentLocation.longitude, 1).orEmpty() - toLocationLoadedState(GeoLocatedAddress(address.firstOrNull(), currentLocation)) + if (isLocationServicesEnabled(context)) { + val locationProvider = LocationServices.getFusedLocationProviderClient(context) + val currentLocation = locationProvider.getCurrentLocation(PRIORITY_HIGH_ACCURACY, CancellationTokenSource().token).await() + val address = Geocoder(context).getFromLocation(currentLocation.latitude, currentLocation.longitude, 1).orEmpty() + toLocationLoadedState(GeoLocatedAddress(address.firstOrNull(), currentLocation)) + } else { + toLocationError() + } } @SuppressLint("MissingPermission") private fun getLocationWithoutGms(context: Context) = viewModelScope.launch { appLogger.d("Getting location without GMS") - val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager - val networkLocationListener: LocationListener = object : LocationListener { - override fun onLocationChanged(location: Location) { - val address = Geocoder(context).getFromLocation(location.latitude, location.longitude, 1).orEmpty() - toLocationLoadedState(GeoLocatedAddress(address.firstOrNull(), location)) - locationManager.removeUpdates(this) // important step, otherwise it will keep listening for location changes + if (isLocationServicesEnabled(context)) { + val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + val networkLocationListener: LocationListener = object : LocationListener { + override fun onLocationChanged(location: Location) { + val address = Geocoder(context).getFromLocation(location.latitude, location.longitude, 1).orEmpty() + toLocationLoadedState(GeoLocatedAddress(address.firstOrNull(), location)) + locationManager.removeUpdates(this) // important step, otherwise it will keep listening for location changes + } } + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, networkLocationListener) + } else { + toLocationError() } - locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, networkLocationListener) + } + + private fun isLocationServicesEnabled(context: Context): Boolean { + val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + return LocationManagerCompat.isLocationEnabled(locationManager) } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 55464d682d5..5d0860c87be 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1360,4 +1360,5 @@ Share Location Allow Wire to access your device location to send your location. Please wait... + Location could not be shared