Commit 9589ff50 authored by Mugurell's avatar Mugurell Committed by Richard Pospesel
Browse files

Bug 1783561 - Allow a custom View for 3rd party downloads

This will allow clients easily implement their own UI

Backport of https://github.com/mozilla-mobile/firefox-android/commit/c53dd65718579006e174803acf683ffd44075049
parent 4ec68dfa
Loading
Loading
Loading
Loading
+61 −26
Original line number Diff line number Diff line
@@ -56,13 +56,25 @@ value class Filename(val value: String)
value class ContentSize(val value: Long)

/**
 * Action for when the positive button of a download dialog was tapped.
 * The list of all applications that can perform a download, including this application.
 */
@JvmInline
value class ThirdPartyDownloaderApps(val value: List<DownloaderApp>)

/**
 * Callback for when the user picked a certain application with which to download the current file.
 */
@JvmInline
value class ThirdPartyDownloaderAppChosenCallback(val value: (DownloaderApp) -> Unit)

/**
 * Callback for when the positive button of a download dialog was tapped.
 */
@JvmInline
value class PositiveActionCallback(val value: () -> Unit)

/**
 * Action for when the negative button of a download dialog was tapped.
 * Callback for when the negative button of a download dialog was tapped.
 */
@JvmInline
value class NegativeActionCallback(val value: () -> Unit)
@@ -86,7 +98,10 @@ value class NegativeActionCallback(val value: () -> Unit)
 * @property promptsStyling styling properties for the dialog.
 * @property shouldForwardToThirdParties Indicates if downloads should be forward to third party apps,
 * if there are multiple apps a chooser dialog will shown.
 * @property customDownloadDialog An optional delegate for showing a download dialog.
 * @property customFirstPartyDownloadDialog An optional delegate for showing a dialog for a download
 * that will be processed by the current application.
 * @property customThirdPartyDownloadDialog An optional delegate for showing a dialog for a download
 * that can be processed by multiple installed applications including the current one.
 */
@Suppress("LongParameterList", "LargeClass")
class DownloadsFeature(
@@ -101,7 +116,10 @@ class DownloadsFeature(
    private val fragmentManager: FragmentManager? = null,
    private val promptsStyling: PromptsStyling? = null,
    private val shouldForwardToThirdParties: () -> Boolean = { false },
    private val customDownloadDialog: ((Filename, ContentSize, PositiveActionCallback, NegativeActionCallback) -> Unit)? = null,
    private val customFirstPartyDownloadDialog:
    ((Filename, ContentSize, PositiveActionCallback, NegativeActionCallback) -> Unit)? = null,
    private val customThirdPartyDownloadDialog:
    ((ThirdPartyDownloaderApps, ThirdPartyDownloaderAppChosenCallback, NegativeActionCallback) -> Unit)? = null,
) : LifecycleAwareFeature, PermissionsFeature {

    var onDownloadStopped: onDownloadStopped
@@ -187,13 +205,25 @@ class DownloadsFeature(
        val shouldShowAppDownloaderDialog = shouldForwardToThirdParties() && apps.size > 1

        return if (shouldShowAppDownloaderDialog) {
            showAppDownloaderDialog(tab, download, apps)
            when (customThirdPartyDownloadDialog) {
                null -> showAppDownloaderDialog(tab, download, apps)
                else -> customThirdPartyDownloadDialog.invoke(
                    ThirdPartyDownloaderApps(apps),
                    ThirdPartyDownloaderAppChosenCallback {
                        onDownloaderAppSelected(it, tab, download)
                    },
                    NegativeActionCallback {
                        useCases.cancelDownloadRequest.invoke(tab.id, download.id)
                    },
                )
            }

            false
        } else {
            if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
                when {
                    customDownloadDialog != null && !download.skipConfirmation -> {
                        customDownloadDialog.invoke(
                    customFirstPartyDownloadDialog != null && !download.skipConfirmation -> {
                        customFirstPartyDownloadDialog.invoke(
                            Filename(download.realFilenameOrGuessed),
                            ContentSize(download.contentLength ?: 0),
                            PositiveActionCallback {
@@ -309,6 +339,20 @@ class DownloadsFeature(
    ) {
        appChooserDialog.setApps(apps)
        appChooserDialog.onAppSelected = { app ->
            onDownloaderAppSelected(app, tab, download)
        }

        appChooserDialog.onDismiss = {
            useCases.cancelDownloadRequest.invoke(tab.id, download.id)
        }

        if (!isAlreadyAppDownloaderDialog() && fragmentManager != null && !fragmentManager.isDestroyed) {
            appChooserDialog.showNow(fragmentManager, DownloadAppChooserDialog.FRAGMENT_TAG)
        }
    }

    @VisibleForTesting
    internal fun onDownloaderAppSelected(app: DownloaderApp, tab: SessionState, download: DownloadState) {
        if (app.packageName == applicationContext.packageName) {
            if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
                startDownload(download)
@@ -322,7 +366,7 @@ class DownloadsFeature(
            } catch (error: ActivityNotFoundException) {
                val errorMessage = applicationContext.getString(
                    R.string.mozac_feature_downloads_unable_to_open_third_party_app,
                        app.name
                    app.name,
                )
                Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_SHORT).show()
            }
@@ -330,15 +374,6 @@ class DownloadsFeature(
        }
    }

        appChooserDialog.onDismiss = {
            useCases.cancelDownloadRequest.invoke(tab.id, download.id)
        }

        if (!isAlreadyAppDownloaderDialog() && fragmentManager != null && !fragmentManager.isDestroyed) {
            appChooserDialog.showNow(fragmentManager, DownloadAppChooserDialog.FRAGMENT_TAG)
        }
    }

    private fun getAppDownloaderDialog() = findPreviousAppDownloaderDialogFragment()
        ?: DownloadAppChooserDialog.newInstance(
            promptsStyling?.gravity,
+7 −4
Original line number Diff line number Diff line
@@ -16,10 +16,10 @@ import mozilla.components.feature.downloads.R
/**
 * An adapter for displaying the applications that can perform downloads.
 */
internal class DownloaderAppAdapter(
class DownloaderAppAdapter(
    context: Context,
    private val apps: List<DownloaderApp>,
    val onAppSelected: ((DownloaderApp) -> Unit)
    val onAppSelected: ((DownloaderApp) -> Unit),
) : RecyclerView.Adapter<DownloaderAppViewHolder>() {

    private val inflater = LayoutInflater.from(context)
@@ -49,11 +49,14 @@ internal class DownloaderAppAdapter(
/**
 * View holder for a [DownloaderApp] item.
 */
internal class DownloaderAppViewHolder(
class DownloaderAppViewHolder(
    itemView: View,
    val nameLabel: TextView,
    val iconImage: ImageView
    val iconImage: ImageView,
) : RecyclerView.ViewHolder(itemView) {
    /**
     * Show a certain downloader application in the current View.
     */
    fun bind(app: DownloaderApp, onAppSelected: ((DownloaderApp) -> Unit)) {
        itemView.app = app
        itemView.setOnClickListener {