Skip to content
This repository has been archived by the owner on Jun 17, 2024. It is now read-only.

Commit

Permalink
[components] Closes mozilla-mobile/android-components#11411: Cancel d…
Browse files Browse the repository at this point in the history
…ownloads prompt
  • Loading branch information
mike a authored and mergify[bot] committed Dec 20, 2021
1 parent 720e69e commit 07ea3d8
Show file tree
Hide file tree
Showing 4 changed files with 445 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.feature.downloads.ui

import android.app.Dialog
import android.content.DialogInterface
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.os.Parcelable
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.Window
import android.widget.LinearLayout
import androidx.annotation.ColorRes
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.core.content.ContextCompat
import kotlinx.parcelize.Parcelize
import mozilla.components.feature.downloads.R
import mozilla.components.feature.downloads.databinding.MozacDownloadCancelBinding

/**
* The dialog warns the user that closing last private tab leads to cancellation of active private
* downloads.
*/
class DownloadCancelDialogFragment : AppCompatDialogFragment() {

var onAcceptClicked: ((tabId: String?, source: String?) -> Unit)? = null
var onDenyClicked: (() -> Unit)? = null

private val safeArguments get() = requireNotNull(arguments)
private val downloadCount by lazy { safeArguments.getInt(KEY_DOWNLOAD_COUNT) }
private val tabId by lazy { safeArguments.getString(KEY_TAB_ID) }
private val source by lazy { safeArguments.getString(KEY_SOURCE) }
private val promptStyling by lazy { safeArguments.getParcelable(KEY_STYLE) ?: PromptStyling() }
private val promptText by lazy { safeArguments.getParcelable(KEY_TEXT) ?: PromptText() }

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return Dialog(requireContext()).apply {
requestWindowFeature(Window.FEATURE_NO_TITLE)
setCanceledOnTouchOutside(true)

setContainerView(promptStyling.shouldWidthMatchParent, createContainer())

window?.apply {
setGravity(promptStyling.gravity)

if (promptStyling.shouldWidthMatchParent) {
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
// This must be called after addContentView, or it won't fully fill to the edge.
setLayout(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
}
}
}
}

override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
onDenyClicked?.invoke()
}

@Suppress("NestedBlockDepth")
private fun Dialog.setContainerView(dialogShouldWidthMatchParent: Boolean, rootView: View) {
if (dialogShouldWidthMatchParent) {
setContentView(rootView)
} else {
addContentView(
rootView,
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
)
}
}

@Suppress("InflateParams", "NestedBlockDepth")
private fun createContainer() = LayoutInflater.from(requireContext()).inflate(
R.layout.mozac_download_cancel,
null,
false
).apply {
with(MozacDownloadCancelBinding.bind(this)) {
acceptButton.setOnClickListener {
onAcceptClicked?.invoke(tabId, source)
dismiss()
}

denyButton.setOnClickListener {
onDenyClicked?.invoke()
dismiss()
}

with(promptText) {
title.text = getString(titleText)
body.text = buildWarningText(downloadCount, bodyText)
acceptButton.text = getString(acceptText)
denyButton.text = getString(denyText)
}

with(promptStyling) {
positiveButtonBackgroundColor?.let {
val backgroundTintList = ContextCompat.getColorStateList(requireContext(), it)
acceptButton.backgroundTintList = backgroundTintList

// It appears there is not guaranteed way to get background color of a button,
// there are always nullable types, hence the code changing the positiveButtonRadius
// executes only if positiveButtonBackgroundColor is provided
positiveButtonRadius?.let {
val shape = GradientDrawable()
shape.shape = GradientDrawable.RECTANGLE
shape.setColor(
ContextCompat.getColor(
requireContext(),
positiveButtonBackgroundColor
)
)
shape.cornerRadius = positiveButtonRadius
acceptButton.background = shape
}
}

positiveButtonTextColor?.let {
val color = ContextCompat.getColor(requireContext(), it)
acceptButton.setTextColor(color)
}
}
}
}

@VisibleForTesting
internal fun buildWarningText(downloadCount: Int, @StringRes stringId: Int) = String.format(
getString(stringId),
downloadCount
)

companion object {
private const val KEY_DOWNLOAD_COUNT = "KEY_DOWNLOAD_COUNT"
private const val KEY_TAB_ID = "KEY_TAB_ID"
private const val KEY_SOURCE = "KEY_SOURCE"
private const val KEY_STYLE = "KEY_STYLE"
private const val KEY_TEXT = "KEY_TEXT"

/**
* Returns a new instance of [DownloadCancelDialogFragment].
* @param downloadCount The number of currently active downloads.
* @param promptStyling Styling properties for the dialog.
* @param onPositiveButtonClicked A lambda called when the allow button is clicked.
* @param onNegativeButtonClicked A lambda called when the deny button is clicked.
*/
fun newInstance(
downloadCount: Int,
tabId: String? = null,
source: String? = null,
promptText: PromptText? = null,
promptStyling: PromptStyling? = null,
onPositiveButtonClicked: ((tabId: String?, source: String?) -> Unit)? = null,
onNegativeButtonClicked: (() -> Unit)? = null
): DownloadCancelDialogFragment {
return DownloadCancelDialogFragment().apply {
this.arguments = Bundle().apply {
putInt(KEY_DOWNLOAD_COUNT, downloadCount)
tabId?.let { putString(KEY_TAB_ID, it) }
source?.let { putString(KEY_SOURCE, it) }
promptText?.let { putParcelable(KEY_TEXT, it) }
promptStyling?.let { putParcelable(KEY_STYLE, it) }
}
this.onAcceptClicked = onPositiveButtonClicked
this.onDenyClicked = onNegativeButtonClicked
}
}
}

/**
* Styling for the downloads cancellation dialog.
* Note that for [positiveButtonRadius] to be applied,
* specifying [positiveButtonBackgroundColor] is necessary.
*/

@Parcelize
data class PromptStyling(
val gravity: Int = Gravity.BOTTOM,
val shouldWidthMatchParent: Boolean = true,
@ColorRes
val positiveButtonBackgroundColor: Int? = null,
@ColorRes
val positiveButtonTextColor: Int? = null,
val positiveButtonRadius: Float? = null
) : Parcelable

/**
* The class gives an option to override string resources used by [DownloadCancelDialogFragment].
*/

@Parcelize
data class PromptText(
@StringRes
val titleText: Int = R.string.cancel_active_downloads_warning_content_title,
@StringRes
val bodyText: Int = R.string.cancel_active_private_downloads_warning_content_body,
@StringRes
val acceptText: Int = R.string.cancel_active_downloads_accept,
@StringRes
val denyText: Int = R.string.cancel_active_private_downloads_deny
) : Parcelable
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:windowBackground"
tools:ignore="Overdraw">

<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentTop="true"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:importantForAccessibility="no"
android:scaleType="center"
app:srcCompat="@drawable/mozac_ic_information"
app:tint="?android:attr/textColorPrimary" />

<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/icon"
android:layout_alignBottom="@+id/icon"
android:layout_toEndOf="@id/icon"
android:gravity="center_vertical"
android:paddingStart="4dp"
android:paddingEnd="8dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
tools:text="@string/cancel_active_downloads_warning_content_title"
tools:textColor="#000000" />

<TextView
android:id="@+id/body"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignStart="@id/title"
android:layout_marginTop="8dp"
android:paddingStart="4dp"
android:paddingEnd="8dp"
android:textColor="?android:attr/textColorPrimary"
tools:text="@string/cancel_active_private_downloads_warning_content_body" />

<Button
android:id="@+id/deny_button"
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/body"
android:layout_marginTop="16dp"
android:layout_toStartOf="@id/accept_button"
android:textAllCaps="false"
tools:text="@string/cancel_active_private_downloads_deny" />

<Button
android:id="@+id/accept_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/body"
android:layout_alignParentEnd="true"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:textAllCaps="false"
tools:text="@string/cancel_active_downloads_accept" />
</RelativeLayout>
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,13 @@
<string name="mozac_feature_downloads_unable_to_open_third_party_app">Unable to open %1$s</string>
<!-- Text for the info dialog when write to storage permissions have been denied but user tries to download a file. -->
<string name="mozac_feature_downloads_write_external_storage_permissions_needed_message">Files and media permission access needed to download files. Go to Android settings, tap permissions, and tap allow.</string>

<!-- Alert dialog confirmation before cancelling downloads, this is the title -->
<string name="cancel_active_downloads_warning_content_title">Cancel private downloads?</string>
<!-- Alert dialog confirmation before cancelling private downloads, this is the body. %1$s will be replaced with the name of the file. -->
<string name="cancel_active_private_downloads_warning_content_body">If you close all Private tabs now, %1$s download will be canceled. Are you sure you want to leave Private Browsing?</string>
<!-- Alert dialog confirmation before cancelling downloads, this is the positive action. -->
<string name="cancel_active_downloads_accept">Cancel downloads</string>
<!-- Alert dialog confirmation before cancelling downloads, this is the negative action. Leaves user in Private browsing -->
<string name="cancel_active_private_downloads_deny">Stay in private browsing</string>
</resources>
Loading

0 comments on commit 07ea3d8

Please sign in to comment.