mirror of
https://github.com/commons-app/apps-android-commons.git
synced 2025-10-27 04:43:54 +01:00
Fetch and use thumbnail across the app (#2906)
This commit is contained in:
parent
17d69cde02
commit
37e9eae314
18 changed files with 161 additions and 260 deletions
|
|
@ -42,6 +42,7 @@ public class Media implements Parcelable {
|
||||||
|
|
||||||
// Primary metadata fields
|
// Primary metadata fields
|
||||||
protected Uri localUri;
|
protected Uri localUri;
|
||||||
|
private String thumbUrl;
|
||||||
protected String imageUrl;
|
protected String imageUrl;
|
||||||
protected String filename;
|
protected String filename;
|
||||||
protected String description; // monolingual description on input...
|
protected String description; // monolingual description on input...
|
||||||
|
|
@ -93,6 +94,7 @@ public class Media implements Parcelable {
|
||||||
long dataLength, Date dateCreated, Date dateUploaded, String creator) {
|
long dataLength, Date dateCreated, Date dateUploaded, String creator) {
|
||||||
this();
|
this();
|
||||||
this.localUri = localUri;
|
this.localUri = localUri;
|
||||||
|
this.thumbUrl = imageUrl;
|
||||||
this.imageUrl = imageUrl;
|
this.imageUrl = imageUrl;
|
||||||
this.filename = filename;
|
this.filename = filename;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
|
@ -107,6 +109,7 @@ public class Media implements Parcelable {
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public Media(Parcel in) {
|
public Media(Parcel in) {
|
||||||
localUri = in.readParcelable(Uri.class.getClassLoader());
|
localUri = in.readParcelable(Uri.class.getClassLoader());
|
||||||
|
thumbUrl = in.readString();
|
||||||
imageUrl = in.readString();
|
imageUrl = in.readString();
|
||||||
filename = in.readString();
|
filename = in.readString();
|
||||||
description = in.readString();
|
description = in.readString();
|
||||||
|
|
@ -124,6 +127,67 @@ public class Media implements Parcelable {
|
||||||
descriptions = in.readHashMap(ClassLoader.getSystemClassLoader());
|
descriptions = in.readHashMap(ClassLoader.getSystemClassLoader());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public static Media from(MwQueryPage page) {
|
||||||
|
ImageInfo imageInfo = page.imageInfo();
|
||||||
|
if (imageInfo == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ExtMetadata metadata = imageInfo.getMetadata();
|
||||||
|
if (metadata == null) {
|
||||||
|
Media media = new Media(null, imageInfo.getOriginalUrl(),
|
||||||
|
page.title(), "", 0, null, null, null);
|
||||||
|
if (!StringUtils.isBlank(imageInfo.getThumbUrl())) {
|
||||||
|
media.setThumbUrl(imageInfo.getThumbUrl());
|
||||||
|
}
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
|
Media media = new Media(null,
|
||||||
|
imageInfo.getOriginalUrl(),
|
||||||
|
page.title(),
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
safeParseDate(metadata.dateTimeOriginal().value()),
|
||||||
|
safeParseDate(metadata.dateTime().value()),
|
||||||
|
StringUtil.fromHtml(metadata.artist().value()).toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!StringUtils.isBlank(imageInfo.getThumbUrl())) {
|
||||||
|
media.setThumbUrl(imageInfo.getThumbUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
String language = Locale.getDefault().getLanguage();
|
||||||
|
if (StringUtils.isBlank(language)) {
|
||||||
|
language = "default";
|
||||||
|
}
|
||||||
|
|
||||||
|
media.setDescriptions(Collections.singletonMap(language, metadata.imageDescription().value()));
|
||||||
|
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories().value()));
|
||||||
|
String latitude = metadata.gpsLatitude().value();
|
||||||
|
String longitude = metadata.gpsLongitude().value();
|
||||||
|
|
||||||
|
if (!StringUtils.isBlank(latitude) && !StringUtils.isBlank(longitude)) {
|
||||||
|
LatLng latLng = new LatLng(Double.parseDouble(latitude), Double.parseDouble(longitude), 0);
|
||||||
|
media.setCoordinates(latLng);
|
||||||
|
}
|
||||||
|
|
||||||
|
media.setLicenseInformation(metadata.licenseShortName().value(), metadata.licenseUrl().value());
|
||||||
|
return media;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getThumbUrl() {
|
||||||
|
return thumbUrl;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets tag of media
|
* Gets tag of media
|
||||||
* @param key Media key
|
* @param key Media key
|
||||||
|
|
@ -322,53 +386,8 @@ public class Media implements Parcelable {
|
||||||
return license;
|
return license;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void setThumbUrl(String thumbUrl) {
|
||||||
* Creating Media object from MWQueryPage.
|
this.thumbUrl = thumbUrl;
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static Media from(MwQueryPage page) {
|
|
||||||
ImageInfo imageInfo = page.imageInfo();
|
|
||||||
if (imageInfo == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
ExtMetadata metadata = imageInfo.getMetadata();
|
|
||||||
if (metadata == null) {
|
|
||||||
return new Media(null, imageInfo.getOriginalUrl(),
|
|
||||||
page.title(), "", 0, null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
Media media = new Media(null,
|
|
||||||
imageInfo.getOriginalUrl(),
|
|
||||||
page.title(),
|
|
||||||
"",
|
|
||||||
0,
|
|
||||||
safeParseDate(metadata.dateTimeOriginal().value()),
|
|
||||||
safeParseDate(metadata.dateTime().value()),
|
|
||||||
StringUtil.fromHtml(metadata.artist().value()).toString()
|
|
||||||
);
|
|
||||||
|
|
||||||
String language = Locale.getDefault().getLanguage();
|
|
||||||
if (StringUtils.isBlank(language)) {
|
|
||||||
language = "default";
|
|
||||||
}
|
|
||||||
|
|
||||||
media.setDescriptions(Collections.singletonMap(language, metadata.imageDescription().value()));
|
|
||||||
media.setCategories(MediaDataExtractorUtil.extractCategoriesFromList(metadata.categories().value()));
|
|
||||||
String latitude = metadata.gpsLatitude().value();
|
|
||||||
String longitude = metadata.gpsLongitude().value();
|
|
||||||
|
|
||||||
if (!StringUtils.isBlank(latitude) && !StringUtils.isBlank(longitude)) {
|
|
||||||
LatLng latLng = new LatLng(Double.parseDouble(latitude), Double.parseDouble(longitude), 0);
|
|
||||||
media.setCoordinates(latLng);
|
|
||||||
}
|
|
||||||
|
|
||||||
media.setLicenseInformation(metadata.licenseShortName().value(), metadata.licenseUrl().value());
|
|
||||||
return media;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getLicenseUrl() {
|
public String getLicenseUrl() {
|
||||||
|
|
@ -482,6 +501,7 @@ public class Media implements Parcelable {
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel parcel, int flags) {
|
public void writeToParcel(Parcel parcel, int flags) {
|
||||||
parcel.writeParcelable(localUri, flags);
|
parcel.writeParcelable(localUri, flags);
|
||||||
|
parcel.writeString(thumbUrl);
|
||||||
parcel.writeString(imageUrl);
|
parcel.writeString(imageUrl);
|
||||||
parcel.writeString(filename);
|
parcel.writeString(filename);
|
||||||
parcel.writeString(description);
|
parcel.writeString(description);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
package fr.free.nrw.commons;
|
package fr.free.nrw.commons;
|
||||||
|
|
||||||
import android.text.Html;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
|
@ -36,7 +34,7 @@ public class MediaDataExtractor {
|
||||||
* @return full Media object with all details including deletion status and talk page
|
* @return full Media object with all details including deletion status and talk page
|
||||||
*/
|
*/
|
||||||
public Single<Media> fetchMediaDetails(String filename) {
|
public Single<Media> fetchMediaDetails(String filename) {
|
||||||
Single<Media> mediaSingle = okHttpJsonApiClient.getMedia(filename, false);
|
Single<Media> mediaSingle = getMediaFromFileName(filename);
|
||||||
Single<Boolean> pageExistsSingle = mediaWikiApi.pageExists("Commons:Deletion_requests/" + filename);
|
Single<Boolean> pageExistsSingle = mediaWikiApi.pageExists("Commons:Deletion_requests/" + filename);
|
||||||
Single<String> discussionSingle = getDiscussion(filename);
|
Single<String> discussionSingle = getDiscussion(filename);
|
||||||
return Single.zip(mediaSingle, pageExistsSingle, discussionSingle, (media, deletionStatus, discussion) -> {
|
return Single.zip(mediaSingle, pageExistsSingle, discussionSingle, (media, deletionStatus, discussion) -> {
|
||||||
|
|
@ -48,6 +46,15 @@ public class MediaDataExtractor {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method can be used to fetch media for a given filename
|
||||||
|
* @param filename Eg. File:Test.jpg
|
||||||
|
* @return return data rich Media object
|
||||||
|
*/
|
||||||
|
public Single<Media> getMediaFromFileName(String filename) {
|
||||||
|
return okHttpJsonApiClient.getMedia(filename, false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch talk page from the MediaWiki API
|
* Fetch talk page from the MediaWiki API
|
||||||
* @param filename
|
* @param filename
|
||||||
|
|
|
||||||
|
|
@ -1,123 +0,0 @@
|
||||||
package fr.free.nrw.commons;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.AttributeSet;
|
|
||||||
|
|
||||||
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
|
|
||||||
import com.facebook.drawee.view.SimpleDraweeView;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.collection.LruCache;
|
|
||||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
|
|
||||||
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
|
||||||
import fr.free.nrw.commons.mwapi.MediaWikiApi;
|
|
||||||
import io.reactivex.Single;
|
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
|
||||||
import io.reactivex.disposables.CompositeDisposable;
|
|
||||||
import io.reactivex.disposables.Disposable;
|
|
||||||
import io.reactivex.schedulers.Schedulers;
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class MediaWikiImageView extends SimpleDraweeView {
|
|
||||||
@Inject MediaWikiApi mwApi;
|
|
||||||
@Inject LruCache<String, String> thumbnailUrlCache;
|
|
||||||
|
|
||||||
protected CompositeDisposable compositeDisposable = new CompositeDisposable();
|
|
||||||
|
|
||||||
public MediaWikiImageView(Context context) {
|
|
||||||
this(context, null);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaWikiImageView(Context context, AttributeSet attrs) {
|
|
||||||
this(context, attrs, 0);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
public MediaWikiImageView(Context context, AttributeSet attrs, int defStyle) {
|
|
||||||
super(context, attrs, defStyle);
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the media. Fetches its thumbnail if necessary.
|
|
||||||
* @param media the new media
|
|
||||||
*/
|
|
||||||
public void setMedia(Media media) {
|
|
||||||
if (media == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Disposable disposable = fetchMediaThumbnail(media)
|
|
||||||
.subscribeOn(Schedulers.io())
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe(thumbnail -> {
|
|
||||||
if (!StringUtils.isBlank(thumbnail)) {
|
|
||||||
setImageUrl(thumbnail);
|
|
||||||
}
|
|
||||||
}, throwable -> Timber.e(throwable, "Error occurred while fetching thumbnail"));
|
|
||||||
|
|
||||||
compositeDisposable.add(disposable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDetachedFromWindow() {
|
|
||||||
compositeDisposable.clear();
|
|
||||||
super.onDetachedFromWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes MediaWikiImageView.
|
|
||||||
*/
|
|
||||||
private void init() {
|
|
||||||
ApplicationlessInjection
|
|
||||||
.getInstance(getContext()
|
|
||||||
.getApplicationContext())
|
|
||||||
.getCommonsApplicationComponent()
|
|
||||||
.inject(this);
|
|
||||||
setHierarchy(GenericDraweeHierarchyBuilder
|
|
||||||
.newInstance(getResources())
|
|
||||||
.setPlaceholderImage(VectorDrawableCompat.create(getResources(),
|
|
||||||
R.drawable.ic_image_black_24dp, getContext().getTheme()))
|
|
||||||
.setFailureImage(VectorDrawableCompat.create(getResources(),
|
|
||||||
R.drawable.ic_image_black_24dp, getContext().getTheme()))
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: refactor the logic for thumbnails. ImageInfo API can be used to fetch thumbnail upfront
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches media thumbnail from the server
|
|
||||||
*
|
|
||||||
* @param media
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
public Single<String> fetchMediaThumbnail(Media media) {
|
|
||||||
if (media.getFilename() != null && thumbnailUrlCache.get(media.getFilename()) != null) {
|
|
||||||
return Single.just(thumbnailUrlCache.get(media.getFilename()));
|
|
||||||
}
|
|
||||||
return mwApi.findThumbnailByFilename(media.getFilename())
|
|
||||||
.map(result -> {
|
|
||||||
if (TextUtils.isEmpty(result) && media.getLocalUri() != null) {
|
|
||||||
return media.getLocalUri().toString();
|
|
||||||
} else {
|
|
||||||
thumbnailUrlCache.put(media.getFilename(), result);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the image from the URL.
|
|
||||||
* @param url the URL of the image
|
|
||||||
*/
|
|
||||||
private void setImageUrl(@Nullable String url) {
|
|
||||||
setImageURI(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -8,12 +8,13 @@ import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -82,12 +83,12 @@ public class GridViewAdapter extends ArrayAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
Media item = data.get(position);
|
Media item = data.get(position);
|
||||||
MediaWikiImageView imageView = convertView.findViewById(R.id.categoryImageView);
|
SimpleDraweeView imageView = convertView.findViewById(R.id.categoryImageView);
|
||||||
TextView fileName = convertView.findViewById(R.id.categoryImageTitle);
|
TextView fileName = convertView.findViewById(R.id.categoryImageTitle);
|
||||||
TextView author = convertView.findViewById(R.id.categoryImageAuthor);
|
TextView author = convertView.findViewById(R.id.categoryImageAuthor);
|
||||||
fileName.setText(item.getDisplayTitle());
|
fileName.setText(item.getDisplayTitle());
|
||||||
setAuthorView(item, author);
|
setAuthorView(item, author);
|
||||||
imageView.setMedia(item);
|
imageView.setImageURI(item.getThumbUrl());
|
||||||
return convertView;
|
return convertView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,23 +6,37 @@ import android.widget.LinearLayout;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
import fr.free.nrw.commons.MediaDataExtractor;
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
import fr.free.nrw.commons.ViewHolder;
|
import fr.free.nrw.commons.ViewHolder;
|
||||||
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
|
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
|
||||||
|
import fr.free.nrw.commons.di.ApplicationlessInjection;
|
||||||
|
import io.reactivex.disposables.CompositeDisposable;
|
||||||
|
import io.reactivex.disposables.Disposable;
|
||||||
|
import io.reactivex.schedulers.Schedulers;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
class ContributionViewHolder implements ViewHolder<DisplayableContribution> {
|
public class ContributionViewHolder implements ViewHolder<DisplayableContribution> {
|
||||||
@BindView(R.id.contributionImage) MediaWikiImageView imageView;
|
@BindView(R.id.contributionImage)
|
||||||
|
SimpleDraweeView imageView;
|
||||||
@BindView(R.id.contributionTitle) TextView titleView;
|
@BindView(R.id.contributionTitle) TextView titleView;
|
||||||
@BindView(R.id.contributionState) TextView stateView;
|
@BindView(R.id.contributionState) TextView stateView;
|
||||||
@BindView(R.id.contributionSequenceNumber) TextView seqNumView;
|
@BindView(R.id.contributionSequenceNumber) TextView seqNumView;
|
||||||
@BindView(R.id.contributionProgress) ProgressBar progressView;
|
@BindView(R.id.contributionProgress) ProgressBar progressView;
|
||||||
@BindView(R.id.failed_image_options) LinearLayout failedImageOptions;
|
@BindView(R.id.failed_image_options) LinearLayout failedImageOptions;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
MediaDataExtractor mediaDataExtractor;
|
||||||
|
|
||||||
private DisplayableContribution contribution;
|
private DisplayableContribution contribution;
|
||||||
|
private CompositeDisposable compositeDisposable = new CompositeDisposable();
|
||||||
|
|
||||||
ContributionViewHolder(View parent) {
|
ContributionViewHolder(View parent) {
|
||||||
ButterKnife.bind(this, parent);
|
ButterKnife.bind(this, parent);
|
||||||
|
|
@ -30,8 +44,10 @@ class ContributionViewHolder implements ViewHolder<DisplayableContribution> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void bindModel(Context context, DisplayableContribution contribution) {
|
public void bindModel(Context context, DisplayableContribution contribution) {
|
||||||
|
ApplicationlessInjection.getInstance(context)
|
||||||
|
.getCommonsApplicationComponent().inject(this);
|
||||||
this.contribution = contribution;
|
this.contribution = contribution;
|
||||||
imageView.setMedia(contribution);
|
fetchAndDisplayThumbnail(contribution);
|
||||||
titleView.setText(contribution.getDisplayTitle());
|
titleView.setText(contribution.getDisplayTitle());
|
||||||
|
|
||||||
seqNumView.setText(String.valueOf(contribution.getPosition() + 1));
|
seqNumView.setText(String.valueOf(contribution.getPosition() + 1));
|
||||||
|
|
@ -71,6 +87,26 @@ class ContributionViewHolder implements ViewHolder<DisplayableContribution> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method fetches the thumbnail url from file name
|
||||||
|
* This can be removed once #2904 is in place and contribution contains all metadata beforehand
|
||||||
|
* @param contribution
|
||||||
|
*/
|
||||||
|
private void fetchAndDisplayThumbnail(DisplayableContribution contribution) {
|
||||||
|
Timber.d("Fetching thumbnail for %s", contribution.getFilename());
|
||||||
|
Disposable disposable = mediaDataExtractor.getMediaFromFileName(contribution.getFilename())
|
||||||
|
.subscribeOn(Schedulers.io())
|
||||||
|
.observeOn(Schedulers.io())
|
||||||
|
.subscribe(media -> {
|
||||||
|
imageView.setImageURI(media.getThumbUrl());
|
||||||
|
});
|
||||||
|
compositeDisposable.add(disposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
compositeDisposable.clear();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retry upload when it is failed
|
* Retry upload when it is failed
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ class ContributionsListAdapter extends CursorAdapter {
|
||||||
final ContributionViewHolder views = (ContributionViewHolder)view.getTag();
|
final ContributionViewHolder views = (ContributionViewHolder)view.getTag();
|
||||||
final Contribution contribution = contributionDao.fromCursor(cursor);
|
final Contribution contribution = contributionDao.fromCursor(cursor);
|
||||||
|
|
||||||
|
Timber.d("Cursor position is %d", cursor.getPosition());
|
||||||
DisplayableContribution displayableContribution = new DisplayableContribution(contribution,
|
DisplayableContribution displayableContribution = new DisplayableContribution(contribution,
|
||||||
cursor.getPosition(),
|
cursor.getPosition(),
|
||||||
new DisplayableContribution.ContributionActions() {
|
new DisplayableContribution.ContributionActions() {
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import dagger.android.AndroidInjectionModule;
|
||||||
import dagger.android.AndroidInjector;
|
import dagger.android.AndroidInjector;
|
||||||
import dagger.android.support.AndroidSupportInjectionModule;
|
import dagger.android.support.AndroidSupportInjectionModule;
|
||||||
import fr.free.nrw.commons.CommonsApplication;
|
import fr.free.nrw.commons.CommonsApplication;
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
|
||||||
import fr.free.nrw.commons.auth.LoginActivity;
|
import fr.free.nrw.commons.auth.LoginActivity;
|
||||||
|
import fr.free.nrw.commons.contributions.ContributionViewHolder;
|
||||||
import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
|
import fr.free.nrw.commons.contributions.ContributionsSyncAdapter;
|
||||||
import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
|
import fr.free.nrw.commons.modifications.ModificationsSyncAdapter;
|
||||||
import fr.free.nrw.commons.nearby.PlaceRenderer;
|
import fr.free.nrw.commons.nearby.PlaceRenderer;
|
||||||
|
|
@ -36,8 +36,6 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
|
||||||
|
|
||||||
void inject(ModificationsSyncAdapter syncAdapter);
|
void inject(ModificationsSyncAdapter syncAdapter);
|
||||||
|
|
||||||
void inject(MediaWikiImageView mediaWikiImageView);
|
|
||||||
|
|
||||||
void inject(LoginActivity activity);
|
void inject(LoginActivity activity);
|
||||||
|
|
||||||
void inject(SettingsFragment fragment);
|
void inject(SettingsFragment fragment);
|
||||||
|
|
@ -53,6 +51,8 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
|
||||||
|
|
||||||
void inject(PicOfDayAppWidget picOfDayAppWidget);
|
void inject(PicOfDayAppWidget picOfDayAppWidget);
|
||||||
|
|
||||||
|
void inject(ContributionViewHolder viewHolder);
|
||||||
|
|
||||||
@Component.Builder
|
@Component.Builder
|
||||||
@SuppressWarnings({"WeakerAccess", "unused"})
|
@SuppressWarnings({"WeakerAccess", "unused"})
|
||||||
interface Builder {
|
interface Builder {
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,12 @@ import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
import com.pedrogomez.renderers.Renderer;
|
import com.pedrogomez.renderers.Renderer;
|
||||||
|
|
||||||
import butterknife.BindView;
|
import butterknife.BindView;
|
||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
|
||||||
import fr.free.nrw.commons.R;
|
import fr.free.nrw.commons.R;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -19,8 +19,7 @@ import fr.free.nrw.commons.R;
|
||||||
class SearchImagesRenderer extends Renderer<Media> {
|
class SearchImagesRenderer extends Renderer<Media> {
|
||||||
@BindView(R.id.categoryImageTitle) TextView tvImageName;
|
@BindView(R.id.categoryImageTitle) TextView tvImageName;
|
||||||
@BindView(R.id.categoryImageAuthor) TextView categoryImageAuthor;
|
@BindView(R.id.categoryImageAuthor) TextView categoryImageAuthor;
|
||||||
@BindView(R.id.categoryImageView)
|
@BindView(R.id.categoryImageView) SimpleDraweeView browseImage;
|
||||||
MediaWikiImageView browseImage;
|
|
||||||
|
|
||||||
private final ImageClickedListener listener;
|
private final ImageClickedListener listener;
|
||||||
|
|
||||||
|
|
@ -52,7 +51,7 @@ class SearchImagesRenderer extends Renderer<Media> {
|
||||||
public void render() {
|
public void render() {
|
||||||
Media item = getContent();
|
Media item = getContent();
|
||||||
tvImageName.setText(item.getDisplayTitle());
|
tvImageName.setText(item.getDisplayTitle());
|
||||||
browseImage.setMedia(item);
|
browseImage.setImageURI(item.getThumbUrl());
|
||||||
setAuthorView(item, categoryImageAuthor);
|
setAuthorView(item, categoryImageAuthor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,11 @@ import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.facebook.drawee.backends.pipeline.Fresco;
|
||||||
|
import com.facebook.drawee.interfaces.DraweeController;
|
||||||
|
import com.facebook.drawee.view.SimpleDraweeView;
|
||||||
|
import com.facebook.imagepipeline.request.ImageRequest;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.wikipedia.util.DateUtil;
|
import org.wikipedia.util.DateUtil;
|
||||||
import org.wikipedia.util.StringUtil;
|
import org.wikipedia.util.StringUtil;
|
||||||
|
|
@ -37,7 +42,6 @@ import butterknife.ButterKnife;
|
||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
import fr.free.nrw.commons.Media;
|
import fr.free.nrw.commons.Media;
|
||||||
import fr.free.nrw.commons.MediaDataExtractor;
|
import fr.free.nrw.commons.MediaDataExtractor;
|
||||||
import fr.free.nrw.commons.MediaWikiImageView;
|
|
||||||
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.category.CategoryDetailsActivity;
|
import fr.free.nrw.commons.category.CategoryDetailsActivity;
|
||||||
|
|
@ -52,12 +56,6 @@ import io.reactivex.Single;
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
import io.reactivex.disposables.Disposable;
|
import io.reactivex.disposables.Disposable;
|
||||||
import io.reactivex.schedulers.Schedulers;
|
import io.reactivex.schedulers.Schedulers;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
|
||||||
import javax.inject.Inject;
|
|
||||||
import javax.inject.Provider;
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
|
|
@ -100,7 +98,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
private int initialListTop = 0;
|
private int initialListTop = 0;
|
||||||
|
|
||||||
@BindView(R.id.mediaDetailImage)
|
@BindView(R.id.mediaDetailImage)
|
||||||
MediaWikiImageView image;
|
SimpleDraweeView image;
|
||||||
@BindView(R.id.mediaDetailSpacer)
|
@BindView(R.id.mediaDetailSpacer)
|
||||||
MediaDetailSpacer spacer;
|
MediaDetailSpacer spacer;
|
||||||
@BindView(R.id.mediaDetailTitle)
|
@BindView(R.id.mediaDetailTitle)
|
||||||
|
|
@ -265,7 +263,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
private void displayMediaDetails() {
|
private void displayMediaDetails() {
|
||||||
//Always load image from Internet to allow viewing the desc, license, and cats
|
//Always load image from Internet to allow viewing the desc, license, and cats
|
||||||
image.setMedia(media);
|
setupImageView();
|
||||||
title.setText(media.getDisplayTitle());
|
title.setText(media.getDisplayTitle());
|
||||||
desc.setHtmlText(media.getDescription());
|
desc.setHtmlText(media.getDescription());
|
||||||
license.setText(media.getLicense());
|
license.setText(media.getLicense());
|
||||||
|
|
@ -277,6 +275,20 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
compositeDisposable.add(disposable);
|
compositeDisposable.add(disposable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses two image sources.
|
||||||
|
* - low resolution thumbnail is shown initially
|
||||||
|
* - when the high resolution image is available, it replaces the low resolution image
|
||||||
|
*/
|
||||||
|
private void setupImageView() {
|
||||||
|
DraweeController controller = Fresco.newDraweeControllerBuilder()
|
||||||
|
.setLowResImageRequest(ImageRequest.fromUri(media.getThumbUrl()))
|
||||||
|
.setImageRequest(ImageRequest.fromUri(media.getImageUrl()))
|
||||||
|
.setOldController(image.getController())
|
||||||
|
.build();
|
||||||
|
image.setController(controller);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
if (layoutListener != null && getView() != null) {
|
if (layoutListener != null && getView() != null) {
|
||||||
|
|
@ -297,6 +309,7 @@ public class MediaDetailFragment extends CommonsDaggerSupportFragment {
|
||||||
|
|
||||||
private void setTextFields(Media media) {
|
private void setTextFields(Media media) {
|
||||||
this.media = media;
|
this.media = media;
|
||||||
|
setupImageView();
|
||||||
desc.setHtmlText(prettyDescription(media));
|
desc.setHtmlText(prettyDescription(media));
|
||||||
license.setText(prettyLicense(media));
|
license.setText(prettyLicense(media));
|
||||||
coordinates.setText(prettyCoordinates(media));
|
coordinates.setText(prettyCoordinates(media));
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ import timber.log.Timber;
|
||||||
* @author Addshore
|
* @author Addshore
|
||||||
*/
|
*/
|
||||||
public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
private static final String THUMB_SIZE = "640";
|
|
||||||
private AbstractHttpClient httpClient;
|
private AbstractHttpClient httpClient;
|
||||||
private CustomMwApi api;
|
private CustomMwApi api;
|
||||||
private CustomMwApi wikidataApi;
|
private CustomMwApi wikidataApi;
|
||||||
|
|
@ -293,18 +292,6 @@ public class ApacheHttpClientMediaWikiApi implements MediaWikiApi {
|
||||||
.getString("/api/edit/@result");
|
.getString("/api/edit/@result");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Single<String> findThumbnailByFilename(String filename) {
|
|
||||||
return Single.fromCallable(() -> api.action("query")
|
|
||||||
.param("format", "xml")
|
|
||||||
.param("prop", "imageinfo")
|
|
||||||
.param("iiprop", "url")
|
|
||||||
.param("iiurlwidth", THUMB_SIZE)
|
|
||||||
.param("titles", filename)
|
|
||||||
.get()
|
|
||||||
.getString("/api/query/pages/page/imageinfo/ii/@thumburl"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Single<String> parseWikicode(String source) {
|
public Single<String> parseWikicode(String source) {
|
||||||
return Single.fromCallable(() -> api.action("flow-parsoid-utils")
|
return Single.fromCallable(() -> api.action("flow-parsoid-utils")
|
||||||
|
|
|
||||||
|
|
@ -34,8 +34,6 @@ public interface MediaWikiApi {
|
||||||
|
|
||||||
Single<Boolean> pageExists(String pageName);
|
Single<Boolean> pageExists(String pageName);
|
||||||
|
|
||||||
Single<String> findThumbnailByFilename(String filename);
|
|
||||||
|
|
||||||
List<String> getSubCategoryList(String categoryName);
|
List<String> getSubCategoryList(String categoryName);
|
||||||
|
|
||||||
List<String> getParentCategoryList(String categoryName);
|
List<String> getParentCategoryList(String categoryName);
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ import timber.log.Timber;
|
||||||
*/
|
*/
|
||||||
@Singleton
|
@Singleton
|
||||||
public class OkHttpJsonApiClient {
|
public class OkHttpJsonApiClient {
|
||||||
|
private static final String THUMB_SIZE = "640";
|
||||||
|
|
||||||
public static final Type mapType = new TypeToken<Map<String, String>>() {
|
public static final Type mapType = new TypeToken<Map<String, String>>() {
|
||||||
}.getType();
|
}.getType();
|
||||||
|
|
@ -274,6 +275,7 @@ public class OkHttpJsonApiClient {
|
||||||
private HttpUrl.Builder appendMediaProperties(HttpUrl.Builder builder) {
|
private HttpUrl.Builder appendMediaProperties(HttpUrl.Builder builder) {
|
||||||
builder.addQueryParameter("prop", "imageinfo")
|
builder.addQueryParameter("prop", "imageinfo")
|
||||||
.addQueryParameter("iiprop", "url|extmetadata")
|
.addQueryParameter("iiprop", "url|extmetadata")
|
||||||
|
.addQueryParameter("iiurlwidth", THUMB_SIZE)
|
||||||
.addQueryParameter("iiextmetadatafilter", "DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl");
|
.addQueryParameter("iiextmetadatafilter", "DateTime|Categories|GPSLatitude|GPSLongitude|ImageDescription|DateTimeOriginal|Artist|LicenseShortName|LicenseUrl");
|
||||||
|
|
||||||
String language = Locale.getDefault().getLanguage();
|
String language = Locale.getDefault().getLanguage();
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
package fr.free.nrw.commons.utils;
|
|
||||||
|
|
||||||
import android.view.View;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This class includes utilities for contribution list fragment indicators, such as number of
|
|
||||||
* uploads, notification and nearby cards and their progress bar behind them.
|
|
||||||
*/
|
|
||||||
public class ContributionListViewUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets indicator and progress bar visibility according to 3 states, data is ready to display,
|
|
||||||
* data still loading, both should be invisible because media details fragment is visible
|
|
||||||
* @param indicator this can be numOfUploads text view, notification/nearby card views
|
|
||||||
* @param progressBar this is the progress bar behind indicators, displays they are loading
|
|
||||||
* @param isIndicatorReady is indicator fetched the information will be displayed
|
|
||||||
* @param isBothInvisible true if contribution list fragment is not active (ie. Media Details Fragment is active)
|
|
||||||
*/
|
|
||||||
public static void setIndicatorVisibility(View indicator, View progressBar, boolean isIndicatorReady, boolean isBothInvisible) {
|
|
||||||
if (indicator!=null && progressBar!=null) {
|
|
||||||
if (isIndicatorReady) {
|
|
||||||
// Indicator ready, display them
|
|
||||||
indicator.setVisibility(View.VISIBLE);
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
if (isBothInvisible) {
|
|
||||||
// Media Details Fragment is visible, hide both
|
|
||||||
indicator.setVisibility(View.GONE);
|
|
||||||
progressBar.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
// Indicator is not ready, still loading
|
|
||||||
indicator.setVisibility(View.GONE);
|
|
||||||
progressBar.setVisibility(View.VISIBLE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -82,7 +82,7 @@ public class PicOfDayAppWidget extends AppWidgetProvider {
|
||||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, viewIntent, 0);
|
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, viewIntent, 0);
|
||||||
views.setOnClickPendingIntent(R.id.appwidget_image, pendingIntent);
|
views.setOnClickPendingIntent(R.id.appwidget_image, pendingIntent);
|
||||||
|
|
||||||
loadImageFromUrl(response.getImageUrl(), context, views, appWidgetManager, appWidgetId);
|
loadImageFromUrl(response.getThumbUrl(), context, views, appWidgetManager, appWidgetId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
t -> Timber.e(t, "Fetching picture of the day failed")
|
t -> Timber.e(t, "Fetching picture of the day failed")
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@
|
||||||
android:contentDescription="@string/mediaimage_failed"
|
android:contentDescription="@string/mediaimage_failed"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<fr.free.nrw.commons.MediaWikiImageView
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:id="@+id/mediaDetailImage"
|
android:id="@+id/mediaDetailImage"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:actualImageScaleType="fitCenter"
|
app:actualImageScaleType="fitXY"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
android:layout_gravity="end|bottom"
|
android:layout_gravity="end|bottom"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<fr.free.nrw.commons.MediaWikiImageView
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:id="@+id/categoryImageView"
|
android:id="@+id/categoryImageView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="240dp"
|
android:layout_height="240dp"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:fresco="http://schemas.android.com/tools"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:padding="2dp"
|
android:padding="2dp"
|
||||||
android:paddingBottom="0dp"
|
android:paddingBottom="0dp">
|
||||||
>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/contributionSequenceNumber"
|
android:id="@+id/contributionSequenceNumber"
|
||||||
|
|
@ -18,11 +18,12 @@
|
||||||
android:layout_gravity="end|bottom"
|
android:layout_gravity="end|bottom"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<fr.free.nrw.commons.MediaWikiImageView
|
<com.facebook.drawee.view.SimpleDraweeView
|
||||||
android:id="@+id/contributionImage"
|
android:id="@+id/contributionImage"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="240dp"
|
android:layout_height="240dp"
|
||||||
app:actualImageScaleType="fitCenter"
|
app:actualImageScaleType="fitXY"
|
||||||
|
fresco:placeholderImage="@drawable/ic_image_black_24dp"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<declare-styleable name="MediaWikiImageView">
|
|
||||||
<attr name="isThumbnail" format="boolean" />
|
|
||||||
</declare-styleable>
|
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue