mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-28 13:23:58 +01:00
Merge branch 'main' into recent-search
This commit is contained in:
commit
5415471045
65 changed files with 2160 additions and 2056 deletions
|
|
@ -180,8 +180,8 @@ public class AboutActivity extends BaseActivity {
|
|||
getString(R.string.about_translate_cancel),
|
||||
positiveButtonRunnable,
|
||||
() -> {},
|
||||
spinner,
|
||||
true);
|
||||
spinner
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ public class WelcomeActivity extends BaseActivity {
|
|||
copyrightBinding = PopupForCopyrightBinding.inflate(getLayoutInflater());
|
||||
final View contactPopupView = copyrightBinding.getRoot();
|
||||
dialogBuilder.setView(contactPopupView);
|
||||
dialogBuilder.setCancelable(false);
|
||||
dialog = dialogBuilder.create();
|
||||
dialog.show();
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
17
app/src/main/java/fr/free/nrw/commons/category/Category.kt
Normal file
17
app/src/main/java/fr/free/nrw/commons/category/Category.kt
Normal file
|
|
@ -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++
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package fr.free.nrw.commons.category;
|
||||
|
||||
public interface CategoryClickedListener {
|
||||
void categoryClicked(CategoryItem item);
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package fr.free.nrw.commons.category
|
||||
|
||||
interface CategoryClickedListener {
|
||||
fun categoryClicked(item: CategoryItem)
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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<String>?, selection: String?,
|
||||
selectionArgs: Array<String>?, 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<String>?): Int {
|
||||
// Not implemented
|
||||
return 0
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
override fun bulkInsert(uri: Uri, values: Array<ContentValues>): 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<String>?): 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}")
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ContentProviderClient> clientProvider;
|
||||
|
||||
@Inject
|
||||
public CategoryDao(@Named("category") Provider<ContentProviderClient> 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<CategoryItem> recentCategories(int limit) {
|
||||
List<CategoryItem> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
194
app/src/main/java/fr/free/nrw/commons/category/CategoryDao.kt
Normal file
194
app/src/main/java/fr/free/nrw/commons/category/CategoryDao.kt
Normal file
|
|
@ -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<ContentProviderClient>
|
||||
) {
|
||||
|
||||
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<CategoryItem> {
|
||||
val items = ArrayList<CategoryItem>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Fragment> fragmentList = new ArrayList<>();
|
||||
List<String> 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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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<Fragment>()
|
||||
val titleList = mutableListOf<String>()
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Boolean> makeCategoryEdit(Context context, Media media, List<String> 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<Boolean> addCategory(Media media, List<String> 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<String> 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<String> categories);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String>,
|
||||
wikiText: String
|
||||
): Single<Boolean> {
|
||||
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<String>?,
|
||||
wikiText: String
|
||||
): Observable<Boolean> {
|
||||
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<String>?): Boolean
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NOTIFICATION_EDIT_CATEGORY = 1
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package fr.free.nrw.commons.category
|
||||
|
||||
interface CategoryImagesCallback {
|
||||
fun viewPagerNotifyDataSetChanged()
|
||||
|
||||
fun onMediaClicked(position: Int)
|
||||
}
|
||||
|
|
@ -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<Media> data;
|
||||
|
||||
public GridViewAdapter(Context context, int layoutResourceId, List<Media> 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<Media> 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<Media> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Media>?
|
||||
) : ArrayAdapter<Media>(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<Media>) {
|
||||
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<Media>?): 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<SimpleDraweeView>(R.id.categoryImageView)
|
||||
val fileName = view.findViewById<TextView>(R.id.categoryImageTitle)
|
||||
val uploader = view.findViewById<TextView>(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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package fr.free.nrw.commons.category;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface OnCategoriesSaveHandler {
|
||||
void onCategoriesSave(List<String> categories);
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package fr.free.nrw.commons.category
|
||||
|
||||
interface OnCategoriesSaveHandler {
|
||||
fun onCategoriesSave(categories: List<String>)
|
||||
}
|
||||
|
|
@ -170,8 +170,8 @@ public class ContributionController {
|
|||
},
|
||||
() -> locationPermissionCallback.onLocationPermissionDenied(
|
||||
activity.getString(R.string.in_app_camera_location_permission_denied)),
|
||||
null,
|
||||
false);
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -203,8 +203,8 @@ public class ContributionController {
|
|||
defaultKvStore.putBoolean("inAppCameraLocationPref", false);
|
||||
initiateCameraUpload(activity, resultLauncher);
|
||||
},
|
||||
null,
|
||||
true);
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -566,8 +566,8 @@ public class ContributionsFragment
|
|||
getString(R.string.nearby_card_permission_explanation),
|
||||
this::requestLocationPermission,
|
||||
this::displayYouWontSeeNearbyMessage,
|
||||
checkBoxView,
|
||||
false);
|
||||
checkBoxView
|
||||
);
|
||||
}
|
||||
|
||||
private void displayYouWontSeeNearbyMessage() {
|
||||
|
|
|
|||
|
|
@ -1,187 +0,0 @@
|
|||
package fr.free.nrw.commons.coordinates;
|
||||
|
||||
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_EDIT_COORDINATES;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.actions.PageEditClient;
|
||||
import fr.free.nrw.commons.notification.NotificationHelper;
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.util.Objects;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Helper class for edit and update given coordinates and showing notification about new coordinates
|
||||
* upgradation
|
||||
*/
|
||||
public class CoordinateEditHelper {
|
||||
|
||||
/**
|
||||
* notificationHelper: helps creating notification
|
||||
*/
|
||||
private final NotificationHelper notificationHelper;
|
||||
/**
|
||||
* * pageEditClient: methods provided by this member posts the edited coordinates
|
||||
* to the Media wiki api
|
||||
*/
|
||||
public final PageEditClient pageEditClient;
|
||||
/**
|
||||
* viewUtil: helps to show Toast
|
||||
*/
|
||||
private final ViewUtilWrapper viewUtil;
|
||||
|
||||
@Inject
|
||||
public CoordinateEditHelper(final NotificationHelper notificationHelper,
|
||||
@Named("commons-page-edit") final PageEditClient pageEditClient,
|
||||
final ViewUtilWrapper viewUtil) {
|
||||
this.notificationHelper = notificationHelper;
|
||||
this.pageEditClient = pageEditClient;
|
||||
this.viewUtil = viewUtil;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public interface to edit coordinates
|
||||
* @param context to be added
|
||||
* @param media to be added
|
||||
* @param Accuracy to be added
|
||||
* @return Single<Boolean>
|
||||
*/
|
||||
public Single<Boolean> makeCoordinatesEdit(final Context context, final Media media,
|
||||
final String Latitude, final String Longitude, final String Accuracy) {
|
||||
viewUtil.showShortToast(context,
|
||||
context.getString(R.string.coordinates_edit_helper_make_edit_toast));
|
||||
return addCoordinates(media, Latitude, Longitude, Accuracy)
|
||||
.flatMapSingle(result -> Single.just(showCoordinatesEditNotification(context, media,
|
||||
Latitude, Longitude, Accuracy, result)))
|
||||
.firstOrError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces new coordinates
|
||||
* @param media to be added
|
||||
* @param Latitude to be added
|
||||
* @param Longitude to be added
|
||||
* @param Accuracy to be added
|
||||
* @return Observable<Boolean>
|
||||
*/
|
||||
private Observable<Boolean> addCoordinates(final Media media, final String Latitude,
|
||||
final String Longitude, final String Accuracy) {
|
||||
Timber.d("thread is coordinates adding %s", Thread.currentThread().getName());
|
||||
final String summary = "Adding Coordinates";
|
||||
|
||||
final StringBuilder buffer = new StringBuilder();
|
||||
|
||||
final String wikiText = pageEditClient.getCurrentWikiText(media.getFilename())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.blockingGet();
|
||||
|
||||
if (Latitude != null) {
|
||||
buffer.append("\n{{Location|").append(Latitude).append("|").append(Longitude)
|
||||
.append("|").append(Accuracy).append("}}");
|
||||
}
|
||||
|
||||
final String editedLocation = buffer.toString();
|
||||
final String appendText = getFormattedWikiText(wikiText, editedLocation);
|
||||
|
||||
return pageEditClient.edit(Objects.requireNonNull(media.getFilename())
|
||||
, appendText, summary);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps to get formatted wikitext with upgraded location
|
||||
* @param wikiText current wikitext
|
||||
* @param editedLocation new location
|
||||
* @return String
|
||||
*/
|
||||
private String getFormattedWikiText(final String wikiText, final String editedLocation){
|
||||
|
||||
if (wikiText.contains("filedesc") && wikiText.contains("Location")) {
|
||||
|
||||
final String fromLocationToEnd = wikiText.substring(wikiText.indexOf("{{Location"));
|
||||
final String firstHalf = wikiText.substring(0, wikiText.indexOf("{{Location"));
|
||||
final String lastHalf = fromLocationToEnd.substring(
|
||||
fromLocationToEnd.indexOf("}}") + 2);
|
||||
|
||||
final int startOfSecondSection = StringUtils.ordinalIndexOf(wikiText,
|
||||
"==", 3);
|
||||
final StringBuilder buffer = new StringBuilder();
|
||||
if (wikiText.charAt(wikiText.indexOf("{{Location")-1) == '\n') {
|
||||
buffer.append(editedLocation.substring(1));
|
||||
} else {
|
||||
buffer.append(editedLocation);
|
||||
}
|
||||
if (startOfSecondSection != -1 && wikiText.charAt(startOfSecondSection-1)!= '\n') {
|
||||
buffer.append("\n");
|
||||
}
|
||||
|
||||
return firstHalf + buffer + lastHalf;
|
||||
|
||||
}
|
||||
if (wikiText.contains("filedesc") && !wikiText.contains("Location")) {
|
||||
|
||||
final int startOfSecondSection = StringUtils.ordinalIndexOf(wikiText,
|
||||
"==", 3);
|
||||
|
||||
if (startOfSecondSection != -1) {
|
||||
final String firstHalf = wikiText.substring(0, startOfSecondSection);
|
||||
final String lastHalf = wikiText.substring(startOfSecondSection);
|
||||
final String buffer = editedLocation.substring(1)
|
||||
+ "\n";
|
||||
return firstHalf + buffer + lastHalf;
|
||||
}
|
||||
|
||||
return wikiText + editedLocation;
|
||||
}
|
||||
return "== {{int:filedesc}} ==" + editedLocation + wikiText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update coordinates and shows notification about coordinates update
|
||||
* @param context to be added
|
||||
* @param media to be added
|
||||
* @param latitude to be added
|
||||
* @param longitude to be added
|
||||
* @param Accuracy to be added
|
||||
* @param result to be added
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean showCoordinatesEditNotification(final Context context, final Media media,
|
||||
final String latitude, final String longitude, final String Accuracy,
|
||||
final boolean result) {
|
||||
final String message;
|
||||
String title = context.getString(R.string.coordinates_edit_helper_show_edit_title);
|
||||
|
||||
if (result) {
|
||||
media.setCoordinates(
|
||||
new fr.free.nrw.commons.location.LatLng(Double.parseDouble(latitude),
|
||||
Double.parseDouble(longitude),
|
||||
Float.parseFloat(Accuracy)));
|
||||
title += ": " + context
|
||||
.getString(R.string.coordinates_edit_helper_show_edit_title_success);
|
||||
final StringBuilder coordinatesInMessage = new StringBuilder();
|
||||
final String mediaCoordinate = String.valueOf(media.getCoordinates());
|
||||
coordinatesInMessage.append(mediaCoordinate);
|
||||
message = context.getString(R.string.coordinates_edit_helper_show_edit_message,
|
||||
coordinatesInMessage.toString());
|
||||
} else {
|
||||
title += ": " + context.getString(R.string.coordinates_edit_helper_show_edit_title);
|
||||
message = context.getString(R.string.coordinates_edit_helper_edit_message_else) ;
|
||||
}
|
||||
|
||||
final String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename();
|
||||
final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile));
|
||||
notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_COORDINATES,
|
||||
browserIntent);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,189 @@
|
|||
package fr.free.nrw.commons.coordinates
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import fr.free.nrw.commons.BuildConfig
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.actions.PageEditClient
|
||||
import fr.free.nrw.commons.notification.NotificationHelper
|
||||
import fr.free.nrw.commons.notification.NotificationHelper.Companion.NOTIFICATION_EDIT_COORDINATES
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.util.Objects
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
import timber.log.Timber
|
||||
|
||||
|
||||
/**
|
||||
* Helper class for edit and update given coordinates and showing notification about new coordinates
|
||||
* upgradation
|
||||
*/
|
||||
class CoordinateEditHelper @Inject constructor(
|
||||
private val notificationHelper: NotificationHelper,
|
||||
@Named("commons-page-edit") private val pageEditClient: PageEditClient,
|
||||
private val viewUtil: ViewUtilWrapper
|
||||
) {
|
||||
|
||||
/**
|
||||
* Public interface to edit coordinates
|
||||
* @param context to be added
|
||||
* @param media to be added
|
||||
* @param latitude to be added
|
||||
* @param longitude to be added
|
||||
* @param accuracy to be added
|
||||
* @return Single<Boolean>
|
||||
*/
|
||||
fun makeCoordinatesEdit(
|
||||
context: Context,
|
||||
media: Media,
|
||||
latitude: String,
|
||||
longitude: String,
|
||||
accuracy: String
|
||||
): Single<Boolean>? {
|
||||
viewUtil.showShortToast(
|
||||
context,
|
||||
context.getString(R.string.coordinates_edit_helper_make_edit_toast)
|
||||
)
|
||||
return addCoordinates(media, latitude, longitude, accuracy)
|
||||
?.flatMapSingle { result ->
|
||||
Single.just(showCoordinatesEditNotification(context, media, latitude, longitude, accuracy, result))
|
||||
}
|
||||
?.firstOrError()
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces new coordinates
|
||||
* @param media to be added
|
||||
* @param Latitude to be added
|
||||
* @param Longitude to be added
|
||||
* @param Accuracy to be added
|
||||
* @return Observable<Boolean>
|
||||
*/
|
||||
private fun addCoordinates(
|
||||
media: Media,
|
||||
Latitude: String,
|
||||
Longitude: String,
|
||||
Accuracy: String
|
||||
): Observable<Boolean>? {
|
||||
Timber.d("thread is coordinates adding %s", Thread.currentThread().getName())
|
||||
val summary = "Adding Coordinates"
|
||||
|
||||
val buffer = StringBuilder()
|
||||
|
||||
val wikiText = media.filename?.let {
|
||||
pageEditClient.getCurrentWikiText(it)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.blockingGet()
|
||||
}
|
||||
|
||||
if (Latitude != null) {
|
||||
buffer.append("\n{{Location|").append(Latitude).append("|").append(Longitude)
|
||||
.append("|").append(Accuracy).append("}}")
|
||||
}
|
||||
|
||||
val editedLocation = buffer.toString()
|
||||
val appendText = wikiText?.let { getFormattedWikiText(it, editedLocation) }
|
||||
|
||||
return Objects.requireNonNull(media.filename)
|
||||
?.let { pageEditClient.edit(it, appendText!!, summary) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Helps to get formatted wikitext with upgraded location
|
||||
* @param wikiText current wikitext
|
||||
* @param editedLocation new location
|
||||
* @return String
|
||||
*/
|
||||
private fun getFormattedWikiText(wikiText: String, editedLocation: String): String {
|
||||
if (wikiText.contains("filedesc") && wikiText.contains("Location")) {
|
||||
val fromLocationToEnd = wikiText.substring(wikiText.indexOf("{{Location"))
|
||||
val firstHalf = wikiText.substring(0, wikiText.indexOf("{{Location"))
|
||||
val lastHalf = fromLocationToEnd.substring(fromLocationToEnd.indexOf("}}") + 2)
|
||||
|
||||
val startOfSecondSection = StringUtils.ordinalIndexOf(wikiText, "==", 3)
|
||||
val buffer = StringBuilder()
|
||||
if (wikiText[wikiText.indexOf("{{Location") - 1] == '\n') {
|
||||
buffer.append(editedLocation.substring(1))
|
||||
} else {
|
||||
buffer.append(editedLocation)
|
||||
}
|
||||
if (startOfSecondSection != -1 && wikiText[startOfSecondSection - 1] != '\n') {
|
||||
buffer.append("\n")
|
||||
}
|
||||
|
||||
return firstHalf + buffer + lastHalf
|
||||
}
|
||||
if (wikiText.contains("filedesc") && !wikiText.contains("Location")) {
|
||||
val startOfSecondSection = StringUtils.ordinalIndexOf(wikiText, "==", 3)
|
||||
|
||||
if (startOfSecondSection != -1) {
|
||||
val firstHalf = wikiText.substring(0, startOfSecondSection)
|
||||
val lastHalf = wikiText.substring(startOfSecondSection)
|
||||
val buffer = editedLocation.substring(1) + "\n"
|
||||
return firstHalf + buffer + lastHalf
|
||||
}
|
||||
|
||||
return wikiText + editedLocation
|
||||
}
|
||||
return "== {{int:filedesc}} ==$editedLocation$wikiText"
|
||||
}
|
||||
|
||||
/**
|
||||
* Update coordinates and shows notification about coordinates update
|
||||
* @param context to be added
|
||||
* @param media to be added
|
||||
* @param latitude to be added
|
||||
* @param longitude to be added
|
||||
* @param Accuracy to be added
|
||||
* @param result to be added
|
||||
* @return boolean
|
||||
*/
|
||||
private fun showCoordinatesEditNotification(
|
||||
context: Context,
|
||||
media: Media,
|
||||
latitude: String,
|
||||
longitude: String,
|
||||
Accuracy: String,
|
||||
result: Boolean
|
||||
): Boolean {
|
||||
val message: String
|
||||
var title = context.getString(R.string.coordinates_edit_helper_show_edit_title)
|
||||
|
||||
if (result) {
|
||||
media.coordinates = fr.free.nrw.commons.location.LatLng(
|
||||
latitude.toDouble(),
|
||||
longitude.toDouble(),
|
||||
Accuracy.toFloat()
|
||||
)
|
||||
title += ": " + context.getString(R.string.coordinates_edit_helper_show_edit_title_success)
|
||||
val coordinatesInMessage = StringBuilder()
|
||||
val mediaCoordinate = media.coordinates.toString()
|
||||
coordinatesInMessage.append(mediaCoordinate)
|
||||
message = context.getString(
|
||||
R.string.coordinates_edit_helper_show_edit_message,
|
||||
coordinatesInMessage.toString()
|
||||
)
|
||||
} else {
|
||||
title += ": " + context.getString(R.string.coordinates_edit_helper_show_edit_title)
|
||||
message = context.getString(R.string.coordinates_edit_helper_edit_message_else)
|
||||
}
|
||||
|
||||
val urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.filename
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile))
|
||||
notificationHelper.showNotification(
|
||||
context,
|
||||
title,
|
||||
message,
|
||||
NOTIFICATION_EDIT_COORDINATES,
|
||||
browserIntent
|
||||
)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
package fr.free.nrw.commons.data;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao;
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
|
||||
import fr.free.nrw.commons.category.CategoryDao;
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
||||
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao;
|
||||
|
||||
public class DBOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String DATABASE_NAME = "commons.db";
|
||||
private static final int DATABASE_VERSION = 20;
|
||||
public static final String CONTRIBUTIONS_TABLE = "contributions";
|
||||
private final String DROP_TABLE_STATEMENT="DROP TABLE IF EXISTS %s";
|
||||
|
||||
/**
|
||||
* Do not use directly - @Inject an instance where it's needed and let
|
||||
* dependency injection take care of managing this as a singleton.
|
||||
*/
|
||||
public DBOpenHelper(Context context) {
|
||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase sqLiteDatabase) {
|
||||
CategoryDao.Table.onCreate(sqLiteDatabase);
|
||||
BookmarkPicturesDao.Table.onCreate(sqLiteDatabase);
|
||||
BookmarkLocationsDao.Table.onCreate(sqLiteDatabase);
|
||||
BookmarkItemsDao.Table.onCreate(sqLiteDatabase);
|
||||
RecentSearchesDao.Table.onCreate(sqLiteDatabase);
|
||||
RecentLanguagesDao.Table.onCreate(sqLiteDatabase);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int from, int to) {
|
||||
CategoryDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||
BookmarkPicturesDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||
BookmarkLocationsDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||
BookmarkItemsDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||
RecentSearchesDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||
RecentLanguagesDao.Table.onUpdate(sqLiteDatabase, from, to);
|
||||
deleteTable(sqLiteDatabase,CONTRIBUTIONS_TABLE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete table in the given db
|
||||
* @param db
|
||||
* @param tableName
|
||||
*/
|
||||
public void deleteTable(SQLiteDatabase db, String tableName) {
|
||||
try {
|
||||
db.execSQL(String.format(DROP_TABLE_STATEMENT, tableName));
|
||||
onCreate(db);
|
||||
} catch (SQLiteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
62
app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt
Normal file
62
app/src/main/java/fr/free/nrw/commons/data/DBOpenHelper.kt
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package fr.free.nrw.commons.data
|
||||
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteException
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import fr.free.nrw.commons.bookmarks.items.BookmarkItemsDao
|
||||
import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao
|
||||
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao
|
||||
import fr.free.nrw.commons.category.CategoryDao
|
||||
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao
|
||||
import fr.free.nrw.commons.recentlanguages.RecentLanguagesDao
|
||||
|
||||
|
||||
class DBOpenHelper(
|
||||
context: Context
|
||||
): SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
|
||||
|
||||
companion object {
|
||||
private const val DATABASE_NAME = "commons.db"
|
||||
private const val DATABASE_VERSION = 20
|
||||
const val CONTRIBUTIONS_TABLE = "contributions"
|
||||
private const val DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS %s"
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not use directly - @Inject an instance where it's needed and let
|
||||
* dependency injection take care of managing this as a singleton.
|
||||
*/
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
CategoryDao.Table.onCreate(db)
|
||||
BookmarkPicturesDao.Table.onCreate(db)
|
||||
BookmarkLocationsDao.Table.onCreate(db)
|
||||
BookmarkItemsDao.Table.onCreate(db)
|
||||
RecentSearchesDao.Table.onCreate(db)
|
||||
RecentLanguagesDao.Table.onCreate(db)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, from: Int, to: Int) {
|
||||
CategoryDao.Table.onUpdate(db, from, to)
|
||||
BookmarkPicturesDao.Table.onUpdate(db, from, to)
|
||||
BookmarkLocationsDao.Table.onUpdate(db, from, to)
|
||||
BookmarkItemsDao.Table.onUpdate(db, from, to)
|
||||
RecentSearchesDao.Table.onUpdate(db, from, to)
|
||||
RecentLanguagesDao.Table.onUpdate(db, from, to)
|
||||
deleteTable(db, CONTRIBUTIONS_TABLE)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete table in the given db
|
||||
* @param db
|
||||
* @param tableName
|
||||
*/
|
||||
fun deleteTable(db: SQLiteDatabase, tableName: String) {
|
||||
try {
|
||||
db.execSQL(String.format(DROP_TABLE_STATEMENT, tableName))
|
||||
onCreate(db)
|
||||
} catch (e: SQLiteException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,165 +0,0 @@
|
|||
package fr.free.nrw.commons.db;
|
||||
|
||||
import android.net.Uri;
|
||||
import androidx.room.TypeConverter;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import fr.free.nrw.commons.CommonsApplication;
|
||||
import fr.free.nrw.commons.contributions.ChunkInfo;
|
||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||
import fr.free.nrw.commons.location.LatLng;
|
||||
import fr.free.nrw.commons.nearby.Sitelinks;
|
||||
import fr.free.nrw.commons.upload.WikidataPlace;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This class supplies converters to write/read types to/from the database.
|
||||
*/
|
||||
public class Converters {
|
||||
|
||||
public static Gson getGson() {
|
||||
return ApplicationlessInjection
|
||||
.getInstance(CommonsApplication.getInstance())
|
||||
.getCommonsApplicationComponent()
|
||||
.gson();
|
||||
}
|
||||
|
||||
/**
|
||||
* convert DepictedItem object to string
|
||||
* input Example -> DepictedItem depictedItem=new DepictedItem ()
|
||||
* output Example -> string
|
||||
*/
|
||||
@TypeConverter
|
||||
public static String depictsItemToString(DepictedItem objects) {
|
||||
return writeObjectToString(objects);
|
||||
}
|
||||
|
||||
/**
|
||||
* convert string to DepictedItem object
|
||||
* output Example -> DepictedItem depictedItem=new DepictedItem ()
|
||||
* input Example -> string
|
||||
*/
|
||||
@TypeConverter
|
||||
public static DepictedItem stringToDepicts(String objectList) {
|
||||
return readObjectWithTypeToken(objectList, new TypeToken<DepictedItem>() {
|
||||
});
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Date fromTimestamp(Long value) {
|
||||
return value == null ? null : new Date(value);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Long dateToTimestamp(Date date) {
|
||||
return date == null ? null : date.getTime();
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Uri fromString(String value) {
|
||||
return value == null ? null : Uri.parse(value);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String uriToString(Uri uri) {
|
||||
return uri == null ? null : uri.toString();
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String listObjectToString(List<String> objectList) {
|
||||
return writeObjectToString(objectList);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static List<String> stringToListObject(String objectList) {
|
||||
return readObjectWithTypeToken(objectList, new TypeToken<List<String>>() {});
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String mapObjectToString(Map<String,String> objectList) {
|
||||
return writeObjectToString(objectList);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String mapObjectToString2(Map<String,Boolean> objectList) {
|
||||
return writeObjectToString(objectList);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Map<String,String> stringToMap(String objectList) {
|
||||
return readObjectWithTypeToken(objectList, new TypeToken<Map<String,String>>(){});
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Map<String,Boolean> stringToMap2(String objectList) {
|
||||
return readObjectWithTypeToken(objectList, new TypeToken<Map<String,Boolean>>(){});
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String latlngObjectToString(LatLng latlng) {
|
||||
return writeObjectToString(latlng);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static LatLng stringToLatLng(String objectList) {
|
||||
return readObjectFromString(objectList,LatLng.class);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String wikidataPlaceToString(WikidataPlace wikidataPlace) {
|
||||
return writeObjectToString(wikidataPlace);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static WikidataPlace stringToWikidataPlace(String wikidataPlace) {
|
||||
return readObjectFromString(wikidataPlace, WikidataPlace.class);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String chunkInfoToString(ChunkInfo chunkInfo) {
|
||||
return writeObjectToString(chunkInfo);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static ChunkInfo stringToChunkInfo(String chunkInfo) {
|
||||
return readObjectFromString(chunkInfo, ChunkInfo.class);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String depictionListToString(List<DepictedItem> depictedItems) {
|
||||
return writeObjectToString(depictedItems);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static List<DepictedItem> stringToList(String depictedItems) {
|
||||
return readObjectWithTypeToken(depictedItems, new TypeToken<List<DepictedItem>>() {});
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static Sitelinks sitelinksFromString(String value) {
|
||||
Type type = new TypeToken<Sitelinks>() {}.getType();
|
||||
return new Gson().fromJson(value, type);
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
public static String fromSitelinks(Sitelinks sitelinks) {
|
||||
Gson gson = new Gson();
|
||||
return gson.toJson(sitelinks);
|
||||
}
|
||||
|
||||
private static String writeObjectToString(Object object) {
|
||||
return object == null ? null : getGson().toJson(object);
|
||||
}
|
||||
|
||||
private static<T> T readObjectFromString(String objectAsString, Class<T> clazz) {
|
||||
return objectAsString == null ? null : getGson().fromJson(objectAsString, clazz);
|
||||
}
|
||||
|
||||
private static <T> T readObjectWithTypeToken(String objectList, TypeToken<T> typeToken) {
|
||||
return objectList == null ? null : getGson().fromJson(objectList, typeToken.getType());
|
||||
}
|
||||
}
|
||||
182
app/src/main/java/fr/free/nrw/commons/db/Converters.kt
Normal file
182
app/src/main/java/fr/free/nrw/commons/db/Converters.kt
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
package fr.free.nrw.commons.db
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.room.TypeConverter
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import fr.free.nrw.commons.CommonsApplication
|
||||
import fr.free.nrw.commons.contributions.ChunkInfo
|
||||
import fr.free.nrw.commons.di.ApplicationlessInjection
|
||||
import fr.free.nrw.commons.location.LatLng
|
||||
import fr.free.nrw.commons.nearby.Sitelinks
|
||||
import fr.free.nrw.commons.upload.WikidataPlace
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import java.util.Date
|
||||
|
||||
/**
|
||||
* This object supplies converters to write/read types to/from the database.
|
||||
*/
|
||||
object Converters {
|
||||
|
||||
fun getGson(): Gson {
|
||||
return ApplicationlessInjection
|
||||
.getInstance(CommonsApplication.instance)
|
||||
.commonsApplicationComponent
|
||||
.gson()
|
||||
}
|
||||
|
||||
/**
|
||||
* convert DepictedItem object to string
|
||||
* input Example -> DepictedItem depictedItem=new DepictedItem ()
|
||||
* output Example -> string
|
||||
*/
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun depictsItemToString(objects: DepictedItem?): String? {
|
||||
return writeObjectToString(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
* convert string to DepictedItem object
|
||||
* output Example -> DepictedItem depictedItem=new DepictedItem ()
|
||||
* input Example -> string
|
||||
*/
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun stringToDepicts(objectList: String?): DepictedItem? {
|
||||
return readObjectWithTypeToken(objectList, object : TypeToken<DepictedItem>() {})
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun fromTimestamp(value: Long?): Date? {
|
||||
return value?.let { Date(it) }
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun dateToTimestamp(date: Date?): Long? {
|
||||
return date?.time
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun fromString(value: String?): Uri? {
|
||||
return value?.let { Uri.parse(it) }
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun uriToString(uri: Uri?): String? {
|
||||
return uri?.toString()
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun listObjectToString(objectList: List<String>?): String? {
|
||||
return writeObjectToString(objectList)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun stringToListObject(objectList: String?): List<String>? {
|
||||
return readObjectWithTypeToken(objectList, object : TypeToken<List<String>>() {})
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun mapObjectToString(objectList: Map<String, String>?): String? {
|
||||
return writeObjectToString(objectList)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun mapObjectToString2(objectList: Map<String, Boolean>?): String? {
|
||||
return writeObjectToString(objectList)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun stringToMap(objectList: String?): Map<String, String>? {
|
||||
return readObjectWithTypeToken(objectList, object : TypeToken<Map<String, String>>() {})
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun stringToMap2(objectList: String?): Map<String, Boolean>? {
|
||||
return readObjectWithTypeToken(objectList, object : TypeToken<Map<String, Boolean>>() {})
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun latlngObjectToString(latlng: LatLng?): String? {
|
||||
return writeObjectToString(latlng)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun stringToLatLng(objectList: String?): LatLng? {
|
||||
return readObjectFromString(objectList, LatLng::class.java)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun wikidataPlaceToString(wikidataPlace: WikidataPlace?): String? {
|
||||
return writeObjectToString(wikidataPlace)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun stringToWikidataPlace(wikidataPlace: String?): WikidataPlace? {
|
||||
return readObjectFromString(wikidataPlace, WikidataPlace::class.java)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun chunkInfoToString(chunkInfo: ChunkInfo?): String? {
|
||||
return writeObjectToString(chunkInfo)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun stringToChunkInfo(chunkInfo: String?): ChunkInfo? {
|
||||
return readObjectFromString(chunkInfo, ChunkInfo::class.java)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun depictionListToString(depictedItems: List<DepictedItem>?): String? {
|
||||
return writeObjectToString(depictedItems)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun stringToList(depictedItems: String?): List<DepictedItem>? {
|
||||
return readObjectWithTypeToken(depictedItems, object : TypeToken<List<DepictedItem>>() {})
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun sitelinksFromString(value: String?): Sitelinks? {
|
||||
val type = object : TypeToken<Sitelinks>() {}.type
|
||||
return Gson().fromJson(value, type)
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
@JvmStatic
|
||||
fun fromSitelinks(sitelinks: Sitelinks?): String? {
|
||||
return Gson().toJson(sitelinks)
|
||||
}
|
||||
|
||||
private fun writeObjectToString(`object`: Any?): String? {
|
||||
return `object`?.let { getGson().toJson(it) }
|
||||
}
|
||||
|
||||
private fun <T> readObjectFromString(objectAsString: String?, clazz: Class<T>): T? {
|
||||
return objectAsString?.let { getGson().fromJson(it, clazz) }
|
||||
}
|
||||
|
||||
private fun <T> readObjectWithTypeToken(objectList: String?, typeToken: TypeToken<T>): T? {
|
||||
return objectList?.let { getGson().fromJson(it, typeToken.type) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,278 +0,0 @@
|
|||
package fr.free.nrw.commons.delete;
|
||||
|
||||
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_DELETE;
|
||||
import static fr.free.nrw.commons.utils.LangCodeUtils.getLocalizedResources;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.actions.PageEditClient;
|
||||
import fr.free.nrw.commons.auth.csrf.InvalidLoginTokenException;
|
||||
import fr.free.nrw.commons.notification.NotificationHelper;
|
||||
import fr.free.nrw.commons.review.ReviewController;
|
||||
import fr.free.nrw.commons.utils.LangCodeUtils;
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
||||
import io.reactivex.Observable;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.SingleSource;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Callable;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import javax.inject.Singleton;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Refactored async task to Rx
|
||||
*/
|
||||
@Singleton
|
||||
public class DeleteHelper {
|
||||
private final NotificationHelper notificationHelper;
|
||||
private final PageEditClient pageEditClient;
|
||||
private final ViewUtilWrapper viewUtil;
|
||||
private final String username;
|
||||
private AlertDialog d;
|
||||
private DialogInterface.OnMultiChoiceClickListener listener;
|
||||
|
||||
@Inject
|
||||
public DeleteHelper(NotificationHelper notificationHelper,
|
||||
@Named("commons-page-edit") PageEditClient pageEditClient,
|
||||
ViewUtilWrapper viewUtil,
|
||||
@Named("username") String username) {
|
||||
this.notificationHelper = notificationHelper;
|
||||
this.pageEditClient = pageEditClient;
|
||||
this.viewUtil = viewUtil;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public interface to nominate a particular media file for deletion
|
||||
* @param context
|
||||
* @param media
|
||||
* @param reason
|
||||
* @return
|
||||
*/
|
||||
public Single<Boolean> makeDeletion(Context context, Media media, String reason) {
|
||||
viewUtil.showShortToast(context, "Trying to nominate " + media.getDisplayTitle() + " for deletion");
|
||||
|
||||
return delete(media, reason)
|
||||
.flatMapSingle(result -> Single.just(showDeletionNotification(context, media, result)))
|
||||
.firstOrError()
|
||||
.onErrorResumeNext(throwable -> {
|
||||
if (throwable instanceof InvalidLoginTokenException) {
|
||||
return Single.error(throwable);
|
||||
}
|
||||
return Single.error(throwable);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes several API calls to nominate the file for deletion
|
||||
* @param media
|
||||
* @param reason
|
||||
* @return
|
||||
*/
|
||||
private Observable<Boolean> delete(Media media, String reason) {
|
||||
Timber.d("thread is delete %s", Thread.currentThread().getName());
|
||||
String summary = "Nominating " + media.getFilename() + " for deletion.";
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
String fileDeleteString = "{{delete|reason=" + reason +
|
||||
"|subpage=" + media.getFilename() +
|
||||
"|day=" + calendar.get(Calendar.DAY_OF_MONTH) +
|
||||
"|month=" + calendar.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.ENGLISH) +
|
||||
"|year=" + calendar.get(Calendar.YEAR) +
|
||||
"}}";
|
||||
|
||||
String subpageString = "=== [[:" + media.getFilename() + "]] ===\n" +
|
||||
reason +
|
||||
" ~~~~";
|
||||
|
||||
String logPageString = "\n{{Commons:Deletion requests/" + media.getFilename() +
|
||||
"}}\n";
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault());
|
||||
String date = sdf.format(calendar.getTime());
|
||||
|
||||
String userPageString = "\n{{subst:idw|" + media.getFilename() +
|
||||
"}} ~~~~";
|
||||
|
||||
String creator = media.getAuthor();
|
||||
if (creator == null || creator.isEmpty()) {
|
||||
throw new RuntimeException("Failed to nominate for deletion");
|
||||
}
|
||||
|
||||
return pageEditClient.prependEdit(media.getFilename(), fileDeleteString + "\n", summary)
|
||||
.onErrorResumeNext(throwable -> {
|
||||
if (throwable instanceof InvalidLoginTokenException) {
|
||||
return Observable.error(throwable);
|
||||
}
|
||||
return Observable.error(throwable);
|
||||
})
|
||||
.flatMap(result -> {
|
||||
if (result) {
|
||||
return pageEditClient.edit("Commons:Deletion_requests/" + media.getFilename(), subpageString + "\n", summary);
|
||||
}
|
||||
return Observable.error(new RuntimeException("Failed to nominate for deletion"));
|
||||
})
|
||||
.flatMap(result -> {
|
||||
if (result) {
|
||||
return pageEditClient.appendEdit("Commons:Deletion_requests/" + date, logPageString + "\n", summary);
|
||||
}
|
||||
return Observable.error(new RuntimeException("Failed to nominate for deletion"));
|
||||
})
|
||||
.flatMap(result -> {
|
||||
if (result) {
|
||||
return pageEditClient.appendEdit("User_Talk:" + creator, userPageString + "\n", summary);
|
||||
}
|
||||
return Observable.error(new RuntimeException("Failed to nominate for deletion"));
|
||||
});
|
||||
}
|
||||
|
||||
private boolean showDeletionNotification(Context context, Media media, boolean result) {
|
||||
String message;
|
||||
String title = context.getString(R.string.delete_helper_show_deletion_title);
|
||||
|
||||
if (result) {
|
||||
title += ": " + context.getString(R.string.delete_helper_show_deletion_title_success);
|
||||
message = context.getString((R.string.delete_helper_show_deletion_message_if),media.getDisplayTitle());
|
||||
} else {
|
||||
title += ": " + context.getString(R.string.delete_helper_show_deletion_title_failed);
|
||||
message = context.getString(R.string.delete_helper_show_deletion_message_else) ;
|
||||
}
|
||||
|
||||
String urlForDelete = BuildConfig.COMMONS_URL + "/wiki/Commons:Deletion_requests/" + media.getFilename();
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForDelete));
|
||||
notificationHelper.showNotification(context, title, message, NOTIFICATION_DELETE, browserIntent);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a reason needs to be asked before nominating for deletion
|
||||
* @param media
|
||||
* @param context
|
||||
* @param question
|
||||
* @param problem
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
public void askReasonAndExecute(Media media,
|
||||
Context context,
|
||||
String question,
|
||||
ReviewController.DeleteReason problem,
|
||||
ReviewController.ReviewCallback reviewCallback) {
|
||||
AlertDialog.Builder alert = new AlertDialog.Builder(context);
|
||||
alert.setTitle(question);
|
||||
|
||||
boolean[] checkedItems = {false, false, false, false};
|
||||
ArrayList<Integer> mUserReason = new ArrayList<>();
|
||||
|
||||
final String[] reasonList;
|
||||
final String[] reasonListEnglish;
|
||||
|
||||
if (problem == ReviewController.DeleteReason.SPAM) {
|
||||
reasonList = new String[] {
|
||||
context.getString(R.string.delete_helper_ask_spam_selfie),
|
||||
context.getString(R.string.delete_helper_ask_spam_blurry),
|
||||
context.getString(R.string.delete_helper_ask_spam_nonsense)
|
||||
};
|
||||
reasonListEnglish = new String[] {
|
||||
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_spam_selfie),
|
||||
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_spam_blurry),
|
||||
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_spam_nonsense)
|
||||
};
|
||||
} else if (problem == ReviewController.DeleteReason.COPYRIGHT_VIOLATION) {
|
||||
reasonList = new String[] {
|
||||
context.getString(R.string.delete_helper_ask_reason_copyright_press_photo),
|
||||
context.getString(R.string.delete_helper_ask_reason_copyright_internet_photo),
|
||||
context.getString(R.string.delete_helper_ask_reason_copyright_logo),
|
||||
context.getString(R.string.delete_helper_ask_reason_copyright_no_freedom_of_panorama)
|
||||
};
|
||||
reasonListEnglish = new String[] {
|
||||
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_reason_copyright_press_photo),
|
||||
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_reason_copyright_internet_photo),
|
||||
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_reason_copyright_logo),
|
||||
getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_reason_copyright_no_freedom_of_panorama)
|
||||
};
|
||||
} else {
|
||||
reasonList = new String[] {};
|
||||
reasonListEnglish = new String[] {};
|
||||
}
|
||||
|
||||
alert.setMultiChoiceItems(reasonList, checkedItems, listener = (dialogInterface, position, isChecked) -> {
|
||||
|
||||
if (isChecked) {
|
||||
mUserReason.add(position);
|
||||
} else {
|
||||
mUserReason.remove((Integer.valueOf(position)));
|
||||
}
|
||||
|
||||
// disable the OK button if no reason selected
|
||||
((AlertDialog) dialogInterface).getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(
|
||||
!mUserReason.isEmpty());
|
||||
});
|
||||
|
||||
alert.setPositiveButton(context.getString(R.string.ok), (dialogInterface, i) -> {
|
||||
reviewCallback.disableButtons();
|
||||
|
||||
|
||||
String reason = getLocalizedResources(context, Locale.ENGLISH).getString(R.string.delete_helper_ask_alert_set_positive_button_reason) + " ";
|
||||
|
||||
for (int j = 0; j < mUserReason.size(); j++) {
|
||||
reason = reason + reasonListEnglish[mUserReason.get(j)];
|
||||
if (j != mUserReason.size() - 1) {
|
||||
reason = reason + ", ";
|
||||
}
|
||||
}
|
||||
|
||||
Timber.d("thread is askReasonAndExecute %s", Thread.currentThread().getName());
|
||||
|
||||
String finalReason = reason;
|
||||
|
||||
Single.defer((Callable<SingleSource<Boolean>>) () ->
|
||||
makeDeletion(context, media, finalReason))
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(aBoolean -> {
|
||||
reviewCallback.onSuccess();
|
||||
}, throwable -> {
|
||||
if (throwable instanceof InvalidLoginTokenException) {
|
||||
reviewCallback.onTokenException((InvalidLoginTokenException) throwable);
|
||||
} else {
|
||||
reviewCallback.onFailure();
|
||||
}
|
||||
reviewCallback.enableButtons();
|
||||
});
|
||||
});
|
||||
alert.setNegativeButton(context.getString(R.string.cancel), (dialog, which) -> reviewCallback.onFailure());
|
||||
d = alert.create();
|
||||
d.show();
|
||||
|
||||
// disable the OK button by default
|
||||
d.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the instance of shown AlertDialog,
|
||||
* used for taking reference during unit test
|
||||
* */
|
||||
public AlertDialog getDialog(){
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the instance of shown DialogInterface.OnMultiChoiceClickListener,
|
||||
* used for taking reference during unit test
|
||||
* */
|
||||
public DialogInterface.OnMultiChoiceClickListener getListener(){
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
334
app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.kt
Normal file
334
app/src/main/java/fr/free/nrw/commons/delete/DeleteHelper.kt
Normal file
|
|
@ -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<Boolean>? {
|
||||
|
||||
if(context == null && media == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
viewUtil.showShortToast(
|
||||
context!!,
|
||||
"Trying to nominate ${media?.displayTitle} for deletion"
|
||||
)
|
||||
|
||||
return reason?.let {
|
||||
delete(media!!, it)
|
||||
.flatMapSingle { result ->
|
||||
Single.just(showDeletionNotification(context, media, result))
|
||||
}
|
||||
.firstOrError()
|
||||
.onErrorResumeNext { throwable ->
|
||||
if (throwable is InvalidLoginTokenException) {
|
||||
Single.error(throwable)
|
||||
} else {
|
||||
Single.error(throwable)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes several API calls to nominate the file for deletion
|
||||
* @param media
|
||||
* @param reason
|
||||
* @return
|
||||
*/
|
||||
private fun delete(media: Media, reason: String): Observable<Boolean> {
|
||||
Timber.d("thread is delete %s", Thread.currentThread().name)
|
||||
val summary = "Nominating ${media.filename} for deletion."
|
||||
val calendar = Calendar.getInstance()
|
||||
val fileDeleteString = """
|
||||
{{delete|reason=$reason|subpage=${media.filename}|day=
|
||||
${calendar.get(Calendar.DAY_OF_MONTH)}|month=${
|
||||
calendar.getDisplayName(
|
||||
Calendar.MONTH,
|
||||
Calendar.LONG,
|
||||
Locale.ENGLISH
|
||||
)
|
||||
}|year=${calendar.get(Calendar.YEAR)}}}
|
||||
""".trimIndent()
|
||||
|
||||
val subpageString = """
|
||||
=== [[:${media.filename}]] ===
|
||||
$reason ~~~~
|
||||
""".trimIndent()
|
||||
|
||||
val logPageString = "\n{{Commons:Deletion requests/${media.filename}}}\n"
|
||||
val sdf = SimpleDateFormat("yyyy/MM/dd", Locale.getDefault())
|
||||
val date = sdf.format(calendar.time)
|
||||
|
||||
val userPageString = "\n{{subst:idw|${media.filename}}} ~~~~"
|
||||
|
||||
val creator = media.author
|
||||
?: throw RuntimeException("Failed to nominate for deletion")
|
||||
|
||||
return pageEditClient.prependEdit(
|
||||
media.filename!!,
|
||||
"$fileDeleteString\n",
|
||||
summary
|
||||
)
|
||||
.onErrorResumeNext { throwable: Throwable ->
|
||||
if (throwable is InvalidLoginTokenException) {
|
||||
Observable.error(throwable)
|
||||
} else {
|
||||
Observable.error(throwable)
|
||||
}
|
||||
}
|
||||
.flatMap { result: Boolean ->
|
||||
if (result) {
|
||||
pageEditClient.edit(
|
||||
"Commons:Deletion_requests/${media.filename}",
|
||||
"$subpageString\n",
|
||||
summary
|
||||
)
|
||||
} else {
|
||||
Observable.error(RuntimeException("Failed to nominate for deletion"))
|
||||
}
|
||||
}
|
||||
.flatMap { result: Boolean ->
|
||||
if (result) {
|
||||
pageEditClient.appendEdit(
|
||||
"Commons:Deletion_requests/$date",
|
||||
"$logPageString\n",
|
||||
summary
|
||||
)
|
||||
} else {
|
||||
Observable.error(RuntimeException("Failed to nominate for deletion"))
|
||||
}
|
||||
}
|
||||
.flatMap { result: Boolean ->
|
||||
if (result) {
|
||||
pageEditClient.appendEdit("User_Talk:$creator", "$userPageString\n", summary)
|
||||
} else {
|
||||
Observable.error(RuntimeException("Failed to nominate for deletion"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
private fun showDeletionNotification(
|
||||
context: Context,
|
||||
media: Media,
|
||||
result: Boolean
|
||||
): Boolean {
|
||||
val title: String
|
||||
val message: String
|
||||
var baseTitle = context.getString(R.string.delete_helper_show_deletion_title)
|
||||
|
||||
if (result) {
|
||||
baseTitle += ": ${
|
||||
context.getString(R.string.delete_helper_show_deletion_title_success)
|
||||
}"
|
||||
title = baseTitle
|
||||
message = context
|
||||
.getString(R.string.delete_helper_show_deletion_message_if, media.displayTitle)
|
||||
} else {
|
||||
baseTitle += ": ${context.getString(R.string.delete_helper_show_deletion_title_failed)}"
|
||||
title = baseTitle
|
||||
message = context.getString(R.string.delete_helper_show_deletion_message_else)
|
||||
}
|
||||
|
||||
val urlForDelete = "${BuildConfig.COMMONS_URL}/wiki/Commons:Deletion_requests/${
|
||||
media.filename
|
||||
}"
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(urlForDelete))
|
||||
notificationHelper
|
||||
.showNotification(context, title, message, NOTIFICATION_DELETE, browserIntent)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked when a reason needs to be asked before nominating for deletion
|
||||
* @param media
|
||||
* @param context
|
||||
* @param question
|
||||
* @param problem
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
fun askReasonAndExecute(
|
||||
media: Media?,
|
||||
context: Context,
|
||||
question: String,
|
||||
problem: ReviewController.DeleteReason,
|
||||
reviewCallback: ReviewController.ReviewCallback
|
||||
) {
|
||||
val alert = AlertDialog.Builder(context)
|
||||
alert.setCancelable(false)
|
||||
alert.setTitle(question)
|
||||
|
||||
val checkedItems = booleanArrayOf(false, false, false, false)
|
||||
val mUserReason = arrayListOf<Int>()
|
||||
|
||||
val reasonList: Array<String>
|
||||
val reasonListEnglish: Array<String>
|
||||
|
||||
when (problem) {
|
||||
ReviewController.DeleteReason.SPAM -> {
|
||||
reasonList = arrayOf(
|
||||
context.getString(R.string.delete_helper_ask_spam_selfie),
|
||||
context.getString(R.string.delete_helper_ask_spam_blurry),
|
||||
context.getString(R.string.delete_helper_ask_spam_nonsense)
|
||||
)
|
||||
reasonListEnglish = arrayOf(
|
||||
getLocalizedResources(context, Locale.ENGLISH)
|
||||
.getString(R.string.delete_helper_ask_spam_selfie),
|
||||
getLocalizedResources(context, Locale.ENGLISH)
|
||||
.getString(R.string.delete_helper_ask_spam_blurry),
|
||||
getLocalizedResources(context, Locale.ENGLISH)
|
||||
.getString(R.string.delete_helper_ask_spam_nonsense)
|
||||
)
|
||||
}
|
||||
ReviewController.DeleteReason.COPYRIGHT_VIOLATION -> {
|
||||
reasonList = arrayOf(
|
||||
context.getString(R.string.delete_helper_ask_reason_copyright_press_photo),
|
||||
context.getString(R.string.delete_helper_ask_reason_copyright_internet_photo),
|
||||
context.getString(R.string.delete_helper_ask_reason_copyright_logo),
|
||||
context.getString(
|
||||
R.string.delete_helper_ask_reason_copyright_no_freedom_of_panorama
|
||||
)
|
||||
)
|
||||
reasonListEnglish = arrayOf(
|
||||
getLocalizedResources(context, Locale.ENGLISH)
|
||||
.getString(R.string.delete_helper_ask_reason_copyright_press_photo),
|
||||
getLocalizedResources(context, Locale.ENGLISH)
|
||||
.getString(R.string.delete_helper_ask_reason_copyright_internet_photo),
|
||||
getLocalizedResources(context, Locale.ENGLISH)
|
||||
.getString(R.string.delete_helper_ask_reason_copyright_logo),
|
||||
getLocalizedResources(context, Locale.ENGLISH)
|
||||
.getString(
|
||||
R.string.delete_helper_ask_reason_copyright_no_freedom_of_panorama
|
||||
)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
reasonList = emptyArray()
|
||||
reasonListEnglish = emptyArray()
|
||||
}
|
||||
}
|
||||
|
||||
alert.setMultiChoiceItems(
|
||||
reasonList,
|
||||
checkedItems
|
||||
) { dialogInterface, position, isChecked ->
|
||||
if (isChecked) {
|
||||
mUserReason.add(position)
|
||||
} else {
|
||||
mUserReason.remove(position)
|
||||
}
|
||||
|
||||
// Safely enable or disable the OK button based on selection
|
||||
val dialog = dialogInterface as? AlertDialog
|
||||
dialog?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = mUserReason.isNotEmpty()
|
||||
}
|
||||
|
||||
alert.setPositiveButton(context.getString(R.string.ok)) { _, _ ->
|
||||
reviewCallback.disableButtons()
|
||||
|
||||
val reason = buildString {
|
||||
append(
|
||||
getLocalizedResources(context, Locale.ENGLISH)
|
||||
.getString(R.string.delete_helper_ask_alert_set_positive_button_reason)
|
||||
)
|
||||
append(" ")
|
||||
|
||||
mUserReason.forEachIndexed { index, position ->
|
||||
append(reasonListEnglish[position])
|
||||
if (index != mUserReason.lastIndex) {
|
||||
append(", ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Timber.d("thread is askReasonAndExecute %s", Thread.currentThread().name)
|
||||
|
||||
if (media != null) {
|
||||
Single.defer { makeDeletion(context, media, reason) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ reviewCallback.onSuccess() }, { throwable ->
|
||||
when (throwable) {
|
||||
is InvalidLoginTokenException ->
|
||||
reviewCallback.onTokenException(throwable)
|
||||
else -> reviewCallback.onFailure()
|
||||
}
|
||||
reviewCallback.enableButtons()
|
||||
})
|
||||
}
|
||||
}
|
||||
alert.setNegativeButton(
|
||||
context.getString(R.string.cancel)
|
||||
) { _, _ -> reviewCallback.onFailure() }
|
||||
|
||||
d = alert.create()
|
||||
d?.setOnShowListener {
|
||||
// Safely initialize the OK button state after the dialog is fully shown
|
||||
d?.getButton(AlertDialog.BUTTON_POSITIVE)?.isEnabled = false
|
||||
}
|
||||
d?.show()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* returns the instance of shown AlertDialog,
|
||||
* used for taking reference during unit test
|
||||
*/
|
||||
fun getDialog(): AlertDialog? = d
|
||||
|
||||
/**
|
||||
* returns the instance of shown DialogInterface.OnMultiChoiceClickListener,
|
||||
* used for taking reference during unit test
|
||||
*/
|
||||
fun getListener(): DialogInterface.OnMultiChoiceClickListener? = listener
|
||||
}
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
package fr.free.nrw.commons.delete;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import fr.free.nrw.commons.utils.DateUtil;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.profile.achievements.FeedbackResponse;
|
||||
import fr.free.nrw.commons.auth.SessionManager;
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
||||
import io.reactivex.Single;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* This class handles the reason for deleting a Media object
|
||||
*/
|
||||
@Singleton
|
||||
public class ReasonBuilder {
|
||||
|
||||
private SessionManager sessionManager;
|
||||
private OkHttpJsonApiClient okHttpJsonApiClient;
|
||||
private Context context;
|
||||
private ViewUtilWrapper viewUtilWrapper;
|
||||
|
||||
@Inject
|
||||
public ReasonBuilder(Context context,
|
||||
SessionManager sessionManager,
|
||||
OkHttpJsonApiClient okHttpJsonApiClient,
|
||||
ViewUtilWrapper viewUtilWrapper) {
|
||||
this.context = context;
|
||||
this.sessionManager = sessionManager;
|
||||
this.okHttpJsonApiClient = okHttpJsonApiClient;
|
||||
this.viewUtilWrapper = viewUtilWrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* To process the reason and append the media's upload date and uploaded_by_me string
|
||||
* @param media
|
||||
* @param reason
|
||||
* @return
|
||||
*/
|
||||
public Single<String> getReason(Media media, String reason) {
|
||||
return fetchArticleNumber(media, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* get upload date for the passed Media
|
||||
*/
|
||||
private String prettyUploadedDate(Media media) {
|
||||
Date date = media.getDateUploaded();
|
||||
if (date == null || date.toString() == null || date.toString().isEmpty()) {
|
||||
return "Uploaded date not available";
|
||||
}
|
||||
return DateUtil.getDateStringWithSkeletonPattern(date,"dd MMM yyyy");
|
||||
}
|
||||
|
||||
private Single<String> fetchArticleNumber(Media media, String reason) {
|
||||
if (checkAccount()) {
|
||||
return okHttpJsonApiClient
|
||||
.getAchievements(sessionManager.getUserName())
|
||||
.map(feedbackResponse -> appendArticlesUsed(feedbackResponse, media, reason));
|
||||
}
|
||||
return Single.just("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the uploaded_by_me string, the upload date, name of articles using images
|
||||
* and appends it to the received reason
|
||||
* @param feedBack object
|
||||
* @param media whose upload data is to be fetched
|
||||
* @param reason
|
||||
*/
|
||||
private String appendArticlesUsed(FeedbackResponse feedBack, Media media, String reason) {
|
||||
String reason1Template = context.getString(R.string.uploaded_by_myself);
|
||||
reason += String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), feedBack.getArticlesUsingImages());
|
||||
Timber.i("New Reason %s", reason);
|
||||
return reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* check to ensure that user is logged in
|
||||
* @return
|
||||
*/
|
||||
private boolean checkAccount(){
|
||||
if (!sessionManager.doesAccountExist()) {
|
||||
Timber.d("Current account is null");
|
||||
viewUtilWrapper.showLongToast(context, context.getResources().getString(R.string.user_not_logged_in));
|
||||
sessionManager.forceLogin(context);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package fr.free.nrw.commons.delete
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
|
||||
import fr.free.nrw.commons.utils.DateUtil
|
||||
import java.util.Locale
|
||||
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.profile.achievements.FeedbackResponse
|
||||
import fr.free.nrw.commons.auth.SessionManager
|
||||
import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper
|
||||
import io.reactivex.Single
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* This class handles the reason for deleting a Media object
|
||||
*/
|
||||
@Singleton
|
||||
class ReasonBuilder @Inject constructor(
|
||||
private val context: Context,
|
||||
private val sessionManager: SessionManager,
|
||||
private val okHttpJsonApiClient: OkHttpJsonApiClient,
|
||||
private val viewUtilWrapper: ViewUtilWrapper
|
||||
) {
|
||||
|
||||
/**
|
||||
* To process the reason and append the media's upload date and uploaded_by_me string
|
||||
* @param media
|
||||
* @param reason
|
||||
* @return
|
||||
*/
|
||||
fun getReason(media: Media?, reason: String?): Single<String> {
|
||||
if (media == null || reason == null) {
|
||||
return Single.just("Not known")
|
||||
}
|
||||
return fetchArticleNumber(media, reason)
|
||||
}
|
||||
|
||||
/**
|
||||
* get upload date for the passed Media
|
||||
*/
|
||||
private fun prettyUploadedDate(media: Media): String {
|
||||
val date = media.dateUploaded
|
||||
return if (date == null || date.toString().isEmpty()) {
|
||||
"Uploaded date not available"
|
||||
} else {
|
||||
DateUtil.getDateStringWithSkeletonPattern(date, "dd MMM yyyy")
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchArticleNumber(media: Media, reason: String): Single<String> {
|
||||
return if (checkAccount()) {
|
||||
okHttpJsonApiClient
|
||||
.getAchievements(sessionManager.userName)
|
||||
.map { feedbackResponse -> appendArticlesUsed(feedbackResponse, media, reason) }
|
||||
} else {
|
||||
Single.just("")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the uploaded_by_me string, the upload date, name of articles using images
|
||||
* and appends it to the received reason
|
||||
* @param feedBack object
|
||||
* @param media whose upload data is to be fetched
|
||||
* @param reason
|
||||
*/
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
private fun appendArticlesUsed(feedBack: FeedbackResponse, media: Media, reason: String): String {
|
||||
val reason1Template = context.getString(R.string.uploaded_by_myself)
|
||||
return reason + String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), feedBack.articlesUsingImages)
|
||||
.also { Timber.i("New Reason %s", it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* check to ensure that user is logged in
|
||||
* @return
|
||||
*/
|
||||
private fun checkAccount(): Boolean {
|
||||
return if (!sessionManager.doesAccountExist()) {
|
||||
Timber.d("Current account is null")
|
||||
viewUtilWrapper.showLongToast(context, context.getString(R.string.user_not_logged_in))
|
||||
sessionManager.forceLogin(context)
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,137 +0,0 @@
|
|||
package fr.free.nrw.commons.description;
|
||||
|
||||
import static fr.free.nrw.commons.notification.NotificationHelper.NOTIFICATION_EDIT_DESCRIPTION;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import fr.free.nrw.commons.BuildConfig;
|
||||
import fr.free.nrw.commons.Media;
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.actions.PageEditClient;
|
||||
import fr.free.nrw.commons.notification.NotificationHelper;
|
||||
import io.reactivex.Single;
|
||||
import java.util.Objects;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
import timber.log.Timber;
|
||||
|
||||
/**
|
||||
* Helper class for edit and update given descriptions and showing notification upgradation
|
||||
*/
|
||||
public class DescriptionEditHelper {
|
||||
|
||||
/**
|
||||
* notificationHelper: helps creating notification
|
||||
*/
|
||||
private final NotificationHelper notificationHelper;
|
||||
/**
|
||||
* * pageEditClient: methods provided by this member posts the edited descriptions
|
||||
* to the Media wiki api
|
||||
*/
|
||||
public final PageEditClient pageEditClient;
|
||||
|
||||
@Inject
|
||||
public DescriptionEditHelper(final NotificationHelper notificationHelper,
|
||||
@Named("commons-page-edit") final PageEditClient pageEditClient) {
|
||||
this.notificationHelper = notificationHelper;
|
||||
this.pageEditClient = pageEditClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces new descriptions
|
||||
*
|
||||
* @param context context
|
||||
* @param media to be added
|
||||
* @param appendText to be added
|
||||
* @return Observable<Boolean>
|
||||
*/
|
||||
public Single<Boolean> addDescription(final Context context, final Media media,
|
||||
final String appendText) {
|
||||
Timber.d("thread is description adding %s", Thread.currentThread().getName());
|
||||
final String summary = "Updating Description";
|
||||
|
||||
return pageEditClient.edit(Objects.requireNonNull(media.getFilename()),
|
||||
appendText, summary)
|
||||
.flatMapSingle(result -> Single.just(showDescriptionEditNotification(context,
|
||||
media, result)))
|
||||
.firstOrError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new captions
|
||||
*
|
||||
* @param context context
|
||||
* @param media to be added
|
||||
* @param language to be added
|
||||
* @param value to be added
|
||||
* @return Observable<Boolean>
|
||||
*/
|
||||
public Single<Boolean> addCaption(final Context context, final Media media,
|
||||
final String language, final String value) {
|
||||
Timber.d("thread is caption adding %s", Thread.currentThread().getName());
|
||||
final String summary = "Updating Caption";
|
||||
|
||||
return pageEditClient.setCaptions(summary, Objects.requireNonNull(media.getFilename()),
|
||||
language, value)
|
||||
.flatMapSingle(result -> Single.just(showCaptionEditNotification(context,
|
||||
media, result)))
|
||||
.firstOrError();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update captions and shows notification about captions update
|
||||
* @param context to be added
|
||||
* @param media to be added
|
||||
* @param result to be added
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean showCaptionEditNotification(final Context context, final Media media,
|
||||
final int result) {
|
||||
final String message;
|
||||
String title = context.getString(R.string.caption_edit_helper_show_edit_title);
|
||||
|
||||
if (result == 1) {
|
||||
title += ": " + context
|
||||
.getString(R.string.coordinates_edit_helper_show_edit_title_success);
|
||||
message = context.getString(R.string.caption_edit_helper_show_edit_message);
|
||||
} else {
|
||||
title += ": " + context.getString(R.string.caption_edit_helper_show_edit_title);
|
||||
message = context.getString(R.string.caption_edit_helper_edit_message_else) ;
|
||||
}
|
||||
|
||||
final String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename();
|
||||
final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile));
|
||||
notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_DESCRIPTION,
|
||||
browserIntent);
|
||||
return result == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update descriptions and shows notification about descriptions update
|
||||
* @param context to be added
|
||||
* @param media to be added
|
||||
* @param result to be added
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean showDescriptionEditNotification(final Context context, final Media media,
|
||||
final boolean result) {
|
||||
final String message;
|
||||
String title = context.getString(R.string.description_edit_helper_show_edit_title);
|
||||
|
||||
if (result) {
|
||||
title += ": " + context
|
||||
.getString(R.string.coordinates_edit_helper_show_edit_title_success);
|
||||
message = context.getString(R.string.description_edit_helper_show_edit_message);
|
||||
} else {
|
||||
title += ": " + context.getString(R.string.description_edit_helper_show_edit_title);
|
||||
message = context.getString(R.string.description_edit_helper_edit_message_else) ;
|
||||
}
|
||||
|
||||
final String urlForFile = BuildConfig.COMMONS_URL + "/wiki/" + media.getFilename();
|
||||
final Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile));
|
||||
notificationHelper.showNotification(context, title, message, NOTIFICATION_EDIT_DESCRIPTION,
|
||||
browserIntent);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
package fr.free.nrw.commons.description
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import fr.free.nrw.commons.BuildConfig
|
||||
import fr.free.nrw.commons.Media
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.actions.PageEditClient
|
||||
import fr.free.nrw.commons.notification.NotificationHelper
|
||||
import fr.free.nrw.commons.notification.NotificationHelper.Companion.NOTIFICATION_EDIT_DESCRIPTION
|
||||
import io.reactivex.Single
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Named
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Helper class for edit and update given descriptions and showing notification upgradation
|
||||
*/
|
||||
class DescriptionEditHelper @Inject constructor(
|
||||
/**
|
||||
* notificationHelper: helps creating notification
|
||||
*/
|
||||
private val notificationHelper: NotificationHelper,
|
||||
|
||||
/**
|
||||
* pageEditClient: methods provided by this member posts the edited descriptions
|
||||
* to the Media wiki api
|
||||
*/
|
||||
@Named("commons-page-edit") val pageEditClient: PageEditClient
|
||||
) {
|
||||
|
||||
/**
|
||||
* Replaces new descriptions
|
||||
*
|
||||
* @param context context
|
||||
* @param media to be added
|
||||
* @param appendText to be added
|
||||
* @return Single<Boolean>
|
||||
*/
|
||||
fun addDescription(context: Context, media: Media, appendText: String): Single<Boolean> {
|
||||
Timber.d("thread is description adding %s", Thread.currentThread().name)
|
||||
val summary = "Updating Description"
|
||||
|
||||
return pageEditClient.edit(
|
||||
requireNotNull(media.filename),
|
||||
appendText,
|
||||
summary
|
||||
).flatMapSingle { result ->
|
||||
Single.just(showDescriptionEditNotification(context, media, result))
|
||||
}.firstOrError()
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds new captions
|
||||
*
|
||||
* @param context context
|
||||
* @param media to be added
|
||||
* @param language to be added
|
||||
* @param value to be added
|
||||
* @return Single<Boolean>
|
||||
*/
|
||||
fun addCaption(
|
||||
context: Context,
|
||||
media: Media,
|
||||
language: String,
|
||||
value: String
|
||||
): Single<Boolean> {
|
||||
Timber.d("thread is caption adding %s", Thread.currentThread().name)
|
||||
val summary = "Updating Caption"
|
||||
|
||||
return pageEditClient.setCaptions(
|
||||
summary,
|
||||
requireNotNull(media.filename),
|
||||
language,
|
||||
value
|
||||
).flatMapSingle { result ->
|
||||
Single.just(showCaptionEditNotification(context, media, result))
|
||||
}.firstOrError()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update captions and shows notification about captions update
|
||||
* @param context to be added
|
||||
* @param media to be added
|
||||
* @param result to be added
|
||||
* @return boolean
|
||||
*/
|
||||
private fun showCaptionEditNotification(context: Context, media: Media, result: Int): Boolean {
|
||||
val message: String
|
||||
var title = context.getString(R.string.caption_edit_helper_show_edit_title)
|
||||
|
||||
if (result == 1) {
|
||||
title += ": " + context.getString(
|
||||
R.string.coordinates_edit_helper_show_edit_title_success
|
||||
)
|
||||
message = context.getString(R.string.caption_edit_helper_show_edit_message)
|
||||
} else {
|
||||
title += ": " + context.getString(R.string.caption_edit_helper_show_edit_title)
|
||||
message = context.getString(R.string.caption_edit_helper_edit_message_else)
|
||||
}
|
||||
|
||||
val urlForFile = "${BuildConfig.COMMONS_URL}/wiki/${media.filename}"
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile))
|
||||
notificationHelper.showNotification(
|
||||
context,
|
||||
title,
|
||||
message,
|
||||
NOTIFICATION_EDIT_DESCRIPTION,
|
||||
browserIntent
|
||||
)
|
||||
return result == 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Update descriptions and shows notification about descriptions update
|
||||
* @param context to be added
|
||||
* @param media to be added
|
||||
* @param result to be added
|
||||
* @return boolean
|
||||
*/
|
||||
private fun showDescriptionEditNotification(
|
||||
context: Context,
|
||||
media: Media,
|
||||
result: Boolean
|
||||
): Boolean {
|
||||
val message: String
|
||||
var title= context.getString(
|
||||
R.string.description_edit_helper_show_edit_title
|
||||
)
|
||||
|
||||
if (result) {
|
||||
title += ": " + context.getString(
|
||||
R.string.coordinates_edit_helper_show_edit_title_success
|
||||
)
|
||||
message = context.getString(R.string.description_edit_helper_show_edit_message)
|
||||
} else {
|
||||
title += ": " + context.getString(R.string.description_edit_helper_show_edit_title)
|
||||
message = context.getString(R.string.description_edit_helper_edit_message_else)
|
||||
}
|
||||
|
||||
val urlForFile = "${BuildConfig.COMMONS_URL}/wiki/${media.filename}"
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(urlForFile))
|
||||
notificationHelper.showNotification(
|
||||
context,
|
||||
title,
|
||||
message,
|
||||
NOTIFICATION_EDIT_DESCRIPTION,
|
||||
browserIntent
|
||||
)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
@ -133,8 +133,8 @@ public class ExploreMapFragment extends CommonsDaggerSupportFragment
|
|||
askForLocationPermission();
|
||||
},
|
||||
null,
|
||||
null,
|
||||
false);
|
||||
null
|
||||
);
|
||||
} else {
|
||||
if (isPermissionDenied) {
|
||||
locationPermissionsHelper.showAppSettingsDialog(getActivity(),
|
||||
|
|
|
|||
|
|
@ -78,8 +78,7 @@ class LocationPermissionsHelper(
|
|||
activity.getString(R.string.upload_map_location_access)
|
||||
)
|
||||
},
|
||||
null,
|
||||
false
|
||||
null
|
||||
)
|
||||
} else {
|
||||
ActivityCompat.requestPermissions(
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -283,6 +283,7 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
|
|||
builder.setItems(R.array.report_violation_options, (dialog, which) -> {
|
||||
sendReportEmail(media, values[which]);
|
||||
});
|
||||
builder.setCancelable(false);
|
||||
builder.show();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,8 +291,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
askForLocationPermission();
|
||||
},
|
||||
null,
|
||||
null,
|
||||
false);
|
||||
null
|
||||
);
|
||||
} else {
|
||||
if (isPermissionDenied) {
|
||||
locationPermissionsHelper.showAppSettingsDialog(getActivity(),
|
||||
|
|
|
|||
|
|
@ -206,8 +206,8 @@ public class ProfileActivity extends BaseActivity {
|
|||
getString(R.string.cancel),
|
||||
() -> shareScreen(screenshot),
|
||||
() -> {},
|
||||
view,
|
||||
true);
|
||||
view
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ class QuizResultActivity : AppCompatActivity() {
|
|||
val shareMessage = view.findViewById<TextView>(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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -270,8 +270,7 @@ public class UploadActivity extends BaseActivity implements UploadContract.View,
|
|||
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 class UploadActivity extends BaseActivity implements UploadContract.View,
|
|||
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 class UploadActivity extends BaseActivity implements UploadContract.View,
|
|||
"",
|
||||
getString(messageResourceId),
|
||||
getString(R.string.ok),
|
||||
onPositiveClick,
|
||||
false);
|
||||
onPositiveClick);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ public class UploadMediaDetailAdapter extends
|
|||
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),
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ public class UploadCategoriesFragment extends UploadBaseFragment implements Cate
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ public class DepictsFragment extends UploadBaseFragment implements DepictsContra
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ public class MediaLicenseFragment extends UploadBaseFragment implements MediaLic
|
|||
getString(R.string.license_step_title),
|
||||
getString(R.string.license_tooltip),
|
||||
getString(android.R.string.ok),
|
||||
null, true)
|
||||
null)
|
||||
);
|
||||
|
||||
initPresenter();
|
||||
|
|
|
|||
|
|
@ -309,7 +309,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
*/
|
||||
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 class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
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 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
// Execute when user cancels the upload of the specified place
|
||||
UploadActivity.nearbyPopupAnswers.put(place, false);
|
||||
},
|
||||
customLayout, true);
|
||||
customLayout
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -526,8 +528,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP);
|
||||
onImageValidationSuccess();
|
||||
}, null,
|
||||
checkBoxView,
|
||||
false);
|
||||
checkBoxView);
|
||||
} else {
|
||||
uploadItem.setImageQuality(ImageUtils.IMAGE_KEEP);
|
||||
onImageValidationSuccess();
|
||||
|
|
@ -588,8 +589,7 @@ public class UploadMediaDetailFragment extends UploadBaseFragment implements
|
|||
basicKvStore.putBoolean(keyForShowingAlertDialog, false);
|
||||
activity.finish();
|
||||
},
|
||||
null,
|
||||
false
|
||||
null
|
||||
);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ class BookmarkPicturesFragmentUnitTests {
|
|||
GridViewAdapter(
|
||||
context,
|
||||
0,
|
||||
listOf(media()),
|
||||
mutableListOf(media()),
|
||||
),
|
||||
)
|
||||
Whitebox.setInternalState(fragment, "binding", binding)
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class GridViewAdapterUnitTest {
|
|||
private lateinit var parent: ViewGroup
|
||||
|
||||
@Mock
|
||||
private lateinit var images: List<Media>
|
||||
private lateinit var images: MutableList<Media>
|
||||
|
||||
@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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
).`when`(deleteHelper).makeDeletion(
|
||||
ArgumentMatchers.any(),
|
||||
ArgumentMatchers.any(),
|
||||
ArgumentMatchers.any()
|
||||
)
|
||||
|
||||
doReturn(Single.just("")).`when`(reasonBuilder).getReason(ArgumentMatchers.any(), ArgumentMatchers.any())
|
||||
doReturn(Single.just("")).`when`(reasonBuilder).getReason(
|
||||
ArgumentMatchers.any(),
|
||||
ArgumentMatchers.any()
|
||||
)
|
||||
|
||||
val method: Method =
|
||||
MediaDetailFragment::class.java.getDeclaredMethod(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue