Migrated the following files in util module to Kotlin

- ImageUtils
- ImageUtilsWrapper
- LangCodeUtils
- LayoutUtils
- LengthUtils
- LocationUtils
- MapUtils
This commit is contained in:
Saifuddin 2024-11-17 21:20:36 +05:30
parent 3126ad947d
commit 4b95f47b29
12 changed files with 453 additions and 421 deletions

View file

@ -5,7 +5,6 @@ import static fr.free.nrw.commons.contributions.Contribution.STATE_FAILED;
import static fr.free.nrw.commons.contributions.Contribution.STATE_PAUSED;
import static fr.free.nrw.commons.nearby.fragments.NearbyParentFragment.WLM_URL;
import static fr.free.nrw.commons.profile.ProfileActivity.KEY_USERNAME;
import static fr.free.nrw.commons.utils.ImageUtils.IMAGE_OK;
import static fr.free.nrw.commons.utils.LengthUtils.computeBearing;
import static fr.free.nrw.commons.utils.LengthUtils.formatDistanceBetween;
@ -23,12 +22,10 @@ import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MenuItem.OnMenuItemClickListener;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.ActivityResultCallback;
@ -39,7 +36,6 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
import androidx.fragment.app.FragmentTransaction;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Utils;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.databinding.FragmentContributionsBinding;

View file

@ -1,10 +1,10 @@
package fr.free.nrw.commons.delete;
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_DELETE;
import static fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources;
import android.annotation.SuppressLint;
import android.content.Context;
import static fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
@ -16,6 +16,7 @@ import fr.free.nrw.commons.actions.PageEditClient;
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
import fr.free.nrw.commons.notification.NotificationHelper;
import fr.free.nrw.commons.review.ReviewController;
import fr.free.nrw.commons.utils.LangCodeUtils;
import fr.free.nrw.commons.utils.ViewUtilWrapper;
import io.reactivex.Observable;
import io.reactivex.Single;

View file

@ -310,7 +310,7 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
}
private void startMapWithoutPermission() {
lastKnownLocation = MapUtils.defaultLatLng;
lastKnownLocation = MapUtils.getDefaultLatLng();
moveCameraToPosition(
new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude()));
presenter.onMapReady(exploreMapController);
@ -331,7 +331,7 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
!locationPermissionsHelper.checkLocationPermission(getActivity())) {
isPermissionDenied = true;
}
lastKnownLocation = MapUtils.defaultLatLng;
lastKnownLocation = MapUtils.getDefaultLatLng();
moveCameraToPosition(
new GeoPoint(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude()));
presenter.onMapReady(exploreMapController);

View file

