Fixes #3359 Duplicate Photos in Contributions Page (#3515)

* Fixes #3359
* Cache thumb url & imageUrl in local db
* Use Fresco's ImageRequest to show images in ContributionViewHolder[this was the issue, we should have always used this to show the image]
* Deleted DisplayableContribution (not needed anymore)
* Exposed abstract function in ContributionDao to updateContribution

* * Make position private in ContributionViewHolder
* Remove MediaDataExtractor from ContributionsFragment

* * Show placeholder image for Contributions while the image loads
* setHasStableId's ContributionsAdapter

* make Random variable private in ContributionViewHolder

* replace local variable-if-with ternary operator in ContributionViewHolder

* Fix indentation/formatting of ternary operator in ContributionViewHolder

* I might revert this commit[I have reasons]

* Create in-memory drawables in CVH's onBind, caches are bad, add mental overhead

* Revert "I might revert this commit[I have reasons]"

This reverts commit 627ac91517.

* minor formatting changes, reverted 627ac91517

* uh-oh missed semicolon, java

* minor formatting changes
This commit is contained in:
Ashish Kumar 2020-03-18 16:17:51 +05:30 committed by GitHub
parent 021105ac4d
commit efdb00b5ee
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 108 additions and 110 deletions

View file

@ -45,6 +45,7 @@ public class Media implements Parcelable {
};
// Primary metadata fields
@Nullable
public Uri localUri;
public String thumbUrl;
public String imageUrl;

View file

@ -8,6 +8,8 @@ import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import androidx.room.Transaction;
import androidx.room.Update;
import io.reactivex.disposables.Disposable;
import java.util.List;
import io.reactivex.Completable;
@ -52,4 +54,7 @@ public abstract class ContributionDao {
@Query("Delete FROM contribution WHERE state = :state")
public abstract void deleteAll(int state);
@Update
public abstract Single<Integer> update(Contribution contribution);
}

View file

