Migrated helper modules to kotlin (#6007)

* Rename .java to .kt

* Migrated delete and description module to kotlin (WIP)

* Fix: Unit tests

* Fix: Unit tests

* Rename .java to .kt

* Migrated data, db, and converter module to kotlin

* Fix: Unit tests

* Fix: Unit tests

---------

Co-authored-by: Nicolas Raoul <nicolas.raoul@gmail.com>
This commit is contained in:
Saifuddin Adenwala 2024-12-11 04:13:36 +05:30 committed by GitHub
parent 73311970c5
commit 3030a6fca7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 1168 additions and 973 deletions

View file

@ -372,16 +372,18 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
*/ */
private fun removeLocationFromImage() { private fun removeLocationFromImage() {
media?.let { media?.let {
compositeDisposable.add( coordinateEditHelper.makeCoordinatesEdit(
coordinateEditHelper.makeCoordinatesEdit( applicationContext, it, "0.0", "0.0", "0.0f"
applicationContext, it, "0.0", "0.0", "0.0f"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe { _ ->
Timber.d("Coordinates removed from the image")
}
) )
?.subscribeOn(Schedulers.io())
?.observeOn(AndroidSchedulers.mainThread())
?.subscribe { _ ->
Timber.d("Coordinates removed from the image")
}?.let { it1 ->
compositeDisposable.add(
it1
)
}
} }
setResult(RESULT_OK, Intent()) setResult(RESULT_OK, Intent())
finish() finish()
@ -473,19 +475,21 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback {
fun updateCoordinates(latitude: String, longitude: String, accuracy: String) { fun updateCoordinates(latitude: String, longitude: String, accuracy: String) {
media?.let { media?.let {
try { try {
compositeDisposable.add( coordinateEditHelper.makeCoordinatesEdit(
coordinateEditHelper.makeCoordinatesEdit( applicationContext,
applicationContext, it,
it, latitude,
latitude, longitude,
longitude, accuracy
accuracy )?.subscribeOn(Schedulers.io())
).subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread()) ?.subscribe { _ ->
.subscribe { _ -> Timber.d("Coordinates updated")
Timber.d("Coordinates updated") }?.let { it1 ->
} compositeDisposable.add(
) it1
)
}
} catch (e: Exception) { } catch (e: Exception) {
if (e.localizedMessage == CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE) { if (e.localizedMessage == CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE) {
val username = sessionManager.userName val username = sessionManager.userName

View file

@ -1,187 +0,0 @@
package fr.free.nrw.commons.coordinates;
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_EDIT_COORDINATES;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.actions.PageEditClient;
import fr.free.nrw.commons.notification.NotificationHelper;
import fr.free.nrw.commons.utils.ViewUtilWrapper;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.schedulers.Schedulers;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.commons.lang3.StringUtils;
import timber.log.Timber;
/**
* Helper class for edit and update given coordinates and showing notification about new coordinates
* upgradation
*/
public class CoordinateEditHelper {
/**
* notificationHelper: helps creating notification
*/
private final NotificationHelper notificationHelper;
/**
* * pageEditClient: methods provided by this member posts the edited coordinates
* to the Media wiki api
*/
public final PageEditClient pageEditClient;
/**
* viewUtil: helps to show Toast
*/
private final ViewUtilWrapper viewUtil;
@Inject
public CoordinateEditHelper(final NotificationHelper notificationHelper,
@Named("commons-page-edit") final PageEditClient pageEditClient,
final ViewUtilWrapper viewUtil) {
this.notificationHelper = notificationHelper;
this.pageEditClient = pageEditClient;
this.viewUtil = viewUtil;
}
/**
* Public interface to edit coordinates
* @param context to be added
* @param media to be added
* @param Accuracy to be added
* @return Single<Boolean>
*/
public Single<Boolean> makeCoordinatesEdit(final Context context, final Media media,
final String Latitude, final String Longitude, final String Accuracy) {
viewUtil.showShortToast(context,
context.getString(R.string.coordinates_edit_helper_make_edit_toast));
return addCoordinates(media, Latitude, Longitude, Accuracy)
.flatMapSingle(result -> Single.just(showCoordinatesEditNotification(context, media,
Latitude, Longitude, Accuracy, result)))
.firstOrError();
}
/**
* Replaces new coordinates
* @param media to be added
* @param Latitude to be added
* @param Longitude to be added
* @param Accuracy to be added
* @return Observable<Boolean>
*/
private Observable<Boolean> addCoordinates(final Media media, final String Latitude,
final String Longitude, final String Accuracy) {
Timber.d("thread is coordinates adding %s", Thread.currentThread().getName());
final String summary = "Adding Coordinates";
final StringBuilder buffer = new StringBuilder();
final String wikiText = pageEditClient.getCurrentWikiText(media.getFilename())
.subscribeOn(Schedulers.io())
.blockingGet();
if (Latitude != null) {
buffer.append("\n{{Location|").append(Latitude).append("|").append(Longitude)
.append("|").append(Accuracy).append("}}");
}
final String editedLocation = buffer.toString();
final String appendText = getFormattedWikiText(wikiText, editedLocation);
return pageEditClient.edit(Objects.requireNonNull(media.getFilename())
, appendText, summary);
}
/**
* Helps to get formatted wikitext with upgraded location
* @param wikiText current wikitext
* @param editedLocation new location
* @return String
*/
private String getFormattedWikiText(final String wikiText, final String editedLocation){
if (wikiText.contains("filedesc") && wikiText.contains("Location")) {
final String fromLocationToEnd = wikiText.substring(wikiText.indexOf("{{Location"));
final String firstHalf = wikiText.substring(0, wikiText.indexOf("{{Location"));
final String lastHalf = fromLocationToEnd.substring(
fromLocationToEnd.indexOf("}}") + 2);
final int startOfSecondSection = StringUtils.ordinalIndexOf(wikiText,
"==", 3);
final StringBuilder buffer = new StringBuilder();
if (wikiText.charAt(wikiText.indexOf("{{Location")-1) == '\n') {
buffer.append(editedLocation.substring(1));
} else {
buffer.append(editedLocation);
}
if (startOfSecondSection != -1 && wikiText.charAt(startOfSecondSection-1)!= '\n') {
buffer.append("\n");
}
return firstHalf + buffer + lastHalf;
}
if (wikiText.contains("filedesc") && !wikiText.contains("Location")) {
final int startOfSecondSection = StringUtils.ordinalIndexOf(wikiText,
"==", 3);
if (startOfSecondSection != -1) {
final String firstHalf = wikiText.substring(0, startOfSecondSection);
final String lastHalf = wikiText.substring(startOfSecondSection);
final String buffer = editedLocation.substring(1)
+ "\n";
return firstHalf + buffer + lastHalf;
}
return wikiText + editedLocation;
}
return "== {{int:filedesc}} ==" + editedLocation + wikiText;
}
/**
* Update coordinates and shows notification about coordinates update
* @param context to be added
* @param media to be added
* @param latitude to be added
* @param longitude to be added
* @param Accuracy to be added
* @param result to be added
* @return boolean
*/
private boolean showCoordinatesEditNotification(final Context context, final Media media,
final String latitude, final String longitude, final String Accuracy,
final boolean result) {
final String message;
String title = context.getString(R.string.coordinates_edit_helper_show_edit_title);
if (result) {
media.setCoordinates(
new fr.free.nrw.commons.location.LatLng(Double.parseDouble(latitude),
Double.parseDouble(longitude),
Float.parseFloat(Accuracy)));
title += ": " + context
.getString(R.string.coordinates_edit_helper_show_edit_title_success);
final StringBuilder coordinatesInMessage = new StringBuilder();
final String mediaCoordinate = String.valueOf(media.getCoordinates());
coordinatesInMessage.append(mediaCoordinate);
message = context.getString(R.string.coordinates_edit_helper_show_edit_message,
coordinatesInMessage.toString());
} else {
title += ": " + context.getString(R.string.coordinates_edit_helper_show_edit_title);
message = context.getString(R.string.coordinates_edit_helper_edit_message_else) ;
}
final String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename();
final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile));
notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_COORDINATES,
browserIntent);
return result;
}
}

View file

@ -0,0 +1,189 @@
package fr.free.nrw.commons.coordinates
import android.content.Context
import android.content.Intent
import android.net.Uri
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.actions.PageEditClient
import fr.free.nrw.commons.notification.NotificationHelper
import fr.free.nrw.commons.notification.NotificationHelper.Companion.NOTIFICATION_EDIT_COORDINATES
import fr.free.nrw.commons.utils.ViewUtilWrapper
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import java.util.Objects
import javax.inject.Inject
import javax.inject.Named
import org.apache.commons.lang3.StringUtils
import timber.log.Timber
/**
* Helper class for edit and update given coordinates and showing notification about new coordinates
* upgradation
*/
class CoordinateEditHelper @Inject constructor(
private val notificationHelper: NotificationHelper,
@Named("commons-page-edit") private val pageEditClient: PageEditClient,
private val viewUtil: ViewUtilWrapper
) {
/**
* Public interface to edit coordinates
* @param context to be added
* @param media to be added
* @param latitude to be added
* @param longitude to be added
* @param accuracy to be added
* @return Single<Boolean>
*/
fun makeCoordinatesEdit(
context: Context,
media: Media,
latitude: String,
longitude: String,
accuracy: String
): Single<Boolean>? {
viewUtil.showShortToast(
context,
context.getString(R.string.coordinates_edit_helper_make_edit_toast)
)
return addCoordinates(media, latitude, longitude, accuracy)
?.flatMapSingle { result ->
Single.just(showCoordinatesEditNotification(context, media, latitude, longitude, accuracy, result))
}
?.firstOrError()
}
/**
* Replaces new coordinates
* @param media to be added
* @param Latitude to be added
* @param Longitude to be added
* @param Accuracy to be added
* @return Observable<Boolean>
*/
private fun addCoordinates(
media: Media,
Latitude: String,
Longitude: String,
Accuracy: String
): Observable<Boolean>? {
Timber.d("thread is coordinates adding %s", Thread.currentThread().getName())
val summary = "Adding Coordinates"
val buffer = StringBuilder()
val wikiText = media.filename?.let {
pageEditClient.getCurrentWikiText(it)
.subscribeOn(Schedulers.io())
.blockingGet()
}
if (Latitude != null) {
buffer.append("\n{{Location|").append(Latitude).append("|").append(Longitude)
.append("|").append(Accuracy).append("}}")
}
val editedLocation = buffer.toString()
val appendText = wikiText?.let { getFormattedWikiText(it, editedLocation) }
return Objects.requireNonNull(media.filename)
?.let { pageEditClient.edit(it, appendText!!, summary) }
}
/**
* Helps to get formatted wikitext with upgraded location
* @param wikiText current wikitext
* @param editedLocation new location
* @return String
*/
private fun getFormattedWikiText(wikiText: String, editedLocation: String): String {
if (wikiText.contains("filedesc") && wikiText.contains("Location")) {
val fromLocationToEnd = wikiText.substring(wikiText.indexOf("{{Location"))
val firstHalf = wikiText.substring(0, wikiText.indexOf("{{Location"))
val lastHalf = fromLocationToEnd.substring(fromLocationToEnd.indexOf("}}") + 2)
val startOfSecondSection = StringUtils.ordinalIndexOf(wikiText, "==", 3)
val buffer = StringBuilder()
if (wikiText[wikiText.indexOf("{{Location") - 1] == '\n') {
buffer.append(editedLocation.substring(1))
} else {
buffer.append(editedLocation)
}
if (startOfSecondSection != -1 && wikiText[startOfSecondSection - 1] != '\n') {
buffer.append("\n")
}
return firstHalf + buffer + lastHalf
}
if (wikiText.contains("filedesc") && !wikiText.contains("Location")) {
val startOfSecondSection = StringUtils.ordinalIndexOf(wikiText, "==", 3)
if (startOfSecondSection != -1) {
val firstHalf = wikiText.substring(0, startOfSecondSection)
val lastHalf = wikiText.substring(startOfSecondSection)
val buffer = editedLocation.substring(1) + "\n"
return firstHalf + buffer + lastHalf
}
return wikiText + editedLocation
}
return "== {{int:filedesc}} ==$editedLocation$wikiText"
}
/**
* Update coordinates and shows notification about coordinates update
* @param context to be added
* @param media to be added
* @param latitude to be added
* @param longitude to be added
* @param Accuracy to be added
* @param result to be added
* @return boolean
*/
private fun showCoordinatesEditNotification(
context: Context,
media: Media,
latitude: String,
longitude: String,
Accuracy: String,
result: Boolean
): Boolean {
val message: String
var title = context.getString(R.string.coordinates_edit_helper_show_edit_title)
if (result) {
media.coordinates = fr.free.nrw.commons.location.LatLng(
latitude.toDouble(),
longitude.toDouble(),
Accuracy.toFloat()
)
title += ": " + context.getString(R.string.coordinates_edit_helper_show_edit_title_success)
val coordinatesInMessage = StringBuilder()
val mediaCoordinate = media.coordinates.toString()
coordinatesInMessage.append(mediaCoordinate)
message = context.getString(
R.string.coordinates_edit_helper_show_edit_message,
coordinatesInMessage.toString()
)
} else {
title += ": " + context.getString(R.string.coordinates_edit_helper_show_edit_title)
message = context.getString(R.string.coordinates_edit_helper_edit_message_else)
}
val urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.filename
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile))
notificationHelper.showNotification(
context,
title,
message,
NOTIFICATION_EDIT_COORDINATES,
browserIntent
)
return result
}
}

View file

@ -1,63 +0,0 @@
package fr.free.nrw.commons.data;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao;
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
import fr.free.nrw.commons.category.CategoryDao;
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
public class DBOpenHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "commons.db";
private static final int DATABASE_VERSION = 20;
public static final String CONTRIBUTIONS_TABLE = "contributions";
private final String DROP_TABLE_STATEMENT="DROP TABLE IF EXISTS %s";
/**
* Do not use directly - @Inject an instance where it's needed and let
* dependency injection take care of managing this as a singleton.
*/
public DBOpenHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
CategoryDao.Table.onCreate(sqLiteDatabase);
BookmarkPicturesDao.Table.onCreate(sqLiteDatabase);
BookmarkLocationsDao.Table.onCreate(sqLiteDatabase);
BookmarkItemsDao.Table.onCreate(sqLiteDatabase);
RecentSearchesDao.Table.onCreate(sqLiteDatabase);
RecentLanguagesDao.Table.onCreate(sqLiteDatabase);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int from, int to) {
CategoryDao.Table.onUpdate(sqLiteDatabase, from, to);
BookmarkPicturesDao.Table.onUpdate(sqLiteDatabase, from, to);
BookmarkLocationsDao.Table.onUpdate(sqLiteDatabase, from, to);
BookmarkItemsDao.Table.onUpdate(sqLiteDatabase, from, to);
RecentSearchesDao.Table.onUpdate(sqLiteDatabase, from, to);
RecentLanguagesDao.Table.onUpdate(sqLiteDatabase, from, to);
deleteTable(sqLiteDatabase,CONTRIBUTIONS_TABLE);
}
/**
* Delete table in the given db
* @param db
* @param tableName
*/
public void deleteTable(SQLiteDatabase db, String tableName) {
try {
db.execSQL(String.format(DROP_TABLE_STATEMENT, tableName));
onCreate(db);
} catch (SQLiteException e) {
e.printStackTrace();
}
}
}

View file

@ -0,0 +1,62 @@
package fr.free.nrw.commons.data
import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteException
import android.database.sqlite.SQLiteOpenHelper
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao
import fr.free.nrw.commons.category.CategoryDao
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
class DBOpenHelper(
context: Context
): SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
companion object {
private const val DATABASE_NAME = "commons.db"
private const val DATABASE_VERSION = 20
const val CONTRIBUTIONS_TABLE = "contributions"
private const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS %s"
}
/**
* Do not use directly - @Inject an instance where it's needed and let
* dependency injection take care of managing this as a singleton.
*/
override fun onCreate(db: SQLiteDatabase) {
CategoryDao.Table.onCreate(db)
BookmarkPicturesDao.Table.onCreate(db)
BookmarkLocationsDao.Table.onCreate(db)
BookmarkItemsDao.Table.onCreate(db)
RecentSearchesDao.Table.onCreate(db)
RecentLanguagesDao.Table.onCreate(db)
}
override fun onUpgrade(db: SQLiteDatabase, from: Int, to: Int) {
CategoryDao.Table.onUpdate(db, from, to)
BookmarkPicturesDao.Table.onUpdate(db, from, to)
BookmarkLocationsDao.Table.onUpdate(db, from, to)
BookmarkItemsDao.Table.onUpdate(db, from, to)
RecentSearchesDao.Table.onUpdate(db, from, to)
RecentLanguagesDao.Table.onUpdate(db, from, to)
deleteTable(db, CONTRIBUTIONS_TABLE)
}
/**
* Delete table in the given db
* @param db
* @param tableName
*/
fun deleteTable(db: SQLiteDatabase, tableName: String) {
try {
db.execSQL(String.format(DROP_TABLE_STATEMENT, tableName))
onCreate(db)
} catch (e: SQLiteException) {
e.printStackTrace()
}
}
}

View file

@ -1,165 +0,0 @@
package fr.free.nrw.commons.db;
import android.net.Uri;
import androidx.room.TypeConverter;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.contributions.ChunkInfo;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Sitelinks;
import fr.free.nrw.commons.upload.WikidataPlace;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* This class supplies converters to write/read types to/from the database.
*/
public class Converters {
public static Gson getGson() {
return ApplicationlessInjection
.getInstance(CommonsApplication.getInstance())
.getCommonsApplicationComponent()
.gson();
}
/**
* convert DepictedItem object to string
* input Example -> DepictedItem depictedItem=new DepictedItem ()
* output Example -> string
*/
@TypeConverter
public static String depictsItemToString(DepictedItem objects) {
return writeObjectToString(objects);
}
/**
* convert string to DepictedItem object
* output Example -> DepictedItem depictedItem=new DepictedItem ()
* input Example -> string
*/
@TypeConverter
public static DepictedItem stringToDepicts(String objectList) {
return readObjectWithTypeToken(objectList, new TypeToken<DepictedItem>() {
});
}
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}
@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
@TypeConverter
public static Uri fromString(String value) {
return value == null ? null : Uri.parse(value);
}
@TypeConverter
public static String uriToString(Uri uri) {
return uri == null ? null : uri.toString();
}
@TypeConverter
public static String listObjectToString(List<String> objectList) {
return writeObjectToString(objectList);
}
@TypeConverter
public static List<String> stringToListObject(String objectList) {
return readObjectWithTypeToken(objectList, new TypeToken<List<String>>() {});
}
@TypeConverter
public static String mapObjectToString(Map<String,String> objectList) {
return writeObjectToString(objectList);
}
@TypeConverter
public static String mapObjectToString2(Map<String,Boolean> objectList) {
return writeObjectToString(objectList);
}
@TypeConverter
public static Map<String,String> stringToMap(String objectList) {
return readObjectWithTypeToken(objectList, new TypeToken<Map<String,String>>(){});
}
@TypeConverter
public static Map<String,Boolean> stringToMap2(String objectList) {
return readObjectWithTypeToken(objectList, new TypeToken<Map<String,Boolean>>(){});
}
@TypeConverter
public static String latlngObjectToString(LatLng latlng) {
return writeObjectToString(latlng);
}
@TypeConverter
public static LatLng stringToLatLng(String objectList) {
return readObjectFromString(objectList,LatLng.class);
}
@TypeConverter
public static String wikidataPlaceToString(WikidataPlace wikidataPlace) {
return writeObjectToString(wikidataPlace);
}
@TypeConverter
public static WikidataPlace stringToWikidataPlace(String wikidataPlace) {
return readObjectFromString(wikidataPlace, WikidataPlace.class);
}
@TypeConverter
public static String chunkInfoToString(ChunkInfo chunkInfo) {
return writeObjectToString(chunkInfo);
}
@TypeConverter
public static ChunkInfo stringToChunkInfo(String chunkInfo) {
return readObjectFromString(chunkInfo, ChunkInfo.class);
}
@TypeConverter
public static String depictionListToString(List<DepictedItem> depictedItems) {
return writeObjectToString(depictedItems);
}
@TypeConverter
public static List<DepictedItem> stringToList(String depictedItems) {
return readObjectWithTypeToken(depictedItems, new TypeToken<List<DepictedItem>>() {});
}
@TypeConverter
public static Sitelinks sitelinksFromString(String value) {
Type type = new TypeToken<Sitelinks>() {}.getType();
return new Gson().fromJson(value, type);
}
@TypeConverter
public static String fromSitelinks(Sitelinks sitelinks) {
Gson gson = new Gson();
return gson.toJson(sitelinks);
}
private static String writeObjectToString(Object object) {
return object == null ? null : getGson().toJson(object);
}
private static<T> T readObjectFromString(String objectAsString, Class<T> clazz) {
return objectAsString == null ? null : getGson().fromJson(objectAsString, clazz);
}
private static <T> T readObjectWithTypeToken(String objectList, TypeToken<T> typeToken) {
return objectList == null ? null : getGson().fromJson(objectList, typeToken.getType());
}
}

View file

@ -0,0 +1,182 @@
package fr.free.nrw.commons.db
import android.net.Uri
import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.contributions.ChunkInfo
import fr.free.nrw.commons.di.ApplicationlessInjection
import fr.free.nrw.commons.location.LatLng
import fr.free.nrw.commons.nearby.Sitelinks
import fr.free.nrw.commons.upload.WikidataPlace
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import java.util.Date
/**
* This object supplies converters to write/read types to/from the database.
*/
object Converters {
fun getGson(): Gson {
return ApplicationlessInjection
.getInstance(CommonsApplication.instance)
.commonsApplicationComponent
.gson()
}
/**
* convert DepictedItem object to string
* input Example -> DepictedItem depictedItem=new DepictedItem ()
* output Example -> string
*/
@TypeConverter
@JvmStatic
fun depictsItemToString(objects: DepictedItem?): String? {
return writeObjectToString(objects)
}
/**
* convert string to DepictedItem object
* output Example -> DepictedItem depictedItem=new DepictedItem ()
* input Example -> string
*/
@TypeConverter
@JvmStatic
fun stringToDepicts(objectList: String?): DepictedItem? {
return readObjectWithTypeToken(objectList, object : TypeToken<DepictedItem>() {})
}
@TypeConverter
@JvmStatic
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
@JvmStatic
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
@TypeConverter
@JvmStatic
fun fromString(value: String?): Uri? {
return value?.let { Uri.parse(it) }
}
@TypeConverter
@JvmStatic
fun uriToString(uri: Uri?): String? {
return uri?.toString()
}
@TypeConverter
@JvmStatic
fun listObjectToString(objectList: List<String>?): String? {
return writeObjectToString(objectList)
}
@TypeConverter
@JvmStatic
fun stringToListObject(objectList: String?): List<String>? {
return readObjectWithTypeToken(objectList, object : TypeToken<List<String>>() {})
}
@TypeConverter
@JvmStatic
fun mapObjectToString(objectList: Map<String, String>?): String? {
return writeObjectToString(objectList)
}
@TypeConverter
@JvmStatic
fun mapObjectToString2(objectList: Map<String, Boolean>?): String? {
return writeObjectToString(objectList)
}
@TypeConverter
@JvmStatic
fun stringToMap(objectList: String?): Map<String, String>? {
return readObjectWithTypeToken(objectList, object : TypeToken<Map<String, String>>() {})
}
@TypeConverter
@JvmStatic
fun stringToMap2(objectList: String?): Map<String, Boolean>? {
return readObjectWithTypeToken(objectList, object : TypeToken<Map<String, Boolean>>() {})
}
@TypeConverter
@JvmStatic
fun latlngObjectToString(latlng: LatLng?): String? {
return writeObjectToString(latlng)
}
@TypeConverter
@JvmStatic
fun stringToLatLng(objectList: String?): LatLng? {
return readObjectFromString(objectList, LatLng::class.java)
}
@TypeConverter
@JvmStatic
fun wikidataPlaceToString(wikidataPlace: WikidataPlace?): String? {
return writeObjectToString(wikidataPlace)
}
@TypeConverter
@JvmStatic
fun stringToWikidataPlace(wikidataPlace: String?): WikidataPlace? {
return readObjectFromString(wikidataPlace, WikidataPlace::class.java)
}
@TypeConverter
@JvmStatic
fun chunkInfoToString(chunkInfo: ChunkInfo?): String? {
return writeObjectToString(chunkInfo)
}
@TypeConverter
@JvmStatic
fun stringToChunkInfo(chunkInfo: String?): ChunkInfo? {
return readObjectFromString(chunkInfo, ChunkInfo::class.java)
}
@TypeConverter
@JvmStatic
fun depictionListToString(depictedItems: List<DepictedItem>?): String? {
return writeObjectToString(depictedItems)
}
@TypeConverter
@JvmStatic
fun stringToList(depictedItems: String?): List<DepictedItem>? {
return readObjectWithTypeToken(depictedItems, object : TypeToken<List<DepictedItem>>() {})
}
@TypeConverter
@JvmStatic
fun sitelinksFromString(value: String?): Sitelinks? {
val type = object : TypeToken<Sitelinks>() {}.type
return Gson().fromJson(value, type)
}
@TypeConverter
@JvmStatic
fun fromSitelinks(sitelinks: Sitelinks?): String? {
return Gson().toJson(sitelinks)
}
private fun writeObjectToString(`object`: Any?): String? {
return `object`?.let { getGson().toJson(it) }
}
private fun <T> readObjectFromString(objectAsString: String?, clazz: Class<T>): T? {
return objectAsString?.let { getGson().fromJson(it, clazz) }
}
private fun <T> readObjectWithTypeToken(objectList: String?, typeToken: TypeToken<T>): T? {
return objectList?.let { getGson().fromJson(it, typeToken.type) }
}
}

View file

@ -1,278 +0,0 @@
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 android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import androidx.appcompat.app.AlertDialog;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
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;
import io.reactivex.SingleSource;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Locale;
import java.util.concurrent.Callable;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import timber.log.Timber;
/**
* Refactored async task to Rx
*/
@Singleton
public class DeleteHelper {
private final NotificationHelper notificationHelper;
private final PageEditClient pageEditClient;
private final ViewUtilWrapper viewUtil;
private final String username;
private AlertDialog d;
private DialogInterface.OnMultiChoiceClickListener listener;
@Inject
public DeleteHelper(NotificationHelper notificationHelper,
@Named("commons-page-edit") PageEditClient pageEditClient,
ViewUtilWrapper viewUtil,
@Named("username") String username) {
this.notificationHelper = notificationHelper;
this.pageEditClient = pageEditClient;
this.viewUtil = viewUtil;
this.username = username;
}
/**
* Public interface to nominate a particular media file for deletion
* @param context
* @param media
* @param reason
* @return
*/
public Single<Boolean> makeDeletion(Context context, Media media, String reason) {
viewUtil.showShortToast(context, "Trying to nominate " + media.getDisplayTitle() + " for deletion");
return delete(media, reason)
.flatMapSingle(result -> Single.just(showDeletionNotification(context, media, result)))
.firstOrError()
.onErrorResumeNext(throwable -> {
if (throwable instanceof InvalidLoginTokenException) {
return Single.error(throwable);
}
return Single.error(throwable);
});
}
/**
* Makes several API calls to nominate the file for deletion
* @param media
* @param reason
* @return
*/
private Observable<Boolean> delete(Media media, String reason) {
Timber.d("thread is delete %s", Thread.currentThread().getName());
String summary = "Nominating " + media.getFilename() + " for deletion.";
Calendar calendar = Calendar.getInstance();
String fileDeleteString = "{{delete|reason=" + reason +
"|subpage=" + media.getFilename() +
"|day=" + calendar.get(Calendar.DAY_OF_MONTH) +
"|month=" + calendar.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.ENGLISH) +
"|year=" + calendar.get(Calendar.YEAR) +
"}}";
String subpageString = "=== [[:" + media.getFilename() + "]] ===\n" +
reason +
" ~~~~";
String logPageString = "\n{{Commons:Deletion requests/" + media.getFilename() +
"}}\n";
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault());
String date = sdf.format(calendar.getTime());
String userPageString = "\n{{subst:idw|" + media.getFilename() +
"}} ~~~~";
String creator = media.getAuthor();
if (creator == null || creator.isEmpty()) {
throw new RuntimeException("Failed to nominate for deletion");
}
return pageEditClient.prependEdit(media.getFilename(), fileDeleteString + "\n", summary)
.onErrorResumeNext(throwable -> {
if (throwable instanceof InvalidLoginTokenException) {
return Observable.error(throwable);
}
return Observable.error(throwable);
})
.flatMap(result -> {
if (result) {
return pageEditClient.edit("Commons:Deletion_requests/" + media.getFilename(), subpageString + "\n", summary);
}
return Observable.error(new RuntimeException("Failed to nominate for deletion"));
})
.flatMap(result -> {
if (result) {
return pageEditClient.appendEdit("Commons:Deletion_requests/" + date, logPageString + "\n", summary);
}
return Observable.error(new RuntimeException("Failed to nominate for deletion"));
})
.flatMap(result -> {
if (result) {
return pageEditClient.appendEdit("User_Talk:" + creator, userPageString + "\n", summary);
}
return Observable.error(new RuntimeException("Failed to nominate for deletion"));
});
}
private boolean showDeletionNotification(Context context, Media media, boolean result) {
String message;
String title = context.getString(R.string.delete_helper_show_deletion_title);
if (result) {
title += ": " + context.getString(R.string.delete_helper_show_deletion_title_success);
message = context.getString((R.string.delete_helper_show_deletion_message_if),media.getDisplayTitle());
} else {
title += ": " + context.getString(R.string.delete_helper_show_deletion_title_failed);
message = context.getString(R.string.delete_helper_show_deletion_message_else) ;
}
String urlForDelete = BuildConfig.COMMONS_URL + "/wiki/Commons:Deletion_requests/" + media.getFilename();
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForDelete));
notificationHelper.showNotification(context, title, message, NOTIFICATION_DELETE, browserIntent);
return result;
}
/**
* Invoked when a reason needs to be asked before nominating for deletion
* @param media
* @param context
* @param question
* @param problem
*/
@SuppressLint("CheckResult")
public void askReasonAndExecute(Media media,
Context context,
String question,
ReviewController.DeleteReason problem,
ReviewController.ReviewCallback reviewCallback) {
AlertDialog.Builder alert = new AlertDialog.Builder(context);
alert.setTitle(question);
boolean[] checkedItems = {false, false, false, false};
ArrayList<Integer> mUserReason = new ArrayList<>();
final String[] reasonList;
final String[] reasonListEnglish;
if (problem == ReviewController.DeleteReason.SPAM) {
reasonList = new String[] {
context.getString(R.string.delete_helper_ask_spam_selfie),
context.getString(R.string.delete_helper_ask_spam_blurry),
context.getString(R.string.delete_helper_ask_spam_nonsense)
};
reasonListEnglish = new String[] {
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_spam_selfie),
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_spam_blurry),
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_spam_nonsense)
};
} else if (problem == ReviewController.DeleteReason.COPYRIGHT_VIOLATION) {
reasonList = new String[] {
context.getString(R.string.delete_helper_ask_reason_copyright_press_photo),
context.getString(R.string.delete_helper_ask_reason_copyright_internet_photo),
context.getString(R.string.delete_helper_ask_reason_copyright_logo),
context.getString(R.string.delete_helper_ask_reason_copyright_no_freedom_of_panorama)
};
reasonListEnglish = new String[] {
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_reason_copyright_press_photo),
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_reason_copyright_internet_photo),
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_reason_copyright_logo),
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_reason_copyright_no_freedom_of_panorama)
};
} else {
reasonList = new String[] {};
reasonListEnglish = new String[] {};
}
alert.setMultiChoiceItems(reasonList, checkedItems, listener = (dialogInterface, position, isChecked) -> {
if (isChecked) {
mUserReason.add(position);
} else {
mUserReason.remove((Integer.valueOf(position)));
}
// disable the OK button if no reason selected
((AlertDialog) dialogInterface).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(
!mUserReason.isEmpty());
});
alert.setPositiveButton(context.getString(R.string.ok), (dialogInterface, i) -> {
reviewCallback.disableButtons();
String reason = getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_alert_set_positive_button_reason) + " ";
for (int j = 0; j < mUserReason.size(); j++) {
reason = reason + reasonListEnglish[mUserReason.get(j)];
if (j != mUserReason.size() - 1) {
reason = reason + ", ";
}
}
Timber.d("thread is askReasonAndExecute %s", Thread.currentThread().getName());
String finalReason = reason;
Single.defer((Callable<SingleSource<Boolean>>) () ->
makeDeletion(context, media, finalReason))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(aBoolean -> {
reviewCallback.onSuccess();
}, throwable -> {
if (throwable instanceof InvalidLoginTokenException) {
reviewCallback.onTokenException((InvalidLoginTokenException) throwable);
} else {
reviewCallback.onFailure();
}
reviewCallback.enableButtons();
});
});
alert.setNegativeButton(context.getString(R.string.cancel), (dialog, which) -> reviewCallback.onFailure());
d = alert.create();
d.show();
// disable the OK button by default
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
}
/**
* returns the instance of shown AlertDialog,
* used for taking reference during unit test
* */
public AlertDialog getDialog(){
return d;
}
/**
* returns the instance of shown DialogInterface.OnMultiChoiceClickListener,
* used for taking reference during unit test
* */
public DialogInterface.OnMultiChoiceClickListener getListener(){
return listener;
}
}

