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())