From 8b15863d7a5794be09308f0c3e592431b7c3a70b Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Sat, 4 Jul 2020 20:59:42 +0530 Subject: [PATCH 01/11] =?UTF-8?q?Fixes=20#3861=20Use=20the=20APIs=20to=20f?= =?UTF-8?q?etch=20leaderboard=E2=80=99s=20based=20on=20uploads=20via=20mob?= =?UTF-8?q?ile=20app=20(all=20time)=20and=20display=20it=20in=20the=20Lead?= =?UTF-8?q?erboard=20screen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../free/nrw/commons/di/NetworkingModule.java | 10 ++ .../commons/mwapi/OkHttpJsonApiClient.java | 43 ++++++ .../leaderboard/LeaderboardFragment.java | 144 ++++++++++++++++++ .../profile/leaderboard/LeaderboardList.java | 53 +++++++ .../leaderboard/LeaderboardListAdapter.java | 73 +++++++++ .../leaderboard/LeaderboardResponse.java | 120 +++++++++++++++ .../main/res/layout/fragment_leaderboard.xml | 110 ++++++++++++- .../res/layout/leaderboard_list_element.xml | 55 +++++++ app/src/main/res/values/strings.xml | 5 + 9 files changed, 610 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardList.java create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardListAdapter.java create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardResponse.java create mode 100644 app/src/main/res/layout/leaderboard_list_element.xml diff --git a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java index 495a8d01ef..84fdfe53a8 100644 --- a/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java +++ b/app/src/main/java/fr/free/nrw/commons/di/NetworkingModule.java @@ -85,11 +85,13 @@ public HttpLoggingInterceptor provideHttpLoggingInterceptor() { public OkHttpJsonApiClient provideOkHttpJsonApiClient(OkHttpClient okHttpClient, DepictsClient depictsClient, @Named("tools_forge") HttpUrl toolsForgeUrl, + @Named("test_tools_forge") HttpUrl testToolsForgeUrl, @Named("default_preferences") JsonKvStore defaultKvStore, Gson gson) { return new OkHttpJsonApiClient(okHttpClient, depictsClient, toolsForgeUrl, + testToolsForgeUrl, WIKIDATA_SPARQL_QUERY_URL, BuildConfig.WIKIMEDIA_CAMPAIGNS_URL, gson); @@ -124,6 +126,14 @@ public HttpUrl provideToolsForgeUrl() { return HttpUrl.parse(TOOLS_FORGE_URL); } + @Provides + @Named("test_tools_forge") + @NonNull + @SuppressWarnings("ConstantConditions") + public HttpUrl provideTestToolsForgeUrl() { + return HttpUrl.parse(TEST_TOOLS_FORGE_URL); + } + @Provides @Singleton @Named(NAMED_COMMONS_WIKI_SITE) 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 eeaba0a73d..d07667b48b 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 @@ -11,6 +11,7 @@ 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.leaderboard.LeaderboardResponse; import fr.free.nrw.commons.upload.FileUtils; import fr.free.nrw.commons.upload.structure.depictions.DepictedItem; import fr.free.nrw.commons.utils.ConfigUtils; @@ -40,6 +41,7 @@ public class OkHttpJsonApiClient { private final OkHttpClient okHttpClient; private final DepictsClient depictsClient; private final HttpUrl wikiMediaToolforgeUrl; + private final HttpUrl wikiMediaTestToolforgeUrl; private final String sparqlQueryUrl; private final String campaignsUrl; private final Gson gson; @@ -49,17 +51,58 @@ public class OkHttpJsonApiClient { public OkHttpJsonApiClient(OkHttpClient okHttpClient, DepictsClient depictsClient, HttpUrl wikiMediaToolforgeUrl, + HttpUrl wikiMediaTestToolforgeUrl, String sparqlQueryUrl, String campaignsUrl, Gson gson) { this.okHttpClient = okHttpClient; this.depictsClient = depictsClient; this.wikiMediaToolforgeUrl = wikiMediaToolforgeUrl; + this.wikiMediaTestToolforgeUrl = wikiMediaTestToolforgeUrl; this.sparqlQueryUrl = sparqlQueryUrl; this.campaignsUrl = campaignsUrl; this.gson = gson; } + @NonNull + public Single 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(); + Response response = okHttpClient.newCall(request).execute(); + if (response != null && response.body() != null && response.isSuccessful()) { + String json = response.body().string(); + if (json == null) { + return null; + } + Timber.d("Response for leaderboard is %s", json); + try { + return gson.fromJson(json, LeaderboardResponse.class); + } catch (Exception e) { + return new LeaderboardResponse(); + } + } + return null; + }); + } + @NonNull public Single getUploadCount(String userName) { HttpUrl.Builder urlBuilder = wikiMediaToolforgeUrl.newBuilder(); 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 76cbc51a3b..e536a950ad 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,162 @@ package fr.free.nrw.commons.profile.leaderboard; +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.recyclerview.widget.LinearLayoutManager; +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; +import fr.free.nrw.commons.mwapi.OkHttpJsonApiClient; +import fr.free.nrw.commons.utils.ViewUtil; +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; + + @BindView(R.id.progressBar) + ProgressBar progressBar; + + @Inject + SessionManager sessionManager; + + @Inject + OkHttpJsonApiClient okHttpJsonApiClient; + + private String avatarSourceURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/%s/1024px-%s.png"; + + private CompositeDisposable compositeDisposable = new CompositeDisposable(); + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_leaderboard, container, false); + ButterKnife.bind(this, rootView); + progressBar.setVisibility(View.VISIBLE); + hideLayouts(); + setLeaderboard(); return rootView; } + /** + * To call the API to get results in form Single + * which then calls parseJson when results are fetched + */ + private void setLeaderboard() { + if (checkAccount()) { + try{ + compositeDisposable.add(okHttpJsonApiClient + .getLeaderboard(Objects.requireNonNull(sessionManager.getCurrentAccount()).name, + "all_time", "upload", null, null) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + response -> { + if (response != null && response.getStatus() == 200) { + setLeaderboardUser(response); + setLeaderboardList(response.getLeaderboardList()); + } + }, + t -> { + Timber.e(t, "Fetching leaderboard statistics failed"); + onError(); + } + )); + } + catch (Exception e){ + Timber.d(e+"success"); + } + } + } + + 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); + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); + leaderboardListRecyclerView.setLayoutManager(linearLayoutManager); + leaderboardListRecyclerView.setAdapter(leaderboardListAdapter); + } + + /** + * to hide progressbar + */ + 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); + } + } + + /** + * 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); + } + + /** + * check to ensure that user is logged in + * @return + */ + private boolean checkAccount(){ + Account currentAccount = sessionManager.getCurrentAccount(); + if (currentAccount == null) { + Timber.d("Current account is null"); + ViewUtil.showLongToast(getActivity(), getResources().getString(R.string.user_not_logged_in)); + sessionManager.forceLogin(getActivity()); + return false; + } + return true; + } + + /** + * Shows a generic error toast when error occurs while loading leaderboard + */ + private void onError() { + ViewUtil.showLongToast(getActivity(), getResources().getString(R.string.error_occurred)); + progressBar.setVisibility(View.GONE); + } + } 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 new file mode 100644 index 0000000000..d6b86a22b9 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardList.java @@ -0,0 +1,53 @@ +package fr.free.nrw.commons.profile.leaderboard; + +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +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; + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Integer getCategoryCount() { + return categoryCount; + } + + public void setCategoryCount(Integer categoryCount) { + this.categoryCount = categoryCount; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public Integer getRank() { + return rank; + } + + public void setRank(Integer rank) { + this.rank = rank; + } + +} \ 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 new file mode 100644 index 0000000000..4a61a1cb19 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardListAdapter.java @@ -0,0 +1,73 @@ +package fr.free.nrw.commons.profile.leaderboard; + +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; +import java.util.List; + +public class LeaderboardListAdapter extends RecyclerView.Adapter { + + private List leaderboardList; + + private String avatarSourceURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/%s/1024px-%s.png"; + + public class ListViewHolder extends RecyclerView.ViewHolder { + TextView rank; + SimpleDraweeView avatar; + TextView username; + TextView count; + + public ListViewHolder(View itemView) { + super(itemView); + this.rank = itemView.findViewById(R.id.user_rank); + this.avatar = itemView.findViewById(R.id.user_avatar); + this.username = itemView.findViewById(R.id.user_name); + this.count = itemView.findViewById(R.id.user_count); + } + + public Context getContext() { + return itemView.getContext(); + } + } + + public LeaderboardListAdapter(List leaderboardList) { + this.leaderboardList = leaderboardList; + } + + @NonNull + @Override + public LeaderboardListAdapter.ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.leaderboard_list_element, parent, false); + + return new ListViewHolder(view); + } + + @Override + public void onBindViewHolder(@NonNull LeaderboardListAdapter.ListViewHolder holder, int position) { + TextView rank = holder.rank; + SimpleDraweeView avatar = holder.avatar; + TextView username = holder.username; + TextView count = holder.count; + + rank.setText(leaderboardList.get(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 + public int getItemCount() { + return leaderboardList.size(); + } +} 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 new file mode 100644 index 0000000000..6624ebfd06 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardResponse.java @@ -0,0 +1,120 @@ +package fr.free.nrw.commons.profile.leaderboard; + +import java.util.List; +import com.google.gson.annotations.Expose; +import com.google.gson.annotations.SerializedName; + +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; + @SerializedName("avatar") + @Expose + private String avatar; + @SerializedName("offset") + @Expose + private Object 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; + + public Integer getStatus() { + return status; + } + + public void setStatus(Integer status) { + this.status = status; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public Integer getCategoryCount() { + return categoryCount; + } + + public void setCategoryCount(Integer categoryCount) { + this.categoryCount = categoryCount; + } + + public Object getLimit() { + return limit; + } + + public void setLimit(Object limit) { + this.limit = limit; + } + + public String getAvatar() { + return avatar; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public Object getOffset() { + return offset; + } + + public void setOffset(Object offset) { + this.offset = offset; + } + + public String getDuration() { + return duration; + } + + public void setDuration(String duration) { + this.duration = duration; + } + + public List getLeaderboardList() { + return leaderboardList; + } + + public void setLeaderboardList(List leaderboardList) { + this.leaderboardList = leaderboardList; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public Integer getRank() { + return rank; + } + + public void setRank(Integer rank) { + this.rank = rank; + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_leaderboard.xml b/app/src/main/res/layout/fragment_leaderboard.xml index 28f5b725cc..b965e83db2 100644 --- a/app/src/main/res/layout/fragment_leaderboard.xml +++ b/app/src/main/res/layout/fragment_leaderboard.xml @@ -1,6 +1,110 @@ - - \ 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 new file mode 100644 index 0000000000..d779fe32a9 --- /dev/null +++ b/app/src/main/res/layout/leaderboard_list_element.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6f2a3a0c6a..64459cbf64 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -651,4 +651,9 @@ Upload your first media by tapping on the add button. Copy wikicode to clipboard Achievements Leaderboard + Rank: + Count: + Rank + User + Count From 089c1537f91fe7dfedb81a5340f140f11e30b3d1 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Sun, 12 Jul 2020 20:52:16 +0530 Subject: [PATCH 02/11] Fixed Bug - missing data in landscape mode --- app/src/main/res/layout/fragment_leaderboard.xml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/layout/fragment_leaderboard.xml b/app/src/main/res/layout/fragment_leaderboard.xml index b965e83db2..27f03eb5d9 100644 --- a/app/src/main/res/layout/fragment_leaderboard.xml +++ b/app/src/main/res/layout/fragment_leaderboard.xml @@ -8,7 +8,7 @@ + android:layout_height="wrap_content"> - + app:layout_constraintTop_toBottomOf="@+id/column_names"> + + + + Date: Mon, 13 Jul 2020 22:28:16 +0530 Subject: [PATCH 03/11] Added Unit Tests for Leaderboard --- app/build.gradle | 7 +- .../nrw/commons/delete/ReasonBuilderTest.kt | 3 + .../leaderboard/LeaderboardApiTest.java | 83 +++++++++++++++++++ .../leaderboard_sample_response.json | 19 +++++ 4 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java create mode 100644 app/src/test/resources/leaderboard_sample_response.json diff --git a/app/build.gradle b/app/build.gradle index 638a4048aa..08f322c055 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,7 +21,7 @@ dependencies { // Utils implementation 'in.yuvi:http.fluent:1.3' implementation 'com.google.code.gson:gson:2.8.5' - implementation 'com.squareup.okhttp3:okhttp:4.5.0' + implementation 'com.squareup.okhttp3:okhttp:4.8.0' implementation 'com.squareup.okio:okio:2.2.2' implementation 'io.reactivex.rxjava2:rxandroid:2.1.0' implementation 'io.reactivex.rxjava2:rxjava:2.2.3' @@ -50,6 +50,7 @@ dependencies { testImplementation "androidx.paging:paging-common-ktx:$PAGING_VERSION" implementation "androidx.paging:paging-rxjava2-ktx:$PAGING_VERSION" implementation "androidx.recyclerview:recyclerview:1.2.0-alpha02" + implementation 'com.squareup.okhttp3:okhttp-ws:3.4.1' // Logging implementation 'ch.acra:acra-dialog:5.3.0' @@ -79,7 +80,7 @@ dependencies { testImplementation 'junit:junit:4.13' testImplementation 'org.robolectric:robolectric:4.3' testImplementation 'androidx.test:core:1.2.0' - testImplementation 'com.squareup.okhttp3:mockwebserver:3.12.1' + testImplementation "com.squareup.okhttp3:mockwebserver:4.8.0" testImplementation "org.powermock:powermock-module-junit4:2.0.0-beta.5" testImplementation "org.powermock:powermock-api-mockito2:2.0.0-beta.5" testImplementation 'org.mockito:mockito-core:2.23.0' @@ -94,7 +95,7 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.annotation:annotation:1.1.0' - androidTestImplementation 'com.squareup.okhttp3:mockwebserver:3.12.1' + androidTestImplementation 'com.squareup.okhttp3:mockwebserver:4.8.0' androidTestUtil 'androidx.test:orchestrator:1.2.0' // Debugging 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 d725a48064..825bcadc61 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 @@ -6,6 +6,7 @@ import fr.free.nrw.commons.Media import fr.free.nrw.commons.auth.SessionManager 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.Single import media @@ -54,6 +55,8 @@ class ReasonBuilderTest { `when`(sessionManager?.doesAccountExist()).thenReturn(true) `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))) val media = media(filename="test_file", dateUploaded = Date()) diff --git a/app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java b/app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java new file mode 100644 index 0000000000..b7d9c4822f --- /dev/null +++ b/app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java @@ -0,0 +1,83 @@ +package fr.free.nrw.commons.leaderboard; + +import com.google.gson.Gson; +import fr.free.nrw.commons.profile.leaderboard.LeaderboardResponse; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Request.Builder; +import okhttp3.Response; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class LeaderboardApiTest { + + MockWebServer server; + private static final String TEST_USERNAME = "user"; + private static final String TEST_AVATAR = "avatar"; + private static final int TEST_USER_RANK = 1; + private static final int TEST_USER_COUNT = 0; + + private static final String FILE_NAME = "leaderboard_sample_response.json"; + private static final String ENDPOINT = "/leaderboard.py"; + + @Before + public void initTest() { + server = new MockWebServer(); + } + + @Before + public void setUp() throws Exception { + + String testResponseBody = convertStreamToString(getClass().getClassLoader().getResourceAsStream(FILE_NAME)); + + server.enqueue(new MockResponse().setBody(testResponseBody)); + server.start(); + } + + private static String convertStreamToString(InputStream is) throws Exception { + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + } + reader.close(); + return sb.toString(); + } + + @Test + public void apiTest() throws IOException { + HttpUrl httpUrl = server.url(ENDPOINT); + LeaderboardResponse response = sendRequest(new OkHttpClient(), httpUrl); + + Assert.assertEquals(TEST_AVATAR, response.getAvatar()); + Assert.assertEquals(TEST_USERNAME, response.getUsername()); + Assert.assertEquals(Integer.valueOf(TEST_USER_RANK), response.getRank()); + Assert.assertEquals(Integer.valueOf(TEST_USER_COUNT), response.getCategoryCount()); + } + + private LeaderboardResponse sendRequest(OkHttpClient okHttpClient, HttpUrl httpUrl) + throws IOException { + Request request = new Builder().url(httpUrl).build(); + Response response = okHttpClient.newCall(request).execute(); + if (response.isSuccessful()) { + Gson gson = new Gson(); + return gson.fromJson(response.body().string(), LeaderboardResponse.class); + } + return null; + } + + @After + public void shutdown() throws IOException { + server.shutdown(); + } +} diff --git a/app/src/test/resources/leaderboard_sample_response.json b/app/src/test/resources/leaderboard_sample_response.json new file mode 100644 index 0000000000..04a205a934 --- /dev/null +++ b/app/src/test/resources/leaderboard_sample_response.json @@ -0,0 +1,19 @@ +{ + "status": 200, + "username": "user", + "category_count": 0, + "limit": null, + "avatar": "avatar", + "offset": null, + "duration": "all_time", + "leaderboard_list": [ + { + "username": "user", + "category_count": 0, + "avatar": "avatar", + "rank": 1 + } + ], + "category": "used", + "rank": 1 +} \ No newline at end of file From 3fca9e940f1e9219ccd4df5fb645896164747439 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Tue, 14 Jul 2020 18:36:42 +0530 Subject: [PATCH 04/11] Added JavaDocs --- .../leaderboard/LeaderboardFragment.java | 4 +++ .../leaderboard/LeaderboardListAdapter.java | 19 +++++++++++++ .../leaderboard/LeaderboardApiTest.java | 28 +++++++++++++++++++ 3 files changed, 51 insertions(+) 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 e536a950ad..cae28b4e71 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 @@ -97,6 +97,10 @@ private void setLeaderboard() { } } + /** + * Set the views + * @param response Leaderboard Response Object + */ private void setLeaderboardUser(LeaderboardResponse response) { hideProgressBar(); avatar.setImageURI( 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 4a61a1cb19..25585fea1f 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 @@ -32,6 +32,10 @@ public ListViewHolder(View itemView) { this.count = itemView.findViewById(R.id.user_count); } + /** + * This method will return the Context + * @return Context + */ public Context getContext() { return itemView.getContext(); } @@ -41,6 +45,12 @@ public LeaderboardListAdapter(List leaderboardList) { this.leaderboardList = leaderboardList; } + /** + * Overrides the onCreateViewHolder and inflates the recyclerview list item layout + * @param parent + * @param viewType + * @return + */ @NonNull @Override public LeaderboardListAdapter.ListViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { @@ -50,6 +60,11 @@ public LeaderboardListAdapter.ListViewHolder onCreateViewHolder(@NonNull ViewGro return new ListViewHolder(view); } + /** + * Overrides the onBindViewHolder Set the view at the specific position with the specific value + * @param holder + * @param position + */ @Override public void onBindViewHolder(@NonNull LeaderboardListAdapter.ListViewHolder holder, int position) { TextView rank = holder.rank; @@ -66,6 +81,10 @@ public void onBindViewHolder(@NonNull LeaderboardListAdapter.ListViewHolder hold 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(); diff --git a/app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java b/app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java index b7d9c4822f..40c3f5b060 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java +++ b/app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java @@ -29,11 +29,18 @@ public class LeaderboardApiTest { private static final String FILE_NAME = "leaderboard_sample_response.json"; private static final String ENDPOINT = "/leaderboard.py"; + /** + * This method initialises a Mock Server + */ @Before public void initTest() { server = new MockWebServer(); } + /** + * This method will setup a Mock Server and load Test JSON Response File + * @throws Exception + */ @Before public void setUp() throws Exception { @@ -43,6 +50,12 @@ public void setUp() throws Exception { server.start(); } + /** + * This method converts a Input Stream to String + * @param is takes Input Stream of JSON File as Parameter + * @return a String with JSON data + * @throws Exception + */ private static String convertStreamToString(InputStream is) throws Exception { BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); @@ -54,6 +67,10 @@ private static String convertStreamToString(InputStream is) throws Exception { return sb.toString(); } + /** + * This method will call the Mock Server and Test it with sample values + * @throws IOException + */ @Test public void apiTest() throws IOException { HttpUrl httpUrl = server.url(ENDPOINT); @@ -65,6 +82,13 @@ public void apiTest() throws IOException { Assert.assertEquals(Integer.valueOf(TEST_USER_COUNT), response.getCategoryCount()); } + /** + * This method will call the Mock API and returns the Leaderboard Response Object + * @param okHttpClient + * @param httpUrl + * @return Leaderboard Response Object + * @throws IOException + */ private LeaderboardResponse sendRequest(OkHttpClient okHttpClient, HttpUrl httpUrl) throws IOException { Request request = new Builder().url(httpUrl).build(); @@ -76,6 +100,10 @@ private LeaderboardResponse sendRequest(OkHttpClient okHttpClient, HttpUrl httpU return null; } + /** + * This method shuts down the Mock Server + * @throws IOException + */ @After public void shutdown() throws IOException { server.shutdown(); From 4b2b7ade203e043066766bf3dc35db26b471c1d3 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Tue, 14 Jul 2020 18:46:35 +0530 Subject: [PATCH 05/11] Updated JavaDocs --- .../java/fr/free/nrw/commons/profile/ProfileActivity.java | 4 +++- .../free/nrw/commons/leaderboard/LeaderboardApiTest.java | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java index 23bbb85fe7..ee319ba9a4 100644 --- a/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java +++ b/app/src/main/java/fr/free/nrw/commons/profile/ProfileActivity.java @@ -56,6 +56,9 @@ public static void startYourself(Context context) { context.startActivity(intent); } + /** + * Set the tabs for the fragments + */ private void setTabs() { List fragmentList = new ArrayList<>(); List titleList = new ArrayList<>(); @@ -69,7 +72,6 @@ private void setTabs() { viewPagerAdapter.notifyDataSetChanged(); } - @Override public void onDestroy() { super.onDestroy(); diff --git a/app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java b/app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java index 40c3f5b060..51d806a88d 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java +++ b/app/src/test/kotlin/fr/free/nrw/commons/leaderboard/LeaderboardApiTest.java @@ -18,6 +18,9 @@ import org.junit.Before; import org.junit.Test; +/** + * This class tests the Leaderboard API calls + */ public class LeaderboardApiTest { MockWebServer server; @@ -68,7 +71,9 @@ private static String convertStreamToString(InputStream is) throws Exception { } /** - * This method will call the Mock Server and Test it with sample values + * This method will call the Mock Server and Test it with sample values. + * It will test the Leaderboard API call functionality and check if the object is + * being created with the correct values * @throws IOException */ @Test From dbd7908bcda8f7ebe90d10403189ff8fe2557ce2 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Fri, 24 Jul 2020 17:30:07 +0530 Subject: [PATCH 06/11] Added Pagination --- .../commons/mwapi/OkHttpJsonApiClient.java | 46 +++++------ .../profile/leaderboard/DataSourceClass.java | 76 +++++++++++++++++++ .../leaderboard/DataSourceFactory.java | 34 +++++++++ .../leaderboard/LeaderboardConstants.java | 11 +++ .../leaderboard/LeaderboardFragment.java | 28 ++++--- .../profile/leaderboard/LeaderboardList.java | 31 ++++++++ .../leaderboard/LeaderboardListAdapter.java | 35 +++------ .../leaderboard/LeaderboardListViewModel.java | 59 ++++++++++++++ .../leaderboard/LeaderboardResponse.java | 21 +++-- .../profile/leaderboard/ViewModelFactory.java | 30 ++++++++ .../main/res/layout/fragment_leaderboard.xml | 14 +--- .../res/layout/leaderboard_list_element.xml | 19 ++--- 12 files changed, 321 insertions(+), 83 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/leaderboard/DataSourceClass.java create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/leaderboard/DataSourceFactory.java create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardConstants.java create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardListViewModel.java create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/leaderboard/ViewModelFactory.java 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..5551e89459 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(); }); } 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..ec19c7ef02 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/DataSourceClass.java @@ -0,0 +1,76 @@ +package fr.free.nrw.commons.profile.leaderboard; + +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); + }).subscribe( + response -> { + if (response != null && response.getStatus() == 200) { + callback.onResult(response.getLeaderboardList(), null, response.getLimit()); + } + }, + t -> Timber.e(t, "Fetching leaderboard statistics failed") + )); + + } + + @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); + }).subscribe( + response -> { + if (response != null && response.getStatus() == 200) { + callback.onResult(response.getLeaderboardList(), params.key + PAGE_SIZE); + } + }, + t -> Timber.e(t, "Fetching leaderboard statistics failed") + )); + } +} 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..a2b2ac6451 --- /dev/null +++ b/app/src/main/java/fr/free/nrw/commons/profile/leaderboard/LeaderboardConstants.java @@ -0,0 +1,11 @@ +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"; + +} 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..d6559cd914 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,5 +1,7 @@ package fr.free.nrw.commons.profile.leaderboard; +import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.AVATAR_SOURCE_URL; + import android.accounts.Account; import android.net.Uri; import android.os.Bundle; @@ -8,6 +10,7 @@ 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.RecyclerView; import butterknife.BindView; @@ -21,7 +24,6 @@ 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; @@ -52,7 +54,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 +72,13 @@ 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 { + setLeaderboardList(); compositeDisposable.add(okHttpJsonApiClient .getLeaderboard(Objects.requireNonNull(sessionManager.getCurrentAccount()).name, "all_time", "upload", null, null) @@ -82,7 +88,6 @@ private void setLeaderboard() { response -> { if (response != null && response.getStatus() == 200) { setLeaderboardUser(response); - setLeaderboardList(response.getLeaderboardList()); } }, t -> { @@ -104,17 +109,22 @@ private void setLeaderboard() { private void setLeaderboardUser(LeaderboardResponse response) { hideProgressBar(); avatar.setImageURI( - Uri.parse(String.format(avatarSourceURL, response.getAvatar(), response.getAvatar()))); + Uri.parse(String.format(AVATAR_SOURCE_URL, 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 setLeaderboardList() { + viewModel = new ViewModelProvider(this, viewModelFactory).get(LeaderboardListViewModel.class); + LeaderboardListAdapter leaderboardListAdapter = new LeaderboardListAdapter(); LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext()); leaderboardListRecyclerView.setLayoutManager(linearLayoutManager); leaderboardListRecyclerView.setAdapter(leaderboardListAdapter); + + viewModel.getListLiveData().observe(getViewLifecycleOwner(), leaderboardListAdapter::submitList); + viewModel.getProgressLoadStatus().observe(getViewLifecycleOwner(), status -> { + }); } /** 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/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..c26df82f2e 100644 --- a/app/src/main/res/layout/fragment_leaderboard.xml +++ b/app/src/main/res/layout/fragment_leaderboard.xml @@ -87,19 +87,13 @@ - - - - - + app:layout_constraintTop_toBottomOf="@+id/column_names"/> - - - + 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 From 4e78b2db8577d0663a3e9c20b80606637c08eb97 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Fri, 24 Jul 2020 19:06:48 +0530 Subject: [PATCH 07/11] Added Merge Adapter --- app/build.gradle | 1 + .../leaderboard/LeaderboardFragment.java | 41 ++------- .../leaderboard/UserDetailAdapter.java | 78 +++++++++++++++++ .../main/res/layout/fragment_leaderboard.xml | 84 +----------------- .../res/layout/leaderboard_user_element.xml | 86 +++++++++++++++++++ 5 files changed, 174 insertions(+), 116 deletions(-) create mode 100644 app/src/main/java/fr/free/nrw/commons/profile/leaderboard/UserDetailAdapter.java create mode 100644 app/src/main/res/layout/leaderboard_user_element.xml diff --git a/app/build.gradle b/app/build.gradle index 08f322c055..f411348a0d 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" 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 d6559cd914..7b1035e726 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,21 +1,17 @@ package fr.free.nrw.commons.profile.leaderboard; -import static fr.free.nrw.commons.profile.leaderboard.LeaderboardConstants.AVATAR_SOURCE_URL; - 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; @@ -30,18 +26,6 @@ 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; @@ -78,7 +62,6 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa private void setLeaderboard() { if (checkAccount()) { try { - setLeaderboardList(); compositeDisposable.add(okHttpJsonApiClient .getLeaderboard(Objects.requireNonNull(sessionManager.getCurrentAccount()).name, "all_time", "upload", null, null) @@ -87,7 +70,7 @@ private void setLeaderboard() { .subscribe( response -> { if (response != null && response.getStatus() == 200) { - setLeaderboardUser(response); + setViews(response); } }, t -> { @@ -106,21 +89,15 @@ private void setLeaderboard() { * Set the views * @param response Leaderboard Response Object */ - private void setLeaderboardUser(LeaderboardResponse response) { + private void setViews(LeaderboardResponse response) { hideProgressBar(); - avatar.setImageURI( - Uri.parse(String.format(AVATAR_SOURCE_URL, 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() { 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 -> { @@ -133,9 +110,6 @@ private void setLeaderboardList() { 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); } } @@ -144,9 +118,6 @@ private void hideProgressBar() { * 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/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/res/layout/fragment_leaderboard.xml b/app/src/main/res/layout/fragment_leaderboard.xml index c26df82f2e..385d664e17 100644 --- a/app/src/main/res/layout/fragment_leaderboard.xml +++ b/app/src/main/res/layout/fragment_leaderboard.xml @@ -10,90 +10,12 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> - - - - - - - - - - - - - - - - - - + android:layout_height="wrap_content" + app:layout_constrainedHeight="true" + app:layout_constraintBottom_toBottomOf="parent" /> + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From f5aa5a01aac882f6359de0d5c6a0651a55e7e201 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Fri, 24 Jul 2020 19:38:49 +0530 Subject: [PATCH 08/11] Fixed Test Case --- .../kotlin/fr/free/nrw/commons/delete/ReasonBuilderTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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()) From 370462c083ca69590d807d5809f497d95bf7f4c1 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Fri, 24 Jul 2020 19:58:06 +0530 Subject: [PATCH 09/11] Added Smooth Scroll --- .../main/res/layout/fragment_leaderboard.xml | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/app/src/main/res/layout/fragment_leaderboard.xml b/app/src/main/res/layout/fragment_leaderboard.xml index 385d664e17..9f35a2f06b 100644 --- a/app/src/main/res/layout/fragment_leaderboard.xml +++ b/app/src/main/res/layout/fragment_leaderboard.xml @@ -1,32 +1,24 @@ - + android:layout_height="wrap_content"> - - - + android:layout_height="match_parent" /> - - - + - \ No newline at end of file + \ No newline at end of file From 68682a95663156a960796aa0cd750639f0466bc3 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Sun, 26 Jul 2020 13:36:52 +0530 Subject: [PATCH 10/11] Added Progress Bar for Paging --- .../profile/leaderboard/DataSourceClass.java | 16 ++++++++++++++-- .../leaderboard/LeaderboardConstants.java | 4 ++++ .../leaderboard/LeaderboardFragment.java | 18 +++++++++++++++++- 3 files changed, 35 insertions(+), 3 deletions(-) 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 index ec19c7ef02..ac522cfd7d 100644 --- 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 @@ -1,5 +1,7 @@ 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; @@ -39,13 +41,18 @@ public void loadInitial(@NonNull LoadInitialParams params, "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") + t -> { + Timber.e(t, "Fetching leaderboard statistics failed"); + progressLiveStatus.postValue(LOADING); + } )); } @@ -64,13 +71,18 @@ public void loadAfter(@NonNull LoadParams params, "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") + t -> { + Timber.e(t, "Fetching leaderboard statistics failed"); + progressLiveStatus.postValue(LOADING); + } )); } } 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 index a2b2ac6451..79b53ef98b 100644 --- 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 @@ -8,4 +8,8 @@ public class LeaderboardConstants { 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 7b1035e726..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,5 +1,8 @@ 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.os.Bundle; import android.view.LayoutInflater; @@ -90,7 +93,6 @@ private void setLeaderboard() { * @param response Leaderboard Response Object */ private void setViews(LeaderboardResponse response) { - hideProgressBar(); viewModel = new ViewModelProvider(this, viewModelFactory).get(LeaderboardListViewModel.class); LeaderboardListAdapter leaderboardListAdapter = new LeaderboardListAdapter(); UserDetailAdapter userDetailAdapter= new UserDetailAdapter(response); @@ -101,6 +103,11 @@ private void setViews(LeaderboardResponse response) { viewModel.getListLiveData().observe(getViewLifecycleOwner(), leaderboardListAdapter::submitList); viewModel.getProgressLoadStatus().observe(getViewLifecycleOwner(), status -> { + if (Objects.requireNonNull(status).equalsIgnoreCase(LOADING)) { + showProgressBar(); + } else if (status.equalsIgnoreCase(LOADED)) { + hideProgressBar(); + } }); } @@ -114,6 +121,15 @@ private void hideProgressBar() { } } + /** + * to show progressbar + */ + private void showProgressBar() { + if (progressBar != null) { + progressBar.setVisibility(View.VISIBLE); + } + } + /** * used to hide the layouts while fetching results from api */ From 5f4c58917d144a9b352842528332bec652010eae Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Mon, 27 Jul 2020 08:39:22 +0530 Subject: [PATCH 11/11] Fixed Gradle --- app/build.gradle | 2 +- .../java/fr/free/nrw/commons/mwapi/OkHttpJsonApiClient.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index f411348a0d..dd64aa19b5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -211,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 5551e89459..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 @@ -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();