diff --git a/app/build.gradle b/app/build.gradle
index 08f322c055..dd64aa19b5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -42,6 +42,7 @@ dependencies {
implementation 'com.dinuscxj:circleprogressbar:1.1.1'
implementation 'com.karumi:dexter:5.0.0'
implementation "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION"
+ implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
kapt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION"
implementation "com.hannesdorfmann:adapterdelegates4-kotlin-dsl-layoutcontainer:$ADAPTER_DELEGATES_VERSION"
@@ -210,8 +211,8 @@ android {
configurations.all {
resolutionStrategy.force 'androidx.annotation:annotation:1.0.2'
+ exclude module: 'okhttp-ws'
}
-
flavorDimensions 'tier'
productFlavors {
prod {
diff --git a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java
index d07667b48b..e7e12ddeff 100644
--- a/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java
+++ b/app/src/main/java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java
@@ -3,14 +3,14 @@
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.google.gson.Gson;
-import fr.free.nrw.commons.profile.achievements.FeaturedImages;
-import fr.free.nrw.commons.profile.achievements.FeedbackResponse;
import fr.free.nrw.commons.campaigns.CampaignResponseDTO;
import fr.free.nrw.commons.explore.depictions.DepictsClient;
import fr.free.nrw.commons.location.LatLng;
import fr.free.nrw.commons.nearby.Place;
import fr.free.nrw.commons.nearby.model.NearbyResponse;
import fr.free.nrw.commons.nearby.model.NearbyResultItem;
+import fr.free.nrw.commons.profile.achievements.FeaturedImages;
+import fr.free.nrw.commons.profile.achievements.FeedbackResponse;
import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse;
import fr.free.nrw.commons.upload.FileUtils;
import fr.free.nrw.commons.upload.structure.depictions.DepictedItem;
@@ -65,32 +65,32 @@ public OkHttpJsonApiClient(OkHttpClient okHttpClient,
}
@NonNull
- public Single getLeaderboard(String userName, String duration, String category, String limit, String offset) {
+ public Observable getLeaderboard(String userName, String duration, String category, String limit, String offset) {
final String fetchLeaderboardUrlTemplate = wikiMediaTestToolforgeUrl
+ "/leaderboard.py";
- return Single.fromCallable(() -> {
- String url = String.format(Locale.ENGLISH,
- fetchLeaderboardUrlTemplate,
- userName,
- duration,
- category,
- limit,
- offset);
- HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
- urlBuilder.addQueryParameter("user", userName);
- urlBuilder.addQueryParameter("duration", duration);
- urlBuilder.addQueryParameter("category", category);
- urlBuilder.addQueryParameter("limit", limit);
- urlBuilder.addQueryParameter("offset", offset);
- Timber.i("Url %s", urlBuilder.toString());
- Request request = new Request.Builder()
- .url(urlBuilder.toString())
- .build();
+ String url = String.format(Locale.ENGLISH,
+ fetchLeaderboardUrlTemplate,
+ userName,
+ duration,
+ category,
+ limit,
+ offset);
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
+ urlBuilder.addQueryParameter("user", userName);
+ urlBuilder.addQueryParameter("duration", duration);
+ urlBuilder.addQueryParameter("category", category);
+ urlBuilder.addQueryParameter("limit", limit);
+ urlBuilder.addQueryParameter("offset", offset);
+ Timber.i("Url %s", urlBuilder.toString());
+ Request request = new Request.Builder()
+ .url(urlBuilder.toString())
+ .build();
+ return Observable.fromCallable(() -> {
Response response = okHttpClient.newCall(request).execute();
if (response != null && response.body() != null && response.isSuccessful()) {
String json = response.body().string();
if (json == null) {
- return null;
+ return new LeaderboardResponse();
}
Timber.d("Response for leaderboard is %s", json);
try {
@@ -99,7 +99,7 @@ public Single getLeaderboard(String userName, String durati
return new LeaderboardResponse();
}
}
- return null;
+ return new LeaderboardResponse();
});
}
@@ -188,7 +188,6 @@ public Single getAchievements(String userName) {
userName);
HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
urlBuilder.addQueryParameter("user", userName);
- Timber.i("Url %s", urlBuilder.toString());
Request request = new Request.Builder()
.url(urlBuilder.toString())
.build();
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/DataSourceClass.java b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/DataSourceClass.java
new file mode 100644
index 0000000000..ac522cfd7d
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/DataSourceClass.java
@@ -0,0 +1,88 @@
+package fr.free.nrw.commons.profile.leaderboard;
+
+import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LOADED;
+import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LOADING;
+import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.PAGE_SIZE;
+import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.START_OFFSET;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.MutableLiveData;
+import androidx.paging.PageKeyedDataSource;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
+import io.reactivex.disposables.CompositeDisposable;
+import java.util.Objects;
+import timber.log.Timber;
+
+public class DataSourceClass extends PageKeyedDataSource {
+
+ private OkHttpJsonApiClient okHttpJsonApiClient;
+ private SessionManager sessionManager;
+ private MutableLiveData progressLiveStatus;
+ private CompositeDisposable compositeDisposable = new CompositeDisposable();
+
+ public DataSourceClass(OkHttpJsonApiClient okHttpJsonApiClient,SessionManager sessionManager) {
+ this.okHttpJsonApiClient = okHttpJsonApiClient;
+ this.sessionManager = sessionManager;
+ progressLiveStatus = new MutableLiveData<>();
+ }
+
+
+ public MutableLiveData getProgressLiveStatus() {
+ return progressLiveStatus;
+ }
+
+ @Override
+ public void loadInitial(@NonNull LoadInitialParams params,
+ @NonNull LoadInitialCallback callback) {
+
+ compositeDisposable.add(okHttpJsonApiClient
+ .getLeaderboard(Objects.requireNonNull(sessionManager.getCurrentAccount()).name,
+ "all_time", "upload", String.valueOf(PAGE_SIZE), String.valueOf(START_OFFSET))
+ .doOnSubscribe(disposable -> {
+ compositeDisposable.add(disposable);
+ progressLiveStatus.postValue(LOADING);
+ }).subscribe(
+ response -> {
+ if (response != null && response.getStatus() == 200) {
+ progressLiveStatus.postValue(LOADED);
+ callback.onResult(response.getLeaderboardList(), null, response.getLimit());
+ }
+ },
+ t -> {
+ Timber.e(t, "Fetching leaderboard statistics failed");
+ progressLiveStatus.postValue(LOADING);
+ }
+ ));
+
+ }
+
+ @Override
+ public void loadBefore(@NonNull LoadParams params,
+ @NonNull LoadCallback callback) {
+
+ }
+
+ @Override
+ public void loadAfter(@NonNull LoadParams params,
+ @NonNull LoadCallback callback) {
+ compositeDisposable.add(okHttpJsonApiClient
+ .getLeaderboard(Objects.requireNonNull(sessionManager.getCurrentAccount()).name,
+ "all_time", "upload", String.valueOf(PAGE_SIZE), String.valueOf(params.key))
+ .doOnSubscribe(disposable -> {
+ compositeDisposable.add(disposable);
+ progressLiveStatus.postValue(LOADING);
+ }).subscribe(
+ response -> {
+ if (response != null && response.getStatus() == 200) {
+ progressLiveStatus.postValue(LOADED);
+ callback.onResult(response.getLeaderboardList(), params.key + PAGE_SIZE);
+ }
+ },
+ t -> {
+ Timber.e(t, "Fetching leaderboard statistics failed");
+ progressLiveStatus.postValue(LOADING);
+ }
+ ));
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/DataSourceFactory.java b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/DataSourceFactory.java
new file mode 100644
index 0000000000..3b5428a2a4
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/DataSourceFactory.java
@@ -0,0 +1,34 @@
+package fr.free.nrw.commons.profile.leaderboard;
+
+import androidx.lifecycle.MutableLiveData;
+import androidx.paging.DataSource;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
+import io.reactivex.disposables.CompositeDisposable;
+
+public class DataSourceFactory extends DataSource.Factory {
+
+ private MutableLiveData liveData;
+ private OkHttpJsonApiClient okHttpJsonApiClient;
+ private CompositeDisposable compositeDisposable;
+ private SessionManager sessionManager;
+
+ public DataSourceFactory(OkHttpJsonApiClient okHttpJsonApiClient, CompositeDisposable compositeDisposable,
+ SessionManager sessionManager) {
+ this.okHttpJsonApiClient = okHttpJsonApiClient;
+ this.compositeDisposable = compositeDisposable;
+ this.sessionManager = sessionManager;
+ liveData = new MutableLiveData<>();
+ }
+
+ public MutableLiveData getMutableLiveData() {
+ return liveData;
+ }
+
+ @Override
+ public DataSource create() {
+ DataSourceClass dataSourceClass = new DataSourceClass(okHttpJsonApiClient, sessionManager);
+ liveData.postValue(dataSourceClass);
+ return dataSourceClass;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardConstants.java b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardConstants.java
new file mode 100644
index 0000000000..79b53ef98b
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardConstants.java
@@ -0,0 +1,15 @@
+package fr.free.nrw.commons.profile.leaderboard;
+
+public class LeaderboardConstants {
+
+ public static final int PAGE_SIZE = 10;
+
+ public static final int START_OFFSET = 0;
+
+ public static final String AVATAR_SOURCE_URL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/%s/1024px-%s.png";
+
+ public final static String LOADING = "Loading";
+
+ public final static String LOADED = "Loaded";
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardFragment.java b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardFragment.java
index cae28b4e71..0cd45df85e 100644
--- a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardFragment.java
+++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardFragment.java
@@ -1,18 +1,20 @@
package fr.free.nrw.commons.profile.leaderboard;
+import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LOADED;
+import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.LOADING;
+
import android.accounts.Account;
-import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
-import android.widget.TextView;
+import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.MergeAdapter;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
-import com.facebook.drawee.view.SimpleDraweeView;
import fr.free.nrw.commons.R;
import fr.free.nrw.commons.auth.SessionManager;
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment;
@@ -21,25 +23,12 @@
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
-import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
import timber.log.Timber;
public class LeaderboardFragment extends CommonsDaggerSupportFragment {
- @BindView(R.id.avatar)
- SimpleDraweeView avatar;
-
- @BindView(R.id.username)
- TextView username;
-
- @BindView(R.id.rank)
- TextView rank;
-
- @BindView(R.id.count)
- TextView count;
-
@BindView(R.id.leaderboard_list)
RecyclerView leaderboardListRecyclerView;
@@ -52,7 +41,10 @@ public class LeaderboardFragment extends CommonsDaggerSupportFragment {
@Inject
OkHttpJsonApiClient okHttpJsonApiClient;
- private String avatarSourceURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/%s/1024px-%s.png";
+ @Inject
+ ViewModelFactory viewModelFactory;
+
+ LeaderboardListViewModel viewModel;
private CompositeDisposable compositeDisposable = new CompositeDisposable();
@@ -67,12 +59,12 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa
}
/**
- * To call the API to get results in form Single
- * which then calls parseJson when results are fetched
+ * To call the API to get results
+ * which then sets the views using setLeaderboardUser method
*/
private void setLeaderboard() {
if (checkAccount()) {
- try{
+ try {
compositeDisposable.add(okHttpJsonApiClient
.getLeaderboard(Objects.requireNonNull(sessionManager.getCurrentAccount()).name,
"all_time", "upload", null, null)
@@ -81,8 +73,7 @@ private void setLeaderboard() {
.subscribe(
response -> {
if (response != null && response.getStatus() == 200) {
- setLeaderboardUser(response);
- setLeaderboardList(response.getLeaderboardList());
+ setViews(response);
}
},
t -> {
@@ -101,20 +92,23 @@ private void setLeaderboard() {
* Set the views
* @param response Leaderboard Response Object
*/
- private void setLeaderboardUser(LeaderboardResponse response) {
- hideProgressBar();
- avatar.setImageURI(
- Uri.parse(String.format(avatarSourceURL, response.getAvatar(), response.getAvatar())));
- username.setText(response.getUsername());
- rank.setText(String.format("%s %d", getString(R.string.rank_prefix), response.getRank()));
- count.setText(String.format("%s %d", getString(R.string.count_prefix), response.getCategoryCount()));
- }
-
- private void setLeaderboardList(List leaderboardList) {
- LeaderboardListAdapter leaderboardListAdapter = new LeaderboardListAdapter(leaderboardList);
+ private void setViews(LeaderboardResponse response) {
+ viewModel = new ViewModelProvider(this, viewModelFactory).get(LeaderboardListViewModel.class);
+ LeaderboardListAdapter leaderboardListAdapter = new LeaderboardListAdapter();
+ UserDetailAdapter userDetailAdapter= new UserDetailAdapter(response);
+ MergeAdapter mergeAdapter = new MergeAdapter(userDetailAdapter, leaderboardListAdapter);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext());
leaderboardListRecyclerView.setLayoutManager(linearLayoutManager);
- leaderboardListRecyclerView.setAdapter(leaderboardListAdapter);
+ leaderboardListRecyclerView.setAdapter(mergeAdapter);
+
+ viewModel.getListLiveData().observe(getViewLifecycleOwner(), leaderboardListAdapter::submitList);
+ viewModel.getProgressLoadStatus().observe(getViewLifecycleOwner(), status -> {
+ if (Objects.requireNonNull(status).equalsIgnoreCase(LOADING)) {
+ showProgressBar();
+ } else if (status.equalsIgnoreCase(LOADED)) {
+ hideProgressBar();
+ }
+ });
}
/**
@@ -123,20 +117,23 @@ private void setLeaderboardList(List leaderboardList) {
private void hideProgressBar() {
if (progressBar != null) {
progressBar.setVisibility(View.GONE);
- avatar.setVisibility(View.VISIBLE);
- username.setVisibility(View.VISIBLE);
- rank.setVisibility(View.VISIBLE);
leaderboardListRecyclerView.setVisibility(View.VISIBLE);
}
}
+ /**
+ * to show progressbar
+ */
+ private void showProgressBar() {
+ if (progressBar != null) {
+ progressBar.setVisibility(View.VISIBLE);
+ }
+ }
+
/**
* used to hide the layouts while fetching results from api
*/
private void hideLayouts(){
- avatar.setVisibility(View.INVISIBLE);
- username.setVisibility(View.INVISIBLE);
- rank.setVisibility(View.INVISIBLE);
leaderboardListRecyclerView.setVisibility(View.INVISIBLE);
}
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardList.java b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardList.java
index d6b86a22b9..3957055684 100644
--- a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardList.java
+++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardList.java
@@ -1,5 +1,8 @@
package fr.free.nrw.commons.profile.leaderboard;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.DiffUtil;
+import androidx.recyclerview.widget.DiffUtil.ItemCallback;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
@@ -8,12 +11,15 @@ public class LeaderboardList {
@SerializedName("username")
@Expose
private String username;
+
@SerializedName("category_count")
@Expose
private Integer categoryCount;
+
@SerializedName("avatar")
@Expose
private String avatar;
+
@SerializedName("rank")
@Expose
private Integer rank;
@@ -50,4 +56,29 @@ public void setRank(Integer rank) {
this.rank = rank;
}
+
+ public static DiffUtil.ItemCallback DIFF_CALLBACK =
+ new ItemCallback() {
+ @Override
+ public boolean areItemsTheSame(@NonNull LeaderboardList oldItem,
+ @NonNull LeaderboardList newItem) {
+ return newItem == oldItem;
+ }
+
+ @Override
+ public boolean areContentsTheSame(@NonNull LeaderboardList oldItem,
+ @NonNull LeaderboardList newItem) {
+ return newItem.getRank().equals(oldItem.getRank());
+ }
+ };
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ LeaderboardList leaderboardList = (LeaderboardList) obj;
+ return leaderboardList.getRank().equals(this.getRank());
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardListAdapter.java b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardListAdapter.java
index 25585fea1f..c6318f9b26 100644
--- a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardListAdapter.java
+++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardListAdapter.java
@@ -1,5 +1,7 @@
package fr.free.nrw.commons.profile.leaderboard;
+import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.AVATAR_SOURCE_URL;
+
import android.content.Context;
import android.net.Uri;
import android.view.LayoutInflater;
@@ -7,16 +9,16 @@
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
+import androidx.paging.PagedListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.facebook.drawee.view.SimpleDraweeView;
import fr.free.nrw.commons.R;
-import java.util.List;
-
-public class LeaderboardListAdapter extends RecyclerView.Adapter {
- private List leaderboardList;
+public class LeaderboardListAdapter extends PagedListAdapter {
- private String avatarSourceURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/%s/1024px-%s.png";
+ protected LeaderboardListAdapter() {
+ super(LeaderboardList.DIFF_CALLBACK);
+ }
public class ListViewHolder extends RecyclerView.ViewHolder {
TextView rank;
@@ -41,10 +43,6 @@ public Context getContext() {
}
}
- public LeaderboardListAdapter(List leaderboardList) {
- this.leaderboardList = leaderboardList;
- }
-
/**
* Overrides the onCreateViewHolder and inflates the recyclerview list item layout
* @param parent
@@ -72,21 +70,12 @@ public void onBindViewHolder(@NonNull LeaderboardListAdapter.ListViewHolder hold
TextView username = holder.username;
TextView count = holder.count;
- rank.setText(leaderboardList.get(position).getRank().toString());
+ rank.setText(getItem(position).getRank().toString());
avatar.setImageURI(
- Uri.parse(String.format(avatarSourceURL, leaderboardList.get(position).getAvatar(),
- leaderboardList.get(position).getAvatar())));
- username.setText(leaderboardList.get(position).getUsername());
- count.setText(leaderboardList.get(position).getCategoryCount().toString());
- }
-
- /**
- * Override the getItemCount method
- * @return the size of the recycler view list
- */
- @Override
- public int getItemCount() {
- return leaderboardList.size();
+ Uri.parse(String.format(AVATAR_SOURCE_URL, getItem(position).getAvatar(),
+ getItem(position).getAvatar())));
+ username.setText(getItem(position).getUsername());
+ count.setText(getItem(position).getCategoryCount().toString());
}
}
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardListViewModel.java b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardListViewModel.java
new file mode 100644
index 0000000000..1c96552696
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardListViewModel.java
@@ -0,0 +1,59 @@
+package fr.free.nrw.commons.profile.leaderboard;
+
+import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.PAGE_SIZE;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Transformations;
+import androidx.lifecycle.ViewModel;
+import androidx.paging.LivePagedListBuilder;
+import androidx.paging.PagedList;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
+import io.reactivex.disposables.CompositeDisposable;
+
+public class LeaderboardListViewModel extends ViewModel {
+
+ private DataSourceFactory dataSourceFactory;
+ private LiveData> listLiveData;
+ private CompositeDisposable compositeDisposable = new CompositeDisposable();
+
+ private LiveData progressLoadStatus = new MutableLiveData<>();
+
+ public LeaderboardListViewModel(OkHttpJsonApiClient okHttpJsonApiClient, SessionManager sessionManager) {
+ dataSourceFactory = new DataSourceFactory(okHttpJsonApiClient, compositeDisposable, sessionManager);
+ initializePaging();
+ }
+
+
+ private void initializePaging() {
+
+ PagedList.Config pagedListConfig =
+ new PagedList.Config.Builder()
+ .setEnablePlaceholders(false)
+ .setInitialLoadSizeHint(PAGE_SIZE)
+ .setPageSize(PAGE_SIZE).build();
+
+ listLiveData = new LivePagedListBuilder<>(dataSourceFactory, pagedListConfig)
+ .build();
+
+ progressLoadStatus = Transformations
+ .switchMap(dataSourceFactory.getMutableLiveData(), DataSourceClass::getProgressLiveStatus);
+
+ }
+
+ public LiveData getProgressLoadStatus() {
+ return progressLoadStatus;
+ }
+
+ public LiveData> getListLiveData() {
+ return listLiveData;
+ }
+
+ @Override
+ protected void onCleared() {
+ super.onCleared();
+ compositeDisposable.clear();
+ }
+
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardResponse.java b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardResponse.java
index 6624ebfd06..838f7352a3 100644
--- a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardResponse.java
+++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardResponse.java
@@ -9,30 +9,39 @@ public class LeaderboardResponse {
@SerializedName("status")
@Expose
private Integer status;
+
@SerializedName("username")
@Expose
private String username;
+
@SerializedName("category_count")
@Expose
private Integer categoryCount;
+
@SerializedName("limit")
@Expose
- private Object limit;
+ private int limit;
+
@SerializedName("avatar")
@Expose
private String avatar;
+
@SerializedName("offset")
@Expose
- private Object offset;
+ private int offset;
+
@SerializedName("duration")
@Expose
private String duration;
+
@SerializedName("leaderboard_list")
@Expose
private List leaderboardList = null;
+
@SerializedName("category")
@Expose
private String category;
+
@SerializedName("rank")
@Expose
private Integer rank;
@@ -61,11 +70,11 @@ public void setCategoryCount(Integer categoryCount) {
this.categoryCount = categoryCount;
}
- public Object getLimit() {
+ public int getLimit() {
return limit;
}
- public void setLimit(Object limit) {
+ public void setLimit(int limit) {
this.limit = limit;
}
@@ -77,11 +86,11 @@ public void setAvatar(String avatar) {
this.avatar = avatar;
}
- public Object getOffset() {
+ public int getOffset() {
return offset;
}
- public void setOffset(Object offset) {
+ public void setOffset(int offset) {
this.offset = offset;
}
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/UserDetailAdapter.java b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/UserDetailAdapter.java
new file mode 100644
index 0000000000..6604812194
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/UserDetailAdapter.java
@@ -0,0 +1,78 @@
+package fr.free.nrw.commons.profile.leaderboard;
+
+import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.AVATAR_SOURCE_URL;
+
+import android.content.Context;
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import com.facebook.drawee.view.SimpleDraweeView;
+import fr.free.nrw.commons.R;
+
+public class UserDetailAdapter extends RecyclerView.Adapter {
+
+ LeaderboardResponse leaderboardResponse;
+
+ public UserDetailAdapter(LeaderboardResponse leaderboardResponse) {
+ this.leaderboardResponse = leaderboardResponse;
+ }
+
+ public class DataViewHolder extends RecyclerView.ViewHolder {
+
+ TextView rank;
+ SimpleDraweeView avatar;
+ TextView username;
+ TextView count;
+
+ public DataViewHolder(@NonNull View itemView) {
+ super(itemView);
+ this.rank = itemView.findViewById(R.id.rank);
+ this.avatar = itemView.findViewById(R.id.avatar);
+ this.username = itemView.findViewById(R.id.username);
+ this.count = itemView.findViewById(R.id.count);
+ }
+
+ public Context getContext() {
+ return itemView.getContext();
+ }
+ }
+
+ @NonNull
+ @Override
+ public UserDetailAdapter.DataViewHolder onCreateViewHolder(@NonNull ViewGroup parent,
+ int viewType) {
+ View view = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.leaderboard_user_element, parent, false);
+ return new DataViewHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull UserDetailAdapter.DataViewHolder holder, int position) {
+ TextView rank = holder.rank;
+ SimpleDraweeView avatar = holder.avatar;
+ TextView username = holder.username;
+ TextView count = holder.count;
+
+ rank.setText(String.format("%s %d",
+ holder.getContext().getResources().getString(R.string.rank_prefix),
+ leaderboardResponse.getRank()));
+
+ avatar.setImageURI(
+ Uri.parse(String.format(AVATAR_SOURCE_URL, leaderboardResponse.getAvatar(),
+ leaderboardResponse.getAvatar())));
+ username.setText(leaderboardResponse.getUsername());
+ count.setText(String.format("%s %d",
+ holder.getContext().getResources().getString(R.string.count_prefix),
+ leaderboardResponse.getCategoryCount()));
+
+ }
+
+ @Override
+ public int getItemCount() {
+ return 1;
+ }
+}
diff --git a/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/ViewModelFactory.java b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/ViewModelFactory.java
new file mode 100644
index 0000000000..cabf686c28
--- /dev/null
+++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/ViewModelFactory.java
@@ -0,0 +1,30 @@
+package fr.free.nrw.commons.profile.leaderboard;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.ViewModelProvider;
+import fr.free.nrw.commons.auth.SessionManager;
+import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient;
+import javax.inject.Inject;
+
+public class ViewModelFactory implements ViewModelProvider.Factory {
+
+ private OkHttpJsonApiClient okHttpJsonApiClient;
+ private SessionManager sessionManager;
+
+ @Inject
+ public ViewModelFactory(OkHttpJsonApiClient okHttpJsonApiClient, SessionManager sessionManager) {
+ this.okHttpJsonApiClient = okHttpJsonApiClient;
+ this.sessionManager = sessionManager;
+ }
+
+
+ @NonNull
+ @Override
+ public T create(@NonNull Class modelClass) {
+ if (modelClass.isAssignableFrom(LeaderboardListViewModel.class)) {
+ return (T) new LeaderboardListViewModel(okHttpJsonApiClient, sessionManager);
+ }
+ throw new IllegalArgumentException("Unknown class name");
+ }
+}
diff --git a/app/src/main/res/layout/fragment_leaderboard.xml b/app/src/main/res/layout/fragment_leaderboard.xml
index 27f03eb5d9..9f35a2f06b 100644
--- a/app/src/main/res/layout/fragment_leaderboard.xml
+++ b/app/src/main/res/layout/fragment_leaderboard.xml
@@ -1,116 +1,24 @@
-
+ android:layout_height="wrap_content">
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:layout_height="match_parent" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/leaderboard_list_element.xml b/app/src/main/res/layout/leaderboard_list_element.xml
index d779fe32a9..f344169424 100644
--- a/app/src/main/res/layout/leaderboard_list_element.xml
+++ b/app/src/main/res/layout/leaderboard_list_element.xml
@@ -1,18 +1,14 @@
-
-
-
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:layout_marginStart="15dp"
+ android:layout_marginEnd="15dp"
+ android:weightSum="1">
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/leaderboard_user_element.xml b/app/src/main/res/layout/leaderboard_user_element.xml
new file mode 100644
index 0000000000..c9337f5a76
--- /dev/null
+++ b/app/src/main/res/layout/leaderboard_user_element.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/kotlin/fr/free/nrw/commons/delete/ReasonBuilderTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/delete/ReasonBuilderTest.kt
index 825bcadc61..4fe5d14ffd 100644
--- a/app/src/test/kotlin/fr/free/nrw/commons/delete/ReasonBuilderTest.kt
+++ b/app/src/test/kotlin/fr/free/nrw/commons/delete/ReasonBuilderTest.kt
@@ -8,6 +8,7 @@ import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient
import fr.free.nrw.commons.profile.achievements.FeedbackResponse
import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse
import fr.free.nrw.commons.utils.ViewUtilWrapper
+import io.reactivex.Observable
import io.reactivex.Single
import media
import org.junit.Before
@@ -56,7 +57,7 @@ class ReasonBuilderTest {
`when`(okHttpJsonApiClient!!.getAchievements(anyString()))
.thenReturn(Single.just(mock(FeedbackResponse::class.java)))
`when`(okHttpJsonApiClient!!.getLeaderboard(anyString(), anyString(), anyString(), anyString(), anyString()))
- .thenReturn(Single.just(mock(LeaderboardResponse::class.java)))
+ .thenReturn(Observable.just(mock(LeaderboardResponse::class.java)))
val media = media(filename="test_file", dateUploaded = Date())