@ -1,34 +1,30 @@
package fr.free.nrw.commons.contributions;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.text.TextUtils;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.collection.LruCache;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.view.SimpleDraweeView;
import org.apache.commons.lang3.StringUtils;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import javax.inject.Inject;
import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import fr.free.nrw.commons.MediaDataExtractor;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
import fr.free.nrw.commons.di.ApplicationlessInjection;
import fr.free.nrw.commons.upload.FileUtils;
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;
import java.util.HashMap;
import java.util.Random;
public class ContributionViewHolder extends RecyclerView.ViewHolder {
@ -41,16 +37,9 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.contributionProgress) ProgressBar progressView;
@BindView(R.id.failed_image_options) LinearLayout failedImageOptions;
@Inject
MediaDataExtractor mediaDataExtractor;
@Inject
@Named("thumbnail-cache")
LruCache<String, String> thumbnailCache;
private DisplayableContribution contribution;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
private int position;
private Contribution contribution;
private Random random = new Random();
ContributionViewHolder(View parent, Callback callback) {
super(parent);
@ -58,15 +47,22 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
this.callback=callback;
}
public void init(int position, DisplayableContribution contribution) {
ApplicationlessInjection.getInstance(itemView.getContext())
.getCommonsApplicationComponent().inject(this);
this.position=position;
public void init(int position, Contribution contribution) {
this.contribution = contribution;
fetchAndDisplayThumbnail(contribution);
this.position = position;
imageView.getHierarchy().setPlaceholderImage(new ColorDrawable(
Color.argb(100, random.nextInt(256), random.nextInt(256), random.nextInt(256))));
String imageSource = chooseImageSource(contribution.thumbUrl, contribution.getLocalUri());
if (!TextUtils.isEmpty(imageSource)) {
final ImageRequest imageRequest =
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
.setProgressiveRenderingEnabled(true)
.build();
imageView.setImageRequest(imageRequest);
}
titleView.setText(contribution.getDisplayTitle());
seqNumView.setText(String.valueOf(contribution.getPosition() + 1));
seqNumView.setText(String.valueOf(position + 1));
seqNumView.setVisibility(View.VISIBLE);
switch (contribution.getState()) {
@ -104,40 +100,18 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
}
/**
* This method fetches the thumbnail url from file name
* If the thumbnail url is present in cache, then it is used otherwise API call is made to fetch the thumbnail
* This can be removed once #2904 is in place and contribution contains all metadata beforehand
* @param contribution
* Returns the image source for the image view, first preference is given to thumbUrl if that is
* null, moves to local uri and if both are null return null
*
* @param thumbUrl
* @param localUri
* @return
*/
private void fetchAndDisplayThumbnail(DisplayableContribution contribution) {
String keyForLRUCache = contribution.getFilename();
String cacheUrl = thumbnailCache.get(keyForLRUCache);
if (!StringUtils.isBlank(cacheUrl)) {
imageView.setImageURI(cacheUrl);
return;
}
imageView.setBackground(null);
if ((contribution.getState() != Contribution.STATE_COMPLETED) && FileUtils.fileExists(
contribution.getLocalUri())) {
imageView.setImageURI(contribution.getLocalUri());
} else {
Timber.d("Fetching thumbnail for %s", contribution.getFilename());
Disposable disposable = mediaDataExtractor
.getMediaFromFileName(contribution.getFilename())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(media -> {
thumbnailCache.put(keyForLRUCache, media.getThumbUrl());
imageView.setImageURI(media.getThumbUrl());
});
compositeDisposable.add(disposable);
}
}
public void clear() {
compositeDisposable.clear();
@Nullable
private String chooseImageSource(String thumbUrl, Uri localUri) {
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
localUri != null ? localUri.toString() :
null;
}
/**

View file

@ -26,10 +26,15 @@ public class ContributionsContract {
}
public interface UserActionListener extends BasePresenter<ContributionsContract.View> {
Contribution getContributionsWithTitle(String uri);
void deleteUpload(Contribution contribution);
Media getItemAtPosition(int i);
void updateContribution(Contribution contribution);
void fetchMediaDetails(Contribution contribution);
}
}

View file

@ -20,6 +20,8 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
import androidx.fragment.app.FragmentTransaction;
import fr.free.nrw.commons.MediaDataExtractor;
import io.reactivex.disposables.Disposable;
import java.util.List;
import javax.inject.Inject;
@ -216,6 +218,12 @@ public class ContributionsFragment
public Contribution getContributionForPosition(int position) {
return (Contribution) contributionsPresenter.getItemAtPosition(position);
}
@Override
public void fetchMediaUriFor(Contribution contribution) {
Timber.d("Fetching thumbnail for %s", contribution.filename);
contributionsPresenter.fetchMediaDetails(contribution);
}
});
if(null==mediaDetailPagerFragment){

View file

@ -1,5 +1,8 @@
package fr.free.nrw.commons.contributions;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@ -10,7 +13,6 @@ import java.util.ArrayList;
import java.util.List;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
/**
* Represents The View Adapter for the List of Contributions
@ -41,9 +43,12 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
@Override
public void onBindViewHolder(@NonNull ContributionViewHolder holder, int position) {
final Contribution contribution = contributions.get(position);
DisplayableContribution displayableContribution = new DisplayableContribution(contribution,
position);
holder.init(position, displayableContribution);
if (TextUtils.isEmpty(contribution.getThumbUrl())
&& contribution.getState() == Contribution.STATE_COMPLETED) {
callback.fetchMediaUriFor(contribution);
}
holder.init(position, contribution);
}
@Override
@ -51,12 +56,14 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
return contributions.size();
}
public void setContributions(List<Contribution> contributionList) {
if(null!=contributionList) {
this.contributions.clear();
this.contributions.addAll(contributionList);
public void setContributions(@NonNull List<Contribution> contributionList) {
contributions = contributionList;
notifyDataSetChanged();
}
@Override
public long getItemId(int position) {
return contributions.get(position)._id;
}
public interface Callback {
@ -68,5 +75,7 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
void openMediaDetail(int contribution);
Contribution getContributionForPosition(int position);
void fetchMediaUriFor(Contribution contribution);
}
}

View file

@ -90,6 +90,7 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment {
private void initAdapter() {
adapter = new ContributionsListAdapter(callback);
adapter.setHasStableIds(true);
}
@Override

View file

@ -74,4 +74,8 @@ class ContributionsLocalDataSource {
public void set(String key, long value) {
defaultKVStore.putLong(key,value);
}
public Single<Integer> updateContribution(Contribution contribution) {
return contributionDao.update(contribution);
}
}

View file

@ -12,6 +12,7 @@ import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import fr.free.nrw.commons.MediaDataExtractor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -62,6 +63,10 @@ public class ContributionsPresenter implements UserActionListener {
@Inject
SessionManager sessionManager;
@Inject
MediaDataExtractor mediaDataExtractor;
private LifecycleOwner lifeCycleOwner;
@Inject
@ -180,4 +185,24 @@ public class ContributionsPresenter implements UserActionListener {
}
return contributionList.get(i);
}
@Override
public void updateContribution(Contribution contribution) {
compositeDisposable.add(repository
.updateContribution(contribution)
.subscribeOn(ioThreadScheduler)
.subscribe());
}
@Override
public void fetchMediaDetails(Contribution contribution) {
compositeDisposable.add(mediaDataExtractor
.getMediaFromFileName(contribution.filename)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(media -> {
contribution.thumbUrl=media.thumbUrl;
updateContribution(contribution);
}));
}
}

View file

@ -61,4 +61,8 @@ public class ContributionsRepository {
public long getLong(String key) {
return localDataSource.getLong(key);
}
public Single<Integer> updateContribution(Contribution contribution) {
return localDataSource.updateContribution(contribution);
}
}

View file

@ -1,36 +0,0 @@
package fr.free.nrw.commons.contributions.model;
import fr.free.nrw.commons.contributions.Contribution;
public class DisplayableContribution extends Contribution {
private int position;
public DisplayableContribution(Contribution contribution,
int position) {
super(contribution.getContentUri(),
contribution.getFilename(),
contribution.getLocalUri(),
contribution.getImageUrl(),
contribution.getDateCreated(),
contribution.getState(),
contribution.getDataLength(),
contribution.getDateUploaded(),
contribution.getTransferred(),
contribution.getSource(),
contribution.getDescription(),
contribution.getCreator(),
contribution.getMultiple(),
contribution.getWidth(),
contribution.getHeight(),
contribution.getLicense());
this._id=contribution._id;
this.position = position;
}
public int getPosition() {
return position;
}
public void setPosition(int position) {
this.position = position;
}
}

View file

@ -53,8 +53,6 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
void inject(PicOfDayAppWidget picOfDayAppWidget);
void inject(ContributionViewHolder viewHolder);
Gson gson();
@Component.Builder