Skip to content

Commit efdb00b

Browse files
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 627ac91. * minor formatting changes, reverted 627ac91 * uh-oh missed semicolon, java * minor formatting changes
1 parent 021105a commit efdb00b

12 files changed

+108
-110
lines changed

app/src/main/java/fr/free/nrw/commons/Media.java

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public Media[] newArray(int i) {
4545
};
4646

4747
// Primary metadata fields
48+
@Nullable
4849
public Uri localUri;
4950
public String thumbUrl;
5051
public String imageUrl;

app/src/main/java/fr/free/nrw/commons/contributions/ContributionDao.java

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import androidx.room.Query;
99
import androidx.room.Transaction;
1010

11+
import androidx.room.Update;
12+
import io.reactivex.disposables.Disposable;
1113
import java.util.List;
1214

1315
import io.reactivex.Completable;
@@ -52,4 +54,7 @@ public void deleteAllAndSaveTransaction(List<Contribution> contributions){
5254

5355
@Query("Delete FROM contribution WHERE state = :state")
5456
public abstract void deleteAll(int state);
57+
58+
@Update
59+
public abstract Single<Integer> update(Contribution contribution);
5560
}

app/src/main/java/fr/free/nrw/commons/contributions/ContributionViewHolder.java

+35-61
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,30 @@
11
package fr.free.nrw.commons.contributions;
22

3+
import android.graphics.Color;
4+
import android.graphics.drawable.ColorDrawable;
5+
import android.net.Uri;
6+
import android.text.TextUtils;
37
import android.view.View;
48
import android.widget.LinearLayout;
59
import android.widget.ProgressBar;
610
import android.widget.TextView;
711

8-
import androidx.collection.LruCache;
12+
import androidx.annotation.Nullable;
913
import androidx.recyclerview.widget.RecyclerView;
1014

1115
import com.facebook.drawee.view.SimpleDraweeView;
1216

13-
import org.apache.commons.lang3.StringUtils;
17+
import com.facebook.imagepipeline.request.ImageRequest;
18+
import com.facebook.imagepipeline.request.ImageRequestBuilder;
1419

15-
import javax.inject.Inject;
16-
import javax.inject.Named;
1720

1821
import butterknife.BindView;
1922
import butterknife.ButterKnife;
2023
import butterknife.OnClick;
21-
import fr.free.nrw.commons.MediaDataExtractor;
2224
import fr.free.nrw.commons.R;
2325
import fr.free.nrw.commons.contributions.ContributionsListAdapter.Callback;
24-
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
25-
import fr.free.nrw.commons.di.ApplicationlessInjection;
26-
import fr.free.nrw.commons.upload.FileUtils;
27-
import io.reactivex.android.schedulers.AndroidSchedulers;
28-
import io.reactivex.disposables.CompositeDisposable;
29-
import io.reactivex.disposables.Disposable;
30-
import io.reactivex.schedulers.Schedulers;
31-
import timber.log.Timber;
26+
import java.util.HashMap;
27+
import java.util.Random;
3228

