Migrated location and language module from Java to Kotlin

This commit is contained in:
Saifuddin 2024-12-02 21:50:24 +05:30
parent 574b9e3c15
commit 4148049156
10 changed files with 467 additions and 521 deletions

View file

@ -423,7 +423,7 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
* Moves map to GPS location
*/
private fun moveMapToGPSLocation() {
locationManager.lastLocation?.let {
locationManager.lastLocationVar?.let {
moveMapTo(GeoPoint(it.latitude, it.longitude))
}
}
@ -591,7 +591,7 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
override fun onLocationPermissionGranted() {
if (moveToCurrentLocation || activity != "MediaActivity") {
if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn) {
if (locationPermissionsHelper.isLocationAccessToAppsTurnedOn()) {
locationManager.requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER)
locationManager.requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER)
addMarkerAtGPSLocation()
@ -606,7 +606,7 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
* Adds a marker at the user's GPS location
*/
private fun addMarkerAtGPSLocation() {
locationManager.lastLocation?.let {
locationManager.lastLocationVar?.let {
addLocationMarker(GeoPoint(it.latitude, it.longitude))
markerImage.translationY = 0f
}

View file

@ -1,116 +1,111 @@
package fr.free.nrw.commons.language;
package fr.free.nrw.commons.language
import android.content.Context;
import android.content.res.Resources;
import android.text.TextUtils;
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 androidx.annotation.ArrayRes
import fr.free.nrw.commons.R
import java.lang.ref.SoftReference
import java.util.Arrays
import java.util.Locale
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.
* present in this table as it is statically bundled with the app. */
class AppLanguageLookUpTable(context: Context) {
@NonNull private final Resources resources;
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.
@NonNull private SoftReference<List<String>> codesRef = new SoftReference<>(null);
private var codesRef = SoftReference<List<String>>(null)
// English names for all app supported languages in fixed order.
@NonNull private SoftReference<List<String>> canonicalNamesRef = new SoftReference<>(null);
private var canonicalNamesRef = SoftReference<List<String>>(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();
}
private var localizedNamesRef = SoftReference<List<String>>(null)
/**
* @return Nonnull immutable list. The special code representing the dynamic system language is
* null.
*/
@NonNull
public List<String> getCodes() {
List<String> codes = codesRef.get();
fun getCodes(): List<String> {
var codes = codesRef.get()
if (codes == null) {
codes = getStringList(R.array.preference_language_keys);
codesRef = new SoftReference<>(codes);
codes = getStringList(R.array.preference_language_keys)
codesRef = SoftReference(codes)
}
return 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);
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;
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);
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;
return name
}
public List<String> getCanonicalNames() {
List<String> names = canonicalNamesRef.get();
fun getCanonicalNames(): List<String> {
var names = canonicalNamesRef.get()
if (names == null) {
names = getStringList(R.array.preference_language_canonical_names);
canonicalNamesRef = new SoftReference<>(names);
names = getStringList(R.array.preference_language_canonical_names)
canonicalNamesRef = SoftReference(names)
}
return names;
return names
}
public List<String> getLocalizedNames() {
List<String> names = localizedNamesRef.get();
fun getLocalizedNames(): List<String> {
var names = localizedNamesRef.get()
if (names == null) {
names = getStringList(R.array.preference_language_local_names);
localizedNamesRef = new SoftReference<>(names);
names = getStringList(R.array.preference_language_local_names)
localizedNamesRef = SoftReference(names)
}
return names;
return names
}
public boolean isSupportedCode(@Nullable String code) {
return getCodes().contains(code);
fun isSupportedCode(code: String?): Boolean {
return getCodes().contains(code)
}
private <T> T defaultIndex(List<T> list, int index, T defaultValue) {
return inBounds(list, index) ? list.get(index) : defaultValue;
private fun <T> defaultIndex(list: List<T>, index: Int, defaultValue: T?): T? {
return if (inBounds(list, index)) list[index] else defaultValue
}
/**
@ -121,21 +116,20 @@ public class AppLanguageLookUpTable {
* 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);
private fun indexOfCode(code: String?): Int {
return getCodes().indexOf(code)
}
/** @return Nonnull immutable list. */
@NonNull
private List<String> getStringList(int id) {
return Arrays.asList(getStringArray(id));
private fun getStringList(id: Int): List<String> {
return getStringArray(id).toList()
}
private boolean inBounds(List<?> list, int index) {
return index >= 0 && index < list.size();
private fun inBounds(list: List<*>, index: Int): Boolean {
return index in list.indices
}
public String[] getStringArray(@ArrayRes int id) {
return resources.getStringArray(id);
fun getStringArray(@ArrayRes id: Int): Array<String> {
return resources.getStringArray(id)
}
}

View file

@ -1,132 +1,106 @@
package fr.free.nrw.commons.location;
package fr.free.nrw.commons.location
import android.location.Location;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
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
import androidx.annotation.NonNull;
/**
* a latitude and longitude point with accuracy information, often of a picture
* 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;
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°
*
* @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;
init {
val adjustedLongitude = when {
longitude in -180.0..180.0 -> longitude
else -> ((longitude - 180.0) % 360.0 + 360.0) % 360.0 - 180.0
}
this.latitude = Math.max(-90.0D, Math.min(90.0D, latitude));
this.accuracy = accuracy;
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 in A parcelable which contains the latitude, longitude, and accuracy
* @param parcel A parcelable which contains the latitude, longitude, and accuracy
*/
public LatLng(Parcel in) {
latitude = in.readDouble();
longitude = in.readDouble();
accuracy = in.readFloat();
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
}
/**
* 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
* Checks for equality of two LatLng objects.
* @param other the second LatLng object
*/
public static LatLng from(@NonNull Location location) {
return new LatLng(location.getLatitude(), location.getLongitude(), location.getAccuracy());
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()
}
/**
* creates a hash code for the longitude and longitude
* Returns a string representation of the latitude 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";
override fun toString(): String {
return "lat/lng: ($latitude,$longitude)"
}
/**
@ -135,64 +109,42 @@ public class LatLng implements Parcelable {
*
* @return The formatted string.
*/
public String getPrettyCoordinateString() {
return formatCoordinate(this.latitude) + " " + this.getNorthSouth() + ", "
+ formatCoordinate(this.longitude) + " " + this.getEastWest();
fun getPrettyCoordinateString(): String {
return "${formatCoordinate(latitude)} ${getNorthSouth()}, " +
"${formatCoordinate(longitude)} ${getEastWest()}"
}
/**
* Return the location accuracy in meter.
*
* @return float
* Gets a URI for a Google Maps intent at the location.
*/
public float getAccuracy() {
return accuracy;
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()
}
/**
* Return the longitude in degrees.
* Returns "N" or "S" depending on the latitude.
*
* @return double
* @return "N" or "S".
*/
public double getLongitude() {
return longitude;
}
private fun getNorthSouth(): String = if (latitude < 0) "S" else "N"
/**
* Return the latitude in degrees.
* Returns "E" or "W" depending on the longitude.
*
* @return double
* @return "E" or "W".
*/
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];
}
};
}
private fun getEastWest(): String = if (longitude in 0.0..179.999) "E" else "W"
}

View file

@ -1,18 +1,16 @@
package fr.free.nrw.commons.location;
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;
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.
@ -37,51 +35,58 @@ import fr.free.nrw.commons.utils.PermissionUtils;
* 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;
}
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
* @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
fun requestForLocationAccess(
dialogTitleResource: Int,
dialogTextResource: Int
) {
if (checkLocationPermission(activity)) {
callback.onLocationPermissionGranted();
callback?.onLocationPermissionGranted()
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
permission.ACCESS_FINE_LOCATION)) {
DialogUtil.showAlertDialog(activity, activity.getString(dialogTitleResource),
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);
{
ActivityCompat.requestPermissions(
activity,
arrayOf(permission.ACCESS_FINE_LOCATION),
1
)
},
{
callback?.onLocationPermissionDenied(
activity.getString(R.string.upload_map_location_access)
)
},
() -> callback.onLocationPermissionDenied(
activity.getString(R.string.upload_map_location_access)),
null,
false);
false
)
} else {
ActivityCompat.requestPermissions(activity,
new String[]{permission.ACCESS_FINE_LOCATION},
RequestCodes.LOCATION);
ActivityCompat.requestPermissions(
activity,
arrayOf(permission.ACCESS_FINE_LOCATION),
RequestCodes.LOCATION
)
}
}
}
@ -92,33 +97,38 @@ public class LocationPermissionsHelper {
* @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()
);
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 Activtiy object
* @param activity Activity object
*/
public void openLocationSettings(Activity activity) {
final Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
final PackageManager packageManager = activity.getPackageManager();
fun openLocationSettings(activity: Activity) {
val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
val packageManager = activity.packageManager
if (intent.resolveActivity(packageManager) != null) {
activity.startActivity(intent);
activity.startActivity(intent)
} else {
Toast.makeText(activity, R.string.cannot_open_location_settings, Toast.LENGTH_LONG)
.show();
.show()
}
}
@ -128,16 +138,22 @@ public class LocationPermissionsHelper {
* @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()
);
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()
}
)
}
/**
@ -145,22 +161,20 @@ public class LocationPermissionsHelper {
*
* @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);
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
*/
public boolean isLocationAccessToAppsTurnedOn() {
return (locationManager.isNetworkProviderEnabled()
|| locationManager.isGPSProviderEnabled());
fun isLocationAccessToAppsTurnedOn(): Boolean {
return locationManager.isNetworkProviderEnabled() || locationManager.isGPSProviderEnabled()
}
/**
@ -169,18 +183,18 @@ public class LocationPermissionsHelper {
* @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});
fun checkLocationPermission(activity: Activity): Boolean {
return PermissionUtils.hasPermission(
activity,
arrayOf(permission.ACCESS_FINE_LOCATION)
)
}
/**
* Handle onPermissionDenied within individual classes based on the requirements
*/
public interface LocationPermissionCallback {
void onLocationPermissionDenied(String toastMessage);
void onLocationPermissionGranted();
interface LocationPermissionCallback {
fun onLocationPermissionDenied(toastMessage: String)
fun onLocationPermissionGranted()
}
}

View file

@ -1,90 +1,85 @@
package fr.free.nrw.commons.location;
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 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
import timber.log.Timber;
public class LocationServiceManager implements LocationListener {
class LocationServiceManager(private val context: Context) : 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;
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 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;
private val locationManager: LocationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
var lastLocationVar: Location? = null
private val locationListeners = CopyOnWriteArrayList<LocationUpdateListener>()
private var isLocationManagerRegistered = false
private val locationExplanationDisplayed = mutableSetOf<Activity>()
/**
* 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);
fun getLastLocation(): LatLng? {
if (lastLocationVar == null) {
lastLocationVar = getLastKnownLocation()
return lastLocationVar?.let { LatLng.from(it) }
}
return LatLng.from(lastLocationVar!!)
}
public LatLng getLastLocation() {
if (lastLocation == null) {
lastLocation = getLastKnownLocation();
if(lastLocation != null) {
return LatLng.from(lastLocation);
}
else {
return null;
}
}
return LatLng.from(lastLocation);
}
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
}
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 (
location != null
&&
(bestLocation == null || location.accuracy < bestLocation.accuracy)
) {
bestLocation = location
}
}
if (bestLocation == null) {
return null;
}
return bestLocation;
return bestLocation
}
/**
* Registers a LocationManager to listen for current location.
*/
public void registerLocationManager() {
fun registerLocationManager() {
if (!isLocationManagerRegistered) {
isLocationManagerRegistered = requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER)
&& requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER);
isLocationManagerRegistered =
requestLocationUpdatesFromProvider(LocationManager.NETWORK_PROVIDER) &&
requestLocationUpdatesFromProvider(LocationManager.GPS_PROVIDER)
}
}
@ -94,100 +89,86 @@ public class LocationServiceManager implements LocationListener {
* @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,
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);
return true;
} catch (IllegalArgumentException e) {
Timber.e(e, "Illegal argument exception");
return false;
} catch (SecurityException e) {
Timber.e(e, "Security exception");
return false;
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 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) {
private fun isBetterLocation(location: Location, currentBestLocation: Location?): LocationChangeType {
if (currentBestLocation == null) {
// A new location is always better than no location
return LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED;
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;
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)
// 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];
val results = FloatArray(5)
Location.distanceBetween(
currentBestLocation.getLatitude(),
currentBestLocation.getLongitude(),
location.getLatitude(),
location.getLongitude(),
results);
currentBestLocation.latitude, currentBestLocation.longitude,
location.latitude, location.longitude,
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;
return when {
isSignificantlyNewer
||
isMoreAccurate
||
(isNewer && !isSignificantlyLessAccurate && isFromSameProvider) -> {
if (results[0] < 1000) LocationChangeType.LOCATION_SLIGHTLY_CHANGED
else LocationChangeType.LOCATION_SIGNIFICANTLY_CHANGED
}
} else{
return LocationChangeType.LOCATION_NOT_CHANGED;
else -> 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);
private fun isSameProvider(provider1: String?, provider2: String?): Boolean {
return provider1 == provider2
}
/**
* Unregisters location manager.
*/
public void unregisterLocationManager() {
isLocationManagerRegistered = false;
locationExplanationDisplayed.clear();
fun unregisterLocationManager() {
isLocationManagerRegistered = false
locationExplanationDisplayed.clear()
try {
locationManager.removeUpdates(this);
} catch (SecurityException e) {
Timber.e(e, "Security exception");
locationManager.removeUpdates(this)
} catch (e: SecurityException) {
Timber.e(e, "Security exception")
}
}
@ -196,9 +177,9 @@ public class LocationServiceManager implements LocationListener {
*
* @param listener the new listener
*/
public void addLocationListener(LocationUpdateListener listener) {
fun addLocationListener(listener: LocationUpdateListener) {
if (!locationListeners.contains(listener)) {
locationListeners.add(listener);
locationListeners.add(listener)
}
}
@ -207,64 +188,55 @@ public class LocationServiceManager implements LocationListener {
*
* @param listener the listener to be removed
*/
public void removeLocationListener(LocationUpdateListener listener) {
locationListeners.remove(listener);
fun removeLocationListener(listener: LocationUpdateListener) {
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 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)) }
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Timber.d("%s's status changed to %d", provider, status);
@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
public void onProviderEnabled(String provider) {
Timber.d("Provider %s enabled", provider);
override fun onProviderEnabled(provider: String) {
Timber.d("Provider %s enabled", provider)
}
@Override
public void onProviderDisabled(String provider) {
Timber.d("Provider %s disabled", provider);
override fun onProviderDisabled(provider: String) {
Timber.d("Provider %s disabled", provider)
}
public boolean isNetworkProviderEnabled() {
return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
fun isNetworkProviderEnabled(): Boolean {
return locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
}
public boolean isGPSProviderEnabled() {
return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
fun isGPSProviderEnabled(): Boolean {
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.
enum class LocationChangeType {
LOCATION_SIGNIFICANTLY_CHANGED,
LOCATION_SLIGHTLY_CHANGED,
LOCATION_MEDIUM_CHANGED,
LOCATION_NOT_CHANGED,
PERMISSION_JUST_GRANTED,
MAP_UPDATED,
@ -272,3 +244,12 @@ public class LocationServiceManager implements LocationListener {
CUSTOM_QUERY
}
}

View file

@ -1,7 +1,12 @@
package fr.free.nrw.commons.location;
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
}
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)
}

View file

@ -42,8 +42,8 @@ class LanguagesAdapter constructor(
AppLanguageLookUpTable(context)
init {
languageNamesList = language.localizedNames
languageCodesList = language.codes
languageNamesList = language.getLocalizedNames()
languageCodesList = language.getCodes()
}
private val filter = LanguageFilter()
@ -117,7 +117,7 @@ class LanguagesAdapter constructor(
*/
fun getIndexOfUserDefaultLocale(context: Context): Int {
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)
@ -128,17 +128,17 @@ class LanguagesAdapter constructor(
override fun performFiltering(constraint: CharSequence?): FilterResults {
val filterResults = FilterResults()
val temp: LinkedHashMap<String, String> = LinkedHashMap()
if (constraint != null && language.localizedNames != null) {
val length: Int = language.localizedNames.size
if (constraint != null) {
val length: Int = language.getLocalizedNames().size
var i = 0
while (i < length) {
val key: String = language.codes[i]
val value: String = language.localizedNames[i]
val key: String = language.getCodes()[i]
val value: String = language.getLocalizedNames()[i]
val defaultlanguagecode = getIndexOfUserDefaultLocale(context)
if (value.contains(constraint, true) ||
Locale(key)
.getDisplayName(
Locale(language.codes[defaultlanguagecode]),
Locale(language.getCodes()[defaultlanguagecode]),
).contains(constraint, true)
) {
temp[key] = value

View file

@ -62,5 +62,5 @@ class LatLngTests {
private fun assertPrettyCoordinateString(
expected: String,
place: LatLng,
) = assertEquals(expected, place.prettyCoordinateString)
) = assertEquals(expected, place.getPrettyCoordinateString())
}

View file

@ -248,11 +248,11 @@ class MediaDetailFragmentUnitTests {
@Throws(Exception::class)
fun testOnUpdateCoordinatesClickedCurrentLocationNull() {
`when`(media.coordinates).thenReturn(null)
`when`(locationManager.lastLocation).thenReturn(null)
`when`(locationManager.getLastLocation()).thenReturn(null)
`when`(applicationKvStore.getString(lastLocation)).thenReturn("37.773972,-122.431297")
fragment.onUpdateCoordinatesClicked()
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 startedIntent = shadowActivity.nextStartedActivity
val shadowIntent: ShadowIntent = shadowOf(startedIntent)
@ -276,11 +276,11 @@ class MediaDetailFragmentUnitTests {
@Throws(Exception::class)
fun testOnUpdateCoordinatesClickedCurrentLocationNotNull() {
`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")
fragment.onUpdateCoordinatesClicked()
Mockito.verify(locationManager, Mockito.times(3)).lastLocation
Mockito.verify(locationManager, Mockito.times(3)).getLastLocation()
val shadowActivity: ShadowActivity = shadowOf(activity)
val startedIntent = shadowActivity.nextStartedActivity
val shadowIntent: ShadowIntent = shadowOf(startedIntent)

View file

@ -54,8 +54,8 @@ class LanguagesAdapterTest {
.from(context)
.inflate(R.layout.row_item_languages_spinner, null) as View
languageNamesList = language.localizedNames
languageCodesList = language.codes
languageNamesList = language.getLocalizedNames()
languageCodesList = language.getCodes()
languagesAdapter = LanguagesAdapter(context, selectedLanguages)
}
@ -124,12 +124,12 @@ class LanguagesAdapterTest {
var i = 0
var s = 0
while (i < length) {
val key: String = language.codes[i]
val value: String = language.localizedNames[i]
val key: String = language.getCodes()[i]
val value: String = language.getLocalizedNames()[i]
if (value.contains(constraint, true) ||
Locale(key)
.getDisplayName(
Locale(language.codes[defaultlanguagecode!!]),
Locale(language.getCodes()[defaultlanguagecode!!]),
).contains(constraint, true)
) {
s++