Skip to content

Fix infinite loading circular progress bar after nominating for deletion #6324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 25, 2025

Conversation

rohit9625
Copy link
Collaborator

Description (required)

Fixes #5531

What changes did you make and why?
Parneet fixed the issue in his PR #5610 by making changes in the MediaDetailFragment.java file. However, that file was migrated to Kotlin and caused merge conflicts. So, I pull from his branch, copy the changes to the migrated file and refactor the related code for better code quality.

Tests performed (required)

Tested prodDebug on Samsung A14 with API level 34.

Screenshots (for UI changes only)

After_Fix_Progress_Bar.mp4

Copy link
Member

@nicolas-raoul nicolas-raoul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried this branch and unfortunately when I tap the red button the progress thing kept circling fo several minutes.

Also, the picture it was about did not get the nomination banner:
https://commons.m.wikimedia.org/wiki/File%3AOktamov_Rozimattilla.jpg

@rohit9625
Copy link
Collaborator Author

rohit9625 commented May 26, 2025

Which variant did you tried? I tried on prodDebug and it worked. Also, what about the Toast message?

@nicolas-raoul
Copy link
Member

prodDebug too.
I saw no toast.
would a logcat help?

@rohit9625
Copy link
Collaborator Author

prodDebug too. I saw no toast. would a logcat help?

Yes, the logs would be helpful here, thanks :)

@rohit9625
Copy link
Collaborator Author

rohit9625 commented May 27, 2025

I just found out that @parneet-guraya was already working on the fix. I just missed it and looked at @neeldoshii's draft PR. However, both of them are not working for @nicolas-raoul. Could you please try to record a screencast while reproducing the issue and logs would be very helpful in fixing this problem?

@nicolas-raoul
Copy link
Member

Here is all I see in logcat after entering a deletion reason ad tapping OK:

05-27 22:52:50.729 14770 14770 D WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): android.view.ViewRootImpl$$ExternalSyntheticLambda12@d3fc3f3
05-27 22:52:50.730 14770 14770 D WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): null
05-27 22:52:50.736 14770 14797 D HWUI    : endAllActiveAnimators on 0xb40000726ea8eb40 (RippleDrawable) with handle 0xb4000071eea3f5d0
05-27 22:52:50.740 14770 15795 V OkHttp  : --> GET https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/feedback.py?user=Syced
05-27 22:52:50.740 14770 15795 V OkHttp  : --> END GET
05-27 22:52:50.798 14770 14770 D InsetsController: hide(ime(), fromIme=false)
05-27 22:52:50.798 14770 14770 I ImeTracker: fr.free.nrw.commons:bc489dd2: onCancelled at PHASE_CLIENT_ALREADY_HIDDEN
05-27 22:52:50.807 14770 14770 W RemoteInputConnectionImpl: requestCursorUpdates on inactive InputConnection
05-27 22:52:50.827 14770 14770 E ImeBackDispatcher: Ime callback not found. Ignoring unregisterReceivedCallback. callbackId: 196534055 remaining callbacks: 0
05-27 22:52:50.877 14770 14770 D WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): android.view.ImeBackAnimationController@39697e9
05-27 22:52:50.897 14770 14770 D WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): android.view.ViewRootImpl$$ExternalSyntheticLambda12@51f3c0d

@nicolas-raoul
Copy link
Member

Do not hesitate to add much more logcat calls everywhere, then I can provide another log. :-)

@rohit9625
Copy link
Collaborator Author

I've added some log statements depending on the expected flow of operation. Please try to repro the issue again and also a screencast as well.

@nicolas-raoul
Copy link
Member

nicolas-raoul commented May 28, 2025

Thanks!

Here is what I am getting on Pixel 7 after entering a deletion reason and tapping OK:

05-28 22:50:56.051 32573 32724 I ree.nrw.commons: Explicit concurrent mark compact GC freed 416KB AllocSpace bytes, 0(0B) LOS objects, 75% free, 17MB/70MB, paused 931us,4.079ms total 75.911ms
05-28 22:50:56.415 32573 32573 D ReasonBuilder: Fetching article number
05-28 22:50:56.416 32573 32573 D ReasonBuilder: Fetching achievements for Syced
05-28 22:50:56.416 32573 32573 D OkHttpJsonApiClient: Url : https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/feedback.py and User Name : Syced
05-28 22:50:56.418 32573 32573 W WindowOnBackDispatcher: sendCancelIfRunning: isInProgress=false callback=android.view.ViewRootImpl$$ExternalSyntheticLambda13@a1a26c3
05-28 22:50:56.418 32573  4297 D OkHttpJsonApiClient: Formatted URL: https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/feedback.py
05-28 22:50:56.419 32573  4297 V OkHttp  : --> GET https://tools.wmflabs.org/commons-android-app/tool-commons-android-app/feedback.py?user=Syced
05-28 22:50:56.419 32573  4297 V OkHttp  : --> END GET
05-28 22:50:56.420 32573 32734 D HWUI    : endAllActiveAnimators on 0xb400007b5f47c660 (RippleDrawable) with handle 0xb400007b6f409be0
05-28 22:50:56.469 32573 32573 D InsetsController: hide(ime(), fromIme=false)
05-28 22:50:56.469 32573 32573 I ImeTracker: fr.free.nrw.commons:35c1bd6a: onCancelled at PHASE_CLIENT_ALREADY_HIDDEN
05-28 22:50:58.240 32573 32724 I ree.nrw.commons: Explicit concurrent mark compact GC freed 1016KB AllocSpace bytes, 2(104KB) LOS objects, 75% free, 17MB/70MB, paused 1.283ms,3.556ms total 80.383ms
05-28 22:51:00.183 32573 32573 D LocationServiceManager: on location changed
05-28 22:51:00.187 32573 32573 D ExploreMapFragment: Location slightly changed
05-28 22:51:00.188 32573 32573 D ExploreMapPresenter: Presenter updates map and listLOCATION_SLIGHTLY_CHANGED
05-28 22:51:00.189 32573 32573 D ExploreMapPresenter: Means location changed slightly
05-28 22:51:00.418 32573 32724 I ree.nrw.commons: Explicit concurrent mark compact GC freed 569KB AllocSpace bytes, 4(80KB) LOS objects, 75% free, 17MB/69MB, paused 830us,2.852ms total 71.023ms
05-28 22:51:02.605 32573 32724 I ree.nrw.commons: Explicit concurrent mark compact GC freed 176KB AllocSpace bytes, 4(80KB) LOS objects, 75% free, 17MB/69MB, paused 1.008ms,3.452ms total 80.361ms
05-28 22:51:04.792 32573 32724 I ree.nrw.commons: Explicit concurrent mark compact GC freed 128KB AllocSpace bytes, 0(0B) LOS objects, 75% free, 17MB/69MB, paused 876us,3.827ms total 79.337ms
05-28 22:51:05.174 32573 32573 D LocationServiceManager: on location changed
05-28 22:51:05.174 32573 32573 D ExploreMapFragment: Location slightly changed
05-28 22:51:05.174 32573 32573 D ExploreMapPresenter: Presenter updates map and listLOCATION_SLIGHTLY_CHANGED
05-28 22:51:05.175 32573 32573 D ExploreMapPresenter: Means location changed slightly
05-28 22:51:06.172 32573 32573 D LocationServiceManager: on location changed
05-28 22:51:06.173 32573 32573 D ExploreMapFragment: Location slightly changed
05-28 22:51:06.173 32573 32573 D ExploreMapPresenter: Presenter updates map and listLOCATION_SLIGHTLY_CHANGED
05-28 22:51:06.174 32573 32573 D ExploreMapPresenter: Means location changed slightly
[...]
[many unrelated messages about location and garbage collection]
[...]
05-28 22:54:55.361 32573  4297 V OkHttp  : <-- HTTP FAILED: java.net.SocketTimeoutException: timeout
05-28 22:54:55.369 32573 32573 E MediaDetailFragment: Error while nominating for deletion: timeout
05-28 22:54:55.369 32573 32573 W System.err: java.net.SocketTimeoutException: timeout
05-28 22:54:55.370 32573 32573 W System.err: 	at okhttp3.internal.http2.Http2Stream$StreamTimeout.newTimeoutException(Http2Stream.kt:675)
05-28 22:54:55.370 32573 32573 W System.err: 	at okhttp3.internal.http2.Http2Stream$StreamTimeout.exitAndThrowIfTimedOut(Http2Stream.kt:684)
05-28 22:54:55.370 32573 32573 W System.err: 	at okhttp3.internal.http2.Http2Stream.takeHeaders(Http2Stream.kt:143)
05-28 22:54:55.371 32573 32573 W System.err: 	at okhttp3.internal.http2.Http2ExchangeCodec.readResponseHeaders(Http2ExchangeCodec.kt:96)
05-28 22:54:55.371 32573 32573 W System.err: 	at okhttp3.internal.connection.Exchange.readResponseHeaders(Exchange.kt:106)
05-28 22:54:55.371 32573 32573 W System.err: 	at okhttp3.internal.http.CallServerInterceptor.intercept(CallServerInterceptor.kt:79)
05-28 22:54:55.371 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.372 32573 32573 W System.err: 	at okhttp3.internal.connection.ConnectInterceptor.intercept(ConnectInterceptor.kt:34)
05-28 22:54:55.372 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.372 32573 32573 W System.err: 	at okhttp3.internal.cache.CacheInterceptor.intercept(CacheInterceptor.kt:95)
05-28 22:54:55.372 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.373 32573 32573 W System.err: 	at okhttp3.internal.http.BridgeInterceptor.intercept(BridgeInterceptor.kt:83)
05-28 22:54:55.373 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.373 32573 32573 W System.err: 	at okhttp3.internal.http.RetryAndFollowUpInterceptor.intercept(RetryAndFollowUpInterceptor.kt:76)
05-28 22:54:55.373 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.374 32573 32573 W System.err: 	at okhttp3.logging.HttpLoggingInterceptor.intercept(HttpLoggingInterceptor.kt:221)
05-28 22:54:55.374 32573 32573 W System.err: 	at okhttp3.internal.http.RealInterceptorChain.proceed(RealInterceptorChain.kt:109)
05-28 22:54:55.374 32573 32573 W System.err: 	at okhttp3.internal.connection.RealCall.getResponseWithInterceptorChain$okhttp(RealCall.kt:201)
05-28 22:54:55.374 32573 32573 W System.err: 	at okhttp3.internal.connection.RealCall.execute(RealCall.kt:154)
05-28 22:54:55.374 32573 32573 W System.err: 	at fr.free.nrw.commons.mwapi.OkHttpJsonApiClient.getAchievements$lambda$5(OkHttpJsonApiClient.kt:274)
05-28 22:54:55.375 32573 32573 W System.err: 	at fr.free.nrw.commons.mwapi.OkHttpJsonApiClient.$r8$lambda$C43DlEx4bcHOhyTBOAxdle8hOfc(Unknown Source:0)
05-28 22:54:55.375 32573 32573 W System.err: 	at fr.free.nrw.commons.mwapi.OkHttpJsonApiClient$$ExternalSyntheticLambda2.call(D8$$SyntheticClass:0)
05-28 22:54:55.375 32573 32573 W System.err: 	at io.reactivex.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:44)
05-28 22:54:55.376 32573 32573 W System.err: 	at io.reactivex.Single.subscribe(Single.java:3603)
05-28 22:54:55.376 32573 32573 W System.err: 	at io.reactivex.internal.operators.single.SingleMap.subscribeActual(SingleMap.java:34)
05-28 22:54:55.376 32573 32573 W System.err: 	at io.reactivex.Single.subscribe(Single.java:3603)
05-28 22:54:55.376 32573 32573 W System.err: 	at io.reactivex.internal.operators.single.SingleFlatMap.subscribeActual(SingleFlatMap.java:36)
05-28 22:54:55.377 32573 32573 W System.err: 	at io.reactivex.Single.subscribe(Single.java:3603)
05-28 22:54:55.377 32573 32573 W System.err: 	at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)
05-28 22:54:55.377 32573 32573 W System.err: 	at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:578)
05-28 22:54:55.377 32573 32573 W System.err: 	at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)
05-28 22:54:55.378 32573 32573 W System.err: 	at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)
05-28 22:54:55.378 32573 32573 W System.err: 	at java.util.concurrent.FutureTask.run(FutureTask.java:264)
05-28 22:54:55.378 32573 32573 W System.err: 	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
05-28 22:54:55.378 32573 32573 W System.err: 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
05-28 22:54:55.378 32573 32573 W System.err: 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
05-28 22:54:55.379 32573 32573 W System.err: 	at java.lang.Thread.run(Thread.java:1012)
05-28 22:54:55.380 32573 32573 W System.err: 	Suppressed: java.io.IOException: unexpected end of stream on https://tools.wmflabs.org/...
05-28 22:54:55.380 32573 32573 W System.err: 		at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.kt:202)
05-28 22:54:55.380 32573 32573 W System.err: 		... 33 more
05-28 22:54:55.382 32573 32573 W System.err: 	Caused by: java.io.EOFException: \n not found: limit=0 content=…
05-28 22:54:55.382 32573 32573 W System.err: 		at okio.RealBufferedSource.readUtf8LineStrict(RealBufferedSource.kt:332)
05-28 22:54:55.382 32573 32573 W System.err: 		at okhttp3.internal.http1.HeadersReader.readLine(HeadersReader.kt:29)
05-28 22:54:55.382 32573 32573 W System.err: 		at okhttp3.internal.http1.Http1ExchangeCodec.readResponseHeaders(Http1ExchangeCodec.kt:178)
05-28 22:54:55.382 32573 32573 W System.err: 		... 33 more
05-28 22:54:55.383 32573 32573 W System.err: 	Suppressed: java.net.SocketException: Software caused connection abort
05-28 22:54:55.383 32573 32573 W System.err: 		at java.net.SocketInputStream.socketRead0(Native Method)
05-28 22:54:55.383 32573 32573 W System.err: 		at java.net.SocketInputStream.socketRead(SocketInputStream.java:118)
05-28 22:54:55.383 32573 32573 W System.err: 		at java.net.SocketInputStream.read(SocketInputStream.java:173)
05-28 22:54:55.383 32573 32573 W System.err: 		at java.net.SocketInputStream.read(SocketInputStream.java:143)
05-28 22:54:55.384 32573 32573 W System.err: 		at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readFromSocket(ConscryptEngineSocket.java:989)
05-28 22:54:55.384 32573 32573 W System.err: 		at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.processDataFromSocket(ConscryptEngineSocket.java:953)
05-28 22:54:55.384 32573 32573 W System.err: 		at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.readUntilDataAvailable(ConscryptEngineSocket.java:868)
05-28 22:54:55.384 32573 32573 W System.err: 		at com.android.org.conscrypt.ConscryptEngineSocket$SSLInputStream.read(ConscryptEngineSocket.java:841)
05-28 22:54:55.384 32573 32573 W System.err: 		at okio.InputStreamSource.read(JvmOkio.kt:94)
05-28 22:54:55.384 32573 32573 W System.err: 		at okio.AsyncTimeout$source$1.read(AsyncTimeout.kt:125)
05-28 22:54:55.386 32573 32573 W System.err: 		at okio.RealBufferedSource.request(RealBufferedSource.kt:206)
05-28 22:54:55.386 32573 32573 W System.err: 		at okio.RealBufferedSource.require(RealBufferedSource.kt:199)
05-28 22:54:55.386 32573 32573 W System.err: 		at okhttp3.internal.http2.Http2Reader.nextFrame(Http2Reader.kt:89)
05-28 22:54:55.386 32573 32573 W System.err: 		at okhttp3.internal.http2.Http2Connection$ReaderRunnable.invoke(Http2Connection.kt:618)
05-28 22:54:55.386 32573 32573 W System.err: 		at okhttp3.internal.http2.Http2Connection$ReaderRunnable.invoke(Http2Connection.kt:609)
05-28 22:54:55.387 32573 32573 W System.err: 		at okhttp3.internal.concurrent.TaskQueue$execute$1.runOnce(TaskQueue.kt:98)
05-28 22:54:55.387 32573 32573 W System.err: 		at okhttp3.internal.concurrent.TaskRunner.runTask(TaskRunner.kt:116)
05-28 22:54:55.387 32573 32573 W System.err: 		at okhttp3.internal.concurrent.TaskRunner.access$runTask(TaskRunner.kt:42)
05-28 22:54:55.387 32573 32573 W System.err: 		at okhttp3.internal.concurrent.TaskRunner$runnable$1.run(TaskRunner.kt:65)
05-28 22:54:55.387 32573 32573 W System.err: 		... 3 more