3329
public class ContributionViewHolder extends RecyclerView.ViewHolder {
3430

@@ -41,32 +37,32 @@ public class ContributionViewHolder extends RecyclerView.ViewHolder {
4137
@BindView(R.id.contributionProgress) ProgressBar progressView;
4238
@BindView(R.id.failed_image_options) LinearLayout failedImageOptions;
4339

44-
@Inject
45-
MediaDataExtractor mediaDataExtractor;
46-
47-
@Inject
48-
@Named("thumbnail-cache")
49-
LruCache<String, String> thumbnailCache;
50-
51-
private DisplayableContribution contribution;
52-
private CompositeDisposable compositeDisposable = new CompositeDisposable();
5340
private int position;
41+
private Contribution contribution;
42+
private Random random = new Random();
5443

5544
ContributionViewHolder(View parent, Callback callback) {
5645
super(parent);
5746
ButterKnife.bind(this, parent);
5847
this.callback=callback;
5948
}
6049

61-
public void init(int position, DisplayableContribution contribution) {
62-
ApplicationlessInjection.getInstance(itemView.getContext())
63-
.getCommonsApplicationComponent().inject(this);
64-
this.position=position;
50+
public void init(int position, Contribution contribution) {
6551
this.contribution = contribution;
66-
fetchAndDisplayThumbnail(contribution);
52+
this.position = position;
53+
imageView.getHierarchy().setPlaceholderImage(new ColorDrawable(
54+
Color.argb(100, random.nextInt(256), random.nextInt(256), random.nextInt(256))));
55+
String imageSource = chooseImageSource(contribution.thumbUrl, contribution.getLocalUri());
56+
if (!TextUtils.isEmpty(imageSource)) {
57+
final ImageRequest imageRequest =
58+
ImageRequestBuilder.newBuilderWithSource(Uri.parse(imageSource))
59+
.setProgressiveRenderingEnabled(true)
60+
.build();
61+
imageView.setImageRequest(imageRequest);
62+
}
6763
titleView.setText(contribution.getDisplayTitle());
6864

69-
seqNumView.setText(String.valueOf(contribution.getPosition() + 1));
65+
seqNumView.setText(String.valueOf(position + 1));
7066
seqNumView.setVisibility(View.VISIBLE);
7167

7268
switch (contribution.getState()) {
@@ -104,40 +100,18 @@ public void init(int position, DisplayableContribution contribution) {
104100
}
105101

106102
/**
107-
* This method fetches the thumbnail url from file name
108-
* If the thumbnail url is present in cache, then it is used otherwise API call is made to fetch the thumbnail
109-
* This can be removed once #2904 is in place and contribution contains all metadata beforehand
110-
* @param contribution
103+
* Returns the image source for the image view, first preference is given to thumbUrl if that is
104+
* null, moves to local uri and if both are null return null
105+
*
106+
* @param thumbUrl
107+
* @param localUri
108+
* @return
111109
*/
112-
private void fetchAndDisplayThumbnail(DisplayableContribution contribution) {
113-
String keyForLRUCache = contribution.getFilename();
114-
String cacheUrl = thumbnailCache.get(keyForLRUCache);
115-
if (!StringUtils.isBlank(cacheUrl)) {
116-
imageView.setImageURI(cacheUrl);
117-
return;
118-
}
119-
120-
imageView.setBackground(null);
121-
if ((contribution.getState() != Contribution.STATE_COMPLETED) && FileUtils.fileExists(
122-
contribution.getLocalUri())) {
123-
imageView.setImageURI(contribution.getLocalUri());
124-
} else {
125-
Timber.d("Fetching thumbnail for %s", contribution.getFilename());
126-
Disposable disposable = mediaDataExtractor
127-
.getMediaFromFileName(contribution.getFilename())
128-
.subscribeOn(Schedulers.io())
129-
.observeOn(AndroidSchedulers.mainThread())
130-
.subscribe(media -> {
131-
thumbnailCache.put(keyForLRUCache, media.getThumbUrl());
132-
imageView.setImageURI(media.getThumbUrl());
133-
});
134-
compositeDisposable.add(disposable);
135-
}
136-
137-
}
138-
139-
public void clear() {
140-
compositeDisposable.clear();
110+
@Nullable
111+
private String chooseImageSource(String thumbUrl, Uri localUri) {
112+
return !TextUtils.isEmpty(thumbUrl) ? thumbUrl :
113+
localUri != null ? localUri.toString() :
114+
null;
141115
}
142116

143117
/**

app/src/main/java/fr/free/nrw/commons/contributions/ContributionsContract.java

+5
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,15 @@ public interface View {
2626
}
2727

2828
public interface UserActionListener extends BasePresenter<ContributionsContract.View> {
29+
2930
Contribution getContributionsWithTitle(String uri);
3031

3132
void deleteUpload(Contribution contribution);
3233

3334
Media getItemAtPosition(int i);
35+
36+
void updateContribution(Contribution contribution);
37+
38+
void fetchMediaDetails(Contribution contribution);
3439
}
3540
}

app/src/main/java/fr/free/nrw/commons/contributions/ContributionsFragment.java

+8
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
2121
import androidx.fragment.app.FragmentTransaction;
2222

23+
import fr.free.nrw.commons.MediaDataExtractor;
24+
import io.reactivex.disposables.Disposable;
2325
import java.util.List;
2426

2527
import javax.inject.Inject;
@@ -216,6 +218,12 @@ public void openMediaDetail(int position) {
216218
public Contribution getContributionForPosition(int position) {
217219
return (Contribution) contributionsPresenter.getItemAtPosition(position);
218220
}
221+
222+
@Override
223+
public void fetchMediaUriFor(Contribution contribution) {
224+
Timber.d("Fetching thumbnail for %s", contribution.filename);
225+
contributionsPresenter.fetchMediaDetails(contribution);
226+
}
219227
});
220228

221229
if(null==mediaDetailPagerFragment){

app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListAdapter.java

+20-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package fr.free.nrw.commons.contributions;
22

3+
import android.os.Handler;
4+
import android.os.Looper;
5+
import android.text.TextUtils;
36
import android.view.LayoutInflater;
47
import android.view.ViewGroup;
58

@@ -10,7 +13,6 @@
1013
import java.util.List;
1114

1215
import fr.free.nrw.commons.R;
13-
import fr.free.nrw.commons.contributions.model.DisplayableContribution;
1416

1517
/**
1618
* Represents The View Adapter for the List of Contributions
@@ -22,7 +24,7 @@ public class ContributionsListAdapter extends RecyclerView.Adapter<ContributionV
2224

2325
public ContributionsListAdapter(Callback callback) {
2426
this.callback = callback;
25-
contributions=new ArrayList<>();
27+
contributions = new ArrayList<>();
2628
}
2729

2830
/**
@@ -41,22 +43,27 @@ public ContributionViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int
4143
@Override
4244
public void onBindViewHolder(@NonNull ContributionViewHolder holder, int position) {
4345
final Contribution contribution = contributions.get(position);
44-
DisplayableContribution displayableContribution = new DisplayableContribution(contribution,
45-
position);
46-
holder.init(position, displayableContribution);
46+
if (TextUtils.isEmpty(contribution.getThumbUrl())
47+
&& contribution.getState() == Contribution.STATE_COMPLETED) {
48+
callback.fetchMediaUriFor(contribution);
49+
}
50+
51+
holder.init(position, contribution);
4752
}
4853

4954
@Override
5055
public int getItemCount() {
5156
return contributions.size();
5257
}
5358

54-
public void setContributions(List<Contribution> contributionList) {
55-
if(null!=contributionList) {
56-
this.contributions.clear();
57-
this.contributions.addAll(contributionList);
58-
notifyDataSetChanged();
59-
}
59+
public void setContributions(@NonNull List<Contribution> contributionList) {
60+
contributions = contributionList;
61+
notifyDataSetChanged();
62+
}
63+
64+
@Override
65+
public long getItemId(int position) {
66+
return contributions.get(position)._id;
6067
}
6168

6269
public interface Callback {
@@ -68,5 +75,7 @@ public interface Callback {
6875
void openMediaDetail(int contribution);
6976

7077
Contribution getContributionForPosition(int position);
78+
79+
void fetchMediaUriFor(Contribution contribution);
7180
}
7281
}

app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public void setCallback(Callback callback) {
9090

9191
private void initAdapter() {
9292
adapter = new ContributionsListAdapter(callback);
93+
adapter.setHasStableIds(true);
9394
}
9495

9596
@Override

app/src/main/java/fr/free/nrw/commons/contributions/ContributionsLocalDataSource.java

+4
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,8 @@ public Completable saveContributions(List<Contribution> contributions) {
7474
public void set(String key, long value) {
7575
defaultKVStore.putLong(key,value);
7676
}
77+
78+
public Single<Integer> updateContribution(Contribution contribution) {
79+
return contributionDao.update(contribution);
80+
}
7781
}

app/src/main/java/fr/free/nrw/commons/contributions/ContributionsPresenter.java

+25
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import androidx.lifecycle.LiveData;
1313
import androidx.lifecycle.Observer;
1414

15+
import fr.free.nrw.commons.MediaDataExtractor;
1516
import java.util.ArrayList;
1617
import java.util.Collections;
1718
import java.util.List;
@@ -62,6 +63,10 @@ public class ContributionsPresenter implements UserActionListener {
6263

6364
@Inject
6465
SessionManager sessionManager;
66+
67+
@Inject
68+
MediaDataExtractor mediaDataExtractor;
69+
6570
private LifecycleOwner lifeCycleOwner;
6671

6772
@Inject
@@ -180,4 +185,24 @@ public Media getItemAtPosition(int i) {
180185
}
181186
return contributionList.get(i);
182187
}
188+
189+
@Override
190+
public void updateContribution(Contribution contribution) {
191+
compositeDisposable.add(repository
192+
.updateContribution(contribution)
193+
.subscribeOn(ioThreadScheduler)
194+
.subscribe());
195+
}
196+
197+
@Override
198+
public void fetchMediaDetails(Contribution contribution) {
199+
compositeDisposable.add(mediaDataExtractor
200+
.getMediaFromFileName(contribution.filename)
201+
.subscribeOn(Schedulers.io())
202+
.observeOn(AndroidSchedulers.mainThread())
203+
.subscribe(media -> {
204+
contribution.thumbUrl=media.thumbUrl;
205+
updateContribution(contribution);
206+
}));
207+
}
183208
}

app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRepository.java

+4
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,8 @@ public void set(String key, long value) {
6161
public long getLong(String key) {
6262
return localDataSource.getLong(key);
6363
}
64+
65+
public Single<Integer> updateContribution(Contribution contribution) {
66+
return localDataSource.updateContribution(contribution);
67+
}
6468
}

app/src/main/java/fr/free/nrw/commons/contributions/model/DisplayableContribution.java

-36
This file was deleted.

app/src/main/java/fr/free/nrw/commons/di/CommonsApplicationComponent.java

-2
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ public interface CommonsApplicationComponent extends AndroidInjector<Application
5353

5454
void inject(PicOfDayAppWidget picOfDayAppWidget);
5555

56-
void inject(ContributionViewHolder viewHolder);
57-
5856
Gson gson();
5957

6058
@Component.Builder

0 commit comments

Comments
 (0)