#3482 Use Room in Structured Data branch - remove unused code (#3483)

* #3482 Use Room in Structured Data branch - remove unused code

* #3482 Use Room in Structured Data branch - fix unit test compilation

* #3482 Use Room in Structured Data branch - add kdoc
This commit is contained in:
Seán Mac Gillicuddy 2020-03-18 15:12:37 +00:00 committed by GitHub
parent 942cef5d5e
commit 66e195d88b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 131 additions and 754 deletions

View file

@ -107,6 +107,7 @@ dependencies {
//Room //Room
def room_version= '2.2.3' def room_version= '2.2.3'
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor kapt "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor
implementation 'com.squareup.retrofit2:retrofit:2.7.1' implementation 'com.squareup.retrofit2:retrofit:2.7.1'
implementation "androidx.room:room-rxjava2:$room_version" implementation "androidx.room:room-rxjava2:$room_version"

View file

@ -183,14 +183,7 @@
android:authorities="${applicationId}.categories.contentprovider" android:authorities="${applicationId}.categories.contentprovider"
android:exported="false" android:exported="false"
android:label="@string/provider_categories" android:label="@string/provider_categories"
android:syncable="false" /> android:syncable="false" />
<provider
android:authorities="${applicationId}.depicts.contentprovider"
android:name=".upload.structure.depictions.DepictsContentProvider"
android:exported="false"
android:label="@string/provider_depictions"
android:syncable="false"/>
<provider <provider
android:name=".explore.recentsearches.RecentSearchesContentProvider" android:name=".explore.recentsearches.RecentSearchesContentProvider"

View file

@ -6,7 +6,6 @@ import android.app.NotificationChannel;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.os.Build; import android.os.Build;
import android.os.Process; import android.os.Process;
import android.util.Log; import android.util.Log;
@ -16,10 +15,7 @@ import androidx.annotation.NonNull;
import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.core.ImagePipeline; import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.core.ImagePipelineConfig; import com.facebook.imagepipeline.core.ImagePipelineConfig;
import com.facebook.imagepipeline.producers.Consumer;
import com.facebook.imagepipeline.producers.FetchState;
import com.facebook.imagepipeline.producers.NetworkFetcher; import com.facebook.imagepipeline.producers.NetworkFetcher;
import com.facebook.imagepipeline.producers.ProducerContext;
import com.mapbox.mapboxsdk.Mapbox; import com.mapbox.mapboxsdk.Mapbox;
import com.squareup.leakcanary.LeakCanary; import com.squareup.leakcanary.LeakCanary;
import com.squareup.leakcanary.RefWatcher; import com.squareup.leakcanary.RefWatcher;
@ -45,15 +41,14 @@ import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
import fr.free.nrw.commons.category.CategoryDao; import fr.free.nrw.commons.category.CategoryDao;
import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler; import fr.free.nrw.commons.concurrency.BackgroundPoolExceptionHandler;
import fr.free.nrw.commons.concurrency.ThreadPoolService; import fr.free.nrw.commons.concurrency.ThreadPoolService;
import fr.free.nrw.commons.contributions.ContributionDao;
import fr.free.nrw.commons.data.DBOpenHelper; import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.db.AppDatabase;
import fr.free.nrw.commons.di.ApplicationlessInjection; import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.kvstore.JsonKvStore; import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.logging.FileLoggingTree; import fr.free.nrw.commons.logging.FileLoggingTree;
import fr.free.nrw.commons.logging.LogUtils; import fr.free.nrw.commons.logging.LogUtils;
import fr.free.nrw.commons.settings.Prefs; import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.FileUtils; import fr.free.nrw.commons.upload.FileUtils;
import fr.free.nrw.commons.upload.structure.depictions.DepictionDao;
import fr.free.nrw.commons.utils.ConfigUtils; import fr.free.nrw.commons.utils.ConfigUtils;
import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.internal.functions.Functions; import io.reactivex.internal.functions.Functions;
@ -129,8 +124,7 @@ public class CommonsApplication extends Application {
return languageLookUpTable; return languageLookUpTable;
} }
@Inject @Inject ContributionDao contributionDao;
AppDatabase appDatabase;
/** /**
* Used to declare and initialize various components and dependencies * Used to declare and initialize various components and dependencies
@ -312,9 +306,8 @@ public class CommonsApplication extends Application {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase(); SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
CategoryDao.Table.onDelete(db); CategoryDao.Table.onDelete(db);
DepictionDao.Table.onDelete(db);
dbOpenHelper.deleteTable(db,CONTRIBUTIONS_TABLE);//Delete the contributions table in the existing db on older versions dbOpenHelper.deleteTable(db,CONTRIBUTIONS_TABLE);//Delete the contributions table in the existing db on older versions
appDatabase.getContributionDao().deleteAll(); contributionDao.deleteAll();
BookmarkPicturesDao.Table.onDelete(db); BookmarkPicturesDao.Table.onDelete(db);
BookmarkLocationsDao.Table.onDelete(db); BookmarkLocationsDao.Table.onDelete(db);
} }

View file

@ -9,7 +9,6 @@ import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsDao;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao; import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesDao;
import fr.free.nrw.commons.category.CategoryDao; import fr.free.nrw.commons.category.CategoryDao;
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao; import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
import fr.free.nrw.commons.upload.structure.depictions.DepictionDao;
public class DBOpenHelper extends SQLiteOpenHelper { public class DBOpenHelper extends SQLiteOpenHelper {
@ -29,7 +28,6 @@ public class DBOpenHelper extends SQLiteOpenHelper {
@Override @Override
public void onCreate(SQLiteDatabase sqLiteDatabase) { public void onCreate(SQLiteDatabase sqLiteDatabase) {
CategoryDao.Table.onCreate(sqLiteDatabase); CategoryDao.Table.onCreate(sqLiteDatabase);
DepictionDao.Table.onCreate(sqLiteDatabase);
BookmarkPicturesDao.Table.onCreate(sqLiteDatabase); BookmarkPicturesDao.Table.onCreate(sqLiteDatabase);
BookmarkLocationsDao.Table.onCreate(sqLiteDatabase); BookmarkLocationsDao.Table.onCreate(sqLiteDatabase);
RecentSearchesDao.Table.onCreate(sqLiteDatabase); RecentSearchesDao.Table.onCreate(sqLiteDatabase);
@ -38,7 +36,6 @@ public class DBOpenHelper extends SQLiteOpenHelper {
@Override @Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int from, int to) { public void onUpgrade(SQLiteDatabase sqLiteDatabase, int from, int to) {
CategoryDao.Table.onUpdate(sqLiteDatabase, from, to); CategoryDao.Table.onUpdate(sqLiteDatabase, from, to);
DepictionDao.Table.onUpdate(sqLiteDatabase, from, to);
BookmarkPicturesDao.Table.onUpdate(sqLiteDatabase, from, to); BookmarkPicturesDao.Table.onUpdate(sqLiteDatabase, from, to);
BookmarkLocationsDao.Table.onUpdate(sqLiteDatabase, from, to); BookmarkLocationsDao.Table.onUpdate(sqLiteDatabase, from, to);
RecentSearchesDao.Table.onUpdate(sqLiteDatabase, from, to); RecentSearchesDao.Table.onUpdate(sqLiteDatabase, from, to);

View file

@ -1,14 +0,0 @@
package fr.free.nrw.commons.db;
import androidx.room.Database;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.contributions.ContributionDao;
@Database(entities = {Contribution.class}, version = 1, exportSchema = false)
@TypeConverters({Converters.class})
abstract public class AppDatabase extends RoomDatabase {
public abstract ContributionDao getContributionDao();
}

View file

@ -0,0 +1,17 @@
package fr.free.nrw.commons.db
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.contributions.ContributionDao
/**
* The database for accessing the respective DAOs
*
*/
@Database(entities = [Contribution::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun contributionDao(): ContributionDao
}

View file

@ -55,7 +55,6 @@ public class CommonsApplicationModule {
private Context applicationContext; private Context applicationContext;
public static final String IO_THREAD="io_thread"; public static final String IO_THREAD="io_thread";
public static final String MAIN_THREAD="main_thread"; public static final String MAIN_THREAD="main_thread";
private AppDatabase appDatabase;
public CommonsApplicationModule(Context applicationContext) { public CommonsApplicationModule(Context applicationContext) {
this.applicationContext = applicationContext; this.applicationContext = applicationContext;
@ -247,12 +246,11 @@ public class CommonsApplicationModule {
@Provides @Provides
@Singleton @Singleton
public AppDatabase provideAppDataBase() { public AppDatabase provideAppDataBase() {
appDatabase=Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db").build(); return Room.databaseBuilder(applicationContext, AppDatabase.class, "commons_room.db").build();
return appDatabase;
} }
@Provides @Provides
public ContributionDao providesContributionsDao() { public ContributionDao providesContributionsDao(AppDatabase appDatabase) {
return appDatabase.getContributionDao(); return appDatabase.contributionDao();
} }
} }

View file

@ -6,30 +6,25 @@ import fr.free.nrw.commons.bookmarks.locations.BookmarkLocationsContentProvider;
import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider; import fr.free.nrw.commons.bookmarks.pictures.BookmarkPicturesContentProvider;
import fr.free.nrw.commons.category.CategoryContentProvider; import fr.free.nrw.commons.category.CategoryContentProvider;
import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider; import fr.free.nrw.commons.explore.recentsearches.RecentSearchesContentProvider;
import fr.free.nrw.commons.upload.structure.depictions.DepictsContentProvider;
/** /**
* This Class Represents the Module for dependency injection (using dagger) * This Class Represents the Module for dependency injection (using dagger)
* so, if a developer needs to add a new ContentProvider to the commons app * so, if a developer needs to add a new ContentProvider to the commons app
* then that must be mentioned here to inject the dependencies * then that must be mentioned here to inject the dependencies
*/ */
@Module @Module
@SuppressWarnings({"WeakerAccess", "unused"}) @SuppressWarnings({ "WeakerAccess", "unused" })
public abstract class ContentProviderBuilderModule { public abstract class ContentProviderBuilderModule {
@ContributesAndroidInjector @ContributesAndroidInjector
abstract CategoryContentProvider bindCategoryContentProvider(); abstract CategoryContentProvider bindCategoryContentProvider();
@ContributesAndroidInjector @ContributesAndroidInjector
abstract DepictsContentProvider bindDepictsContentProvider(); abstract RecentSearchesContentProvider bindRecentSearchesContentProvider();
@ContributesAndroidInjector @ContributesAndroidInjector
abstract RecentSearchesContentProvider bindRecentSearchesContentProvider(); abstract BookmarkPicturesContentProvider bindBookmarkContentProvider();
@ContributesAndroidInjector
abstract BookmarkPicturesContentProvider bindBookmarkContentProvider();
@ContributesAndroidInjector
abstract BookmarkLocationsContentProvider bindBookmarkLocationContentProvider();
@ContributesAndroidInjector
abstract BookmarkLocationsContentProvider bindBookmarkLocationContentProvider();
} }

View file

@ -58,7 +58,7 @@ public class DepictsClient {
String.valueOf(offset) String.valueOf(offset)
) )
.flatMap(depictSearchResponse ->Observable.fromIterable(depictSearchResponse.getSearch())) .flatMap(depictSearchResponse ->Observable.fromIterable(depictSearchResponse.getSearch()))
.map(depictSearchItem -> new DepictedItem(depictSearchItem.getLabel(), depictSearchItem.getDescription(), "", false, depictSearchItem.getId())); .map(DepictedItem::new);
} }
/** /**

View file

@ -21,7 +21,6 @@ import fr.free.nrw.commons.upload.UploadModel;
import fr.free.nrw.commons.upload.UploadModel.UploadItem; import fr.free.nrw.commons.upload.UploadModel.UploadItem;
import fr.free.nrw.commons.upload.structure.depictions.DepictModel; import fr.free.nrw.commons.upload.structure.depictions.DepictModel;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import io.reactivex.Observable; import io.reactivex.Observable;
import io.reactivex.Single; import io.reactivex.Single;
@ -230,8 +229,8 @@ public class UploadRemoteDataSource {
* get all depictions * get all depictions
*/ */
public Observable<DepictedItem> searchAllEntities(String query, List<String> imageTitleList) { public Observable<DepictedItem> searchAllEntities(String query) {
return depictModel.searchAllEntities(query, imageTitleList); return depictModel.searchAllEntities(query);
} }
public void setSelectedDepictions(List<String> selectedDepictions) { public void setSelectedDepictions(List<String> selectedDepictions) {

View file

@ -287,12 +287,11 @@ public class UploadRepository {
* Search all depictions from * Search all depictions from
* *
* @param query * @param query
* @param imageTitleList
* @return * @return
*/ */
public Observable<DepictedItem> searchAllEntities(String query, List<String> imageTitleList) { public Observable<DepictedItem> searchAllEntities(String query) {
return remoteDataSource.searchAllEntities(query, imageTitleList); return remoteDataSource.searchAllEntities(query);
} }
public List<String> getDepictionsEntityIdList() { public List<String> getDepictionsEntityIdList() {

View file

@ -3,8 +3,6 @@ package fr.free.nrw.commons.upload.depicts;
import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD; import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD;
import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD; import static fr.free.nrw.commons.di.CommonsApplicationModule.MAIN_THREAD;
import android.util.Log;
import fr.free.nrw.commons.explore.depictions.DepictsClient; import fr.free.nrw.commons.explore.depictions.DepictsClient;
import fr.free.nrw.commons.repository.UploadRepository; import fr.free.nrw.commons.repository.UploadRepository;
import fr.free.nrw.commons.upload.UploadModel; import fr.free.nrw.commons.upload.UploadModel;
@ -85,7 +83,6 @@ public class DepictsPresenter implements DepictsContract.UserActionListener {
@Override @Override
public void searchForDepictions(String query) { public void searchForDepictions(String query) {
List<DepictedItem> depictedItemList = new ArrayList<>(); List<DepictedItem> depictedItemList = new ArrayList<>();
List<String> imageTitleList = getImageTitleList();
Observable<DepictedItem> distinctDepictsObservable = Observable Observable<DepictedItem> distinctDepictsObservable = Observable
.fromIterable(repository.getSelectedDepictions()) .fromIterable(repository.getSelectedDepictions())
.subscribeOn(ioScheduler) .subscribeOn(ioScheduler)
@ -97,27 +94,22 @@ public class DepictsPresenter implements DepictsContract.UserActionListener {
}) })
.observeOn(ioScheduler) .observeOn(ioScheduler)
.concatWith( .concatWith(
repository.searchAllEntities(query, imageTitleList) repository.searchAllEntities(query)
) )
.distinct(); .distinct();
Disposable searchDepictsDisposable = distinctDepictsObservable Disposable searchDepictsDisposable = distinctDepictsObservable
.observeOn(mainThreadScheduler) .observeOn(mainThreadScheduler)
.subscribe( .subscribe(
s -> depictedItemList.add(s), depictedItemList::add,
Timber::e, Timber::e,
() -> { () -> {
view.showProgress(false); view.showProgress(false);
if (null == depictedItemList || depictedItemList.isEmpty()) { if (depictedItemList.isEmpty()) {
view.showError(true); view.showError(true);
} else { } else {
view.showError(false); view.showError(false);
//Understand this is shitty, but yes, doing it the other way is even worse and adapter positions can not be trusted
for (int position = 0; position < depictedItemList.size();
position++) {
//depictedItemList.get(position).setPosition(position);
}
view.setDepictsList(depictedItemList); view.setDepictsList(depictedItemList);
} }
} }
@ -156,18 +148,4 @@ public class DepictsPresenter implements DepictsContract.UserActionListener {
view.onImageUrlFetched(response,position); view.onImageUrlFetched(response,position);
})); }));
} }
/**
* Returns image title list from UploadItem
* @return
*/
private List<String> getImageTitleList() {
List<String> titleList = new ArrayList<>();
for (UploadModel.UploadItem item : repository.getUploads()) {
if (item.getTitle().isSet()) {
titleList.add(item.getTitle().toString());
}
}
return titleList;
}
} }

View file

@ -1,141 +0,0 @@
package fr.free.nrw.commons.upload.structure.depictions;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import javax.inject.Inject;
import javax.inject.Named;
import fr.free.nrw.commons.explore.depictions.DepictsClient;
import fr.free.nrw.commons.kvstore.JsonKvStore;
import fr.free.nrw.commons.upload.depicts.DepictsInterface;
import fr.free.nrw.commons.utils.StringSortingUtils;
import fr.free.nrw.commons.wikidata.model.DepictSearchItem;
import io.reactivex.Observable;
import io.reactivex.disposables.CompositeDisposable;
import timber.log.Timber;
/**
* The model class for depictions in upload
*/
public class DepictModel {
private static final int SEARCH_DEPICTS_LIMIT = 25;
private final DepictionDao depictDao;
private final DepictsInterface depictsInterface;
private final JsonKvStore directKvStore;
@Inject
DepictsClient depictsClient;
private List<DepictedItem> selectedDepictedItems;
private HashMap<String, ArrayList<String>> depictsCache;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
@Inject
public DepictModel(DepictionDao depictDao, @Named("default_preferences") JsonKvStore directKvStore, DepictsInterface depictsInterface) {
this.depictDao = depictDao;
this.directKvStore = directKvStore;
this.depictsInterface = depictsInterface;
this.depictsCache = new HashMap<>();
this.selectedDepictedItems = new ArrayList<>();
}
public Comparator<DepictedItem> sortBySimilarity(final String filter) {
Comparator<String> stringSimilarityComparator = StringSortingUtils.sortBySimilarity(filter);
return (firstItem, secondItem) -> stringSimilarityComparator
.compare(firstItem.getDepictsLabel(), secondItem.getDescription());
}
public void cacheAll(HashMap<String, ArrayList<String>> depictsCache) {
depictsCache.putAll(depictsCache);
}
public HashMap<String, ArrayList<String>> getDepictsCache() {
return depictsCache;
}
boolean cacheContainsKey(String term) {
return depictsCache.containsKey(term);
}
public void onDepictItemClicked(DepictedItem depictedItem) {
if (depictedItem.isSelected()) {
selectDepictItem(depictedItem);
// updateDepictCount(depictedItem);
} else {
unselectDepiction(depictedItem);
}
}
private void unselectDepiction(DepictedItem depictedItem) {
selectedDepictedItems.remove(depictedItem);
}
private void updateDepictCount(DepictedItem depictedItem) {
Depiction depiction = depictDao.find(depictedItem.getDepictsLabel());
if (depictedItem == null) {
depiction = new Depiction(null, depictedItem.getDepictsLabel(), new Date(), 0);
}
depiction.incTimesUsed();
depictDao.save(depiction);
}
private void selectDepictItem(DepictedItem depictedItem) {
selectedDepictedItems.add(depictedItem);
}
private Observable<DepictedItem> titleDepicts(List<String> titleList) {
return Observable.fromIterable(titleList)
.concatMap(this::getTitleDepicts);
}
private Observable<DepictedItem> getTitleDepicts(String title) {
return depictsInterface.searchForDepicts(title, String.valueOf(SEARCH_DEPICTS_LIMIT), Locale.getDefault().getLanguage(), Locale.getDefault().getLanguage(),"0")
.map(depictSearchResponse -> {
DepictSearchItem depictedItem = depictSearchResponse.getSearch().get(0);
return new DepictedItem(depictedItem.getLabel(), depictedItem.getDescription(), "", false, depictedItem.getId());
});
}
private Observable<DepictedItem> recentDepicts() {
return Observable.fromIterable(depictDao.recentDepicts(SEARCH_DEPICTS_LIMIT))
.map(s -> new DepictedItem(s, "", "", false, ""));
}
/**
* Get selected Depictions
* @return selected depictions
*/
public List<DepictedItem> getSelectedDepictions() {
return selectedDepictedItems;
}
/**
* Search for depictions
* @param query
* @param imageTitleList
* @return
*/
public Observable<DepictedItem> searchAllEntities(String query, List<String> imageTitleList) {
return depictsInterface.searchForDepicts(query, String.valueOf(SEARCH_DEPICTS_LIMIT), Locale.getDefault().getLanguage(), Locale.getDefault().getLanguage(), "0")
.flatMap(depictSearchResponse -> Observable.fromIterable(depictSearchResponse.getSearch()))
.map(depictSearchItem -> new DepictedItem(depictSearchItem.getLabel(), depictSearchItem.getDescription(), "", false, depictSearchItem.getId()));
}
public List<String> depictionsEntityIdList() {
List<String> output = new ArrayList<>();
for (DepictedItem d : selectedDepictedItems) {
output.add(d.getEntityId());
}
return output;
}
}

