mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Merge remote-tracking branch 'origin/master' into macgills/3760-categories-pagination
# Conflicts: # app/src/main/java/fr/free/nrw/commons/explore/SearchActivity.java # app/src/main/java/fr/free/nrw/commons/explore/SearchModule.java # app/src/main/java/fr/free/nrw/commons/explore/categories/SearchCategoryFragment.java # app/src/main/java/fr/free/nrw/commons/explore/depictions/DepictionAdapter.kt # app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragment.kt # app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentContract.kt # app/src/main/java/fr/free/nrw/commons/explore/depictions/SearchDepictionsFragmentPresenter.kt # app/src/main/java/fr/free/nrw/commons/nearby/PlaceAdapterDelegate.kt # app/src/main/java/fr/free/nrw/commons/nearby/fragments/NearbyParentFragment.java
This commit is contained in:
commit
1a1a8389d5
60 changed files with 1024 additions and 259 deletions
14
README.md
14
README.md
|
|
@ -13,7 +13,7 @@ Initially started by the Wikimedia Foundation, this app is now maintained by gra
|
|||
|
||||
## Documentation
|
||||
|
||||
We try to have an extensive documentation at [our wiki here at Github][4]:
|
||||
We try to have an extensive documentation at our [documentation repository][4]:
|
||||
|
||||
* [User Documentation][5]
|
||||
* [Contributor Documentation][6]
|
||||
|
|
@ -45,11 +45,11 @@ This software is open source, licensed under the [Apache License 2.0][10].
|
|||
[2]: https://commons-app.github.io/
|
||||
[3]: https://github.com/commons-app/apps-android-commons/issues
|
||||
|
||||
[4]: https://github.com/commons-app/apps-android-commons/wiki
|
||||
[5]: https://github.com/commons-app/apps-android-commons/wiki#user-documentation
|
||||
[6]: https://github.com/commons-app/apps-android-commons/wiki#contributor-documentation
|
||||
[7]: https://github.com/commons-app/apps-android-commons/wiki/Volunteers-welcome%21
|
||||
[8]: https://github.com/commons-app/apps-android-commons/wiki#developer-documentation
|
||||
[9]: https://github.com/commons-app/apps-android-commons/wiki/Libraries-used
|
||||
[4]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-android-documentation
|
||||
[5]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-user-documentation
|
||||
[6]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#️-contributor-documentation
|
||||
[7]: https://github.com/commons-app/commons-app-documentation/blob/master/android/Volunteers-welcome!.md#volunteers-welcome
|
||||
[8]: https://github.com/commons-app/commons-app-documentation/blob/master/android/README.md#-developer-documentation
|
||||
[9]: https://github.com/commons-app/commons-app-documentation/blob/master/android/Libraries-used.md#libraries-used
|
||||
|
||||
[10]: https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
package fr.free.nrw.commons;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import okhttp3.logging.HttpLoggingInterceptor.Level;
|
||||
import org.wikipedia.dataclient.SharedPreferenceCookieManager;
|
||||
import org.wikipedia.dataclient.okhttp.HttpStatusException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import okhttp3.Cache;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import android.os.RemoteException;
|
|||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import fr.free.nrw.commons.nearby.NearbyController;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
|
@ -74,8 +75,10 @@ public class BookmarkLocationsDao {
|
|||
boolean bookmarkExists = findBookmarkLocation(bookmarkLocation);
|
||||
if (bookmarkExists) {
|
||||
deleteBookmarkLocation(bookmarkLocation);
|
||||
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, false);
|
||||
} else {
|
||||
addBookmarkLocation(bookmarkLocation);
|
||||
NearbyController.updateMarkerLabelListBookmark(bookmarkLocation, true);
|
||||
}
|
||||
return !bookmarkExists;
|
||||
}
|
||||
|
|
@ -160,10 +163,9 @@ public class BookmarkLocationsDao {
|
|||
location,
|
||||
cursor.getString(cursor.getColumnIndex(Table.COLUMN_CATEGORY)),
|
||||
builder.build(),
|
||||
null,
|
||||
null
|
||||
cursor.getString(cursor.getColumnIndex(Table.COLUMN_PIC)),
|
||||
cursor.getString(cursor.getColumnIndex(Table.COLUMN_DESTROYED))
|
||||
);
|
||||
// TODO: add pic and destroyed to bookmark location dao
|
||||
}
|
||||
|
||||
private ContentValues toContentValues(Place bookmarkLocation) {
|
||||
|
|
@ -179,6 +181,7 @@ public class BookmarkLocationsDao {
|
|||
cv.put(BookmarkLocationsDao.Table.COLUMN_LAT, bookmarkLocation.location.getLatitude());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_LONG, bookmarkLocation.location.getLongitude());
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_PIC, bookmarkLocation.pic);
|
||||
cv.put(BookmarkLocationsDao.Table.COLUMN_DESTROYED, bookmarkLocation.destroyed);
|
||||
return cv;
|
||||
}
|
||||
|
||||
|
|
@ -197,6 +200,7 @@ public class BookmarkLocationsDao {
|
|||
static final String COLUMN_WIKIDATA_LINK = "location_wikidata_link";
|
||||
static final String COLUMN_COMMONS_LINK = "location_commons_link";
|
||||
static final String COLUMN_PIC = "location_pic";
|
||||
static final String COLUMN_DESTROYED = "location_destroyed";
|
||||
|
||||
// NOTE! KEEP IN SAME ORDER AS THEY ARE DEFINED UP THERE. HELPS HARD CODE COLUMN INDICES.
|
||||
public static final String[] ALL_FIELDS = {
|
||||
|
|
@ -211,7 +215,8 @@ public class BookmarkLocationsDao {
|
|||
COLUMN_WIKIPEDIA_LINK,
|
||||
COLUMN_WIKIDATA_LINK,
|
||||
COLUMN_COMMONS_LINK,
|
||||
COLUMN_PIC
|
||||
COLUMN_PIC,
|
||||
COLUMN_DESTROYED
|
||||
};
|
||||
|
||||
static final String DROP_TABLE_STATEMENT = "DROP TABLE IF EXISTS " + TABLE_NAME;
|
||||
|
|
@ -228,7 +233,8 @@ public class BookmarkLocationsDao {
|
|||
+ COLUMN_WIKIPEDIA_LINK + " STRING,"
|
||||
+ COLUMN_WIKIDATA_LINK + " STRING,"
|
||||
+ COLUMN_COMMONS_LINK + " STRING,"
|
||||
+ COLUMN_PIC + " STRING"
|
||||
+ COLUMN_PIC + " STRING,"
|
||||
+ COLUMN_DESTROYED + " STRING"
|
||||
+ ");";
|
||||
|
||||
public static void onCreate(SQLiteDatabase db) {
|
||||
|
|
@ -242,37 +248,24 @@ public class BookmarkLocationsDao {
|
|||
|
||||
public static void onUpdate(SQLiteDatabase db, int from, int to) {
|
||||
Timber.d("bookmarksLocations db is updated from:"+from+", to:"+to);
|
||||
if (from == to) {
|
||||
return;
|
||||
}
|
||||
if (from < 7) {
|
||||
// doesn't exist yet
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if (from == 7) {
|
||||
// table added in version 8
|
||||
onCreate(db);
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if (from == 8) {
|
||||
from++;
|
||||
onUpdate(db, from, to);
|
||||
return;
|
||||
}
|
||||
if (from == 10 && to == 11) {
|
||||
from++;
|
||||
//This is safe, and can be called clean, as we/I do not remember the appropriate version for this
|
||||
//We are anyways switching to room, these things won't be nescessary then
|
||||
try {
|
||||
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_pic STRING;");
|
||||
}catch (SQLiteException exception){
|
||||
Timber.e(exception);//
|
||||
}
|
||||
return;
|
||||
switch (from) {
|
||||
case 7: onCreate(db);
|
||||
case 8: // No change
|
||||
case 9: // No change
|
||||
case 10:
|
||||
try {
|
||||
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_pic STRING;");
|
||||
} catch (SQLiteException exception){
|
||||
Timber.e(exception);
|
||||
}
|
||||
case 11: // No change
|
||||
case 12:
|
||||
try {
|
||||
db.execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;");
|
||||
}catch (SQLiteException exception){
|
||||
Timber.e(exception);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ package fr.free.nrw.commons.category
|
|||
|
||||
import android.text.TextUtils
|
||||
import fr.free.nrw.commons.upload.GpsCategoryModel
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import fr.free.nrw.commons.utils.StringSortingUtils
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.functions.Function3
|
||||
import io.reactivex.functions.Function4
|
||||
import timber.log.Timber
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
|
|
@ -67,30 +68,42 @@ class CategoriesModel @Inject constructor(
|
|||
* @param imageTitleList
|
||||
* @return
|
||||
*/
|
||||
fun searchAll(term: String, imageTitleList: List<String>): Observable<List<CategoryItem>> {
|
||||
return suggestionsOrSearch(term, imageTitleList)
|
||||
fun searchAll(
|
||||
term: String,
|
||||
imageTitleList: List<String>,
|
||||
selectedDepictions: List<DepictedItem>
|
||||
): Observable<List<CategoryItem>> {
|
||||
return suggestionsOrSearch(term, imageTitleList, selectedDepictions)
|
||||
.map { it.map { CategoryItem(it, false) } }
|
||||
}
|
||||
|
||||
private fun suggestionsOrSearch(term: String, imageTitleList: List<String>):
|
||||
Observable<List<String>> {
|
||||
private fun suggestionsOrSearch(
|
||||
term: String,
|
||||
imageTitleList: List<String>,
|
||||
selectedDepictions: List<DepictedItem>
|
||||
): Observable<List<String>> {
|
||||
return if (TextUtils.isEmpty(term))
|
||||
Observable.combineLatest(
|
||||
categoriesFromDepiction(selectedDepictions),
|
||||
gpsCategoryModel.categoriesFromLocation,
|
||||
titleCategories(imageTitleList),
|
||||
Observable.just(categoryDao.recentCategories(SEARCH_CATS_LIMIT)),
|
||||
Function3(::combine)
|
||||
Function4(::combine)
|
||||
)
|
||||
else
|
||||
categoryClient.searchCategoriesForPrefix(term.toLowerCase(), SEARCH_CATS_LIMIT)
|
||||
.map { it.sortedWith(StringSortingUtils.sortBySimilarity(term)) }
|
||||
}
|
||||
|
||||
private fun categoriesFromDepiction(selectedDepictions: List<DepictedItem>) =
|
||||
Observable.just(selectedDepictions.map { it.commonsCategories }.flatten())
|
||||
|
||||
private fun combine(
|
||||
depictionCategories: List<String>,
|
||||
locationCategories: List<String>,
|
||||
titles: List<String>,
|
||||
recents: List<String>
|
||||
) = locationCategories + titles + recents
|
||||
) = depictionCategories + locationCategories + titles + recents
|
||||
|
||||
|
||||
/**
|
||||
|
|
@ -98,14 +111,13 @@ class CategoriesModel @Inject constructor(
|
|||
* @param titleList
|
||||
* @return
|
||||
*/
|
||||
private fun titleCategories(titleList: List<String>): Observable<List<String>> {
|
||||
return if (titleList.isNotEmpty())
|
||||
private fun titleCategories(titleList: List<String>) =
|
||||
if (titleList.isNotEmpty())
|
||||
Observable.combineLatest(titleList.map { getTitleCategories(it) }) { searchResults ->
|
||||
searchResults.map { it as List<String> }.flatten()
|
||||
}
|
||||
else
|
||||
Observable.just(emptyList())
|
||||
}
|
||||
|
||||
/**
|
||||
* Return category for single title
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import fr.free.nrw.commons.explore.recentsearches.RecentSearchesDao;
|
|||
public class DBOpenHelper extends SQLiteOpenHelper {
|
||||
|
||||
private static final String DATABASE_NAME = "commons.db";
|
||||
private static final int DATABASE_VERSION = 12;
|
||||
private static final int DATABASE_VERSION = 13;
|
||||
public static final String CONTRIBUTIONS_TABLE = "contributions";
|
||||
private final String DROP_TABLE_STATEMENT="DROP TABLE IF EXISTS %s";
|
||||
|
||||
|
|
|
|||
|
|
@ -90,8 +90,9 @@ public interface DepictedImagesContract {
|
|||
|
||||
/**
|
||||
* Fetches more images for the item and adds it to the grid view adapter
|
||||
* @param entityId
|
||||
*/
|
||||
void fetchMoreImages();
|
||||
void fetchMoreImages(String entityId);
|
||||
|
||||
/**
|
||||
* fetch captions for the image using filename and replace title of on the image thumbnail(if captions are available)
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ public class DepictedImagesFragment extends DaggerFragment implements DepictedIm
|
|||
if (!NetworkUtils.isInternetConnectionEstablished(getContext())) {
|
||||
handleNoInternet();
|
||||
} else {
|
||||
presenter.fetchMoreImages();
|
||||
presenter.fetchMoreImages(entityId);
|
||||
}
|
||||
}
|
||||
if (isLastPage) {
|
||||
|
|
|
|||
|
|
@ -80,10 +80,11 @@ public class DepictedImagesPresenter implements DepictedImagesContract.UserActio
|
|||
|
||||
/**
|
||||
* Fetches more images for the item and adds it to the grid view adapter
|
||||
* @param entityId
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
@Override
|
||||
public void fetchMoreImages() {
|
||||
public void fetchMoreImages(String entityId) {
|
||||
view.progressBarVisible(true);
|
||||
compositeDisposable.add(depictsClient.fetchImagesForDepictedItem(entityId, queryList.size())
|
||||
.subscribeOn(ioScheduler)
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import androidx.paging.PagedListAdapter
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.explore.BaseViewHolder
|
||||
import fr.free.nrw.commons.explore.inflate
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.item_depictions.*
|
||||
|
||||
|
||||
|
|
@ -22,18 +23,17 @@ class DepictionAdapter(val onDepictionClicked: (DepictedItem) -> Unit) :
|
|||
}
|
||||
) {
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DepictedItemViewHolder {
|
||||
return DepictedItemViewHolder(parent.inflate(R.layout.item_depictions), onDepictionClicked)
|
||||
return DepictedItemViewHolder(parent.inflate(R.layout.item_depictions))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: DepictedItemViewHolder, position: Int) {
|
||||
holder.bind(getItem(position)!!)
|
||||
holder.bind(getItem(position)!!, onDepictionClicked)
|
||||
}
|
||||
}
|
||||
|
||||
class DepictedItemViewHolder(containerView: View, val onDepictionClicked: (DepictedItem) -> Unit) :
|
||||
BaseViewHolder<DepictedItem>(containerView) {
|
||||
|
||||
override fun bind(item: DepictedItem) {
|
||||
class DepictedItemViewHolder(override val containerView: View) :
|
||||
RecyclerView.ViewHolder(containerView), LayoutContainer {
|
||||
fun bind(item: DepictedItem, onDepictionClicked: (DepictedItem) -> Unit) {
|
||||
containerView.setOnClickListener { onDepictionClicked(item) }
|
||||
depicts_label.text = item.name
|
||||
description.text = item.description
|
||||
|
|
@ -44,7 +44,3 @@ class DepictedItemViewHolder(containerView: View, val onDepictionClicked: (Depic
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class DepictsClient @Inject constructor(
|
|||
}
|
||||
|
||||
fun getEntities(ids: String): Single<Entities> {
|
||||
return depictsInterface.getEntities(ids, Locale.getDefault().language)
|
||||
return depictsInterface.getEntities(ids)
|
||||
}
|
||||
|
||||
fun toDepictions(sparqlResponse: Observable<SparqlResponse>): Observable<List<DepictedItem>> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import fr.free.nrw.commons.R
|
||||
import kotlinx.android.extensions.LayoutContainer
|
||||
import kotlinx.android.synthetic.main.list_item_load_more.*
|
||||
|
||||
class FooterAdapter(private val onRefreshClicked: () -> Unit) :
|
||||
ListAdapter<FooterItem, FooterViewHolder>(object :
|
||||
DiffUtil.ItemCallback<FooterItem>() {
|
||||
override fun areItemsTheSame(oldItem: FooterItem, newItem: FooterItem) = oldItem == newItem
|
||||
|
||||
override fun areContentsTheSame(oldItem: FooterItem, newItem: FooterItem) =
|
||||
oldItem == newItem
|
||||
}) {
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return getItem(position).ordinal
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||
when (FooterItem.values()[viewType]) {
|
||||
FooterItem.LoadingItem -> LoadingViewHolder(parent.inflate(R.layout.list_item_progress))
|
||||
FooterItem.RefreshItem -> RefreshViewHolder(
|
||||
parent.inflate(R.layout.list_item_load_more),
|
||||
onRefreshClicked
|
||||
)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: FooterViewHolder, position: Int) {}
|
||||
}
|
||||
|
||||
open class FooterViewHolder(override val containerView: View) :
|
||||
RecyclerView.ViewHolder(containerView),
|
||||
LayoutContainer
|
||||
|
||||
class LoadingViewHolder(containerView: View) : FooterViewHolder(containerView)
|
||||
class RefreshViewHolder(containerView: View, onRefreshClicked: () -> Unit) :
|
||||
FooterViewHolder(containerView) {
|
||||
init {
|
||||
listItemLoadMoreButton.setOnClickListener { onRefreshClicked() }
|
||||
}
|
||||
}
|
||||
|
||||
enum class FooterItem { LoadingItem, RefreshItem }
|
||||
|
||||
fun ViewGroup.inflate(@LayoutRes layoutId: Int, attachToRoot: Boolean = false): View =
|
||||
LayoutInflater.from(context).inflate(layoutId, this, attachToRoot)
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import androidx.paging.PositionalDataSource
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.Completable
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
|
||||
|
||||
data class SearchDepictionsDataSource constructor(
|
||||
private val depictsClient: DepictsClient,
|
||||
private val loadingStates: PublishProcessor<LoadingState>,
|
||||
private val query: String
|
||||
) : PositionalDataSource<DepictedItem>() {
|
||||
|
||||
private var lastExecutedRequest: (() -> Boolean)? = null
|
||||
|
||||
override fun loadInitial(
|
||||
params: LoadInitialParams,
|
||||
callback: LoadInitialCallback<DepictedItem>
|
||||
) {
|
||||
storeAndExecute {
|
||||
loadingStates.offer(LoadingState.InitialLoad)
|
||||
performWithTryCatch {
|
||||
callback.onResult(
|
||||
getItems(query, params.requestedLoadSize, params.requestedStartPosition),
|
||||
params.requestedStartPosition
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadRange(params: LoadRangeParams, callback: LoadRangeCallback<DepictedItem>) {
|
||||
storeAndExecute {
|
||||
loadingStates.offer(LoadingState.Loading)
|
||||
performWithTryCatch {
|
||||
callback.onResult(getItems(query, params.loadSize, params.startPosition))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun retryFailedRequest() {
|
||||
Completable.fromAction { lastExecutedRequest?.invoke() }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
private fun getItems(query: String, limit: Int, offset: Int) =
|
||||
depictsClient.searchForDepictions(query, limit, offset).blockingGet()
|
||||
|
||||
private fun storeAndExecute(function: () -> Boolean) {
|
||||
function.also { lastExecutedRequest = it }.invoke()
|
||||
}
|
||||
|
||||
private fun performWithTryCatch(function: () -> Unit) = try {
|
||||
function.invoke()
|
||||
loadingStates.offer(LoadingState.Complete)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
loadingStates.offer(LoadingState.Error)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.Config
|
||||
import androidx.paging.DataSource
|
||||
import androidx.paging.PagedList
|
||||
import androidx.paging.toLiveData
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val PAGE_SIZE = 50
|
||||
private const val INITIAL_LOAD_SIZE = 50
|
||||
|
||||
class SearchableDepictionsDataSourceFactory @Inject constructor(
|
||||
val searchDepictionsDataSourceFactoryFactory: SearchDepictionsDataSourceFactoryFactory,
|
||||
val liveDataConverter: LiveDataConverter
|
||||
) {
|
||||
private val _loadingStates = PublishProcessor.create<LoadingState>()
|
||||
val loadingStates: Flowable<LoadingState> = _loadingStates
|
||||
private val _searchResults = PublishProcessor.create<LiveData<PagedList<DepictedItem>>>()
|
||||
val searchResults: Flowable<LiveData<PagedList<DepictedItem>>> = _searchResults
|
||||
private val _noItemsLoadedEvent = PublishProcessor.create<Unit>()
|
||||
val noItemsLoadedEvent: Flowable<Unit> = _noItemsLoadedEvent
|
||||
|
||||
private var currentFactory: SearchDepictionsDataSourceFactory? = null
|
||||
|
||||
fun onQueryUpdated(query: String) {
|
||||
_searchResults.offer(
|
||||
liveDataConverter.convert(
|
||||
searchDepictionsDataSourceFactoryFactory.create(query, _loadingStates)
|
||||
.also { currentFactory = it }
|
||||
) { _noItemsLoadedEvent.offer(Unit) }
|
||||
)
|
||||
}
|
||||
|
||||
fun retryFailedRequest() {
|
||||
currentFactory?.retryFailedRequest()
|
||||
}
|
||||
}
|
||||
|
||||
class LiveDataConverter @Inject constructor() {
|
||||
fun convert(
|
||||
dataSourceFactory: SearchDepictionsDataSourceFactory,
|
||||
zeroItemsLoadedFunction: () -> Unit
|
||||
): LiveData<PagedList<DepictedItem>> {
|
||||
return dataSourceFactory.toLiveData(
|
||||
Config(
|
||||
pageSize = PAGE_SIZE,
|
||||
initialLoadSizeHint = INITIAL_LOAD_SIZE,
|
||||
enablePlaceholders = false
|
||||
),
|
||||
boundaryCallback = object : PagedList.BoundaryCallback<DepictedItem>() {
|
||||
override fun onZeroItemsLoaded() {
|
||||
zeroItemsLoadedFunction()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface SearchDepictionsDataSourceFactoryFactory {
|
||||
fun create(query: String, loadingStates: PublishProcessor<LoadingState>)
|
||||
: SearchDepictionsDataSourceFactory
|
||||
}
|
||||
|
||||
class SearchDepictionsDataSourceFactory constructor(
|
||||
private val depictsClient: DepictsClient,
|
||||
private val query: String,
|
||||
private val loadingStates: PublishProcessor<LoadingState>
|
||||
) : DataSource.Factory<Int, DepictedItem>() {
|
||||
private var currentDataSource: SearchDepictionsDataSource? = null
|
||||
override fun create() = SearchDepictionsDataSource(depictsClient, loadingStates, query)
|
||||
.also { currentDataSource = it }
|
||||
|
||||
fun retryFailedRequest() {
|
||||
currentDataSource?.retryFailedRequest()
|
||||
}
|
||||
}
|
||||
|
||||
sealed class LoadingState {
|
||||
object InitialLoad : LoadingState()
|
||||
object Loading : LoadingState()
|
||||
object Complete : LoadingState()
|
||||
object Error : LoadingState()
|
||||
}
|
||||
|
|
@ -171,7 +171,7 @@ public class MediaClient {
|
|||
* @return caption for image using wikibaseIdentifier
|
||||
*/
|
||||
public Single<String> getCaptionByWikibaseIdentifier(String wikibaseIdentifier) {
|
||||
return mediaDetailInterface.getCaptionForImage(Locale.getDefault().getLanguage(), wikibaseIdentifier)
|
||||
return mediaDetailInterface.getEntityForImage(Locale.getDefault().getLanguage(), wikibaseIdentifier)
|
||||
.map(mediaDetailResponse -> {
|
||||
if (isSuccess(mediaDetailResponse)) {
|
||||
for (Entity wikibaseItem : mediaDetailResponse.entities().values()) {
|
||||
|
|
@ -208,11 +208,15 @@ public class MediaClient {
|
|||
* @return label
|
||||
*/
|
||||
public Single<String> getLabelForDepiction(String entityId, String language) {
|
||||
return mediaDetailInterface.getEntity(entityId, language)
|
||||
return mediaDetailInterface.getEntity(entityId)
|
||||
.map(entities -> {
|
||||
if (isSuccess(entities)) {
|
||||
for (Entity entity : entities.entities().values()) {
|
||||
for (Label label : entity.labels().values()) {
|
||||
final Map<String, Label> languageToLabelMap = entity.labels();
|
||||
if (languageToLabelMap.containsKey(language)) {
|
||||
return languageToLabelMap.get(language).value();
|
||||
}
|
||||
for (Label label : languageToLabelMap.values()) {
|
||||
return label.value();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ import fr.free.nrw.commons.delete.ReasonBuilder;
|
|||
import fr.free.nrw.commons.depictions.WikidataItemDetailsActivity;
|
||||
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
|
||||
import fr.free.nrw.commons.ui.widget.HtmlTextView;
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
|
||||
import fr.free.nrw.commons.utils.ViewUtilWrapper;
|
||||
import io.reactivex.Single;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
|
|
|
|||
|
|
@ -20,12 +20,11 @@ public interface MediaDetailInterface {
|
|||
|
||||
/**
|
||||
* Gets labels for Depictions using Entity Id from MediaWikiAPI
|
||||
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
||||
*
|
||||
* @param entityId EntityId (Ex: Q81566) of the depict entity
|
||||
* @param language user's locale
|
||||
*/
|
||||
@GET("/w/api.php?format=json&action=wbgetentities&props=labels&languagefallback=1")
|
||||
Observable<Entities> getEntity(@Query("ids") String entityId, @Query("languages") String language);
|
||||
Observable<Entities> getEntity(@Query("ids") String entityId);
|
||||
|
||||
/**
|
||||
* Fetches caption using wikibaseIdentifier
|
||||
|
|
@ -33,5 +32,5 @@ public interface MediaDetailInterface {
|
|||
* @param wikibaseIdentifier pageId for the media
|
||||
*/
|
||||
@GET("/w/api.php?action=wbgetentities&props=labels&format=json&languagefallback=1&sites=commonswiki")
|
||||
Observable<Entities> getCaptionForImage(@Query("languages") String language, @Query("ids") String wikibaseIdentifier);
|
||||
Observable<Entities> getEntityForImage(@Query("languages") String language, @Query("ids") String wikibaseIdentifier);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import android.os.Parcel;
|
|||
|
||||
import androidx.annotation.DrawableRes;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import fr.free.nrw.commons.R;
|
||||
|
|
@ -17,6 +19,7 @@ import fr.free.nrw.commons.R;
|
|||
*/
|
||||
public enum Label {
|
||||
|
||||
BOOKMARKS("BOOKMARK", R.drawable.ic_filled_star_24dp),
|
||||
BUILDING("Q41176", R.drawable.round_icon_generic_building),
|
||||
HOUSE("Q3947", R.drawable.round_icon_house),
|
||||
COTTAGE("Q5783996", R.drawable.round_icon_house),
|
||||
|
|
@ -92,4 +95,8 @@ public enum Label {
|
|||
Label label = TEXT_TO_DESCRIPTION.get(text);
|
||||
return label == null ? UNKNOWN : label;
|
||||
}
|
||||
|
||||
public static List<Label> valuesAsList() {
|
||||
return Arrays.asList(Label.values());
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
||||
|
||||
import com.mapbox.mapboxsdk.annotations.IconFactory;
|
||||
|
|
@ -14,6 +15,7 @@ import java.util.ArrayList;
|
|||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
|
|
@ -166,36 +168,6 @@ public class NearbyController {
|
|||
VectorDrawableCompat vectorDrawable = null;
|
||||
VectorDrawableCompat vectorDrawableGreen = null;
|
||||
VectorDrawableCompat vectorDrawableGrey = null;
|
||||
try {
|
||||
vectorDrawable = VectorDrawableCompat.create(
|
||||
context.getResources(), R.drawable.ic_custom_bookmark_marker, context.getTheme()
|
||||
);
|
||||
} catch (Resources.NotFoundException e) {
|
||||
// ignore when running tests.
|
||||
}
|
||||
if (vectorDrawable != null) {
|
||||
Bitmap icon = UiUtils.getBitmap(vectorDrawable);
|
||||
|
||||
for (Place place : bookmarkplacelist) {
|
||||
|
||||
String distance = formatDistanceBetween(curLatLng, place.location);
|
||||
place.setDistance(distance);
|
||||
|
||||
NearbyBaseMarker nearbyBaseMarker = new NearbyBaseMarker();
|
||||
nearbyBaseMarker.title(place.name);
|
||||
nearbyBaseMarker.position(
|
||||
new com.mapbox.mapboxsdk.geometry.LatLng(
|
||||
place.location.getLatitude(),
|
||||
place.location.getLongitude()));
|
||||
nearbyBaseMarker.place(place);
|
||||
nearbyBaseMarker.icon(IconFactory.getInstance(context)
|
||||
.fromBitmap(icon));
|
||||
placeList.remove(place);
|
||||
|
||||
baseMarkerOptions.add(nearbyBaseMarker);
|
||||
}
|
||||
}
|
||||
|
||||
vectorDrawable = null;
|
||||
try {
|
||||
vectorDrawable = VectorDrawableCompat.create(
|
||||
|
|
@ -255,4 +227,19 @@ public class NearbyController {
|
|||
public LatLng curLatLng; // Current location when this places are populated
|
||||
public LatLng searchLatLng; // Search location for finding this places
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates makerLabelList item isBookmarked value
|
||||
* @param place place which is bookmarked
|
||||
* @param isBookmarked true is bookmarked, false if bookmark removed
|
||||
*/
|
||||
@MainThread
|
||||
public static void updateMarkerLabelListBookmark(Place place, boolean isBookmarked) {
|
||||
for (ListIterator<MarkerPlaceGroup> iter = markerLabelList.listIterator(); iter.hasNext();) {
|
||||
MarkerPlaceGroup markerPlaceGroup = iter.next();
|
||||
if (markerPlaceGroup.getPlace().getWikiDataEntityId().equals(place.getWikiDataEntityId())) {
|
||||
iter.set(new MarkerPlaceGroup(markerPlaceGroup.getMarker(), isBookmarked, place));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import java.util.ArrayList;
|
|||
|
||||
import fr.free.nrw.commons.R;
|
||||
import fr.free.nrw.commons.nearby.presenter.NearbyParentFragmentPresenter;
|
||||
import java.util.Collections;
|
||||
|
||||
public class NearbyFilterSearchRecyclerViewAdapter
|
||||
extends RecyclerView.Adapter<NearbyFilterSearchRecyclerViewAdapter.RecyclerViewHolder>
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ fun placeAdapterDelegate(
|
|||
}
|
||||
cameraButton.setOnClickListener { onCameraClicked(item) }
|
||||
galleryButton.setOnClickListener { onGalleryClicked(item) }
|
||||
bookmarkRowButton.setOnClickListener {
|
||||
bookmarkButtonImage.setOnClickListener {
|
||||
val isBookmarked = bookmarkLocationDao.updateBookmarkLocation(item)
|
||||
bookmarkRowButtonImage.setImageResource(if (isBookmarked) R.drawable.ic_round_star_filled_24px else R.drawable.ic_round_star_border_24px)
|
||||
bookmarkButtonImage.setImageResource(if (isBookmarked) R.drawable.ic_round_star_filled_24px else R.drawable.ic_round_star_border_24px)
|
||||
onBookmarkClicked(item, isBookmarked)
|
||||
}
|
||||
iconOverflow.setOnClickListener { onOverflowIconClicked(item, it) }
|
||||
|
|
@ -59,7 +59,7 @@ fun placeAdapterDelegate(
|
|||
if (item.hasCommonsLink() || item.hasWikidataLink()) VISIBLE
|
||||
else GONE
|
||||
|
||||
bookmarkRowButtonImage.setImageResource(
|
||||
bookmarkButtonImage.setImageResource(
|
||||
if (bookmarkLocationDao.findBookmarkLocation(item))
|
||||
R.drawable.ic_round_star_filled_24px
|
||||
else
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import android.content.IntentFilter;
|
|||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
|
@ -33,6 +34,7 @@ import android.widget.RelativeLayout;
|
|||
import android.widget.SearchView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
|
@ -427,7 +429,7 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
|
||||
recyclerView.setLayoutManager(linearLayoutManager);
|
||||
|
||||
nearbyFilterSearchRecyclerViewAdapter = new NearbyFilterSearchRecyclerViewAdapter(getContext(),new ArrayList<>(TEXT_TO_DESCRIPTION.values()), recyclerView);
|
||||
nearbyFilterSearchRecyclerViewAdapter = new NearbyFilterSearchRecyclerViewAdapter(getContext(), new ArrayList<>(Label.valuesAsList()), recyclerView);
|
||||
nearbyFilterSearchRecyclerViewAdapter.setCallback(new NearbyFilterSearchRecyclerViewAdapter.Callback() {
|
||||
@Override
|
||||
public void setCheckboxUnknown() {
|
||||
|
|
@ -1128,7 +1130,8 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
// When label filter is engaged
|
||||
// then compare it against place's label
|
||||
if (selectedLabels != null && (selectedLabels.size() != 0 || !filterForPlaceState)
|
||||
&& !selectedLabels.contains(place.getLabel())) {
|
||||
&& (!selectedLabels.contains(place.getLabel())
|
||||
&& !(selectedLabels.contains(Label.BOOKMARKS) && markerPlaceGroup.getIsBookmarked()))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -1168,25 +1171,10 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
public void updateMarker(final boolean isBookmarked, final Place place, @Nullable final fr.free.nrw.commons.location.LatLng curLatLng) {
|
||||
addPlaceToNearbyList(place);
|
||||
|
||||
final VectorDrawableCompat vectorDrawable;
|
||||
if (isBookmarked) {
|
||||
vectorDrawable = VectorDrawableCompat.create(
|
||||
getContext().getResources(), R.drawable.ic_custom_bookmark_marker, getContext().getTheme()
|
||||
);
|
||||
} else if (!place.pic.trim().isEmpty()) {
|
||||
vectorDrawable = VectorDrawableCompat.create( // Means place has picture
|
||||
getContext().getResources(), R.drawable.ic_custom_map_marker_green, getContext().getTheme()
|
||||
);
|
||||
} else if (!place.destroyed.trim().isEmpty()) { // Means place is destroyed
|
||||
vectorDrawable = VectorDrawableCompat.create( // Means place has picture
|
||||
getContext().getResources(), R.drawable.ic_custom_map_marker_grey, getContext().getTheme()
|
||||
);
|
||||
} else {
|
||||
vectorDrawable = VectorDrawableCompat.create(
|
||||
getContext().getResources(), R.drawable.ic_custom_map_marker, getContext().getTheme()
|
||||
);
|
||||
}
|
||||
for (final Marker marker : mapBox.getMarkers()) {
|
||||
VectorDrawableCompat vectorDrawable = VectorDrawableCompat.create(
|
||||
getContext().getResources(), getIconFor(place, isBookmarked), getContext().getTheme());
|
||||
|
||||
for (Marker marker : mapBox.getMarkers()) {
|
||||
if (marker.getTitle() != null && marker.getTitle().equals(place.getName())) {
|
||||
|
||||
final Bitmap icon = UiUtils.getBitmap(vectorDrawable);
|
||||
|
|
@ -1209,6 +1197,22 @@ public class NearbyParentFragment extends CommonsDaggerSupportFragment
|
|||
}
|
||||
}
|
||||
|
||||
private @DrawableRes int getIconFor(Place place, Boolean isBookmarked) {
|
||||
if (!place.pic.trim().isEmpty()) {
|
||||
return (isBookmarked ?
|
||||
R.drawable.ic_custom_map_marker_green_bookmarked :
|
||||
R.drawable.ic_custom_map_marker_green);
|
||||
} else if (!place.destroyed.trim().isEmpty()) { // Means place is destroyed
|
||||
return (isBookmarked ?
|
||||
R.drawable.ic_custom_map_marker_grey_bookmarked :
|
||||
R.drawable.ic_custom_map_marker_grey);
|
||||
} else {
|
||||
return (isBookmarked ?
|
||||
R.drawable.ic_custom_map_marker_blue_bookmarked :
|
||||
R.drawable.ic_custom_map_marker);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all markers except current location marker, an icon has been used
|
||||
* but it is transparent more than grey(as the name of the icon might suggest)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
package fr.free.nrw.commons.nearby.presenter;
|
||||
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import com.mapbox.mapboxsdk.annotations.Marker;
|
||||
|
||||
import java.lang.reflect.Proxy;
|
||||
|
|
@ -301,6 +303,7 @@ public class NearbyParentFragmentPresenter
|
|||
}
|
||||
|
||||
@Override
|
||||
@MainThread
|
||||
public void updateMapMarkersToController(List<NearbyBaseMarker> nearbyBaseMarkers) {
|
||||
NearbyController.markerExistsMap = new HashMap<>();
|
||||
NearbyController.markerNeedPicMap = new HashMap<>();
|
||||
|
|
@ -308,7 +311,7 @@ public class NearbyParentFragmentPresenter
|
|||
for (int i = 0; i < nearbyBaseMarkers.size(); i++) {
|
||||
NearbyBaseMarker nearbyBaseMarker = nearbyBaseMarkers.get(i);
|
||||
NearbyController.markerLabelList.add(
|
||||
new MarkerPlaceGroup(nearbyBaseMarkers.get(i).getMarker(), bookmarkLocationDao.findBookmarkLocation(nearbyBaseMarkers.get(i).getPlace()), nearbyBaseMarker.getPlace()));
|
||||
new MarkerPlaceGroup(nearbyBaseMarker.getMarker(), bookmarkLocationDao.findBookmarkLocation(nearbyBaseMarker.getPlace()), nearbyBaseMarker.getPlace()));
|
||||
//TODO: fix bookmark location
|
||||
NearbyController.markerExistsMap.put((nearbyBaseMarkers.get(i).getPlace().hasWikidataLink()), nearbyBaseMarkers.get(i).getMarker());
|
||||
NearbyController.markerNeedPicMap.put(((nearbyBaseMarkers.get(i).getPlace().pic == null) ? true : false), nearbyBaseMarkers.get(i).getMarker());
|
||||
|
|
|
|||
|
|
@ -108,10 +108,12 @@ public class UploadRepository {
|
|||
*
|
||||
* @param query
|
||||
* @param imageTitleList
|
||||
* @param selectedDepictions
|
||||
* @return
|
||||
*/
|
||||
public Observable<List<CategoryItem>> searchAll(String query, List<String> imageTitleList) {
|
||||
return categoriesModel.searchAll(query, imageTitleList);
|
||||
public Observable<List<CategoryItem>> searchAll(String query, List<String> imageTitleList,
|
||||
List<DepictedItem> selectedDepictions) {
|
||||
return categoriesModel.searchAll(query, imageTitleList, selectedDepictions);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class CategoriesPresenter @Inject constructor(
|
|||
}
|
||||
|
||||
private fun searchResults(term: String) =
|
||||
repository.searchAll(term, getImageTitleList())
|
||||
repository.searchAll(term, getImageTitleList(), repository.selectedDepictions)
|
||||
.subscribeOn(ioScheduler)
|
||||
.map { it.filterNot { categoryItem -> repository.containsYear(categoryItem.name) } }
|
||||
|
||||
|
|
|
|||
|
|
@ -24,5 +24,5 @@ public interface DepictsInterface {
|
|||
Single<DepictSearchResponse> searchForDepicts(@Query("search") String query, @Query("limit") String limit, @Query("language") String language, @Query("uselang") String uselang, @Query("continue") String offset);
|
||||
|
||||
@GET("/w/api.php?format=json&action=wbgetentities")
|
||||
Single<Entities> getEntities(@Query("ids")String ids, @Query("languages")String language);
|
||||
Single<Entities> getEntities(@Query("ids") String ids);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,7 +107,6 @@ class DepictsPresenter @Inject constructor(
|
|||
view.noDepictionSelected()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ import fr.free.nrw.commons.explore.depictions.THUMB_IMAGE_SIZE
|
|||
import fr.free.nrw.commons.nearby.Place
|
||||
import fr.free.nrw.commons.upload.WikidataItem
|
||||
import fr.free.nrw.commons.wikidata.WikidataProperties
|
||||
import fr.free.nrw.commons.wikidata.WikidataProperties.*
|
||||
import org.wikipedia.wikidata.DataValue
|
||||
import org.wikipedia.wikidata.Entities
|
||||
import org.wikipedia.wikidata.Statement_partial
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Model class for Depicted Item in Upload and Explore
|
||||
|
|
@ -17,14 +19,15 @@ data class DepictedItem constructor(
|
|||
val description: String?,
|
||||
val imageUrl: String?,
|
||||
val instanceOfs: List<String>,
|
||||
val commonsCategories: List<String>,
|
||||
var isSelected: Boolean,
|
||||
override val id: String
|
||||
) : WikidataItem {
|
||||
|
||||
constructor(entity: Entities.Entity) : this(
|
||||
entity,
|
||||
entity.labels().values.firstOrNull()?.value() ?: "",
|
||||
entity.descriptions().values.firstOrNull()?.value() ?: ""
|
||||
entity.labels().byLanguageOrFirstOrEmpty(),
|
||||
entity.descriptions().byLanguageOrFirstOrEmpty()
|
||||
)
|
||||
|
||||
constructor(entity: Entities.Entity, place: Place) : this(
|
||||
|
|
@ -36,10 +39,12 @@ data class DepictedItem constructor(
|
|||
constructor(entity: Entities.Entity, name: String, description: String) : this(
|
||||
name,
|
||||
description,
|
||||
entity[WikidataProperties.IMAGE].primaryImageValue?.let {
|
||||
entity[IMAGE].primaryImageValue?.let {
|
||||
getImageUrl(it.value, THUMB_IMAGE_SIZE)
|
||||
},
|
||||
entity[WikidataProperties.INSTANCE_OF].toIds(),
|
||||
entity[INSTANCE_OF].toIds(),
|
||||
entity[COMMONS_CATEGORY]?.map { (it.mainSnak.dataValue as DataValue.ValueString).value }
|
||||
?: emptyList(),
|
||||
false,
|
||||
entity.id()
|
||||
)
|
||||
|
|
@ -57,7 +62,7 @@ data class DepictedItem constructor(
|
|||
}
|
||||
|
||||
private fun List<Statement_partial>?.toIds(): List<String> {
|
||||
return this?.map { it.mainSnak.dataValue }
|
||||
return this?.map { it.mainSnak.dataValue }
|
||||
?.filterIsInstance<DataValue.EntityId>()
|
||||
?.map { it.value.id }
|
||||
?: emptyList()
|
||||
|
|
@ -69,3 +74,5 @@ private val List<Statement_partial>?.primaryImageValue: DataValue.ValueString?
|
|||
operator fun Entities.Entity.get(property: WikidataProperties) =
|
||||
statements?.get(property.propertyName)
|
||||
|
||||
private fun Map<String, Entities.Label>.byLanguageOrFirstOrEmpty() =
|
||||
let { it[Locale.getDefault().language] ?: it.values.firstOrNull() }?.value() ?: ""
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package fr.free.nrw.commons.wikidata
|
|||
import fr.free.nrw.commons.BuildConfig
|
||||
|
||||
enum class WikidataProperties(val propertyName: String) {
|
||||
IMAGE("P18"), DEPICTS(BuildConfig.DEPICTS_PROPERTY), INSTANCE_OF("P31");
|
||||
|
||||
IMAGE("P18"),
|
||||
DEPICTS(BuildConfig.DEPICTS_PROPERTY),
|
||||
COMMONS_CATEGORY("P373"),
|
||||
INSTANCE_OF("P31");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="28">
|
||||
<path
|
||||
android:pathData="M6.072,22.223a6.031,3.672 0,1 0,12.062 0a6.031,3.672 0,1 0,-12.062 0z"
|
||||
android:strokeAlpha="0.1"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#000000"
|
||||
android:fillAlpha="0.1"/>
|
||||
<path
|
||||
android:pathData="M11.575,11.62C10.689,11.462 9.902,10.759 9.625,9.878 9.553,9.65 9.535,9.499 9.538,9.14c0.004,-0.397 0.019,-0.492 0.13,-0.787 0.236,-0.631 0.646,-1.099 1.212,-1.382 0.386,-0.193 0.709,-0.272 1.116,-0.272 0.676,0 1.263,0.247 1.744,0.734 0.355,0.359 0.541,0.682 0.657,1.136 0.327,1.278 -0.442,2.611 -1.723,2.987 -0.282,0.083 -0.817,0.114 -1.099,0.063z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#00ff00"/>
|
||||
<path
|
||||
android:pathData="M11.617,21.707C10.518,20.424 9.338,18.864 8.395,17.449 6.524,14.641 5.455,12.305 5.102,10.255 5.014,9.744 5.006,8.628 5.088,8.137 5.348,6.561 6.043,5.221 7.158,4.148 9.148,2.231 12.016,1.668 14.593,2.688c2.043,0.809 3.607,2.581 4.162,4.719 0.174,0.67 0.204,0.933 0.203,1.761 -0.001,0.81 -0.035,1.098 -0.22,1.857 -0.614,2.524 -2.571,5.977 -5.383,9.501 -0.645,0.809 -1.321,1.61 -1.358,1.61 -0.008,0 -0.179,-0.193 -0.381,-0.428zM12.617,11.603c0.783,-0.188 1.457,-0.795 1.738,-1.564 0.516,-1.415 -0.317,-2.962 -1.783,-3.312 -0.216,-0.052 -0.317,-0.059 -0.661,-0.047 -0.354,0.012 -0.441,0.025 -0.682,0.104 -0.673,0.221 -1.205,0.695 -1.506,1.344 -0.176,0.38 -0.218,0.584 -0.217,1.054 0.001,0.324 0.014,0.452 0.064,0.635 0.266,0.97 1.077,1.689 2.079,1.844 0.243,0.038 0.68,0.012 0.968,-0.057z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#006699"
|
||||
android:strokeColor="#003b59"
|
||||
android:fillAlpha="1"/>
|
||||
<path
|
||||
android:pathData="M17.9025,7.0798 L14.1612,6.7552 12.7003,3.3154c-0.2628,-0.6261 -1.1595,-0.6261 -1.4223,0L9.817,6.7629 6.0835,7.0798C5.4032,7.134 5.125,7.9842 5.6429,8.4326l2.8369,2.4581 -0.8503,3.6485c-0.1546,0.6648 0.5643,1.1904 1.1518,0.8348l3.2079,-1.9325 3.2079,1.9402c0.5875,0.3556 1.3064,-0.1701 1.1518,-0.8348L15.4985,10.8907 18.3354,8.4326C18.8533,7.9842 18.5827,7.134 17.9025,7.0798Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#f84d4d"
|
||||
android:fillAlpha="1"
|
||||
android:strokeColor="#003b59"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="28">
|
||||
<path
|
||||
android:pathData="M6.072,22.223a6.031,3.672 0,1 0,12.062 0a6.031,3.672 0,1 0,-12.062 0z"
|
||||
android:strokeAlpha="0.1"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#000000"
|
||||
android:fillAlpha="0.1"/>
|
||||
<path
|
||||
android:pathData="M11.575,11.62C10.689,11.462 9.902,10.759 9.625,9.878 9.553,9.65 9.535,9.499 9.538,9.14c0.004,-0.397 0.019,-0.492 0.13,-0.787 0.236,-0.631 0.646,-1.099 1.212,-1.382 0.386,-0.193 0.709,-0.272 1.116,-0.272 0.676,0 1.263,0.247 1.744,0.734 0.355,0.359 0.541,0.682 0.657,1.136 0.327,1.278 -0.442,2.611 -1.723,2.987 -0.282,0.083 -0.817,0.114 -1.099,0.063z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#00ff00"/>
|
||||
<path
|
||||
android:pathData="M11.617,21.707C10.518,20.424 9.338,18.864 8.395,17.449 6.524,14.641 5.455,12.305 5.102,10.255 5.014,9.744 5.006,8.628 5.088,8.137 5.348,6.561 6.043,5.221 7.158,4.148 9.148,2.231 12.016,1.668 14.593,2.688c2.043,0.809 3.607,2.581 4.162,4.719 0.174,0.67 0.204,0.933 0.203,1.761 -0.001,0.81 -0.035,1.098 -0.22,1.857 -0.614,2.524 -2.571,5.977 -5.383,9.501 -0.645,0.809 -1.321,1.61 -1.358,1.61 -0.008,0 -0.179,-0.193 -0.381,-0.428zM12.617,11.603c0.783,-0.188 1.457,-0.795 1.738,-1.564 0.516,-1.415 -0.317,-2.962 -1.783,-3.312 -0.216,-0.052 -0.317,-0.059 -0.661,-0.047 -0.354,0.012 -0.441,0.025 -0.682,0.104 -0.673,0.221 -1.205,0.695 -1.506,1.344 -0.176,0.38 -0.218,0.584 -0.217,1.054 0.001,0.324 0.014,0.452 0.064,0.635 0.266,0.97 1.077,1.689 2.079,1.844 0.243,0.038 0.68,0.012 0.968,-0.057z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#4caf50"
|
||||
android:strokeColor="#003b59"
|
||||
android:fillAlpha="1"/>
|
||||
<path
|
||||
android:pathData="M17.9025,7.0798 L14.1612,6.7552 12.7003,3.3154c-0.2628,-0.6261 -1.1595,-0.6261 -1.4223,0L9.817,6.7629 6.0835,7.0798C5.4032,7.134 5.125,7.9842 5.6429,8.4326l2.8369,2.4581 -0.8503,3.6485c-0.1546,0.6648 0.5643,1.1904 1.1518,0.8348l3.2079,-1.9325 3.2079,1.9402c0.5875,0.3556 1.3064,-0.1701 1.1518,-0.8348L15.4985,10.8907 18.3354,8.4326C18.8533,7.9842 18.5827,7.134 17.9025,7.0798Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#f84d4d"
|
||||
android:fillAlpha="1"
|
||||
android:strokeColor="#003b59"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="28dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="28">
|
||||
<path
|
||||
android:pathData="M6.072,22.223a6.031,3.672 0,1 0,12.062 0a6.031,3.672 0,1 0,-12.062 0z"
|
||||
android:strokeAlpha="0.1"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#000000"
|
||||
android:fillAlpha="0.1"/>
|
||||
<path
|
||||
android:pathData="M11.575,11.62C10.689,11.462 9.902,10.759 9.625,9.878 9.553,9.65 9.535,9.499 9.538,9.14c0.004,-0.397 0.019,-0.492 0.13,-0.787 0.236,-0.631 0.646,-1.099 1.212,-1.382 0.386,-0.193 0.709,-0.272 1.116,-0.272 0.676,0 1.263,0.247 1.744,0.734 0.355,0.359 0.541,0.682 0.657,1.136 0.327,1.278 -0.442,2.611 -1.723,2.987 -0.282,0.083 -0.817,0.114 -1.099,0.063z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#00ff00"/>
|
||||
<path
|
||||
android:pathData="M11.617,21.707C10.518,20.424 9.338,18.864 8.395,17.449 6.524,14.641 5.455,12.305 5.102,10.255 5.014,9.744 5.006,8.628 5.088,8.137 5.348,6.561 6.043,5.221 7.158,4.148 9.148,2.231 12.016,1.668 14.593,2.688c2.043,0.809 3.607,2.581 4.162,4.719 0.174,0.67 0.204,0.933 0.203,1.761 -0.001,0.81 -0.035,1.098 -0.22,1.857 -0.614,2.524 -2.571,5.977 -5.383,9.501 -0.645,0.809 -1.321,1.61 -1.358,1.61 -0.008,0 -0.179,-0.193 -0.381,-0.428zM12.617,11.603c0.783,-0.188 1.457,-0.795 1.738,-1.564 0.516,-1.415 -0.317,-2.962 -1.783,-3.312 -0.216,-0.052 -0.317,-0.059 -0.661,-0.047 -0.354,0.012 -0.441,0.025 -0.682,0.104 -0.673,0.221 -1.205,0.695 -1.506,1.344 -0.176,0.38 -0.218,0.584 -0.217,1.054 0.001,0.324 0.014,0.452 0.064,0.635 0.266,0.97 1.077,1.689 2.079,1.844 0.243,0.038 0.68,0.012 0.968,-0.057z"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#454547"
|
||||
android:strokeColor="#003b59"
|
||||
android:fillAlpha="1"/>
|
||||
<path
|
||||
android:pathData="M17.9025,7.0798 L14.1612,6.7552 12.7003,3.3154c-0.2628,-0.6261 -1.1595,-0.6261 -1.4223,0L9.817,6.7629 6.0835,7.0798C5.4032,7.134 5.125,7.9842 5.6429,8.4326l2.8369,2.4581 -0.8503,3.6485c-0.1546,0.6648 0.5643,1.1904 1.1518,0.8348l3.2079,-1.9325 3.2079,1.9402c0.5875,0.3556 1.3064,-0.1701 1.1518,-0.8348L15.4985,10.8907 18.3354,8.4326C18.8533,7.9842 18.5827,7.134 17.9025,7.0798Z"
|
||||
android:strokeAlpha="1"
|
||||
android:strokeWidth="1"
|
||||
android:fillColor="#f84d4d"
|
||||
android:fillAlpha="1"
|
||||
android:strokeColor="#003b59"/>
|
||||
</vector>
|
||||
29
app/src/main/res/layout/fragment_search_depictions.xml
Normal file
29
app/src/main/res/layout/fragment_search_depictions.xml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="@dimen/tiny_gap"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/depictionNotFound"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:gravity="center"
|
||||
android:visibility="gone" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/depictionsSearchResultsList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbarSize="@dimen/standard_gap"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/depictionSearchInitialLoadProgress"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
@ -6,13 +6,21 @@
|
|||
android:focusableInTouchMode="true"
|
||||
android:minHeight="@dimen/large_height">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bookmarkButtonImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/standard_gap"
|
||||
android:tint="?attr/rowButtonColor"
|
||||
app:srcCompat="@drawable/ic_round_star_border_24px" />
|
||||
|
||||
<com.facebook.drawee.view.SimpleDraweeView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="@dimen/dimen_40"
|
||||
android:layout_height="@dimen/dimen_40"
|
||||
android:layout_marginLeft="@dimen/standard_gap"
|
||||
android:layout_marginStart="@dimen/standard_gap"
|
||||
android:layout_marginTop="@dimen/standard_gap"
|
||||
android:layout_alignStart="@id/bookmarkButtonImage"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="@drawable/empty_photo"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,42 +1,14 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/buttonLayout"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
android:layout_marginTop="@dimen/standard_gap"
|
||||
android:layout_below="@+id/icon"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bookmarkRowButton"
|
||||
android:layout_width="@dimen/dimen_0"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:padding="@dimen/standard_gap"
|
||||
android:clickable="true"
|
||||
android:orientation="vertical"
|
||||
android:background="@drawable/button_background_selector"
|
||||
>
|
||||
<ImageView
|
||||
android:id="@+id/bookmarkRowButtonImage"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
app:srcCompat="@drawable/ic_round_star_border_24px"
|
||||
android:tint="?attr/bookmarkButtonColor"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/bookmarkButtonText"
|
||||
android:paddingTop="@dimen/activity_margin_horizontal"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:text="CAMERA"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
</LinearLayout>
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/buttonLayout"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"
|
||||
android:layout_marginTop="@dimen/standard_gap"
|
||||
android:layout_below="@+id/icon"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/cameraButton"
|
||||
|
|
@ -151,4 +123,4 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
|||
|
|
@ -103,29 +103,28 @@
|
|||
android:background="@color/divider_grey" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/depicts_next"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:text="@string/next" />
|
||||
android:id="@+id/depicts_next"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginRight="24dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:text="@string/next" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/depicts_previous"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/standard_gap"
|
||||
android:layout_marginRight="@dimen/standard_gap"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:layout_toStartOf="@id/depicts_next"
|
||||
android:layout_toLeftOf="@id/depicts_next"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:text="@string/previous" />
|
||||
android:id="@+id/depicts_previous"
|
||||
style="@style/Widget.AppCompat.Button.Borderless"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:layout_marginEnd="@dimen/standard_gap"
|
||||
android:layout_marginRight="@dimen/standard_gap"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_toLeftOf="@id/depicts_next"
|
||||
android:layout_toStartOf="@id/depicts_next"
|
||||
android:text="@string/previous" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
|
|||
|
|
@ -70,8 +70,10 @@
|
|||
<string name="provider_contributions">بارگذاریهای من</string>
|
||||
<string name="menu_share">به اشتراکگذاشتن</string>
|
||||
<string name="menu_open_in_browser">مشاهده در مرورگر</string>
|
||||
<string name="share_title_hint" fuzzy="true">عنوان (مورد نیاز)</string>
|
||||
<string name="share_title_hint">شرح مختصر (الزامی)</string>
|
||||
<string name="add_caption_toast">لطفاً شرح مختصری برای این پرونده بنویسید</string>
|
||||
<string name="share_description_hint">توضیحات</string>
|
||||
<string name="share_caption_hint">شرح مختصر (محدود به ۲۵۵ نویسه)</string>
|
||||
<string name="login_failed_network">قادر به ورود نیست - شکست شبکهای</string>
|
||||
<string name="login_failed_wrong_credentials">ورود به سیستم امکان پذیر نیست . لطفا نام کاربری و رمز عبور خود را بررسی کنید.</string>
|
||||
<string name="login_failed_throttled">تلاش ناموفق بیش از حد. لطفاً چند دقیقهٔ دیگر دوباره تلاش کنید</string>
|
||||
|
|
@ -175,6 +177,7 @@
|
|||
<string name="detail_panel_cats_label">ردهها</string>
|
||||
<string name="detail_panel_cats_loading">در حال بارگیری…</string>
|
||||
<string name="detail_panel_cats_none">هیچکدام انتخابنشده</string>
|
||||
<string name="detail_caption_empty">فاقد شرح</string>
|
||||
<string name="detail_description_empty">بدون توضیحات</string>
|
||||
<string name="detail_discussion_empty">بدون بحث</string>
|
||||
<string name="detail_license_empty">مجوز ناشناخته</string>
|
||||
|
|
@ -193,6 +196,7 @@
|
|||
<string name="upload">بارگذاری</string>
|
||||
<string name="yes">بله</string>
|
||||
<string name="no">خیر</string>
|
||||
<string name="media_detail_caption">شرح مختصر</string>
|
||||
<string name="media_detail_title">عنوان</string>
|
||||
<string name="media_detail_description">توضیح</string>
|
||||
<string name="media_detail_discussion">بحث</string>
|
||||
|
|
@ -255,6 +259,7 @@
|
|||
<string name="error_while_cache">خطا در زمان دریافت تصاویر</string>
|
||||
<string name="title_info">عنوانی توصیفی و یکتا برای پرونده که به عنوان نام پرونده در نظر گرفته خواهد شد. ترجیحاً به زبان ساده باشد، میتوانید فاصله هم به کار ببرید. پسوند پرونده را ننویسید.</string>
|
||||
<string name="description_info">لطفاً تصویر را تا حد توان شرح دهید. کجا گرفته شدهاست؟ شامل چه چیزی میشود؟ لطفاً اشیا یا افراد را شرح دهید. اطلاعاتی که به راحتی قابل مشاهده هستند را صرفهنظر کنید. اگر چیزی در تصویر غیر طبیعی به نظر میرسد آن را شرح دهید.</string>
|
||||
<string name="caption_info">لطفاً شرح مختصری برای این تصویر بنویسید. (محدود به ۲۵۵ نویسه)</string>
|
||||
<string name="upload_image_too_dark">این تصویر خیلی تار است. آیا مطمئنید که میخواهید آن را بارگذاری کنید؟ ویکیانبار فقط برای نگهداری از تصاویری است که ارزش دانشنامهای داشته باشند.</string>
|
||||
<string name="upload_image_blurry">این تصویر خیلی محو است. آیا مطمئنید که میخواهید آن را بارگذاری کنید؟ ویکیانبار فقط برای نگهداری از تصاویری است که ارزش دانشنامهای داشته باشند.</string>
|
||||
<string name="upload_problem_exist">مشکلات احتمالی با این تصویر :</string>
|
||||
|
|
@ -385,6 +390,7 @@
|
|||
<string name="list_sheet">فهرست</string>
|
||||
<string name="storage_permission">اجازه ذخیره</string>
|
||||
<string name="step_count">گام %1$d از %2$d</string>
|
||||
<string name="image_in_set_label">تصویر %1$d در مجموعه</string>
|
||||
<string name="next">بعدی</string>
|
||||
<string name="previous">قبلی</string>
|
||||
<string name="submit">ارسال</string>
|
||||
|
|
@ -473,6 +479,7 @@
|
|||
<string name="share_via">اشتراک از طریق...</string>
|
||||
<string name="image_info">اطلاعات عکس</string>
|
||||
<string name="no_categories_found">هیچ ردهای یافت نشد</string>
|
||||
<string name="upload_cancelled">بارگذاری لغو شد</string>
|
||||
<string name="delete_helper_show_deletion_title_success">موفق</string>
|
||||
<string name="delete_helper_show_deletion_title_failed">ناموفق</string>
|
||||
<string name="delete_helper_ask_spam_selfie">یک سلفی</string>
|
||||
|
|
|
|||
|
|
@ -618,4 +618,5 @@
|
|||
<string name="ask_to_turn_location_on">Activer la localisation ?</string>
|
||||
<string name="nearby_needs_location">À proximité nécessite que la localisation soit activée pour bien fonctionner</string>
|
||||
<string name="use_location_from_similar_image">Avez-vous pris ces deux photos du même endroit ? Voulez-vous utiliser les latitude/longitude de l’image sur la droite ?</string>
|
||||
<string name="nearby_no_results">Pas d’endroit trouvé, essayez de modifier vos critères de recherche.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
<string name="preference_category_privacy">Persónuvernd</string>
|
||||
<string name="preference_category_location">Staðsetning</string>
|
||||
<string name="app_name">Commons</string>
|
||||
<string name="bullet">• \</string>
|
||||
<string name="bullet">• \\</string>
|
||||
<string name="menu_settings">Stillingar</string>
|
||||
<string name="intent_share_upload_label">Senda inn á Commons</string>
|
||||
<string name="username">Notandanafn</string>
|
||||
|
|
@ -92,7 +92,7 @@
|
|||
<string name="title_activity_category_details">Flokkur</string>
|
||||
<string name="title_activity_review">Yfirlestur jafningja</string>
|
||||
<string name="menu_about">Um</string>
|
||||
<string name="about_license">Wikimedia Commons forritið er opinn og frjáls hugbúnaður sem gerður er og viðhaldið af stuðningsaðilum og sjálfboðaliðum Wikimedia samfélagsins. Wikimedia Foundation sjálfseignarstofnunin kemur ekki að gerð, forritun eða viðhaldi forritsins. \</string>
|
||||
<string name="about_license">Wikimedia Commons forritið er opinn og frjáls hugbúnaður sem gerður er og viðhaldið af stuðningsaðilum og sjálfboðaliðum Wikimedia samfélagsins. Wikimedia Foundation sjálfseignarstofnunin kemur ekki að gerð, forritun eða viðhaldi forritsins. \\</string>
|
||||
<string name="about_improve">Útbúðu nýjar <a href=\"%1$s\">GitHub tilkynningar (issue)</a> til að koma villum og uppástungum á framfæri.</string>
|
||||
<string name="about_privacy_policy">Meðferð persónuupplýsinga</string>
|
||||
<string name="about_credits">Framlög</string>
|
||||
|
|
@ -322,7 +322,7 @@
|
|||
<string name="wallpaper_set_successfully">Tókst að stilla bakgrunnsmynd!</string>
|
||||
<string name="quiz">Spurningaleikur</string>
|
||||
<string name="quiz_question_string">Er í lagi að senda inn þessa mynd?</string>
|
||||
<string name="question">Spurning \</string>
|
||||
<string name="question">Spurning \\</string>
|
||||
<string name="result">Niðurstaða</string>
|
||||
<string name="quiz_back_button">Ef þú heldur áfram að senda inn myndir sem krefjast að þeim sé eytt, er líklegt að aðgangurinn þinn verði bannaður. Ertu viss um að þú viljir hætta í spurningaleiknum?</string>
|
||||
<string name="quiz_alert_message">Meira en %1$s myndanna sem þú hefur sent inn hefur verið eytt. Ef þú heldur áfram að senda inn myndir sem krefjast að þeim sé eytt, er líklegt að aðgangurinn þinn verði bannaður.\n\nMyndir þú vilja skða kennsluefnið aftur og taka síðan einn léttan spurningaleik til að hjálpa þér til að læra hvernig myndir þú ættir að senda inn og hverjar ekki?</string>
|
||||
|
|
@ -361,7 +361,7 @@
|
|||
<string name="images_used_by_wiki">Myndir notaðar</string>
|
||||
<string name="achievements_share_message">Deildu frammistöðu þinni með vinum þínum!</string>
|
||||
<string name="achievements_info_message">Stig þitt eykst eftir því sem þú uppfyllir betur þessar kröfur. Atriði í \"tölfræði\"-hlutanum teljast ekki með í stigaútreikningi þínum.</string>
|
||||
<string name="achievements_revert_limit_message">lágmarksfjöldi sem er krafist: \</string>
|
||||
<string name="achievements_revert_limit_message">lágmarksfjöldi sem er krafist: \\</string>
|
||||
<string name="images_uploaded_explanation">Heildarfjöldi mynda sem þú hefur sent inn á Commons, með hverskyns innsendingarhugbúnaði</string>
|
||||
<string name="images_reverted_explanation">Prósentuhlutfall mynda sem þú hefur sent inn á Commons sem ekki hefur verið eytt</string>
|
||||
<string name="images_used_explanation">Heildarfjöldi mynda sem þú hefur sent inn á Commons sem hafa verið notaðar með Wikimedia-greinum</string>
|
||||
|
|
|
|||
|
|
@ -620,4 +620,5 @@
|
|||
<string name="ask_to_turn_location_on">להפעיל מיקום?</string>
|
||||
<string name="nearby_needs_location">פעולת \"בסביבה\" זקוקה לשירותי מיקומי פועלים כדי לעבוד כמו שצריך</string>
|
||||
<string name="use_location_from_similar_image">האם צילמת את שתי התמונות באותו המקום? האם ברצונך להשתמש בקו הרוחב וקו האורך של התמונה משמאל?</string>
|
||||
<string name="nearby_no_results">לא נמצאו מקומות, נא לנסות לשנות את החיפוש.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Authors:
|
||||
* Abijeet Patro
|
||||
* Diki Ananta
|
||||
* N219
|
||||
* NoiX180
|
||||
* Notanotheramy
|
||||
-->
|
||||
|
|
@ -15,11 +17,11 @@
|
|||
<string name="bullet">•</string>
|
||||
<string name="menu_settings">Setèlan</string>
|
||||
<string name="intent_share_upload_label">Unggah menyang Commons</string>
|
||||
<string name="username">Jeneng panganggo</string>
|
||||
<string name="password">Tembung wadi</string>
|
||||
<string name="username">Jeneng naraguna</string>
|
||||
<string name="password">Tembung sandi</string>
|
||||
<string name="login_credential">Mlebu log akun Commons Beta panjenengan</string>
|
||||
<string name="login">Mlebu log</string>
|
||||
<string name="forgot_password">Lali Tembung Wadi?</string>
|
||||
<string name="forgot_password">Lali Tembung Sandi?</string>
|
||||
<string name="signup">Dhaftar</string>
|
||||
<string name="logging_in_title">Lagi mlebu log</string>
|
||||
<string name="logging_in_message">Entènana sadhela…</string>
|
||||
|
|
@ -53,9 +55,9 @@
|
|||
<string name="share_title_hint">Sesirah (Kudu)</string>
|
||||
<string name="share_description_hint">Wedharan</string>
|
||||
<string name="login_failed_network">Ora bisa mlebu log - jaringané gagal</string>
|
||||
<string name="login_failed_wrong_credentials">Ora bisa mlebu log - priksa jeneng panganggo lan tembung wadi panjenengan</string>
|
||||
<string name="login_failed_wrong_credentials">Ora bisa mlebu log - priksa jeneng naraguna lan tembung sandi panjenengan</string>
|
||||
<string name="login_failed_throttled">Kakèhan upaya sing gagal. Jajalana manèh mengko.</string>
|
||||
<string name="login_failed_blocked">Ngapunten, panganggo iki wis diblokir ing Commons</string>
|
||||
<string name="login_failed_blocked">Ngapunten, naraguna iki wis diblokir ing Commons</string>
|
||||
<string name="login_failed_2fa_needed">Panjenengan kudu ngisi kodhe otèntifikasi rong faktoré panjenengan</string>
|
||||
<string name="login_failed_generic">Wurung mlebu log</string>
|
||||
<string name="share_upload_button">Unggah</string>
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
<!-- Authors:
|
||||
* Abijeet Patro
|
||||
* CYAN
|
||||
* Codenstory
|
||||
* Dlsrks1021
|
||||
* Doyoon1995
|
||||
* Freebiekr
|
||||
|
|
@ -78,6 +79,7 @@
|
|||
<string name="provider_modifications">바뀜</string>
|
||||
<string name="menu_upload_single">올리기</string>
|
||||
<string name="categories_search_text_hint">분류 검색</string>
|
||||
<string name="depicts_search_text_hint">미디어가 서술한 항목을 검색하세요. (산, 타지마할 등)</string>
|
||||
<string name="menu_save_categories">저장</string>
|
||||
<string name="refresh_button">새로 고침</string>
|
||||
<string name="display_list_button">목록</string>
|
||||
|
|
@ -98,6 +100,9 @@
|
|||
</plurals>
|
||||
<string name="categories_not_found">%1$s와(과) 일치하는 분류를 찾을 수 없습니다</string>
|
||||
<string name="depictions_not_found">%1$s에 대한 위키데이터 검색 결과가 없습니다</string>
|
||||
<string name="no_child_classes">%1$s에 자식 클래스가 없습니다</string>
|
||||
<string name="no_parent_classes">%1$s에 부모 클래스가 없습니다</string>
|
||||
<string name="depictions_image_not_found">서술을 위한 이미지가 없습니다</string>
|
||||
<string name="categories_skip_explanation">위키미디어 공용에서 그림을 더 찾기 쉽게 만들기 위해 분류를 추가합니다.\n분류를 추가하려면 입력을 시작하세요.</string>
|
||||
<string name="categories_activity_title">분류</string>
|
||||
<string name="title_activity_settings">설정</string>
|
||||
|
|
@ -113,6 +118,7 @@
|
|||
<string name="menu_feedback">(이메일로) 피드백 보내기</string>
|
||||
<string name="no_email_client">설치된 이메일 클라이언트가 없습니다</string>
|
||||
<string name="provider_categories">최근에 사용한 분류</string>
|
||||
<string name="provider_depictions">최근에 사용한 서술</string>
|
||||
<string name="waiting_first_sync">첫 동기화를 기다리는 중…</string>
|
||||
<string name="no_uploads_yet">아직 사진을 올리지 않았습니다.</string>
|
||||
<string name="menu_retry_upload">다시 시도</string>
|
||||
|
|
@ -173,6 +179,7 @@
|
|||
<string name="detail_panel_cats_loading">불러오는 중…</string>
|
||||
<string name="detail_panel_cats_none">선택하지 않음</string>
|
||||
<string name="detail_caption_empty">설명 없음</string>
|
||||
<string name="detail_depiction_empty">서술 없음</string>
|
||||
<string name="detail_description_empty">설명 없음</string>
|
||||
<string name="detail_discussion_empty">토론 없음</string>
|
||||
<string name="detail_license_empty">알 수 없는 라이선스</string>
|
||||
|
|
@ -193,6 +200,7 @@
|
|||
<string name="no">아니오</string>
|
||||
<string name="media_detail_caption">설명</string>
|
||||
<string name="media_detail_title">제목</string>
|
||||
<string name="media_detail_depiction">서술</string>
|
||||
<string name="media_detail_description">설명</string>
|
||||
<string name="media_detail_discussion">토론</string>
|
||||
<string name="media_detail_author">저자</string>
|
||||
|
|
@ -253,6 +261,7 @@
|
|||
<string name="error_while_cache">그림 캐시 처리 오류</string>
|
||||
<string name="title_info">이 파일을 설명할 수 있는 제목으로, 파일 이름으로 사용됩니다. 띄어쓰기를 포함한 일반적인 단어를 사용할 수 있습니다. 파일 확장자는 포함하지 마세요</string>
|
||||
<string name="description_info">미디어에 대해 가능한 많이 설명하십시오: 어디서 촬영한 것인가? 무엇을 보여주는가? 무슨 문맥을 가지는가? 물건이나 사람에 대해 설명하십시오. 풍경에서 시간을 알려주는 것처럼 쉽게 추측할 수 없는 정보를 제공합니다. 미디어가 평범하지 않다면 무엇이 이를 평범하지 않게 만들었는지 설명하십시오.</string>
|
||||
<string name="caption_info">그림 설명을 작성해 주세요. (255자 제한)</string>
|
||||
<string name="upload_image_too_dark">이 사진은 너무 어둡습니다. 정말 업로드하시겠습니까? 위키미디어 공용은 백과사전적인 가치가 있는 사진을 위한 공간입니다.</string>
|
||||
<string name="upload_image_blurry">이 사진은 흐릿합니다. 정말 업로드하시겠습니까? 위키미디어 공용은 백과사전적인 가치가 있는 사진을 위한 공간입니다.</string>
|
||||
<string name="upload_problem_exist">이 그림에 잠재적인 문제가 있습니다:</string>
|
||||
|
|
@ -326,6 +335,7 @@
|
|||
<string name="search_recent_header">최근 검색:</string>
|
||||
<string name="provider_searches">최근 검색된 쿼리</string>
|
||||
<string name="error_loading_categories">분류를 불러오는 동안 오류가 발생했습니다.</string>
|
||||
<string name="error_loading_depictions">서술을 불러오는 동안 오류가 발생했습니다.</string>
|
||||
<string name="error_loading_subcategories">하위 분류를 불러오는 동안 오류가 발생했습니다.</string>
|
||||
<string name="search_tab_title_media">미디어</string>
|
||||
<string name="search_tab_title_categories">분류</string>
|
||||
|
|
@ -426,7 +436,8 @@
|
|||
<string name="desc_language_Asia">아시아</string>
|
||||
<string name="desc_language_Pacific">태평양</string>
|
||||
<string name="no_categories_selected">선택된 분류 없음</string>
|
||||
<string name="no_categories_selected_warning_desc" fuzzy="true">분류가 없는 그림은 거의 유용하지 않습니다. 분류를 선택하지 않고 제출하시겠습니까?</string>
|
||||
<string name="no_categories_selected_warning_desc">분류가 없는 그림은 거의 유용하지 않습니다. 분류를 선택하지 않고 제출하시겠습니까?</string>
|
||||
<string name="no_depictions_selected">선택된 서술 없음</string>
|
||||
<string name="search_this_area">여기를 검색</string>
|
||||
<string name="nearby_card_permission_title">권한 요청</string>
|
||||
<string name="nearby_card_permission_explanation">사진이 필요한 주변 장소를 표시하기 위해 현재 위치를 사용하시겠습니까?</string>
|
||||
|
|
@ -477,6 +488,7 @@
|
|||
<string name="share_via">앱 공유...</string>
|
||||
<string name="image_info">이미지 정보</string>
|
||||
<string name="no_categories_found">분류가 없습니다</string>
|
||||
<string name="no_depiction_found">서술이 발견되지 않았습니다</string>
|
||||
<string name="upload_cancelled">업로드 취소됨</string>
|
||||
<string name="default_description_language">기본 설명 언어</string>
|
||||
<string name="delete_helper_show_deletion_title_success">성공</string>
|
||||
|
|
@ -500,6 +512,8 @@
|
|||
<string name="place_type">장소 유형:</string>
|
||||
<string name="nearby_search_hint">다리, 박물관, 호텔 등.</string>
|
||||
<string name="title_for_media">미디어</string>
|
||||
<string name="title_for_child_classes">자식 클래스</string>
|
||||
<string name="title_for_parent_classes">부모 클래스</string>
|
||||
<string name="title_app_shortcut_explore">찾아보기</string>
|
||||
<string name="title_app_shortcut_bookmark">북마크</string>
|
||||
<string name="title_app_shortcut_setting">설정</string>
|
||||
|
|
@ -511,4 +525,5 @@
|
|||
<string name="theme_dark_name">어두움</string>
|
||||
<string name="theme_light_name">밝음</string>
|
||||
<string name="ask_to_turn_location_on">위치를 켭니까?</string>
|
||||
<string name="nearby_no_results">발견된 장소가 없습니다. 검색 기준을 바꾸어 보십시오.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -594,4 +594,5 @@
|
|||
<string name="ask_to_turn_location_on">Да ја вклучам местоположбата?</string>
|
||||
<string name="nearby_needs_location">„Во близина“ бара местоположба за да работи</string>
|
||||
<string name="use_location_from_similar_image">Дали ги направивте ови две слики на истото место? Дали сакате да ја искористите географската ширина/должина од десната слика?</string>
|
||||
<string name="nearby_no_results">Не пронајдов места. Изменете ги критериумите во барањето.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -592,4 +592,5 @@
|
|||
<string name="ask_to_turn_location_on">Anvisché la localisassion?</string>
|
||||
<string name="nearby_needs_location">Nearby a l\'ha damanca dla localisassion abilità për marcé \'me ch\'as dev</string>
|
||||
<string name="use_location_from_similar_image">A la fàit se doe fòto ant l\'istess pòst? A veul dovré la latitùdin e la longitùdin ëd la fòto an sla drita?</string>
|
||||
<string name="nearby_no_results">Gnun pòst trovà, ch\'a preuva a modifiché ij sò criteri d\'arserca.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -606,4 +606,5 @@
|
|||
<string name="ask_to_turn_location_on">Ativar a localização?</string>
|
||||
<string name="nearby_needs_location">Proximidade precisa de localização ativado para funcionar corretamente</string>
|
||||
<string name="use_location_from_similar_image">Você tirou essas duas fotos no mesmo lugar? Deseja usar a latitude/longitude da imagem à direita?</string>
|
||||
<string name="nearby_no_results">Nenhum local encontrado, tente alterar seus critérios de pesquisa.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
* Redredsonia
|
||||
* Rubin16
|
||||
* Vlad5250
|
||||
* VoxelJ
|
||||
* Wikisaurus
|
||||
* Wirbel78
|
||||
* Евгения
|
||||
|
|
@ -76,6 +77,7 @@
|
|||
<string name="menu_share">Поделиться</string>
|
||||
<string name="menu_open_in_browser">Открыть в браузере</string>
|
||||
<string name="share_title_hint">Подпись (Обязательна)</string>
|
||||
<string name="add_caption_toast">Пожалуйста, укажите название этого файла</string>
|
||||
<string name="share_description_hint">Описание</string>
|
||||
<string name="login_failed_network">Не удаётся войти — сбой сети</string>
|
||||
<string name="login_failed_wrong_credentials">Не удаётся войти — проверьте ваше имя пользователя и пароль</string>
|
||||
|
|
@ -537,6 +539,7 @@
|
|||
<string name="images_featured_explanation">Избранные изображения обычно сделаны профессиональными фотографами и иллюстраторами. Такие изображения отмечены сообществом участников Викисклада как имеющие высшее качество на этом вебсайте.</string>
|
||||
<string name="images_via_nearby_explanation">Изображения, загруженные участниками, которые находили места с помощью функционала \"Места поблизости\".</string>
|
||||
<string name="thanks_received_explanation">Это функция позволяет участникам послать благодарность другим участникам за их полезные правки с помощью маленькой ссылки на странице истории страницы или странице разницы версий.</string>
|
||||
<string name="previous_image_caption_description">Скопировать предыдущие название и описание</string>
|
||||
<string name="previous_button_tooltip_message">Нажмите, чтобы использовать название и описание, которые вы ввели для предыдущего изображения, а потом потом модифицировать их</string>
|
||||
<string name="welcome_do_upload_content_description">Примеры изображений, подходящих для загрузки на Викисклад</string>
|
||||
<string name="welcome_dont_upload_content_description">Примеры изображений, которые не следует загружать на Викисклад</string>
|
||||
|
|
@ -607,4 +610,5 @@
|
|||
<string name="recommend_high_accuracy_mode">Для достижения наилучших результатов выберите режим высокой точности.</string>
|
||||
<string name="ask_to_turn_location_on">Включить местоположение?</string>
|
||||
<string name="nearby_needs_location">Рядом необходимо, чтобы местоположение было включено для правильной работы.</string>
|
||||
<string name="nearby_no_results">Мест не найдено, попытайтесь изменить критерии поиска.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -53,8 +53,10 @@
|
|||
<string name="provider_contributions">Moje obrázky</string>
|
||||
<string name="menu_share">Zdieľať</string>
|
||||
<string name="menu_open_in_browser">Otvoriť v prehliadači</string>
|
||||
<string name="share_title_hint" fuzzy="true">Názov (povinné)</string>
|
||||
<string name="share_title_hint">Popisok (povinné)</string>
|
||||
<string name="add_caption_toast">Prosím, pridajte popisok pre tento súbor</string>
|
||||
<string name="share_description_hint">Opis</string>
|
||||
<string name="share_caption_hint">Popisok (max. 255 znakov)</string>
|
||||
<string name="login_failed_network">prihlásenie zlyhalo - zlyhanie siete</string>
|
||||
<string name="login_failed_wrong_credentials">Nie je možné sa prihlásiť – skontrolujte, prosím, svoje používateľské meno a heslo</string>
|
||||
<string name="login_failed_throttled">Príliš veľa neúspešných pokusov. Skúste to znova o niekoľko minút.</string>
|
||||
|
|
@ -67,6 +69,7 @@
|
|||
<string name="provider_modifications">Úpravy</string>
|
||||
<string name="menu_upload_single">Nahrať</string>
|
||||
<string name="categories_search_text_hint">Vyhľadávanie kategórií</string>
|
||||
<string name="depicts_search_text_hint">Hľadať položky, ktoré váš multimediálny súbor zobrazuje (vrch, Tádž Mahal atď.)</string>
|
||||
<string name="menu_save_categories">Uložiť</string>
|
||||
<string name="refresh_button">Obnoviť</string>
|
||||
<string name="display_list_button">Zoznam</string>
|
||||
|
|
@ -87,6 +90,7 @@
|
|||
<item quantity="other">%1$d nahrania</item>
|
||||
</plurals>
|
||||
<string name="categories_not_found">Žiadne kategórie nezodpovedajú „%1$s“</string>
|
||||
<string name="depictions_not_found">Neboli nájdené žiadne položky Wikiúdajov zhodujúce sa s %1$s</string>
|
||||
<string name="categories_skip_explanation">Pridajte kategórie, aby bolo vaše obrázky možné na Wikimedia Commons nájsť.\nPre pridanie kategórií začnite písať.</string>
|
||||
<string name="categories_activity_title">Kategórie</string>
|
||||
<string name="title_activity_settings">Nastavenia</string>
|
||||
|
|
@ -162,6 +166,7 @@
|
|||
<string name="detail_panel_cats_label">Kategórie</string>
|
||||
<string name="detail_panel_cats_loading">Načítava sa…</string>
|
||||
<string name="detail_panel_cats_none">Nič nebolo vybrané</string>
|
||||
<string name="detail_caption_empty">Žiadny titulok</string>
|
||||
<string name="detail_description_empty">Bez popisu</string>
|
||||
<string name="detail_discussion_empty">Žiadna diskusia</string>
|
||||
<string name="detail_license_empty">Neznáma licencia</string>
|
||||
|
|
@ -175,6 +180,7 @@
|
|||
<string name="title_activity_nearby">Miesta v okolí</string>
|
||||
<string name="no_nearby">V okolí sa nenašli žiadne miesta</string>
|
||||
<string name="warning">Upozornenie</string>
|
||||
<string name="duplicate_image_found">Nájdený duplicitný obrázok</string>
|
||||
<string name="upload_image_duplicate">Tento súbor už na Commons existuje. Ste si istí, že chcete pokračovať?</string>
|
||||
<string name="upload">Nahrať</string>
|
||||
<string name="yes">Áno</string>
|
||||
|
|
@ -566,4 +572,5 @@
|
|||
<string name="theme_default_name" fuzzy="true">Predvolený</string>
|
||||
<string name="theme_dark_name">Tmavý</string>
|
||||
<string name="theme_light_name">Svetlý</string>
|
||||
<string name="ask_to_turn_location_on">Zapnúť lokáciu?</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -598,4 +598,5 @@
|
|||
<string name="ask_to_turn_location_on">Aktivera plats?</string>
|
||||
<string name="nearby_needs_location">I närheten behöver ha plats aktiverat för att fungera ordentligt</string>
|
||||
<string name="use_location_from_similar_image">Tog du dessa två bilder på samma plats? Vill du använda den högra bildens latitud/longitud?</string>
|
||||
<string name="nearby_no_results">Inga platser hittades, försök ändra dina sökkriterier.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -610,4 +610,5 @@
|
|||
<string name="ask_to_turn_location_on">Konum açılsın mı?</string>
|
||||
<string name="nearby_needs_location">Yakınımdakilerin düzgün çalışması için konumun açık olması gerekiyor</string>
|
||||
<string name="use_location_from_similar_image">Bu iki fotoğrafı aynı yerde mi çektiniz? Sağdaki resmin enlem/boylamını kullanmak ister misiniz?</string>
|
||||
<string name="nearby_no_results">Yer bulunamadı, arama kriterlerinizi değiştirmeyi deneyin.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -106,6 +106,9 @@
|
|||
</plurals>
|
||||
<string name="categories_not_found">Категорій, відповідних %1$s, не знайдено</string>
|
||||
<string name="depictions_not_found">Не знайдено елементів вікіданих, що відповідають %1$s</string>
|
||||
<string name="no_child_classes">%1$s не має дочірніх класів</string>
|
||||
<string name="no_parent_classes">%1$s не має батьківських класів</string>
|
||||
<string name="depictions_image_not_found">Немає зображення для описування</string>
|
||||
<string name="categories_skip_explanation">Додайте категорії, щоб Ваші зображення було легше знайти у Вікісховищі. \n\nПочніть вводити текст, щоб додати категорії.</string>
|
||||
<string name="categories_activity_title">Категорії</string>
|
||||
<string name="title_activity_settings">Налаштування</string>
|
||||
|
|
@ -206,6 +209,7 @@
|
|||
<string name="no">Ні</string>
|
||||
<string name="media_detail_caption">Підпис</string>
|
||||
<string name="media_detail_title">Назва</string>
|
||||
<string name="media_detail_depiction">Описи зображеного</string>
|
||||
<string name="media_detail_description">Опис</string>
|
||||
<string name="media_detail_discussion">Обговорення</string>
|
||||
<string name="media_detail_author">Автор</string>
|
||||
|
|
@ -347,9 +351,11 @@
|
|||
<string name="search_recent_header">Недавні пошуки:</string>
|
||||
<string name="provider_searches">Недавні пошукові запити</string>
|
||||
<string name="error_loading_categories">Сталася помилка під час завантаження категорій.</string>
|
||||
<string name="error_loading_depictions">Сталася помилка при завантаженні описів зображеного.</string>
|
||||
<string name="error_loading_subcategories">Сталася помилка під час завантаження підкатегорій.</string>
|
||||
<string name="search_tab_title_media">Медіафайли</string>
|
||||
<string name="search_tab_title_categories">Категорії</string>
|
||||
<string name="search_tab_title_depictions">Елементи</string>
|
||||
<string name="explore_tab_title_featured">Обране</string>
|
||||
<string name="explore_tab_title_mobile">Завантаження з мобільного</string>
|
||||
<string name="successful_wikidata_edit">Зображення додано до сторінки %1$s у Вікіданих!</string>
|
||||
|
|
@ -455,6 +461,7 @@
|
|||
<string name="desc_language_Pacific">Океанія</string>
|
||||
<string name="no_categories_selected">Жодної категорії не вибрано</string>
|
||||
<string name="no_categories_selected_warning_desc">Зображення без категорій рідко використовуються. Ви впевнені, що хочете продовжити без вказаних категорій?</string>
|
||||
<string name="no_depictions_selected">Описів зображеного не вибрано</string>
|
||||
<string name="no_depictions_selected_warning_desc">Медіа, у яких вказані зображувані об\'єкти, можуть бути легше знайдені та використані надалі. Ви впевнені, що хочете продовжити не вказавши що саме тут зображено?</string>
|
||||
<string name="upload_flow_all_images_in_set">(Для всіх зображень у наборі)</string>
|
||||
<string name="search_this_area">Шукати в цій зоні</string>
|
||||
|
|
@ -555,6 +562,7 @@
|
|||
<string name="share_via">Поділитися програмкою через…</string>
|
||||
<string name="image_info">Інформація про зображення</string>
|
||||
<string name="no_categories_found">Категорії не знайдені</string>
|
||||
<string name="no_depiction_found">Описів зображеного не знайдено</string>
|
||||
<string name="upload_cancelled">Завантаження скасовано</string>
|
||||
<string name="previous_image_title_description_not_found">Відсутній заголовок або опис попереднього зображення</string>
|
||||
<string name="dialog_box_text_nomination">Чому %1$s має бути видалено?</string>
|
||||
|
|
@ -587,6 +595,9 @@
|
|||
<string name="place_type">Тип місця:</string>
|
||||
<string name="nearby_search_hint">Міст, музей, готель тощо</string>
|
||||
<string name="you_must_reset_your_passsword">Щось пішло не так із процесом входу, ви маєте скинути пароль !</string>
|
||||
<string name="title_for_media">МЕДІА</string>
|
||||
<string name="title_for_child_classes">ДОЧІРНІ КЛАСИ</string>
|
||||
<string name="title_for_parent_classes">БАТЬКІВСЬКІ КЛАСИ</string>
|
||||
<string name="upload_nearby_place_found_title">Знайдено місце поблизу</string>
|
||||
<string name="upload_nearby_place_found_description">Чи це — фото місця %1$s?</string>
|
||||
<string name="title_app_shortcut_explore">Дослідити</string>
|
||||
|
|
@ -605,4 +616,5 @@
|
|||
<string name="ask_to_turn_location_on">Увімкнути визначення місця розташування?</string>
|
||||
<string name="nearby_needs_location">«Поблизу» потребує увімкненого визначення місця розташування, щоб працювати належним чином</string>
|
||||
<string name="use_location_from_similar_image">Ви зробили ці два знімки в одному й тому ж місці? Хочете використати широту/довготу зображення справа?</string>
|
||||
<string name="nearby_no_results">Місць не знайдено, спробуйте змінити критерії пошуку.</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -603,4 +603,5 @@
|
|||
<string name="ask_to_turn_location_on">打開位置?</string>
|
||||
<string name="nearby_needs_location">附近功能需要啟用位置才能運作正常</string>
|
||||
<string name="use_location_from_similar_image">您是否在同一地點拍攝了這兩張圖片?您要使用圖片右側的緯度/經度嗎?</string>
|
||||
<string name="nearby_no_results">查無地點,請嘗試更改您的搜尋條件。</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
* ZhaoGang
|
||||
* 予弦
|
||||
* 佛壁灯
|
||||
* 列维劳德
|
||||
* 夢蝶葬花
|
||||
* 沈澄心
|
||||
* 神樂坂秀吉
|
||||
|
|
@ -74,6 +75,7 @@
|
|||
<string name="menu_share">分享</string>
|
||||
<string name="menu_open_in_browser">在浏览器中查看</string>
|
||||
<string name="share_title_hint" fuzzy="true">标题 (要求)</string>
|
||||
<string name="add_caption_toast">请提供此文件的描述</string>
|
||||
<string name="share_description_hint">描述</string>
|
||||
<string name="login_failed_network">无法登录 - 网络故障</string>
|
||||
<string name="login_failed_wrong_credentials">无法登录——请检查您的用户名和密码</string>
|
||||
|
|
|
|||
23
app/src/test/kotlin/ModelFunctions.kt
Normal file
23
app/src/test/kotlin/ModelFunctions.kt
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import fr.free.nrw.commons.category.CategoryItem
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
|
||||
fun depictedItem(
|
||||
name: String = "label",
|
||||
description: String = "desc",
|
||||
imageUrl: String = "",
|
||||
instanceOfs: List<String> = listOf(),
|
||||
commonsCategories: List<String> = listOf(),
|
||||
isSelected: Boolean = false,
|
||||
id: String = "entityId"
|
||||
) = DepictedItem(
|
||||
name = name,
|
||||
description = description,
|
||||
imageUrl = imageUrl,
|
||||
instanceOfs = instanceOfs,
|
||||
commonsCategories = commonsCategories,
|
||||
isSelected = isSelected,
|
||||
id = id
|
||||
)
|
||||
|
||||
fun categoryItem(name: String = "name", selected: Boolean = false) =
|
||||
CategoryItem(name, selected)
|
||||
|
|
@ -35,7 +35,9 @@ class BookMarkLocationDaoTest {
|
|||
COLUMN_WIKIDATA_LINK,
|
||||
COLUMN_COMMONS_LINK,
|
||||
COLUMN_LAT,
|
||||
COLUMN_LONG)
|
||||
COLUMN_LONG,
|
||||
COLUMN_PIC,
|
||||
COLUMN_DESTROYED)
|
||||
private val client: ContentProviderClient = mock()
|
||||
private val database: SQLiteDatabase = mock()
|
||||
private val captor = argumentCaptor<ContentValues>()
|
||||
|
|
@ -93,6 +95,8 @@ class BookMarkLocationDaoTest {
|
|||
assertEquals(builder.build().wikipediaLink, it.siteLinks.wikipediaLink)
|
||||
assertEquals(builder.build().wikidataLink, it.siteLinks.wikidataLink)
|
||||
assertEquals(builder.build().commonsLink, it.siteLinks.commonsLink)
|
||||
assertEquals("picName",it.pic)
|
||||
assertEquals("placeDestroyed", it.destroyed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,7 +149,7 @@ class BookMarkLocationDaoTest {
|
|||
assertTrue(testObject.updateBookmarkLocation(examplePlaceBookmark))
|
||||
verify(client).insert(eq(BASE_URI), captor.capture())
|
||||
captor.firstValue.let { cv ->
|
||||
assertEquals(11, cv.size())
|
||||
assertEquals(12, cv.size())
|
||||
assertEquals(examplePlaceBookmark.name, cv.getAsString(COLUMN_NAME))
|
||||
assertEquals(examplePlaceBookmark.longDescription, cv.getAsString(COLUMN_DESCRIPTION))
|
||||
assertEquals(examplePlaceBookmark.label.text, cv.getAsString(COLUMN_LABEL_TEXT))
|
||||
|
|
@ -156,6 +160,7 @@ class BookMarkLocationDaoTest {
|
|||
assertEquals(examplePlaceBookmark.siteLinks.wikidataLink.toString(), cv.getAsString(COLUMN_WIKIDATA_LINK))
|
||||
assertEquals(examplePlaceBookmark.siteLinks.commonsLink.toString(), cv.getAsString(COLUMN_COMMONS_LINK))
|
||||
assertEquals(examplePlaceBookmark.pic.toString(), cv.getAsString(COLUMN_PIC))
|
||||
assertEquals(examplePlaceBookmark.destroyed.toString(), cv.getAsString(COLUMN_DESTROYED))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -251,12 +256,18 @@ class BookMarkLocationDaoTest {
|
|||
verify(database).execSQL(CREATE_TABLE_STATEMENT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun migrateTableVersionFrom_v12_to_v13() {
|
||||
onUpdate(database, 12, 13)
|
||||
verify(database).execSQL("ALTER TABLE bookmarksLocations ADD COLUMN location_destroyed STRING;")
|
||||
}
|
||||
|
||||
private fun createCursor(rowCount: Int) = MatrixCursor(columns, rowCount).apply {
|
||||
|
||||
for (i in 0 until rowCount) {
|
||||
addRow(listOf("placeName", "placeDescription","placeCategory", exampleLabel.text, exampleLabel.icon,
|
||||
exampleUri, builder.build().wikipediaLink, builder.build().wikidataLink, builder.build().commonsLink,
|
||||
exampleLocation.latitude, exampleLocation.longitude))
|
||||
exampleLocation.latitude, exampleLocation.longitude, "picName", "placeDestroyed"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
package fr.free.nrw.commons.category
|
||||
|
||||
import categoryItem
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import depictedItem
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||
import fr.free.nrw.commons.upload.GpsCategoryModel
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.subjects.BehaviorSubject
|
||||
|
|
@ -36,11 +39,11 @@ class CategoriesModelTest {
|
|||
|
||||
// Checking if both return "Test"
|
||||
val expectedItems = expectedList.map { CategoryItem(it, false) }
|
||||
categoriesModel.searchAll("tes", emptyList())
|
||||
categoriesModel.searchAll("tes", emptyList(), emptyList())
|
||||
.test()
|
||||
.assertValues(expectedItems)
|
||||
|
||||
categoriesModel.searchAll("Tes", emptyList())
|
||||
categoriesModel.searchAll("Tes", emptyList(), emptyList())
|
||||
.test()
|
||||
.assertValues(expectedItems)
|
||||
}
|
||||
|
|
@ -48,6 +51,7 @@ class CategoriesModelTest {
|
|||
@Test
|
||||
fun `searchAll with empty search terms creates results from gps, title search & recents`() {
|
||||
val gpsCategoryModel: GpsCategoryModel = mock()
|
||||
val depictedItem = depictedItem(commonsCategories = listOf("depictionCategory"))
|
||||
|
||||
whenever(gpsCategoryModel.categoriesFromLocation)
|
||||
.thenReturn(BehaviorSubject.createDefault(listOf("gpsCategory")))
|
||||
|
|
@ -55,13 +59,14 @@ class CategoriesModelTest {
|
|||
.thenReturn(Observable.just(listOf("titleSearch")))
|
||||
whenever(categoryDao.recentCategories(25)).thenReturn(listOf("recentCategories"))
|
||||
CategoriesModel(categoryClient, categoryDao, gpsCategoryModel)
|
||||
.searchAll("", listOf("tes"))
|
||||
.searchAll("", listOf("tes"), listOf(depictedItem))
|
||||
.test()
|
||||
.assertValue(
|
||||
listOf(
|
||||
CategoryItem("gpsCategory", false),
|
||||
CategoryItem("titleSearch", false),
|
||||
CategoryItem("recentCategories", false)
|
||||
categoryItem("depictionCategory"),
|
||||
categoryItem("gpsCategory"),
|
||||
categoryItem("titleSearch"),
|
||||
categoryItem("recentCategories")
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import com.nhaarman.mockitokotlin2.mock
|
||||
import com.nhaarman.mockitokotlin2.spy
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.Matchers.`is`
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
class SearchDepictionsDataSourceFactoryTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var depictsClient: DepictsClient
|
||||
|
||||
@Mock
|
||||
private lateinit var loadingStates: PublishProcessor<LoadingState>
|
||||
private lateinit var factory: SearchDepictionsDataSourceFactory
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
factory = SearchDepictionsDataSourceFactory(depictsClient, "test", loadingStates)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `create returns a dataSource`() {
|
||||
assertThat(
|
||||
factory.create(),
|
||||
`is`(SearchDepictionsDataSource(depictsClient, loadingStates, "test"))
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Rewrite with Mockk constructor mocks")
|
||||
fun `retryFailedRequest invokes method if not null`() {
|
||||
val spyFactory = spy(factory)
|
||||
val dataSource = mock<SearchDepictionsDataSource>()
|
||||
Mockito.doReturn(dataSource).`when`(spyFactory).create()
|
||||
factory.retryFailedRequest()
|
||||
verify(dataSource).retryFailedRequest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retryFailedRequest does not invoke method if null`() {
|
||||
factory.retryFailedRequest()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import androidx.paging.PositionalDataSource
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import fr.free.nrw.commons.explore.depictions.LoadingState.*
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.plugins.RxJavaPlugins
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
class SearchDepictionsDataSourceTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var depictsClient: DepictsClient
|
||||
|
||||
private lateinit var loadingStates: PublishProcessor<LoadingState>
|
||||
private lateinit var searchDepictionsDataSource: SearchDepictionsDataSource
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
|
||||
MockitoAnnotations.initMocks(this)
|
||||
loadingStates = PublishProcessor.create()
|
||||
searchDepictionsDataSource =
|
||||
SearchDepictionsDataSource(depictsClient, loadingStates, "test")
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
RxJavaPlugins.reset()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadInitial returns results and emits InitialLoad & Complete`() {
|
||||
val params = PositionalDataSource.LoadInitialParams(0, 1, 2, false)
|
||||
val callback = mock<PositionalDataSource.LoadInitialCallback<DepictedItem>>()
|
||||
whenever(depictsClient.searchForDepictions("test", 1, 0))
|
||||
.thenReturn(Single.just(emptyList()))
|
||||
val testSubscriber = loadingStates.test()
|
||||
searchDepictionsDataSource.loadInitial(params, callback)
|
||||
verify(callback).onResult(emptyList(), 0)
|
||||
testSubscriber.assertValues(InitialLoad, Complete)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadInitial onError does not return results and emits InitialLoad & Error`() {
|
||||
val params = PositionalDataSource.LoadInitialParams(0, 1, 2, false)
|
||||
val callback = mock<PositionalDataSource.LoadInitialCallback<DepictedItem>>()
|
||||
whenever(depictsClient.searchForDepictions("test", 1, 0))
|
||||
.thenThrow(RuntimeException())
|
||||
val testSubscriber = loadingStates.test()
|
||||
searchDepictionsDataSource.loadInitial(params, callback)
|
||||
verify(callback, never()).onResult(any(), any())
|
||||
testSubscriber.assertValues(InitialLoad, Error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadRange returns results and emits Loading & Complete`() {
|
||||
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||
.thenReturn(Single.just(emptyList()))
|
||||
val testSubscriber = loadingStates.test()
|
||||
searchDepictionsDataSource.loadRange(params, callback)
|
||||
verify(callback).onResult(emptyList())
|
||||
testSubscriber.assertValues(Loading, Complete)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadRange onError does not return results and emits Loading & Error`() {
|
||||
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||
.thenThrow(RuntimeException())
|
||||
val testSubscriber = loadingStates.test()
|
||||
searchDepictionsDataSource.loadRange(params, callback)
|
||||
verify(callback, never()).onResult(any())
|
||||
testSubscriber.assertValues(Loading, Error)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retryFailedRequest does nothing when null`() {
|
||||
searchDepictionsDataSource.retryFailedRequest()
|
||||
verifyNoMoreInteractions(depictsClient)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retryFailedRequest retries last request`() {
|
||||
val callback: PositionalDataSource.LoadRangeCallback<DepictedItem> = mock()
|
||||
val params = PositionalDataSource.LoadRangeParams(0, 1)
|
||||
whenever(depictsClient.searchForDepictions("test", params.loadSize, params.startPosition))
|
||||
.thenThrow(RuntimeException()).thenReturn(Single.just(emptyList()))
|
||||
val testSubscriber = loadingStates.test()
|
||||
searchDepictionsDataSource.loadRange(params, callback)
|
||||
verify(callback, never()).onResult(any())
|
||||
searchDepictionsDataSource.retryFailedRequest()
|
||||
verify(callback).onResult(emptyList())
|
||||
testSubscriber.assertValues(Loading, Error, Loading, Complete)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import com.jraska.livedata.test
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
class SearchDepictionsFragmentPresenterTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var instantTaskExecutorRule = InstantTaskExecutorRule()
|
||||
|
||||
@Mock
|
||||
internal lateinit var view: SearchDepictionsFragmentContract.View
|
||||
|
||||
private lateinit var searchDepictionsFragmentPresenter: SearchDepictionsFragmentPresenter
|
||||
|
||||
private lateinit var testScheduler: TestScheduler
|
||||
|
||||
@Mock
|
||||
private lateinit var searchableDepictionsDataSourceFactory: SearchableDepictionsDataSourceFactory
|
||||
|
||||
private var loadingStates: PublishProcessor<LoadingState> = PublishProcessor.create()
|
||||
|
||||
private var searchResults: PublishProcessor<LiveData<PagedList<DepictedItem>>> =
|
||||
PublishProcessor.create()
|
||||
|
||||
private var noItemLoadedEvent: PublishProcessor<Unit> = PublishProcessor.create()
|
||||
|
||||
@Before
|
||||
@Throws(Exception::class)
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(searchableDepictionsDataSourceFactory.searchResults).thenReturn(searchResults)
|
||||
whenever(searchableDepictionsDataSourceFactory.loadingStates).thenReturn(loadingStates)
|
||||
whenever(searchableDepictionsDataSourceFactory.noItemsLoadedEvent)
|
||||
.thenReturn(noItemLoadedEvent)
|
||||
testScheduler = TestScheduler()
|
||||
searchDepictionsFragmentPresenter = SearchDepictionsFragmentPresenter(
|
||||
testScheduler,
|
||||
searchableDepictionsDataSourceFactory
|
||||
)
|
||||
searchDepictionsFragmentPresenter.onAttachView(view)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `searchResults emission updates the view`() {
|
||||
val pagedListLiveData = mock<LiveData<PagedList<DepictedItem>>>()
|
||||
searchResults.offer(pagedListLiveData)
|
||||
verify(view).observeSearchResults(pagedListLiveData)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Loading offers a loading list item`() {
|
||||
onLoadingState(LoadingState.Loading)
|
||||
searchDepictionsFragmentPresenter.listFooterData.test()
|
||||
.assertValue(listOf(FooterItem.LoadingItem))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Complete offers an empty list item and hides initial loader`() {
|
||||
onLoadingState(LoadingState.Complete)
|
||||
searchDepictionsFragmentPresenter.listFooterData.test()
|
||||
.assertValue(emptyList())
|
||||
verify(view).hideInitialLoadProgress()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `InitialLoad shows initial loader`() {
|
||||
onLoadingState(LoadingState.InitialLoad)
|
||||
verify(view).showInitialLoadInProgress()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Error offers a refresh list item, hides initial loader and shows error with a set text`() {
|
||||
searchDepictionsFragmentPresenter.onQueryUpdated("test")
|
||||
onLoadingState(LoadingState.Error)
|
||||
verify(view).setEmptyViewText("test")
|
||||
verify(view).showSnackbar()
|
||||
verify(view).hideInitialLoadProgress()
|
||||
searchDepictionsFragmentPresenter.listFooterData.test()
|
||||
.assertValue(listOf(FooterItem.RefreshItem))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Error offers a refresh list item, hides initial loader and shows error with a unset text`() {
|
||||
onLoadingState(LoadingState.Error)
|
||||
verify(view, never()).setEmptyViewText(any())
|
||||
verify(view).showSnackbar()
|
||||
verify(view).hideInitialLoadProgress()
|
||||
searchDepictionsFragmentPresenter.listFooterData.test()
|
||||
.assertValue(listOf(FooterItem.RefreshItem))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no Items event sets empty view text`() {
|
||||
searchDepictionsFragmentPresenter.onQueryUpdated("test")
|
||||
noItemLoadedEvent.offer(Unit)
|
||||
verify(view).setEmptyViewText("test")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retryFailedRequest calls retry`() {
|
||||
searchDepictionsFragmentPresenter.retryFailedRequest()
|
||||
verify(searchableDepictionsDataSourceFactory).retryFailedRequest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onDetachView stops subscriptions`() {
|
||||
searchDepictionsFragmentPresenter.onDetachView()
|
||||
onLoadingState(LoadingState.Loading)
|
||||
searchDepictionsFragmentPresenter.listFooterData.test()
|
||||
.assertValue(emptyList())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onQueryUpdated updates dataSourceFactory`() {
|
||||
searchDepictionsFragmentPresenter.onQueryUpdated("test")
|
||||
verify(searchableDepictionsDataSourceFactory).onQueryUpdated("test")
|
||||
}
|
||||
|
||||
private fun onLoadingState(loadingState: LoadingState) {
|
||||
loadingStates.offer(loadingState)
|
||||
testScheduler.triggerActions()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
package fr.free.nrw.commons.explore.depictions
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.paging.PagedList
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import io.reactivex.processors.PublishProcessor
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
class SearchableDepictionsDataSourceFactoryTest {
|
||||
|
||||
@Mock
|
||||
private lateinit var searchDepictionsDataSourceFactoryFactory: SearchDepictionsDataSourceFactoryFactory
|
||||
|
||||
@Mock
|
||||
private lateinit var liveDataConverter: LiveDataConverter
|
||||
|
||||
private lateinit var factory: SearchableDepictionsDataSourceFactory
|
||||
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
factory = SearchableDepictionsDataSourceFactory(
|
||||
searchDepictionsDataSourceFactoryFactory,
|
||||
liveDataConverter
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onQueryUpdated emits new liveData`() {
|
||||
val (_, liveData) = expectNewLiveData()
|
||||
factory.searchResults.test()
|
||||
.also { factory.onQueryUpdated("test") }
|
||||
.assertValue(liveData)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `onQueryUpdated invokes livedatconverter with no items emitter`() {
|
||||
val (captor, _) = expectNewLiveData()
|
||||
factory.onQueryUpdated("test")
|
||||
factory.noItemsLoadedEvent.test()
|
||||
.also { captor.firstValue.invoke() }
|
||||
.assertValue(Unit)
|
||||
}
|
||||
|
||||
/*
|
||||
* Just for coverage, no way to really assert this
|
||||
* */
|
||||
@Test
|
||||
fun `retryFailedRequest does nothing without a factory`() {
|
||||
factory.retryFailedRequest()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `retryFailedRequest retries with a factory`() {
|
||||
val (_, _, dataSourceFactory) = expectNewLiveData()
|
||||
factory.onQueryUpdated("test")
|
||||
factory.retryFailedRequest()
|
||||
verify(dataSourceFactory).retryFailedRequest()
|
||||
}
|
||||
|
||||
private fun expectNewLiveData(): Triple<KArgumentCaptor<() -> Unit>, LiveData<PagedList<DepictedItem>>, SearchDepictionsDataSourceFactory> {
|
||||
val dataSourceFactory: SearchDepictionsDataSourceFactory = mock()
|
||||
whenever(
|
||||
searchDepictionsDataSourceFactoryFactory.create(
|
||||
"test",
|
||||
factory.loadingStates as PublishProcessor<LoadingState>
|
||||
)
|
||||
).thenReturn(dataSourceFactory)
|
||||
val captor = argumentCaptor<() -> Unit>()
|
||||
val liveData: LiveData<PagedList<DepictedItem>> = mock()
|
||||
whenever(liveDataConverter.convert(eq(dataSourceFactory), captor.capture()))
|
||||
.thenReturn(liveData)
|
||||
return Triple(captor, liveData, dataSourceFactory)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package fr.free.nrw.commons.upload
|
||||
|
||||
import categoryItem
|
||||
import com.nhaarman.mockitokotlin2.*
|
||||
import fr.free.nrw.commons.R
|
||||
import fr.free.nrw.commons.category.CategoryItem
|
||||
import fr.free.nrw.commons.repository.UploadRepository
|
||||
import fr.free.nrw.commons.upload.categories.CategoriesContract
|
||||
import fr.free.nrw.commons.upload.categories.CategoriesPresenter
|
||||
|
|
@ -27,11 +27,6 @@ class CategoriesPresenterTest {
|
|||
|
||||
private lateinit var testScheduler: TestScheduler
|
||||
|
||||
private val categoryItems: ArrayList<CategoryItem> = ArrayList()
|
||||
|
||||
@Mock
|
||||
lateinit var categoryItem: CategoryItem
|
||||
|
||||
/**
|
||||
* initial setup
|
||||
*/
|
||||
|
|
@ -40,7 +35,6 @@ class CategoriesPresenterTest {
|
|||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
testScheduler = TestScheduler()
|
||||
categoryItems.add(categoryItem)
|
||||
categoriesPresenter = CategoriesPresenter(repository, testScheduler, testScheduler)
|
||||
categoriesPresenter.onAttachView(view)
|
||||
}
|
||||
|
|
@ -62,7 +56,7 @@ class CategoriesPresenterTest {
|
|||
emptyCaptionUploadItem
|
||||
)
|
||||
)
|
||||
whenever(repository.searchAll("test", listOf("nonEmpty")))
|
||||
whenever(repository.searchAll("test", listOf("nonEmpty"), repository.selectedDepictions))
|
||||
.thenReturn(
|
||||
Observable.just(
|
||||
listOf(
|
||||
|
|
@ -87,7 +81,7 @@ class CategoriesPresenterTest {
|
|||
@Test
|
||||
fun `searchForCategoriesTest sets Error when list is empty`() {
|
||||
whenever(repository.uploads).thenReturn(listOf())
|
||||
whenever(repository.searchAll(any(), any())).thenReturn(Observable.just(listOf()))
|
||||
whenever(repository.searchAll(any(), any(), any())).thenReturn(Observable.just(listOf()))
|
||||
whenever(repository.selectedCategories).thenReturn(listOf())
|
||||
categoriesPresenter.searchForCategories("test")
|
||||
testScheduler.triggerActions()
|
||||
|
|
@ -124,10 +118,8 @@ class CategoriesPresenterTest {
|
|||
*/
|
||||
@Test
|
||||
fun onCategoryItemClickedTest() {
|
||||
val categoryItem = categoryItem()
|
||||
categoriesPresenter.onCategoryItemClicked(categoryItem)
|
||||
verify(repository).onCategoryClicked(categoryItem)
|
||||
}
|
||||
|
||||
private fun categoryItem(name: String = "name", selected: Boolean = false) =
|
||||
CategoryItem(name, selected)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
|||
import com.jraska.livedata.test
|
||||
import com.nhaarman.mockitokotlin2.verify
|
||||
import com.nhaarman.mockitokotlin2.whenever
|
||||
import depictedItem
|
||||
import fr.free.nrw.commons.explore.depictions.DepictsClient
|
||||
import fr.free.nrw.commons.repository.UploadRepository
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsContract
|
||||
import fr.free.nrw.commons.upload.depicts.DepictsPresenter
|
||||
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
|
||||
import fr.free.nrw.commons.wikidata.WikidataDisambiguationItems
|
||||
import io.reactivex.Flowable
|
||||
import io.reactivex.schedulers.TestScheduler
|
||||
|
|
@ -62,8 +62,8 @@ class DepictsPresenterTest {
|
|||
depictedItem(id="nonUnique"),
|
||||
depictedItem(id="nonUnique"),
|
||||
depictedItem(
|
||||
id = "unique",
|
||||
instanceOfs = listOf(WikidataDisambiguationItems.CATEGORY.id)
|
||||
instanceOfs = listOf(WikidataDisambiguationItems.CATEGORY.id),
|
||||
id = "unique"
|
||||
)
|
||||
)
|
||||
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(searchResults))
|
||||
|
|
@ -78,6 +78,7 @@ class DepictsPresenterTest {
|
|||
.assertValue(listOf(selectedItem, depictedItem(id="nonUnique")))
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `empty search results with empty term do not show error`() {
|
||||
whenever(repository.searchAllEntities("")).thenReturn(Flowable.just(emptyList()))
|
||||
|
|
@ -137,15 +138,4 @@ class DepictsPresenterTest {
|
|||
depictsPresenter.verifyDepictions()
|
||||
verify(view).noDepictionSelected()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
fun depictedItem(
|
||||
name: String = "label",
|
||||
description: String = "desc",
|
||||
imageUrl: String = "",
|
||||
instanceOfs: List<String> = listOf(),
|
||||
isSelected: Boolean = false,
|
||||
id: String = "entityId"
|
||||
) = DepictedItem(name, description, imageUrl, instanceOfs, isSelected, id)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue