From 4c9637c821f749bd97a65e9fc0e07cff22a060cb Mon Sep 17 00:00:00 2001 From: Parneet Singh <111801812+parneet-guraya@users.noreply.github.com> Date: Fri, 20 Dec 2024 06:36:07 +0530 Subject: [PATCH] Add pull down to refresh in Contributions screen (#6041) * pull down to refresh Signed-off-by: parneet-guraya * add kdoc Signed-off-by: parneet-guraya * only enabled for self user Signed-off-by: parneet-guraya * fix test Signed-off-by: parneet-guraya --------- Signed-off-by: parneet-guraya --- .../ContributionBoundaryCallback.kt | 38 +++- .../ContributionsListContract.java | 4 + .../ContributionsListFragment.java | 9 + .../ContributionsListPresenter.java | 18 +- .../layout/fragment_contributions_list.xml | 192 +++++++++--------- .../ContributionBoundaryCallbackTest.kt | 7 +- 6 files changed, 160 insertions(+), 108 deletions(-) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt index 3f7bffe91..b5075a21e 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionBoundaryCallback.kt @@ -2,7 +2,6 @@ package fr.free.nrw.commons.contributions import androidx.paging.PagedList.BoundaryCallback import fr.free.nrw.commons.auth.SessionManager -import fr.free.nrw.commons.di.CommonsApplicationModule import fr.free.nrw.commons.di.CommonsApplicationModule.Companion.IO_THREAD import fr.free.nrw.commons.media.MediaClient import io.reactivex.Scheduler @@ -31,10 +30,7 @@ class ContributionBoundaryCallback * network */ override fun onZeroItemsLoaded() { - if (sessionManager.userName != null) { - mediaClient.resetUserNameContinuation(sessionManager.userName!!) - } - fetchContributions() + refreshList() } /** @@ -52,9 +48,25 @@ class ContributionBoundaryCallback } /** - * Fetches contributions using the MediaWiki API + * Fetch list from network and save it to local DB. + * + * @param onRefreshFinish callback to invoke when operations finishes + * with either error or success. */ - private fun fetchContributions() { + fun refreshList(onRefreshFinish: () -> Unit = {}){ + if (sessionManager.userName != null) { + mediaClient.resetUserNameContinuation(sessionManager.userName!!) + } + fetchContributions(onRefreshFinish) + } + + /** + * Fetches contributions using the MediaWiki API + * + * @param onRefreshFinish callback to invoke when operations finishes + * with either error or success. + */ + private fun fetchContributions(onRefreshFinish: () -> Unit = {}) { if (sessionManager.userName != null) { userName ?.let { userName -> @@ -65,12 +77,15 @@ class ContributionBoundaryCallback Contribution(media = media, state = Contribution.STATE_COMPLETED) } }.subscribeOn(ioThreadScheduler) - .subscribe(::saveContributionsToDB) { error: Throwable -> + .subscribe({ list -> + saveContributionsToDB(list, onRefreshFinish) + },{ error -> + onRefreshFinish() Timber.e( "Failed to fetch contributions: %s", error.message, ) - } + }) }?.let { compositeDisposable.add( it, @@ -83,13 +98,16 @@ class ContributionBoundaryCallback /** * Saves the contributions the the local DB + * + * @param onRefreshFinish callback to invoke when successfully saved to DB. */ - private fun saveContributionsToDB(contributions: List) { + private fun saveContributionsToDB(contributions: List, onRefreshFinish: () -> Unit) { compositeDisposable.add( repository .save(contributions) .subscribeOn(ioThreadScheduler) .subscribe { longs: List? -> + onRefreshFinish() repository["last_fetch_timestamp"] = System.currentTimeMillis() }, ) diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListContract.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListContract.java index 58bd2783d..0d0a19436 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListContract.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListContract.java @@ -1,5 +1,6 @@ package fr.free.nrw.commons.contributions; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import fr.free.nrw.commons.BasePresenter; /** @@ -17,5 +18,8 @@ public class ContributionsListContract { } public interface UserActionListener extends BasePresenter { + + void refreshList(SwipeRefreshLayout swipeRefreshLayout); + } } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java index 509d1eb95..df65a91cc 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListFragment.java @@ -191,6 +191,15 @@ public class ContributionsListFragment extends CommonsDaggerSupportFragment impl initAdapter(); + // pull down to refresh only enabled for self user. + if(Objects.equals(sessionManager.getUserName(), userName)){ + binding.swipeRefreshLayout.setOnRefreshListener(() -> { + contributionsListPresenter.refreshList(binding.swipeRefreshLayout); + }); + } else { + binding.swipeRefreshLayout.setEnabled(false); + } + return binding.getRoot(); } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListPresenter.java b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListPresenter.java index 735ff63d4..100c8be03 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListPresenter.java +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsListPresenter.java @@ -8,14 +8,15 @@ import androidx.paging.DataSource; import androidx.paging.DataSource.Factory; import androidx.paging.LivePagedListBuilder; import androidx.paging.PagedList; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import fr.free.nrw.commons.contributions.ContributionsListContract.UserActionListener; -import fr.free.nrw.commons.di.CommonsApplicationModule; import io.reactivex.Scheduler; import io.reactivex.disposables.CompositeDisposable; -import java.util.Arrays; import java.util.Collections; import javax.inject.Inject; import javax.inject.Named; +import kotlin.Unit; +import kotlin.jvm.functions.Function0; /** * The presenter class for Contributions @@ -95,4 +96,17 @@ public class ContributionsListPresenter implements UserActionListener { contributionBoundaryCallback.dispose(); } + /** + * It is used to refresh list. + * + * @param swipeRefreshLayout used to stop refresh animation when + * refresh finishes. + */ + @Override + public void refreshList(final SwipeRefreshLayout swipeRefreshLayout) { + contributionBoundaryCallback.refreshList(() -> { + swipeRefreshLayout.setRefreshing(false); + return Unit.INSTANCE; + }); + } } diff --git a/app/src/main/res/layout/fragment_contributions_list.xml b/app/src/main/res/layout/fragment_contributions_list.xml index b490c35ff..41121c92a 100644 --- a/app/src/main/res/layout/fragment_contributions_list.xml +++ b/app/src/main/res/layout/fragment_contributions_list.xml @@ -1,125 +1,129 @@ - + + + android:orientation="vertical"> + + android:id="@+id/noContributionsYet" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:layout_marginEnd="@dimen/tiny_gap" + android:layout_marginRight="@dimen/tiny_gap" + android:gravity="center" + android:text="@string/no_uploads" + android:visibility="gone" /> + android:visibility="gone" /> - + - + + + + android:contentDescription="@string/add_contribution_from_camera" + android:tint="@color/button_blue" + android:visibility="gone" + app:backgroundTint="@color/main_background_light" + app:elevation="@dimen/tiny_margin" + app:fabSize="mini" + app:srcCompat="@drawable/ic_photo_camera_white_24dp" + app:useCompatPadding="true" /> - + - + - - - + - + + diff --git a/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt b/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt index 19b36521d..11b5fe9a2 100644 --- a/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt +++ b/app/src/test/kotlin/fr/free/nrw/commons/contributions/ContributionBoundaryCallbackTest.kt @@ -19,6 +19,7 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.verifyNoInteractions import org.mockito.MockitoAnnotations import java.lang.reflect.Method +import kotlin.reflect.jvm.internal.impl.builtins.functions.FunctionTypeKind /** * The unit test class for ContributionBoundaryCallbackTest @@ -99,9 +100,10 @@ class ContributionBoundaryCallbackTest { val method: Method = ContributionBoundaryCallback::class.java.getDeclaredMethod( "fetchContributions", + Function0::class.java ) method.isAccessible = true - method.invoke(contributionBoundaryCallback) + method.invoke(contributionBoundaryCallback, {}) verify(repository).save(anyList()) verify(mediaClient).getMediaListForUser(anyString()) } @@ -113,9 +115,10 @@ class ContributionBoundaryCallbackTest { val method: Method = ContributionBoundaryCallback::class.java.getDeclaredMethod( "fetchContributions", + Function0::class.java ) method.isAccessible = true - method.invoke(contributionBoundaryCallback) + method.invoke(contributionBoundaryCallback, {}) verifyNoInteractions(repository) verify(mediaClient).getMediaListForUser(anyString()) }