-
Notifications
You must be signed in to change notification settings - Fork 28
Commit
…-form-external
- Loading branch information
There are no files selected for viewing
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 Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L36-L37
|
||
|
||
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 Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L41-L43
|
||
) | ||
} 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 Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L46-L48
|
||
) | ||
} | ||
} | ||
Check warning on line 51 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L51
|
||
|
||
/** | ||
* 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 Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L58
|
||
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 Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L60-L62
|
||
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 Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L64
|
||
} else { | ||
onError() | ||
Check warning on line 66 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L66
|
||
} | ||
} | ||
Check warning on line 68 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L68
|
||
|
||
@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 Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L73-L74
|
||
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 Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L77-L79
|
||
} | ||
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 Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L81
|
||
} else { | ||
onError() | ||
Check warning on line 83 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L83
|
||
} | ||
} | ||
Check warning on line 85 in app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L85
|
||
|
||
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 Codecov / codecov/patchapp/src/main/kotlin/com/wire/android/ui/home/messagecomposer/location/LocationPickerHelper.kt#L88-L89
|
||
} | ||
} |
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 |