@ -43,7 +43,6 @@ import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
import android.widget.Toast;
import androidx.activity.result.ActivityResultCallback;
import androidx.activity.result.ActivityResultLauncher;
@ -701,7 +700,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
= new LatLng(Double.parseDouble(locationLatLng[0]),
Double.parseDouble(locationLatLng[1]), 1f);
} else {
lastKnownLocation = MapUtils.defaultLatLng;
lastKnownLocation = MapUtils.getDefaultLatLng();
}
if (binding.map != null) {
moveCameraToPosition(
@ -793,7 +792,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
hideBottomSheet();
binding.nearbyFilter.searchViewLayout.searchView.setOnQueryTextFocusChangeListener(
(v, hasFocus) -> {
LayoutUtils.setLayoutHeightAllignedToWidth(1.25,
LayoutUtils.setLayoutHeightAlignedToWidth(1.25,
binding.nearbyFilterList.getRoot());
if (hasFocus) {
binding.nearbyFilterList.getRoot().setVisibility(View.VISIBLE);
@ -834,7 +833,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
.getLayoutParams().width = (int) LayoutUtils.getScreenWidth(getActivity(),
0.75);
binding.nearbyFilterList.searchListView.setAdapter(nearbyFilterSearchRecyclerViewAdapter);
LayoutUtils.setLayoutHeightAllignedToWidth(1.25, binding.nearbyFilterList.getRoot());
LayoutUtils.setLayoutHeightAlignedToWidth(1.25, binding.nearbyFilterList.getRoot());
compositeDisposable.add(
RxSearchView.queryTextChanges(binding.nearbyFilter.searchViewLayout.searchView)
.takeUntil(RxView.detaches(binding.nearbyFilter.searchViewLayout.searchView))

View file

@ -11,13 +11,10 @@ import static fr.free.nrw.commons.nearby.CheckBoxTriStates.UNKNOWN;
import static fr.free.nrw.commons.wikidata.WikidataConstants.PLACE_OBJECT;
import android.location.Location;
import android.view.View;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import androidx.work.ExistingWorkPolicy;
import fr.free.nrw.commons.BaseMarker;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager.LocationChangeType;
@ -26,14 +23,10 @@ import fr.free.nrw.commons.nearby.CheckBoxTriStates;
import fr.free.nrw.commons.nearby.Label;
import fr.free.nrw.commons.nearby.MarkerPlaceGroup;
import fr.free.nrw.commons.nearby.NearbyController;
import fr.free.nrw.commons.nearby.NearbyFilterState;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.PlaceDao;
import fr.free.nrw.commons.nearby.contract.NearbyParentFragmentContract;
import fr.free.nrw.commons.upload.worker.WorkRequestHelper;
import fr.free.nrw.commons.utils.LocationUtils;
import fr.free.nrw.commons.wikidata.WikidataEditListener;
import io.reactivex.disposables.CompositeDisposable;
import java.lang.reflect.Proxy;
import java.util.List;
import timber.log.Timber;

View file

@ -1,197 +1,194 @@
package fr.free.nrw.commons.utils;
package fr.free.nrw.commons.utils
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.ProgressDialog;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.exifinterface.media.ExifInterface;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.SetWallpaperWorker;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import timber.log.Timber;
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.ProgressDialog
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Canvas
import android.graphics.Color
import android.net.Uri
import android.os.Build
import androidx.annotation.IntDef
import androidx.core.content.ContextCompat
import androidx.exifinterface.media.ExifInterface
import androidx.work.Data
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import fr.free.nrw.commons.R
import fr.free.nrw.commons.contributions.SetWallpaperWorker
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
/**
* Created by bluesir9 on 3/10/17.
* Created by blueSir9 on 3/10/17.
*/
public class ImageUtils {
object ImageUtils {
/**
* Set 0th bit as 1 for dark image ie. 0001
*/
public static final int IMAGE_DARK = 1 << 0; // 1
const val IMAGE_DARK = 1 shl 0 // 1
/**
* Set 1st bit as 1 for blurry image ie. 0010
*/
public static final int IMAGE_BLURRY = 1 << 1; // 2
const val IMAGE_BLURRY = 1 shl 1 // 2
/**
* Set 2nd bit as 1 for duplicate image ie. 0100
*/
public static final int IMAGE_DUPLICATE = 1 << 2; //4
const val IMAGE_DUPLICATE = 1 shl 2 // 4
/**
* Set 3rd bit as 1 for image with different geo location ie. 1000
*/
public static final int IMAGE_GEOLOCATION_DIFFERENT = 1 << 3; //8
const val IMAGE_GEOLOCATION_DIFFERENT = 1 shl 3 // 8
/**
* The parameter FILE_FBMD is returned from the class ReadFBMD if the uploaded image contains FBMD data else returns IMAGE_OK
* The parameter FILE_FBMD is returned from the class ReadFBMD if the uploaded image contains
* FBMD data else returns IMAGE_OK
* ie. 10000
*/
public static final int FILE_FBMD = 1 << 4;
const val FILE_FBMD = 1 shl 4 // 16
/**
* The parameter FILE_NO_EXIF is returned from the class EXIFReader if the uploaded image does not contains EXIF data else returns IMAGE_OK
* ie. 100000
*/
public static final int FILE_NO_EXIF = 1 << 5;
public static final int IMAGE_OK = 0;
public static final int IMAGE_KEEP = -1;
public static final int IMAGE_WAIT = -2;
public static final int EMPTY_CAPTION = -3;
public static final int FILE_NAME_EXISTS = 1 << 6;
static final int NO_CATEGORY_SELECTED = -5;
* The parameter FILE_NO_EXIF is returned from the class EXIFReader if the uploaded image does
* not contains EXIF data else returns IMAGE_OK
* ie. 100000
*/
const val FILE_NO_EXIF = 1 shl 5 // 32
private static ProgressDialog progressDialogWallpaper;
const val IMAGE_OK = 0
const val IMAGE_KEEP = -1
const val IMAGE_WAIT = -2
const val EMPTY_CAPTION = -3
const val FILE_NAME_EXISTS = 1 shl 6 // 64
const val NO_CATEGORY_SELECTED = -5
private static ProgressDialog progressDialogAvatar;
private var progressDialogWallpaper: ProgressDialog? = null
private var progressDialogAvatar: ProgressDialog? = null
@IntDef(
flag = true,
value = {
IMAGE_DARK,
IMAGE_BLURRY,
IMAGE_DUPLICATE,
IMAGE_OK,
IMAGE_KEEP,
IMAGE_WAIT,
EMPTY_CAPTION,
FILE_NAME_EXISTS,
NO_CATEGORY_SELECTED,
IMAGE_GEOLOCATION_DIFFERENT
}
flag = true,
value = [
IMAGE_DARK,
IMAGE_BLURRY,
IMAGE_DUPLICATE,
IMAGE_OK,
IMAGE_KEEP,
IMAGE_WAIT,
EMPTY_CAPTION,
FILE_NAME_EXISTS,
NO_CATEGORY_SELECTED,
IMAGE_GEOLOCATION_DIFFERENT
]
)
@Retention(RetentionPolicy.SOURCE)
public @interface Result {
}
@Retention
annotation class Result
/**
* @return IMAGE_OK if image is not too dark
* IMAGE_DARK if image is too dark
*/
static @Result int checkIfImageIsTooDark(String imagePath) {
long millis = System.currentTimeMillis();
try {
Bitmap bmp = new ExifInterface(imagePath).getThumbnailBitmap();
@JvmStatic
fun checkIfImageIsTooDark(imagePath: String): Int {
val millis = System.currentTimeMillis()
return try {
var bmp = ExifInterface(imagePath).thumbnailBitmap
if (bmp == null) {
bmp = BitmapFactory.decodeFile(imagePath);
bmp = BitmapFactory.decodeFile(imagePath)
}
if (checkIfImageIsDark(bmp)) {
return IMAGE_DARK;
IMAGE_DARK
} else {
IMAGE_OK
}
} catch (Exception e) {
Timber.d(e, "Error while checking image darkness.");
} catch (e: Exception) {
Timber.d(e, "Error while checking image darkness.")
IMAGE_OK
} finally {
Timber.d("Checking image darkness took " + (System.currentTimeMillis() - millis) + " ms.");
Timber.d("Checking image darkness took ${System.currentTimeMillis() - millis} ms.")
}
return IMAGE_OK;
}
/**
* @param geolocationOfFileString Geolocation of image. If geotag doesn't exists, then this will be an empty string
* @param geolocationOfFileString Geolocation of image. If geotag doesn't exists, then this will
* be an empty string
* @param latLng Location of wikidata item will be edited after upload
* @return false if image is neither dark nor blurry or if the input bitmapRegionDecoder provided is null
* true if geolocation of the image and wikidata item are different
* @return false if image is neither dark nor blurry or if the input bitmapRegionDecoder provide
* d is null true if geolocation of the image and wikidata item are different
*/
static boolean checkImageGeolocationIsDifferent(String geolocationOfFileString, LatLng latLng) {
Timber.d("Comparing geolocation of file with nearby place location");
@JvmStatic
fun checkImageGeolocationIsDifferent(geolocationOfFileString: String, latLng: LatLng?): Boolean {
Timber.d("Comparing geolocation of file with nearby place location")
if (latLng == null) { // Means that geolocation for this image is not given
return false; // Since we don't know geolocation of file, we choose letting upload
return false // Since we don't know geolocation of file, we choose letting upload
}
String[] geolocationOfFile = geolocationOfFileString.split("\\|");
Double distance = LengthUtils.computeDistanceBetween(
new LatLng(Double.parseDouble(geolocationOfFile[0]),Double.parseDouble(geolocationOfFile[1]),0)
, latLng);
val geolocationOfFile = geolocationOfFileString.split("|")
val distance = LengthUtils.computeDistanceBetween(
LatLng(geolocationOfFile[0].toDouble(), geolocationOfFile[1].toDouble(), 0.0F),
latLng
)
// Distance is more than 1 km, means that geolocation is wrong
return distance >= 1000;
return distance >= 1000
}
private static boolean checkIfImageIsDark(Bitmap bitmap) {
@JvmStatic
private fun checkIfImageIsDark(bitmap: Bitmap?): Boolean {
if (bitmap == null) {
Timber.e("Expected bitmap was null");
return true;
Timber.e("Expected bitmap was null")
return true
}
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
val bitmapWidth = bitmap.width
val bitmapHeight = bitmap.height
int allPixelsCount = bitmapWidth * bitmapHeight;
int numberOfBrightPixels = 0;
int numberOfMediumBrightnessPixels = 0;
double brightPixelThreshold = 0.025 * allPixelsCount;
double mediumBrightPixelThreshold = 0.3 * allPixelsCount;
val allPixelsCount = bitmapWidth * bitmapHeight
var numberOfBrightPixels = 0
var numberOfMediumBrightnessPixels = 0
val brightPixelThreshold = 0.025 * allPixelsCount
val mediumBrightPixelThreshold = 0.3 * allPixelsCount
for (int x = 0; x < bitmapWidth; x++) {
for (int y = 0; y < bitmapHeight; y++) {
int pixel = bitmap.getPixel(x, y);
int r = Color.red(pixel);
int g = Color.green(pixel);
int b = Color.blue(pixel);
for (x in 0 until bitmapWidth) {
for (y in 0 until bitmapHeight) {
val pixel = bitmap.getPixel(x, y)
val r = Color.red(pixel)
val g = Color.green(pixel)
val b = Color.blue(pixel)
int secondMax = r > g ? r : g;
double max = (secondMax > b ? secondMax : b) / 255.0;
val max = maxOf(r, g, b) / 255.0
val min = minOf(r, g, b) / 255.0
int secondMin = r < g ? r : g;
double min = (secondMin < b ? secondMin : b) / 255.0;
val luminance = ((max + min) / 2.0) * 100
double luminance = ((max + min) / 2.0) * 100;
int highBrightnessLuminance = 40;
int mediumBrightnessLuminance = 26;
val highBrightnessLuminance = 40
val mediumBrightnessLuminance = 26
if (luminance < highBrightnessLuminance) {
if (luminance > mediumBrightnessLuminance) {
numberOfMediumBrightnessPixels++;
numberOfMediumBrightnessPixels++
}
} else {
numberOfBrightPixels++;
numberOfBrightPixels++
}
if (numberOfBrightPixels >= brightPixelThreshold || numberOfMediumBrightnessPixels >= mediumBrightPixelThreshold) {
return false;
return false
}
}
}
return true;
return true
}
/**
@ -201,25 +198,25 @@ public class ImageUtils {
* @param context context
* @param imageUrl Url of the image
*/
public static void setWallpaperFromImageUrl(Context context, Uri imageUrl) {
enqueueSetWallpaperWork(context, imageUrl);
@JvmStatic
fun setWallpaperFromImageUrl(context: Context, imageUrl: Uri) {
enqueueSetWallpaperWork(context, imageUrl)
}
private static void createNotificationChannel(Context context) {
@JvmStatic
private fun createNotificationChannel(context: Context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
CharSequence name = "Wallpaper Setting";
String description = "Notifications for wallpaper setting progress";
int importance = NotificationManager.IMPORTANCE_DEFAULT;
NotificationChannel channel = new NotificationChannel("set_wallpaper_channel", name, importance);
channel.setDescription(description);
NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
val name = "Wallpaper Setting"
val description = "Notifications for wallpaper setting progress"
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel = NotificationChannel("set_wallpaper_channel", name, importance).apply {
this.description = description
}
val notificationManager = context.getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(channel)
}
}
/**
* Calls the set avatar api to set the image url as user's avatar
* @param context
@ -228,66 +225,96 @@ public class ImageUtils {
* @param okHttpJsonApiClient
* @param compositeDisposable
*/
public static void setAvatarFromImageUrl(Context context, String url, String username,
OkHttpJsonApiClient okHttpJsonApiClient, CompositeDisposable compositeDisposable) {
showSettingAvatarProgressBar(context);
@JvmStatic
fun setAvatarFromImageUrl(
context: Context,
url: String,
username: String,
okHttpJsonApiClient: OkHttpJsonApiClient,
compositeDisposable: CompositeDisposable
) {
showSettingAvatarProgressBar(context)
try {
compositeDisposable.add(okHttpJsonApiClient
.setAvatar(username, url)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
response -> {
if (response != null && response.getStatus().equals("200")) {
ViewUtil.showLongToast(context, context.getString(R.string.avatar_set_successfully));
if (progressDialogAvatar != null && progressDialogAvatar.isShowing()) {
progressDialogAvatar.dismiss();
compositeDisposable.add(
okHttpJsonApiClient
.setAvatar(username, url)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ response ->
if (response?.status == "200") {
ViewUtil.showLongToast(context, context.getString(R.string.avatar_set_successfully))
progressDialogAvatar?.dismiss()
}
},
{ t ->
Timber.e(t, "Setting Avatar Failed")
ViewUtil.showLongToast(context, context.getString(R.string.avatar_set_unsuccessfully))
progressDialogAvatar?.cancel()
}
},
t -> {
Timber.e(t, "Setting Avatar Failed");
ViewUtil.showLongToast(context, context.getString(R.string.avatar_set_unsuccessfully));
if (progressDialogAvatar != null) {
progressDialogAvatar.cancel();
}
}
));
)
)
} catch (e: Exception) {
Timber.d("$e success")
ViewUtil.showLongToast(context, context.getString(R.string.avatar_set_unsuccessfully))
progressDialogAvatar?.cancel()
}
catch (Exception e){
Timber.d(e+"success");
ViewUtil.showLongToast(context, context.getString(R.string.avatar_set_unsuccessfully));
if (progressDialogAvatar != null) {
progressDialogAvatar.cancel();
}
}
}
public static void enqueueSetWallpaperWork(Context context, Uri imageUrl) {
createNotificationChannel(context); // Ensure the notification channel is created
@JvmStatic
fun enqueueSetWallpaperWork(context: Context, imageUrl: Uri) {
createNotificationChannel(context) // Ensure the notification channel is created
Data inputData = new Data.Builder()
val inputData = Data.Builder()
.putString("imageUrl", imageUrl.toString())
.build();
.build()
OneTimeWorkRequest setWallpaperWork = new OneTimeWorkRequest.Builder(SetWallpaperWorker.class)
val setWallpaperWork = OneTimeWorkRequest.Builder(SetWallpaperWorker::class.java)
.setInputData(inputData)
.build();
.build()
WorkManager.getInstance(context).enqueue(setWallpaperWork);
WorkManager.getInstance(context).enqueue(setWallpaperWork)
}
private static void showSettingWallpaperProgressBar(Context context) {
progressDialogWallpaper = ProgressDialog.show(context, context.getString(R.string.setting_wallpaper_dialog_title),
context.getString(R.string.setting_wallpaper_dialog_message), true);
@JvmStatic
private fun showSettingWallpaperProgressBar(context: Context) {
progressDialogWallpaper = ProgressDialog.show(
context,
context.getString(R.string.setting_wallpaper_dialog_title),
context.getString(R.string.setting_wallpaper_dialog_message),
true
)
}
private static void showSettingAvatarProgressBar(Context context) {
progressDialogAvatar = ProgressDialog.show(context, context.getString(R.string.setting_avatar_dialog_title),
context.getString(R.string.setting_avatar_dialog_message), true);
@JvmStatic
private fun showSettingAvatarProgressBar(context: Context) {
progressDialogAvatar = ProgressDialog.show(
context,
context.getString(R.string.setting_avatar_dialog_title),
context.getString(R.string.setting_avatar_dialog_message),
true
)
}
/**
* Adds red border to bitmap with specified border size
* * @param bitmap
* * @param borderSize
* * @param context
* * @return
*/
@JvmStatic
fun addRedBorder(bitmap: Bitmap, borderSize: Int, context: Context): Bitmap {
val bmpWithBorder = Bitmap.createBitmap(
bitmap.width + borderSize * 2,
bitmap.height + borderSize * 2,
bitmap.config
)
val canvas = Canvas(bmpWithBorder)
canvas.drawColor(ContextCompat.getColor(context, R.color.deleteRed))
canvas.drawBitmap(bitmap, borderSize.toFloat(), borderSize.toFloat(), null)
return bmpWithBorder
}
/**
@ -295,57 +322,42 @@ public class ImageUtils {
* is 0001 means IMAGE_DARK
* if result is 1100 IMAGE_DUPLICATE and IMAGE_GEOLOCATION_DIFFERENT
*/
public static String getErrorMessageForResult(Context context, @Result int result) {
StringBuilder errorMessage = new StringBuilder();
if (result <= 0 ) {
Timber.d("No issues to warn user is found");
@JvmStatic
fun getErrorMessageForResult(context: Context, @Result result: Int): String {
val errorMessage = StringBuilder()
if (result <= 0) {
Timber.d("No issues to warn user are found")
} else {
Timber.d("Issues found to warn user");
Timber.d("Issues found to warn user")
errorMessage.append(context.getString(R.string.upload_problem_exist))
errorMessage.append(context.getResources().getString(R.string.upload_problem_exist));
if ((IMAGE_DARK & result) != 0 ) { // We are checking image dark bit to see if that bit is set or not
errorMessage.append("\n - ").append(context.getResources().getString(R.string.upload_problem_image_dark));
if (result and IMAGE_DARK != 0) {
errorMessage.append("\n - ")
.append(context.getString(R.string.upload_problem_image_dark))
}
if ((IMAGE_BLURRY & result) != 0 ) {
errorMessage.append("\n - ").append(context.getResources().getString(R.string.upload_problem_image_blurry));
if (result and IMAGE_BLURRY != 0) {
errorMessage.append("\n - ")
.append(context.getString(R.string.upload_problem_image_blurry))
}
if ((IMAGE_DUPLICATE & result) != 0 ) {
errorMessage.append("\n - ").append(context.getResources().getString(R.string.upload_problem_image_duplicate));
if (result and IMAGE_DUPLICATE != 0) {
errorMessage.append("\n - ").
append(context.getString(R.string.upload_problem_image_duplicate))
}
if ((IMAGE_GEOLOCATION_DIFFERENT & result) != 0 ) {
errorMessage.append("\n - ").append(context.getResources().getString(R.string.upload_problem_different_geolocation));
if (result and IMAGE_GEOLOCATION_DIFFERENT != 0) {
errorMessage.append("\n - ")
.append(context.getString(R.string.upload_problem_different_geolocation))
}
if ((FILE_FBMD & result) != 0) {
errorMessage.append("\n - ").append(context.getResources().getString(R.string.upload_problem_fbmd));
if (result and FILE_FBMD != 0) {
errorMessage.append("\n - ")
.append(context.getString(R.string.upload_problem_fbmd))
}
if ((FILE_NO_EXIF & result) != 0){
errorMessage.append("\n - ").append(context.getResources().getString(R.string.internet_downloaded));
if (result and FILE_NO_EXIF != 0) {
errorMessage.append("\n - ")
.append(context.getString(R.string.internet_downloaded))
}
errorMessage.append("\n\n").append(context.getResources().getString(R.string.upload_problem_do_you_continue));
errorMessage.append("\n\n")
.append(context.getString(R.string.upload_problem_do_you_continue))
}
return errorMessage.toString();
return errorMessage.toString()
}
/**
* Adds red border to a bitmap
* @param bitmap
* @param borderSize
* @param context
* @return
*/
public static Bitmap addRedBorder(Bitmap bitmap, int borderSize, Context context) {
Bitmap bmpWithBorder = Bitmap.createBitmap(bitmap.getWidth() + borderSize * 2, bitmap.getHeight() + borderSize * 2, bitmap.getConfig());
Canvas canvas = new Canvas(bmpWithBorder);
canvas.drawColor(ContextCompat.getColor(context, R.color.deleteRed));
canvas.drawBitmap(bitmap, borderSize, borderSize, null);
return bmpWithBorder;
}
}
}

View file

@ -1,30 +1,29 @@
package fr.free.nrw.commons.utils;
package fr.free.nrw.commons.utils
import fr.free.nrw.commons.location.LatLng;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import javax.inject.Inject;
import javax.inject.Singleton;
import fr.free.nrw.commons.location.LatLng
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
public class ImageUtilsWrapper {
class ImageUtilsWrapper @Inject constructor() {
@Inject
public ImageUtilsWrapper() {
}
public Single<Integer> checkIfImageIsTooDark(String bitmapPath) {
return Single.fromCallable(() -> ImageUtils.checkIfImageIsTooDark(bitmapPath))
.subscribeOn(Schedulers.computation());
}
public Single<Integer> checkImageGeolocationIsDifferent(String geolocationOfFileString,
LatLng latLng) {
return Single.fromCallable(
() -> ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng))
fun checkIfImageIsTooDark(bitmapPath: String): Single<Int> {
return Single.fromCallable { ImageUtils.checkIfImageIsTooDark(bitmapPath) }
.subscribeOn(Schedulers.computation())
.map(isDifferent -> isDifferent ? ImageUtils.IMAGE_GEOLOCATION_DIFFERENT
: ImageUtils.IMAGE_OK);
}
}
fun checkImageGeolocationIsDifferent(
geolocationOfFileString: String,
latLng: LatLng
): Single<Int> {
return Single.fromCallable {
ImageUtils.checkImageGeolocationIsDifferent(geolocationOfFileString, latLng)
}
.subscribeOn(Schedulers.computation())
.map { isDifferent ->
if (isDifferent) ImageUtils.IMAGE_GEOLOCATION_DIFFERENT else ImageUtils.IMAGE_OK
}
}
}

View file

@ -1,39 +1,40 @@
package fr.free.nrw.commons.utils;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import java.util.Locale;
package fr.free.nrw.commons.utils
import android.content.Context
import android.content.res.Configuration
import android.content.res.Resources
import java.util.Locale
/**
* Utilities class for miscellaneous strings
*/
public class LangCodeUtils {
object LangCodeUtils {
/**
* Replaces the deprecated ISO-639 language codes used by Android with the updated ISO-639-1.
* @param code Language code you want to update.
* @return Updated language code. If not in the "deprecated list" returns the same code.
*/
public static String fixLanguageCode(String code) {
if (code.equalsIgnoreCase("iw")) {
return "he";
} else if (code.equalsIgnoreCase("in")) {
return "id";
} else if (code.equalsIgnoreCase("ji")) {
return "yi";
} else {
return code;
@JvmStatic
fun fixLanguageCode(code: String): String {
return when (code.lowercase()) {
"iw" -> "he"
"in" -> "id"
"ji" -> "yi"
else -> code
}
}
/**
* Returns configuration for locale of
* our choice regardless of user's device settings
*/
public static Resources getLocalizedResources(Context context, Locale desiredLocale) {
Configuration conf = context.getResources().getConfiguration();
conf = new Configuration(conf);
conf.setLocale(desiredLocale);
Context localizedContext = context.createConfigurationContext(conf);
return localizedContext.getResources();
@JvmStatic
fun getLocalizedResources(context: Context, desiredLocale: Locale): Resources {
val conf = Configuration(context.resources.configuration).apply {
setLocale(desiredLocale)
}
val localizedContext = context.createConfigurationContext(conf)
return localizedContext.resources
}
}

View file

@ -1,38 +1,47 @@
package fr.free.nrw.commons.utils;
package fr.free.nrw.commons.utils
import android.app.Activity;
import android.content.Context;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.app.Activity
import android.content.Context
import android.util.DisplayMetrics
import android.view.View
import android.view.ViewTreeObserver
public class LayoutUtils {
/**
* Utility class for layout-related operations.
*/
object LayoutUtils {
/**
* Can be used for keeping aspect radios suggested by material guidelines. See:
* Can be used for keeping aspect ratios suggested by material guidelines. See:
* https://material.io/design/layout/spacing-methods.html#containers-aspect-ratios
* In some cases we don't know exact width, for such cases this method measures
* In some cases, we don't know the exact width, for such cases this method measures
* width and sets height by multiplying the width with height.
* @param rate Aspect ratios, ie 1 for 1:1. (width * rate = height)
* @param view view to change height
* @param rate Aspect ratios, i.e., 1 for 1:1 (width * rate = height)
* @param view View to change height
*/
public static void setLayoutHeightAllignedToWidth(double rate, View view) {
ViewTreeObserver vto = view.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
layoutParams.height = (int) (view.getWidth() * rate);
view.setLayoutParams(layoutParams);
@JvmStatic
fun setLayoutHeightAlignedToWidth(rate: Double, view: View) {
val vto = view.viewTreeObserver
vto.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
val layoutParams = view.layoutParams
layoutParams.height = (view.width * rate).toInt()
view.layoutParams = layoutParams
}
});
})
}
public static double getScreenWidth(Context context, double rate) {
DisplayMetrics displayMetrics = new DisplayMetrics();
((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
return displayMetrics.widthPixels * rate;
/**
* Calculates and returns the screen width multiplied by the provided rate.
* @param context Context used to access display metrics.
* @param rate Multiplier for screen width.
* @return Calculated screen width multiplied by the rate.
*/
@JvmStatic
fun getScreenWidth(context: Context, rate: Double): Double {
val displayMetrics = DisplayMetrics()
(context as Activity).windowManager.defaultDisplay.getMetrics(displayMetrics)
return displayMetrics.widthPixels * rate
}
}

View file

@ -1,12 +1,15 @@
package fr.free.nrw.commons.utils;
package fr.free.nrw.commons.utils
import androidx.annotation.NonNull;
import java.text.NumberFormat
import fr.free.nrw.commons.location.LatLng
import kotlin.math.asin
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.roundToInt
import kotlin.math.sin
import kotlin.math.sqrt
import java.text.NumberFormat;
import fr.free.nrw.commons.location.LatLng;
public class LengthUtils {
object LengthUtils {
/**
* Returns a formatted distance string between two points.
*
@ -14,13 +17,14 @@ public class LengthUtils {
* @param point2 LatLng type point2
* @return string distance
*/
public static String formatDistanceBetween(LatLng point1, LatLng point2) {
@JvmStatic
fun formatDistanceBetween(point1: LatLng?, point2: LatLng?): String? {
if (point1 == null || point2 == null) {
return null;
return null
}
int distance = (int) Math.round(computeDistanceBetween(point1, point2));
return formatDistance(distance);
val distance = computeDistanceBetween(point1, point2).roundToInt()
return formatDistance(distance)
}
/**
@ -32,21 +36,21 @@ public class LengthUtils {
* @return A string representing the distance
* @throws IllegalArgumentException If distance is negative
*/
public static String formatDistance(int distance) {
@JvmStatic
fun formatDistance(distance: Int): String {
if (distance < 0) {
throw new IllegalArgumentException("Distance must be non-negative");
throw IllegalArgumentException("Distance must be non-negative")
}
NumberFormat numberFormat = NumberFormat.getNumberInstance();
val numberFormat = NumberFormat.getNumberInstance()
// Adjust to km if distance is over 1000m (1km)
if (distance >= 1000) {
numberFormat.setMaximumFractionDigits(1);
return numberFormat.format(distance / 1000.0) + "km";
return if (distance >= 1000) {
numberFormat.maximumFractionDigits = 1
"${numberFormat.format(distance / 1000.0)}km"
} else {
"${numberFormat.format(distance)}m"
}
// Otherwise just return in meters
return numberFormat.format(distance) + "m";
}
/**
@ -57,8 +61,9 @@ public class LengthUtils {
* @return distance between the points in meters
* @throws NullPointerException if one or both the points are null
*/
public static double computeDistanceBetween(@NonNull LatLng point1, @NonNull LatLng point2) {
return computeAngleBetween(point1, point2) * 6371009.0D; // Earth's radius in meter
@JvmStatic
fun computeDistanceBetween(point1: LatLng, point2: LatLng): Double {
return computeAngleBetween(point1, point2) * 6371009.0 // Earth's radius in meters
}
/**
@ -66,16 +71,17 @@ public class LengthUtils {
*
* @param point1 one of the two end points
* @param point2 one of the two end points
* @return Angle in radius
* @return Angle in radians
* @throws NullPointerException if one or both the points are null
*/
private static double computeAngleBetween(@NonNull LatLng point1, @NonNull LatLng point2) {
@JvmStatic
private fun computeAngleBetween(point1: LatLng, point2: LatLng): Double {
return distanceRadians(
Math.toRadians(point1.getLatitude()),
Math.toRadians(point1.getLongitude()),
Math.toRadians(point2.getLatitude()),
Math.toRadians(point2.getLongitude())
);
Math.toRadians(point1.latitude),
Math.toRadians(point1.longitude),
Math.toRadians(point2.latitude),
Math.toRadians(point2.longitude)
)
}
/**
@ -87,8 +93,9 @@ public class LengthUtils {
* @param lng2 Longitude of point B
* @return Arc length between the points
*/
private static double distanceRadians(double lat1, double lng1, double lat2, double lng2) {
return arcHav(havDistance(lat1, lat2, lng1 - lng2));
@JvmStatic
private fun distanceRadians(lat1: Double, lng1: Double, lat2: Double, lng2: Double): Double {
return arcHav(havDistance(lat1, lat2, lng1 - lng2))
}
/**
@ -97,8 +104,9 @@ public class LengthUtils {
* @param x Angle in radian
* @return Inverse of haversine
*/
private static double arcHav(double x) {
return 2.0D * Math.asin(Math.sqrt(x));
@JvmStatic
private fun arcHav(x: Double): Double {
return 2.0 * asin(sqrt(x))
}
/**
@ -109,8 +117,9 @@ public class LengthUtils {
* @param longitude Longitude on which they lie
* @return Arc length between points
*/
private static double havDistance(double lat1, double lat2, double longitude) {
return hav(lat1 - lat2) + hav(longitude) * Math.cos(lat1) * Math.cos(lat2);
@JvmStatic
private fun havDistance(lat1: Double, lat2: Double, longitude: Double): Double {
return hav(lat1 - lat2) + hav(longitude) * cos(lat1) * cos(lat2)
}
/**
@ -119,9 +128,10 @@ public class LengthUtils {
* @param x Angle in radians
* @return Haversine of x
*/
private static double hav(double x) {
double sinHalf = Math.sin(x * 0.5D);
return sinHalf * sinHalf;
@JvmStatic
private fun hav(x: Double): Double {
val sinHalf = sin(x * 0.5)
return sinHalf * sinHalf
}
/**
@ -133,13 +143,14 @@ public class LengthUtils {
* @return Bearing between the two end points in degrees
* @throws NullPointerException if one or both the points are null
*/
public static double computeBearing(@NonNull LatLng point1, @NonNull LatLng point2) {
double diffLongitute = Math.toRadians(point2.getLongitude() - point1.getLongitude());
double lat1 = Math.toRadians(point1.getLatitude());
double lat2 = Math.toRadians(point2.getLatitude());
double y = Math.sin(diffLongitute) * Math.cos(lat2);
double x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(diffLongitute);
double bearing = Math.atan2(y, x);
return (Math.toDegrees(bearing) + 360) % 360;
@JvmStatic
fun computeBearing(point1: LatLng, point2: LatLng): Double {
val diffLongitude = Math.toRadians(point2.longitude - point1.longitude)
val lat1 = Math.toRadians(point1.latitude)
val lat2 = Math.toRadians(point2.latitude)
val y = sin(diffLongitude) * cos(lat2)
val x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(diffLongitude)
val bearing = atan2(y, x)
return (Math.toDegrees(bearing) + 360) % 360
}
}
}

View file

@ -1,58 +1,63 @@
package fr.free.nrw.commons.utils;
package fr.free.nrw.commons.utils
import fr.free.nrw.commons.location.LatLng;
import timber.log.Timber;
import fr.free.nrw.commons.location.LatLng
import timber.log.Timber
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
public class LocationUtils {
public static final double RADIUS_OF_EARTH_KM = 6371.0; // Earth's radius in kilometers
object LocationUtils {
const val RADIUS_OF_EARTH_KM = 6371.0 // Earth's radius in kilometers
public static LatLng deriveUpdatedLocationFromSearchQuery(String customQuery) {
LatLng latLng = null;
final int indexOfPrefix = customQuery.indexOf("Point(");
@JvmStatic
fun deriveUpdatedLocationFromSearchQuery(customQuery: String): LatLng? {
var latLng: LatLng? = null
val indexOfPrefix = customQuery.indexOf("Point(")
if (indexOfPrefix == -1) {
Timber.e("Invalid prefix index - Seems like user has entered an invalid query");
return latLng;
Timber.e("Invalid prefix index - Seems like user has entered an invalid query")
return latLng
}
final int indexOfSuffix = customQuery.indexOf(")\"", indexOfPrefix);
val indexOfSuffix = customQuery.indexOf(")\"", indexOfPrefix)
if (indexOfSuffix == -1) {
Timber.e("Invalid suffix index - Seems like user has entered an invalid query");
return latLng;
Timber.e("Invalid suffix index - Seems like user has entered an invalid query")
return latLng
}
String latLngString = customQuery.substring(indexOfPrefix+"Point(".length(), indexOfSuffix);
val latLngString = customQuery.substring(indexOfPrefix + "Point(".length, indexOfSuffix)
if (latLngString.isEmpty()) {
return null;
return null
}
String latLngArray[] = latLngString.split(" ");
if (latLngArray.length != 2) {
return null;
val latLngArray = latLngString.split(" ")
if (latLngArray.size != 2) {
return null
}
try {
latLng = new LatLng(Double.parseDouble(latLngArray[1].trim()),
Double.parseDouble(latLngArray[0].trim()), 1f);
}catch (Exception e){
Timber.e("Error while parsing user entered lat long: %s", e);
latLng = LatLng(latLngArray[1].trim().toDouble(),
latLngArray[0].trim().toDouble(), 1f)
} catch (e: Exception) {
Timber.e("Error while parsing user entered lat long: %s", e)
}
return latLng;
return latLng
}
public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
double lat1Rad = Math.toRadians(lat1);
double lon1Rad = Math.toRadians(lon1);
double lat2Rad = Math.toRadians(lat2);
double lon2Rad = Math.toRadians(lon2);
@JvmStatic
fun calculateDistance(lat1: Double, lon1: Double, lat2: Double, lon2: Double): Double {
val lat1Rad = Math.toRadians(lat1)
val lon1Rad = Math.toRadians(lon1)
val lat2Rad = Math.toRadians(lat2)
val lon2Rad = Math.toRadians(lon2)
// Haversine formula
double dlon = lon2Rad - lon1Rad;
double dlat = lat2Rad - lat1Rad;
double a = Math.pow(Math.sin(dlat / 2), 2) + Math.cos(lat1Rad) * Math.cos(lat2Rad) * Math.pow(Math.sin(dlon / 2), 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
val dlon = lon2Rad - lon1Rad
val dlat = lat2Rad - lat1Rad
val a = Math.pow(
sin(dlat / 2), 2.0) + cos(lat1Rad) * cos(lat2Rad) * Math.pow(sin(dlon / 2), 2.0
)
val c = 2 * atan2(sqrt(a), sqrt(1 - a))
double distance = RADIUS_OF_EARTH_KM * c;
return distance;
return RADIUS_OF_EARTH_KM * c
}
}

View file

@ -1,33 +1,39 @@
package fr.free.nrw.commons.utils;
package fr.free.nrw.commons.utils
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.location.LocationServiceManager;
import fr.free.nrw.commons.location.LocationUpdateListener;
import timber.log.Timber;
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.location.LocationServiceManager
import fr.free.nrw.commons.location.LocationUpdateListener
import timber.log.Timber
public class MapUtils {
public static final float ZOOM_LEVEL = 14f;
public static final double CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.005;
public static final double CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.004;
public static final String NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
public static final float ZOOM_OUT = 0f;
object MapUtils {
const val ZOOM_LEVEL = 14f
const val CAMERA_TARGET_SHIFT_FACTOR_PORTRAIT = 0.005
const val CAMERA_TARGET_SHIFT_FACTOR_LANDSCAPE = 0.004
const val NETWORK_INTENT_ACTION = "android.net.conn.CONNECTIVITY_CHANGE"
const val ZOOM_OUT = 0f
public static final LatLng defaultLatLng = new fr.free.nrw.commons.location.LatLng(51.50550,-0.07520,1f);
@JvmStatic
val defaultLatLng = LatLng(51.50550, -0.07520, 1f)
public static void registerUnregisterLocationListener(final boolean removeLocationListener, LocationServiceManager locationManager, LocationUpdateListener locationUpdateListener) {
@JvmStatic
fun registerUnregisterLocationListener(
removeLocationListener: Boolean,
locationManager: LocationServiceManager,
locationUpdateListener: LocationUpdateListener
) {
try {
if (removeLocationListener) {
locationManager.unregisterLocationManager();
locationManager.removeLocationListener(locationUpdateListener);
Timber.d("Location service manager unregistered and removed");
locationManager.unregisterLocationManager()
locationManager.removeLocationListener(locationUpdateListener)
Timber.d("Location service manager unregistered and removed")
} else {
locationManager.addLocationListener(locationUpdateListener);
locationManager.registerLocationManager();
Timber.d("Location service manager added and registered");
locationManager.addLocationListener(locationUpdateListener)
locationManager.registerLocationManager()
Timber.d("Location service manager added and registered")
}
}catch (final Exception e){
Timber.e(e);
//Broadcasts are tricky, should be catchedonR
} catch (e: Exception) {
Timber.e(e)
// Broadcasts are tricky, should be caught on onR
}
}
}