View file

@ -0,0 +1,333 @@
package fr.free.nrw.commons.delete
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AlertDialog
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
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.notification.NotificationHelper.Companion.NOTIFICATION_DELETE
import fr.free.nrw.commons.review.ReviewController
import fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources
import fr.free.nrw.commons.utils.ViewUtilWrapper
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import timber.log.Timber
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import javax.inject.Inject
import javax.inject.Named
import javax.inject.Singleton
/**
* Refactored async task to Rx
*/
@Singleton
class DeleteHelper @Inject constructor(
private val notificationHelper: NotificationHelper,
@Named("commons-page-edit") private val pageEditClient: PageEditClient,
private val viewUtil: ViewUtilWrapper,
@Named("username") private val username: String
) {
private var d: AlertDialog? = null
private var listener: DialogInterface.OnMultiChoiceClickListener? = null
/**
* Public interface to nominate a particular media file for deletion
* @param context
* @param media
* @param reason
* @return
*/
fun makeDeletion(
context: Context?,
media: Media?,
reason: String?
): Single<Boolean>? {
if(context == null && media == null) {
return null
}
viewUtil.showShortToast(
context!!,
"Trying to nominate ${media?.displayTitle} for deletion"
)
return reason?.let {
delete(media!!, it)
.flatMapSingle { result ->
Single.just(showDeletionNotification(context, media, result))
}
.firstOrError()
.onErrorResumeNext { throwable ->
if (throwable is InvalidLoginTokenException) {
Single.error(throwable)
} else {
Single.error(throwable)
}
}
}
}
/**
* Makes several API calls to nominate the file for deletion
* @param media
* @param reason
* @return
*/
private fun delete(media: Media, reason: String): Observable<Boolean> {
Timber.d("thread is delete %s", Thread.currentThread().name)
val summary = "Nominating ${media.filename} for deletion."
val calendar = Calendar.getInstance()
val fileDeleteString = """
{{delete|reason=$reason|subpage=${media.filename}|day=
${calendar.get(Calendar.DAY_OF_MONTH)}|month=${
calendar.getDisplayName(
Calendar.MONTH,
Calendar.LONG,
Locale.ENGLISH
)
}|year=${calendar.get(Calendar.YEAR)}}}
""".trimIndent()
val subpageString = """
=== [[:${media.filename}]] ===
$reason ~~~~
""".trimIndent()
val logPageString = "\n{{Commons:Deletion requests/${media.filename}}}\n"
val sdf = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault())
val date = sdf.format(calendar.time)
val userPageString = "\n{{subst:idw|${media.filename}}} ~~~~"
val creator = media.author
?: throw RuntimeException("Failed to nominate for deletion")
return pageEditClient.prependEdit(
media.filename!!,
"$fileDeleteString\n",
summary
)
.onErrorResumeNext { throwable: Throwable ->
if (throwable is InvalidLoginTokenException) {
Observable.error(throwable)
} else {
Observable.error(throwable)
}
}
.flatMap { result: Boolean ->
if (result) {
pageEditClient.edit(
"Commons:Deletion_requests/${media.filename}",
"$subpageString\n",
summary
)
} else {
Observable.error(RuntimeException("Failed to nominate for deletion"))
}
}
.flatMap { result: Boolean ->
if (result) {
pageEditClient.appendEdit(
"Commons:Deletion_requests/$date",
"$logPageString\n",
summary
)
} else {
Observable.error(RuntimeException("Failed to nominate for deletion"))
}
}
.flatMap { result: Boolean ->
if (result) {
pageEditClient.appendEdit("User_Talk:$creator", "$userPageString\n", summary)
} else {
Observable.error(RuntimeException("Failed to nominate for deletion"))
}
}
}
@SuppressLint("StringFormatInvalid")
private fun showDeletionNotification(
context: Context,
media: Media,
result: Boolean
): Boolean {
val title: String
val message: String
var baseTitle = context.getString(R.string.delete_helper_show_deletion_title)
if (result) {
baseTitle += ": ${
context.getString(R.string.delete_helper_show_deletion_title_success)
}"
title = baseTitle
message = context
.getString(R.string.delete_helper_show_deletion_message_if, media.displayTitle)
} else {
baseTitle += ": ${context.getString(R.string.delete_helper_show_deletion_title_failed)}"
title = baseTitle
message = context.getString(R.string.delete_helper_show_deletion_message_else)
}
val urlForDelete = "${BuildConfig.COMMONS_URL}/wiki/Commons:Deletion_requests/${
media.filename
}"
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(urlForDelete))
notificationHelper
.showNotification(context, title, message, NOTIFICATION_DELETE, browserIntent)
return result
}
/**
* Invoked when a reason needs to be asked before nominating for deletion
* @param media
* @param context
* @param question
* @param problem
*/
@SuppressLint("CheckResult")
fun askReasonAndExecute(
media: Media?,
context: Context,
question: String,
problem: ReviewController.DeleteReason,
reviewCallback: ReviewController.ReviewCallback
) {
val alert = AlertDialog.Builder(context)
alert.setTitle(question)
val checkedItems = booleanArrayOf(false, false, false, false)
val mUserReason = arrayListOf<Int>()
val reasonList: Array<String>
val reasonListEnglish: Array<String>
when (problem) {
ReviewController.DeleteReason.SPAM -> {
reasonList = arrayOf(
context.getString(R.string.delete_helper_ask_spam_selfie),
context.getString(R.string.delete_helper_ask_spam_blurry),
context.getString(R.string.delete_helper_ask_spam_nonsense)
)
reasonListEnglish = arrayOf(
getLocalizedResources(context, Locale.ENGLISH)
.getString(R.string.delete_helper_ask_spam_selfie),
getLocalizedResources(context, Locale.ENGLISH)
.getString(R.string.delete_helper_ask_spam_blurry),
getLocalizedResources(context, Locale.ENGLISH)
.getString(R.string.delete_helper_ask_spam_nonsense)
)
}
ReviewController.DeleteReason.COPYRIGHT_VIOLATION -> {
reasonList = arrayOf(
context.getString(R.string.delete_helper_ask_reason_copyright_press_photo),
context.getString(R.string.delete_helper_ask_reason_copyright_internet_photo),
context.getString(R.string.delete_helper_ask_reason_copyright_logo),
context.getString(
R.string.delete_helper_ask_reason_copyright_no_freedom_of_panorama
)
)
reasonListEnglish = arrayOf(
getLocalizedResources(context, Locale.ENGLISH)
.getString(R.string.delete_helper_ask_reason_copyright_press_photo),
getLocalizedResources(context, Locale.ENGLISH)
.getString(R.string.delete_helper_ask_reason_copyright_internet_photo),
getLocalizedResources(context, Locale.ENGLISH)
.getString(R.string.delete_helper_ask_reason_copyright_logo),
getLocalizedResources(context, Locale.ENGLISH)
.getString(
R.string.delete_helper_ask_reason_copyright_no_freedom_of_panorama
)
)
}
else -> {
reasonList = emptyArray()
reasonListEnglish = emptyArray()
}
}
alert.setMultiChoiceItems(
reasonList,
checkedItems
) { dialogInterface, position, isChecked ->
if (isChecked) {
mUserReason.add(position)
} else {
mUserReason.remove(position)
}
// Safely enable or disable the OK button based on selection
val dialog = dialogInterface as? AlertDialog
dialog?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = mUserReason.isNotEmpty()
}
alert.setPositiveButton(context.getString(R.string.ok)) { _, _ ->
reviewCallback.disableButtons()
val reason = buildString {
append(
getLocalizedResources(context, Locale.ENGLISH)
.getString(R.string.delete_helper_ask_alert_set_positive_button_reason)
)
append(" ")
mUserReason.forEachIndexed { index, position ->
append(reasonListEnglish[position])
if (index != mUserReason.lastIndex) {
append(", ")
}
}
}
Timber.d("thread is askReasonAndExecute %s", Thread.currentThread().name)
if (media != null) {
Single.defer { makeDeletion(context, media, reason) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ reviewCallback.onSuccess() }, { throwable ->
when (throwable) {
is InvalidLoginTokenException ->
reviewCallback.onTokenException(throwable)
else -> reviewCallback.onFailure()
}
reviewCallback.enableButtons()
})
}
}
alert.setNegativeButton(
context.getString(R.string.cancel)
) { _, _ -> reviewCallback.onFailure() }
d = alert.create()
d?.setOnShowListener {
// Safely initialize the OK button state after the dialog is fully shown
d?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = false
}
d?.show()
}
/**
* returns the instance of shown AlertDialog,
* used for taking reference during unit test
*/
fun getDialog(): AlertDialog? = d
/**
* returns the instance of shown DialogInterface.OnMultiChoiceClickListener,
* used for taking reference during unit test
*/
fun getListener(): DialogInterface.OnMultiChoiceClickListener? = listener
}