View file

@ -0,0 +1,48 @@
package fr.free.nrw.commons.upload.structure.depictions
import fr.free.nrw.commons.upload.depicts.DepictsInterface
import io.reactivex.Observable
import java.util.Locale
import javax.inject.Inject
/**
* The model class for depictions in upload
*/
class DepictModel @Inject constructor(private val depictsInterface: DepictsInterface) {
companion object {
private const val SEARCH_DEPICTS_LIMIT = 25
}
val selectedDepictions = mutableListOf<DepictedItem>()
fun onDepictItemClicked(depictedItem: DepictedItem) {
if (depictedItem.isSelected) {
selectDepictItem(depictedItem)
} else {
unselectDepiction(depictedItem)
}
}
private fun unselectDepiction(depictedItem: DepictedItem) {
selectedDepictions.remove(depictedItem)
}
private fun selectDepictItem(depictedItem: DepictedItem) {
selectedDepictions.add(depictedItem)
}
/**
* Search for depictions
*/
fun searchAllEntities(query: String?): Observable<DepictedItem> {
return depictsInterface.searchForDepicts(
query, "$SEARCH_DEPICTS_LIMIT", Locale.getDefault().language,
Locale.getDefault().language, "0"
)
.flatMap { Observable.fromIterable(it.search) }
.map(::DepictedItem)
}
fun depictionsEntityIdList() = selectedDepictions.map { it.entityId }
}

