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
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.paging.PagedList
import androidx.recyclerview.widget.LinearLayoutManager
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.R
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.databinding.FragmentPendingUploadsBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import fr.free.nrw.commons.utils.ViewUtil
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.
*/
class PendingUploadsFragment :
CommonsDaggerSupportFragment(),
PendingUploadsContract.View,
PendingUploadsAdapter.Callback {
@Inject
lateinit var pendingUploadsPresenter: PendingUploadsPresenter
private lateinit var binding: FragmentPendingUploadsBinding
private lateinit var uploadProgressActivity: UploadProgressActivity
private lateinit var adapter: PendingUploadsAdapter
private var contributionsSize = 0
var contributionsList = ArrayList<Contribution>()
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is UploadProgressActivity) {
uploadProgressActivity = context
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
super.onCreate(savedInstanceState)
binding = FragmentPendingUploadsBinding.inflate(inflater, container, false)
pendingUploadsPresenter.onAttachView(this)
initAdapter()
return binding.root
}
fun initAdapter() {
adapter = PendingUploadsAdapter(this)
}
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
initRecyclerView()
}
/**
* Initializes the recycler view.
*/
fun initRecyclerView() {
binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context))
binding.pendingUploadsRecyclerView.adapter = adapter
pendingUploadsPresenter!!.setup()
pendingUploadsPresenter!!.totalContributionList.observe(
viewLifecycleOwner,
) { list: PagedList<Contribution?> ->
contributionsSize = list.size
contributionsList = ArrayList()
var pausedOrQueuedUploads = 0
list.forEach {
if (it != null) {
if (it.state == Contribution.STATE_PAUSED ||
it.state == Contribution.STATE_QUEUED ||
it.state == Contribution.STATE_IN_PROGRESS
) {
contributionsList.add(it)
}
if (it.state == Contribution.STATE_PAUSED ||
it.state == Contribution.STATE_QUEUED
) {
pausedOrQueuedUploads++
}
}
}
if (contributionsSize == 0) {
binding.nopendingTextView.visibility = View.VISIBLE
binding.pendingUplaodsLl.visibility = View.GONE
uploadProgressActivity.hidePendingIcons()
} else {
binding.nopendingTextView.visibility = View.GONE
binding.pendingUplaodsLl.visibility = View.VISIBLE
adapter.submitList(list)
binding.progressTextView.setText(contributionsSize.toString() + " uploads left")
if ((pausedOrQueuedUploads == contributionsSize) || CommonsApplication.isPaused) {
uploadProgressActivity.setPausedIcon(true)
} else {
uploadProgressActivity.setPausedIcon(false)
}
}
}
}
/**
* Cancels a specific upload after getting a confirmation from the user using Dialog.
*/
override fun deleteUpload(contribution: Contribution?) {
showAlertDialog(
requireActivity(),
String.format(
Locale.getDefault(),
requireActivity().getString(R.string.cancelling_upload),
),
String.format(
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(
contribution,
this.requireContext().applicationContext,
)
},
{},
)
}
/**
* Restarts all the paused uploads.
*/
fun restartUploads() {
if (contributionsList != null) {
pendingUploadsPresenter.restartUploads(
contributionsList,
0,
this.requireContext().applicationContext,
)
}
}
/**
* Pauses all the ongoing uploads.
*/
fun pauseUploads() {
pendingUploadsPresenter.pauseUploads()
}
/**
* Cancels all the uploads after getting a confirmation from the user using Dialog.
*/
fun deleteUploads() {
showAlertDialog(
requireActivity(),
String.format(
Locale.getDefault(),
requireActivity().getString(R.string.cancelling_all_the_uploads),
),
String.format(
Locale.getDefault(),
requireActivity().getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads),
),
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,
),
)
},
{},
)
}
}
package fr.free.nrw.commons.upload
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.paging.PagedList
import androidx.recyclerview.widget.LinearLayoutManager
import fr.free.nrw.commons.CommonsApplication
import fr.free.nrw.commons.R
import fr.free.nrw.commons.contributions.Contribution
import fr.free.nrw.commons.databinding.FragmentPendingUploadsBinding
import fr.free.nrw.commons.di.CommonsDaggerSupportFragment
import fr.free.nrw.commons.upload.worker.WorkRequestHelper
import fr.free.nrw.commons.utils.DialogUtil.showAlertDialog
import fr.free.nrw.commons.utils.ViewUtil
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.
*/
class PendingUploadsFragment :
CommonsDaggerSupportFragment(),
PendingUploadsContract.View,
PendingUploadsAdapter.Callback {
@Inject
lateinit var pendingUploadsPresenter: PendingUploadsPresenter
private lateinit var binding: FragmentPendingUploadsBinding
private lateinit var uploadProgressActivity: UploadProgressActivity
private lateinit var adapter: PendingUploadsAdapter
private var contributionsSize = 0
var contributionsList = ArrayList<Contribution>()
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is UploadProgressActivity) {
uploadProgressActivity = context
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
super.onCreate(savedInstanceState)
binding = FragmentPendingUploadsBinding.inflate(inflater, container, false)
pendingUploadsPresenter.onAttachView(this)
initAdapter()
return binding.root
}
fun initAdapter() {
adapter = PendingUploadsAdapter(this)
}
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
initRecyclerView()
}
/**
* Initializes the recycler view.
*/
fun initRecyclerView() {
binding.pendingUploadsRecyclerView.setLayoutManager(LinearLayoutManager(this.context))
binding.pendingUploadsRecyclerView.adapter = adapter
pendingUploadsPresenter!!.setup()
pendingUploadsPresenter!!.totalContributionList.observe(
viewLifecycleOwner,
) { list: PagedList<Contribution?> ->
contributionsSize = list.size
contributionsList = ArrayList()
var pausedOrQueuedUploads = 0
list.forEach {
if (it != null) {
if (it.state == Contribution.STATE_PAUSED ||
it.state == Contribution.STATE_QUEUED ||
it.state == Contribution.STATE_IN_PROGRESS
) {
contributionsList.add(it)
}
if (it.state == Contribution.STATE_PAUSED ||
it.state == Contribution.STATE_QUEUED
) {
pausedOrQueuedUploads++
}
}
}
if (contributionsSize == 0) {
binding.nopendingTextView.visibility = View.VISIBLE
binding.pendingUplaodsLl.visibility = View.GONE
uploadProgressActivity.hidePendingIcons()
} else {
binding.nopendingTextView.visibility = View.GONE
binding.pendingUplaodsLl.visibility = View.VISIBLE
adapter.submitList(list)
binding.progressTextView.setText(contributionsSize.toString() + " uploads left")
if ((pausedOrQueuedUploads == contributionsSize) || CommonsApplication.isPaused) {
//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 {
if (WorkRequestHelper.Companion.getisUploadWorkerRunning()) {
uploadProgressActivity.setPausedIcon(false)
}
}
}
}
}
/**
* Cancels a specific upload after getting a confirmation from the user using Dialog.
*/
override fun deleteUpload(contribution: Contribution?) {
showAlertDialog(
requireActivity(),
String.format(
Locale.getDefault(),
requireActivity().getString(R.string.cancelling_upload),
),
String.format(
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(
contribution,
this.requireContext().applicationContext,
)
},
{},
)
}
/**
* Restarts all the paused uploads.
*/
fun restartUploads() {
if (contributionsList != null) {
pendingUploadsPresenter.restartUploads(
contributionsList,
0,
this.requireContext().applicationContext,
)
}
}
/**
* Pauses all the ongoing uploads.
*/
fun pauseUploads() {
pendingUploadsPresenter.pauseUploads()
}
/**
* Cancels all the uploads after getting a confirmation from the user using Dialog.
*/
fun deleteUploads() {
showAlertDialog(
requireActivity(),
String.format(
Locale.getDefault(),
requireActivity().getString(R.string.cancelling_all_the_uploads),
),
String.format(
Locale.getDefault(),
requireActivity().getString(R.string.are_you_sure_that_you_want_cancel_all_the_uploads),
),
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
import android.content.Context
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkRequest.Companion.MIN_BACKOFF_MILLIS
import timber.log.Timber
import java.util.concurrent.TimeUnit
/**
* Helper class for all the one time work requests
*/
class WorkRequestHelper {
companion object {
private var isUploadWorkerRunning = false
private val lock = Object()
fun makeOneTimeWorkRequest(
context: Context,
existingWorkPolicy: ExistingWorkPolicy,
) {
synchronized(lock) {
if (isUploadWorkerRunning) {
Timber.e("UploadWorker is already running. Cannot start another instance.")
return
} else {
Timber.e("Setting isUploadWorkerRunning to true")
isUploadWorkerRunning = true
}
}
/* Set backoff criteria for the work request
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
minimum backoff delay value of 10 seconds.
More details on when exactly it is retried:
https://developer.android.com/guide/background/persistent/getting-started/define-work#retries_backoff
*/
val constraints: Constraints =
Constraints
.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val uploadRequest: OneTimeWorkRequest =
OneTimeWorkRequest
.Builder(UploadWorker::class.java)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS,
).setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
UploadWorker::class.java.simpleName,
existingWorkPolicy,
uploadRequest,
)
}
/**
* Sets the flag isUploadWorkerRunning to`false` allowing new worker to be started.
*/
fun markUploadWorkerAsStopped() {
synchronized(lock) {
isUploadWorkerRunning = false
}
}
}
}
package fr.free.nrw.commons.upload.worker
import android.content.Context
import androidx.work.BackoffPolicy
import androidx.work.Constraints
import androidx.work.ExistingWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.WorkRequest.Companion.MIN_BACKOFF_MILLIS
import timber.log.Timber
import java.util.concurrent.TimeUnit
/**
* Helper class for all the one time work requests
*/
class WorkRequestHelper {
companion object {
private var isUploadWorkerRunning = false
private val lock = Object()
fun makeOneTimeWorkRequest(
context: Context,
existingWorkPolicy: ExistingWorkPolicy,
) {
synchronized(lock) {
if (isUploadWorkerRunning) {
Timber.e("UploadWorker is already running. Cannot start another instance.")
return
} else {
Timber.e("Setting isUploadWorkerRunning to true")
isUploadWorkerRunning = true
}
}
/* Set backoff criteria for the work request
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
minimum backoff delay value of 10 seconds.
More details on when exactly it is retried:
https://developer.android.com/guide/background/persistent/getting-started/define-work#retries_backoff
*/
val constraints: Constraints =
Constraints
.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val uploadRequest: OneTimeWorkRequest =
OneTimeWorkRequest
.Builder(UploadWorker::class.java)
.setBackoffCriteria(
BackoffPolicy.LINEAR,
MIN_BACKOFF_MILLIS,
TimeUnit.MILLISECONDS,
).setConstraints(constraints)
.build()
WorkManager.getInstance(context).enqueueUniqueWork(
UploadWorker::class.java.simpleName,
existingWorkPolicy,
uploadRequest,
)
}
/**
* Sets the flag isUploadWorkerRunning to`false` allowing new worker to be started.
*/
fun markUploadWorkerAsStopped() {
synchronized(lock) {
isUploadWorkerRunning = false
}
}
/**
* Provide a function for other class to get the flag isUploadWorkerRunning
*/
fun getisUploadWorkerRunning(): Boolean {
return isUploadWorkerRunning;
}
}
}