Skip to content

Commit

Permalink
Merge branch 'develop' into fix/original-images-was-sent-when-sending…
Browse files Browse the repository at this point in the history
…-form-external
  • Loading branch information
MohamadJaara authored Jan 25, 2024
2 parents 8ee949d + 7c7bbef commit 90a2df8
Show file tree
Hide file tree
Showing 6 changed files with 212 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
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
Expand All @@ -61,6 +59,7 @@ import com.wire.android.ui.common.dimensions
import com.wire.android.ui.common.progress.WireCircularProgressIndicator
import com.wire.android.ui.common.spacers.HorizontalSpace
import com.wire.android.ui.common.spacers.VerticalSpace
import com.wire.android.ui.theme.wireColorScheme
import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.orDefault
import com.wire.android.util.permission.PermissionsDeniedRequestDialog
Expand All @@ -78,12 +77,11 @@ fun LocationPickerComponent(
onLocationClosed: () -> Unit
) {
val viewModel = hiltViewModel<LocationPickerViewModel>()
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val sheetState = rememberDismissibleWireModalSheetState(initialValue = SheetValue.Expanded, onLocationClosed)

val locationFlow = LocationFlow(
onCurrentLocationPicked = { viewModel.getCurrentLocation(context) },
onCurrentLocationPicked = viewModel::getCurrentLocation,
onLocationDenied = viewModel::onPermissionsDenied
)
LaunchedEffect(Unit) {
Expand Down Expand Up @@ -219,7 +217,7 @@ private fun LocationInformation(geoLocatedAddress: GeoLocatedAddress?) {
@Composable
private fun RowScope.LoadingLocation() {
WireCircularProgressIndicator(
progressColor = Color.Black,
progressColor = MaterialTheme.wireColorScheme.primary,
modifier = Modifier.align(alignment = Alignment.CenterVertically)
)
HorizontalSpace.x8()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.messagecomposer.location

import android.annotation.SuppressLint
import android.content.Context
import android.location.Geocoder
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import androidx.core.location.LocationManagerCompat
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.Priority
import com.google.android.gms.tasks.CancellationTokenSource
import com.wire.android.util.extension.isGoogleServicesAvailable
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.tasks.await
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class LocationPickerHelper @Inject constructor(@ApplicationContext val context: Context) {

Check warning on line 37 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L36-L37

Added lines #L36 - L37 were not covered by tests

suspend fun getLocation(onSuccess: (GeoLocatedAddress) -> Unit, onError: () -> Unit) {
if (context.isGoogleServicesAvailable()) {
getLocationWithGms(
onSuccess = onSuccess,
onError = onError

Check warning on line 43 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L41-L43

Added lines #L41 - L43 were not covered by tests
)
} else {
getLocationWithoutGms(
onSuccess = onSuccess,
onError = onError

Check warning on line 48 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L46-L48

Added lines #L46 - L48 were not covered by tests
)
}
}

Check warning on line 51 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L51

Added line #L51 was not covered by tests

/**
* Choosing the best location estimate by docs.
* https://developer.android.com/develop/sensors-and-location/location/retrieve-current#BestEstimate
*/
@SuppressLint("MissingPermission")
private suspend fun getLocationWithGms(onSuccess: (GeoLocatedAddress) -> Unit, onError: () -> Unit) {

Check warning on line 58 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L58

Added line #L58 was not covered by tests
if (isLocationServicesEnabled()) {
val locationProvider = LocationServices.getFusedLocationProviderClient(context)
val currentLocation =
locationProvider.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, CancellationTokenSource().token).await()

Check warning on line 62 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L60-L62

Added lines #L60 - L62 were not covered by tests
val address = Geocoder(context).getFromLocation(currentLocation.latitude, currentLocation.longitude, 1).orEmpty()
onSuccess(GeoLocatedAddress(address.firstOrNull(), currentLocation))

Check warning on line 64 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L64

Added line #L64 was not covered by tests
} else {
onError()

Check warning on line 66 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L66

Added line #L66 was not covered by tests
}
}

Check warning on line 68 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L68

Added line #L68 was not covered by tests

@SuppressLint("MissingPermission")
private fun getLocationWithoutGms(onSuccess: (GeoLocatedAddress) -> Unit, onError: () -> Unit) {
if (isLocationServicesEnabled()) {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val networkLocationListener: LocationListener = object : LocationListener {

Check warning on line 74 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L73-L74

Added lines #L73 - L74 were not covered by tests
override fun onLocationChanged(location: Location) {
val address = Geocoder(context).getFromLocation(location.latitude, location.longitude, 1).orEmpty()
onSuccess(GeoLocatedAddress(address.firstOrNull(), location))
locationManager.removeUpdates(this) // important step, otherwise it will keep listening for location changes
}

Check warning on line 79 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L77-L79

Added lines #L77 - L79 were not covered by tests
}
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, networkLocationListener)

Check warning on line 81 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L81

Added line #L81 was not covered by tests
} else {
onError()

Check warning on line 83 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L83

Added line #L83 was not covered by tests
}
}

Check warning on line 85 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L85

Added line #L85 was not covered by tests

private fun isLocationServicesEnabled(): Boolean {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
return LocationManagerCompat.isLocationEnabled(locationManager)

Check warning on line 89 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L88-L89

Added lines #L88 - L89 were not covered by tests
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,17 @@
*/
package com.wire.android.ui.home.messagecomposer.location

import android.annotation.SuppressLint
import android.content.Context
import android.location.Geocoder
import android.location.Location
import android.location.LocationListener
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
import com.google.android.gms.location.Priority.PRIORITY_HIGH_ACCURACY
import com.google.android.gms.tasks.CancellationTokenSource
import com.wire.android.appLogger
import com.wire.android.util.extension.isGoogleServicesAvailable
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import kotlinx.coroutines.tasks.await
import javax.inject.Inject

@HiltViewModel
class LocationPickerViewModel @Inject constructor() : ViewModel() {
class LocationPickerViewModel @Inject constructor(private val locationPickerHelper: LocationPickerHelper) : ViewModel() {

var state: LocationPickerState by mutableStateOf(LocationPickerState())
private set
Expand All @@ -57,6 +44,16 @@ class LocationPickerViewModel @Inject constructor() : ViewModel() {
state = state.copy(showPermissionDeniedDialog = true)
}

fun getCurrentLocation() {
viewModelScope.launch {
toStartLoadingLocationState()
locationPickerHelper.getLocation(
onSuccess = { toLocationLoadedState(it) },
onError = ::toLocationError
)
}
}

private fun toStartLoadingLocationState() {
state = state.copy(
showLocationSharingError = false,
Expand All @@ -80,52 +77,4 @@ class LocationPickerViewModel @Inject constructor() : ViewModel() {
geoLocatedAddress = null,
)
}

fun getCurrentLocation(context: Context) {
toStartLoadingLocationState()
when (context.isGoogleServicesAvailable()) {
true -> getLocationWithGms(context)
false -> getLocationWithoutGms(context)
}
}

/**
* Choosing the best location estimate by docs.
* https://developer.android.com/develop/sensors-and-location/location/retrieve-current#BestEstimate
*/
@SuppressLint("MissingPermission")
private fun getLocationWithGms(context: Context) = viewModelScope.launch {
appLogger.d("Getting location with GMS")
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")
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()
}
}

private fun isLocationServicesEnabled(context: Context): Boolean {
val locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
return LocationManagerCompat.isLocationEnabled(locationManager)
}
}
10 changes: 5 additions & 5 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1012,11 +1012,11 @@
<string name="custom_backend_dialog_body">If you proceed, your client will be redirected to the following on-premises backend:</string>
<string name="custom_backend_dialog_body_backend_name">Backend name:</string>
<string name="custom_backend_dialog_body_backend_api">Backend URL:</string>
<string name="custom_backend_dialog_body_backend_blacklist">blackListURL:</string>
<string name="custom_backend_dialog_body_backend_teams">teamsURL:</string>
<string name="custom_backend_dialog_body_backend_accounts">accountsURL:</string>
<string name="custom_backend_dialog_body_backend_website">websiteURL:</string>
<string name="custom_backend_dialog_body_backend_websocket">backendWSURL:</string>
<string name="custom_backend_dialog_body_backend_blacklist">Blacklist URL:</string>
<string name="custom_backend_dialog_body_backend_teams">Teams URL:</string>
<string name="custom_backend_dialog_body_backend_accounts">Accounts URL:</string>
<string name="custom_backend_dialog_body_backend_website">Website URL:</string>
<string name="custom_backend_dialog_body_backend_websocket">Backend WSURL:</string>
<string name="label_fetching_your_messages">Receiving new messages</string>
<string name="label_text_copied">Text copied to clipboard</string>
<string name="label_logs_option_title">Logs</string>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.ui.home.messagecomposer.location

import android.location.Location
import com.wire.android.config.CoroutineTestExtension
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.mockk
import io.mockk.slot
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.internal.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

@ExtendWith(CoroutineTestExtension::class)
class LocationPickerViewModelTest {

@Test
fun `given user has device location disabled, when sharing location, then an error message will be shown`() = runTest {
// given
val (_, viewModel) = Arrangement()
.withGetGeoLocationError()
.arrange()

// when
viewModel.getCurrentLocation()

// then
assertEquals(true, viewModel.state.showLocationSharingError)
assertEquals(true, viewModel.state.geoLocatedAddress == null)
}

@Test
fun `given user has device location enabled, when sharing location, then should load the location`() = runTest {
// given
val (arrangement, viewModel) = Arrangement()
.withGetGeoLocationSuccess()
.arrange()

// when
viewModel.getCurrentLocation()

// then
assertEquals(false, viewModel.state.showLocationSharingError)
assertEquals(true, viewModel.state.geoLocatedAddress != null)
coVerify(exactly = 1) { arrangement.locationPickerHelper.getLocation(any(), any()) }
}

private class Arrangement {

val locationPickerHelper = mockk<LocationPickerHelper>()

fun withGetGeoLocationSuccess() = apply {
coEvery {
locationPickerHelper.getLocation(
capture(onPickedLocationSuccess),
capture(onPickedLocationFailure)
)
} coAnswers {
firstArg<PickedGeoLocation>().invoke(successResponse)
}
}

fun withGetGeoLocationError() = apply {
coEvery {
locationPickerHelper.getLocation(
capture(onPickedLocationSuccess),
capture(onPickedLocationFailure)
)
} coAnswers {
secondArg<() -> Unit>().invoke()
}
}

fun arrange() = this to LocationPickerViewModel(locationPickerHelper)
}

private companion object {
val onPickedLocationSuccess = slot<PickedGeoLocation>()
val onPickedLocationFailure = slot<() -> Unit>()
val successResponse = GeoLocatedAddress(null, Location("dummy-location"))
}
}

private typealias PickedGeoLocation = (GeoLocatedAddress) -> Unit
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ androidx-text-archCore = "2.1.0"
junit4 = "4.13.2"
junit5 = "5.10.0"
kluent = "1.73"
mockk = "1.13.5"
mockk = "1.13.9"
okio = "3.6.0"
turbine = "1.0.0"

Expand Down

0 comments on commit 90a2df8

Please sign in to comment.