diff --git a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java b/app/src/main/java/fr/free/nrw/commons/AboutActivity.java index ca433a2635..dcc9bfd435 100644 --- a/app/src/main/java/fr/free/nrw/commons/AboutActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/AboutActivity.java @@ -180,8 +180,8 @@ public void launchTranslate(View view) { getString(R.string.about_translate_cancel), positiveButtonRunnable, () -> {}, - spinner, - true); + spinner + ); } } diff --git a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.kt b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.kt index 1a5ec0a348..2b4ccf0ec5 100644 --- a/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/LocationPicker/LocationPickerActivity.kt @@ -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 diff --git a/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java b/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java index bed2a8755f..c8cedfef1a 100644 --- a/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/WelcomeActivity.java @@ -50,6 +50,7 @@ public void onCreate(final Bundle savedInstanceState) { copyrightBinding = PopupForCopyrightBinding.inflate(getLayoutInflater()); final View contactPopupView = copyrightBinding.getRoot(); dialogBuilder.setView(contactPopupView); + dialogBuilder.setCancelable(false); dialog = dialogBuilder.create(); dialog.show(); diff --git a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt index 3aa9b26d95..330792fa73 100644 --- a/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/auth/LoginActivity.kt @@ -319,7 +319,7 @@ class LoginActivity : AccountAuthenticatorActivity() { isIndeterminate = true setTitle(getString(R.string.logging_in_title)) setMessage(getString(R.string.logging_in_message)) - setCanceledOnTouchOutside(false) + setCancelable(false) } progressDialog!!.show() } diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt index 1cb50b8f3b..7e6fee2fca 100644 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoriesModel.kt @@ -78,7 +78,13 @@ class CategoriesModel // Newly used category... if (category == null) { - category = Category(null, item.name, item.description, item.thumbnail, Date(), 0) + category = Category( + null, item.name, + item.description, + item.thumbnail, + Date(), + 0 + ) } category.incTimesUsed() categoryDao.save(category) diff --git a/app/src/main/java/fr/free/nrw/commons/category/Category.java b/app/src/main/java/fr/free/nrw/commons/category/Category.java deleted file mode 100644 index 32bba67baf..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/category/Category.java +++ /dev/null @@ -1,115 +0,0 @@ -package fr.free.nrw.commons.category; - -import android.net.Uri; - -import java.util.Date; - -/** - * Represents a category - */ -public class Category { - private Uri contentUri; - private String name; - private String description; - private String thumbnail; - private Date lastUsed; - private int timesUsed; - - public Category() { - } - - public Category(Uri contentUri, String name, String description, String thumbnail, Date lastUsed, int timesUsed) { - this.contentUri = contentUri; - this.name = name; - this.description = description; - this.thumbnail = thumbnail; - this.lastUsed = lastUsed; - this.timesUsed = timesUsed; - } - - /** - * Gets name - * - * @return name - */ - public String getName() { - return name; - } - - /** - * Modifies name - * - * @param name Category name - */ - public void setName(String name) { - this.name = name; - } - - /** - * Gets last used date - * - * @return Last used date - */ - public Date getLastUsed() { - // warning: Date objects are mutable. - return (Date)lastUsed.clone(); - } - - /** - * Generates new last used date - */ - private void touch() { - lastUsed = new Date(); - } - - /** - * Gets no. of times the category is used - * - * @return no. of times used - */ - public int getTimesUsed() { - return timesUsed; - } - - /** - * Increments timesUsed by 1 and sets last used date as now. - */ - public void incTimesUsed() { - timesUsed++; - touch(); - } - - /** - * Gets the content URI for this category - * - * @return content URI - */ - public Uri getContentUri() { - return contentUri; - } - - /** - * Modifies the content URI - marking this category as already saved in the database - * - * @param contentUri the content URI - */ - public void setContentUri(Uri contentUri) { - this.contentUri = contentUri; - } - - public String getDescription() { - return description; - } - - public String getThumbnail() { - return thumbnail; - } - - public void setDescription(final String description) { - this.description = description; - } - - public void setThumbnail(final String thumbnail) { - this.thumbnail = thumbnail; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/category/Category.kt b/app/src/main/java/fr/free/nrw/commons/category/Category.kt new file mode 100644 index 0000000000..e4bfb957a2 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/Category.kt @@ -0,0 +1,17 @@ +package fr.free.nrw.commons.category + +import android.net.Uri +import java.util.Date + +data class Category( + var contentUri: Uri? = null, + val name: String? = null, + val description: String? = null, + val thumbnail: String? = null, + val lastUsed: Date? = null, + var timesUsed: Int = 0 +) { + fun incTimesUsed() { + timesUsed++ + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryClickedListener.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryClickedListener.java deleted file mode 100644 index df99b40603..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryClickedListener.java +++ /dev/null @@ -1,5 +0,0 @@ -package fr.free.nrw.commons.category; - -public interface CategoryClickedListener { - void categoryClicked(CategoryItem item); -} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryClickedListener.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryClickedListener.kt new file mode 100644 index 0000000000..ef4ec3d39b --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryClickedListener.kt @@ -0,0 +1,5 @@ +package fr.free.nrw.commons.category + +interface CategoryClickedListener { + fun categoryClicked(item: CategoryItem) +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java deleted file mode 100644 index 01793ca954..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.java +++ /dev/null @@ -1,169 +0,0 @@ -package fr.free.nrw.commons.category; - -import android.content.ContentValues; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteQueryBuilder; -import android.net.Uri; -import android.text.TextUtils; - -import androidx.annotation.NonNull; - -import javax.inject.Inject; - -import fr.free.nrw.commons.BuildConfig; -import fr.free.nrw.commons.data.DBOpenHelper; -import fr.free.nrw.commons.di.CommonsDaggerContentProvider; -import timber.log.Timber; - -import static android.content.UriMatcher.NO_MATCH; -import static fr.free.nrw.commons.category.CategoryDao.Table.ALL_FIELDS; -import static fr.free.nrw.commons.category.CategoryDao.Table.COLUMN_ID; -import static fr.free.nrw.commons.category.CategoryDao.Table.TABLE_NAME; - -public class CategoryContentProvider extends CommonsDaggerContentProvider { - - // For URI matcher - private static final int CATEGORIES = 1; - private static final int CATEGORIES_ID = 2; - private static final String BASE_PATH = "categories"; - - public static final Uri BASE_URI = Uri.parse("content://" + BuildConfig.CATEGORY_AUTHORITY + "/" + BASE_PATH); - - private static final UriMatcher uriMatcher = new UriMatcher(NO_MATCH); - - static { - uriMatcher.addURI(BuildConfig.CATEGORY_AUTHORITY, BASE_PATH, CATEGORIES); - uriMatcher.addURI(BuildConfig.CATEGORY_AUTHORITY, BASE_PATH + "/#", CATEGORIES_ID); - } - - public static Uri uriForId(int id) { - return Uri.parse(BASE_URI.toString() + "/" + id); - } - - @Inject DBOpenHelper dbOpenHelper; - - @SuppressWarnings("ConstantConditions") - @Override - public Cursor query(@NonNull Uri uri, String[] projection, String selection, - String[] selectionArgs, String sortOrder) { - SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); - queryBuilder.setTables(TABLE_NAME); - - int uriType = uriMatcher.match(uri); - - SQLiteDatabase db = dbOpenHelper.getReadableDatabase(); - Cursor cursor; - - switch (uriType) { - case CATEGORIES: - cursor = queryBuilder.query(db, projection, selection, selectionArgs, - null, null, sortOrder); - break; - case CATEGORIES_ID: - cursor = queryBuilder.query(db, - ALL_FIELDS, - "_id = ?", - new String[]{uri.getLastPathSegment()}, - null, - null, - sortOrder - ); - break; - default: - throw new IllegalArgumentException("Unknown URI" + uri); - } - - cursor.setNotificationUri(getContext().getContentResolver(), uri); - - return cursor; - } - - @Override - public String getType(@NonNull Uri uri) { - return null; - } - - @SuppressWarnings("ConstantConditions") - @Override - public Uri insert(@NonNull Uri uri, ContentValues contentValues) { - int uriType = uriMatcher.match(uri); - SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); - long id; - switch (uriType) { - case CATEGORIES: - id = sqlDB.insert(TABLE_NAME, null, contentValues); - break; - default: - throw new IllegalArgumentException("Unknown URI: " + uri); - } - getContext().getContentResolver().notifyChange(uri, null); - return Uri.parse(BASE_URI + "/" + id); - } - - @Override - public int delete(@NonNull Uri uri, String s, String[] strings) { - return 0; - } - - @SuppressWarnings("ConstantConditions") - @Override - public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) { - Timber.d("Hello, bulk insert! (CategoryContentProvider)"); - int uriType = uriMatcher.match(uri); - SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); - sqlDB.beginTransaction(); - switch (uriType) { - case CATEGORIES: - for (ContentValues value : values) { - Timber.d("Inserting! %s", value); - sqlDB.insert(TABLE_NAME, null, value); - } - break; - default: - throw new IllegalArgumentException("Unknown URI: " + uri); - } - sqlDB.setTransactionSuccessful(); - sqlDB.endTransaction(); - getContext().getContentResolver().notifyChange(uri, null); - return values.length; - } - - @SuppressWarnings("ConstantConditions") - @Override - public int update(@NonNull Uri uri, ContentValues contentValues, String selection, - String[] selectionArgs) { - /* - SQL Injection warnings: First, note that we're not exposing this to the - outside world (exported="false"). Even then, we should make sure to sanitize - all user input appropriately. Input that passes through ContentValues - should be fine. So only issues are those that pass in via concating. - - In here, the only concat created argument is for id. It is cast to an int, - and will error out otherwise. - */ - int uriType = uriMatcher.match(uri); - SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase(); - int rowsUpdated; - switch (uriType) { - case CATEGORIES_ID: - if (TextUtils.isEmpty(selection)) { - int id = Integer.valueOf(uri.getLastPathSegment()); - rowsUpdated = sqlDB.update(TABLE_NAME, - contentValues, - COLUMN_ID + " = ?", - new String[]{String.valueOf(id)}); - } else { - throw new IllegalArgumentException( - "Parameter `selection` should be empty when updating an ID"); - } - break; - default: - throw new IllegalArgumentException("Unknown URI: " + uri + " with type " + uriType); - } - getContext().getContentResolver().notifyChange(uri, null); - return rowsUpdated; - } -} - diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.kt new file mode 100644 index 0000000000..ddd7f5ae41 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryContentProvider.kt @@ -0,0 +1,205 @@ +package fr.free.nrw.commons.category + + +import android.content.ContentValues +import android.content.UriMatcher +import android.content.UriMatcher.NO_MATCH +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteQueryBuilder +import android.net.Uri +import android.text.TextUtils +import androidx.annotation.NonNull +import fr.free.nrw.commons.BuildConfig +import fr.free.nrw.commons.data.DBOpenHelper +import fr.free.nrw.commons.di.CommonsDaggerContentProvider +import timber.log.Timber +import javax.inject.Inject + +class CategoryContentProvider : CommonsDaggerContentProvider() { + + private val uriMatcher = UriMatcher(NO_MATCH).apply { + addURI(BuildConfig.CATEGORY_AUTHORITY, BASE_PATH, CATEGORIES) + addURI(BuildConfig.CATEGORY_AUTHORITY, "${BASE_PATH}/#", CATEGORIES_ID) + } + + @Inject + lateinit var dbOpenHelper: DBOpenHelper + + @SuppressWarnings("ConstantConditions") + override fun query(uri: Uri, projection: Array?, selection: String?, + selectionArgs: Array?, sortOrder: String?): Cursor? { + val queryBuilder = SQLiteQueryBuilder().apply { + tables = TABLE_NAME + } + + val uriType = uriMatcher.match(uri) + val db = dbOpenHelper.readableDatabase + + val cursor: Cursor? = when (uriType) { + CATEGORIES -> queryBuilder.query( + db, + projection, + selection, + selectionArgs, + null, + null, + sortOrder + ) + CATEGORIES_ID -> queryBuilder.query( + db, + ALL_FIELDS, + "_id = ?", + arrayOf(uri.lastPathSegment), + null, + null, + sortOrder + ) + else -> throw IllegalArgumentException("Unknown URI $uri") + } + + cursor?.setNotificationUri(context?.contentResolver, uri) + return cursor + } + + override fun getType(uri: Uri): String? { + return null + } + + @SuppressWarnings("ConstantConditions") + override fun insert(uri: Uri, contentValues: ContentValues?): Uri? { + val uriType = uriMatcher.match(uri) + val sqlDB = dbOpenHelper.writableDatabase + val id: Long + when (uriType) { + CATEGORIES -> { + id = sqlDB.insert(TABLE_NAME, null, contentValues) + } + else -> throw IllegalArgumentException("Unknown URI: $uri") + } + context?.contentResolver?.notifyChange(uri, null) + return Uri.parse("${Companion.BASE_URI}/$id") + } + + @SuppressWarnings("ConstantConditions") + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { + // Not implemented + return 0 + } + + @SuppressWarnings("ConstantConditions") + override fun bulkInsert(uri: Uri, values: Array): Int { + Timber.d("Hello, bulk insert! (CategoryContentProvider)") + val uriType = uriMatcher.match(uri) + val sqlDB = dbOpenHelper.writableDatabase + sqlDB.beginTransaction() + when (uriType) { + CATEGORIES -> { + for (value in values) { + Timber.d("Inserting! %s", value) + sqlDB.insert(TABLE_NAME, null, value) + } + sqlDB.setTransactionSuccessful() + } + else -> throw IllegalArgumentException("Unknown URI: $uri") + } + sqlDB.endTransaction() + context?.contentResolver?.notifyChange(uri, null) + return values.size + } + + @SuppressWarnings("ConstantConditions") + override fun update(uri: Uri, contentValues: ContentValues?, selection: String?, + selectionArgs: Array?): Int { + val uriType = uriMatcher.match(uri) + val sqlDB = dbOpenHelper.writableDatabase + val rowsUpdated: Int + when (uriType) { + CATEGORIES_ID -> { + if (TextUtils.isEmpty(selection)) { + val id = uri.lastPathSegment?.toInt() + ?: throw IllegalArgumentException("Invalid ID") + rowsUpdated = sqlDB.update(TABLE_NAME, + contentValues, + "$COLUMN_ID = ?", + arrayOf(id.toString())) + } else { + throw IllegalArgumentException( + "Parameter `selection` should be empty when updating an ID") + } + } + else -> throw IllegalArgumentException("Unknown URI: $uri with type $uriType") + } + context?.contentResolver?.notifyChange(uri, null) + return rowsUpdated + } + + companion object { + const val TABLE_NAME = "categories" + + const val COLUMN_ID = "_id" + const val COLUMN_NAME = "name" + const val COLUMN_DESCRIPTION = "description" + const val COLUMN_THUMBNAIL = "thumbnail" + const val COLUMN_LAST_USED = "last_used" + const val COLUMN_TIMES_USED = "times_used" + + // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES. + val ALL_FIELDS = arrayOf( + COLUMN_ID, + COLUMN_NAME, + COLUMN_DESCRIPTION, + COLUMN_THUMBNAIL, + COLUMN_LAST_USED, + COLUMN_TIMES_USED + ) + + const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS $TABLE_NAME" + + const val CREATE_TABLE_STATEMENT = "CREATE TABLE $TABLE_NAME (" + + "$COLUMN_ID INTEGER PRIMARY KEY," + + "$COLUMN_NAME TEXT," + + "$COLUMN_DESCRIPTION TEXT," + + "$COLUMN_THUMBNAIL TEXT," + + "$COLUMN_LAST_USED INTEGER," + + "$COLUMN_TIMES_USED INTEGER" + + ");" + + fun uriForId(id: Int): Uri { + return Uri.parse("${BASE_URI}/$id") + } + + fun onCreate(db: SQLiteDatabase) { + db.execSQL(CREATE_TABLE_STATEMENT) + } + + fun onDelete(db: SQLiteDatabase) { + db.execSQL(DROP_TABLE_STATEMENT) + onCreate(db) + } + + fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) { + if (from == to) return + if (from < 4) { + // doesn't exist yet + onUpdate(db, from + 1, to) + } else if (from == 4) { + // table added in version 5 + onCreate(db) + onUpdate(db, from + 1, to) + } else if (from == 5) { + onUpdate(db, from + 1, to) + } else if (from == 17) { + db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN description TEXT;") + db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN thumbnail TEXT;") + onUpdate(db, from + 1, to) + } + } + + // For URI matcher + private const val CATEGORIES = 1 + private const val CATEGORIES_ID = 2 + private const val BASE_PATH = "categories" + val BASE_URI: Uri = Uri.parse("content://${BuildConfig.CATEGORY_AUTHORITY}/${Companion.BASE_PATH}") + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java deleted file mode 100644 index 3cd60ac81a..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.java +++ /dev/null @@ -1,209 +0,0 @@ -package fr.free.nrw.commons.category; - -import android.annotation.SuppressLint; -import android.content.ContentProviderClient; -import android.content.ContentValues; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.os.RemoteException; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.ArrayList; -import java.util.Date; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.inject.Provider; - -public class CategoryDao { - - private final Provider clientProvider; - - @Inject - public CategoryDao(@Named("category") Provider clientProvider) { - this.clientProvider = clientProvider; - } - - public void save(Category category) { - ContentProviderClient db = clientProvider.get(); - try { - if (category.getContentUri() == null) { - category.setContentUri(db.insert(CategoryContentProvider.BASE_URI, toContentValues(category))); - } else { - db.update(category.getContentUri(), toContentValues(category), null, null); - } - } catch (RemoteException e) { - throw new RuntimeException(e); - } finally { - db.release(); - } - } - - /** - * Find persisted category in database, based on its name. - * - * @param name Category's name - * @return category from database, or null if not found - */ - @Nullable - Category find(String name) { - Cursor cursor = null; - ContentProviderClient db = clientProvider.get(); - try { - cursor = db.query( - CategoryContentProvider.BASE_URI, - Table.ALL_FIELDS, - Table.COLUMN_NAME + "=?", - new String[]{name}, - null); - if (cursor != null && cursor.moveToFirst()) { - return fromCursor(cursor); - } - } catch (RemoteException e) { - // This feels lazy, but to hell with checked exceptions. :) - throw new RuntimeException(e); - } finally { - if (cursor != null) { - cursor.close(); - } - db.release(); - } - return null; - } - - /** - * Retrieve recently-used categories, ordered by descending date. - * - * @return a list containing recent categories - */ - @NonNull - List recentCategories(int limit) { - List items = new ArrayList<>(); - Cursor cursor = null; - ContentProviderClient db = clientProvider.get(); - try { - cursor = db.query( - CategoryContentProvider.BASE_URI, - Table.ALL_FIELDS, - null, - new String[]{}, - Table.COLUMN_LAST_USED + " DESC"); - // fixme add a limit on the original query instead of falling out of the loop? - while (cursor != null && cursor.moveToNext() - && cursor.getPosition() < limit) { - if (fromCursor(cursor).getName() != null ) { - items.add(new CategoryItem(fromCursor(cursor).getName(), - fromCursor(cursor).getDescription(), fromCursor(cursor).getThumbnail(), - false)); - } - } - } catch (RemoteException e) { - throw new RuntimeException(e); - } finally { - if (cursor != null) { - cursor.close(); - } - db.release(); - } - return items; - } - - @NonNull - @SuppressLint("Range") - Category fromCursor(Cursor cursor) { - // Hardcoding column positions! - return new Category( - CategoryContentProvider.uriForId(cursor.getInt(cursor.getColumnIndex(Table.COLUMN_ID))), - cursor.getString(cursor.getColumnIndex(Table.COLUMN_NAME)), - cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESCRIPTION)), - cursor.getString(cursor.getColumnIndex(Table.COLUMN_THUMBNAIL)), - new Date(cursor.getLong(cursor.getColumnIndex(Table.COLUMN_LAST_USED))), - cursor.getInt(cursor.getColumnIndex(Table.COLUMN_TIMES_USED)) - ); - } - - private ContentValues toContentValues(Category category) { - ContentValues cv = new ContentValues(); - cv.put(CategoryDao.Table.COLUMN_NAME, category.getName()); - cv.put(Table.COLUMN_DESCRIPTION, category.getDescription()); - cv.put(Table.COLUMN_THUMBNAIL, category.getThumbnail()); - cv.put(CategoryDao.Table.COLUMN_LAST_USED, category.getLastUsed().getTime()); - cv.put(CategoryDao.Table.COLUMN_TIMES_USED, category.getTimesUsed()); - return cv; - } - - public static class Table { - public static final String TABLE_NAME = "categories"; - - public static final String COLUMN_ID = "_id"; - static final String COLUMN_NAME = "name"; - static final String COLUMN_DESCRIPTION = "description"; - static final String COLUMN_THUMBNAIL = "thumbnail"; - static final String COLUMN_LAST_USED = "last_used"; - static final String COLUMN_TIMES_USED = "times_used"; - - // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES. - public static final String[] ALL_FIELDS = { - COLUMN_ID, - COLUMN_NAME, - COLUMN_DESCRIPTION, - COLUMN_THUMBNAIL, - COLUMN_LAST_USED, - COLUMN_TIMES_USED - }; - - static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME; - - static final String CREATE_TABLE_STATEMENT = "CREATE TABLE " + TABLE_NAME + " (" - + COLUMN_ID + " INTEGER PRIMARY KEY," - + COLUMN_NAME + " STRING," - + COLUMN_DESCRIPTION + " STRING," - + COLUMN_THUMBNAIL + " STRING," - + COLUMN_LAST_USED + " INTEGER," - + COLUMN_TIMES_USED + " INTEGER" - + ");"; - - public static void onCreate(SQLiteDatabase db) { - db.execSQL(CREATE_TABLE_STATEMENT); - } - - public static void onDelete(SQLiteDatabase db) { - db.execSQL(DROP_TABLE_STATEMENT); - onCreate(db); - } - - public static void onUpdate(SQLiteDatabase db, int from, int to) { - if (from == to) { - return; - } - if (from < 4) { - // doesn't exist yet - from++; - onUpdate(db, from, to); - return; - } - if (from == 4) { - // table added in version 5 - onCreate(db); - from++; - onUpdate(db, from, to); - return; - } - if (from == 5) { - from++; - onUpdate(db, from, to); - return; - } - if (from == 17) { - db.execSQL("ALTER TABLE categories ADD COLUMN description STRING;"); - db.execSQL("ALTER TABLE categories ADD COLUMN thumbnail STRING;"); - from++; - onUpdate(db, from, to); - return; - } - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.kt new file mode 100644 index 0000000000..3371da1840 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDao.kt @@ -0,0 +1,194 @@ +package fr.free.nrw.commons.category + +import android.annotation.SuppressLint +import android.content.ContentProviderClient +import android.content.ContentValues +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import android.os.RemoteException + +import java.util.ArrayList +import java.util.Date +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Provider + +class CategoryDao @Inject constructor( + @Named("category") private val clientProvider: Provider +) { + + fun save(category: Category) { + val db = clientProvider.get() + try { + if (category.contentUri == null) { + category.contentUri = db.insert( + CategoryContentProvider.BASE_URI, + toContentValues(category) + ) + } else { + db.update( + category.contentUri!!, + toContentValues(category), + null, + null + ) + } + } catch (e: RemoteException) { + throw RuntimeException(e) + } finally { + db.release() + } + } + + /** + * Find persisted category in database, based on its name. + * + * @param name Category's name + * @return category from database, or null if not found + */ + fun find(name: String): Category? { + var cursor: Cursor? = null + val db = clientProvider.get() + try { + cursor = db.query( + CategoryContentProvider.BASE_URI, + ALL_FIELDS, + "${COLUMN_NAME}=?", + arrayOf(name), + null + ) + if (cursor != null && cursor.moveToFirst()) { + return fromCursor(cursor) + } + } catch (e: RemoteException) { + throw RuntimeException(e) + } finally { + cursor?.close() + db.release() + } + return null + } + + /** + * Retrieve recently-used categories, ordered by descending date. + * + * @return a list containing recent categories + */ + fun recentCategories(limit: Int): List { + val items = ArrayList() + var cursor: Cursor? = null + val db = clientProvider.get() + try { + cursor = db.query( + CategoryContentProvider.BASE_URI, + ALL_FIELDS, + null, + emptyArray(), + "$COLUMN_LAST_USED DESC" + ) + while (cursor != null && cursor.moveToNext() && cursor.position < limit) { + val category = fromCursor(cursor) + if (category.name != null) { + items.add( + CategoryItem( + category.name, + category.description, + category.thumbnail, + false + ) + ) + } + } + } catch (e: RemoteException) { + throw RuntimeException(e) + } finally { + cursor?.close() + db.release() + } + return items + } + + @SuppressLint("Range") + fun fromCursor(cursor: Cursor): Category { + // Hardcoding column positions! + return Category( + CategoryContentProvider.uriForId(cursor.getInt(cursor.getColumnIndex(COLUMN_ID))), + cursor.getString(cursor.getColumnIndex(COLUMN_NAME)), + cursor.getString(cursor.getColumnIndex(COLUMN_DESCRIPTION)), + cursor.getString(cursor.getColumnIndex(COLUMN_THUMBNAIL)), + Date(cursor.getLong(cursor.getColumnIndex(COLUMN_LAST_USED))), + cursor.getInt(cursor.getColumnIndex(COLUMN_TIMES_USED)) + ) + } + + private fun toContentValues(category: Category): ContentValues { + return ContentValues().apply { + put(COLUMN_NAME, category.name) + put(COLUMN_DESCRIPTION, category.description) + put(COLUMN_THUMBNAIL, category.thumbnail) + put(COLUMN_LAST_USED, category.lastUsed?.time) + put(COLUMN_TIMES_USED, category.timesUsed) + } + } + + companion object Table { + const val TABLE_NAME = "categories" + + const val COLUMN_ID = "_id" + const val COLUMN_NAME = "name" + const val COLUMN_DESCRIPTION = "description" + const val COLUMN_THUMBNAIL = "thumbnail" + const val COLUMN_LAST_USED = "last_used" + const val COLUMN_TIMES_USED = "times_used" + + // NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES. + val ALL_FIELDS = arrayOf( + COLUMN_ID, + COLUMN_NAME, + COLUMN_DESCRIPTION, + COLUMN_THUMBNAIL, + COLUMN_LAST_USED, + COLUMN_TIMES_USED + ) + + const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS $TABLE_NAME" + + const val CREATE_TABLE_STATEMENT = "CREATE TABLE $TABLE_NAME (" + + "$COLUMN_ID INTEGER PRIMARY KEY," + + "$COLUMN_NAME STRING," + + "$COLUMN_DESCRIPTION STRING," + + "$COLUMN_THUMBNAIL STRING," + + "$COLUMN_LAST_USED INTEGER," + + "$COLUMN_TIMES_USED INTEGER" + + ");" + + @SuppressLint("SQLiteString") + fun onCreate(db: SQLiteDatabase) { + db.execSQL(CREATE_TABLE_STATEMENT) + } + + fun onDelete(db: SQLiteDatabase) { + db.execSQL(DROP_TABLE_STATEMENT) + onCreate(db) + } + + @SuppressLint("SQLiteString") + fun onUpdate(db: SQLiteDatabase, from: Int, to: Int) { + if (from == to) return + if (from < 4) { + // doesn't exist yet + onUpdate(db, from + 1, to) + } else if (from == 4) { + // table added in version 5 + onCreate(db) + onUpdate(db, from + 1, to) + } else if (from == 5) { + onUpdate(db, from + 1, to) + } else if (from == 17) { + db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN description STRING;") + db.execSQL("ALTER TABLE $TABLE_NAME ADD COLUMN thumbnail STRING;") + onUpdate(db, from + 1, to) + } + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java deleted file mode 100644 index 457bd48c6a..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.java +++ /dev/null @@ -1,236 +0,0 @@ -package fr.free.nrw.commons.category; - -import static fr.free.nrw.commons.category.CategoryClientKt.CATEGORY_PREFIX; - -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.widget.FrameLayout; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.viewpager.widget.ViewPager; -import com.google.android.material.tabs.TabLayout; -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; -import fr.free.nrw.commons.Utils; -import fr.free.nrw.commons.ViewPagerAdapter; -import fr.free.nrw.commons.databinding.ActivityCategoryDetailsBinding; -import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment; -import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesFragment; -import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment; -import fr.free.nrw.commons.media.MediaDetailPagerFragment; -import fr.free.nrw.commons.theme.BaseActivity; -import java.util.ArrayList; -import java.util.List; -import fr.free.nrw.commons.wikidata.model.page.PageTitle; - -/** - * This activity displays details of a particular category - * Its generic and simply takes the name of category name in its start intent to load all images, subcategories in - * a particular category on wikimedia commons. - */ - -public class CategoryDetailsActivity extends BaseActivity - implements MediaDetailPagerFragment.MediaDetailProvider, CategoryImagesCallback { - - - private FragmentManager supportFragmentManager; - private CategoriesMediaFragment categoriesMediaFragment; - private MediaDetailPagerFragment mediaDetails; - private String categoryName; - ViewPagerAdapter viewPagerAdapter; - - private ActivityCategoryDetailsBinding binding; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - binding = ActivityCategoryDetailsBinding.inflate(getLayoutInflater()); - final View view = binding.getRoot(); - setContentView(view); - supportFragmentManager = getSupportFragmentManager(); - viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager()); - binding.viewPager.setAdapter(viewPagerAdapter); - binding.viewPager.setOffscreenPageLimit(2); - binding.tabLayout.setupWithViewPager(binding.viewPager); - setSupportActionBar(binding.toolbarBinding.toolbar); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - setTabs(); - setPageTitle(); - } - - /** - * This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab, - * Set the fragments according to the tab selected in the viewPager. - */ - private void setTabs() { - List fragmentList = new ArrayList<>(); - List titleList = new ArrayList<>(); - categoriesMediaFragment = new CategoriesMediaFragment(); - SubCategoriesFragment subCategoryListFragment = new SubCategoriesFragment(); - ParentCategoriesFragment parentCategoriesFragment = new ParentCategoriesFragment(); - categoryName = getIntent().getStringExtra("categoryName"); - if (getIntent() != null && categoryName != null) { - Bundle arguments = new Bundle(); - arguments.putString("categoryName", categoryName); - categoriesMediaFragment.setArguments(arguments); - subCategoryListFragment.setArguments(arguments); - parentCategoriesFragment.setArguments(arguments); - } - fragmentList.add(categoriesMediaFragment); - titleList.add("MEDIA"); - fragmentList.add(subCategoryListFragment); - titleList.add("SUBCATEGORIES"); - fragmentList.add(parentCategoriesFragment); - titleList.add("PARENT CATEGORIES"); - viewPagerAdapter.setTabData(fragmentList, titleList); - viewPagerAdapter.notifyDataSetChanged(); - - } - - /** - * Gets the passed categoryName from the intents and displays it as the page title - */ - private void setPageTitle() { - if (getIntent() != null && getIntent().getStringExtra("categoryName") != null) { - setTitle(getIntent().getStringExtra("categoryName")); - } - } - - /** - * This method is called onClick of media inside category details (CategoryImageListFragment). - */ - @Override - public void onMediaClicked(int position) { - binding.tabLayout.setVisibility(View.GONE); - binding.viewPager.setVisibility(View.GONE); - binding.mediaContainer.setVisibility(View.VISIBLE); - if (mediaDetails == null || !mediaDetails.isVisible()) { - // set isFeaturedImage true for featured images, to include author field on media detail - mediaDetails = MediaDetailPagerFragment.newInstance(false, true); - FragmentManager supportFragmentManager = getSupportFragmentManager(); - supportFragmentManager - .beginTransaction() - .replace(R.id.mediaContainer, mediaDetails) - .addToBackStack(null) - .commit(); - supportFragmentManager.executePendingTransactions(); - } - mediaDetails.showImage(position); - } - - - /** - * Consumers should be simply using this method to use this activity. - * @param context A Context of the application package implementing this class. - * @param categoryName Name of the category for displaying its details - */ - public static void startYourself(Context context, String categoryName) { - Intent intent = new Intent(context, CategoryDetailsActivity.class); - intent.putExtra("categoryName", categoryName); - context.startActivity(intent); - } - - /** - * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index - * @param i It is the index of which media object is to be returned which is same as - * current index of viewPager. - * @return Media Object - */ - @Override - public Media getMediaAtPosition(int i) { - return categoriesMediaFragment.getMediaAtPosition(i); - } - - /** - * This method is called on from getCount of MediaDetailPagerFragment - * The viewpager will contain same number of media items as that of media elements in adapter. - * @return Total Media count in the adapter - */ - @Override - public int getTotalMediaCount() { - return categoriesMediaFragment.getTotalMediaCount(); - } - - @Override - public Integer getContributionStateAt(int position) { - return null; - } - - /** - * Reload media detail fragment once media is nominated - * - * @param index item position that has been nominated - */ - @Override - public void refreshNominatedMedia(int index) { - if (getSupportFragmentManager().getBackStackEntryCount() == 1) { - onBackPressed(); - onMediaClicked(index); - } - } - - /** - * This method inflates the menu in the toolbar - */ - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.fragment_category_detail, menu); - return super.onCreateOptionsMenu(menu); - } - - /** - * This method handles the logic on ItemSelect in toolbar menu - * Currently only 1 choice is available to open category details page in browser - */ - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - // Handle item selection - switch (item.getItemId()) { - case R.id.menu_browser_current_category: - PageTitle title = Utils.getPageTitle(CATEGORY_PREFIX + categoryName); - Utils.handleWebUrl(this, Uri.parse(title.getCanonicalUri())); - return true; - case android.R.id.home: - onBackPressed(); - return true; - default: - return super.onOptionsItemSelected(item); - } - } - - /** - * This method is called on backPressed of anyFragment in the activity. - * If condition is called when mediaDetailFragment is opened. - */ - @Override - public void onBackPressed() { - if (supportFragmentManager.getBackStackEntryCount() == 1){ - binding.tabLayout.setVisibility(View.VISIBLE); - binding.viewPager.setVisibility(View.VISIBLE); - binding.mediaContainer.setVisibility(View.GONE); - } - super.onBackPressed(); - } - - /** - * This method is called on success of API call for Images inside a category. - * The viewpager will notified that number of items have changed. - */ - @Override - public void viewPagerNotifyDataSetChanged() { - if (mediaDetails!=null){ - mediaDetails.notifyDataSetChanged(); - } - } - -} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt new file mode 100644 index 0000000000..ba1fcfdae2 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryDetailsActivity.kt @@ -0,0 +1,216 @@ +package fr.free.nrw.commons.category + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R +import fr.free.nrw.commons.Utils +import fr.free.nrw.commons.ViewPagerAdapter +import fr.free.nrw.commons.databinding.ActivityCategoryDetailsBinding +import fr.free.nrw.commons.explore.categories.media.CategoriesMediaFragment +import fr.free.nrw.commons.explore.categories.parent.ParentCategoriesFragment +import fr.free.nrw.commons.explore.categories.sub.SubCategoriesFragment +import fr.free.nrw.commons.media.MediaDetailPagerFragment +import fr.free.nrw.commons.theme.BaseActivity + + +/** + * This activity displays details of a particular category + * Its generic and simply takes the name of category name in its start intent to load all images, subcategories in + * a particular category on wikimedia commons. + */ +class CategoryDetailsActivity : BaseActivity(), + MediaDetailPagerFragment.MediaDetailProvider, + CategoryImagesCallback { + + private lateinit var supportFragmentManager: FragmentManager + private lateinit var categoriesMediaFragment: CategoriesMediaFragment + private var mediaDetails: MediaDetailPagerFragment? = null + private var categoryName: String? = null + private lateinit var viewPagerAdapter: ViewPagerAdapter + + private lateinit var binding: ActivityCategoryDetailsBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = ActivityCategoryDetailsBinding.inflate(layoutInflater) + val view = binding.root + setContentView(view) + supportFragmentManager = getSupportFragmentManager() + viewPagerAdapter = ViewPagerAdapter(supportFragmentManager) + binding.viewPager.adapter = viewPagerAdapter + binding.viewPager.offscreenPageLimit = 2 + binding.tabLayout.setupWithViewPager(binding.viewPager) + setSupportActionBar(binding.toolbarBinding.toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + setTabs() + setPageTitle() + } + + /** + * This activity contains 3 tabs and a viewpager. This method is used to set the titles of tab, + * Set the fragments according to the tab selected in the viewPager. + */ + private fun setTabs() { + val fragmentList = mutableListOf() + val titleList = mutableListOf() + categoriesMediaFragment = CategoriesMediaFragment() + val subCategoryListFragment = SubCategoriesFragment() + val parentCategoriesFragment = ParentCategoriesFragment() + categoryName = intent?.getStringExtra("categoryName") + if (intent != null && categoryName != null) { + val arguments = Bundle().apply { + putString("categoryName", categoryName) + } + categoriesMediaFragment.arguments = arguments + subCategoryListFragment.arguments = arguments + parentCategoriesFragment.arguments = arguments + } + fragmentList.add(categoriesMediaFragment) + titleList.add("MEDIA") + fragmentList.add(subCategoryListFragment) + titleList.add("SUBCATEGORIES") + fragmentList.add(parentCategoriesFragment) + titleList.add("PARENT CATEGORIES") + viewPagerAdapter.setTabData(fragmentList, titleList) + viewPagerAdapter.notifyDataSetChanged() + } + + /** + * Gets the passed categoryName from the intents and displays it as the page title + */ + private fun setPageTitle() { + intent?.getStringExtra("categoryName")?.let { + title = it + } + } + + /** + * This method is called onClick of media inside category details (CategoryImageListFragment). + */ + override fun onMediaClicked(position: Int) { + binding.tabLayout.visibility = View.GONE + binding.viewPager.visibility = View.GONE + binding.mediaContainer.visibility = View.VISIBLE + if (mediaDetails == null || mediaDetails?.isVisible == false) { + // set isFeaturedImage true for featured images, to include author field on media detail + mediaDetails = MediaDetailPagerFragment.newInstance(false, true) + supportFragmentManager.beginTransaction() + .replace(R.id.mediaContainer, mediaDetails!!) + .addToBackStack(null) + .commit() + supportFragmentManager.executePendingTransactions() + } + mediaDetails?.showImage(position) + } + + + companion object { + /** + * Consumers should be simply using this method to use this activity. + * @param context A Context of the application package implementing this class. + * @param categoryName Name of the category for displaying its details + */ + fun startYourself(context: Context?, categoryName: String) { + val intent = Intent(context, CategoryDetailsActivity::class.java).apply { + putExtra("categoryName", categoryName) + } + context?.startActivity(intent) + } + } + + /** + * This method is called mediaDetailPagerFragment. It returns the Media Object at that Index + * @param i It is the index of which media object is to be returned which is same as + * current index of viewPager. + * @return Media Object + */ + override fun getMediaAtPosition(i: Int): Media? { + return categoriesMediaFragment.getMediaAtPosition(i) + } + + /** + * This method is called on from getCount of MediaDetailPagerFragment + * The viewpager will contain same number of media items as that of media elements in adapter. + * @return Total Media count in the adapter + */ + override fun getTotalMediaCount(): Int { + return categoriesMediaFragment.getTotalMediaCount() + } + + override fun getContributionStateAt(position: Int): Int? { + return null + } + + /** + * Reload media detail fragment once media is nominated + * + * @param index item position that has been nominated + */ + override fun refreshNominatedMedia(index: Int) { + if (supportFragmentManager.backStackEntryCount == 1) { + onBackPressed() + onMediaClicked(index) + } + } + + /** + * This method inflates the menu in the toolbar + */ + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.fragment_category_detail, menu) + return super.onCreateOptionsMenu(menu) + } + + /** + * This method handles the logic on ItemSelect in toolbar menu + * Currently only 1 choice is available to open category details page in browser + */ + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.menu_browser_current_category -> { + val title = Utils.getPageTitle(CATEGORY_PREFIX + categoryName) + Utils.handleWebUrl(this, Uri.parse(title.canonicalUri)) + true + } + android.R.id.home -> { + onBackPressed() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + /** + * This method is called on backPressed of anyFragment in the activity. + * If condition is called when mediaDetailFragment is opened. + */ + @Deprecated("This method has been deprecated in favor of using the" + + "{@link OnBackPressedDispatcher} via {@link #getOnBackPressedDispatcher()}." + + "The OnBackPressedDispatcher controls how back button events are dispatched" + + "to one or more {@link OnBackPressedCallback} objects.") + override fun onBackPressed() { + if (supportFragmentManager.backStackEntryCount == 1) { + binding.tabLayout.visibility = View.VISIBLE + binding.viewPager.visibility = View.VISIBLE + binding.mediaContainer.visibility = View.GONE + } + super.onBackPressed() + } + + /** + * This method is called on success of API call for Images inside a category. + * The viewpager will notified that number of items have changed. + */ + override fun viewPagerNotifyDataSetChanged() { + mediaDetails?.notifyDataSetChanged() + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryEditHelper.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryEditHelper.java deleted file mode 100644 index 393a8dba49..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryEditHelper.java +++ /dev/null @@ -1,123 +0,0 @@ -package fr.free.nrw.commons.category; - -import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_EDIT_CATEGORY; - -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 java.util.List; -import javax.inject.Inject; -import javax.inject.Named; -import timber.log.Timber; - -public class CategoryEditHelper { - private final NotificationHelper notificationHelper; - public final PageEditClient pageEditClient; - private final ViewUtilWrapper viewUtil; - private final String username; - - @Inject - public CategoryEditHelper(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 edit categories - * @param context - * @param media - * @param categories - * @return - */ - public Single makeCategoryEdit(Context context, Media media, List categories, - final String wikiText) { - viewUtil.showShortToast(context, context.getString(R.string.category_edit_helper_make_edit_toast)); - return addCategory(media, categories, wikiText) - .flatMapSingle(result -> Single.just(showCategoryEditNotification(context, media, result))) - .firstOrError(); - } - - /** - * Rebuilds the WikiText with new categpries and post it on server - * - * @param media - * @param categories to be added - * @return - */ - private Observable addCategory(Media media, List categories, - final String wikiText) { - Timber.d("thread is category adding %s", Thread.currentThread().getName()); - String summary = "Adding categories"; - final StringBuilder buffer = new StringBuilder(); - final String wikiTextWithoutCategory; - //If the picture was uploaded without a category, the wikitext will contain "Uncategorized" instead of "[[Category" - if (wikiText.contains("Uncategorized")) { - wikiTextWithoutCategory = wikiText.substring(0, wikiText.indexOf("Uncategorized")); - } else if (wikiText.contains("[[Category")) { - wikiTextWithoutCategory = wikiText.substring(0, wikiText.indexOf("[[Category")); - } else { - wikiTextWithoutCategory = ""; - } - if (categories != null && !categories.isEmpty()) { - //If the categories list is empty, when reading the categories of a picture, - // the code will add "None selected" to categories list in order to see in picture's categories with "None selected". - // So that after selected some category,"None selected" should be removed from list - for (int i = 0; i < categories.size(); i++) { - if (!categories.get(i).equals("None selected")//Not to add "None selected" as category to wikiText - || !wikiText.contains("Uncategorized")) { - buffer.append("[[Category:").append(categories.get(i)).append("]]\n"); - } - } - categories.remove("None selected"); - } else { - buffer.append("{{subst:unc}}"); - } - final String appendText = wikiTextWithoutCategory + buffer; - return pageEditClient.edit(media.getFilename(), appendText + "\n", summary); - } - - private boolean showCategoryEditNotification(Context context, Media media, boolean result) { - String message; - String title = context.getString(R.string.category_edit_helper_show_edit_title); - - if (result) { - title += ": " + context.getString(R.string.category_edit_helper_show_edit_title_success); - StringBuilder categoriesInMessage = new StringBuilder(); - List mediaCategoryList = media.getCategories(); - for (String category : mediaCategoryList) { - categoriesInMessage.append(category); - if (category.equals(mediaCategoryList.get(mediaCategoryList.size()-1))) { - continue; - } - categoriesInMessage.append(","); - } - - message = context.getResources().getQuantityString(R.plurals.category_edit_helper_show_edit_message_if, mediaCategoryList.size(), categoriesInMessage.toString()); - } else { - title += ": " + context.getString(R.string.category_edit_helper_show_edit_title); - message = context.getString(R.string.category_edit_helper_edit_message_else) ; - } - - String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename(); - Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile)); - notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_CATEGORY, browserIntent); - return result; - } - - public interface Callback { - boolean updateCategoryDisplay(List categories); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryEditHelper.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryEditHelper.kt new file mode 100644 index 0000000000..22cb191723 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryEditHelper.kt @@ -0,0 +1,144 @@ +package fr.free.nrw.commons.category + +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 javax.inject.Inject +import javax.inject.Named +import timber.log.Timber + + +class CategoryEditHelper @Inject constructor( + private val notificationHelper: NotificationHelper, + @Named("commons-page-edit") val pageEditClient: PageEditClient, + private val viewUtil: ViewUtilWrapper, + @Named("username") private val username: String +) { + + /** + * Public interface to edit categories + * @param context + * @param media + * @param categories + * @return + */ + fun makeCategoryEdit( + context: Context, + media: Media, + categories: List, + wikiText: String + ): Single { + viewUtil.showShortToast( + context, + context.getString(R.string.category_edit_helper_make_edit_toast) + ) + return addCategory(media, categories, wikiText) + .flatMapSingle { result -> + Single.just(showCategoryEditNotification(context, media, result)) + } + .firstOrError() + } + + /** + * Rebuilds the WikiText with new categories and post it on server + * + * @param media + * @param categories to be added + * @return + */ + private fun addCategory( + media: Media, + categories: List?, + wikiText: String + ): Observable { + Timber.d("thread is category adding %s", Thread.currentThread().name) + val summary = "Adding categories" + val buffer = StringBuilder() + + // If the picture was uploaded without a category, the wikitext will contain "Uncategorized" instead of "[[Category" + val wikiTextWithoutCategory: String = when { + wikiText.contains("Uncategorized") -> wikiText.substring(0, wikiText.indexOf("Uncategorized")) + wikiText.contains("[[Category") -> wikiText.substring(0, wikiText.indexOf("[[Category")) + else -> "" + } + + if (!categories.isNullOrEmpty()) { + // If the categories list is empty, when reading the categories of a picture, + // the code will add "None selected" to categories list in order to see in picture's categories with "None selected". + // So that after selecting some category, "None selected" should be removed from list + for (category in categories) { + if (category != "None selected" || !wikiText.contains("Uncategorized")) { + buffer.append("[[Category:").append(category).append("]]\n") + } + } + categories.dropWhile { + it == "None selected" + } + } else { + buffer.append("{{subst:unc}}") + } + + val appendText = wikiTextWithoutCategory + buffer + return pageEditClient.edit(media.filename!!, "$appendText\n", summary) + } + + private fun showCategoryEditNotification( + context: Context, + media: Media, + result: Boolean + ): Boolean { + val title: String + val message: String + + if (result) { + title = context.getString(R.string.category_edit_helper_show_edit_title) + ": " + + context.getString(R.string.category_edit_helper_show_edit_title_success) + + val categoriesInMessage = StringBuilder() + val mediaCategoryList = media.categories + for ((index, category) in mediaCategoryList?.withIndex()!!) { + categoriesInMessage.append(category) + if (index != mediaCategoryList.size - 1) { + categoriesInMessage.append(",") + } + } + + message = context.resources.getQuantityString( + R.plurals.category_edit_helper_show_edit_message_if, + mediaCategoryList.size, + categoriesInMessage.toString() + ) + } else { + title = context.getString(R.string.category_edit_helper_show_edit_title) + ": " + + context.getString(R.string.category_edit_helper_show_edit_title) + message = context.getString(R.string.category_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_CATEGORY, + browserIntent + ) + return result + } + + interface Callback { + fun updateCategoryDisplay(categories: List?): Boolean + } + + companion object { + const val NOTIFICATION_EDIT_CATEGORY = 1 + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesCallback.java b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesCallback.java deleted file mode 100644 index 5b85a2f814..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesCallback.java +++ /dev/null @@ -1,13 +0,0 @@ -package fr.free.nrw.commons.category; - -/** - * Callback for notifying the viewpager that the number of items have changed - * and for requesting more images when the viewpager has been scrolled to its end. - */ - -public interface CategoryImagesCallback { - void viewPagerNotifyDataSetChanged(); - void onMediaClicked(int position); -} - - diff --git a/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesCallback.kt b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesCallback.kt new file mode 100644 index 0000000000..9fe811f74e --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/CategoryImagesCallback.kt @@ -0,0 +1,7 @@ +package fr.free.nrw.commons.category + +interface CategoryImagesCallback { + fun viewPagerNotifyDataSetChanged() + + fun onMediaClicked(position: Int) +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java b/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java deleted file mode 100644 index af28ad07d1..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.java +++ /dev/null @@ -1,119 +0,0 @@ -package fr.free.nrw.commons.category; - -import android.content.Context; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ArrayAdapter; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import com.facebook.drawee.view.SimpleDraweeView; - -import java.util.ArrayList; -import java.util.List; - -import fr.free.nrw.commons.Media; -import fr.free.nrw.commons.R; - -/** - * This is created to only display UI implementation. Needs to be changed in real implementation - */ - -public class GridViewAdapter extends ArrayAdapter { - private List data; - - public GridViewAdapter(Context context, int layoutResourceId, List data) { - super(context, layoutResourceId, data); - this.data = data; - } - - /** - * Adds more item to the list - * Its triggered on scrolling down in the list - * @param images - */ - public void addItems(List images) { - if (data == null) { - data = new ArrayList<>(); - } - data.addAll(images); - notifyDataSetChanged(); - } - - /** - * Check the first item in the new list with old list and returns true if they are same - * Its triggered on successful response of the fetch images API. - * @param images - */ - public boolean containsAll(List images){ - if (images == null || images.isEmpty()) { - return false; - } - if (data == null) { - data = new ArrayList<>(); - return false; - } - if (data.isEmpty()) { - return false; - } - String fileName = data.get(0).getFilename(); - String imageName = images.get(0).getFilename(); - return imageName.equals(fileName); - } - - @Override - public boolean isEmpty() { - return data == null || data.isEmpty(); - } - - /** - * Sets up the UI for the category image item - * @param position - * @param convertView - * @param parent - * @return - */ - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - if (convertView == null) { - convertView = LayoutInflater.from(getContext()).inflate(R.layout.layout_category_images, null); - } - - Media item = data.get(position); - SimpleDraweeView imageView = convertView.findViewById(R.id.categoryImageView); - TextView fileName = convertView.findViewById(R.id.categoryImageTitle); - TextView uploader = convertView.findViewById(R.id.categoryImageAuthor); - fileName.setText(item.getMostRelevantCaption()); - setUploaderView(item, uploader); - imageView.setImageURI(item.getThumbUrl()); - return convertView; - } - - /** - * @return the Media item at the given position - */ - @Nullable - @Override - public Media getItem(int position) { - return data.get(position); - } - - - /** - * Shows author information if its present - * @param item - * @param uploader - */ - private void setUploaderView(Media item, TextView uploader) { - if (!TextUtils.isEmpty(item.getAuthor())) { - uploader.setVisibility(View.VISIBLE); - uploader.setText(getContext().getString(R.string.image_uploaded_by, item.getUser())); - } else { - uploader.setVisibility(View.GONE); - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.kt b/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.kt new file mode 100644 index 0000000000..5dbcc59fda --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/GridViewAdapter.kt @@ -0,0 +1,111 @@ +package fr.free.nrw.commons.category + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.TextView +import com.facebook.drawee.view.SimpleDraweeView +import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R + + +/** + * This is created to only display UI implementation. Needs to be changed in real implementation + */ +class GridViewAdapter( + context: Context, + layoutResourceId: Int, + private var data: MutableList? +) : ArrayAdapter(context, layoutResourceId, data ?: mutableListOf()) { + + /** + * Adds more items to the list + * It's triggered on scrolling down in the list + * @param images + */ + fun addItems(images: List) { + if (data == null) { + data = mutableListOf() + } + data?.addAll(images) + notifyDataSetChanged() + } + + /** + * Checks the first item in the new list with the old list and returns true if they are the same + * It's triggered on a successful response of the fetch images API. + * @param images + */ + fun containsAll(images: List?): Boolean { + if (images.isNullOrEmpty()) { + return false + } + if (data.isNullOrEmpty()) { + data = mutableListOf() + return false + } + val fileName = data?.get(0)?.filename + val imageName = images[0].filename + return imageName == fileName + } + + override fun isEmpty(): Boolean { + return data.isNullOrEmpty() + } + + /** + * Sets up the UI for the category image item + * @param position + * @param convertView + * @param parent + * @return + */ + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + val view = convertView ?: LayoutInflater.from(context).inflate( + R.layout.layout_category_images, + parent, + false + ) + + val item = data?.get(position) + val imageView = view.findViewById(R.id.categoryImageView) + val fileName = view.findViewById(R.id.categoryImageTitle) + val uploader = view.findViewById(R.id.categoryImageAuthor) + + item?.let { + fileName.text = it.mostRelevantCaption + setUploaderView(it, uploader) + imageView.setImageURI(it.thumbUrl) + } + + return view + } + + /** + * @return the Media item at the given position + */ + override fun getItem(position: Int): Media? { + return data?.get(position) + } + + /** + * Shows author information if it's present + * @param item + * @param uploader + */ + @SuppressLint("StringFormatInvalid") + private fun setUploaderView(item: Media, uploader: TextView) { + if (!item.author.isNullOrEmpty()) { + uploader.visibility = View.VISIBLE + uploader.text = context.getString( + R.string.image_uploaded_by, + item.user + ) + } else { + uploader.visibility = View.GONE + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/category/OnCategoriesSaveHandler.java b/app/src/main/java/fr/free/nrw/commons/category/OnCategoriesSaveHandler.java deleted file mode 100644 index 5899d59051..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/category/OnCategoriesSaveHandler.java +++ /dev/null @@ -1,7 +0,0 @@ -package fr.free.nrw.commons.category; - -import java.util.List; - -public interface OnCategoriesSaveHandler { - void onCategoriesSave(List categories); -} diff --git a/app/src/main/java/fr/free/nrw/commons/category/OnCategoriesSaveHandler.kt b/app/src/main/java/fr/free/nrw/commons/category/OnCategoriesSaveHandler.kt new file mode 100644 index 0000000000..68200992c7 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/category/OnCategoriesSaveHandler.kt @@ -0,0 +1,5 @@ +package fr.free.nrw.commons.category + +interface OnCategoriesSaveHandler { + fun onCategoriesSave(categories: List) +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java index e910799d07..551852451c 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionController.java @@ -170,8 +170,8 @@ public void handleShowRationaleFlowCameraLocation(Activity activity, }, () -> locationPermissionCallback.onLocationPermissionDenied( activity.getString(R.string.in_app_camera_location_permission_denied)), - null, - false); + null + ); } /** @@ -203,8 +203,8 @@ private void askUserToAllowLocationAccess(Activity activity, defaultKvStore.putBoolean("inAppCameraLocationPref", false); initiateCameraUpload(activity, resultLauncher); }, - null, - true); + null + ); } /** diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java index bffafaef15..f4ff89dfcd 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java @@ -566,8 +566,8 @@ private void showNearbyCardPermissionRationale() { getString(R.string.nearby_card_permission_explanation), this::requestLocationPermission, this::displayYouWontSeeNearbyMessage, - checkBoxView, - false); + checkBoxView + ); } private void displayYouWontSeeNearbyMessage() { diff --git a/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.java b/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.java deleted file mode 100644 index 8b62093425..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.java +++ /dev/null @@ -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 - */ - public Single 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 - */ - private Observable 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; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.kt b/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.kt new file mode 100644 index 0000000000..3095497c36 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/coordinates/CoordinateEditHelper.kt @@ -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 + */ + fun makeCoordinatesEdit( + context: Context, + media: Media, + latitude: String, + longitude: String, + accuracy: String + ): Single? { + 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 + */ + private fun addCoordinates( + media: Media, + Latitude: String, + Longitude: String, + Accuracy: String + ): Observable? { + 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 + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java deleted file mode 100644 index 7ee417fbc1..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.java +++ /dev/null @@ -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(); - } - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt new file mode 100644 index 0000000000..83f7687d49 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt @@ -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() + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/db/Converters.java b/app/src/main/java/fr/free/nrw/commons/db/Converters.java deleted file mode 100644 index c0f85420f5..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/db/Converters.java +++ /dev/null @@ -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() { - }); - } - - @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 objectList) { - return writeObjectToString(objectList); - } - - @TypeConverter - public static List stringToListObject(String objectList) { - return readObjectWithTypeToken(objectList, new TypeToken>() {}); - } - - @TypeConverter - public static String mapObjectToString(Map objectList) { - return writeObjectToString(objectList); - } - - @TypeConverter - public static String mapObjectToString2(Map objectList) { - return writeObjectToString(objectList); - } - - @TypeConverter - public static Map stringToMap(String objectList) { - return readObjectWithTypeToken(objectList, new TypeToken>(){}); - } - - @TypeConverter - public static Map stringToMap2(String objectList) { - return readObjectWithTypeToken(objectList, new TypeToken>(){}); - } - - @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 depictedItems) { - return writeObjectToString(depictedItems); - } - - @TypeConverter - public static List stringToList(String depictedItems) { - return readObjectWithTypeToken(depictedItems, new TypeToken>() {}); - } - - @TypeConverter - public static Sitelinks sitelinksFromString(String value) { - Type type = new TypeToken() {}.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 readObjectFromString(String objectAsString, Class clazz) { - return objectAsString == null ? null : getGson().fromJson(objectAsString, clazz); - } - - private static T readObjectWithTypeToken(String objectList, TypeToken typeToken) { - return objectList == null ? null : getGson().fromJson(objectList, typeToken.getType()); - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/db/Converters.kt b/app/src/main/java/fr/free/nrw/commons/db/Converters.kt new file mode 100644 index 0000000000..43b3a61841 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/db/Converters.kt @@ -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() {}) + } + + @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? { + return writeObjectToString(objectList) + } + + @TypeConverter + @JvmStatic + fun stringToListObject(objectList: String?): List? { + return readObjectWithTypeToken(objectList, object : TypeToken>() {}) + } + + @TypeConverter + @JvmStatic + fun mapObjectToString(objectList: Map?): String? { + return writeObjectToString(objectList) + } + + @TypeConverter + @JvmStatic + fun mapObjectToString2(objectList: Map?): String? { + return writeObjectToString(objectList) + } + + @TypeConverter + @JvmStatic + fun stringToMap(objectList: String?): Map? { + return readObjectWithTypeToken(objectList, object : TypeToken>() {}) + } + + @TypeConverter + @JvmStatic + fun stringToMap2(objectList: String?): Map? { + return readObjectWithTypeToken(objectList, object : TypeToken>() {}) + } + + @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?): String? { + return writeObjectToString(depictedItems) + } + + @TypeConverter + @JvmStatic + fun stringToList(depictedItems: String?): List? { + return readObjectWithTypeToken(depictedItems, object : TypeToken>() {}) + } + + @TypeConverter + @JvmStatic + fun sitelinksFromString(value: String?): Sitelinks? { + val type = object : TypeToken() {}.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 readObjectFromString(objectAsString: String?, clazz: Class): T? { + return objectAsString?.let { getGson().fromJson(it, clazz) } + } + + private fun readObjectWithTypeToken(objectList: String?, typeToken: TypeToken): T? { + return objectList?.let { getGson().fromJson(it, typeToken.type) } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.java b/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.java deleted file mode 100644 index 134ee48d9d..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.java +++ /dev/null @@ -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 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 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 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>) () -> - 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; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.kt b/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.kt new file mode 100644 index 0000000000..be0b2bd798 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.kt @@ -0,0 +1,334 @@ +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? { + + 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 { + 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.setCancelable(false) + alert.setTitle(question) + + val checkedItems = booleanArrayOf(false, false, false, false) + val mUserReason = arrayListOf() + + val reasonList: Array + val reasonListEnglish: Array + + 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 +} \ No newline at end of file diff --git a/app/src/main/java/fr/free/nrw/commons/delete/ReasonBuilder.java b/app/src/main/java/fr/free/nrw/commons/delete/ReasonBuilder.java deleted file mode 100644 index 7912375a4d..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/delete/ReasonBuilder.java +++ /dev/null @@ -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 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 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; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/delete/ReasonBuilder.kt b/app/src/main/java/fr/free/nrw/commons/delete/ReasonBuilder.kt new file mode 100644 index 0000000000..09018c249b --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/delete/ReasonBuilder.kt @@ -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 { + 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 { + 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 + } + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt index fa4349dbf4..32c1e58291 100644 --- a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditActivity.kt @@ -149,8 +149,7 @@ class DescriptionEditActivity : getString(titleStringID), getString(messageStringId), getString(android.R.string.ok), - null, - true, + null ) } @@ -272,7 +271,7 @@ class DescriptionEditActivity : .addCaption( applicationContext, media, - mediaDetail.languageCode, + mediaDetail.languageCode!!, mediaDetail.captionText, ).subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) @@ -304,7 +303,7 @@ class DescriptionEditActivity : progressDialog!!.isIndeterminate = true progressDialog!!.setTitle(getString(R.string.updating_caption_title)) progressDialog!!.setMessage(getString(R.string.updating_caption_message)) - progressDialog!!.setCanceledOnTouchOutside(false) + progressDialog!!.setCancelable(false) progressDialog!!.show() } diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditHelper.java b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditHelper.java deleted file mode 100644 index 05f6e9f2de..0000000000 --- a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditHelper.java +++ /dev/null @@ -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 - */ - public Single 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 - */ - public Single 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; - } -} diff --git a/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditHelper.kt b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditHelper.kt new file mode 100644 index 0000000000..5d948ddf9a --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/description/DescriptionEditHelper.kt @@ -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 + */ + fun addDescription(context: Context, media: Media, appendText: String): Single { + 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 + */ + fun addCaption( + context: Context, + media: Media, + language: String, + value: String + ): Single { + 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 + } +} diff --git a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java index 441f46e610..76627ebcfd 100644 --- a/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/explore/map/ExploreMapFragment.java @@ -133,8 +133,8 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment askForLocationPermission(); }, null, - null, - false); + null + ); } else { if (isPermissionDenied) { locationPermissionsHelper.showAppSettingsDialog(getActivity(), diff --git a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt index 771d9efdcf..fefb59adb6 100644 --- a/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt +++ b/app/src/main/java/fr/free/nrw/commons/location/LocationPermissionsHelper.kt @@ -78,8 +78,7 @@ class LocationPermissionsHelper( activity.getString(R.string.upload_map_location_access) ) }, - null, - false + null ) } else { ActivityCompat.requestPermissions( diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt index 32785fd425..bf3a1ab386 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailFragment.kt @@ -1596,8 +1596,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C getString(R.string.about_translate_cancel), { onDeleteClicked(spinner) }, {}, - spinner, - true + spinner ) if (isDeleted) { dialog!!.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = false @@ -1616,8 +1615,7 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C onDeleteClickeddialogtext(reason) }, {}, - input, - true + input ) input.addTextChangedListener(object : TextWatcher { fun handleText() { diff --git a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java index f6ef824e7e..8f52b1ced5 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/media/MediaDetailPagerFragment.java @@ -283,6 +283,7 @@ private void showReportDialog(final Media media) { builder.setItems(R.array.report_violation_options, (dialog, which) -> { sendReportEmail(media, values[which]); }); + builder.setCancelable(false); builder.show(); } diff --git a/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt b/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt index 14b5788c24..3b72982f1e 100644 --- a/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/media/ZoomableActivity.kt @@ -196,6 +196,7 @@ class ZoomableActivity : BaseActivity() { val dialog = Dialog(this) dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) dialog.setContentView(R.layout.full_screen_mode_info_dialog) + dialog.setCancelable(false) (dialog.findViewById(R.id.btn_ok) as Button).setOnClickListener { dialog.dismiss() } dialog.show() } diff --git a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java index 5da4e5478f..eb42606baa 100644 --- a/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java @@ -291,8 +291,8 @@ public void onActivityResult(Map result) { askForLocationPermission(); }, null, - null, - false); + null + ); } else { if (isPermissionDenied) { locationPermissionsHelper.showAppSettingsDialog(getActivity(), diff --git a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java index 390768416f..4a08760af2 100644 --- a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java @@ -206,8 +206,8 @@ public void showAlert(final Bitmap screenshot) { getString(R.string.cancel), () -> shareScreen(screenshot), () -> {}, - view, - true); + view + ); } /** diff --git a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt index 020a67f24f..af07423eba 100644 --- a/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/profile/achievements/AchievementsFragment.kt @@ -323,8 +323,7 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ null, message, getString(R.string.ok), - {}, - true + {} ) // binding.imagesUploadedProgressbar.setVisibility(View.INVISIBLE); @@ -510,8 +509,7 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ title, message, getString(R.string.ok), - {}, - true + {} ) } @@ -527,8 +525,7 @@ class AchievementsFragment : CommonsDaggerSupportFragment(){ getString(R.string.read_help_link), {}, { Utils.handleWebUrl(requireContext(), Uri.parse(helpLinkUrl)) }, - null, - true + null ) } /** diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt index a243c2637e..e60d4685a3 100644 --- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizActivity.kt @@ -66,6 +66,7 @@ class QuizActivity : AppCompatActivity() { AlertDialog.Builder(this) .setTitle(getString(R.string.warning)) .setMessage(getString(R.string.quiz_back_button)) + .setCancelable(false) .setPositiveButton(R.string.continue_message) { dialog, _ -> val intent = Intent(this, QuizResultActivity::class.java) dialog.dismiss() @@ -137,6 +138,7 @@ class QuizActivity : AppCompatActivity() { AlertDialog.Builder(this) .setTitle(title) .setMessage(message) + .setCancelable(false) .setPositiveButton(R.string.continue_message) { dialog, _ -> questionIndex++ if (questionIndex == quiz.size) { diff --git a/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt b/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt index 1d4821ee3d..617f25a78e 100644 --- a/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt +++ b/app/src/main/java/fr/free/nrw/commons/quiz/QuizResultActivity.kt @@ -181,6 +181,7 @@ class QuizResultActivity : AppCompatActivity() { val shareMessage = view.findViewById(R.id.alert_text) shareMessage.setText(R.string.quiz_result_share_message) alertadd.setView(view) + alertadd.setCancelable(false) alertadd.setPositiveButton(R.string.about_translate_proceed) { dialog, _ -> shareScreen(screenshot) } diff --git a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt index 91146059d4..fe93ebc8a7 100644 --- a/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt +++ b/app/src/main/java/fr/free/nrw/commons/settings/SettingsFragment.kt @@ -273,8 +273,7 @@ class SettingsFragment : PreferenceFragmentCompat() { getString(R.string.read_help_link), { }, { Utils.handleWebUrl(requireContext(), Uri.parse(GET_CONTENT_PICKER_HELP_URL)) }, - null, - true + null ) } @@ -333,7 +332,7 @@ class SettingsFragment : PreferenceFragmentCompat() { val dialog = Dialog(requireActivity()) dialog.setContentView(R.layout.dialog_select_language) - dialog.setCanceledOnTouchOutside(true) + dialog.setCancelable(false) dialog.window?.setLayout( (resources.displayMetrics.widthPixels * 0.90).toInt(), (resources.displayMetrics.heightPixels * 0.90).toInt() diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java index ed65b05dff..659c1e99ac 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadActivity.java @@ -270,8 +270,7 @@ protected void checkBlockStatus() { getString(R.string.block_notification_title), getString(R.string.block_notification), getString(R.string.ok), - this::finish, - true))); + this::finish))); } public void checkStoragePermissions() { @@ -418,16 +417,14 @@ public void onRequestPermissionsResult(final int requestCode, getString(R.string.storage_permissions_denied), getString(R.string.unable_to_share_upload_item), getString(android.R.string.ok), - this::finish, - false); + this::finish); } else { DialogUtil.showAlertDialog(this, getString(R.string.storage_permission_title), getString( R.string.write_storage_permission_rationale_for_image_share), getString(android.R.string.ok), - this::checkStoragePermissions, - false); + this::checkStoragePermissions); } } } @@ -754,8 +751,7 @@ public void showAlertDialog(int messageResourceId, Runnable onPositiveClick) { "", getString(messageResourceId), getString(R.string.ok), - onPositiveClick, - false); + onPositiveClick); } @Override diff --git a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java index a1a639a595..43331193ee 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/UploadMediaDetailAdapter.java @@ -346,7 +346,7 @@ private void initLanguage(int position, UploadMediaDetail description) { public void onClick(View view) { Dialog dialog = new Dialog(view.getContext()); dialog.setContentView(R.layout.dialog_select_language); - dialog.setCanceledOnTouchOutside(true); + dialog.setCancelable(false); dialog.getWindow().setLayout( (int) (view.getContext().getResources().getDisplayMetrics().widthPixels * 0.90), diff --git a/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.java index dd264655f4..fbc6bc7c52 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/categories/UploadCategoriesFragment.java @@ -105,7 +105,7 @@ private void init() { binding.tooltip.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - DialogUtil.showAlertDialog(getActivity(), getString(R.string.categories_activity_title), getString(R.string.categories_tooltip), getString(android.R.string.ok), null, true); + DialogUtil.showAlertDialog(getActivity(), getString(R.string.categories_activity_title), getString(R.string.categories_tooltip), getString(android.R.string.ok), null); } }); if (media == null) { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.java index 9000e513d5..0bbe4b15a3 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/depicts/DepictsFragment.java @@ -114,7 +114,7 @@ private void init() { setDepictsSubTitle(); binding.tooltip.setOnClickListener(v -> DialogUtil .showAlertDialog(getActivity(), getString(R.string.depicts_step_title), - getString(R.string.depicts_tooltip), getString(android.R.string.ok), null, true)); + getString(R.string.depicts_tooltip), getString(android.R.string.ok), null)); if (media == null) { presenter.onAttachView(this); } else { diff --git a/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.java index 01bee67f2d..939ad9a953 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/license/MediaLicenseFragment.java @@ -69,7 +69,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat getString(R.string.license_step_title), getString(R.string.license_tooltip), getString(android.R.string.ok), - null, true) + null) ); initPresenter(); diff --git a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java index fb836445a8..7a1266d89c 100644 --- a/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/upload/mediaDetails/UploadMediaDetailFragment.java @@ -309,7 +309,7 @@ private void initRecyclerView() { */ private void showInfoAlert(int titleStringID, int messageStringId) { DialogUtil.showAlertDialog(getActivity(), getString(titleStringID), - getString(messageStringId), getString(android.R.string.ok), null, true); + getString(messageStringId), getString(android.R.string.ok), null); } @@ -336,6 +336,7 @@ public void showSimilarImageFragment(String originalFilePath, String possibleFil BasicKvStore basicKvStore = new BasicKvStore(getActivity(), "IsAnyImageCancelled"); if (!basicKvStore.getBoolean("IsAnyImageCancelled", false)) { SimilarImageDialogFragment newFragment = new SimilarImageDialogFragment(); + newFragment.setCancelable(false); newFragment.setCallback(new SimilarImageDialogFragment.Callback() { @Override public void onPositiveResponse() { @@ -450,7 +451,8 @@ private void showNearbyPlaceFound(Place place) { // Execute when user cancels the upload of the specified place UploadActivity.nearbyPopupAnswers.put(place, false); }, - customLayout, true); + customLayout + ); } } @@ -526,8 +528,7 @@ public void showDuplicatePicturePopup(UploadItem uploadItem) { uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP); onImageValidationSuccess(); }, null, - checkBoxView, - false); + checkBoxView); } else { uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP); onImageValidationSuccess(); @@ -588,8 +589,7 @@ public void showConnectionErrorPopup() { basicKvStore.putBoolean(keyForShowingAlertDialog, false); activity.finish(); }, - null, - false + null ); } } catch (Exception e) { diff --git a/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.kt b/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.kt index ed0487c6aa..6d6fbb0a3a 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.kt +++ b/app/src/main/java/fr/free/nrw/commons/utils/DialogUtil.kt @@ -79,7 +79,6 @@ object DialogUtil { onPositiveBtnClick: Runnable?, onNegativeBtnClick: Runnable?, customView: View?, - cancelable: Boolean, ): AlertDialog? = createAndShowDialogSafely( activity = activity, @@ -90,7 +89,6 @@ object DialogUtil { onPositiveBtnClick = onPositiveBtnClick, onNegativeBtnClick = onNegativeBtnClick, customView = customView, - cancelable = cancelable, ) @JvmStatic @@ -103,7 +101,6 @@ object DialogUtil { onPositiveBtnClick: Runnable?, onNegativeBtnClick: Runnable?, customView: View?, - cancelable: Boolean, ): AlertDialog? = createAndShowDialogSafely( activity = activity, @@ -114,7 +111,6 @@ object DialogUtil { onPositiveBtnClick = onPositiveBtnClick, onNegativeBtnClick = onNegativeBtnClick, customView = customView, - cancelable = cancelable, ) @JvmStatic @@ -124,7 +120,6 @@ object DialogUtil { message: String?, positiveButtonText: String?, onPositiveBtnClick: Runnable?, - cancelable: Boolean, ): AlertDialog? = createAndShowDialogSafely( activity = activity, @@ -132,7 +127,6 @@ object DialogUtil { message = message, positiveButtonText = positiveButtonText, onPositiveBtnClick = onPositiveBtnClick, - cancelable = cancelable, ) /** @@ -156,7 +150,7 @@ object DialogUtil { onPositiveBtnClick: Runnable? = null, onNegativeBtnClick: Runnable? = null, customView: View? = null, - cancelable: Boolean = true, + cancelable: Boolean = false, ): AlertDialog? { /* If the custom view already has a parent, there is already a dialog showing with the view * This happens for on resume - return to avoid creating a second dialog - the first one diff --git a/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt index 78a877600b..ebff3d054f 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt +++ b/app/src/main/java/fr/free/nrw/commons/utils/ImageUtils.kt @@ -283,7 +283,8 @@ object ImageUtils { context, context.getString(R.string.setting_wallpaper_dialog_title), context.getString(R.string.setting_wallpaper_dialog_message), - true + true, + false ) } @@ -293,7 +294,8 @@ object ImageUtils { context, context.getString(R.string.setting_avatar_dialog_title), context.getString(R.string.setting_avatar_dialog_message), - true + true, + false ) } diff --git a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt index 305388fab7..f5a98dc5dc 100644 --- a/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt +++ b/app/src/main/java/fr/free/nrw/commons/utils/PermissionUtils.kt @@ -186,7 +186,7 @@ object PermissionUtils { activity.isShowPermissionsDialog = true } }, - null, null, activity !is UploadActivity + null, null ) } else -> Thread(onPermissionDenied).start() @@ -223,7 +223,7 @@ object PermissionUtils { activity.finish() } }, - null, false + null ) } }).onSameThread().check() diff --git a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragmentUnitTests.kt index 301e31d6da..03de2638d7 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragmentUnitTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/bookmarks/pictures/BookmarkPicturesFragmentUnitTests.kt @@ -108,7 +108,7 @@ class BookmarkPicturesFragmentUnitTests { GridViewAdapter( context, 0, - listOf(media()), + mutableListOf(media()), ), ) Whitebox.setInternalState(fragment, "binding", binding) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt index ab222b4ebe..e93f48c55b 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/category/CategoryDaoTest.kt @@ -17,8 +17,6 @@ import com.nhaarman.mockitokotlin2.mock import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.TestCommonsApplication -import fr.free.nrw.commons.category.CategoryContentProvider.BASE_URI -import fr.free.nrw.commons.category.CategoryContentProvider.uriForId import fr.free.nrw.commons.category.CategoryDao.Table.ALL_FIELDS import fr.free.nrw.commons.category.CategoryDao.Table.COLUMN_DESCRIPTION import fr.free.nrw.commons.category.CategoryDao.Table.COLUMN_ID @@ -31,6 +29,7 @@ import fr.free.nrw.commons.category.CategoryDao.Table.DROP_TABLE_STATEMENT import fr.free.nrw.commons.category.CategoryDao.Table.onCreate import fr.free.nrw.commons.category.CategoryDao.Table.onDelete import fr.free.nrw.commons.category.CategoryDao.Table.onUpdate +import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider.uriForId import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -85,21 +84,21 @@ class CategoryDaoTest { @Test fun migrateTableVersionFrom_v1_to_v2() { onUpdate(database, 1, 2) - // Table didnt exist before v5 + // Table didn't exist before v5 verifyNoInteractions(database) } @Test fun migrateTableVersionFrom_v2_to_v3() { onUpdate(database, 2, 3) - // Table didnt exist before v5 + // Table didn't exist before v5 verifyNoInteractions(database) } @Test fun migrateTableVersionFrom_v3_to_v4() { onUpdate(database, 3, 4) - // Table didnt exist before v5 + // Table didn't exist before v5 verifyNoInteractions(database) } @@ -112,21 +111,21 @@ class CategoryDaoTest { @Test fun migrateTableVersionFrom_v5_to_v6() { onUpdate(database, 5, 6) - // Table didnt change in version 6 + // Table didn't change in version 6 verifyNoInteractions(database) } @Test fun migrateTableVersionFrom_v6_to_v7() { onUpdate(database, 6, 7) - // Table didnt change in version 7 + // Table didn't change in version 7 verifyNoInteractions(database) } @Test fun migrateTableVersionFrom_v7_to_v8() { onUpdate(database, 7, 8) - // Table didnt change in version 8 + // Table didn't change in version 8 verifyNoInteractions(database) } @@ -135,9 +134,9 @@ class CategoryDaoTest { createCursor(1).let { cursor -> cursor.moveToFirst() testObject.fromCursor(cursor).let { - assertEquals(uriForId(1), it.contentUri) + assertEquals(CategoryContentProvider.uriForId(1), it.contentUri) assertEquals("showImageWithItem", it.name) - assertEquals(123, it.lastUsed.time) + assertEquals(123L, it.lastUsed?.time) assertEquals(2, it.timesUsed) } } @@ -150,13 +149,18 @@ class CategoryDaoTest { testObject.save(category) - verify(client).update(eq(category.contentUri), captor.capture(), isNull(), isNull()) + verify(client).update( + eq(category.contentUri)!!, + captor.capture(), + isNull(), + isNull() + ) captor.firstValue.let { cv -> assertEquals(5, cv.size()) assertEquals(category.name, cv.getAsString(COLUMN_NAME)) assertEquals(category.description, cv.getAsString(COLUMN_DESCRIPTION)) assertEquals(category.thumbnail, cv.getAsString(COLUMN_THUMBNAIL)) - assertEquals(category.lastUsed.time, cv.getAsLong(COLUMN_LAST_USED)) + assertEquals(category.lastUsed?.time, cv.getAsLong(COLUMN_LAST_USED)) assertEquals(category.timesUsed, cv.getAsInteger(COLUMN_TIMES_USED)) } } @@ -164,7 +168,7 @@ class CategoryDaoTest { @Test fun saveNewCategory() { - val contentUri = CategoryContentProvider.uriForId(111) + val contentUri = uriForId(111) whenever(client.insert(isA(), isA())).thenReturn(contentUri) val category = Category( @@ -178,13 +182,13 @@ class CategoryDaoTest { testObject.save(category) - verify(client).insert(eq(BASE_URI), captor.capture()) + verify(client).insert(eq(CategoryContentProvider.BASE_URI), captor.capture()) captor.firstValue.let { cv -> assertEquals(5, cv.size()) assertEquals(category.name, cv.getAsString(COLUMN_NAME)) assertEquals(category.description, cv.getAsString(COLUMN_DESCRIPTION)) assertEquals(category.thumbnail, cv.getAsString(COLUMN_THUMBNAIL)) - assertEquals(category.lastUsed.time, cv.getAsLong(COLUMN_LAST_USED)) + assertEquals(category.lastUsed?.time, cv.getAsLong(COLUMN_LAST_USED)) assertEquals(category.timesUsed, cv.getAsInteger(COLUMN_TIMES_USED)) assertEquals(contentUri, category.contentUri) } @@ -226,7 +230,7 @@ class CategoryDaoTest { val category = testObject.find("showImageWithItem") assertNotNull(category) - assertEquals(uriForId(1), category?.contentUri) + assertEquals(CategoryContentProvider.uriForId(1), category?.contentUri) assertEquals("showImageWithItem", category?.name) assertEquals("description", category?.description) assertEquals("image", category?.thumbnail) @@ -234,7 +238,7 @@ class CategoryDaoTest { assertEquals(2, category?.timesUsed) verify(client).query( - eq(BASE_URI), + eq(CategoryContentProvider.BASE_URI), eq(ALL_FIELDS), eq("$COLUMN_NAME=?"), queryCaptor.capture(), @@ -288,7 +292,7 @@ class CategoryDaoTest { assertEquals("showImageWithItem", result[0].name) verify(client).query( - eq(BASE_URI), + eq(CategoryContentProvider.BASE_URI), eq(ALL_FIELDS), isNull(), queryCaptor.capture(), diff --git a/app/src/test/kotlin/fr/free/nrw/commons/category/GridViewAdapterUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/category/GridViewAdapterUnitTest.kt index d3901113a0..6fb400f3e8 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/category/GridViewAdapterUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/category/GridViewAdapterUnitTest.kt @@ -38,7 +38,7 @@ class GridViewAdapterUnitTest { private lateinit var parent: ViewGroup @Mock - private lateinit var images: List + private lateinit var images: MutableList @Mock private lateinit var textView: TextView @@ -82,20 +82,20 @@ class GridViewAdapterUnitTest { @Test fun testContainsAllDataEmpty() { - gridViewAdapter = GridViewAdapter(context, 0, listOf()) + gridViewAdapter = GridViewAdapter(context, 0, mutableListOf()) Assert.assertEquals(gridViewAdapter.containsAll(images), false) } @Test fun testContainsAll() { - gridViewAdapter = GridViewAdapter(context, 0, listOf(media1)) + gridViewAdapter = GridViewAdapter(context, 0, mutableListOf(media1)) `when`(media1.filename).thenReturn("") Assert.assertEquals(gridViewAdapter.containsAll(listOf(media1)), true) } @Test fun testGetItem() { - gridViewAdapter = GridViewAdapter(context, 0, listOf(media1)) + gridViewAdapter = GridViewAdapter(context, 0, mutableListOf(media1)) Assert.assertEquals(gridViewAdapter.getItem(0), media1) } @@ -107,7 +107,7 @@ class GridViewAdapterUnitTest { @Test fun testGetView() { - gridViewAdapter = GridViewAdapter(context, 0, listOf(media1)) + gridViewAdapter = GridViewAdapter(context, 0, mutableListOf(media1)) `when`(media1.mostRelevantCaption).thenReturn("") Assert.assertEquals(gridViewAdapter.getView(0, convertView, parent), convertView) } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt index 9e60b74e6a..80cced5c99 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/delete/DeleteHelperTest.kt @@ -15,11 +15,14 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Before +import fr.free.nrw.commons.R import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers import org.mockito.Mock +import org.mockito.Mockito.spy import org.mockito.MockitoAnnotations +import org.powermock.api.mockito.PowerMockito.`when` import org.robolectric.RobolectricTestRunner import org.robolectric.RuntimeEnvironment import org.robolectric.annotation.Config @@ -44,7 +47,7 @@ class DeleteHelperTest { @Mock internal lateinit var media: Media - lateinit var deleteHelper: DeleteHelper + private lateinit var deleteHelper: DeleteHelper /** * Init mocks for test @@ -60,19 +63,46 @@ class DeleteHelperTest { */ @Test fun makeDeletion() { - whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(Observable.just(true)) - whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(Observable.just(true)) - whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(Observable.just(true)) + whenever(pageEditClient.prependEdit( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString()) + ).thenReturn(Observable.just(true)) + + whenever(pageEditClient.appendEdit( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString()) + ).thenReturn(Observable.just(true)) + + whenever(pageEditClient.edit( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString()) + ).thenReturn(Observable.just(true)) whenever(media.displayTitle).thenReturn("Test file") + `when`(context.getString(R.string.delete_helper_show_deletion_title)) + .thenReturn("Deletion Notification") + `when`(context.getString(R.string.delete_helper_show_deletion_title_success)) + .thenReturn("Success") + `when`(context.getString(R.string.delete_helper_show_deletion_title_failed)) + .thenReturn("Failed") + `when`(context.getString(R.string.delete_helper_show_deletion_message_else)) + .thenReturn("Media deletion failed") + `when`(context.getString( + R.string.delete_helper_show_deletion_message_if, media.displayTitle) + ).thenReturn("Media successfully deleted: Test Media Title") + val creatorName = "Creator" whenever(media.author).thenReturn("$creatorName") whenever(media.filename).thenReturn("Test file.jpg") - val makeDeletion = deleteHelper.makeDeletion(context, media, "Test reason")?.blockingGet() + val makeDeletion = deleteHelper.makeDeletion( + context, + media, + "Test reason" + )?.blockingGet() assertNotNull(makeDeletion) assertTrue(makeDeletion!!) verify(pageEditClient).appendEdit(eq("User_Talk:$creatorName"), ArgumentMatchers.anyString(), ArgumentMatchers.anyString()) @@ -83,12 +113,24 @@ class DeleteHelperTest { */ @Test(expected = RuntimeException::class) fun makeDeletionForPrependEditFailure() { - whenever(pageEditClient.prependEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(Observable.just(false)) - whenever(pageEditClient.appendEdit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(Observable.just(true)) - whenever(pageEditClient.edit(ArgumentMatchers.anyString(), ArgumentMatchers.anyString(), ArgumentMatchers.anyString())) - .thenReturn(Observable.just(true)) + whenever(pageEditClient.prependEdit( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString()) + ).thenReturn(Observable.just(false)) + + whenever(pageEditClient.appendEdit( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString()) + ).thenReturn(Observable.just(true)) + + whenever(pageEditClient.edit( + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString(), + ArgumentMatchers.anyString()) + ).thenReturn(Observable.just(true)) + whenever(media.displayTitle).thenReturn("Test file") whenever(media.filename).thenReturn("Test file.jpg") whenever(media.author).thenReturn("Creator (page does not exist)") @@ -141,16 +183,30 @@ class DeleteHelperTest { @Test fun alertDialogPositiveButtonDisableTest() { val mContext = RuntimeEnvironment.getApplication().applicationContext - deleteHelper.askReasonAndExecute(media, mContext, "My Question", ReviewController.DeleteReason.COPYRIGHT_VIOLATION, callback) - assertEquals(false, deleteHelper.dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled) + deleteHelper.askReasonAndExecute( + media, + mContext, + "My Question", + ReviewController.DeleteReason.COPYRIGHT_VIOLATION, callback + ) + + deleteHelper.getListener()?.onClick( + deleteHelper.getDialog(), + 1, + true + ) + assertEquals( + true, + deleteHelper.getDialog()?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled + ) } @Test fun alertDialogPositiveButtonEnableTest() { val mContext = RuntimeEnvironment.getApplication().applicationContext deleteHelper.askReasonAndExecute(media, mContext, "My Question", ReviewController.DeleteReason.COPYRIGHT_VIOLATION, callback) - deleteHelper.listener.onClick(deleteHelper.dialog, 1, true) - assertEquals(true, deleteHelper.dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled) + deleteHelper.getListener()?.onClick(deleteHelper.getDialog(), 1, true) + assertEquals(true, deleteHelper.getDialog()?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled) } @Test(expected = RuntimeException::class) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/delete/ReasonBuilderTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/delete/ReasonBuilderTest.kt index 75aadc17a4..e89a02dece 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/delete/ReasonBuilderTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/delete/ReasonBuilderTest.kt @@ -3,6 +3,7 @@ package fr.free.nrw.commons.delete import android.content.Context import android.content.res.Resources import fr.free.nrw.commons.Media +import fr.free.nrw.commons.R import fr.free.nrw.commons.auth.SessionManager import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient import fr.free.nrw.commons.profile.achievements.FeedbackResponse @@ -24,6 +25,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import org.powermock.api.mockito.PowerMockito import java.util.Date class ReasonBuilderTest { @@ -53,6 +55,9 @@ class ReasonBuilderTest { @Test fun forceLoginWhenAccountIsNull() { + PowerMockito.`when`(context?.getString(R.string.user_not_logged_in)) + .thenReturn("Log-in expired. Please log in again.") + reasonBuilder!!.getReason(mock(Media::class.java), "test") verify(sessionManager, times(1))!!.forceLogin(any(Context::class.java)) } diff --git a/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditHelperUnitTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditHelperUnitTest.kt index 5415a87a61..e7504f9ac4 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditHelperUnitTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/description/DescriptionEditHelperUnitTest.kt @@ -10,6 +10,7 @@ import org.junit.Test import org.junit.jupiter.api.Assertions.assertEquals import org.mockito.ArgumentMatchers.anyString import org.mockito.Mock +import fr.free.nrw.commons.R import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` @@ -73,6 +74,15 @@ class DescriptionEditHelperUnitTest { @Test fun testShowCaptionEditNotificationCaseFalse() { + `when`(context.getString(R.string.caption_edit_helper_show_edit_title)) + .thenReturn("Edit Caption") + `when`(context.getString(R.string.coordinates_edit_helper_show_edit_title_success)) + .thenReturn("Success") + `when`(context.getString(R.string.caption_edit_helper_show_edit_message)) + .thenReturn("Edit caption was successful") + `when`(context.getString(R.string.caption_edit_helper_edit_message_else)) + .thenReturn("Edit caption failed") + val method: Method = DescriptionEditHelper::class.java.getDeclaredMethod( "showCaptionEditNotification", @@ -86,6 +96,15 @@ class DescriptionEditHelperUnitTest { @Test fun testShowCaptionEditNotificationCaseTrue() { + `when`(context.getString(R.string.caption_edit_helper_show_edit_title)) + .thenReturn("Edit Caption") + `when`(context.getString(R.string.coordinates_edit_helper_show_edit_title_success)) + .thenReturn("Success") + `when`(context.getString(R.string.caption_edit_helper_show_edit_message)) + .thenReturn("Edit caption was successful") + `when`(context.getString(R.string.caption_edit_helper_edit_message_else)) + .thenReturn("Edit caption failed") + val method: Method = DescriptionEditHelper::class.java.getDeclaredMethod( "showCaptionEditNotification", @@ -99,6 +118,15 @@ class DescriptionEditHelperUnitTest { @Test fun testShowDescriptionEditNotificationCaseFalse() { + `when`(context.getString(R.string.description_edit_helper_show_edit_title)) + .thenReturn("Edit Description") + `when`(context.getString(R.string.coordinates_edit_helper_show_edit_title_success)) + .thenReturn("Success") + `when`(context.getString(R.string.description_edit_helper_show_edit_message)) + .thenReturn("Edit message") + `when`(context.getString(R.string.description_edit_helper_edit_message_else)) + .thenReturn("Edit failed") + val method: Method = DescriptionEditHelper::class.java.getDeclaredMethod( "showDescriptionEditNotification", @@ -112,6 +140,15 @@ class DescriptionEditHelperUnitTest { @Test fun testShowDescriptionEditNotificationCaseTrue() { + `when`(context.getString(R.string.description_edit_helper_show_edit_title)) + .thenReturn("Edit Description") + `when`(context.getString(R.string.coordinates_edit_helper_show_edit_title_success)) + .thenReturn("Success") + `when`(context.getString(R.string.description_edit_helper_show_edit_message)) + .thenReturn("Edit message") + `when`(context.getString(R.string.description_edit_helper_edit_message_else)) + .thenReturn("Edit failed") + val method: Method = DescriptionEditHelper::class.java.getDeclaredMethod( "showDescriptionEditNotification", diff --git a/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt b/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt index 37b5277f47..0695cbca74 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/media/MediaDetailFragmentUnitTests.kt @@ -27,6 +27,7 @@ import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.drawee.generic.GenericDraweeHierarchy import com.facebook.drawee.view.SimpleDraweeView import com.facebook.soloader.SoLoader +import com.nhaarman.mockitokotlin2.anyOrNull import com.nhaarman.mockitokotlin2.doReturn import com.nhaarman.mockitokotlin2.whenever import fr.free.nrw.commons.LocationPicker.LocationPickerActivity @@ -768,9 +769,16 @@ class MediaDetailFragmentUnitTests { ).thenReturn(true) doReturn( Single.just(true), - ).`when`(deleteHelper).makeDeletion(ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any()) - - doReturn(Single.just("")).`when`(reasonBuilder).getReason(ArgumentMatchers.any(), ArgumentMatchers.any()) + ).`when`(deleteHelper).makeDeletion( + ArgumentMatchers.any(), + ArgumentMatchers.any(), + ArgumentMatchers.any() + ) + + doReturn(Single.just("")).`when`(reasonBuilder).getReason( + ArgumentMatchers.any(), + ArgumentMatchers.any() + ) val method: Method = MediaDetailFragment::class.java.getDeclaredMethod(