View file

@ -1,81 +0,0 @@
package fr.free.nrw.commons.upload.structure.depictions;
/**
* Model class for Depicted Item in Upload and Explore
*/
public class DepictedItem {
private final String depictsLabel;
private final String description;
private String imageUrl;
private boolean selected;
private String entityId;
private int position;
public DepictedItem(String depictsLabel, String description, String imageUrl, boolean selected, String entityId) {
this.depictsLabel = depictsLabel;
this.selected = selected;
this.description = description;
this.imageUrl = imageUrl;
this.entityId = entityId;
}
public String getEntityId() {
return entityId;
}
public String getDescription() {
return description;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getDepictsLabel() {
return depictsLabel;
}
public boolean isSelected() {
return selected;
}
public void setSelected(boolean selected) {
this.selected = selected;
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public String toString() {
return super.toString();
}
public void setPosition(int position) {
this.position = position;
}
public int getPosition() {
return position;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DepictedItem that = (DepictedItem) o;
return depictsLabel.equals(that.depictsLabel);
}
}

View file

@ -0,0 +1,31 @@
package fr.free.nrw.commons.upload.structure.depictions
import fr.free.nrw.commons.wikidata.model.DepictSearchItem
/**
* Model class for Depicted Item in Upload and Explore
*/
data class DepictedItem constructor(
val depictsLabel: String,
val description: String,
var imageUrl: String,
var isSelected: Boolean,
val entityId: String
) {
constructor(depictSearchItem: DepictSearchItem) : this(
depictSearchItem.label,
depictSearchItem.description,
"",
false,
depictSearchItem.id
)
var position = 0
override fun equals(o: Any?) = when {
this === o -> true
o is DepictedItem -> depictsLabel == o.depictsLabel
else -> false
}
}

View file

@ -1,106 +0,0 @@
package fr.free.nrw.commons.upload.structure.depictions;
import android.net.Uri;
import java.util.Date;
/**
* Represents the fact that a given Commons picture depicts a given Wikidata item.
* Example: https://commons.wikimedia.org/wiki/File:Sorting_quicksort_anim.gif depicts https://www.wikidata.org/wiki/Q486598
*/
public class Depiction {
private Uri contentUri;
private String name;
private Date lastUsed;
private int timesUsed;
public Depiction() {
}
public Depiction(Uri contentUri, String name, Date lastUsed, int timesUsed) {
this.contentUri = contentUri;
this.name = name;
this.lastUsed = lastUsed;
this.timesUsed = timesUsed;
}
/**
* Gets the content URI for this category
*
* @return content URI
*/
public Uri getContentUri() {
return contentUri;
}
/**
* Modifies the content URI - marking this depiction as already saved in the database
*
* @param contentUri the content URI
*/
public void setContentUri(Uri contentUri) {
this.contentUri = contentUri;
}
/**
* Gets name
*
* @return name
*/
public String getName() {
return name;
}
/**
* Modifies name
*
* @param name Depicts name
*/
public void setName(String name) {
this.name = name;
}
/**
* Gets last used date
*
* @return Last used date
*/
public Date getLastUsed() {
return lastUsed;
}
/**
* Set last used date
*
* @param lastUsed last used date of depiction
*/
public void setLastUsed(Date lastUsed) {
this.lastUsed = lastUsed;
}
/**
* Gets no. of times the depiction 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();
}
/**
* Generates new last used date
*/
private void touch() {
lastUsed = new Date();
}
}

View file

@ -1,176 +0,0 @@
package fr.free.nrw.commons.upload.structure.depictions;
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 DepictionDao {
private final Provider<ContentProviderClient> clientProvider;
@Inject
public DepictionDao(@Named("depictions") Provider<ContentProviderClient> clientProvider) {
this.clientProvider = clientProvider;
}
public void save(Depiction depiction) {
ContentProviderClient db = clientProvider.get();
try {
if (depiction.getContentUri() == null) {
depiction.setContentUri(db.insert(DepictsContentProvider.BASE_URI, toContentValues(depiction)));
} else {
db.update(depiction.getContentUri(), toContentValues(depiction), null, null);
}
} catch (RemoteException e) {
throw new RuntimeException(e);
} finally {
db.release();
}
}
/**
* Find persisted depicts in database, based on its name.
*
* @param name Depiction name
* @return depiction from database, or null if not found
*/
@Nullable
Depiction find(String name) {
Cursor cursor = null;
ContentProviderClient db = clientProvider.get();
try {
cursor = db.query(
DepictsContentProvider.BASE_URI,
Table.ALL_FIELDS,
Table.COLUMN_NAME + "=?",
new String[]{name},
null);
if (cursor != null && cursor.moveToFirst()) {
return fromCursor(cursor);
}
db.release();
} catch (RemoteException e) {
// This feels lazy, but to hell with checked exceptions. :)
throw new RuntimeException(e);
} finally {
if (cursor != null) {
cursor.close();
}
}
return null;
}
/**
* Retrieve recently-used depictions, ordered by descending date.
*
* @return a list containing recent depicts
*/
@NonNull
List<String> recentDepicts(int limit) {
List<String> items = new ArrayList<>();
Cursor cursor = null;
ContentProviderClient db = clientProvider.get();
try {
cursor = db.query(
DepictsContentProvider.BASE_URI,
Table.ALL_FIELDS,
null,
new String[]{},
Table.COLUMN_LAST_USED + "DESC");
while (cursor != null && cursor.moveToNext()
&& cursor.getPosition() < limit) {
items.add(fromCursor(cursor).getName());
}
} catch (RemoteException e) {
throw new RuntimeException(e);
} finally {
if (cursor != null) {
cursor.close();
}
db.release();
}
return items;
}
@NonNull
Depiction fromCursor(Cursor cursor) {
// Hardcoding column positions!
return new Depiction(
DepictsContentProvider.uriForId(cursor.getInt(cursor.getColumnIndex(DepictionDao.Table.COLUMN_ID))),
cursor.getString(cursor.getColumnIndex(DepictionDao.Table.COLUMN_NAME)),
new Date(cursor.getLong(cursor.getColumnIndex(DepictionDao.Table.COLUMN_LAST_USED))),
cursor.getInt(cursor.getColumnIndex(DepictionDao.Table.COLUMN_TIMES_USED))
);
}
private ContentValues toContentValues(Depiction depiction) {
ContentValues cv = new ContentValues();
cv.put(DepictionDao.Table.COLUMN_NAME, depiction.getName());
cv.put(DepictionDao.Table.COLUMN_LAST_USED, depiction.getLastUsed().getTime());
cv.put(DepictionDao.Table.COLUMN_TIMES_USED, depiction.getTimesUsed());
return cv;
}
/**
* Example Table: TABLE_NAME: depictions
* COLUMN_ID: unique id for the column
* COLUMN_NAME: depiction name
* COLUMN_LAST_USED: Time stamp for the previous usage of the depiction
* COLUMN_TIMES_USED: Number of times the depiction was used previously
*/
public static class Table {
public static final String TABLE_NAME = "depictions";
public static final String COLUMN_ID = "_id";
static final String COLUMN_NAME = "name";
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_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_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;
}
}
}
}

