mirror of
				https://github.com/commons-app/apps-android-commons.git
				synced 2025-10-31 06:43:56 +01:00 
			
		
		
		
	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:
		
							parent
							
								
									73311970c5
								
							
						
					
					
						commit
						3030a6fca7
					
				
					 18 changed files with 1168 additions and 973 deletions
				
			
		|  | @ -372,16 +372,18 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback { | |||
|      */ | ||||
|     private fun removeLocationFromImage() { | ||||
|         media?.let { | ||||
|             compositeDisposable.add( | ||||
|                 coordinateEditHelper.makeCoordinatesEdit( | ||||
|                     applicationContext, it, "0.0", "0.0", "0.0f" | ||||
|                 ) | ||||
|                     .subscribeOn(Schedulers.io()) | ||||
|                     .observeOn(AndroidSchedulers.mainThread()) | ||||
|                     .subscribe { _ -> | ||||
|                         Timber.d("Coordinates removed from the image") | ||||
|                     } | ||||
|             coordinateEditHelper.makeCoordinatesEdit( | ||||
|                 applicationContext, it, "0.0", "0.0", "0.0f" | ||||
|             ) | ||||
|                 ?.subscribeOn(Schedulers.io()) | ||||
|                 ?.observeOn(AndroidSchedulers.mainThread()) | ||||
|                 ?.subscribe { _ -> | ||||
|                     Timber.d("Coordinates removed from the image") | ||||
|                 }?.let { it1 -> | ||||
|                     compositeDisposable.add( | ||||
|                         it1 | ||||
|                     ) | ||||
|                 } | ||||
|         } | ||||
|         setResult(RESULT_OK, Intent()) | ||||
|         finish() | ||||
|  | @ -473,19 +475,21 @@ class LocationPickerActivity : BaseActivity(), LocationPermissionCallback { | |||
|     fun updateCoordinates(latitude: String, longitude: String, accuracy: String) { | ||||
|         media?.let { | ||||
|             try { | ||||
|                 compositeDisposable.add( | ||||
|                     coordinateEditHelper.makeCoordinatesEdit( | ||||
|                         applicationContext, | ||||
|                         it, | ||||
|                         latitude, | ||||
|                         longitude, | ||||
|                         accuracy | ||||
|                     ).subscribeOn(Schedulers.io()) | ||||
|                         .observeOn(AndroidSchedulers.mainThread()) | ||||
|                         .subscribe { _ -> | ||||
|                             Timber.d("Coordinates updated") | ||||
|                         } | ||||
|                 ) | ||||
|                 coordinateEditHelper.makeCoordinatesEdit( | ||||
|                     applicationContext, | ||||
|                     it, | ||||
|                     latitude, | ||||
|                     longitude, | ||||
|                     accuracy | ||||
|                 )?.subscribeOn(Schedulers.io()) | ||||
|                     ?.observeOn(AndroidSchedulers.mainThread()) | ||||
|                     ?.subscribe { _ -> | ||||
|                         Timber.d("Coordinates updated") | ||||
|                     }?.let { it1 -> | ||||
|                         compositeDisposable.add( | ||||
|                             it1 | ||||
|                         ) | ||||
|                     } | ||||
|             } catch (e: Exception) { | ||||
|                 if (e.localizedMessage == CsrfTokenClient.ANONYMOUS_TOKEN_MESSAGE) { | ||||
|                     val username = sessionManager.userName | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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 | ||||
|     } | ||||
| } | ||||
|  | @ -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(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										62
									
								
								app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt
									
										
									
									
									
										Normal 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() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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()); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										182
									
								
								app/src/main/java/fr/free/nrw/commons/db/Converters.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										182
									
								
								app/src/main/java/fr/free/nrw/commons/db/Converters.kt
									
										
									
									
									
										Normal 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) } | ||||
|     } | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										333
									
								
								app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.kt
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										333
									
								
								app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.kt
									
										
									
									
									
										Normal 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 | ||||
| } | ||||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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 | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -272,7 +272,7 @@ class DescriptionEditActivity : | |||
|                         .addCaption( | ||||
|                             applicationContext, | ||||
|                             media, | ||||
|                             mediaDetail.languageCode, | ||||
|                             mediaDetail.languageCode!!, | ||||
|                             mediaDetail.captionText, | ||||
|                         ).subscribeOn(Schedulers.io()) | ||||
|                         .observeOn(AndroidSchedulers.mainThread()) | ||||
|  |  | |||
|  | @ -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; | ||||
|     } | ||||
| } | ||||
|  | @ -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 | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Saifuddin Adenwala
						Saifuddin Adenwala