From 9377b7c057bf4aac2cce4f40ef8e809b26c68418 Mon Sep 17 00:00:00 2001 From: Sujal-Gupta-SG Date: Sat, 4 Jan 2025 02:09:33 +0530 Subject: [PATCH 1/2] Fixed #6084 --- .../ContributionsListPresenter.java | 76 +++++++++++++++++++ .../ContributionsRemoteDataSource.kt | 8 +- 2 files changed, 83 insertions(+), 1 deletion(-) 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 100c8be03..a7c49c23e 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 @@ -2,8 +2,12 @@ package fr.free.nrw.commons.contributions; import static fr.free.nrw.commons.di.CommonsApplicationModule.IO_THREAD; +import android.os.Handler; +import android.os.Looper; + import androidx.annotation.NonNull; import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; import androidx.paging.DataSource; import androidx.paging.DataSource.Factory; import androidx.paging.LivePagedListBuilder; @@ -12,7 +16,9 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import fr.free.nrw.commons.contributions.ContributionsListContract.UserActionListener; import io.reactivex.Scheduler; import io.reactivex.disposables.CompositeDisposable; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import javax.inject.Inject; import javax.inject.Named; import kotlin.Unit; @@ -32,6 +38,15 @@ public class ContributionsListPresenter implements UserActionListener { LiveData> contributionList; + private MutableLiveData> liveData = new MutableLiveData<>(); + + private List existingContributions = new ArrayList<>(); + + // Timer for polling new contributions + private Handler pollingHandler; + private Runnable pollingRunnable; + private long pollingInterval = 1 * 60 * 1000L; // Poll every minute + @Inject ContributionsListPresenter( final ContributionBoundaryCallback contributionBoundaryCallback, @@ -87,6 +102,15 @@ public class ContributionsListPresenter implements UserActionListener { } contributionList = livePagedListBuilder.build(); + contributionList.observeForever(pagedList -> { + if (pagedList != null) { + existingContributions.clear(); + existingContributions.addAll(pagedList); + liveData.setValue(existingContributions); // Update liveData with the latest list + } + }); + // Start polling for new contributions + startPollingForNewContributions(); } @Override @@ -94,6 +118,7 @@ public class ContributionsListPresenter implements UserActionListener { compositeDisposable.clear(); contributionsRemoteDataSource.dispose(); contributionBoundaryCallback.dispose(); + stopPollingForNewContributions(); } /** @@ -109,4 +134,55 @@ public class ContributionsListPresenter implements UserActionListener { return Unit.INSTANCE; }); } + + /** + * Start polling for new contributions every 15 minutes. + */ + private void startPollingForNewContributions() { + if (pollingHandler != null) { + stopPollingForNewContributions(); + } + + pollingHandler = new Handler(Looper.getMainLooper()); + pollingRunnable = new Runnable() { + @Override + public void run() { + fetchNewContributions(); // Fetch new contributions in background + pollingHandler.postDelayed(this, pollingInterval); // Repeat after the interval + } + }; + pollingHandler.post(pollingRunnable); // Start polling immediately + } + + /** + * Stop the polling task when the view is detached or the activity is paused. + */ + private void stopPollingForNewContributions() { + if (pollingHandler != null && pollingRunnable != null) { + pollingHandler.removeCallbacks(pollingRunnable); + pollingHandler = null; + pollingRunnable = null; + } + } + + public void appendContributions(List newContributions) { + if (newContributions != null && !newContributions.isEmpty()) { + existingContributions.addAll(newContributions); + liveData.postValue(existingContributions); + } + } + /** + * Fetch new contributions from the server and append them to the existing list. + */ + private void fetchNewContributions() { + contributionsRemoteDataSource.fetchContributions(new ContributionsRemoteDataSource.LoadCallback() { + @Override + public void onResult(List newContributions) { + if (newContributions != null && !newContributions.isEmpty()) { + appendContributions(newContributions); // Add new contributions + } + } + }); + } + } diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRemoteDataSource.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRemoteDataSource.kt index e8ff01b3e..e840fe86f 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRemoteDataSource.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRemoteDataSource.kt @@ -46,7 +46,13 @@ class ContributionsRemoteDataSource /** * Fetches contributions using the MediaWiki API */ - private fun fetchContributions(callback: LoadCallback) { + public fun fetchContributions(callback: LoadCallback) { + if (userName.isNullOrEmpty()) { + Timber.e("Failed to fetch contributions: userName is null or empty") + return + } + Timber.d("Fetching contributions for user: $userName") + compositeDisposable.add( mediaClient .getMediaListForUser(userName!!) From d39a57f257bf8558a208f292d0f6a4ced6874338 Mon Sep 17 00:00:00 2001 From: Sujal-Gupta-SG Date: Sat, 4 Jan 2025 18:20:06 +0530 Subject: [PATCH 2/2] Fixed #6084 --- .../ContributionsListPresenter.java | 34 +++++++++++------ .../ContributionsRemoteDataSource.kt | 37 ++++++++++++++++--- 2 files changed, 53 insertions(+), 18 deletions(-) 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 a7c49c23e..c867511ab 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 @@ -22,7 +22,6 @@ import java.util.List; import javax.inject.Inject; import javax.inject.Named; import kotlin.Unit; -import kotlin.jvm.functions.Function0; /** * The presenter class for Contributions @@ -45,7 +44,7 @@ public class ContributionsListPresenter implements UserActionListener { // Timer for polling new contributions private Handler pollingHandler; private Runnable pollingRunnable; - private long pollingInterval = 1 * 60 * 1000L; // Poll every minute + private long pollingInterval = 24 * 60 * 60 * 1000L; // Poll every day @Inject ContributionsListPresenter( @@ -136,7 +135,7 @@ public class ContributionsListPresenter implements UserActionListener { } /** - * Start polling for new contributions every 15 minutes. + * Start polling for new contributions every 24 hour. */ private void startPollingForNewContributions() { if (pollingHandler != null) { @@ -147,7 +146,7 @@ public class ContributionsListPresenter implements UserActionListener { pollingRunnable = new Runnable() { @Override public void run() { - fetchNewContributions(); // Fetch new contributions in background + checkForNewContributions(); pollingHandler.postDelayed(this, pollingInterval); // Repeat after the interval } }; @@ -164,22 +163,33 @@ public class ContributionsListPresenter implements UserActionListener { pollingRunnable = null; } } + private String lastKnownIdentifier = null; // Declare and initialize + + /** + * Check for new contributions by comparing the latest contribution identifier. + */ + private void checkForNewContributions() { + contributionsRemoteDataSource.fetchLatestContributionIdentifier(latestIdentifier -> { + if (latestIdentifier != null && !latestIdentifier.equals(lastKnownIdentifier)) { + lastKnownIdentifier = latestIdentifier; + fetchAllContributions(); // Fetch the full list of contributions + } + return Unit.INSTANCE; // Explicitly return Unit for Kotlin compatibility + }); - public void appendContributions(List newContributions) { - if (newContributions != null && !newContributions.isEmpty()) { - existingContributions.addAll(newContributions); - liveData.postValue(existingContributions); - } } + /** * Fetch new contributions from the server and append them to the existing list. */ - private void fetchNewContributions() { - contributionsRemoteDataSource.fetchContributions(new ContributionsRemoteDataSource.LoadCallback() { + private void fetchAllContributions() { + contributionsRemoteDataSource.fetchAllContributions(new ContributionsRemoteDataSource.LoadCallback() { @Override public void onResult(List newContributions) { if (newContributions != null && !newContributions.isEmpty()) { - appendContributions(newContributions); // Add new contributions + existingContributions.clear(); + existingContributions.addAll(newContributions); + liveData.postValue(existingContributions); // Update liveData with the new list } } }); diff --git a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRemoteDataSource.kt b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRemoteDataSource.kt index e840fe86f..a62030e90 100644 --- a/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRemoteDataSource.kt +++ b/app/src/main/java/fr/free/nrw/commons/contributions/ContributionsRemoteDataSource.kt @@ -25,14 +25,14 @@ class ContributionsRemoteDataSource params: LoadInitialParams, callback: LoadInitialCallback, ) { - fetchContributions(callback) + fetchAllContributions(callback) } override fun loadAfter( params: LoadParams, callback: LoadCallback, ) { - fetchContributions(callback) + fetchAllContributions(callback) } override fun loadBefore( @@ -46,7 +46,7 @@ class ContributionsRemoteDataSource /** * Fetches contributions using the MediaWiki API */ - public fun fetchContributions(callback: LoadCallback) { + fun fetchAllContributions(callback: LoadCallback) { if (userName.isNullOrEmpty()) { Timber.e("Failed to fetch contributions: userName is null or empty") return @@ -61,9 +61,10 @@ class ContributionsRemoteDataSource Contribution(media = it, state = Contribution.STATE_COMPLETED) } }.subscribeOn(ioThreadScheduler) - .subscribe({ - callback.onResult(it) - }) { error: Throwable -> + .subscribe({ contributions -> + // Pass the contributions to the callback + callback.onResult(contributions) + }) { error: Throwable -> Timber.e( "Failed to fetch contributions: %s", error.message, @@ -71,6 +72,30 @@ class ContributionsRemoteDataSource }, ) } + /** + * Fetches the latest contribution identifier only + */ + fun fetchLatestContributionIdentifier(callback: (String?) -> Unit) { + if (userName.isNullOrEmpty()) { + Timber.e("Failed to fetch latest contribution: userName is null or empty") + return + } + Timber.d("Fetching latest contribution identifier for user: $userName") + + compositeDisposable.add( + mediaClient.getMediaListForUser(userName!!) + .map { mediaList -> + mediaList.firstOrNull()?.pageId.toString() // Extract the first contribution's pageId + } + .subscribeOn(ioThreadScheduler) + .subscribe({ latestIdentifier -> + callback(latestIdentifier) + }) { error: Throwable -> + Timber.e("Failed to fetch latest contribution identifier: %s", error.message) + callback(null) + }, + ) + } fun dispose() { compositeDisposable.dispose()