mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 20:33:53 +01:00
Migrate location and language module from Java to Kotlin (#5988)
* Rename .java to .kt * Migrated location and language module from Java to Kotlin * Changed lastLocation visibility
This commit is contained in:
parent
771f370f9a
commit
8265cc6306
15 changed files with 773 additions and 827 deletions
|
|
@ -423,7 +423,7 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
|
||||||
* Moves map to GPS location
|
* Moves map to GPS location
|
||||||
*/
|
*/
|
||||||
private fun moveMapToGPSLocation() {
|
private fun moveMapToGPSLocation() {
|
||||||
locationManager.lastLocation?.let {
|
locationManager.getLastLocation()?.let {
|
||||||
moveMapTo(GeoPoint(it.latitude, it.longitude))
|
moveMapTo(GeoPoint(it.latitude, it.longitude))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -591,7 +591,7 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
|
||||||
|
|
||||||
override fun onLocationPermissionGranted() {
|
override fun onLocationPermissionGranted() {
|
||||||
if (moveToCurrentLocation || activity != "MediaActivity") {
|
if (moveToCurrentLocation || activity != "MediaActivity") {
|
||||||
if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn) {
|
if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) {
|
||||||
locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER)
|
locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER)
|
||||||
locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER)
|
locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER)
|
||||||
addMarkerAtGPSLocation()
|
addMarkerAtGPSLocation()
|
||||||
|
|
@ -606,7 +606,7 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
|
||||||
* Adds a marker at the user's GPS location
|
* Adds a marker at the user's GPS location
|
||||||
*/
|
*/
|
||||||
private fun addMarkerAtGPSLocation() {
|
private fun addMarkerAtGPSLocation() {
|
||||||
locationManager.lastLocation?.let {
|
locationManager.getLastLocation()?.let {
|
||||||
addLocationMarker(GeoPoint(it.latitude, it.longitude))
|
addLocationMarker(GeoPoint(it.latitude, it.longitude))
|
||||||
markerImage.translationY = 0f
|
markerImage.translationY = 0f
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
package fr.free.nrw.commons.language;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.res.Resources;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import androidx.annotation.ArrayRes;
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import java.lang.ref.SoftReference;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/** Immutable look up table for all app supported languages. All article languages may not be
|
|
||||||
* present in this table as it is statically bundled with the app. */
|
|
||||||
public class AppLanguageLookUpTable {
|
|
||||||
public static final String SIMPLIFIED_CHINESE_LANGUAGE_CODE = "zh-hans";
|
|
||||||
public static final String TRADITIONAL_CHINESE_LANGUAGE_CODE = "zh-hant";
|
|
||||||
public static final String CHINESE_CN_LANGUAGE_CODE = "zh-cn";
|
|
||||||
public static final String CHINESE_HK_LANGUAGE_CODE = "zh-hk";
|
|
||||||
public static final String CHINESE_MO_LANGUAGE_CODE = "zh-mo";
|
|
||||||
public static final String CHINESE_SG_LANGUAGE_CODE = "zh-sg";
|
|
||||||
public static final String CHINESE_TW_LANGUAGE_CODE = "zh-tw";
|
|
||||||
public static final String CHINESE_YUE_LANGUAGE_CODE = "zh-yue";
|
|
||||||
public static final String CHINESE_LANGUAGE_CODE = "zh";
|
|
||||||
public static final String NORWEGIAN_LEGACY_LANGUAGE_CODE = "no";
|
|
||||||
public static final String NORWEGIAN_BOKMAL_LANGUAGE_CODE = "nb";
|
|
||||||
public static final String TEST_LANGUAGE_CODE = "test";
|
|
||||||
public static final String FALLBACK_LANGUAGE_CODE = "en"; // Must exist in preference_language_keys.
|
|
||||||
|
|
||||||
@NonNull private final Resources resources;
|
|
||||||
|
|
||||||
// Language codes for all app supported languages in fixed order. The special code representing
|
|
||||||
// the dynamic system language is null.
|
|
||||||
@NonNull private SoftReference<List<String>> codesRef = new SoftReference<>(null);
|
|
||||||
|
|
||||||
// English names for all app supported languages in fixed order.
|
|
||||||
@NonNull private SoftReference<List<String>> canonicalNamesRef = new SoftReference<>(null);
|
|
||||||
|
|
||||||
// Native names for all app supported languages in fixed order.
|
|
||||||
@NonNull private SoftReference<List<String>> localizedNamesRef = new SoftReference<>(null);
|
|
||||||
|
|
||||||
public AppLanguageLookUpTable(@NonNull Context context) {
|
|
||||||
resources = context.getResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Nonnull immutable list. The special code representing the dynamic system language is
|
|
||||||
* null.
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public List<String> getCodes() {
|
|
||||||
List<String> codes = codesRef.get();
|
|
||||||
if (codes == null) {
|
|
||||||
codes = getStringList(R.array.preference_language_keys);
|
|
||||||
codesRef = new SoftReference<>(codes);
|
|
||||||
}
|
|
||||||
return codes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getCanonicalName(@Nullable String code) {
|
|
||||||
String name = defaultIndex(getCanonicalNames(), indexOfCode(code), null);
|
|
||||||
if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(code)) {
|
|
||||||
if (code.equals(Locale.CHINESE.getLanguage())) {
|
|
||||||
name = Locale.CHINESE.getDisplayName(Locale.ENGLISH);
|
|
||||||
} else if (code.equals(NORWEGIAN_LEGACY_LANGUAGE_CODE)) {
|
|
||||||
name = defaultIndex(getCanonicalNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getLocalizedName(@Nullable String code) {
|
|
||||||
String name = defaultIndex(getLocalizedNames(), indexOfCode(code), null);
|
|
||||||
if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(code)) {
|
|
||||||
if (code.equals(Locale.CHINESE.getLanguage())) {
|
|
||||||
name = Locale.CHINESE.getDisplayName(Locale.CHINESE);
|
|
||||||
} else if (code.equals(NORWEGIAN_LEGACY_LANGUAGE_CODE)) {
|
|
||||||
name = defaultIndex(getLocalizedNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getCanonicalNames() {
|
|
||||||
List<String> names = canonicalNamesRef.get();
|
|
||||||
if (names == null) {
|
|
||||||
names = getStringList(R.array.preference_language_canonical_names);
|
|
||||||
canonicalNamesRef = new SoftReference<>(names);
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getLocalizedNames() {
|
|
||||||
List<String> names = localizedNamesRef.get();
|
|
||||||
if (names == null) {
|
|
||||||
names = getStringList(R.array.preference_language_local_names);
|
|
||||||
localizedNamesRef = new SoftReference<>(names);
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSupportedCode(@Nullable String code) {
|
|
||||||
return getCodes().contains(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> T defaultIndex(List<T> list, int index, T defaultValue) {
|
|
||||||
return inBounds(list, index) ? list.get(index) : defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches #codes for the specified language code and returns the index for use in
|
|
||||||
* #canonicalNames and #localizedNames.
|
|
||||||
*
|
|
||||||
* @param code The language code to search for. The special code representing the dynamic system
|
|
||||||
* language is null.
|
|
||||||
* @return The index of the language code or -1 if the code is not supported.
|
|
||||||
*/
|
|
||||||
private int indexOfCode(@Nullable String code) {
|
|
||||||
return getCodes().indexOf(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return Nonnull immutable list. */
|
|
||||||
@NonNull
|
|
||||||
private List<String> getStringList(int id) {
|
|
||||||
return Arrays.asList(getStringArray(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean inBounds(List<?> list, int index) {
|
|
||||||
return index >= 0 && index < list.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String[] getStringArray(@ArrayRes int id) {
|
|
||||||
return resources.getStringArray(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
package fr.free.nrw.commons.language
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.text.TextUtils
|
||||||
|
|
||||||
|
import androidx.annotation.ArrayRes
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import java.lang.ref.SoftReference
|
||||||
|
import java.util.Arrays
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
/** Immutable look up table for all app supported languages. All article languages may not be
|
||||||
|
* present in this table as it is statically bundled with the app. */
|
||||||
|
class AppLanguageLookUpTable(context: Context) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val SIMPLIFIED_CHINESE_LANGUAGE_CODE = "zh-hans"
|
||||||
|
const val TRADITIONAL_CHINESE_LANGUAGE_CODE = "zh-hant"
|
||||||
|
const val CHINESE_CN_LANGUAGE_CODE = "zh-cn"
|
||||||
|
const val CHINESE_HK_LANGUAGE_CODE = "zh-hk"
|
||||||
|
const val CHINESE_MO_LANGUAGE_CODE = "zh-mo"
|
||||||
|
const val CHINESE_SG_LANGUAGE_CODE = "zh-sg"
|
||||||
|
const val CHINESE_TW_LANGUAGE_CODE = "zh-tw"
|
||||||
|
const val CHINESE_YUE_LANGUAGE_CODE = "zh-yue"
|
||||||
|
const val CHINESE_LANGUAGE_CODE = "zh"
|
||||||
|
const val NORWEGIAN_LEGACY_LANGUAGE_CODE = "no"
|
||||||
|
const val NORWEGIAN_BOKMAL_LANGUAGE_CODE = "nb"
|
||||||
|
const val TEST_LANGUAGE_CODE = "test"
|
||||||
|
const val FALLBACK_LANGUAGE_CODE = "en" // Must exist in preference_language_keys.
|
||||||
|
}
|
||||||
|
|
||||||
|
private val resources: Resources = context.resources
|
||||||
|
|
||||||
|
// Language codes for all app supported languages in fixed order. The special code representing
|
||||||
|
// the dynamic system language is null.
|
||||||
|
private var codesRef = SoftReference<List<String>>(null)
|
||||||
|
|
||||||
|
// English names for all app supported languages in fixed order.
|
||||||
|
private var canonicalNamesRef = SoftReference<List<String>>(null)
|
||||||
|
|
||||||
|
// Native names for all app supported languages in fixed order.
|
||||||
|
private var localizedNamesRef = SoftReference<List<String>>(null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Nonnull immutable list. The special code representing the dynamic system language is
|
||||||
|
* null.
|
||||||
|
*/
|
||||||
|
fun getCodes(): List<String> {
|
||||||
|
var codes = codesRef.get()
|
||||||
|
if (codes == null) {
|
||||||
|
codes = getStringList(R.array.preference_language_keys)
|
||||||
|
codesRef = SoftReference(codes)
|
||||||
|
}
|
||||||
|
return codes
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCanonicalName(code: String?): String? {
|
||||||
|
var name = defaultIndex(getCanonicalNames(), indexOfCode(code), null)
|
||||||
|
if (name.isNullOrEmpty() && !code.isNullOrEmpty()) {
|
||||||
|
name = when (code) {
|
||||||
|
Locale.CHINESE.language -> Locale.CHINESE.getDisplayName(Locale.ENGLISH)
|
||||||
|
NORWEGIAN_LEGACY_LANGUAGE_CODE ->
|
||||||
|
defaultIndex(getCanonicalNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLocalizedName(code: String?): String? {
|
||||||
|
var name = defaultIndex(getLocalizedNames(), indexOfCode(code), null)
|
||||||
|
if (name.isNullOrEmpty() && !code.isNullOrEmpty()) {
|
||||||
|
name = when (code) {
|
||||||
|
Locale.CHINESE.language -> Locale.CHINESE.getDisplayName(Locale.CHINESE)
|
||||||
|
NORWEGIAN_LEGACY_LANGUAGE_CODE ->
|
||||||
|
defaultIndex(getLocalizedNames(), indexOfCode(NORWEGIAN_BOKMAL_LANGUAGE_CODE), null)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCanonicalNames(): List<String> {
|
||||||
|
var names = canonicalNamesRef.get()
|
||||||
|
if (names == null) {
|
||||||
|
names = getStringList(R.array.preference_language_canonical_names)
|
||||||
|
canonicalNamesRef = SoftReference(names)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getLocalizedNames(): List<String> {
|
||||||
|
var names = localizedNamesRef.get()
|
||||||
|
if (names == null) {
|
||||||
|
names = getStringList(R.array.preference_language_local_names)
|
||||||
|
localizedNamesRef = SoftReference(names)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSupportedCode(code: String?): Boolean {
|
||||||
|
return getCodes().contains(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> defaultIndex(list: List<T>, index: Int, defaultValue: T?): T? {
|
||||||
|
return if (inBounds(list, index)) list[index] else defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Searches #codes for the specified language code and returns the index for use in
|
||||||
|
* #canonicalNames and #localizedNames.
|
||||||
|
*
|
||||||
|
* @param code The language code to search for. The special code representing the dynamic system
|
||||||
|
* language is null.
|
||||||
|
* @return The index of the language code or -1 if the code is not supported.
|
||||||
|
*/
|
||||||
|
private fun indexOfCode(code: String?): Int {
|
||||||
|
return getCodes().indexOf(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return Nonnull immutable list. */
|
||||||
|
private fun getStringList(id: Int): List<String> {
|
||||||
|
return getStringArray(id).toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun inBounds(list: List<*>, index: Int): Boolean {
|
||||||
|
return index in list.indices
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getStringArray(@ArrayRes id: Int): Array<String> {
|
||||||
|
return resources.getStringArray(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,198 +0,0 @@
|
||||||
package fr.free.nrw.commons.location;
|
|
||||||
|
|
||||||
import android.location.Location;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a latitude and longitude point with accuracy information, often of a picture
|
|
||||||
*/
|
|
||||||
public class LatLng implements Parcelable {
|
|
||||||
|
|
||||||
private final double latitude;
|
|
||||||
private final double longitude;
|
|
||||||
private final float accuracy;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accepts latitude and longitude.
|
|
||||||
* North and South values are cut off at 90°
|
|
||||||
*
|
|
||||||
* @param latitude the latitude
|
|
||||||
* @param longitude the longitude
|
|
||||||
* @param accuracy the accuracy
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
* the Statue of Liberty is located at 40.69° N, 74.04° W
|
|
||||||
* The Statue of Liberty could be constructed as LatLng(40.69, -74.04, 1.0)
|
|
||||||
* where positive signifies north, east and negative signifies south, west.
|
|
||||||
*/
|
|
||||||
public LatLng(double latitude, double longitude, float accuracy) {
|
|
||||||
if (-180.0D <= longitude && longitude < 180.0D) {
|
|
||||||
this.longitude = longitude;
|
|
||||||
} else {
|
|
||||||
this.longitude = ((longitude - 180.0D) % 360.0D + 360.0D) % 360.0D - 180.0D;
|
|
||||||
}
|
|
||||||
this.latitude = Math.max(-90.0D, Math.min(90.0D, latitude));
|
|
||||||
this.accuracy = accuracy;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* An alternate constructor for this class.
|
|
||||||
* @param in A parcelable which contains the latitude, longitude, and accuracy
|
|
||||||
*/
|
|
||||||
public LatLng(Parcel in) {
|
|
||||||
latitude = in.readDouble();
|
|
||||||
longitude = in.readDouble();
|
|
||||||
accuracy = in.readFloat();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gets the latitude and longitude of a given non-null location
|
|
||||||
* @param location the non-null location of the user
|
|
||||||
* @return LatLng the Latitude and Longitude of a given location
|
|
||||||
*/
|
|
||||||
public static LatLng from(@NonNull Location location) {
|
|
||||||
return new LatLng(location.getLatitude(), location.getLongitude(), location.getAccuracy());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* creates a hash code for the longitude and longitude
|
|
||||||
*/
|
|
||||||
public int hashCode() {
|
|
||||||
byte var1 = 1;
|
|
||||||
long var2 = Double.doubleToLongBits(this.latitude);
|
|
||||||
int var3 = 31 * var1 + (int)(var2 ^ var2 >>> 32);
|
|
||||||
var2 = Double.doubleToLongBits(this.longitude);
|
|
||||||
var3 = 31 * var3 + (int)(var2 ^ var2 >>> 32);
|
|
||||||
return var3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checks for equality of two LatLng objects
|
|
||||||
* @param o the second LatLng object
|
|
||||||
*/
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
} else if (!(o instanceof LatLng)) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
LatLng var2 = (LatLng)o;
|
|
||||||
return Double.doubleToLongBits(this.latitude) == Double.doubleToLongBits(var2.latitude) && Double.doubleToLongBits(this.longitude) == Double.doubleToLongBits(var2.longitude);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns a string representation of the latitude and longitude
|
|
||||||
*/
|
|
||||||
public String toString() {
|
|
||||||
return "lat/lng: (" + this.latitude + "," + this.longitude + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rounds the float to 4 digits and returns absolute value.
|
|
||||||
*
|
|
||||||
* @param coordinate A coordinate value as string.
|
|
||||||
* @return String of the rounded number.
|
|
||||||
*/
|
|
||||||
private String formatCoordinate(double coordinate) {
|
|
||||||
double roundedNumber = Math.round(coordinate * 10000d) / 10000d;
|
|
||||||
double absoluteNumber = Math.abs(roundedNumber);
|
|
||||||
return String.valueOf(absoluteNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns "N" or "S" depending on the latitude.
|
|
||||||
*
|
|
||||||
* @return "N" or "S".
|
|
||||||
*/
|
|
||||||
private String getNorthSouth() {
|
|
||||||
if (this.latitude < 0) {
|
|
||||||
return "S";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "N";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns "E" or "W" depending on the longitude.
|
|
||||||
*
|
|
||||||
* @return "E" or "W".
|
|
||||||
*/
|
|
||||||
private String getEastWest() {
|
|
||||||
if (this.longitude >= 0 && this.longitude < 180) {
|
|
||||||
return "E";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "W";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a nicely formatted coordinate string. Used e.g. in
|
|
||||||
* the detail view.
|
|
||||||
*
|
|
||||||
* @return The formatted string.
|
|
||||||
*/
|
|
||||||
public String getPrettyCoordinateString() {
|
|
||||||
return formatCoordinate(this.latitude) + " " + this.getNorthSouth() + ", "
|
|
||||||
+ formatCoordinate(this.longitude) + " " + this.getEastWest();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the location accuracy in meter.
|
|
||||||
*
|
|
||||||
* @return float
|
|
||||||
*/
|
|
||||||
public float getAccuracy() {
|
|
||||||
return accuracy;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the longitude in degrees.
|
|
||||||
*
|
|
||||||
* @return double
|
|
||||||
*/
|
|
||||||
public double getLongitude() {
|
|
||||||
return longitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the latitude in degrees.
|
|
||||||
*
|
|
||||||
* @return double
|
|
||||||
*/
|
|
||||||
public double getLatitude() {
|
|
||||||
return latitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Uri getGmmIntentUri() {
|
|
||||||
return Uri.parse("geo:" + latitude + "," + longitude + "?z=16");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel dest, int flags) {
|
|
||||||
dest.writeDouble(latitude);
|
|
||||||
dest.writeDouble(longitude);
|
|
||||||
dest.writeFloat(accuracy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Creator<LatLng> CREATOR = new Creator<LatLng>() {
|
|
||||||
@Override
|
|
||||||
public LatLng createFromParcel(Parcel in) {
|
|
||||||
return new LatLng(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public LatLng[] newArray(int size) {
|
|
||||||
return new LatLng[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
150
app/src/main/java/fr/free/nrw/commons/location/LatLng.kt
Normal file
150
app/src/main/java/fr/free/nrw/commons/location/LatLng.kt
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
package fr.free.nrw.commons.location
|
||||||
|
|
||||||
|
import android.location.Location
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.round
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A latitude and longitude point with accuracy information, often of a picture.
|
||||||
|
*/
|
||||||
|
data class LatLng(
|
||||||
|
var latitude: Double,
|
||||||
|
var longitude: Double,
|
||||||
|
val accuracy: Float
|
||||||
|
) : Parcelable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts latitude and longitude.
|
||||||
|
* North and South values are cut off at 90°
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* the Statue of Liberty is located at 40.69° N, 74.04° W
|
||||||
|
* The Statue of Liberty could be constructed as LatLng(40.69, -74.04, 1.0)
|
||||||
|
* where positive signifies north, east and negative signifies south, west.
|
||||||
|
*/
|
||||||
|
init {
|
||||||
|
val adjustedLongitude = when {
|
||||||
|
longitude in -180.0..180.0 -> longitude
|
||||||
|
else -> ((longitude - 180.0) % 360.0 + 360.0) % 360.0 - 180.0
|
||||||
|
}
|
||||||
|
latitude = max(-90.0, min(90.0, latitude))
|
||||||
|
longitude = adjustedLongitude
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accepts a non-null [Location] and converts it to a [LatLng].
|
||||||
|
*/
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* gets the latitude and longitude of a given non-null location
|
||||||
|
* @param location the non-null location of the user
|
||||||
|
* @return LatLng the Latitude and Longitude of a given location
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun from(location: Location): LatLng {
|
||||||
|
return LatLng(location.latitude, location.longitude, location.accuracy)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val CREATOR: Parcelable.Creator<LatLng> = object : Parcelable.Creator<LatLng> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): LatLng {
|
||||||
|
return LatLng(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<LatLng?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An alternate constructor for this class.
|
||||||
|
* @param parcel A parcelable which contains the latitude, longitude, and accuracy
|
||||||
|
*/
|
||||||
|
private constructor(parcel: Parcel) : this(
|
||||||
|
latitude = parcel.readDouble(),
|
||||||
|
longitude = parcel.readDouble(),
|
||||||
|
accuracy = parcel.readFloat()
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a hash code for the latitude and longitude.
|
||||||
|
*/
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = 1
|
||||||
|
val latitudeBits = latitude.toBits()
|
||||||
|
result = 31 * result + (latitudeBits xor (latitudeBits ushr 32)).toInt()
|
||||||
|
val longitudeBits = longitude.toBits()
|
||||||
|
result = 31 * result + (longitudeBits xor (longitudeBits ushr 32)).toInt()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for equality of two LatLng objects.
|
||||||
|
* @param other the second LatLng object
|
||||||
|
*/
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (other !is LatLng) return false
|
||||||
|
return latitude.toBits() == other.latitude.toBits() &&
|
||||||
|
longitude.toBits() == other.longitude.toBits()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the latitude and longitude.
|
||||||
|
*/
|
||||||
|
override fun toString(): String {
|
||||||
|
return "lat/lng: ($latitude,$longitude)"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a nicely formatted coordinate string. Used e.g. in
|
||||||
|
* the detail view.
|
||||||
|
*
|
||||||
|
* @return The formatted string.
|
||||||
|
*/
|
||||||
|
fun getPrettyCoordinateString(): String {
|
||||||
|
return "${formatCoordinate(latitude)} ${getNorthSouth()}, " +
|
||||||
|
"${formatCoordinate(longitude)} ${getEastWest()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a URI for a Google Maps intent at the location.
|
||||||
|
*/
|
||||||
|
fun getGmmIntentUri(): Uri {
|
||||||
|
return Uri.parse("geo:$latitude,$longitude?z=16")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeDouble(latitude)
|
||||||
|
parcel.writeDouble(longitude)
|
||||||
|
parcel.writeFloat(accuracy)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int = 0
|
||||||
|
|
||||||
|
private fun formatCoordinate(coordinate: Double): String {
|
||||||
|
val roundedNumber = round(coordinate * 10000) / 10000
|
||||||
|
return abs(roundedNumber).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns "N" or "S" depending on the latitude.
|
||||||
|
*
|
||||||
|
* @return "N" or "S".
|
||||||
|
*/
|
||||||
|
private fun getNorthSouth(): String = if (latitude < 0) "S" else "N"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns "E" or "W" depending on the longitude.
|
||||||
|
*
|
||||||
|
* @return "E" or "W".
|
||||||
|
*/
|
||||||
|
private fun getEastWest(): String = if (longitude in 0.0..179.999) "E" else "W"
|
||||||
|
}
|
||||||
|
|
@ -1,186 +0,0 @@
|
||||||
package fr.free.nrw.commons.location;
|
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.Manifest.permission;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.provider.Settings;
|
|
||||||
import android.widget.Toast;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import fr.free.nrw.commons.R;
|
|
||||||
import fr.free.nrw.commons.filepicker.Constants.RequestCodes;
|
|
||||||
import fr.free.nrw.commons.utils.DialogUtil;
|
|
||||||
import fr.free.nrw.commons.utils.PermissionUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper class to handle location permissions.
|
|
||||||
*
|
|
||||||
* Location flow for fragments containing a map is as follows:
|
|
||||||
* Case 1: When location permission has never been asked for or denied before
|
|
||||||
* Check if permission is already granted or not.
|
|
||||||
* If not already granted, ask for it (if it isn't denied twice before).
|
|
||||||
* If now user grants permission, go to Case 3/4, else go to Case 2.
|
|
||||||
*
|
|
||||||
* Case 2: When location permission is just asked but has been denied
|
|
||||||
* Shows a toast to tell the user why location permission is needed.
|
|
||||||
* Also shows a rationale to the user, on agreeing to which, we go back to Case 1.
|
|
||||||
* Show current location / nearby pins / nearby images according to the default location.
|
|
||||||
*
|
|
||||||
* Case 3: When location permission are already granted, but location services are off
|
|
||||||
* Asks the user to turn on the location service, using a dialog.
|
|
||||||
* If the user rejects, checks for the last known location and shows stuff using that location.
|
|
||||||
* Also displays a toast telling the user why location should be turned on.
|
|
||||||
*
|
|
||||||
* Case 4: When location permission has been granted and location services are also on
|
|
||||||
* Do whatever is required by that particular activity / fragment using current location.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class LocationPermissionsHelper {
|
|
||||||
|
|
||||||
Activity activity;
|
|
||||||
LocationServiceManager locationManager;
|
|
||||||
LocationPermissionCallback callback;
|
|
||||||
|
|
||||||
public LocationPermissionsHelper(Activity activity, LocationServiceManager locationManager,
|
|
||||||
LocationPermissionCallback callback) {
|
|
||||||
this.activity = activity;
|
|
||||||
this.locationManager = locationManager;
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ask for location permission if the user agrees on attaching location with pictures and the
|
|
||||||
* app does not have the access to location
|
|
||||||
*
|
|
||||||
* @param dialogTitleResource Resource id of the title of the dialog
|
|
||||||
* @param dialogTextResource Resource id of the text of the dialog
|
|
||||||
*/
|
|
||||||
public void requestForLocationAccess(
|
|
||||||
int dialogTitleResource,
|
|
||||||
int dialogTextResource
|
|
||||||
) {
|
|
||||||
if (checkLocationPermission(activity)) {
|
|
||||||
callback.onLocationPermissionGranted();
|
|
||||||
} else {
|
|
||||||
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
|
|
||||||
permission.ACCESS_FINE_LOCATION)) {
|
|
||||||
DialogUtil.showAlertDialog(activity, activity.getString(dialogTitleResource),
|
|
||||||
activity.getString(dialogTextResource),
|
|
||||||
activity.getString(android.R.string.ok),
|
|
||||||
activity.getString(android.R.string.cancel),
|
|
||||||
() -> {
|
|
||||||
ActivityCompat.requestPermissions(activity,
|
|
||||||
new String[]{permission.ACCESS_FINE_LOCATION}, 1);
|
|
||||||
},
|
|
||||||
() -> callback.onLocationPermissionDenied(
|
|
||||||
activity.getString(R.string.upload_map_location_access)),
|
|
||||||
null,
|
|
||||||
false);
|
|
||||||
} else {
|
|
||||||
ActivityCompat.requestPermissions(activity,
|
|
||||||
new String[]{permission.ACCESS_FINE_LOCATION},
|
|
||||||
RequestCodes.LOCATION);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a dialog for user to open the settings page and turn on location services
|
|
||||||
*
|
|
||||||
* @param activity Activity object
|
|
||||||
* @param dialogTextResource int id of the required string resource
|
|
||||||
*/
|
|
||||||
public void showLocationOffDialog(Activity activity, int dialogTextResource) {
|
|
||||||
DialogUtil
|
|
||||||
.showAlertDialog(activity,
|
|
||||||
activity.getString(R.string.ask_to_turn_location_on),
|
|
||||||
activity.getString(dialogTextResource),
|
|
||||||
activity.getString(R.string.title_app_shortcut_setting),
|
|
||||||
activity.getString(R.string.cancel),
|
|
||||||
() -> openLocationSettings(activity),
|
|
||||||
() -> Toast.makeText(activity, activity.getString(dialogTextResource),
|
|
||||||
Toast.LENGTH_LONG).show()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the location access page in settings, for user to turn on location services
|
|
||||||
*
|
|
||||||
* @param activity Activtiy object
|
|
||||||
*/
|
|
||||||
public void openLocationSettings(Activity activity) {
|
|
||||||
final Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
|
|
||||||
final PackageManager packageManager = activity.getPackageManager();
|
|
||||||
|
|
||||||
if (intent.resolveActivity(packageManager) != null) {
|
|
||||||
activity.startActivity(intent);
|
|
||||||
} else {
|
|
||||||
Toast.makeText(activity, R.string.cannot_open_location_settings, Toast.LENGTH_LONG)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a dialog for user to open the app's settings page and give location permission
|
|
||||||
*
|
|
||||||
* @param activity Activity object
|
|
||||||
* @param dialogTextResource int id of the required string resource
|
|
||||||
*/
|
|
||||||
public void showAppSettingsDialog(Activity activity, int dialogTextResource) {
|
|
||||||
DialogUtil
|
|
||||||
.showAlertDialog(activity, activity.getString(R.string.location_permission_title),
|
|
||||||
activity.getString(dialogTextResource),
|
|
||||||
activity.getString(R.string.title_app_shortcut_setting),
|
|
||||||
activity.getString(R.string.cancel),
|
|
||||||
() -> openAppSettings(activity),
|
|
||||||
() -> Toast.makeText(activity, activity.getString(dialogTextResource),
|
|
||||||
Toast.LENGTH_LONG).show()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens detailed settings page of the app for the user to turn on location services
|
|
||||||
*
|
|
||||||
* @param activity Activity object
|
|
||||||
*/
|
|
||||||
public void openAppSettings(Activity activity) {
|
|
||||||
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
||||||
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
|
|
||||||
intent.setData(uri);
|
|
||||||
activity.startActivity(intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if apps have access to location even after having individual access
|
|
||||||
*
|
|
||||||
* @return Returns true if location services are on and false otherwise
|
|
||||||
*/
|
|
||||||
public boolean isLocationAccessToAppsTurnedOn() {
|
|
||||||
return (locationManager.isNetworkProviderEnabled()
|
|
||||||
|| locationManager.isGPSProviderEnabled());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if location permission is already granted or not
|
|
||||||
*
|
|
||||||
* @param activity Activity object
|
|
||||||
* @return Returns true if location permission is granted and false otherwise
|
|
||||||
*/
|
|
||||||
public boolean checkLocationPermission(Activity activity) {
|
|
||||||
return PermissionUtils.hasPermission(activity,
|
|
||||||
new String[]{Manifest.permission.ACCESS_FINE_LOCATION});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle onPermissionDenied within individual classes based on the requirements
|
|
||||||
*/
|
|
||||||
public interface LocationPermissionCallback {
|
|
||||||
|
|
||||||
void onLocationPermissionDenied(String toastMessage);
|
|
||||||
|
|
||||||
void onLocationPermissionGranted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
package fr.free.nrw.commons.location
|
||||||
|
|
||||||
|
import android.Manifest.permission
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.filepicker.Constants.RequestCodes
|
||||||
|
import fr.free.nrw.commons.utils.DialogUtil
|
||||||
|
import fr.free.nrw.commons.utils.PermissionUtils
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to handle location permissions.
|
||||||
|
*
|
||||||
|
* Location flow for fragments containing a map is as follows:
|
||||||
|
* Case 1: When location permission has never been asked for or denied before
|
||||||
|
* Check if permission is already granted or not.
|
||||||
|
* If not already granted, ask for it (if it isn't denied twice before).
|
||||||
|
* If now user grants permission, go to Case 3/4, else go to Case 2.
|
||||||
|
*
|
||||||
|
* Case 2: When location permission is just asked but has been denied
|
||||||
|
* Shows a toast to tell the user why location permission is needed.
|
||||||
|
* Also shows a rationale to the user, on agreeing to which, we go back to Case 1.
|
||||||
|
* Show current location / nearby pins / nearby images according to the default location.
|
||||||
|
*
|
||||||
|
* Case 3: When location permission are already granted, but location services are off
|
||||||
|
* Asks the user to turn on the location service, using a dialog.
|
||||||
|
* If the user rejects, checks for the last known location and shows stuff using that location.
|
||||||
|
* Also displays a toast telling the user why location should be turned on.
|
||||||
|
*
|
||||||
|
* Case 4: When location permission has been granted and location services are also on
|
||||||
|
* Do whatever is required by that particular activity / fragment using current location.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class LocationPermissionsHelper(
|
||||||
|
private val activity: Activity,
|
||||||
|
private val locationManager: LocationServiceManager,
|
||||||
|
private val callback: LocationPermissionCallback?
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask for location permission if the user agrees on attaching location with pictures and the
|
||||||
|
* app does not have the access to location
|
||||||
|
*
|
||||||
|
* @param dialogTitleResource Resource id of the title of the dialog
|
||||||
|
* @param dialogTextResource Resource id of the text of the dialog
|
||||||
|
*/
|
||||||
|
fun requestForLocationAccess(
|
||||||
|
dialogTitleResource: Int,
|
||||||
|
dialogTextResource: Int
|
||||||
|
) {
|
||||||
|
if (checkLocationPermission(activity)) {
|
||||||
|
callback?.onLocationPermissionGranted()
|
||||||
|
} else {
|
||||||
|
if (ActivityCompat.shouldShowRequestPermissionRationale(
|
||||||
|
activity,
|
||||||
|
permission.ACCESS_FINE_LOCATION
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
DialogUtil.showAlertDialog(
|
||||||
|
activity,
|
||||||
|
activity.getString(dialogTitleResource),
|
||||||
|
activity.getString(dialogTextResource),
|
||||||
|
activity.getString(android.R.string.ok),
|
||||||
|
activity.getString(android.R.string.cancel),
|
||||||
|
{
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
activity,
|
||||||
|
arrayOf(permission.ACCESS_FINE_LOCATION),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
callback?.onLocationPermissionDenied(
|
||||||
|
activity.getString(R.string.upload_map_location_access)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
activity,
|
||||||
|
arrayOf(permission.ACCESS_FINE_LOCATION),
|
||||||
|
RequestCodes.LOCATION
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a dialog for user to open the settings page and turn on location services
|
||||||
|
*
|
||||||
|
* @param activity Activity object
|
||||||
|
* @param dialogTextResource int id of the required string resource
|
||||||
|
*/
|
||||||
|
fun showLocationOffDialog(activity: Activity, dialogTextResource: Int) {
|
||||||
|
DialogUtil.showAlertDialog(
|
||||||
|
activity,
|
||||||
|
activity.getString(R.string.ask_to_turn_location_on),
|
||||||
|
activity.getString(dialogTextResource),
|
||||||
|
activity.getString(R.string.title_app_shortcut_setting),
|
||||||
|
activity.getString(R.string.cancel),
|
||||||
|
{ openLocationSettings(activity) },
|
||||||
|
{
|
||||||
|
Toast.makeText(
|
||||||
|
activity,
|
||||||
|
activity.getString(dialogTextResource),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the location access page in settings, for user to turn on location services
|
||||||
|
*
|
||||||
|
* @param activity Activity object
|
||||||
|
*/
|
||||||
|
fun openLocationSettings(activity: Activity) {
|
||||||
|
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
|
||||||
|
val packageManager = activity.packageManager
|
||||||
|
|
||||||
|
if (intent.resolveActivity(packageManager) != null) {
|
||||||
|
activity.startActivity(intent)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(activity, R.string.cannot_open_location_settings, Toast.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a dialog for user to open the app's settings page and give location permission
|
||||||
|
*
|
||||||
|
* @param activity Activity object
|
||||||
|
* @param dialogTextResource int id of the required string resource
|
||||||
|
*/
|
||||||
|
fun showAppSettingsDialog(activity: Activity, dialogTextResource: Int) {
|
||||||
|
DialogUtil.showAlertDialog(
|
||||||
|
activity,
|
||||||
|
activity.getString(R.string.location_permission_title),
|
||||||
|
activity.getString(dialogTextResource),
|
||||||
|
activity.getString(R.string.title_app_shortcut_setting),
|
||||||
|
activity.getString(R.string.cancel),
|
||||||
|
{ openAppSettings(activity) },
|
||||||
|
{
|
||||||
|
Toast.makeText(
|
||||||
|
activity,
|
||||||
|
activity.getString(dialogTextResource),
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens detailed settings page of the app for the user to turn on location services
|
||||||
|
*
|
||||||
|
* @param activity Activity object
|
||||||
|
*/
|
||||||
|
private fun openAppSettings(activity: Activity) {
|
||||||
|
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||||
|
val uri = Uri.fromParts("package", activity.packageName, null)
|
||||||
|
intent.data = uri
|
||||||
|
activity.startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if apps have access to location even after having individual access
|
||||||
|
*
|
||||||
|
* @return Returns true if location services are on and false otherwise
|
||||||
|
*/
|
||||||
|
fun isLocationAccessToAppsTurnedOn(): Boolean {
|
||||||
|
return locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if location permission is already granted or not
|
||||||
|
*
|
||||||
|
* @param activity Activity object
|
||||||
|
* @return Returns true if location permission is granted and false otherwise
|
||||||
|
*/
|
||||||
|
fun checkLocationPermission(activity: Activity): Boolean {
|
||||||
|
return PermissionUtils.hasPermission(
|
||||||
|
activity,
|
||||||
|
arrayOf(permission.ACCESS_FINE_LOCATION)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle onPermissionDenied within individual classes based on the requirements
|
||||||
|
*/
|
||||||
|
interface LocationPermissionCallback {
|
||||||
|
fun onLocationPermissionDenied(toastMessage: String)
|
||||||
|
fun onLocationPermissionGranted()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,274 +0,0 @@
|
||||||
package fr.free.nrw.commons.location;
|
|
||||||
|
|
||||||
import android.Manifest.permission;
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.location.Location;
|
|
||||||
import android.location.LocationListener;
|
|
||||||
import android.location.LocationManager;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.core.app.ActivityCompat;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class LocationServiceManager implements LocationListener {
|
|
||||||
|
|
||||||
// Maybe these values can be improved for efficiency
|
|
||||||
private static final long MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 10 * 100;
|
|
||||||
private static final long MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS = 1;
|
|
||||||
|
|
||||||
private LocationManager locationManager;
|
|
||||||
private Location lastLocation;
|
|
||||||
//private Location lastLocationDuplicate; // Will be used for nearby card view on contributions activity
|
|
||||||
private final List<LocationUpdateListener> locationListeners = new CopyOnWriteArrayList<>();
|
|
||||||
private boolean isLocationManagerRegistered = false;
|
|
||||||
private Set<Activity> locationExplanationDisplayed = new HashSet<>();
|
|
||||||
private Context context;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new instance of LocationServiceManager.
|
|
||||||
*
|
|
||||||
* @param context the context
|
|
||||||
*/
|
|
||||||
public LocationServiceManager(Context context) {
|
|
||||||
this.context = context;
|
|
||||||
this.locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LatLng getLastLocation() {
|
|
||||||
if (lastLocation == null) {
|
|
||||||
lastLocation = getLastKnownLocation();
|
|
||||||
if(lastLocation != null) {
|
|
||||||
return LatLng.from(lastLocation);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return LatLng.from(lastLocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Location getLastKnownLocation() {
|
|
||||||
List<String> providers = locationManager.getProviders(true);
|
|
||||||
Location bestLocation = null;
|
|
||||||
for (String provider : providers) {
|
|
||||||
Location l=null;
|
|
||||||
if (ActivityCompat.checkSelfPermission(context, permission.ACCESS_FINE_LOCATION)
|
|
||||||
== PackageManager.PERMISSION_GRANTED
|
|
||||||
&& ActivityCompat.checkSelfPermission(context, permission.ACCESS_COARSE_LOCATION)
|
|
||||||
== PackageManager.PERMISSION_GRANTED) {
|
|
||||||
l = locationManager.getLastKnownLocation(provider);
|
|
||||||
}
|
|
||||||
if (l == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (bestLocation == null
|
|
||||||
|| l.getAccuracy() < bestLocation.getAccuracy()) {
|
|
||||||
bestLocation = l;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (bestLocation == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return bestLocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Registers a LocationManager to listen for current location.
|
|
||||||
*/
|
|
||||||
public void registerLocationManager() {
|
|
||||||
if (!isLocationManagerRegistered) {
|
|
||||||
isLocationManagerRegistered = requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER)
|
|
||||||
&& requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Requests location updates from the specified provider.
|
|
||||||
*
|
|
||||||
* @param locationProvider the location provider
|
|
||||||
* @return true if successful
|
|
||||||
*/
|
|
||||||
public boolean requestLocationUpdatesFromProvider(String locationProvider) {
|
|
||||||
try {
|
|
||||||
// If both providers are not available
|
|
||||||
if (locationManager == null || !(locationManager.getAllProviders().contains(locationProvider))) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
locationManager.requestLocationUpdates(locationProvider,
|
|
||||||
MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS,
|
|
||||||
MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS,
|
|
||||||
this);
|
|
||||||
return true;
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
Timber.e(e, "Illegal argument exception");
|
|
||||||
return false;
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
Timber.e(e, "Security exception");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether a given location is better than the current best location.
|
|
||||||
*
|
|
||||||
* @param location the location to be tested
|
|
||||||
* @param currentBestLocation the current best location
|
|
||||||
* @return LOCATION_SIGNIFICANTLY_CHANGED if location changed significantly
|
|
||||||
* LOCATION_SLIGHTLY_CHANGED if location changed slightly
|
|
||||||
*/
|
|
||||||
private LocationChangeType isBetterLocation(Location location, Location currentBestLocation) {
|
|
||||||
|
|
||||||
if (currentBestLocation == null) {
|
|
||||||
// A new location is always better than no location
|
|
||||||
return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check whether the new location fix is newer or older
|
|
||||||
long timeDelta = location.getTime() - currentBestLocation.getTime();
|
|
||||||
boolean isSignificantlyNewer = timeDelta > MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS;
|
|
||||||
boolean isNewer = timeDelta > 0;
|
|
||||||
|
|
||||||
// Check whether the new location fix is more or less accurate
|
|
||||||
int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
|
|
||||||
boolean isLessAccurate = accuracyDelta > 0;
|
|
||||||
boolean isMoreAccurate = accuracyDelta < 0;
|
|
||||||
boolean isSignificantlyLessAccurate = accuracyDelta > 200;
|
|
||||||
|
|
||||||
// Check if the old and new location are from the same provider
|
|
||||||
boolean isFromSameProvider = isSameProvider(location.getProvider(),
|
|
||||||
currentBestLocation.getProvider());
|
|
||||||
|
|
||||||
float[] results = new float[5];
|
|
||||||
Location.distanceBetween(
|
|
||||||
currentBestLocation.getLatitude(),
|
|
||||||
currentBestLocation.getLongitude(),
|
|
||||||
location.getLatitude(),
|
|
||||||
location.getLongitude(),
|
|
||||||
results);
|
|
||||||
|
|
||||||
// If it's been more than two minutes since the current location, use the new location
|
|
||||||
// because the user has likely moved
|
|
||||||
if (isSignificantlyNewer
|
|
||||||
|| isMoreAccurate
|
|
||||||
|| (isNewer && !isLessAccurate)
|
|
||||||
|| (isNewer && !isSignificantlyLessAccurate && isFromSameProvider)) {
|
|
||||||
if (results[0] < 1000) { // Means change is smaller than 1000 meter
|
|
||||||
return LocationChangeType.LOCATION_SLIGHTLY_CHANGED;
|
|
||||||
} else {
|
|
||||||
return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
|
|
||||||
}
|
|
||||||
} else{
|
|
||||||
return LocationChangeType.LOCATION_NOT_CHANGED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether two providers are the same
|
|
||||||
*/
|
|
||||||
private boolean isSameProvider(String provider1, String provider2) {
|
|
||||||
if (provider1 == null) {
|
|
||||||
return provider2 == null;
|
|
||||||
}
|
|
||||||
return provider1.equals(provider2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters location manager.
|
|
||||||
*/
|
|
||||||
public void unregisterLocationManager() {
|
|
||||||
isLocationManagerRegistered = false;
|
|
||||||
locationExplanationDisplayed.clear();
|
|
||||||
try {
|
|
||||||
locationManager.removeUpdates(this);
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
Timber.e(e, "Security exception");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new listener to the list of location listeners.
|
|
||||||
*
|
|
||||||
* @param listener the new listener
|
|
||||||
*/
|
|
||||||
public void addLocationListener(LocationUpdateListener listener) {
|
|
||||||
if (!locationListeners.contains(listener)) {
|
|
||||||
locationListeners.add(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a listener from the list of location listeners.
|
|
||||||
*
|
|
||||||
* @param listener the listener to be removed
|
|
||||||
*/
|
|
||||||
public void removeLocationListener(LocationUpdateListener listener) {
|
|
||||||
locationListeners.remove(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onLocationChanged(Location location) {
|
|
||||||
Timber.d("on location changed");
|
|
||||||
if (isBetterLocation(location, lastLocation)
|
|
||||||
.equals(LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED)) {
|
|
||||||
lastLocation = location;
|
|
||||||
//lastLocationDuplicate = location;
|
|
||||||
for (LocationUpdateListener listener : locationListeners) {
|
|
||||||
listener.onLocationChangedSignificantly(LatLng.from(lastLocation));
|
|
||||||
}
|
|
||||||
} else if (location.distanceTo(lastLocation) >= 500) {
|
|
||||||
// Update nearby notification card at every 500 meters.
|
|
||||||
for (LocationUpdateListener listener : locationListeners) {
|
|
||||||
listener.onLocationChangedMedium(LatLng.from(lastLocation));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (isBetterLocation(location, lastLocation)
|
|
||||||
.equals(LocationChangeType.LOCATION_SLIGHTLY_CHANGED)) {
|
|
||||||
lastLocation = location;
|
|
||||||
//lastLocationDuplicate = location;
|
|
||||||
for (LocationUpdateListener listener : locationListeners) {
|
|
||||||
listener.onLocationChangedSlightly(LatLng.from(lastLocation));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStatusChanged(String provider, int status, Bundle extras) {
|
|
||||||
Timber.d("%s's status changed to %d", provider, status);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProviderEnabled(String provider) {
|
|
||||||
Timber.d("Provider %s enabled", provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onProviderDisabled(String provider) {
|
|
||||||
Timber.d("Provider %s disabled", provider);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNetworkProviderEnabled() {
|
|
||||||
return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isGPSProviderEnabled() {
|
|
||||||
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum LocationChangeType{
|
|
||||||
LOCATION_SIGNIFICANTLY_CHANGED, //Went out of borders of nearby markers
|
|
||||||
LOCATION_SLIGHTLY_CHANGED, //User might be walking or driving
|
|
||||||
LOCATION_MEDIUM_CHANGED, //Between slight and significant changes, will be used for nearby card view updates.
|
|
||||||
LOCATION_NOT_CHANGED,
|
|
||||||
PERMISSION_JUST_GRANTED,
|
|
||||||
MAP_UPDATED,
|
|
||||||
SEARCH_CUSTOM_AREA,
|
|
||||||
CUSTOM_QUERY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,255 @@
|
||||||
|
package fr.free.nrw.commons.location
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.location.Location
|
||||||
|
import android.location.LocationListener
|
||||||
|
import android.location.LocationManager
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
|
|
||||||
|
class LocationServiceManager(private val context: Context) : LocationListener {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// Maybe these values can be improved for efficiency
|
||||||
|
private const val MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS = 10 * 100L
|
||||||
|
private const val MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS = 1f
|
||||||
|
}
|
||||||
|
|
||||||
|
private val locationManager: LocationManager =
|
||||||
|
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
|
private var lastLocationVar: Location? = null
|
||||||
|
private val locationListeners = CopyOnWriteArrayList<LocationUpdateListener>()
|
||||||
|
private var isLocationManagerRegistered = false
|
||||||
|
private val locationExplanationDisplayed = mutableSetOf<Activity>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new instance of LocationServiceManager.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
fun getLastLocation(): LatLng? {
|
||||||
|
if (lastLocationVar == null) {
|
||||||
|
lastLocationVar = getLastKnownLocation()
|
||||||
|
return lastLocationVar?.let { LatLng.from(it) }
|
||||||
|
}
|
||||||
|
return LatLng.from(lastLocationVar!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLastKnownLocation(): Location? {
|
||||||
|
val providers = locationManager.getProviders(true)
|
||||||
|
var bestLocation: Location? = null
|
||||||
|
for (provider in providers) {
|
||||||
|
val location: Location? = if (
|
||||||
|
ActivityCompat.checkSelfPermission(
|
||||||
|
context,
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION)
|
||||||
|
==
|
||||||
|
PackageManager.PERMISSION_GRANTED &&
|
||||||
|
ActivityCompat.checkSelfPermission(
|
||||||
|
context,
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
|
==
|
||||||
|
PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
locationManager.getLastKnownLocation(provider)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
location != null
|
||||||
|
&&
|
||||||
|
(bestLocation == null || location.accuracy < bestLocation.accuracy)
|
||||||
|
) {
|
||||||
|
bestLocation = location
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bestLocation
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a LocationManager to listen for current location.
|
||||||
|
*/
|
||||||
|
fun registerLocationManager() {
|
||||||
|
if (!isLocationManagerRegistered) {
|
||||||
|
isLocationManagerRegistered =
|
||||||
|
requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) &&
|
||||||
|
requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests location updates from the specified provider.
|
||||||
|
*
|
||||||
|
* @param locationProvider the location provider
|
||||||
|
* @return true if successful
|
||||||
|
*/
|
||||||
|
fun requestLocationUpdatesFromProvider(locationProvider: String): Boolean {
|
||||||
|
return try {
|
||||||
|
if (locationManager.allProviders.contains(locationProvider)) {
|
||||||
|
locationManager.requestLocationUpdates(
|
||||||
|
locationProvider,
|
||||||
|
MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS,
|
||||||
|
MIN_LOCATION_UPDATE_REQUEST_DISTANCE_IN_METERS,
|
||||||
|
this
|
||||||
|
)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
Timber.e(e, "Illegal argument exception")
|
||||||
|
false
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
Timber.e(e, "Security exception")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a given location is better than the current best location.
|
||||||
|
*
|
||||||
|
* @param location the location to be tested
|
||||||
|
* @param currentBestLocation the current best location
|
||||||
|
* @return LOCATION_SIGNIFICANTLY_CHANGED if location changed significantly
|
||||||
|
* LOCATION_SLIGHTLY_CHANGED if location changed slightly
|
||||||
|
*/
|
||||||
|
private fun isBetterLocation(location: Location, currentBestLocation: Location?): LocationChangeType {
|
||||||
|
if (currentBestLocation == null) {
|
||||||
|
return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED
|
||||||
|
}
|
||||||
|
|
||||||
|
val timeDelta = location.time - currentBestLocation.time
|
||||||
|
val isSignificantlyNewer = timeDelta > MIN_LOCATION_UPDATE_REQUEST_TIME_IN_MILLIS
|
||||||
|
val isNewer = timeDelta > 0
|
||||||
|
val accuracyDelta = (location.accuracy - currentBestLocation.accuracy).toInt()
|
||||||
|
val isMoreAccurate = accuracyDelta < 0
|
||||||
|
val isSignificantlyLessAccurate = accuracyDelta > 200
|
||||||
|
val isFromSameProvider = isSameProvider(location.provider, currentBestLocation.provider)
|
||||||
|
|
||||||
|
val results = FloatArray(5)
|
||||||
|
Location.distanceBetween(
|
||||||
|
currentBestLocation.latitude, currentBestLocation.longitude,
|
||||||
|
location.latitude, location.longitude,
|
||||||
|
results
|
||||||
|
)
|
||||||
|
|
||||||
|
return when {
|
||||||
|
isSignificantlyNewer
|
||||||
|
||
|
||||||
|
isMoreAccurate
|
||||||
|
||
|
||||||
|
(isNewer && !isSignificantlyLessAccurate && isFromSameProvider) -> {
|
||||||
|
if (results[0] < 1000) LocationChangeType.LOCATION_SLIGHTLY_CHANGED
|
||||||
|
else LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED
|
||||||
|
}
|
||||||
|
else -> LocationChangeType.LOCATION_NOT_CHANGED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether two providers are the same
|
||||||
|
*/
|
||||||
|
private fun isSameProvider(provider1: String?, provider2: String?): Boolean {
|
||||||
|
return provider1 == provider2
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters location manager.
|
||||||
|
*/
|
||||||
|
fun unregisterLocationManager() {
|
||||||
|
isLocationManagerRegistered = false
|
||||||
|
locationExplanationDisplayed.clear()
|
||||||
|
try {
|
||||||
|
locationManager.removeUpdates(this)
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
Timber.e(e, "Security exception")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new listener to the list of location listeners.
|
||||||
|
*
|
||||||
|
* @param listener the new listener
|
||||||
|
*/
|
||||||
|
fun addLocationListener(listener: LocationUpdateListener) {
|
||||||
|
if (!locationListeners.contains(listener)) {
|
||||||
|
locationListeners.add(listener)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a listener from the list of location listeners.
|
||||||
|
*
|
||||||
|
* @param listener the listener to be removed
|
||||||
|
*/
|
||||||
|
fun removeLocationListener(listener: LocationUpdateListener) {
|
||||||
|
locationListeners.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLocationChanged(location: Location) {
|
||||||
|
Timber.d("on location changed")
|
||||||
|
val changeType = isBetterLocation(location, lastLocationVar)
|
||||||
|
if (changeType == LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED) {
|
||||||
|
lastLocationVar = location
|
||||||
|
locationListeners.forEach { it.onLocationChangedSignificantly(LatLng.from(location)) }
|
||||||
|
} else if (lastLocationVar?.let { location.distanceTo(it) }!! >= 500) {
|
||||||
|
locationListeners.forEach { it.onLocationChangedMedium(LatLng.from(location)) }
|
||||||
|
} else if (changeType == LocationChangeType.LOCATION_SLIGHTLY_CHANGED) {
|
||||||
|
lastLocationVar = location
|
||||||
|
locationListeners.forEach { it.onLocationChangedSlightly(LatLng.from(location)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated("Deprecated in Java", ReplaceWith(
|
||||||
|
"Timber.d(\"%s's status changed to %d\", provider, status)",
|
||||||
|
"timber.log.Timber"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {
|
||||||
|
Timber.d("%s's status changed to %d", provider, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
override fun onProviderEnabled(provider: String) {
|
||||||
|
Timber.d("Provider %s enabled", provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onProviderDisabled(provider: String) {
|
||||||
|
Timber.d("Provider %s disabled", provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isNetworkProviderEnabled(): Boolean {
|
||||||
|
return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isGPSProviderEnabled(): Boolean {
|
||||||
|
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class LocationChangeType {
|
||||||
|
LOCATION_SIGNIFICANTLY_CHANGED,
|
||||||
|
LOCATION_SLIGHTLY_CHANGED,
|
||||||
|
LOCATION_MEDIUM_CHANGED,
|
||||||
|
LOCATION_NOT_CHANGED,
|
||||||
|
PERMISSION_JUST_GRANTED,
|
||||||
|
MAP_UPDATED,
|
||||||
|
SEARCH_CUSTOM_AREA,
|
||||||
|
CUSTOM_QUERY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
package fr.free.nrw.commons.location;
|
|
||||||
|
|
||||||
public interface LocationUpdateListener {
|
|
||||||
void onLocationChangedSignificantly(LatLng latLng); // Will be used to update all nearby markers on the map
|
|
||||||
void onLocationChangedSlightly(LatLng latLng); // Will be used to track users motion
|
|
||||||
void onLocationChangedMedium(LatLng latLng); // Will be used updating nearby card view notification
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.free.nrw.commons.location
|
||||||
|
|
||||||
|
interface LocationUpdateListener {
|
||||||
|
// Will be used to update all nearby markers on the map
|
||||||
|
fun onLocationChangedSignificantly(latLng: LatLng)
|
||||||
|
|
||||||
|
// Will be used to track users motion
|
||||||
|
fun onLocationChangedSlightly(latLng: LatLng)
|
||||||
|
|
||||||
|
// Will be used updating nearby card view notification
|
||||||
|
fun onLocationChangedMedium(latLng: LatLng)
|
||||||
|
}
|
||||||
|
|
@ -42,8 +42,8 @@ class LanguagesAdapter constructor(
|
||||||
AppLanguageLookUpTable(context)
|
AppLanguageLookUpTable(context)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
languageNamesList = language.localizedNames
|
languageNamesList = language.getLocalizedNames()
|
||||||
languageCodesList = language.codes
|
languageCodesList = language.getCodes()
|
||||||
}
|
}
|
||||||
|
|
||||||
private val filter = LanguageFilter()
|
private val filter = LanguageFilter()
|
||||||
|
|
@ -117,7 +117,7 @@ class LanguagesAdapter constructor(
|
||||||
*/
|
*/
|
||||||
fun getIndexOfUserDefaultLocale(context: Context): Int {
|
fun getIndexOfUserDefaultLocale(context: Context): Int {
|
||||||
val userLanguageCode = context.locale?.language ?: return DEFAULT_INDEX
|
val userLanguageCode = context.locale?.language ?: return DEFAULT_INDEX
|
||||||
return language.codes.indexOf(userLanguageCode).takeIf { it >= 0 } ?: DEFAULT_INDEX
|
return language.getCodes().indexOf(userLanguageCode).takeIf { it >= 0 } ?: DEFAULT_INDEX
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIndexOfLanguageCode(languageCode: String): Int = languageCodesList.indexOf(languageCode)
|
fun getIndexOfLanguageCode(languageCode: String): Int = languageCodesList.indexOf(languageCode)
|
||||||
|
|
@ -128,17 +128,17 @@ class LanguagesAdapter constructor(
|
||||||
override fun performFiltering(constraint: CharSequence?): FilterResults {
|
override fun performFiltering(constraint: CharSequence?): FilterResults {
|
||||||
val filterResults = FilterResults()
|
val filterResults = FilterResults()
|
||||||
val temp: LinkedHashMap<String, String> = LinkedHashMap()
|
val temp: LinkedHashMap<String, String> = LinkedHashMap()
|
||||||
if (constraint != null && language.localizedNames != null) {
|
if (constraint != null) {
|
||||||
val length: Int = language.localizedNames.size
|
val length: Int = language.getLocalizedNames().size
|
||||||
var i = 0
|
var i = 0
|
||||||
while (i < length) {
|
while (i < length) {
|
||||||
val key: String = language.codes[i]
|
val key: String = language.getCodes()[i]
|
||||||
val value: String = language.localizedNames[i]
|
val value: String = language.getLocalizedNames()[i]
|
||||||
val defaultlanguagecode = getIndexOfUserDefaultLocale(context)
|
val defaultlanguagecode = getIndexOfUserDefaultLocale(context)
|
||||||
if (value.contains(constraint, true) ||
|
if (value.contains(constraint, true) ||
|
||||||
Locale(key)
|
Locale(key)
|
||||||
.getDisplayName(
|
.getDisplayName(
|
||||||
Locale(language.codes[defaultlanguagecode]),
|
Locale(language.getCodes()[defaultlanguagecode]),
|
||||||
).contains(constraint, true)
|
).contains(constraint, true)
|
||||||
) {
|
) {
|
||||||
temp[key] = value
|
temp[key] = value
|
||||||
|
|
|
||||||
|
|
@ -62,5 +62,5 @@ class LatLngTests {
|
||||||
private fun assertPrettyCoordinateString(
|
private fun assertPrettyCoordinateString(
|
||||||
expected: String,
|
expected: String,
|
||||||
place: LatLng,
|
place: LatLng,
|
||||||
) = assertEquals(expected, place.prettyCoordinateString)
|
) = assertEquals(expected, place.getPrettyCoordinateString())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -248,11 +248,11 @@ class MediaDetailFragmentUnitTests {
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun testOnUpdateCoordinatesClickedCurrentLocationNull() {
|
fun testOnUpdateCoordinatesClickedCurrentLocationNull() {
|
||||||
`when`(media.coordinates).thenReturn(null)
|
`when`(media.coordinates).thenReturn(null)
|
||||||
`when`(locationManager.lastLocation).thenReturn(null)
|
`when`(locationManager.getLastLocation()).thenReturn(null)
|
||||||
`when`(applicationKvStore.getString(lastLocation)).thenReturn("37.773972,-122.431297")
|
`when`(applicationKvStore.getString(lastLocation)).thenReturn("37.773972,-122.431297")
|
||||||
fragment.onUpdateCoordinatesClicked()
|
fragment.onUpdateCoordinatesClicked()
|
||||||
Mockito.verify(media, Mockito.times(1)).coordinates
|
Mockito.verify(media, Mockito.times(1)).coordinates
|
||||||
Mockito.verify(locationManager, Mockito.times(1)).lastLocation
|
Mockito.verify(locationManager, Mockito.times(1)).getLastLocation()
|
||||||
val shadowActivity: ShadowActivity = shadowOf(activity)
|
val shadowActivity: ShadowActivity = shadowOf(activity)
|
||||||
val startedIntent = shadowActivity.nextStartedActivity
|
val startedIntent = shadowActivity.nextStartedActivity
|
||||||
val shadowIntent: ShadowIntent = shadowOf(startedIntent)
|
val shadowIntent: ShadowIntent = shadowOf(startedIntent)
|
||||||
|
|
@ -276,11 +276,11 @@ class MediaDetailFragmentUnitTests {
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun testOnUpdateCoordinatesClickedCurrentLocationNotNull() {
|
fun testOnUpdateCoordinatesClickedCurrentLocationNotNull() {
|
||||||
`when`(media.coordinates).thenReturn(null)
|
`when`(media.coordinates).thenReturn(null)
|
||||||
`when`(locationManager.lastLocation).thenReturn(LatLng(-0.000001, -0.999999, 0f))
|
`when`(locationManager.getLastLocation()).thenReturn(LatLng(-0.000001, -0.999999, 0f))
|
||||||
`when`(applicationKvStore.getString(lastLocation)).thenReturn("37.773972,-122.431297")
|
`when`(applicationKvStore.getString(lastLocation)).thenReturn("37.773972,-122.431297")
|
||||||
|
|
||||||
fragment.onUpdateCoordinatesClicked()
|
fragment.onUpdateCoordinatesClicked()
|
||||||
Mockito.verify(locationManager, Mockito.times(3)).lastLocation
|
Mockito.verify(locationManager, Mockito.times(3)).getLastLocation()
|
||||||
val shadowActivity: ShadowActivity = shadowOf(activity)
|
val shadowActivity: ShadowActivity = shadowOf(activity)
|
||||||
val startedIntent = shadowActivity.nextStartedActivity
|
val startedIntent = shadowActivity.nextStartedActivity
|
||||||
val shadowIntent: ShadowIntent = shadowOf(startedIntent)
|
val shadowIntent: ShadowIntent = shadowOf(startedIntent)
|
||||||
|
|
|
||||||
|
|
@ -54,8 +54,8 @@ class LanguagesAdapterTest {
|
||||||
.from(context)
|
.from(context)
|
||||||
.inflate(R.layout.row_item_languages_spinner, null) as View
|
.inflate(R.layout.row_item_languages_spinner, null) as View
|
||||||
|
|
||||||
languageNamesList = language.localizedNames
|
languageNamesList = language.getLocalizedNames()
|
||||||
languageCodesList = language.codes
|
languageCodesList = language.getCodes()
|
||||||
|
|
||||||
languagesAdapter = LanguagesAdapter(context, selectedLanguages)
|
languagesAdapter = LanguagesAdapter(context, selectedLanguages)
|
||||||
}
|
}
|
||||||
|
|
@ -124,12 +124,12 @@ class LanguagesAdapterTest {
|
||||||
var i = 0
|
var i = 0
|
||||||
var s = 0
|
var s = 0
|
||||||
while (i < length) {
|
while (i < length) {
|
||||||
val key: String = language.codes[i]
|
val key: String = language.getCodes()[i]
|
||||||
val value: String = language.localizedNames[i]
|
val value: String = language.getLocalizedNames()[i]
|
||||||
if (value.contains(constraint, true) ||
|
if (value.contains(constraint, true) ||
|
||||||
Locale(key)
|
Locale(key)
|
||||||
.getDisplayName(
|
.getDisplayName(
|
||||||
Locale(language.codes[defaultlanguagecode!!]),
|
Locale(language.getCodes()[defaultlanguagecode!!]),
|
||||||
).contains(constraint, true)
|
).contains(constraint, true)
|
||||||
) {
|
) {
|
||||||
s++
|
s++
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue