This commit is contained in:
SK 2024-10-27 22:45:14 +09:00 committed by GitHub
commit c0a74ccb06
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 284 additions and 268 deletions

View file

@ -1,194 +1,203 @@
package fr.free.nrw.commons.upload package fr.free.nrw.commons.upload
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.paging.PagedList import androidx.paging.PagedList
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import fr.free.nrw.commons.CommonsApplication import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.R import fr.free.nrw.commons.R
import fr.free.nrw.commons.contributions.Contribution import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.databinding.FragmentPendingUploadsBinding import fr.free.nrw.commons.databinding.FragmentPendingUploadsBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog import fr.free.nrw.commons.upload.worker.WorkRequestHelper
import fr.free.nrw.commons.utils.ViewUtil import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import java.util.Locale import fr.free.nrw.commons.utils.ViewUtil
import javax.inject.Inject import java.util.Locale
import javax.inject.Inject
/**
* Fragment for showing pending uploads in Upload Progress Activity. This fragment provides /**
* functionality for the user to pause uploads. * Fragment for showing pending uploads in Upload Progress Activity. This fragment provides
*/ * functionality for the user to pause uploads.
class PendingUploadsFragment : */
CommonsDaggerSupportFragment(), class PendingUploadsFragment :
PendingUploadsContract.View, CommonsDaggerSupportFragment(),
PendingUploadsAdapter.Callback { PendingUploadsContract.View,
@Inject PendingUploadsAdapter.Callback {
lateinit var pendingUploadsPresenter: PendingUploadsPresenter @Inject
lateinit var pendingUploadsPresenter: PendingUploadsPresenter
private lateinit var binding: FragmentPendingUploadsBinding
private lateinit var binding: FragmentPendingUploadsBinding
private lateinit var uploadProgressActivity: UploadProgressActivity
private lateinit var uploadProgressActivity: UploadProgressActivity
private lateinit var adapter: PendingUploadsAdapter
private lateinit var adapter: PendingUploadsAdapter
private var contributionsSize = 0
var contributionsList = ArrayList<Contribution>() private var contributionsSize = 0
var contributionsList = ArrayList<Contribution>()
override fun onAttach(context: Context) {
super.onAttach(context) override fun onAttach(context: Context) {
if (context is UploadProgressActivity) { super.onAttach(context)
uploadProgressActivity = context if (context is UploadProgressActivity) {
} uploadProgressActivity = context
} }
}
override fun onCreateView(
inflater: LayoutInflater, override fun onCreateView(
container: ViewGroup?, inflater: LayoutInflater,
savedInstanceState: Bundle?, container: ViewGroup?,
): View? { savedInstanceState: Bundle?,
super.onCreate(savedInstanceState) ): View? {
binding = FragmentPendingUploadsBinding.inflate(inflater, container, false) super.onCreate(savedInstanceState)
pendingUploadsPresenter.onAttachView(this) binding = FragmentPendingUploadsBinding.inflate(inflater, container, false)
initAdapter() pendingUploadsPresenter.onAttachView(this)
return binding.root initAdapter()
} return binding.root
}
fun initAdapter() {
adapter = PendingUploadsAdapter(this) fun initAdapter() {
} adapter = PendingUploadsAdapter(this)
}
override fun onViewCreated(
view: View, override fun onViewCreated(
savedInstanceState: Bundle?, view: View,
) { savedInstanceState: Bundle?,
super.onViewCreated(view, savedInstanceState) ) {
initRecyclerView() super.onViewCreated(view, savedInstanceState)
} initRecyclerView()
}
/**
* Initializes the recycler view. /**
*/ * Initializes the recycler view.
fun initRecyclerView() { */
binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context)) fun initRecyclerView() {
binding.pendingUploadsRecyclerView.adapter = adapter binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context))
pendingUploadsPresenter!!.setup() binding.pendingUploadsRecyclerView.adapter = adapter
pendingUploadsPresenter!!.totalContributionList.observe( pendingUploadsPresenter!!.setup()
viewLifecycleOwner, pendingUploadsPresenter!!.totalContributionList.observe(
) { list: PagedList<Contribution?> -> viewLifecycleOwner,
contributionsSize = list.size ) { list: PagedList<Contribution?> ->
contributionsList = ArrayList() contributionsSize = list.size
var pausedOrQueuedUploads = 0 contributionsList = ArrayList()
list.forEach { var pausedOrQueuedUploads = 0
if (it != null) { list.forEach {
if (it.state == Contribution.STATE_PAUSED || if (it != null) {
it.state == Contribution.STATE_QUEUED || if (it.state == Contribution.STATE_PAUSED ||
it.state == Contribution.STATE_IN_PROGRESS it.state == Contribution.STATE_QUEUED ||
) { it.state == Contribution.STATE_IN_PROGRESS
contributionsList.add(it) ) {
} contributionsList.add(it)
if (it.state == Contribution.STATE_PAUSED || }
it.state == Contribution.STATE_QUEUED if (it.state == Contribution.STATE_PAUSED ||
) { it.state == Contribution.STATE_QUEUED
pausedOrQueuedUploads++ ) {
} pausedOrQueuedUploads++
} }
} }
if (contributionsSize == 0) { }
binding.nopendingTextView.visibility = View.VISIBLE if (contributionsSize == 0) {
binding.pendingUplaodsLl.visibility = View.GONE binding.nopendingTextView.visibility = View.VISIBLE
uploadProgressActivity.hidePendingIcons() binding.pendingUplaodsLl.visibility = View.GONE
} else { uploadProgressActivity.hidePendingIcons()
binding.nopendingTextView.visibility = View.GONE } else {
binding.pendingUplaodsLl.visibility = View.VISIBLE binding.nopendingTextView.visibility = View.GONE
adapter.submitList(list) binding.pendingUplaodsLl.visibility = View.VISIBLE
binding.progressTextView.setText(contributionsSize.toString() + " uploads left") adapter.submitList(list)
if ((pausedOrQueuedUploads == contributionsSize) || CommonsApplication.isPaused) { binding.progressTextView.setText(contributionsSize.toString() + " uploads left")
uploadProgressActivity.setPausedIcon(true)
} else { if ((pausedOrQueuedUploads == contributionsSize) || CommonsApplication.isPaused) {
uploadProgressActivity.setPausedIcon(false) //As this function would be triggered multiple times by one tap
} //Add new checking to enable/ disable the pause button for better UI
} if (!WorkRequestHelper.Companion.getisUploadWorkerRunning()) {
} uploadProgressActivity.setPausedIcon(true)
} }
/** } else {
* Cancels a specific upload after getting a confirmation from the user using Dialog. if (WorkRequestHelper.Companion.getisUploadWorkerRunning()) {
*/ uploadProgressActivity.setPausedIcon(false)
override fun deleteUpload(contribution: Contribution?) { }
showAlertDialog( }
requireActivity(), }
String.format( }
Locale.getDefault(), }
requireActivity().getString(R.string.cancelling_upload),
), /**
String.format( * Cancels a specific upload after getting a confirmation from the user using Dialog.
Locale.getDefault(), */
requireActivity().getString(R.string.cancel_upload_dialog), override fun deleteUpload(contribution: Contribution?) {
), showAlertDialog(
String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)), requireActivity(),
String.format(Locale.getDefault(), requireActivity().getString(R.string.no)), String.format(
{ Locale.getDefault(),
ViewUtil.showShortToast(context, R.string.cancelling_upload) requireActivity().getString(R.string.cancelling_upload),
pendingUploadsPresenter.deleteUpload( ),
contribution, String.format(
this.requireContext().applicationContext, Locale.getDefault(),
) requireActivity().getString(R.string.cancel_upload_dialog),
}, ),
{}, String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)),
) String.format(Locale.getDefault(), requireActivity().getString(R.string.no)),
} {
ViewUtil.showShortToast(context, R.string.cancelling_upload)
/** pendingUploadsPresenter.deleteUpload(
* Restarts all the paused uploads. contribution,
*/ this.requireContext().applicationContext,
fun restartUploads() { )
if (contributionsList != null) { },
pendingUploadsPresenter.restartUploads( {},
contributionsList, )
0, }
this.requireContext().applicationContext,
) /**
} * Restarts all the paused uploads.
} */
fun restartUploads() {
/** if (contributionsList != null) {
* Pauses all the ongoing uploads. pendingUploadsPresenter.restartUploads(
*/ contributionsList,
fun pauseUploads() { 0,
pendingUploadsPresenter.pauseUploads() this.requireContext().applicationContext,
} )
}
/** }
* Cancels all the uploads after getting a confirmation from the user using Dialog.
*/ /**
fun deleteUploads() { * Pauses all the ongoing uploads.
showAlertDialog( */
requireActivity(), fun pauseUploads() {
String.format( pendingUploadsPresenter.pauseUploads()
Locale.getDefault(), }
requireActivity().getString(R.string.cancelling_all_the_uploads),
), /**
String.format( * Cancels all the uploads after getting a confirmation from the user using Dialog.
Locale.getDefault(), */
requireActivity().getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads), fun deleteUploads() {
), showAlertDialog(
String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)), requireActivity(),
String.format(Locale.getDefault(), requireActivity().getString(R.string.no)), String.format(
{ Locale.getDefault(),
ViewUtil.showShortToast(context, R.string.cancelling_upload) requireActivity().getString(R.string.cancelling_all_the_uploads),
uploadProgressActivity.hidePendingIcons() ),
pendingUploadsPresenter.deleteUploads( String.format(
listOf( Locale.getDefault(),
Contribution.STATE_QUEUED, requireActivity().getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads),
Contribution.STATE_IN_PROGRESS, ),
Contribution.STATE_PAUSED, String.format(Locale.getDefault(), requireActivity().getString(R.string.yes)),
), String.format(Locale.getDefault(), requireActivity().getString(R.string.no)),
) {
}, ViewUtil.showShortToast(context, R.string.cancelling_upload)
{}, uploadProgressActivity.hidePendingIcons()
) pendingUploadsPresenter.deleteUploads(
} listOf(
} Contribution.STATE_QUEUED,
Contribution.STATE_IN_PROGRESS,
Contribution.STATE_PAUSED,
),
)
},
{},
)
}
}