Strangely the app is otherwise working great, I even reinstalled it and it is loading all thumbnails very quickly from the Internet.
I am not sure why it is fetching my achievements, this seems unnecessary. By the way, achievements do not work for me because I have 30000 contributions, that;s a different issue but somehow there might be some kind of weird relationship between the two?

Screencast taken at the same time as the logcat above:

395.mp4

@rohit9625
Copy link
Collaborator Author

Thanks for the logs @nicolas-raoul
It seems like a timeout error. I'll look into why it happened; perhaps there's an issue on the backend side, such as the API not returning the correct response or needing additional information to process the request. However, for the frontend we should show an error message to the user, eg. Something went wrong or Please try after some time. What do you think?

@nicolas-raoul
Copy link
Member

Both great ideas! 👍

@rohit9625
Copy link
Collaborator Author

Hi @nicolas-raoul,
I reviewed the MediaWiki API references and didn't find any useful information about marking an image for deletion. In our codebase, we are required to fetch achievements to construct the reason template by appending "No. of Articles using Image":

private fun appendArticlesUsed(feedBack: FeedbackResponse, media: Media, reason: String): String {
val reason1Template = context.getString(R.string.uploaded_by_myself)
return reason + String.format(Locale.getDefault(), reason1Template, prettyUploadedDate(media), feedBack.articlesUsingImages)
.also { Timber.i("New Reason %s", it) }
}