View file

@ -1,153 +0,0 @@
package fr.free.nrw.commons.upload.structure.depictions;
import android.annotation.SuppressLint;
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 org.jetbrains.annotations.NotNull;
import javax.inject.Inject;
import fr.free.nrw.commons.data.DBOpenHelper;
import fr.free.nrw.commons.di.CommonsDaggerContentProvider;
import timber.log.Timber;
import static fr.free.nrw.commons.BuildConfig.DEPICTION_AUTHORITY;
import static fr.free.nrw.commons.upload.structure.depictions.DepictionDao.Table.ALL_FIELDS;
import static fr.free.nrw.commons.upload.structure.depictions.DepictionDao.Table.COLUMN_ID;
import static fr.free.nrw.commons.upload.structure.depictions.DepictionDao.Table.TABLE_NAME;
@SuppressLint("Registered")
public class DepictsContentProvider extends CommonsDaggerContentProvider {
private static final int DEPICTS = 1;
private static final int DEPICTS_ID = 2;
private static final String BASE_PATH = "depictions";
public static final Uri BASE_URI = Uri.parse("content://" + DEPICTION_AUTHORITY + "/" + BASE_PATH);
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
uriMatcher.addURI(DEPICTION_AUTHORITY, BASE_PATH, DEPICTS);
uriMatcher.addURI(DEPICTION_AUTHORITY, BASE_PATH + "/#", DEPICTS_ID);
}
@Inject
DBOpenHelper dbOpenHelper;
public static Uri uriForId(int id) {
return Uri.parse(BASE_URI.toString() + "/" + id);
}
@Override
public Cursor query(@NotNull 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 DEPICTS:
cursor = queryBuilder.query(db, projection, selection, selectionArgs,
null, null, sortOrder);
break;
case DEPICTS_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(@NotNull Uri uri) {
return null;
}
@Override
public Uri insert(@NotNull Uri uri, ContentValues values) {
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
long id;
switch (uriType) {
case DEPICTS:
id = sqlDB.insert(TABLE_NAME, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return Uri.parse(BASE_URI + "/" + id);
}
@Override
public int delete(@NotNull Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int bulkInsert(@NotNull Uri uri, @NotNull ContentValues[] values) {
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
sqlDB.beginTransaction();
switch (uriType) {
case DEPICTS:
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;
}
@Override
public int update(@NotNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
int uriType = uriMatcher.match(uri);
SQLiteDatabase sqlDB = dbOpenHelper.getWritableDatabase();
int rowsUpdated;
switch (uriType) {
case DEPICTS:
if (TextUtils.isEmpty(selection)) {
int id = Integer.valueOf(uri.getLastPathSegment());
rowsUpdated = sqlDB.update(TABLE_NAME,
values,
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;
}
}

View file

@ -2,7 +2,6 @@ package fr.free.nrw.commons.upload
//import com.nhaarman.mockito_kotlin.verify //import com.nhaarman.mockito_kotlin.verify
import com.nhaarman.mockitokotlin2.whenever import com.nhaarman.mockitokotlin2.whenever
import org.mockito.Mockito.verify
import fr.free.nrw.commons.category.CategoryItem import fr.free.nrw.commons.category.CategoryItem
import fr.free.nrw.commons.explore.depictions.DepictsClient import fr.free.nrw.commons.explore.depictions.DepictsClient
import fr.free.nrw.commons.repository.UploadRepository import fr.free.nrw.commons.repository.UploadRepository
@ -16,7 +15,7 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
class DepictsPresenterTest { class DepictsPresenterTest {
@ -50,7 +49,7 @@ class DepictsPresenterTest {
fun setUp() { fun setUp() {
MockitoAnnotations.initMocks(this) MockitoAnnotations.initMocks(this)
testScheduler = TestScheduler() testScheduler = TestScheduler()
depictedItem = DepictedItem("label", "desc", null, false, "entityId") depictedItem = DepictedItem("label", "desc", "", false, "entityId")
depictedItems.add(depictedItem) depictedItems.add(depictedItem)
testObservable = Observable.just(depictedItem) testObservable = Observable.just(depictedItem)
depictsPresenter = DepictsPresenter(repository, testScheduler, testScheduler, depictsClient) depictsPresenter = DepictsPresenter(repository, testScheduler, testScheduler, depictsClient)
@ -62,7 +61,7 @@ class DepictsPresenterTest {
fun searchEnglishDepictionsTest() { fun searchEnglishDepictionsTest() {
whenever(repository?.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 }) whenever(repository?.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 })
whenever(repository?.selectedDepictions).thenReturn(depictedItems) whenever(repository?.selectedDepictions).thenReturn(depictedItems)
whenever(repository?.searchAllEntities(ArgumentMatchers.anyString(), ArgumentMatchers.anyList())).thenReturn(Observable.empty()) whenever(repository?.searchAllEntities(ArgumentMatchers.anyString())).thenReturn(Observable.empty())
depictsPresenter?.searchForDepictions("test") depictsPresenter?.searchForDepictions("test")
verify(view)?.showProgress(true) verify(view)?.showProgress(true)
verify(view)?.showError(true) verify(view)?.showError(true)
@ -75,7 +74,7 @@ class DepictsPresenterTest {
fun searchOtherLanguageDepictions() { fun searchOtherLanguageDepictions() {
whenever(repository?.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 }) whenever(repository?.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 })
whenever(repository?.selectedDepictions).thenReturn(depictedItems) whenever(repository?.selectedDepictions).thenReturn(depictedItems)
whenever(repository?.searchAllEntities(ArgumentMatchers.anyString(), ArgumentMatchers.anyList())).thenReturn(Observable.empty()) whenever(repository?.searchAllEntities(ArgumentMatchers.anyString())).thenReturn(Observable.empty())
depictsPresenter?.searchForDepictions("वी") depictsPresenter?.searchForDepictions("वी")
verify(view)?.showProgress(true) verify(view)?.showProgress(true)
verify(view)?.showError(true) verify(view)?.showError(true)
@ -88,7 +87,7 @@ class DepictsPresenterTest {
fun searchForNonExistingDepictions() { fun searchForNonExistingDepictions() {
whenever(repository?.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 }) whenever(repository?.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 })
whenever(repository?.selectedDepictions).thenReturn(depictedItems) whenever(repository?.selectedDepictions).thenReturn(depictedItems)
whenever(repository?.searchAllEntities(ArgumentMatchers.anyString(), ArgumentMatchers.anyList())).thenReturn(Observable.empty()) whenever(repository?.searchAllEntities(ArgumentMatchers.anyString())).thenReturn(Observable.empty())
depictsPresenter?.searchForDepictions("******") depictsPresenter?.searchForDepictions("******")
verify(view)?.showProgress(true) verify(view)?.showProgress(true)
verify(view)?.setDepictsList(null) verify(view)?.setDepictsList(null)
@ -101,7 +100,7 @@ class DepictsPresenterTest {
fun setSingleDepiction() { fun setSingleDepiction() {
whenever(repository?.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 }) whenever(repository?.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 })
whenever(repository?.selectedDepictions).thenReturn(depictedItems) whenever(repository?.selectedDepictions).thenReturn(depictedItems)
whenever(repository?.searchAllEntities(ArgumentMatchers.anyString(), ArgumentMatchers.anyList())).thenReturn(Observable.empty()) whenever(repository?.searchAllEntities(ArgumentMatchers.anyString())).thenReturn(Observable.empty())
depictsPresenter?.onDepictItemClicked(depictedItem) depictsPresenter?.onDepictItemClicked(depictedItem)
depictsPresenter?.verifyDepictions() depictsPresenter?.verifyDepictions()
verify(view)?.goToNextScreen() verify(view)?.goToNextScreen()
@ -111,9 +110,9 @@ class DepictsPresenterTest {
fun setMultipleDepictions() { fun setMultipleDepictions() {
whenever(repository?.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 }) whenever(repository?.sortBySimilarity(ArgumentMatchers.anyString())).thenReturn(Comparator<CategoryItem> { _, _ -> 1 })
whenever(repository?.selectedDepictions).thenReturn(depictedItems) whenever(repository?.selectedDepictions).thenReturn(depictedItems)
whenever(repository?.searchAllEntities(ArgumentMatchers.anyString(), ArgumentMatchers.anyList())).thenReturn(Observable.empty()) whenever(repository?.searchAllEntities(ArgumentMatchers.anyString())).thenReturn(Observable.empty())
depictsPresenter?.onDepictItemClicked(depictedItem) depictsPresenter?.onDepictItemClicked(depictedItem)
val depictedItem2 = DepictedItem("label2", "desc2", null, false, "entityid2") val depictedItem2 = DepictedItem("label2", "desc2", "", false, "entityid2")
depictsPresenter?.onDepictItemClicked(depictedItem2) depictsPresenter?.onDepictItemClicked(depictedItem2)
depictsPresenter?.verifyDepictions() depictsPresenter?.verifyDepictions()
verify(view)?.goToNextScreen() verify(view)?.goToNextScreen()