View file

@ -1,74 +1,81 @@
package fr.free.nrw.commons.upload.worker package fr.free.nrw.commons.upload.worker
import android.content.Context import android.content.Context
import androidx.work.BackoffPolicy import androidx.work.BackoffPolicy
import androidx.work.Constraints import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkRequest.Companion.MIN_BACKOFF_MILLIS import androidx.work.WorkRequest.Companion.MIN_BACKOFF_MILLIS
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
* Helper class for all the one time work requests * Helper class for all the one time work requests
*/ */
class WorkRequestHelper { class WorkRequestHelper {
companion object { companion object {
private var isUploadWorkerRunning = false private var isUploadWorkerRunning = false
private val lock = Object() private val lock = Object()
fun makeOneTimeWorkRequest( fun makeOneTimeWorkRequest(
context: Context, context: Context,
existingWorkPolicy: ExistingWorkPolicy, existingWorkPolicy: ExistingWorkPolicy,
) { ) {
synchronized(lock) { synchronized(lock) {
if (isUploadWorkerRunning) { if (isUploadWorkerRunning) {
Timber.e("UploadWorker is already running. Cannot start another instance.") Timber.e("UploadWorker is already running. Cannot start another instance.")
return return
} else { } else {
Timber.e("Setting isUploadWorkerRunning to true") Timber.e("Setting isUploadWorkerRunning to true")
isUploadWorkerRunning = true isUploadWorkerRunning = true
} }
} }
/* Set backoff criteria for the work request /* Set backoff criteria for the work request
The default backoff policy is EXPONENTIAL, but while testing we found that it The default backoff policy is EXPONENTIAL, but while testing we found that it
too long for the uploads to finish. So, set the backoff policy as LINEAR with the too long for the uploads to finish. So, set the backoff policy as LINEAR with the
minimum backoff delay value of 10 seconds. minimum backoff delay value of 10 seconds.
More details on when exactly it is retried: More details on when exactly it is retried:
https://developer.android.com/guide/background/persistent/getting-started/define-work#retries_backoff https://developer.android.com/guide/background/persistent/getting-started/define-work#retries_backoff
*/ */
val constraints: Constraints = val constraints: Constraints =
Constraints Constraints
.Builder() .Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) .setRequiredNetworkType(NetworkType.CONNECTED)
.build() .build()
val uploadRequest: OneTimeWorkRequest = val uploadRequest: OneTimeWorkRequest =
OneTimeWorkRequest OneTimeWorkRequest
.Builder(UploadWorker::class.java) .Builder(UploadWorker::class.java)
.setBackoffCriteria( .setBackoffCriteria(
BackoffPolicy.LINEAR, BackoffPolicy.LINEAR,
MIN_BACKOFF_MILLIS, MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS, TimeUnit.MILLISECONDS,
).setConstraints(constraints) ).setConstraints(constraints)
.build() .build()
WorkManager.getInstance(context).enqueueUniqueWork( WorkManager.getInstance(context).enqueueUniqueWork(
UploadWorker::class.java.simpleName, UploadWorker::class.java.simpleName,
existingWorkPolicy, existingWorkPolicy,
uploadRequest, uploadRequest,
) )
} }
/** /**
* Sets the flag isUploadWorkerRunning to`false` allowing new worker to be started. * Sets the flag isUploadWorkerRunning to`false` allowing new worker to be started.
*/ */
fun markUploadWorkerAsStopped() { fun markUploadWorkerAsStopped() {
synchronized(lock) { synchronized(lock) {
isUploadWorkerRunning = false isUploadWorkerRunning = false
} }
} }
}
} /**
* Provide a function for other class to get the flag isUploadWorkerRunning
*/
fun getisUploadWorkerRunning(): Boolean {
return isUploadWorkerRunning;
}
}
}