It's appended to the reason string which is passed when making a deletion request:

return if (checkAccount()) {
okHttpJsonApiClient
.getAchievements(sessionManager.userName)
.map { feedbackResponse -> appendArticlesUsed(feedbackResponse, media, reason) }

As you mentioned, fetching achievements for a user with thousands of contributions is time-consuming. So, we should look for some other API that can get us articlesUsingImages so that Mark for Deletion won't take much time.

From the logs:

05-28 22:51:06.174 32573 32573 D ExploreMapPresenter: Means location changed slightly
[...]
[many unrelated messages about location and garbage collection]
[...]
05-28 22:54:55.361 32573  4297 V OkHttp  : <-- HTTP FAILED: java.net.SocketTimeoutException: timeout
05-28 22:54:55.369 32573 32573 E MediaDetailFragment: Error while nominating for deletion: timeout

It seems that it took about 3 minutes before throwing SocketTimeoutException.

The handleDeletionError method executed successfully which further disables the progress bar. However, if we navigated away from the MediaDetailsFragment and an exception is thrown when we were not on that screen then it's possible that UI won't be updated.
That might be the reason @parneet-guraya added this block here, which ensures that the activity!= null, and if it's null then the code to disable the progress bar doesn't execute:

    /**
     * Disables Progress Bar and Update delete button text.
     */
    private fun disableProgressBar() {
        activity?.run {
            runOnUiThread(Runnable {
                binding.progressBarDeletion.visibility = View.GONE
            })
        } ?: return // Prevent NullPointerException when fragment is not attached to activity
    }

@nicolas-raoul
Copy link
Member

Great research, thanks!

I don't think it is vital to know the number of articles using the image, to perform the nomination.

So, in case a timeout happens, how about proceeding with the nomination, just with the number of articles being unknown?

@rohit9625
Copy link
Collaborator Author

Great research, thanks!

I don't think it is vital to know the number of articles using the image, to perform the nomination.

So, in case a timeout happens, how about proceeding with the nomination, just with the number of articles being unknown?

Hmm, I think that also works but we also need to have a lesser timeout because currently, it's like 3 minutes which is enough to frustrate the user. Do know the reason for appending no. of articles in the reason or if there are any other APIs that can do the work?

@nicolas-raoul
Copy link
Member

Frankly, I don't really think we need that number. Do you know what commit added it? (you can use "git blame")

@rohit9625
Copy link
Collaborator Author

Frankly, I don't really think we need that number. Do you know what commit added it? (you can use "git blame")

Okay, I am looking for the reason why it was added.

@rohit9625
Copy link
Collaborator Author

Hi @nicolas-raoul

This PR added the ReasonBuilder class in 2018. Also, you suggested to include "No. Of articles using this image" in this comment, which makes sense because Commons Admins must know how many articles using an image if that's being considered for deletion.

I also found these methods which return the file usage of a file:

suspend fun getFileUsagesOnCommons(
fileName: String?,
pageSize: Int
): FileUsagesResponse? {
return withContext(Dispatchers.IO) {
return@withContext try {

And this one:

suspend fun getGlobalFileUsages(
fileName: String?,
pageSize: Int
): GlobalFileUsagesResponse? {

Do you think we can utilize the above method which uses the query API of MediaWiki to obtain the same results, as fetching achievements is a time-consuming operation?

@nicolas-raoul
Copy link
Member

Sorry for the delay!

getGlobalFileUsages sounds good, hopefully it should return the result within a few seconds . :-)

@rohit9625
Copy link
Collaborator Author

Hi @nicolas-raoul
I tried logging the response of the other two functions, i.e., globalFileUsage and fileUsageOnCommons and this was the response:

Global File Usage: FileUsagesResponse(continueResponse=null, batchComplete=true, query=Query(pages=[Page(pageId=152702844, nameSpace=6, title=File:White Cosmos Bipinnatus.jpg, fileUsage=[FileUsage(pageId=152759610, nameSpace=2, title=User:Didym/Mobile upload/2024 September 16-20, redirect=false)])])) | Pages Fetched: 1
File Usage on Commons: FileUsagesResponse(continueResponse=null, batchComplete=true, query=Query(pages=[Page(pageId=152702844, nameSpace=6, title=File:White Cosmos Bipinnatus.jpg, fileUsage=[FileUsage(pageId=152759610, nameSpace=2, title=User:Didym/Mobile upload/2024 September 16-20, redirect=false)])])) | Pages Fetched: 1
Fetching achievements for Rohitverma9625

For both, we are getting only one page using this image. However, the feedbackResponse says 4 articlesUsingThisImage and 2 uniqeUsedImages:

Feedback Response: FeedbackResponse(uniqueUsedImages=2, articlesUsingImages=4, deletedUploads=3, featuredImages=FeaturedImages(qualityImages=0, featuredPicturesOnWikimediaCommons=0), thanksReceived=0, user=Rohitverma9625) | ArticlesUsingThisImage: 4

I don't think that fetching achievements can be replaced with fetching file usages. What do you think?

@rohit9625
Copy link
Collaborator Author

rohit9625 commented Jun 19, 2025

Also, I can see only one usage is displayed on the Media Details Page for the same, which I was trying to delete:

image

According to this, the file usage is correctly fetched via the globalFileUsages function. However, I noticed that we can also nominate pictures, uploaded by others, for deletion. In that case, fetching own contributions won't make sense, right? So, using globalFileUsage can be a better alternative even though it doesn't return the exact value. Let me know your thoughts about this, @nicolas-raoul and @RitikaPahwa4444 :)

Edit: The image you're trying to remove in this screencast doesn't have any usage, so it's clear that fetching our contributions is not a viable option.

@nicolas-raoul
Copy link
Member

How about changing the wording to "at least"?

@rohit9625
Copy link
Collaborator Author

That's a good idea. It will be consistent for both images either uploaded by us or someone else. Should I work on this?

@nicolas-raoul
Copy link
Member

If you have time, sure! Thanks a lot for all of your hard work. 🙂

@rohit9625
Copy link
Collaborator Author

No worries at all, I'd love to work on this as this issue is been opened for quite a long. I'll make the changes today :)

@rohit9625
Copy link
Collaborator Author

rohit9625 commented Jun 19, 2025

Hi @nicolas-raoul, I'm sorry I accidentally used fileUsageOnCommons even for the globalFileUsage in the logs of this comment. That's why we were getting the same response. I just noticed that and this is the response of globalFileUsage method:

Global File Usage: GlobalQuery(pages=[GlobalPage(pageId=152702844, nameSpace=6, title=File:White Cosmos Bipinnatus.jpg, fileUsage=[GlobalFileUsage(title=Schmuckkörbchen, wiki=de.wikipedia.org, url=https://de.wikipedia.org/wiki/Schmuckk%C3%B6rbchen), GlobalFileUsage(title=Benutzer:Molgreen/AAAA_Hilfe/Bilder-Einbindung-Größe-Dia-Show, wiki=de.wikipedia.org, url=https://de.wikipedia.org/wiki/Benutzer:Molgreen/AAAA_Hilfe/Bilder-Einbindung-Gr%C3%B6%C3%9Fe-Dia-Show), GlobalFileUsage(title=Cosmos_bipinnatus, wiki=en.wikipedia.org, url=https://en.wikipedia.org/wiki/Cosmos_bipinnatus)])])

It's only giving the pages that are outside of commons, i.e., 3 for my image, whether fileUsageOnCommons gave the pages that are used within commons, i.e., 1 for my image.

Now, it makes sense why the feedbackResponse gave 4 articlesUsingImages => globalFileUsage + fileUsageOnCommons. Should I add the result of both APIs?

@nicolas-raoul
Copy link
Member

If easily doable, we should ignore usage on Commons.

Reason: Most usage on Commons is just galleries, not useful to actual readers.

Fetching achievements is a time consuming operation and globalFileUsage gives the similar result in optimal time
@rohit9625
Copy link
Collaborator Author

Hi @nicolas-raoul, I made the changes. Please record the logs again while testing the recent changes. Also, is there any place on the Commons web page where we can see who submitted a deletion request with what reason?
I am asking because I saw some redundant code or think that the file usage is not appended in the reason being sent with deletion request.

@rohit9625
Copy link
Collaborator Author

I will fix the unit test and remove logs added for testing once the behavior is verified.

@nicolas-raoul
Copy link
Member

I just tested on a picture with doubtful copyright status, it is working great!

@rohit9625
Copy link
Collaborator Author

I just tested on a picture with doubtful copyright status, it is working great!

That's great, as I asked can we see the deletion request for a picture somewhere in the commons website because even though I replaced the API call there's some code which I think won't be sending the reason we were building so far.

@rohit9625
Copy link
Collaborator Author

Hey @nicolas-raoul, I just found this page where I can see the deletion request that I made previously:

https://commons.m.wikimedia.org/wiki/Commons:Deletion_requests/File:A_phone_holder.jpg

It's clear that the template for deletion we expect is not sent with request not even before.

@rohit9625
Copy link
Collaborator Author

rohit9625 commented Jun 22, 2025

I just tested on a picture with doubtful copyright status, it is working great!

Could you please send me the image file name or a screenshot so that I can verify?

@rohit9625
Copy link
Collaborator Author

rohit9625 commented Jun 22, 2025

I'm so sorry for bothering you again and again, but I'm just trying to provide as much information as possible about this issue.

So, I tweaked the code to include the reason we generated (which includes file usage and upload date) and used the exact value returned by the getReason() method. With this change, the final reason looks like this:

I uploaded it by mistakeUploaded by myself on Sep 16, 2024, used in 3 article(s) at least.

As you can see, the formatting doesn’t look quite right — there’s no spacing or punctuation between the main reason and the additional metadata. Additionally, the phrase “Uploaded by myself” seems somewhat redundant, as the username is already visible in the context of the deletion request. However, the metadata (date and usage count) still adds value to the reason.

image

Ideally, I would recommend this format for the deletion requests:

I uploaded it by mistake (uploaded on Sep 16, 2024, used in at least 3 article(s)).

The same format could be used when making deletion requests for images uploaded by others, but a custom reason entered by the user will be prepended before the metadata.

If we still want to add the "Uploaded by myself" string to the reason, then we make sure it will be added for images uploaded by the current user, not when nominating files uploaded by others.

Please feel free to suggest more formats as well.
@RitikaPahwa4444 @nicolas-raoul @mnalis @psh @sivaraam

@rohit9625
Copy link
Collaborator Author

Some other formats:

  • I uploaded it by mistake [uploaded on Sep 16, 2024; used in ≥3 article(s)]
  • I uploaded it by mistake – uploaded on Sep 16, 2024, used in 3+ article(s).
  • I uploaded it by mistake: uploaded on Sep 16, 2024, used in at least 3 article(s).
  • Reason: I uploaded it by mistake; Uploaded: Sep 16, 2024; Used by: 3 article(s);

@RitikaPahwa4444
Copy link
Collaborator

“Uploaded by myself” seems somewhat redundant, as the username is already visible in the context of the deletion request.

Does that work even if we nominate someone else's work for deletion?

On a side note, the changes can still be merged as they fix the issue and you can pick the formatting up as a follow-up PR :)

@rohit9625
Copy link
Collaborator Author

Does that work even if we nominate someone else's work for deletion?

Nope, doesn't work either :(

On a side note, the changes can still be merged as they fix the issue and you can pick the formatting up as a follow-up PR :)

You're right, but these are pretty minor ones, and we already have enough context of the problem in this PR, so I thought we could solve this in this PR. Anyways, I am fixing the tests and removing test logs. Let me know if I should create a separate issue for that.

@RitikaPahwa4444
Copy link
Collaborator

Nope, doesn't work either :(

Should we retain the user name then?

Let me know if I should create a separate issue for that.

Works even in this PR. I suggested that because we still had a few more cases to explore like nomination by other users and we could take that up as a separate discussion.

@rohit9625
Copy link
Collaborator Author

Should we retain the user name then?

No, we directly pass the raw reason string entered by the user without any username, date, or file usages. However, we do construct the formatted reason by calling the getReason function, but its result is not being used:

val resultSingletext: Single<Boolean> = reasonBuilder.getReason(media, reason)
.flatMap { _ ->
deleteHelper.makeDeletion(
context, media, reason
)
}

Works even in this PR. I suggested that because we still had a few more cases to explore like nomination by other users and we could take that up as a separate discussion.

Yep, you're right, I'll create a separate issue for that. Let's make this PR limited to the progress bar issue only.

@nicolas-raoul
Copy link
Member

Sorry, here is the file I nominated:
https://commons.m.wikimedia.org/wiki/File:Linaje.jpg

@nicolas-raoul
Copy link
Member

Please let me know when ready, feel free to mark as draft for now. :-)

Copy link

✅ Generated APK variants!

@rohit9625 rohit9625 requested a review from nicolas-raoul June 23, 2025 08:43
@rohit9625
Copy link
Collaborator Author

It's ready to merge @nicolas-raoul :)

Copy link

✅ Generated APK variants!

Copy link
Member

@nicolas-raoul nicolas-raoul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested with https://commons.m.wikimedia.org/wiki/File:Zainuri_Kamaruddin.jpg it works great. Thanks a lot Rohit!

@nicolas-raoul nicolas-raoul merged commit ad7ddda into commons-app:main Jun 25, 2025
1 check passed
@rohit9625 rohit9625 deleted the fix/infinite-progress-bar branch July 11, 2025 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug]: Circular Progress bar keeps on moving even though nomination for deletion is successful
4 participants