mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-26 12:23:58 +01:00
Improve credit line in image list (#6295)
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
Some checks are pending
Android CI / Run tests and generate APK (push) Waiting to run
- When author is not uploader, show both. - When failing to parse author from HTML, use structured data.
This commit is contained in:
parent
30762971db
commit
329a68216e
21 changed files with 363 additions and 81 deletions
|
|
@ -333,6 +333,7 @@ android {
|
||||||
buildConfigField "String", "TEST_USERNAME", "\"" + getTestUserName() + "\""
|
buildConfigField "String", "TEST_USERNAME", "\"" + getTestUserName() + "\""
|
||||||
buildConfigField "String", "TEST_PASSWORD", "\"" + getTestPassword() + "\""
|
buildConfigField "String", "TEST_PASSWORD", "\"" + getTestPassword() + "\""
|
||||||
buildConfigField "String", "DEPICTS_PROPERTY", "\"P180\""
|
buildConfigField "String", "DEPICTS_PROPERTY", "\"P180\""
|
||||||
|
buildConfigField "String", "CREATOR_PROPERTY", "\"P170\""
|
||||||
dimension 'tier'
|
dimension 'tier'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -370,6 +371,7 @@ android {
|
||||||
buildConfigField "String", "TEST_USERNAME", "\"" + getTestUserName() + "\""
|
buildConfigField "String", "TEST_USERNAME", "\"" + getTestUserName() + "\""
|
||||||
buildConfigField "String", "TEST_PASSWORD", "\"" + getTestPassword() + "\""
|
buildConfigField "String", "TEST_PASSWORD", "\"" + getTestPassword() + "\""
|
||||||
buildConfigField "String", "DEPICTS_PROPERTY", "\"P245962\""
|
buildConfigField "String", "DEPICTS_PROPERTY", "\"P245962\""
|
||||||
|
buildConfigField "String", "CREATOR_PROPERTY", "\"P253075\""
|
||||||
dimension 'tier'
|
dimension 'tier'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ class Media constructor(
|
||||||
*/
|
*/
|
||||||
var author: String? = null,
|
var author: String? = null,
|
||||||
var user: String? = null,
|
var user: String? = null,
|
||||||
|
var creatorName: String? = null,
|
||||||
/**
|
/**
|
||||||
* Gets the categories the file falls under.
|
* Gets the categories the file falls under.
|
||||||
* @return file categories as an ArrayList of Strings
|
* @return file categories as an ArrayList of Strings
|
||||||
|
|
@ -66,6 +67,7 @@ class Media constructor(
|
||||||
var captions: Map<String, String> = emptyMap(),
|
var captions: Map<String, String> = emptyMap(),
|
||||||
var descriptions: Map<String, String> = emptyMap(),
|
var descriptions: Map<String, String> = emptyMap(),
|
||||||
var depictionIds: List<String> = emptyList(),
|
var depictionIds: List<String> = emptyList(),
|
||||||
|
var creatorIds: List<String> = emptyList(),
|
||||||
/**
|
/**
|
||||||
* This field was added to find non-hidden categories
|
* This field was added to find non-hidden categories
|
||||||
* Stores the mapping of category title to hidden attribute
|
* Stores the mapping of category title to hidden attribute
|
||||||
|
|
@ -130,6 +132,7 @@ class Media constructor(
|
||||||
* returns user
|
* returns user
|
||||||
* @return Author or User
|
* @return Author or User
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("Use user for uploader username. Use attributedAuthor() for attribution. Note that the uploader may not be the creator/author.")
|
||||||
fun getAuthorOrUser(): String? {
|
fun getAuthorOrUser(): String? {
|
||||||
return if (!author.isNullOrEmpty()) {
|
return if (!author.isNullOrEmpty()) {
|
||||||
author
|
author
|
||||||
|
|
@ -138,6 +141,19 @@ class Media constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns author if it's not null or empty, otherwise
|
||||||
|
* returns creator name
|
||||||
|
* @return name of author or creator
|
||||||
|
*/
|
||||||
|
fun getAttributedAuthor(): String? {
|
||||||
|
return if (!author.isNullOrEmpty()) {
|
||||||
|
author
|
||||||
|
} else{
|
||||||
|
creatorName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets media display title
|
* Gets media display title
|
||||||
* @return Media title
|
* @return Media title
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package fr.free.nrw.commons
|
package fr.free.nrw.commons
|
||||||
|
|
||||||
import androidx.core.text.HtmlCompat
|
import androidx.core.text.HtmlCompat
|
||||||
import fr.free.nrw.commons.media.IdAndCaptions
|
import fr.free.nrw.commons.media.IdAndLabels
|
||||||
import fr.free.nrw.commons.media.MediaClient
|
import fr.free.nrw.commons.media.MediaClient
|
||||||
import fr.free.nrw.commons.media.PAGE_ID_PREFIX
|
import fr.free.nrw.commons.media.PAGE_ID_PREFIX
|
||||||
import io.reactivex.Single
|
import io.reactivex.Single
|
||||||
|
|
@ -23,13 +23,23 @@ class MediaDataExtractor
|
||||||
private val mediaClient: MediaClient,
|
private val mediaClient: MediaClient,
|
||||||
) {
|
) {
|
||||||
fun fetchDepictionIdsAndLabels(media: Media) =
|
fun fetchDepictionIdsAndLabels(media: Media) =
|
||||||
mediaClient
|
mediaClient
|
||||||
.getEntities(media.depictionIds)
|
.getEntities(media.depictionIds)
|
||||||
.map {
|
.map {
|
||||||
it
|
it
|
||||||
.entities()
|
.entities()
|
||||||
.mapValues { entry -> entry.value.labels().mapValues { it.value.value() } }
|
.mapValues { entry -> entry.value.labels().mapValues { it.value.value() } }
|
||||||
}.map { it.map { (key, value) -> IdAndCaptions(key, value) } }
|
}.map { it.map { (key, value) -> IdAndLabels(key, value) } }
|
||||||
|
.onErrorReturn { emptyList() }
|
||||||
|
|
||||||
|
fun fetchCreatorIdsAndLabels(media: Media) =
|
||||||
|
mediaClient
|
||||||
|
.getEntities(media.creatorIds)
|
||||||
|
.map {
|
||||||
|
it
|
||||||
|
.entities()
|
||||||
|
.mapValues { entry -> entry.value.labels().mapValues { it.value.value() } }
|
||||||
|
}.map { it.map { (key, value) -> IdAndLabels(key, value) } }
|
||||||
.onErrorReturn { emptyList() }
|
.onErrorReturn { emptyList() }
|
||||||
|
|
||||||
fun checkDeletionRequestExists(media: Media) = mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + media.filename)
|
fun checkDeletionRequestExists(media: Media) = mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + media.filename)
|
||||||
|
|
|
||||||
|
|
@ -8,23 +8,29 @@ import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.facebook.imagepipeline.request.ImageRequest
|
import com.facebook.imagepipeline.request.ImageRequest
|
||||||
import com.facebook.imagepipeline.request.ImageRequestBuilder
|
import com.facebook.imagepipeline.request.ImageRequestBuilder
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.utils.MediaAttributionUtil
|
||||||
|
import fr.free.nrw.commons.MediaDataExtractor
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.databinding.LayoutContributionBinding
|
import fr.free.nrw.commons.databinding.LayoutContributionBinding
|
||||||
import fr.free.nrw.commons.media.MediaClient
|
import fr.free.nrw.commons.media.MediaClient
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
import io.reactivex.schedulers.Schedulers
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class ContributionViewHolder internal constructor(
|
class ContributionViewHolder internal constructor(
|
||||||
private val parent: View, private val callback: ContributionsListAdapter.Callback,
|
parent: View,
|
||||||
private val mediaClient: MediaClient
|
private val callback: ContributionsListAdapter.Callback,
|
||||||
|
private val compositeDisposable: CompositeDisposable,
|
||||||
|
private val mediaClient: MediaClient,
|
||||||
|
private val mediaDataExtractor: MediaDataExtractor
|
||||||
) : RecyclerView.ViewHolder(parent) {
|
) : RecyclerView.ViewHolder(parent) {
|
||||||
var binding: LayoutContributionBinding = LayoutContributionBinding.bind(parent)
|
var binding: LayoutContributionBinding = LayoutContributionBinding.bind(parent)
|
||||||
|
|
||||||
private var position = 0
|
private var position = 0
|
||||||
private var contribution: Contribution? = null
|
private var contribution: Contribution? = null
|
||||||
private val compositeDisposable = CompositeDisposable()
|
|
||||||
private var isWikipediaButtonDisplayed = false
|
private var isWikipediaButtonDisplayed = false
|
||||||
private val pausingPopUp: AlertDialog
|
private val pausingPopUp: AlertDialog
|
||||||
var imageRequest: ImageRequest? = null
|
var imageRequest: ImageRequest? = null
|
||||||
|
|
@ -54,7 +60,7 @@ an upload might take a dozen seconds. */
|
||||||
this.contribution = contribution
|
this.contribution = contribution
|
||||||
this.position = position
|
this.position = position
|
||||||
binding.contributionTitle.text = contribution.media.mostRelevantCaption
|
binding.contributionTitle.text = contribution.media.mostRelevantCaption
|
||||||
binding.authorView.text = contribution.media.getAuthorOrUser()
|
setAuthorText(contribution.media)
|
||||||
|
|
||||||
//Removes flicker of loading image.
|
//Removes flicker of loading image.
|
||||||
binding.contributionImage.hierarchy.fadeDuration = 0
|
binding.contributionImage.hierarchy.fadeDuration = 0
|
||||||
|
|
@ -93,6 +99,30 @@ an upload might take a dozen seconds. */
|
||||||
checkIfMediaExistsOnWikipediaPage(contribution)
|
checkIfMediaExistsOnWikipediaPage(contribution)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateAttribution() {
|
||||||
|
if (contribution != null) {
|
||||||
|
val media = contribution!!.media
|
||||||
|
if (!media.getAttributedAuthor().isNullOrEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
compositeDisposable.addAll(
|
||||||
|
mediaDataExtractor.fetchCreatorIdsAndLabels(media)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ idAndLabels ->
|
||||||
|
media.creatorName = MediaAttributionUtil.getCreatorName(idAndLabels)
|
||||||
|
setAuthorText(media)
|
||||||
|
},
|
||||||
|
{ t: Throwable? -> Timber.e(t) })
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setAuthorText(media: Media) {
|
||||||
|
binding.authorView.text = MediaAttributionUtil.getTagLine(media, itemView.context)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a media exists on the corresponding Wikipedia article Currently the check is made
|
* Checks if a media exists on the corresponding Wikipedia article Currently the check is made
|
||||||
* for the device's current language Wikipedia
|
* for the device's current language Wikipedia
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,26 @@ import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.paging.PagedListAdapter
|
import androidx.paging.PagedListAdapter
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
|
import fr.free.nrw.commons.MediaDataExtractor
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.media.MediaClient
|
import fr.free.nrw.commons.media.MediaClient
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents The View Adapter for the List of Contributions
|
* Represents The View Adapter for the List of Contributions
|
||||||
*/
|
*/
|
||||||
class ContributionsListAdapter internal constructor(
|
class ContributionsListAdapter internal constructor(
|
||||||
private val callback: Callback,
|
private val callback: Callback,
|
||||||
private val mediaClient: MediaClient
|
private val mediaClient: MediaClient,
|
||||||
|
private val mediaDataExtractor: MediaDataExtractor,
|
||||||
|
private val compositeDisposable: CompositeDisposable
|
||||||
) : PagedListAdapter<Contribution, ContributionViewHolder>(DIFF_CALLBACK) {
|
) : PagedListAdapter<Contribution, ContributionViewHolder>(DIFF_CALLBACK) {
|
||||||
/**
|
/**
|
||||||
* Initializes the view holder with contribution data
|
* Initializes the view holder with contribution data
|
||||||
*/
|
*/
|
||||||
override fun onBindViewHolder(holder: ContributionViewHolder, position: Int) {
|
override fun onBindViewHolder(holder: ContributionViewHolder, position: Int) {
|
||||||
holder.init(position, getItem(position))
|
holder.init(position, getItem(position))
|
||||||
|
holder.updateAttribution()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getContributionForPosition(position: Int): Contribution? {
|
fun getContributionForPosition(position: Int): Contribution? {
|
||||||
|
|
@ -36,7 +41,7 @@ class ContributionsListAdapter internal constructor(
|
||||||
val viewHolder = ContributionViewHolder(
|
val viewHolder = ContributionViewHolder(
|
||||||
LayoutInflater.from(parent.context)
|
LayoutInflater.from(parent.context)
|
||||||
.inflate(R.layout.layout_contribution, parent, false),
|
.inflate(R.layout.layout_contribution, parent, false),
|
||||||
callback, mediaClient
|
callback, compositeDisposable, mediaClient, mediaDataExtractor
|
||||||
)
|
)
|
||||||
return viewHolder
|
return viewHolder
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
|
||||||
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener
|
import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener
|
||||||
import androidx.recyclerview.widget.SimpleItemAnimator
|
import androidx.recyclerview.widget.SimpleItemAnimator
|
||||||
import fr.free.nrw.commons.Media
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.MediaDataExtractor
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.Utils
|
import fr.free.nrw.commons.Utils
|
||||||
import fr.free.nrw.commons.auth.SessionManager
|
import fr.free.nrw.commons.auth.SessionManager
|
||||||
|
|
@ -63,6 +64,10 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
||||||
@Inject
|
@Inject
|
||||||
var mediaClient: MediaClient? = null
|
var mediaClient: MediaClient? = null
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
@Inject
|
||||||
|
var mediaDataExtractor: MediaDataExtractor? = null
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
@Named(NetworkingModule.NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE)
|
@Named(NetworkingModule.NAMED_LANGUAGE_WIKI_PEDIA_WIKI_SITE)
|
||||||
@Inject
|
@Inject
|
||||||
|
|
@ -231,7 +236,7 @@ class ContributionsListFragment : CommonsDaggerSupportFragment(), ContributionsL
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initAdapter() {
|
private fun initAdapter() {
|
||||||
adapter = ContributionsListAdapter(this, mediaClient!!)
|
adapter = ContributionsListAdapter(this, mediaClient!!, mediaDataExtractor!!, compositeDisposable)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,12 @@ import javax.inject.Inject
|
||||||
class MediaConverter
|
class MediaConverter
|
||||||
@Inject
|
@Inject
|
||||||
constructor() {
|
constructor() {
|
||||||
|
/**
|
||||||
|
* Creating Media object from MWQueryPage.
|
||||||
|
*
|
||||||
|
* @param page response from the API
|
||||||
|
* @return Media object
|
||||||
|
*/
|
||||||
fun convert(
|
fun convert(
|
||||||
page: MwQueryPage,
|
page: MwQueryPage,
|
||||||
entity: Entities.Entity,
|
entity: Entities.Entity,
|
||||||
|
|
@ -40,24 +46,17 @@ class MediaConverter
|
||||||
metadata.prefixedLicenseUrl,
|
metadata.prefixedLicenseUrl,
|
||||||
getAuthor(metadata),
|
getAuthor(metadata),
|
||||||
imageInfo.getUser(),
|
imageInfo.getUser(),
|
||||||
|
null,
|
||||||
MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories()),
|
MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories()),
|
||||||
metadata.latLng,
|
metadata.latLng,
|
||||||
entity.labels().mapValues { it.value.value() },
|
entity.labels().mapValues { it.value.value() },
|
||||||
entity.descriptions().mapValues { it.value.value() },
|
entity.descriptions().mapValues { it.value.value() },
|
||||||
entity.depictionIds(),
|
entity.depictionIds(),
|
||||||
|
entity.creatorIds(),
|
||||||
myMap,
|
myMap,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creating Media object from MWQueryPage.
|
|
||||||
* Earlier only basic details were set for the media object but going forward,
|
|
||||||
* a full media object(with categories, descriptions, coordinates etc) can be constructed using this method
|
|
||||||
*
|
|
||||||
* @param page response from the API
|
|
||||||
* @return Media object
|
|
||||||
*/
|
|
||||||
|
|
||||||
private fun safeParseDate(dateStr: String): Date? =
|
private fun safeParseDate(dateStr: String): Date? =
|
||||||
try {
|
try {
|
||||||
CommonsDateUtil.getMediaSimpleDateFormat().parse(dateStr)
|
CommonsDateUtil.getMediaSimpleDateFormat().parse(dateStr)
|
||||||
|
|
@ -66,24 +65,32 @@ class MediaConverter
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method extracts the Commons Username from the artist HTML information
|
* This method extracts the Commons Username from the artist HTML information.
|
||||||
|
* When the HTML is in customized formatting, it may fail to parse and return null.
|
||||||
* @param metadata
|
* @param metadata
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private fun getAuthor(metadata: ExtMetadata): String? {
|
private fun getAuthor(metadata: ExtMetadata): String? {
|
||||||
return try {
|
val authorHtml = metadata.artist()
|
||||||
val authorHtml = metadata.artist()
|
val anchorStartTagTerminalString = "\">"
|
||||||
val anchorStartTagTerminalChars = "\">"
|
val anchorCloseTag = "</a>"
|
||||||
val anchorCloseTag = "</a>"
|
|
||||||
|
|
||||||
return authorHtml.substring(
|
return if (!authorHtml.contains("<") && !authorHtml.contains(">") ) {
|
||||||
authorHtml.indexOf(anchorStartTagTerminalChars) +
|
authorHtml.trim()
|
||||||
anchorStartTagTerminalChars
|
} else if (!authorHtml.contains(anchorStartTagTerminalString) || !authorHtml.endsWith(anchorCloseTag)) {
|
||||||
.length,
|
null
|
||||||
|
} else {
|
||||||
|
|
||||||
|
val authorText = authorHtml.substring(
|
||||||
|
authorHtml.indexOf(anchorStartTagTerminalString) +
|
||||||
|
anchorStartTagTerminalString.length,
|
||||||
authorHtml.indexOf(anchorCloseTag),
|
authorHtml.indexOf(anchorCloseTag),
|
||||||
)
|
)
|
||||||
} catch (ex: java.lang.Exception) {
|
if (authorText.contains("<") || authorText.contains(">")) {
|
||||||
""
|
null
|
||||||
|
} else {
|
||||||
|
authorText
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -92,6 +99,10 @@ private fun Entities.Entity.depictionIds() =
|
||||||
this[WikidataProperties.DEPICTS]?.mapNotNull { (it.mainSnak.dataValue as? DataValue.EntityId)?.value?.id }
|
this[WikidataProperties.DEPICTS]?.mapNotNull { (it.mainSnak.dataValue as? DataValue.EntityId)?.value?.id }
|
||||||
?: emptyList()
|
?: emptyList()
|
||||||
|
|
||||||
|
private fun Entities.Entity.creatorIds() =
|
||||||
|
this[WikidataProperties.CREATOR]?.mapNotNull { (it.mainSnak.dataValue as? DataValue.EntityId)?.value?.id }
|
||||||
|
?: emptyList()
|
||||||
|
|
||||||
private val ExtMetadata.prefixedLicenseUrl: String
|
private val ExtMetadata.prefixedLicenseUrl: String
|
||||||
get() =
|
get() =
|
||||||
licenseUrl().let {
|
licenseUrl().let {
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,18 @@ import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import fr.free.nrw.commons.Media
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.MediaDataExtractor
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.category.CategoryImagesCallback
|
import fr.free.nrw.commons.category.CategoryImagesCallback
|
||||||
import fr.free.nrw.commons.explore.paging.BasePagingFragment
|
import fr.free.nrw.commons.explore.paging.BasePagingFragment
|
||||||
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
|
import fr.free.nrw.commons.media.MediaDetailPagerFragment.MediaDetailProvider
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
abstract class PageableMediaFragment :
|
abstract class PageableMediaFragment :
|
||||||
BasePagingFragment<Media>(),
|
BasePagingFragment<Media>(),
|
||||||
MediaDetailProvider {
|
MediaDetailProvider {
|
||||||
override val pagedListAdapter by lazy {
|
override val pagedListAdapter by lazy {
|
||||||
PagedMediaAdapter(categoryImagesCallback::onMediaClicked)
|
PagedMediaAdapter(categoryImagesCallback::onMediaClicked, mediaDataExtractor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val errorTextId: Int = R.string.error_loading_images
|
override val errorTextId: Int = R.string.error_loading_images
|
||||||
|
|
@ -22,6 +24,9 @@ abstract class PageableMediaFragment :
|
||||||
|
|
||||||
lateinit var categoryImagesCallback: CategoryImagesCallback
|
lateinit var categoryImagesCallback: CategoryImagesCallback
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var mediaDataExtractor: MediaDataExtractor
|
||||||
|
|
||||||
override fun onAttach(context: Context) {
|
override fun onAttach(context: Context) {
|
||||||
super.onAttach(context)
|
super.onAttach(context)
|
||||||
if (parentFragment != null) {
|
if (parentFragment != null) {
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,22 @@ import android.view.ViewGroup
|
||||||
import androidx.paging.PagedListAdapter
|
import androidx.paging.PagedListAdapter
|
||||||
import androidx.recyclerview.widget.DiffUtil
|
import androidx.recyclerview.widget.DiffUtil
|
||||||
import fr.free.nrw.commons.Media
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.MediaDataExtractor
|
||||||
|
import fr.free.nrw.commons.utils.MediaAttributionUtil
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.databinding.LayoutCategoryImagesBinding
|
import fr.free.nrw.commons.databinding.LayoutCategoryImagesBinding
|
||||||
import fr.free.nrw.commons.explore.paging.BaseViewHolder
|
import fr.free.nrw.commons.explore.paging.BaseViewHolder
|
||||||
import fr.free.nrw.commons.explore.paging.inflate
|
import fr.free.nrw.commons.explore.paging.inflate
|
||||||
|
import fr.free.nrw.commons.media.IdAndLabels
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import io.reactivex.schedulers.Schedulers
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class PagedMediaAdapter(
|
class PagedMediaAdapter(
|
||||||
private val onImageClicked: (Int) -> Unit,
|
private val onImageClicked: (Int) -> Unit,
|
||||||
|
private val mediaDataExtractor: MediaDataExtractor,
|
||||||
|
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
|
||||||
) : PagedListAdapter<Media, SearchImagesViewHolder>(
|
) : PagedListAdapter<Media, SearchImagesViewHolder>(
|
||||||
object : DiffUtil.ItemCallback<Media>() {
|
object : DiffUtil.ItemCallback<Media>() {
|
||||||
override fun areItemsTheSame(
|
override fun areItemsTheSame(
|
||||||
|
|
@ -25,6 +34,7 @@ class PagedMediaAdapter(
|
||||||
) = oldItem.pageId == newItem.pageId
|
) = oldItem.pageId == newItem.pageId
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
override fun onCreateViewHolder(
|
||||||
parent: ViewGroup,
|
parent: ViewGroup,
|
||||||
viewType: Int,
|
viewType: Int,
|
||||||
|
|
@ -37,7 +47,24 @@ class PagedMediaAdapter(
|
||||||
holder: SearchImagesViewHolder,
|
holder: SearchImagesViewHolder,
|
||||||
position: Int,
|
position: Int,
|
||||||
) {
|
) {
|
||||||
holder.bind(getItem(position)!! to position)
|
val media = getItem(position) ?: return
|
||||||
|
holder.bind(media to position)
|
||||||
|
|
||||||
|
if (!media.getAttributedAuthor().isNullOrEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
compositeDisposable.addAll(
|
||||||
|
mediaDataExtractor.fetchCreatorIdsAndLabels(media)
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe(
|
||||||
|
{ idAndLabels ->
|
||||||
|
media.creatorName = MediaAttributionUtil.getCreatorName(idAndLabels);
|
||||||
|
holder.setAuthorText(media)
|
||||||
|
},
|
||||||
|
{ t: Throwable? -> Timber.e(t) })
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -52,7 +79,10 @@ class SearchImagesViewHolder(
|
||||||
binding.categoryImageView.setOnClickListener { onImageClicked(item.second) }
|
binding.categoryImageView.setOnClickListener { onImageClicked(item.second) }
|
||||||
binding.categoryImageTitle.text = media.mostRelevantCaption
|
binding.categoryImageTitle.text = media.mostRelevantCaption
|
||||||
binding.categoryImageView.setImageURI(media.thumbUrl)
|
binding.categoryImageView.setImageURI(media.thumbUrl)
|
||||||
binding.categoryImageAuthor.text =
|
setAuthorText(media)
|
||||||
containerView.context.getString(R.string.image_uploaded_by, media.getAuthorOrUser())
|
}
|
||||||
|
|
||||||
|
fun setAuthorText(media: Media) {
|
||||||
|
binding.categoryImageAuthor.text = MediaAttributionUtil.getTagLine(media, containerView.context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
package fr.free.nrw.commons.media
|
|
||||||
|
|
||||||
data class IdAndCaptions(
|
|
||||||
val id: String,
|
|
||||||
val captions: Map<String, String>,
|
|
||||||
)
|
|
||||||
18
app/src/main/java/fr/free/nrw/commons/media/IdAndLabels.kt
Normal file
18
app/src/main/java/fr/free/nrw/commons/media/IdAndLabels.kt
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
package fr.free.nrw.commons.media
|
||||||
|
|
||||||
|
data class IdAndLabels(
|
||||||
|
val id: String,
|
||||||
|
val labels: Map<String, String>,
|
||||||
|
) {
|
||||||
|
// if a label is available in user's locale, return it
|
||||||
|
// if not then check for english, else show any available.
|
||||||
|
fun getLocalizedLabel(locale: String): String? {
|
||||||
|
if (labels[locale] != null) {
|
||||||
|
return labels[locale]
|
||||||
|
}
|
||||||
|
if (labels["en"] != null) {
|
||||||
|
return labels["en"]
|
||||||
|
}
|
||||||
|
return labels.values.firstOrNull() ?: id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -16,7 +16,6 @@ import android.view.KeyEvent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewTreeObserver
|
|
||||||
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
|
|
@ -622,10 +621,9 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(
|
.subscribe(
|
||||||
{ idAndCaptions: List<IdAndCaptions> -> onDepictionsLoaded(idAndCaptions) },
|
{ IdAndLabels: List<IdAndLabels> -> onDepictionsLoaded(IdAndLabels) },
|
||||||
{ t: Throwable? -> Timber.e(t) })
|
{ t: Throwable? -> Timber.e(t) })
|
||||||
)
|
)
|
||||||
// compositeDisposable.add(disposable);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onDiscussionLoaded(discussion: String) {
|
private fun onDiscussionLoaded(discussion: String) {
|
||||||
|
|
@ -655,10 +653,10 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onDepictionsLoaded(idAndCaptions: List<IdAndCaptions>) {
|
private fun onDepictionsLoaded(IdAndLabels: List<IdAndLabels>) {
|
||||||
binding.depictsLayout.visibility = View.VISIBLE
|
binding.depictsLayout.visibility = View.VISIBLE
|
||||||
binding.depictionsEditButton.visibility = View.VISIBLE
|
binding.depictionsEditButton.visibility = View.VISIBLE
|
||||||
buildDepictionList(idAndCaptions)
|
buildDepictionList(IdAndLabels)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -863,26 +861,26 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populates media details fragment with depiction list
|
* Populates media details fragment with depiction list
|
||||||
* @param idAndCaptions
|
* @param IdAndLabels
|
||||||
*/
|
*/
|
||||||
private fun buildDepictionList(idAndCaptions: List<IdAndCaptions>) {
|
private fun buildDepictionList(IdAndLabels: List<IdAndLabels>) {
|
||||||
binding.mediaDetailDepictionContainer.removeAllViews()
|
binding.mediaDetailDepictionContainer.removeAllViews()
|
||||||
|
|
||||||
// Create a mutable list from the original list
|
// Create a mutable list from the original list
|
||||||
val mutableIdAndCaptions = idAndCaptions.toMutableList()
|
val mutableIdAndLabels = IdAndLabels.toMutableList()
|
||||||
|
|
||||||
if (mutableIdAndCaptions.isEmpty()) {
|
if (mutableIdAndLabels.isEmpty()) {
|
||||||
// Create a placeholder IdAndCaptions object and add it to the list
|
// Create a placeholder IdAndLabels object and add it to the list
|
||||||
mutableIdAndCaptions.add(
|
mutableIdAndLabels.add(
|
||||||
IdAndCaptions(
|
IdAndLabels(
|
||||||
id = media?.pageId ?: "", // Use an empty string if media?.pageId is null
|
id = media?.pageId ?: "", // Use an empty string if media?.pageId is null
|
||||||
captions = mapOf(Locale.getDefault().language to getString(R.string.detail_panel_cats_none)) // Create a Map with the language as the key and the message as the value
|
labels = mapOf(Locale.getDefault().language to getString(R.string.detail_panel_cats_none)) // Create a Map with the language as the key and the message as the value
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val locale: String = Locale.getDefault().language
|
val locale: String = Locale.getDefault().language
|
||||||
for (idAndCaption: IdAndCaptions in mutableIdAndCaptions) {
|
for (idAndCaption: IdAndLabels in mutableIdAndLabels) {
|
||||||
binding.mediaDetailDepictionContainer.addView(
|
binding.mediaDetailDepictionContainer.addView(
|
||||||
buildDepictLabel(
|
buildDepictLabel(
|
||||||
getDepictionCaption(idAndCaption, locale),
|
getDepictionCaption(idAndCaption, locale),
|
||||||
|
|
@ -894,16 +892,16 @@ class MediaDetailFragment : CommonsDaggerSupportFragment(), CategoryEditHelper.C
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun getDepictionCaption(idAndCaption: IdAndCaptions, locale: String): String? {
|
private fun getDepictionCaption(idAndCaption: IdAndLabels, locale: String): String? {
|
||||||
// Check if the Depiction Caption is available in user's locale
|
// Check if the Depiction Caption is available in user's locale
|
||||||
// if not then check for english, else show any available.
|
// if not then check for english, else show any available.
|
||||||
if (idAndCaption.captions[locale] != null) {
|
if (idAndCaption.labels[locale] != null) {
|
||||||
return idAndCaption.captions[locale]
|
return idAndCaption.labels[locale]
|
||||||
}
|
}
|
||||||
if (idAndCaption.captions["en"] != null) {
|
if (idAndCaption.labels["en"] != null) {
|
||||||
return idAndCaption.captions["en"]
|
return idAndCaption.labels["en"]
|
||||||
}
|
}
|
||||||
return idAndCaption.captions.values.iterator().next()
|
return idAndCaption.labels.values.iterator().next()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onMediaDetailLicenceClicked() {
|
private fun onMediaDetailLicenceClicked() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
package fr.free.nrw.commons.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.icu.text.ListFormatter
|
||||||
|
import android.os.Build
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.R
|
||||||
|
import fr.free.nrw.commons.media.IdAndLabels
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
object MediaAttributionUtil {
|
||||||
|
fun getTagLine(media: Media, context: Context): String {
|
||||||
|
val uploader = media.user
|
||||||
|
val author = media.getAttributedAuthor()
|
||||||
|
return if (author.isNullOrEmpty()) {
|
||||||
|
context.getString(R.string.image_uploaded_by, uploader)
|
||||||
|
} else if (author == uploader) {
|
||||||
|
context.getString(R.string.image_tag_line_created_and_uploaded_by, author)
|
||||||
|
} else {
|
||||||
|
context.getString(
|
||||||
|
R.string.image_tag_line_created_by_and_uploaded_by,
|
||||||
|
author,
|
||||||
|
uploader
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCreatorName(idAndLabels: List<IdAndLabels>): String? {
|
||||||
|
val locale = Locale.getDefault()
|
||||||
|
val names = idAndLabels.map{ x -> x.getLocalizedLabel(locale.language)}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
val formatter = ListFormatter.getInstance(locale)
|
||||||
|
return formatter.format(names)
|
||||||
|
} else {
|
||||||
|
return names.joinToString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
package fr.free.nrw.commons.utils
|
package fr.free.nrw.commons.utils
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.text.Html
|
|
||||||
import android.text.Spanned
|
import android.text.Spanned
|
||||||
import android.text.SpannedString
|
import android.text.SpannedString
|
||||||
|
import androidx.core.text.HtmlCompat
|
||||||
|
|
||||||
object StringUtil {
|
object StringUtil {
|
||||||
|
|
||||||
|
|
@ -26,12 +25,6 @@ object StringUtil {
|
||||||
.replace("‏", "\u200F")
|
.replace("‏", "\u200F")
|
||||||
.replace("&", "&")
|
.replace("&", "&")
|
||||||
|
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
return HtmlCompat.fromHtml(processedSource, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||||
Html.fromHtml(processedSource, Html.FROM_HTML_MODE_LEGACY)
|
|
||||||
} else {
|
|
||||||
//noinspection deprecation
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
Html.fromHtml(processedSource)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ enum class WikidataProperties(
|
||||||
) {
|
) {
|
||||||
IMAGE("P18"),
|
IMAGE("P18"),
|
||||||
DEPICTS(BuildConfig.DEPICTS_PROPERTY),
|
DEPICTS(BuildConfig.DEPICTS_PROPERTY),
|
||||||
|
CREATOR(BuildConfig.CREATOR_PROPERTY),
|
||||||
COMMONS_CATEGORY("P373"),
|
COMMONS_CATEGORY("P373"),
|
||||||
INSTANCE_OF("P31"),
|
INSTANCE_OF("P31"),
|
||||||
MEDIA_LEGENDS("P2096"),
|
MEDIA_LEGENDS("P2096"),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package fr.free.nrw.commons.wikidata.model.gallery
|
package fr.free.nrw.commons.wikidata.model.gallery
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import org.apache.commons.lang3.StringUtils
|
|
||||||
|
|
||||||
class ExtMetadata {
|
class ExtMetadata {
|
||||||
@SerializedName("DateTime") private val dateTime: Values? = null
|
@SerializedName("DateTime") private val dateTime: Values? = null
|
||||||
|
|
|
||||||
|
|
@ -873,4 +873,6 @@ Upload your first media by tapping on the add button.</string>
|
||||||
|
|
||||||
<string name="show_in_explore">Show in Explore</string>
|
<string name="show_in_explore">Show in Explore</string>
|
||||||
<string name="show_in_nearby">Show in Nearby</string>
|
<string name="show_in_nearby">Show in Nearby</string>
|
||||||
|
<string name="image_tag_line_created_and_uploaded_by">Created and uploaded by: %1$s</string>
|
||||||
|
<string name="image_tag_line_created_by_and_uploaded_by">Created by %1$s and uploaded by %2$s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ fun media(
|
||||||
licenseUrl: String? = "licenseUrl",
|
licenseUrl: String? = "licenseUrl",
|
||||||
author: String? = "creator",
|
author: String? = "creator",
|
||||||
user: String? = "user",
|
user: String? = "user",
|
||||||
|
creatorName: String? = null,
|
||||||
pageId: String = "pageId",
|
pageId: String = "pageId",
|
||||||
categories: List<String>? = listOf("categories"),
|
categories: List<String>? = listOf("categories"),
|
||||||
coordinates: LatLng? = LatLng(0.0, 0.0, 0.0f),
|
coordinates: LatLng? = LatLng(0.0, 0.0, 0.0f),
|
||||||
|
|
@ -67,6 +68,7 @@ fun media(
|
||||||
licenseUrl,
|
licenseUrl,
|
||||||
author,
|
author,
|
||||||
user,
|
user,
|
||||||
|
creatorName,
|
||||||
categories,
|
categories,
|
||||||
coordinates,
|
coordinates,
|
||||||
captions,
|
captions,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import androidx.test.core.app.ApplicationProvider
|
||||||
import com.facebook.drawee.backends.pipeline.Fresco
|
import com.facebook.drawee.backends.pipeline.Fresco
|
||||||
import com.facebook.soloader.SoLoader
|
import com.facebook.soloader.SoLoader
|
||||||
import fr.free.nrw.commons.Media
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.MediaDataExtractor
|
||||||
import fr.free.nrw.commons.R
|
import fr.free.nrw.commons.R
|
||||||
import fr.free.nrw.commons.TestCommonsApplication
|
import fr.free.nrw.commons.TestCommonsApplication
|
||||||
import fr.free.nrw.commons.TestUtility.setFinalStatic
|
import fr.free.nrw.commons.TestUtility.setFinalStatic
|
||||||
|
|
@ -46,6 +47,9 @@ class ContributionViewHolderUnitTests {
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var mediaClient: MediaClient
|
private lateinit var mediaClient: MediaClient
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var mediaDataExtractor: MediaDataExtractor
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var uri: Uri
|
private lateinit var uri: Uri
|
||||||
|
|
||||||
|
|
@ -66,8 +70,9 @@ class ContributionViewHolderUnitTests {
|
||||||
SoLoader.setInTestMode()
|
SoLoader.setInTestMode()
|
||||||
Fresco.initialize(ApplicationProvider.getApplicationContext())
|
Fresco.initialize(ApplicationProvider.getApplicationContext())
|
||||||
activity = Robolectric.buildActivity(ProfileActivity::class.java).create().get()
|
activity = Robolectric.buildActivity(ProfileActivity::class.java).create().get()
|
||||||
|
compositeDisposable = CompositeDisposable()
|
||||||
parent = LayoutInflater.from(activity).inflate(R.layout.layout_contribution, null)
|
parent = LayoutInflater.from(activity).inflate(R.layout.layout_contribution, null)
|
||||||
contributionViewHolder = ContributionViewHolder(parent, callback, mediaClient)
|
contributionViewHolder = ContributionViewHolder(parent, callback, compositeDisposable, mediaClient, mediaDataExtractor)
|
||||||
|
|
||||||
bindind = LayoutContributionBinding.bind(parent)
|
bindind = LayoutContributionBinding.bind(parent)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.Mock
|
import org.mockito.Mock
|
||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
import org.mockito.MockitoAnnotations
|
import org.mockito.MockitoAnnotations
|
||||||
import java.lang.IllegalArgumentException
|
import java.lang.IllegalArgumentException
|
||||||
|
|
||||||
|
|
@ -42,23 +43,61 @@ class MediaConverterTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testConvertIfThumbUrlBlank() {
|
fun testConvertIfThumbUrlBlank() {
|
||||||
Mockito.`when`(imageInfo.getMetadata()).thenReturn(metadata)
|
`when`(imageInfo.getMetadata()).thenReturn(metadata)
|
||||||
Mockito.`when`(imageInfo.getThumbUrl()).thenReturn("")
|
`when`(imageInfo.getThumbUrl()).thenReturn("")
|
||||||
Mockito.`when`(imageInfo.getOriginalUrl()).thenReturn("originalUrl")
|
`when`(imageInfo.getOriginalUrl()).thenReturn("originalUrl")
|
||||||
Mockito.`when`(imageInfo.getMetadata()?.licenseUrl()).thenReturn("licenseUrl")
|
`when`(metadata.licenseUrl()).thenReturn("licenseUrl")
|
||||||
Mockito.`when`(imageInfo.getMetadata()?.dateTime()).thenReturn("yyyy-MM-dd HH:mm:ss")
|
`when`(metadata.dateTime()).thenReturn("yyyy-MM-dd HH:mm:ss")
|
||||||
|
`when`(metadata.artist()).thenReturn("Foo Bar")
|
||||||
media = mediaConverter.convert(page, entity, imageInfo)
|
media = mediaConverter.convert(page, entity, imageInfo)
|
||||||
assertEquals(media.thumbUrl, media.imageUrl, "originalUrl")
|
assertEquals(media.thumbUrl, media.imageUrl, "originalUrl")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testConvertIfThumbUrlNotBlank() {
|
fun testConvertIfThumbUrlNotBlank() {
|
||||||
Mockito.`when`(imageInfo.getMetadata()).thenReturn(metadata)
|
`when`(imageInfo.getMetadata()).thenReturn(metadata)
|
||||||
Mockito.`when`(imageInfo.getThumbUrl()).thenReturn("thumbUrl")
|
`when`(imageInfo.getThumbUrl()).thenReturn("thumbUrl")
|
||||||
Mockito.`when`(imageInfo.getOriginalUrl()).thenReturn("originalUrl")
|
`when`(imageInfo.getOriginalUrl()).thenReturn("originalUrl")
|
||||||
Mockito.`when`(imageInfo.getMetadata()?.licenseUrl()).thenReturn("licenseUrl")
|
`when`(metadata.licenseUrl()).thenReturn("licenseUrl")
|
||||||
Mockito.`when`(imageInfo.getMetadata()?.dateTime()).thenReturn("yyyy-MM-dd HH:mm:ss")
|
`when`(metadata.dateTime()).thenReturn("yyyy-MM-dd HH:mm:ss")
|
||||||
|
`when`(metadata.artist()).thenReturn("Foo Bar")
|
||||||
media = mediaConverter.convert(page, entity, imageInfo)
|
media = mediaConverter.convert(page, entity, imageInfo)
|
||||||
assertEquals(media.thumbUrl, "thumbUrl")
|
assertEquals(media.thumbUrl, "thumbUrl")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test converting artist value (author) with html links`() {
|
||||||
|
`when`(imageInfo.getMetadata()).thenReturn(metadata)
|
||||||
|
`when`(imageInfo.getThumbUrl()).thenReturn("thumbUrl")
|
||||||
|
`when`(imageInfo.getOriginalUrl()).thenReturn("originalUrl")
|
||||||
|
`when`(metadata.licenseUrl()).thenReturn("licenseUrl")
|
||||||
|
`when`(metadata.dateTime()).thenReturn("yyyy-MM-dd HH:mm:ss")
|
||||||
|
`when`(metadata.artist()).thenReturn("<a href=\"//commons.wikimedia.org/wiki/User:Foo_Bar\" title=\"Foo Bar\">Foo Bar</a>")
|
||||||
|
// Artist values like above is very common, found in file pages created via UploadWizard
|
||||||
|
media = mediaConverter.convert(page, entity, imageInfo)
|
||||||
|
assertEquals("Foo Bar", media.author)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test convert artist value (author) in plain text`() {
|
||||||
|
`when`(imageInfo.getMetadata()).thenReturn(metadata)
|
||||||
|
`when`(imageInfo.getThumbUrl()).thenReturn("thumbUrl")
|
||||||
|
`when`(imageInfo.getOriginalUrl()).thenReturn("originalUrl")
|
||||||
|
`when`(metadata.licenseUrl()).thenReturn("licenseUrl")
|
||||||
|
`when`(metadata.dateTime()).thenReturn("yyyy-MM-dd HH:mm:ss")
|
||||||
|
`when`(metadata.artist()).thenReturn("Foo Bar")
|
||||||
|
media = mediaConverter.convert(page, entity, imageInfo)
|
||||||
|
assertEquals("Foo Bar", media.author)
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun `test convert artist value (author) containing red link`() {
|
||||||
|
`when`(imageInfo.getMetadata()).thenReturn(metadata)
|
||||||
|
`when`(imageInfo.getThumbUrl()).thenReturn("thumbUrl")
|
||||||
|
`when`(imageInfo.getOriginalUrl()).thenReturn("originalUrl")
|
||||||
|
`when`(metadata.licenseUrl()).thenReturn("licenseUrl")
|
||||||
|
`when`(metadata.dateTime()).thenReturn("yyyy-MM-dd HH:mm:ss")
|
||||||
|
`when`(metadata.artist()).thenReturn("<a href=\"/w/index.php?title=User:Foo&action=edit&redlink=1\" class=\"new\" title=\"User:Foo (page does not exist)\">Foo</a>")
|
||||||
|
media = mediaConverter.convert(page, entity, imageInfo)
|
||||||
|
assertEquals("Foo", media.author)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
package fr.free.nrw.commons.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.test.core.app.ApplicationProvider
|
||||||
|
import com.nhaarman.mockitokotlin2.whenever
|
||||||
|
import fr.free.nrw.commons.Media
|
||||||
|
import fr.free.nrw.commons.TestCommonsApplication
|
||||||
|
import fr.free.nrw.commons.media.IdAndLabels
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Before
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.mock
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(sdk = [21], application = TestCommonsApplication::class, qualifiers="en-rUS")
|
||||||
|
class MediaAttributionUtilTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private lateinit var appContext: Context
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
appContext = ApplicationProvider.getApplicationContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun getTagLineWithUploaderOnly() {
|
||||||
|
val media = mock(Media::class.java)
|
||||||
|
whenever(media.user).thenReturn("TestUploader")
|
||||||
|
whenever(media.author).thenReturn(null)
|
||||||
|
assertEquals("Uploaded by: TestUploader",
|
||||||
|
MediaAttributionUtil.getTagLine(media, appContext))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `get tag line from same author and uploader`() {
|
||||||
|
val media = mock(Media::class.java)
|
||||||
|
whenever(media.user).thenReturn("TestUser")
|
||||||
|
whenever(media.getAttributedAuthor()).thenReturn("TestUser")
|
||||||
|
assertEquals("Created and uploaded by: TestUser",
|
||||||
|
MediaAttributionUtil.getTagLine(media, appContext))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `get creator name from EN label`() {
|
||||||
|
assertEquals("FooBar",
|
||||||
|
MediaAttributionUtil.getCreatorName(listOf(IdAndLabels("Q1", mapOf("en" to "FooBar")))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `get creator name from ES label`() {
|
||||||
|
assertEquals("FooBar",
|
||||||
|
MediaAttributionUtil.getCreatorName(listOf(IdAndLabels("Q2", mapOf("es" to "FooBar")))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `get creator name from EN label and ignore ES label`() {
|
||||||
|
assertEquals("Bar",
|
||||||
|
MediaAttributionUtil.getCreatorName(listOf(
|
||||||
|
IdAndLabels("Q3", mapOf("en" to "Bar", "es" to "Foo")))))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `get creator name from two creators`() {
|
||||||
|
val name = MediaAttributionUtil.getCreatorName(listOf(
|
||||||
|
IdAndLabels("Q1", mapOf("en" to "Foo")),
|
||||||
|
IdAndLabels("Q1", mapOf("en" to "Bar"))
|
||||||
|
))
|
||||||
|
assertNotNull(name)
|
||||||
|
assertTrue(name!!.contains("Foo"))
|
||||||
|
assertTrue(name.contains("Bar"))
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue