#3847 Convert Media and Contribution to Kotlin Data Classes - convert to data classes - compose a contribution with a media

This commit is contained in:
Sean Mac Gillicuddy 2020-06-26 13:07:01 +01:00
parent 799369d6b8
commit 1ac0dfc4aa
34 changed files with 335 additions and 742 deletions

View file

@ -16,6 +16,10 @@
<option name="REPORT_PARAMETERS" value="true" />
</inspection_tool>
<inspection_tool class="LongLine" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="ignoreObjectMethods" value="true" />
<option name="ignoreAnonymousClassMethods" value="false" />
</inspection_tool>
<inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
<inspection_tool class="OverlyStrongTypeCast" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoreInMatchingInstanceof" value="false" />

View file

@ -1,426 +0,0 @@
package fr.free.nrw.commons;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import fr.free.nrw.commons.location.LatLng;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import org.wikipedia.page.PageTitle;
@Entity
public class Media implements Parcelable {
private String thumbUrl;
private String imageUrl;
private String filename;
private String fallbackDescription; // monolingual description on input...
@Nullable private Date dateUploaded;
private String license;
private String licenseUrl;
private String creator;
/**
* Wikibase Identifier associated with media files
*/
@PrimaryKey
@NonNull
private String pageId;
private List<String> categories; // as loaded at runtime?
@Nullable private LatLng coordinates;
@NotNull
private Map<String, String> captions = Collections.emptyMap();
@NotNull
private Map<String, String> descriptions = Collections.emptyMap();
@NotNull
private List<String> depictionIds = Collections.emptyList();
/**
* Provides local constructor
*/
public Media() {
pageId = UUID.randomUUID().toString();
}
/**
* Constructor with all parameters
*/
public Media(final String thumbUrl,
final String imageUrl,
final String filename,
final String fallbackDescription,
@Nullable final Date dateUploaded,
final String license,
final String licenseUrl,
final String creator,
@NonNull final String pageId,
final List<String> categories,
@Nullable final LatLng coordinates,
@NotNull final Map<String, String> captions,
@NotNull final Map<String, String> descriptions,
@NotNull final List<String> depictionIds) {
this.thumbUrl = thumbUrl;
this.imageUrl = imageUrl;
this.filename = filename;
this.fallbackDescription = fallbackDescription;
this.dateUploaded = dateUploaded;
this.license = license;
this.licenseUrl = licenseUrl;
this.creator = creator;
this.pageId = pageId;
this.categories = categories;
this.coordinates = coordinates;
this.captions = captions;
this.descriptions = descriptions;
this.depictionIds = depictionIds;
}
public Media(Media media) {
this(media.getThumbUrl(), media.getImageUrl(), media.getFilename(),
media.getFallbackDescription(), media.getDateUploaded(), media.getLicense(),
media.getLicenseUrl(), media.getCreator(), media.getPageId(), media.getCategories(),
media.getCoordinates(), media.getCaptions(), media.getDescriptions(),
media.getDepictionIds());
}
public Media(final String filename,
Map<String, String> captions, final String fallbackDescription,
final String creator, final List<String> categories) {
this();
thumbUrl = null;
this.imageUrl = null;
this.filename = filename;
this.fallbackDescription = fallbackDescription;
this.dateUploaded = new Date();
this.creator = creator;
this.categories = categories;
this.captions=captions;
}
protected Media(final Parcel in) {
this(in.readString(), in.readString(), in.readString(),
in.readString(), readDateUploaded(in), in.readString(),
in.readString(), in.readString(), in.readString(), readList(in),
in.readParcelable(LatLng.class.getClassLoader()),
((Map<String, String>) in.readSerializable()),
((Map<String, String>) in.readSerializable()),
readList(in));
}
private static List<String> readList(Parcel in) {
final List<String> list = new ArrayList<>();
in.readStringList(list);
return list;
}
private static Date readDateUploaded(Parcel in) {
final long tmpDateUploaded = in.readLong();
return tmpDateUploaded == -1 ? null : new Date(tmpDateUploaded);
}
public static final Creator<Media> CREATOR = new Creator<Media>() {
@Override
public Media createFromParcel(final Parcel source) {
return new Media(source);
}
@Override
public Media[] newArray(final int size) {
return new Media[size];
}
};
@Nullable
public String getThumbUrl() {
return thumbUrl;
}
/**
* Gets media display title
* @return Media title
*/
@NonNull public String getDisplayTitle() {
return filename != null ? getPageTitle().getDisplayTextWithoutNamespace().replaceFirst("[.][^.]+$", "") : "";
}
/**
* Gets file page title
* @return New media page title
*/
@NonNull public PageTitle getPageTitle() {
return Utils.getPageTitle(getFilename());
}
/**
* Gets image URL
* can be null.
* @return Image URL
*/
@Nullable
public String getImageUrl() {
return imageUrl;
}
/**
* Gets the name of the file.
* @return file name as a string
*/
public String getFilename() {
return filename;
}
/**
* @return pageId for the current media object*/
@NonNull
public String getPageId() {
return pageId;
}
/**
*sets pageId for the current media object
*/
public void setPageId(final String pageId) {
this.pageId = pageId;
}
/**
* Gets the file description.
* @return file description as a string
*/
public String getFallbackDescription() {
return fallbackDescription;
}
/**
* Sets the name of the file.
* @param filename the new name of the file
*/
public void setFilename(final String filename) {
this.filename = filename;
}
/**
* Sets the file description.
* @param fallbackDescription the new description of the file
*/
public void setFallbackDescription(final String fallbackDescription) {
this.fallbackDescription = fallbackDescription;
}
/**
* Gets the upload date of the file.
* Can be null.
* @return upload date as a Date
*/
public @Nullable
Date getDateUploaded() {
return dateUploaded;
}
/**
* Gets the name of the creator of the file.
* @return creator name as a String
*/
public String getCreator() {
return creator;
}
/**
* Gets the license name of the file.
* @return license as a String
*/
public String getLicense() {
return license;
}
public String getLicenseUrl() {
return licenseUrl;
}
/**
* Sets the creator name of the file.
* @param creator creator name as a string
*/
public void setCreator(final String creator) {
this.creator = creator;
}
/**
* Gets the coordinates of where the file was created.
* @return file coordinates as a LatLng
*/
public @Nullable
LatLng getCoordinates() {
return coordinates;
}
public void setThumbUrl(final String thumbUrl) {
this.thumbUrl = thumbUrl;
}
/**
* Gets the categories the file falls under.
* @return file categories as an ArrayList of Strings
*/
public List<String> getCategories() {
return categories;
}
/**
* Sets the coordinates of where the file was created.
* @param coordinates file coordinates as a LatLng
*/
public void setCoordinates(@Nullable final LatLng coordinates) {
this.coordinates = coordinates;
}
/**
* Returns wikicode to use the media file on a MediaWiki site
* @return
*/
public String getWikiCode() {
return String.format("[[%s|thumb|%s]]", filename, getMostRelevantCaption());
}
public String getMostRelevantCaption() {
final String languageAppropriateCaption = captions.get(Locale.getDefault().getLanguage());
if (languageAppropriateCaption != null) {
return languageAppropriateCaption;
}
for (String firstCaption : captions.values()) {
return firstCaption;
}
return getDisplayTitle();
}
/**
* Sets the categories the file falls under.
* </p>
* Does not append: i.e. will clear the current categories
* and then add the specified ones.
* @param categories file categories as a list of Strings
*/
public void setCategories(final List<String> categories) {
this.categories = categories;
}
/**
* Sets the license name of the file.
*
* @param license license name as a String
*/
public void setLicense(final String license) {
this.license = license;
}
public void setImageUrl(final String imageUrl) {
this.imageUrl = imageUrl;
}
public void setDateUploaded(@Nullable final Date dateUploaded) {
this.dateUploaded = dateUploaded;
}
public void setLicenseUrl(final String licenseUrl) {
this.licenseUrl = licenseUrl;
}
@Override
public int describeContents() {
return 0;
}
/**
* Creates a way to transfer information between two or more
* activities.
* @param dest Instance of Parcel
* @param flags Parcel flag
*/
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(thumbUrl);
dest.writeString(imageUrl);
dest.writeString(filename);
dest.writeString(fallbackDescription);
dest.writeLong(dateUploaded != null ? dateUploaded.getTime() : -1);
dest.writeString(license);
dest.writeString(licenseUrl);
dest.writeString(creator);
dest.writeString(pageId);
dest.writeStringList(categories);
dest.writeParcelable(coordinates, flags);
dest.writeSerializable((Serializable) captions);
dest.writeSerializable((Serializable) descriptions);
dest.writeList(depictionIds);
}
public Map<String, String> getCaptions() {
return captions;
}
public void setCaptions(Map<String, String> captions) {
this.captions = captions;
}
public Map<String, String> getDescriptions() {
return descriptions;
}
public void setDescriptions(Map<String, String> descriptions) {
this.descriptions = descriptions;
}
public List<String> getDepictionIds() {
return depictionIds;
}
public void setDepictionIds(final List<String> depictionIds) {
this.depictionIds = depictionIds;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final Media media = (Media) o;
return Objects.equals(thumbUrl, media.thumbUrl) &&
Objects.equals(imageUrl, media.imageUrl) &&
Objects.equals(filename, media.filename) &&
Objects.equals(fallbackDescription, media.fallbackDescription) &&
Objects.equals(dateUploaded, media.dateUploaded) &&
Objects.equals(license, media.license) &&
Objects.equals(licenseUrl, media.licenseUrl) &&
Objects.equals(creator, media.creator) &&
pageId.equals(media.pageId) &&
Objects.equals(categories, media.categories) &&
Objects.equals(coordinates, media.coordinates) &&
captions.equals(media.captions) &&
descriptions.equals(media.descriptions) &&
depictionIds.equals(media.depictionIds);
}
@Override
public int hashCode() {
return Objects
.hash(thumbUrl, imageUrl, filename, fallbackDescription, dateUploaded, license,
licenseUrl,
creator, pageId, categories, coordinates, captions, descriptions, depictionIds);
}
}

View file

@ -0,0 +1,124 @@
package fr.free.nrw.commons
import android.os.Parcelable
import fr.free.nrw.commons.location.LatLng
import kotlinx.android.parcel.Parcelize
import org.wikipedia.page.PageTitle
import java.util.*
@Parcelize
class Media constructor(
/**
* @return pageId for the current media object
* Wikibase Identifier associated with media files
*/
val pageId: String = UUID.randomUUID().toString(),
val thumbUrl: String? = null,
/**
* Gets image URL
* @return Image URL
*/
val imageUrl: String? = null,
/**
* Gets the name of the file.
* @return file name as a string
*/
val filename: String? = null,
/**
* Gets the file description.
* @return file description as a string
*/
// monolingual description on input...
/**
* Sets the file description.
* @param fallbackDescription the new description of the file
*/
var fallbackDescription: String? = null,
/**
* Gets the upload date of the file.
* Can be null.
* @return upload date as a Date
*/
val dateUploaded: Date? = null,
/**
* Gets the license name of the file.
* @return license as a String
*/
/**
* Sets the license name of the file.
*
* @param license license name as a String
*/
var license: String? = null,
val licenseUrl: String? = null,
/**
* Gets the name of the creator of the file.
* @return creator name as a String
*/
/**
* Sets the creator name of the file.
* @param creator creator name as a string
*/
var creator: String? = null,
/**
* Gets the categories the file falls under.
* @return file categories as an ArrayList of Strings
*/
val categories: List<String>? = null,
/**
* Gets the coordinates of where the file was created.
* @return file coordinates as a LatLng
*/
val coordinates: LatLng? = null,
val captions: Map<String, String> = emptyMap(),
val descriptions: Map<String, String> = emptyMap(),
val depictionIds: List<String> = emptyList()
) : Parcelable {
constructor(
captions: Map<String, String>,
categories: List<String>?,
filename: String?,
fallbackDescription: String?,
creator: String?
) : this(
filename = filename,
fallbackDescription = fallbackDescription,
dateUploaded = Date(),
creator = creator,
categories = categories,
captions = captions
)
/**
* Gets media display title
* @return Media title
*/
val displayTitle: String
get() =
if (filename != null)
pageTitle.displayTextWithoutNamespace.replaceFirst("[.][^.]+$".toRegex(), "")
else
""
/**
* Gets file page title
* @return New media page title
*/
val pageTitle: PageTitle get() = Utils.getPageTitle(filename!!)
/**
* Returns wikicode to use the media file on a MediaWiki site
* @return
*/
val wikiCode: String
get() = String.format("[[%s|thumb|%s]]", filename, mostRelevantCaption)
val mostRelevantCaption: String
get() = captions[Locale.getDefault().language]
?: captions.values.firstOrNull()
?: displayTitle
}

View file

@ -32,7 +32,7 @@ class MediaDataExtractor @Inject constructor(private val mediaClient: MediaClien
mediaClient.checkPageExistsUsingTitle("Commons:Deletion_requests/" + media.filename)
fun fetchDiscussion(media: Media) =
mediaClient.getPageHtml(media.filename.replace("File", "File talk"))
mediaClient.getPageHtml(media.filename!!.replace("File", "File talk"))
.map { HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_LEGACY).toString() }
.onErrorReturn {
Timber.d("Error occurred while fetching discussion")

View file

@ -144,4 +144,9 @@ public class BookmarksActivity extends NavigationBaseActivity
public void onMediaClicked(int position) {
//TODO use with pagination
}
@Override
public Integer getContributionStateAt(int position) {
return null;
}
}

View file

@ -156,6 +156,11 @@ public class CategoryDetailsActivity extends NavigationBaseActivity
return categoriesMediaFragment.getTotalMediaCount();
}
@Override
public Integer getContributionStateAt(int position) {
return null;
}
/**
* This method inflates the menu in the toolbar
*/

View file

@ -160,6 +160,11 @@ public class CategoryImagesActivity
return categoriesMediaFragment.getTotalMediaCount();
}
@Override
public Integer getContributionStateAt(int position) {
return null;
}
/**
* This method inflates the menu in the toolbar
*/

View file

@ -1,220 +0,0 @@
package fr.free.nrw.commons.contributions;
import android.net.Uri;
import android.os.Parcel;
import androidx.annotation.Nullable;
import androidx.room.Entity;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.upload.UploadItem;
import fr.free.nrw.commons.upload.UploadMediaDetail;
import fr.free.nrw.commons.upload.WikidataPlace;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
@Entity(tableName = "contribution")
public class Contribution extends Media {
// No need to be bitwise - they're mutually exclusive
public static final int STATE_COMPLETED = -1;
public static final int STATE_FAILED = 1;
public static final int STATE_QUEUED = 2;
public static final int STATE_IN_PROGRESS = 3;
private int state;
private long transferred;
private String decimalCoords;
private String dateCreatedSource;
private WikidataPlace wikidataPlace;
/**
* Each depiction loaded in depictions activity is associated with a wikidata entity id, this Id
* is in turn used to upload depictions to wikibase
*/
private List<DepictedItem> depictedItems = new ArrayList<>();
private String mimeType;
@Nullable
private Uri localUri;
private long dataLength;
private Date dateCreated;
public Contribution() {
}
public Contribution(final UploadItem item, final SessionManager sessionManager,
final List<DepictedItem> depictedItems, final List<String> categories) {
super(
item.getFileName(),
UploadMediaDetail.formatCaptions(item.getUploadMediaDetails()),
UploadMediaDetail.formatDescriptions(item.getUploadMediaDetails()),
sessionManager.getAuthorName(),
categories);
localUri = item.getMediaUri();
decimalCoords = item.getGpsCoords().getDecimalCoords();
dateCreatedSource = "";
this.depictedItems = depictedItems;
wikidataPlace = WikidataPlace.from(item.getPlace());
}
public void setDateCreatedSource(final String dateCreatedSource) {
this.dateCreatedSource = dateCreatedSource;
}
public String getDateCreatedSource() {
return dateCreatedSource;
}
public long getTransferred() {
return transferred;
}
public void setTransferred(final long transferred) {
this.transferred = transferred;
}
public int getState() {
return state;
}
public void setState(final int state) {
this.state = state;
}
/**
* @return array list of entityids for the depictions
*/
public List<DepictedItem> getDepictedItems() {
return depictedItems;
}
public void setWikidataPlace(final WikidataPlace wikidataPlace) {
this.wikidataPlace = wikidataPlace;
}
public WikidataPlace getWikidataPlace() {
return wikidataPlace;
}
public String getDecimalCoords() {
return decimalCoords;
}
public void setDecimalCoords(final String decimalCoords) {
this.decimalCoords = decimalCoords;
}
public void setDepictedItems(final List<DepictedItem> depictedItems) {
this.depictedItems = depictedItems;
}
public String getMimeType() {
return mimeType;
}
public void setMimeType(final String mimeType) {
this.mimeType = mimeType;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(final Parcel dest, final int flags) {
super.writeToParcel(dest, flags);
dest.writeInt(state);
dest.writeLong(transferred);
dest.writeString(decimalCoords);
dest.writeString(dateCreatedSource);
}
/**
* Constructor that takes Media object and state as parameters and builds a new Contribution object
* @param media
* @param state
*/
public Contribution(Media media, int state) {
super(media);
this.state = state;
}
protected Contribution(final Parcel in) {
super(in);
state = in.readInt();
transferred = in.readLong();
decimalCoords = in.readString();
dateCreatedSource = in.readString();
}
public static final Creator<Contribution> CREATOR = new Creator<Contribution>() {
@Override
public Contribution createFromParcel(final Parcel source) {
return new Contribution(source);
}
@Override
public Contribution[] newArray(final int size) {
return new Contribution[size];
}
};
@Nullable
public Uri getLocalUri() {
return localUri;
}
public void setLocalUri(@Nullable Uri localUri) {
this.localUri = localUri;
}
public long getDataLength() {
return dataLength;
}
public void setDataLength(long dataLength) {
this.dataLength = dataLength;
}
public Date getDateCreated() {
return dateCreated;
}
public void setDateCreated(Date dateCreated) {
this.dateCreated = dateCreated;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
if (!super.equals(o)) {
return false;
}
final Contribution that = (Contribution) o;
return state == that.state &&
transferred == that.transferred &&
dataLength == that.dataLength &&
Objects.equals(decimalCoords, that.decimalCoords) &&
Objects.equals(dateCreatedSource, that.dateCreatedSource) &&
Objects.equals(wikidataPlace, that.wikidataPlace) &&
Objects.equals(depictedItems, that.depictedItems) &&
Objects.equals(mimeType, that.mimeType) &&
Objects.equals(localUri, that.localUri) &&
Objects.equals(dateCreated, that.dateCreated);
}
@Override
public int hashCode() {
return Objects
.hash(super.hashCode(), state, transferred, decimalCoords, dateCreatedSource,
wikidataPlace,
depictedItems, mimeType, localUri, dataLength, dateCreated);
}
}

View file

@ -0,0 +1,90 @@
package fr.free.nrw.commons.contributions
import android.net.Uri
import android.os.Parcelable
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.PrimaryKey
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.upload.UploadItem
import fr.free.nrw.commons.upload.UploadMediaDetail
import fr.free.nrw.commons.upload.WikidataPlace
import fr.free.nrw.commons.upload.WikidataPlace.Companion.from
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem
import kotlinx.android.parcel.Parcelize
import java.util.*
@Entity(tableName = "contribution")
@Parcelize
data class Contribution constructor(
@Embedded(prefix = "media_") val media: Media,
@PrimaryKey val pageId: String = media.pageId,
var state: Int = 0,
var transferred: Long = 0,
val decimalCoords: String? = null,
var dateCreatedSource: String? = null,
var wikidataPlace: WikidataPlace? = null,
/**
* @return array list of entityids for the depictions
*/
/**
* Each depiction loaded in depictions activity is associated with a wikidata entity id, this Id
* is in turn used to upload depictions to wikibase
*/
val depictedItems: List<DepictedItem> = ArrayList(),
var mimeType: String? = null,
val localUri: Uri? = null,
var dataLength: Long = 0,
var dateCreated: Date? = null
) : Parcelable {
fun completeWith(media: Media): Contribution {
return copy(pageId = media.pageId, media = media, state = STATE_COMPLETED)
}
constructor(
item: UploadItem,
sessionManager: SessionManager,
depictedItems: List<DepictedItem>,
categories: List<String>
) : this(
Media(
formatCaptions(item.uploadMediaDetails),
categories,
item.fileName,
formatDescriptions(item.uploadMediaDetails),
sessionManager.authorName
),
localUri = item.mediaUri,
decimalCoords = item.gpsCoords.decimalCoords,
dateCreatedSource = "",
depictedItems = depictedItems,
wikidataPlace = from(item.place)
)
companion object {
const val STATE_COMPLETED = -1
const val STATE_FAILED = 1
const val STATE_QUEUED = 2
const val STATE_IN_PROGRESS = 3
/**
* Formatting captions to the Wikibase format for sending labels
* @param uploadMediaDetails list of media Details
*/
fun formatCaptions(uploadMediaDetails: List<UploadMediaDetail>) =
uploadMediaDetails.associate { it.languageCode!! to it.captionText }
.filter { it.value.isNotBlank() }
/**
* Formats the list of descriptions into the format Commons requires for uploads.
*
* @param descriptions the list of descriptions, description is ignored if text is null.
* @return a string with the pattern of {{en|1=descriptionText}}
*/
fun formatDescriptions(descriptions: List<UploadMediaDetail>) =
descriptions.filter { it.descriptionText.isNotEmpty() }
.joinToString { "{{${it.languageCode}|1=${it.descriptionText}}}" }
}
}

View file

@ -1,7 +1,6 @@
package fr.free.nrw.commons.contributions
import androidx.paging.PagedList.BoundaryCallback
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.di.CommonsApplicationModule
import fr.free.nrw.commons.media.MediaClient
@ -53,9 +52,9 @@ class ContributionBoundaryCallback @Inject constructor(
fun fetchContributions() {
compositeDisposable.add(
mediaClient.getMediaListForUser(sessionManager.userName!!)
.map { mediaList: List<Media?> ->
.map { mediaList ->
mediaList.map {
Contribution(it, Contribution.STATE_COMPLETED)
Contribution(media=it, state=Contribution.STATE_COMPLETED)
}
}
.subscribeOn(ioThreadScheduler)

View file

@ -15,7 +15,7 @@ import java.util.List;
@Dao
public abstract class ContributionDao {
@Query("SELECT * FROM contribution order by dateUploaded DESC")
@Query("SELECT * FROM contribution order by media_dateUploaded DESC")
abstract DataSource.Factory<Integer, Contribution> fetchContributions();
@Insert(onConflict = OnConflictStrategy.REPLACE)
@ -50,7 +50,7 @@ public abstract class ContributionDao {
.fromAction(() -> deleteSynchronous(contribution));
}
@Query("SELECT * from contribution WHERE filename=:fileName")
@Query("SELECT * from contribution WHERE media_filename=:fileName")
public abstract List<Contribution> getContributionWithTitle(String fileName);
@Query("SELECT * from contribution WHERE pageId=:pageId")

View file

@ -61,8 +61,8 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
public void init(final int position, final Contribution contribution) {
this.contribution = contribution;
this.position = position;
titleView.setText(contribution.getMostRelevantCaption());
final String imageSource = chooseImageSource(contribution.getThumbUrl(),
titleView.setText(contribution.getMedia().getMostRelevantCaption());
final String imageSource = chooseImageSource(contribution.getMedia().getThumbUrl(),
contribution.getLocalUri());
if (!TextUtils.isEmpty(imageSource)) {
final ImageRequest imageRequest =

View file

@ -41,7 +41,6 @@ import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
import fr.free.nrw.commons.nearby.NearbyController;
import fr.free.nrw.commons.nearby.NearbyNotificationCardView;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.settings.Prefs;
import fr.free.nrw.commons.upload.UploadService;
import fr.free.nrw.commons.utils.ConfigUtils;
import fr.free.nrw.commons.utils.DialogUtil;
@ -490,5 +489,10 @@ public class ContributionsFragment
public int getTotalMediaCount() {
return contributionsListFragment.getTotalMediaCount();
}
@Override
public Integer getContributionStateAt(int position) {
return contributionsListFragment.getContributionStateAt(position);
}
}

View file

@ -277,7 +277,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
public Media getMediaAtPosition(final int i) {
return adapter.getContributionForPosition(i);
return adapter.getContributionForPosition(i).getMedia();
}
public int getTotalMediaCount() {
@ -292,7 +292,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
@Override
public void onConfirmClicked(@Nullable Contribution contribution, boolean copyWikicode) {
if(copyWikicode) {
String wikicode = contribution.getWikiCode();
String wikicode = contribution.getMedia().getWikiCode();
Utils.copy("wikicode", wikicode, getContext());
}
@ -301,6 +301,10 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl
Utils.handleWebUrl(getContext(), Uri.parse(url));
}
public Integer getContributionStateAt(int position) {
return adapter.getContributionForPosition(position).getState();
}
public interface Callback {
void retryUpload(Contribution contribution);

View file

@ -30,7 +30,7 @@ class WikipediaInstructionsDialogFragment : DialogFragment() {
) {
super.onViewCreated(view, savedInstanceState)
contribution = arguments!!.getParcelable(ARG_CONTRIBUTION)
tv_wikicode.setText(contribution?.wikiCode)
tv_wikicode.setText(contribution?.media?.wikiCode)
instructions_cancel.setOnClickListener {
dismiss()
}

View file

@ -10,7 +10,7 @@ import fr.free.nrw.commons.contributions.ContributionDao
* The database for accessing the respective DAOs
*
*/
@Database(entities = [Contribution::class], version = 3, exportSchema = false)
@Database(entities = [Contribution::class], version = 4, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun contributionDao(): ContributionDao

View file

@ -134,6 +134,11 @@ public class ExploreActivity
}
}
@Override
public Integer getContributionStateAt(int position) {
return null;
}
/**
* This method is called on success of API call for featured images or mobile uploads.
* The viewpager will notified that number of items have changed.

View file

@ -176,6 +176,11 @@ public class SearchActivity extends NavigationBaseActivity
return searchMediaFragment.getTotalMediaCount();
}
@Override
public Integer getContributionStateAt(int position) {
return null;
}
/**
* This method is called on success of API call for image Search.
* The viewpager will notified that number of items have changed.

View file

@ -173,6 +173,11 @@ public class WikidataItemDetailsActivity extends NavigationBaseActivity implemen
return depictionImagesListFragment.getTotalMediaCount();
}
@Override
public Integer getContributionStateAt(int position) {
return null;
}
/**
* Consumers should be simply using this method to use this activity.
*

View file

@ -17,14 +17,11 @@ import java.util.*
import javax.inject.Inject
class MediaConverter @Inject constructor() {
fun convert(
page: MwQueryPage,
entity: Entities.Entity,
imageInfo: ImageInfo
): Media {
fun convert(page: MwQueryPage, entity: Entities.Entity, imageInfo: ImageInfo): Media {
val metadata = imageInfo.metadata
requireNotNull(metadata) { "No metadata" }
return Media(
page.pageId().toString(),
imageInfo.thumbUrl.takeIf { it.isNotBlank() } ?: imageInfo.originalUrl,
imageInfo.originalUrl,
page.title(),
@ -33,14 +30,12 @@ class MediaConverter @Inject constructor() {
metadata.licenseShortName(),
metadata.prefixedLicenseUrl,
getArtist(metadata),
page.pageId().toString(),
MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories),
metadata.latLng,
entity.labels().mapValues { it.value.value() },
entity.descriptions().mapValues { it.value.value() },
entity.depictionIds()
)
}
/**

View file

@ -49,4 +49,6 @@ abstract class PageableMediaFragment : BasePagingFragment<Media>(), MediaDetailP
}
override fun getTotalMediaCount(): Int = pagedListAdapter.itemCount
override fun getContributionStateAt(position: Int) = null
}

View file

@ -188,7 +188,8 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
return;
}
Media m = provider.getMediaAtPosition(pager.getCurrentItem());
final int position = pager.getCurrentItem();
Media m = provider.getMediaAtPosition(position);
if (m != null) {
// Enable default set of actions, then re-enable different set of actions only if it is a failed contrib
menu.findItem(R.id.menu_browser_current_image).setEnabled(true).setVisible(true);
@ -204,10 +205,9 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
BookmarkPicturesContentProvider.uriForName(m.getFilename())
);
updateBookmarkState(menu.findItem(R.id.menu_bookmark_current_image));
if (m instanceof Contribution) {
Contribution c = (Contribution) m;
switch (c.getState()) {
final Integer contributionState = provider.getContributionStateAt(position);
if (contributionState != null) {
switch (contributionState) {
case Contribution.STATE_FAILED:
case Contribution.STATE_IN_PROGRESS:
case Contribution.STATE_QUEUED:
@ -289,6 +289,8 @@ public class MediaDetailPagerFragment extends CommonsDaggerSupportFragment imple
Media getMediaAtPosition(int i);
int getTotalMediaCount();
Integer getContributionStateAt(int position);
}
//FragmentStatePagerAdapter allows user to swipe across collection of images (no. of images undetermined)

View file

@ -2,6 +2,7 @@ package fr.free.nrw.commons.upload;
import android.content.Context;
import androidx.annotation.NonNull;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.contributions.Contribution;
import fr.free.nrw.commons.filepicker.UploadableFile.DateTimeWithSource;
import fr.free.nrw.commons.settings.Prefs.Licenses;
@ -30,13 +31,14 @@ class PageContentsCreator {
public String createFrom(Contribution contribution) {
StringBuilder buffer = new StringBuilder();
final Media media = contribution.getMedia();
buffer
.append("== {{int:filedesc}} ==\n")
.append("{{Information\n")
.append("|description=").append(contribution.getFallbackDescription()).append("\n")
.append("|description=").append(media.getFallbackDescription()).append("\n")
.append("|source=").append("{{own}}\n")
.append("|author=[[User:").append(contribution.getCreator()).append("|")
.append(contribution.getCreator()).append("]]\n");
.append("|author=[[User:").append(media.getCreator()).append("|")
.append(media.getCreator()).append("]]\n");
String templatizedCreatedDate = getTemplatizedCreatedDate(
contribution.getDateCreated(), contribution.getDateCreatedSource());
@ -53,10 +55,10 @@ class PageContentsCreator {
}
buffer.append("== {{int:license-header}} ==\n")
.append(licenseTemplateFor(contribution.getLicense())).append("\n\n")
.append(licenseTemplateFor(media.getLicense())).append("\n\n")
.append("{{Uploaded from Mobile|platform=Android|version=")
.append(ConfigUtils.getVersionNameWithSha(context)).append("}}\n");
final List<String> categories = contribution.getCategories();
final List<String> categories = media.getCategories();
if (categories != null && categories.size() != 0) {
for (int i = 0; i < categories.size(); i++) {
buffer.append("\n[[Category:").append(categories.get(i)).append("]]");

View file

@ -13,6 +13,7 @@ import android.net.Uri;
import android.os.IBinder;
import android.provider.MediaStore;
import android.text.TextUtils;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
@ -93,12 +94,13 @@ public class UploadController {
//Set creator, desc, and license
// If author name is enabled and set, use it
final Media media = contribution.getMedia();
if (store.getBoolean("useAuthorName", false)) {
final String authorName = store.getString("authorName", "");
contribution.setCreator(authorName);
media.setCreator(authorName);
}
if (TextUtils.isEmpty(contribution.getCreator())) {
if (TextUtils.isEmpty(media.getCreator())) {
final Account currentAccount = sessionManager.getCurrentAccount();
if (currentAccount == null) {
Timber.d("Current account is null");
@ -106,15 +108,15 @@ public class UploadController {
sessionManager.forceLogin(context);
return;
}
contribution.setCreator(sessionManager.getAuthorName());
media.setCreator(sessionManager.getAuthorName());
}
if (contribution.getFallbackDescription() == null) {
contribution.setFallbackDescription("");
if (media.getFallbackDescription() == null) {
media.setFallbackDescription("");
}
final String license = store.getString(Prefs.DEFAULT_LICENSE, Prefs.Licenses.CC_BY_SA_3);
contribution.setLicense(license);
media.setLicense(license);
uploadTask(contribution);
}

View file

@ -41,24 +41,4 @@ data class UploadMediaDetail constructor(
*/
var isManuallyAdded: Boolean = false
companion object {
/**
* Formatting captions to the Wikibase format for sending labels
* @param uploadMediaDetails list of media Details
*/
@JvmStatic
fun formatCaptions(uploadMediaDetails: List<UploadMediaDetail>) =
uploadMediaDetails.associate { it.languageCode to it.captionText }.filter { it.value.isNotBlank() }
/**
* Formats the list of descriptions into the format Commons requires for uploads.
*
* @param descriptions the list of descriptions, description is ignored if text is null.
* @return a string with the pattern of {{en|1=descriptionText}}
*/
@JvmStatic
fun formatDescriptions(descriptions: List<UploadMediaDetail>) =
descriptions.filter { it.descriptionText.isNotEmpty() }
.joinToString { "{{${it.languageCode}|1=${it.descriptionText}}}" }
}
}

View file

@ -13,6 +13,7 @@ import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import fr.free.nrw.commons.BuildConfig;
import fr.free.nrw.commons.CommonsApplication;
import fr.free.nrw.commons.Media;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.contributions.Contribution;
@ -224,25 +225,27 @@ public class UploadService extends CommonsDaggerService {
File localFile = new File(localUri.getPath());
Timber.d("Before execution!");
final Media media = contribution.getMedia();
final String displayTitle = media.getDisplayTitle();
curNotification.setContentTitle(getString(R.string.upload_progress_notification_title_start,
contribution.getDisplayTitle()))
displayTitle))
.setContentText(getResources()
.getQuantityString(R.plurals.uploads_pending_notification_indicator, toUpload,
toUpload))
.setTicker(getString(R.string.upload_progress_notification_title_in_progress,
contribution.getDisplayTitle()))
displayTitle))
.setOngoing(true);
notificationManager
.notify(notificationTag, NOTIFICATION_UPLOAD_IN_PROGRESS, curNotification.build());
String filename = contribution.getFilename();
String filename = media.getFilename();
NotificationUpdateProgressListener notificationUpdater = new NotificationUpdateProgressListener(
notificationTag,
getString(R.string.upload_progress_notification_title_in_progress,
contribution.getDisplayTitle()),
displayTitle),
getString(R.string.upload_progress_notification_title_finishing,
contribution.getDisplayTitle()),
displayTitle),
contribution
);
@ -320,13 +323,7 @@ public class UploadService extends CommonsDaggerService {
private void saveCompletedContribution(Contribution contribution, UploadResult uploadResult) {
compositeDisposable.add(mediaClient.getMedia("File:" + uploadResult.getFilename())
.map(media -> {
Contribution newContribution = new Contribution(media, Contribution.STATE_COMPLETED);
if (contribution.getWikidataPlace() != null) {
newContribution.setWikidataPlace(contribution.getWikidataPlace());
}
return newContribution;
})
.map(contribution::completeWith)
.flatMapCompletable(
newContribution -> contributionDao.saveAndDelete(contribution, newContribution))
.subscribe());
@ -335,10 +332,9 @@ public class UploadService extends CommonsDaggerService {
@SuppressLint("StringFormatInvalid")
@SuppressWarnings("deprecation")
private void showFailedNotification(Contribution contribution) {
curNotification.setTicker(
getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle()))
.setContentTitle(
getString(R.string.upload_failed_notification_title, contribution.getDisplayTitle()))
final String displayTitle = contribution.getMedia().getDisplayTitle();
curNotification.setTicker(getString(R.string.upload_failed_notification_title, displayTitle))
.setContentTitle(getString(R.string.upload_failed_notification_title, displayTitle))
.setContentText(getString(R.string.upload_failed_notification_subtitle))
.setProgress(0, 0, false)
.setOngoing(false);

View file

@ -5,7 +5,7 @@ import fr.free.nrw.commons.nearby.Place
import kotlinx.android.parcel.Parcelize
@Parcelize
internal data class WikidataPlace(
data class WikidataPlace(
override val id: String,
override val name: String,
val imageValue: String?,

View file

@ -1,9 +1,11 @@
package fr.free.nrw.commons.upload.structure.depictions
import android.os.Parcelable
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 kotlinx.android.parcel.Parcelize
import org.wikipedia.wikidata.DataValue
import org.wikipedia.wikidata.Entities
import org.wikipedia.wikidata.Statement_partial
@ -17,6 +19,7 @@ const val THUMB_IMAGE_SIZE = "70px"
/**
* Model class for Depicted Item in Upload and Explore
*/
@Parcelize
data class DepictedItem constructor(
override val name: String,
val description: String?,
@ -25,7 +28,7 @@ data class DepictedItem constructor(
val commonsCategories: List<String>,
var isSelected: Boolean,
override val id: String
) : WikidataItem {
) : WikidataItem, Parcelable {
constructor(entity: Entities.Entity) : this(
entity,

View file

@ -191,7 +191,7 @@ public class WikidataEditService {
}
private Observable<Boolean> captionEdits(Contribution contribution, Long fileEntityId) {
return Observable.fromIterable(contribution.getCaptions().entrySet())
return Observable.fromIterable(contribution.getMedia().getCaptions().entrySet())
.concatMap(entry -> addCaption(fileEntityId, entry.getKey(), entry.getValue()));
}

View file

@ -44,10 +44,11 @@ fun media(
pageId: String = "pageId",
categories: List<String>? = listOf("categories"),
coordinates: LatLng? = LatLng(0.0, 0.0, 0.0f),
captions: Map<String?, String?> = mapOf("en" to "caption"),
descriptions: Map<String?, String?> = mapOf("en" to "description"),
captions: Map<String, String> = mapOf("en" to "caption"),
descriptions: Map<String, String> = mapOf("en" to "description"),
depictionIds: List<String> = listOf("depictionId")
) = Media(
pageId,
thumbUrl,
imageUrl,
filename,
@ -56,7 +57,6 @@ fun media(
license,
licenseUrl,
creator,
pageId,
categories,
coordinates,
captions,

View file

@ -4,12 +4,12 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyZeroInteractions
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.media.MediaClient
import io.reactivex.Scheduler
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import media
import org.junit.Before
import org.junit.Rule
import org.junit.Test
@ -57,9 +57,8 @@ class ContributionBoundaryCallbackTest {
whenever(repository.save(anyList<Contribution>()))
.thenReturn(Single.just(listOf(1L, 2L)))
whenever(sessionManager.userName).thenReturn("Test")
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
Single.just(listOf(mock(Media::class.java)))
)
whenever(mediaClient.getMediaListForUser(anyString()))
.thenReturn(Single.just(listOf(media())))
contributionBoundaryCallback.onZeroItemsLoaded()
verify(repository).save(anyList<Contribution>());
verify(mediaClient).getMediaListForUser(anyString());
@ -70,9 +69,8 @@ class ContributionBoundaryCallbackTest {
whenever(repository.save(anyList<Contribution>()))
.thenReturn(Single.just(listOf(1L, 2L)))
whenever(sessionManager.userName).thenReturn("Test")
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
Single.just(listOf(mock(Media::class.java)))
)
whenever(mediaClient.getMediaListForUser(anyString()))
.thenReturn(Single.just(listOf(media())))
contributionBoundaryCallback.onItemAtEndLoaded(mock(Contribution::class.java))
verify(repository).save(anyList());
verify(mediaClient).getMediaListForUser(anyString());
@ -83,9 +81,8 @@ class ContributionBoundaryCallbackTest {
whenever(repository.save(anyList<Contribution>()))
.thenReturn(Single.just(listOf(1L, 2L)))
whenever(sessionManager.userName).thenReturn("Test")
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
Single.just(listOf(mock(Media::class.java)))
)
whenever(mediaClient.getMediaListForUser(anyString()))
.thenReturn(Single.just(listOf(media())))
contributionBoundaryCallback.onItemAtFrontLoaded(mock(Contribution::class.java))
verify(repository).save(anyList());
verify(mediaClient).getMediaListForUser(anyString());
@ -97,7 +94,7 @@ class ContributionBoundaryCallbackTest {
.thenReturn(Single.just(listOf(1L, 2L)))
whenever(sessionManager.userName).thenReturn("Test")
whenever(mediaClient.getMediaListForUser(anyString())).thenReturn(
Single.just(listOf(mock(Media::class.java)))
Single.just(listOf(media()))
)
contributionBoundaryCallback.fetchContributions()
verify(repository).save(anyList());

View file

@ -123,7 +123,7 @@ class DeleteHelperTest {
.thenReturn(Observable.just(true))
whenever(media.displayTitle).thenReturn("Test file")
media.filename ="Test file.jpg"
whenever(media.filename).thenReturn("Test file.jpg")
whenever(media.creator).thenReturn(null)

View file

@ -1,10 +1,10 @@
package fr.free.nrw.commons.review
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.media.MediaClient
import io.reactivex.Observable
import io.reactivex.Single
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
@ -61,7 +61,7 @@ class ReviewHelperTest {
.thenReturn(Observable.just(mockResponse))
val media = mock(Media::class.java)
media.filename="File:Test.jpg"
whenever(media.filename).thenReturn("Test file.jpg")
`when`(mediaClient?.getMedia(ArgumentMatchers.anyString()))
.thenReturn(Single.just(media))
}

View file

@ -2,6 +2,9 @@ package fr.free.nrw.commons.upload
import android.content.ComponentName
import android.content.Context
import com.nhaarman.mockitokotlin2.mock
import com.nhaarman.mockitokotlin2.whenever
import fr.free.nrw.commons.Media
import fr.free.nrw.commons.auth.SessionManager
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.kvstore.JsonKvStore
@ -47,7 +50,9 @@ class UploadControllerTest {
@Test
fun startUpload() {
val contribution = mock(Contribution::class.java)
`when`(contribution.getCreator()).thenReturn("Creator")
val media = mock<Media>()
whenever(contribution.media).thenReturn(media)
whenever(media.creator).thenReturn("Creator")
uploadController!!.startUpload(contribution)
}
}