View file

@ -1,100 +0,0 @@
package fr.free.nrw.commons.delete;
import android.content.Context;
import fr.free.nrw.commons.utils.DateUtil;
import java.util.Date;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Singleton;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.profile.achievements.FeedbackResponse;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import fr.free.nrw.commons.utils.ViewUtilWrapper;
import io.reactivex.Single;
import timber.log.Timber;
/**
* This class handles the reason for deleting a Media object
*/
@Singleton
public class ReasonBuilder {
private SessionManager sessionManager;
private OkHttpJsonApiClient okHttpJsonApiClient;
private Context context;
private ViewUtilWrapper viewUtilWrapper;
@Inject
public ReasonBuilder(Context context,
SessionManager sessionManager,
OkHttpJsonApiClient okHttpJsonApiClient,
ViewUtilWrapper viewUtilWrapper) {
this.context = context;
this.sessionManager = sessionManager;
this.okHttpJsonApiClient = okHttpJsonApiClient;
this.viewUtilWrapper = viewUtilWrapper;
}
/**
* To process the reason and append the media's upload date and uploaded_by_me string
* @param media
* @param reason
* @return
*/
public Single<String> getReason(Media media, String reason) {
return fetchArticleNumber(media, reason);
}
/**
* get upload date for the passed Media
*/
private String prettyUploadedDate(Media media) {
Date date = media.getDateUploaded();
if (date == null || date.toString() == null || date.toString().isEmpty()) {
return "Uploaded date not available";
}
return DateUtil.getDateStringWithSkeletonPattern(date,"dd MMM yyyy");
}
private Single<String> fetchArticleNumber(Media media, String reason) {
if (checkAccount()) {
return okHttpJsonApiClient
.getAchievements(sessionManager.getUserName())
.map(feedbackResponse -> appendArticlesUsed(feedbackResponse, media, reason));
}
return Single.just("");
}
/**
* Takes the uploaded_by_me string, the upload date, name of articles using images
* and appends it to the received reason
* @param feedBack object
* @param media whose upload data is to be fetched
* @param reason
*/
private String appendArticlesUsed(FeedbackResponse feedBack, Media media, String reason) {
String reason1Template = context.getString(R.string.uploaded_by_myself);
reason += String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), feedBack.getArticlesUsingImages());
Timber.i("New Reason %s", reason);
return reason;
}
/**
* check to ensure that user is logged in
* @return
*/
private boolean checkAccount(){
if (!sessionManager.doesAccountExist()) {
Timber.d("Current account is null");
viewUtilWrapper.showLongToast(context, context.getResources().getString(R.string.user_not_logged_in));
sessionManager.forceLogin(context);
return false;
}
return true;
}
}

View file

@ -0,0 +1,95 @@
package fr.free.nrw.commons.delete
import android.annotation.SuppressLint
import android.content.Context
import fr.free.nrw.commons.utils.DateUtil
import java.util.Locale
import javax.inject.Inject
import javax.inject.Singleton
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.profile.achievements.FeedbackResponse
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import fr.free.nrw.commons.utils.ViewUtilWrapper
import io.reactivex.Single
import timber.log.Timber
/**
* This class handles the reason for deleting a Media object
*/
@Singleton
class ReasonBuilder @Inject constructor(
private val context: Context,
private val sessionManager: SessionManager,
private val okHttpJsonApiClient: OkHttpJsonApiClient,
private val viewUtilWrapper: ViewUtilWrapper
) {
/**
* To process the reason and append the media's upload date and uploaded_by_me string
* @param media
* @param reason
* @return
*/
fun getReason(media: Media?, reason: String?): Single<String> {
if (media == null || reason == null) {
return Single.just("Not known")
}
return fetchArticleNumber(media, reason)
}
/**
* get upload date for the passed Media
*/
private fun prettyUploadedDate(media: Media): String {
val date = media.dateUploaded
return if (date == null || date.toString().isEmpty()) {
"Uploaded date not available"
} else {
DateUtil.getDateStringWithSkeletonPattern(date, "dd MMM yyyy")
}
}
private fun fetchArticleNumber(media: Media, reason: String): Single<String> {
return if (checkAccount()) {
okHttpJsonApiClient
.getAchievements(sessionManager.userName)
.map { feedbackResponse -> appendArticlesUsed(feedbackResponse, media, reason) }
} else {
Single.just("")
}
}
/**
* Takes the uploaded_by_me string, the upload date, name of articles using images
* and appends it to the received reason
* @param feedBack object
* @param media whose upload data is to be fetched
* @param reason
*/
@SuppressLint("StringFormatInvalid")
private fun appendArticlesUsed(feedBack: FeedbackResponse, media: Media, reason: String): String {
val reason1Template = context.getString(R.string.uploaded_by_myself)
return reason + String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), feedBack.articlesUsingImages)
.also { Timber.i("New Reason %s", it) }
}
/**
* check to ensure that user is logged in
* @return
*/
private fun checkAccount(): Boolean {
return if (!sessionManager.doesAccountExist()) {
Timber.d("Current account is null")
viewUtilWrapper.showLongToast(context, context.getString(R.string.user_not_logged_in))
sessionManager.forceLogin(context)
false
} else {
true
}
}
}

View file

@ -272,7 +272,7 @@ class DescriptionEditActivity :
.addCaption( .addCaption(
applicationContext, applicationContext,
media, media,
mediaDetail.languageCode, mediaDetail.languageCode!!,
mediaDetail.captionText, mediaDetail.captionText,
).subscribeOn(Schedulers.io()) ).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())

View file

@ -1,137 +0,0 @@
package fr.free.nrw.commons.description;
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_EDIT_DESCRIPTION;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.actions.PageEditClient;
import fr.free.nrw.commons.notification.NotificationHelper;
import io.reactivex.Single;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Named;
import timber.log.Timber;
/**
* Helper class for edit and update given descriptions and showing notification upgradation
*/
public class DescriptionEditHelper {
/**
* notificationHelper: helps creating notification
*/
private final NotificationHelper notificationHelper;
/**
* * pageEditClient: methods provided by this member posts the edited descriptions
* to the Media wiki api
*/
public final PageEditClient pageEditClient;
@Inject
public DescriptionEditHelper(final NotificationHelper notificationHelper,
@Named("commons-page-edit") final PageEditClient pageEditClient) {
this.notificationHelper = notificationHelper;
this.pageEditClient = pageEditClient;
}
/**
* Replaces new descriptions
*
* @param context context
* @param media to be added
* @param appendText to be added
* @return Observable<Boolean>
*/
public Single<Boolean> addDescription(final Context context, final Media media,
final String appendText) {
Timber.d("thread is description adding %s", Thread.currentThread().getName());
final String summary = "Updating Description";
return pageEditClient.edit(Objects.requireNonNull(media.getFilename()),
appendText, summary)
.flatMapSingle(result -> Single.just(showDescriptionEditNotification(context,
media, result)))
.firstOrError();
}
/**
* Adds new captions
*
* @param context context
* @param media to be added
* @param language to be added
* @param value to be added
* @return Observable<Boolean>
*/
public Single<Boolean> addCaption(final Context context, final Media media,
final String language, final String value) {
Timber.d("thread is caption adding %s", Thread.currentThread().getName());
final String summary = "Updating Caption";
return pageEditClient.setCaptions(summary, Objects.requireNonNull(media.getFilename()),
language, value)
.flatMapSingle(result -> Single.just(showCaptionEditNotification(context,
media, result)))
.firstOrError();
}
/**
* Update captions and shows notification about captions update
* @param context to be added
* @param media to be added
* @param result to be added
* @return boolean
*/
private boolean showCaptionEditNotification(final Context context, final Media media,
final int result) {
final String message;
String title = context.getString(R.string.caption_edit_helper_show_edit_title);
if (result == 1) {
title += ": " + context
.getString(R.string.coordinates_edit_helper_show_edit_title_success);
message = context.getString(R.string.caption_edit_helper_show_edit_message);
} else {
title += ": " + context.getString(R.string.caption_edit_helper_show_edit_title);
message = context.getString(R.string.caption_edit_helper_edit_message_else) ;
}
final String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename();
final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile));
notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_DESCRIPTION,
browserIntent);
return result == 1;
}
/**
* Update descriptions and shows notification about descriptions update
* @param context to be added
* @param media to be added
* @param result to be added
* @return boolean
*/
private boolean showDescriptionEditNotification(final Context context, final Media media,
final boolean result) {
final String message;
String title = context.getString(R.string.description_edit_helper_show_edit_title);
if (result) {
title += ": " + context
.getString(R.string.coordinates_edit_helper_show_edit_title_success);
message = context.getString(R.string.description_edit_helper_show_edit_message);
} else {
title += ": " + context.getString(R.string.description_edit_helper_show_edit_title);
message = context.getString(R.string.description_edit_helper_edit_message_else) ;
}
final String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename();
final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile));
notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_DESCRIPTION,
browserIntent);
return result;
}
}

View file

@ -0,0 +1,154 @@
package fr.free.nrw.commons.description
import android.content.Context
import android.content.Intent
import android.net.Uri
import fr.free.nrw.commons.BuildConfig
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.actions.PageEditClient
import fr.free.nrw.commons.notification.NotificationHelper
import fr.free.nrw.commons.notification.NotificationHelper.Companion.NOTIFICATION_EDIT_DESCRIPTION
import io.reactivex.Single
import javax.inject.Inject
import javax.inject.Named
import timber.log.Timber
/**
* Helper class for edit and update given descriptions and showing notification upgradation
*/
class DescriptionEditHelper @Inject constructor(
/**
* notificationHelper: helps creating notification
*/
private val notificationHelper: NotificationHelper,
/**
* pageEditClient: methods provided by this member posts the edited descriptions
* to the Media wiki api
*/
@Named("commons-page-edit") val pageEditClient: PageEditClient
) {
/**
* Replaces new descriptions
*
* @param context context
* @param media to be added
* @param appendText to be added
* @return Single<Boolean>
*/
fun addDescription(context: Context, media: Media, appendText: String): Single<Boolean> {
Timber.d("thread is description adding %s", Thread.currentThread().name)
val summary = "Updating Description"
return pageEditClient.edit(
requireNotNull(media.filename),
appendText,
summary
).flatMapSingle { result ->
Single.just(showDescriptionEditNotification(context, media, result))
}.firstOrError()
}
/**
* Adds new captions
*
* @param context context
* @param media to be added
* @param language to be added
* @param value to be added
* @return Single<Boolean>
*/
fun addCaption(
context: Context,
media: Media,
language: String,
value: String
): Single<Boolean> {
Timber.d("thread is caption adding %s", Thread.currentThread().name)
val summary = "Updating Caption"
return pageEditClient.setCaptions(
summary,
requireNotNull(media.filename),
language,
value
).flatMapSingle { result ->
Single.just(showCaptionEditNotification(context, media, result))
}.firstOrError()
}
/**
* Update captions and shows notification about captions update
* @param context to be added
* @param media to be added
* @param result to be added
* @return boolean
*/
private fun showCaptionEditNotification(context: Context, media: Media, result: Int): Boolean {
val message: String
var title = context.getString(R.string.caption_edit_helper_show_edit_title)
if (result == 1) {
title += ": " + context.getString(
R.string.coordinates_edit_helper_show_edit_title_success
)
message = context.getString(R.string.caption_edit_helper_show_edit_message)
} else {
title += ": " + context.getString(R.string.caption_edit_helper_show_edit_title)
message = context.getString(R.string.caption_edit_helper_edit_message_else)
}
val urlForFile = "${BuildConfig.COMMONS_URL}/wiki/${media.filename}"
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile))
notificationHelper.showNotification(
context,
title,
message,
NOTIFICATION_EDIT_DESCRIPTION,
browserIntent
)
return result == 1
}
/**
* Update descriptions and shows notification about descriptions update
* @param context to be added
* @param media to be added
* @param result to be added
* @return boolean
*/
private fun showDescriptionEditNotification(
context: Context,
media: Media,
result: Boolean
): Boolean {
val message: String
var title= context.getString(
R.string.description_edit_helper_show_edit_title
)
if (result) {
title += ": " + context.getString(
R.string.coordinates_edit_helper_show_edit_title_success
)
message = context.getString(R.string.description_edit_helper_show_edit_message)
} else {
title += ": " + context.getString(R.string.description_edit_helper_show_edit_title)
message = context.getString(R.string.description_edit_helper_edit_message_else)
}
val urlForFile = "${BuildConfig.COMMONS_URL}/wiki/${media.filename}"
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile))
notificationHelper.showNotification(
context,
title,
message,
NOTIFICATION_EDIT_DESCRIPTION,
browserIntent
)
return result
}
}

View file

@ -15,11 +15,14 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Before import org.junit.Before
import fr.free.nrw.commons.R
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.spy
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.powermock.api.mockito.PowerMockito.`when`
import org.robolectric.RobolectricTestRunner import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config import org.robolectric.annotation.Config
@ -44,7 +47,7 @@ class DeleteHelperTest {
@Mock @Mock
internal lateinit var media: Media internal lateinit var media: Media
lateinit var deleteHelper: DeleteHelper private lateinit var deleteHelper: DeleteHelper
/** /**
* Init mocks for test * Init mocks for test
@ -60,19 +63,46 @@ class DeleteHelperTest {
*/ */
@Test @Test
fun makeDeletion() { fun makeDeletion() {
whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) whenever(pageEditClient.prependEdit(
.thenReturn(Observable.just(true)) ArgumentMatchers.anyString(),
whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) ArgumentMatchers.anyString(),
.thenReturn(Observable.just(true)) ArgumentMatchers.anyString())
whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) ).thenReturn(Observable.just(true))
.thenReturn(Observable.just(true))
whenever(pageEditClient.appendEdit(
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString())
).thenReturn(Observable.just(true))
whenever(pageEditClient.edit(
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString())
).thenReturn(Observable.just(true))
whenever(media.displayTitle).thenReturn("Test file") whenever(media.displayTitle).thenReturn("Test file")
`when`(context.getString(R.string.delete_helper_show_deletion_title))
.thenReturn("Deletion Notification")
`when`(context.getString(R.string.delete_helper_show_deletion_title_success))
.thenReturn("Success")
`when`(context.getString(R.string.delete_helper_show_deletion_title_failed))
.thenReturn("Failed")
`when`(context.getString(R.string.delete_helper_show_deletion_message_else))
.thenReturn("Media deletion failed")
`when`(context.getString(
R.string.delete_helper_show_deletion_message_if, media.displayTitle)
).thenReturn("Media successfully deleted: Test Media Title")
val creatorName = "Creator" val creatorName = "Creator"
whenever(media.author).thenReturn("$creatorName") whenever(media.author).thenReturn("$creatorName")
whenever(media.filename).thenReturn("Test file.jpg") whenever(media.filename).thenReturn("Test file.jpg")
val makeDeletion = deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet() val makeDeletion = deleteHelper.makeDeletion(
context,
media,
"Test reason"
)?.blockingGet()
assertNotNull(makeDeletion) assertNotNull(makeDeletion)
assertTrue(makeDeletion!!) assertTrue(makeDeletion!!)
verify(pageEditClient).appendEdit(eq("User_Talk:$creatorName"), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()) verify(pageEditClient).appendEdit(eq("User_Talk:$creatorName"), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())
@ -83,12 +113,24 @@ class DeleteHelperTest {
*/ */
@Test(expected = RuntimeException::class) @Test(expected = RuntimeException::class)
fun makeDeletionForPrependEditFailure() { fun makeDeletionForPrependEditFailure() {
whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) whenever(pageEditClient.prependEdit(
.thenReturn(Observable.just(false)) ArgumentMatchers.anyString(),
whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) ArgumentMatchers.anyString(),
.thenReturn(Observable.just(true)) ArgumentMatchers.anyString())
whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) ).thenReturn(Observable.just(false))
.thenReturn(Observable.just(true))
whenever(pageEditClient.appendEdit(
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString())
).thenReturn(Observable.just(true))
whenever(pageEditClient.edit(
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString(),
ArgumentMatchers.anyString())
).thenReturn(Observable.just(true))
whenever(media.displayTitle).thenReturn("Test file") whenever(media.displayTitle).thenReturn("Test file")
whenever(media.filename).thenReturn("Test file.jpg") whenever(media.filename).thenReturn("Test file.jpg")
whenever(media.author).thenReturn("Creator (page does not exist)") whenever(media.author).thenReturn("Creator (page does not exist)")
@ -141,16 +183,30 @@ class DeleteHelperTest {
@Test @Test
fun alertDialogPositiveButtonDisableTest() { fun alertDialogPositiveButtonDisableTest() {
val mContext = RuntimeEnvironment.getApplication().applicationContext val mContext = RuntimeEnvironment.getApplication().applicationContext
deleteHelper.askReasonAndExecute(media, mContext, "My Question", ReviewController.DeleteReason.COPYRIGHT_VIOLATION, callback) deleteHelper.askReasonAndExecute(
assertEquals(false, deleteHelper.dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled) media,
mContext,
"My Question",
ReviewController.DeleteReason.COPYRIGHT_VIOLATION, callback
)
deleteHelper.getListener()?.onClick(
deleteHelper.getDialog(),
1,
true
)
assertEquals(
true,
deleteHelper.getDialog()?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled
)
} }
@Test @Test
fun alertDialogPositiveButtonEnableTest() { fun alertDialogPositiveButtonEnableTest() {
val mContext = RuntimeEnvironment.getApplication().applicationContext val mContext = RuntimeEnvironment.getApplication().applicationContext
deleteHelper.askReasonAndExecute(media, mContext, "My Question", ReviewController.DeleteReason.COPYRIGHT_VIOLATION, callback) deleteHelper.askReasonAndExecute(media, mContext, "My Question", ReviewController.DeleteReason.COPYRIGHT_VIOLATION, callback)
deleteHelper.listener.onClick(deleteHelper.dialog, 1, true) deleteHelper.getListener()?.onClick(deleteHelper.getDialog(), 1, true)
assertEquals(true, deleteHelper.dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled) assertEquals(true, deleteHelper.getDialog()?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled)
} }
@Test(expected = RuntimeException::class) @Test(expected = RuntimeException::class)

View file

@ -3,6 +3,7 @@ package fr.free.nrw.commons.delete
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import fr.free.nrw.commons.Media import fr.free.nrw.commons.Media
import fr.free.nrw.commons.R
import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import fr.free.nrw.commons.profile.achievements.FeedbackResponse import fr.free.nrw.commons.profile.achievements.FeedbackResponse
@ -24,6 +25,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import org.powermock.api.mockito.PowerMockito
import java.util.Date import java.util.Date
class ReasonBuilderTest { class ReasonBuilderTest {
@ -53,6 +55,9 @@ class ReasonBuilderTest {
@Test @Test
fun forceLoginWhenAccountIsNull() { fun forceLoginWhenAccountIsNull() {
PowerMockito.`when`(context?.getString(R.string.user_not_logged_in))
.thenReturn("Log-in expired. Please log in again.")
reasonBuilder!!.getReason(mock(Media::class.java), "test") reasonBuilder!!.getReason(mock(Media::class.java), "test")
verify(sessionManager, times(1))!!.forceLogin(any(Context::class.java)) verify(sessionManager, times(1))!!.forceLogin(any(Context::class.java))
} }

View file

@ -10,6 +10,7 @@ import org.junit.Test
import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Assertions.assertEquals
import org.mockito.ArgumentMatchers.anyString import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock import org.mockito.Mock
import fr.free.nrw.commons.R
import org.mockito.Mockito.times import org.mockito.Mockito.times
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` import org.mockito.Mockito.`when`
@ -73,6 +74,15 @@ class DescriptionEditHelperUnitTest {
@Test @Test
fun testShowCaptionEditNotificationCaseFalse() { fun testShowCaptionEditNotificationCaseFalse() {
`when`(context.getString(R.string.caption_edit_helper_show_edit_title))
.thenReturn("Edit Caption")
`when`(context.getString(R.string.coordinates_edit_helper_show_edit_title_success))
.thenReturn("Success")
`when`(context.getString(R.string.caption_edit_helper_show_edit_message))
.thenReturn("Edit caption was successful")
`when`(context.getString(R.string.caption_edit_helper_edit_message_else))
.thenReturn("Edit caption failed")
val method: Method = val method: Method =
DescriptionEditHelper::class.java.getDeclaredMethod( DescriptionEditHelper::class.java.getDeclaredMethod(
"showCaptionEditNotification", "showCaptionEditNotification",
@ -86,6 +96,15 @@ class DescriptionEditHelperUnitTest {
@Test @Test
fun testShowCaptionEditNotificationCaseTrue() { fun testShowCaptionEditNotificationCaseTrue() {
`when`(context.getString(R.string.caption_edit_helper_show_edit_title))
.thenReturn("Edit Caption")
`when`(context.getString(R.string.coordinates_edit_helper_show_edit_title_success))
.thenReturn("Success")
`when`(context.getString(R.string.caption_edit_helper_show_edit_message))
.thenReturn("Edit caption was successful")
`when`(context.getString(R.string.caption_edit_helper_edit_message_else))
.thenReturn("Edit caption failed")
val method: Method = val method: Method =
DescriptionEditHelper::class.java.getDeclaredMethod( DescriptionEditHelper::class.java.getDeclaredMethod(
"showCaptionEditNotification", "showCaptionEditNotification",
@ -99,6 +118,15 @@ class DescriptionEditHelperUnitTest {
@Test @Test
fun testShowDescriptionEditNotificationCaseFalse() { fun testShowDescriptionEditNotificationCaseFalse() {
`when`(context.getString(R.string.description_edit_helper_show_edit_title))
.thenReturn("Edit Description")
`when`(context.getString(R.string.coordinates_edit_helper_show_edit_title_success))
.thenReturn("Success")
`when`(context.getString(R.string.description_edit_helper_show_edit_message))
.thenReturn("Edit message")
`when`(context.getString(R.string.description_edit_helper_edit_message_else))
.thenReturn("Edit failed")
val method: Method = val method: Method =
DescriptionEditHelper::class.java.getDeclaredMethod( DescriptionEditHelper::class.java.getDeclaredMethod(
"showDescriptionEditNotification", "showDescriptionEditNotification",
@ -112,6 +140,15 @@ class DescriptionEditHelperUnitTest {
@Test @Test
fun testShowDescriptionEditNotificationCaseTrue() { fun testShowDescriptionEditNotificationCaseTrue() {
`when`(context.getString(R.string.description_edit_helper_show_edit_title))
.thenReturn("Edit Description")
`when`(context.getString(R.string.coordinates_edit_helper_show_edit_title_success))
.thenReturn("Success")
`when`(context.getString(R.string.description_edit_helper_show_edit_message))
.thenReturn("Edit message")
`when`(context.getString(R.string.description_edit_helper_edit_message_else))
.thenReturn("Edit failed")
val method: Method = val method: Method =
DescriptionEditHelper::class.java.getDeclaredMethod( DescriptionEditHelper::class.java.getDeclaredMethod(
"showDescriptionEditNotification", "showDescriptionEditNotification",

View file

@ -27,6 +27,7 @@ import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.generic.GenericDraweeHierarchy import com.facebook.drawee.generic.GenericDraweeHierarchy
import com.facebook.drawee.view.SimpleDraweeView import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.soloader.SoLoader import com.facebook.soloader.SoLoader
import com.nhaarman.mockitokotlin2.anyOrNull
import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.doReturn
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.LocationPicker.LocationPickerActivity import fr.free.nrw.commons.LocationPicker.LocationPickerActivity
@ -768,9 +769,16 @@ class MediaDetailFragmentUnitTests {
).thenReturn(true) ).thenReturn(true)
doReturn( doReturn(
Single.just(true), Single.just(true),
).`when`(deleteHelper).makeDeletion(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any()) ).`when`(deleteHelper).makeDeletion(
ArgumentMatchers.any(),
ArgumentMatchers.any(),
ArgumentMatchers.any()
)
doReturn(Single.just("")).`when`(reasonBuilder).getReason(ArgumentMatchers.any(), ArgumentMatchers.any()) doReturn(Single.just("")).`when`(reasonBuilder).getReason(
ArgumentMatchers.any(),
ArgumentMatchers.any()
)
val method: Method = val method: Method =
MediaDetailFragment::class.java.getDeclaredMethod( MediaDetailFragment::class.java.getDeclaredMethod(