Commit c81d2b28 authored by MozLando's avatar MozLando
Browse files

Merge #5110

5110: Closes #5036: Add dialog for accepting addon permissions r=csadilek a=Amejia481

[Video demo ](https://drive.google.com/file/d/1xyLa539tuQD1hF078QMfV2dSNcSzq_8W/view?usp=sharing

)
Co-authored-by: default avatarArturo Mejia <arturomejiamarmol@gmail.com>
parents b5c787f3 3f1d5d28
......@@ -29,6 +29,7 @@ android {
dependencies {
implementation Dependencies.androidx_appcompat
implementation Dependencies.kotlin_stdlib
implementation Dependencies.kotlin_coroutines
......
......@@ -6,6 +6,7 @@ package org.mozilla.samples.browser.addons
import android.content.Intent
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
......@@ -22,6 +23,7 @@ import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import mozilla.components.feature.addons.AddOn
import org.mozilla.samples.browser.R
import org.mozilla.samples.browser.addons.PermissionsDialogFragment.PromptsStyling
import org.mozilla.samples.browser.ext.components
/**
......@@ -44,6 +46,14 @@ class AddOnsFragment : Fragment(), View.OnClickListener {
bindRecyclerView(rootView)
}
override fun onStart() {
super.onStart()
findPreviousDialogFragment()?.let { dialog ->
dialog.onPositiveButtonClicked = onPositiveButtonClicked
dialog.onNegativeButtonClicked = onNegativeButtonClicked
}
}
private fun bindRecyclerView(rootView: View) {
recyclerView = rootView.findViewById(R.id.add_ons_list)
recyclerView.layoutManager = LinearLayoutManager(requireContext())
......@@ -140,8 +150,8 @@ class AddOnsFragment : Fragment(), View.OnClickListener {
val context = view.context
when (view.id) {
R.id.add_button -> {
Toast.makeText(this.requireContext(), "Installing add-on", Toast.LENGTH_SHORT)
.show()
val addOn = (((view.parent) as View).tag as AddOn)
showPermissionDialog(addOn)
}
R.id.add_on_item -> {
val intent = Intent(context, InstalledAddOnDetailsActivity::class.java)
......@@ -152,4 +162,44 @@ class AddOnsFragment : Fragment(), View.OnClickListener {
}
}
}
private fun isAlreadyADialogCreated(): Boolean {
return findPreviousDialogFragment() != null
}
private fun findPreviousDialogFragment(): PermissionsDialogFragment? {
return fragmentManager?.findFragmentByTag(PERMISSIONS_DIALOG_FRAGMENT_TAG) as? PermissionsDialogFragment
}
private fun showPermissionDialog(addOn: AddOn) {
val dialog = PermissionsDialogFragment.newInstance(
addOnId = addOn.id,
title = addOn.translatableName.translate(),
permissions = addOn.translatePermissions(),
promptsStyling = PromptsStyling(
gravity = Gravity.BOTTOM,
shouldWidthMatchParent = true
),
onPositiveButtonClicked = onPositiveButtonClicked,
onNegativeButtonClicked = onNegativeButtonClicked
)
if (!isAlreadyADialogCreated() && fragmentManager != null) {
dialog.show(requireFragmentManager(), PERMISSIONS_DIALOG_FRAGMENT_TAG)
}
}
private val onPositiveButtonClicked: ((String) -> Unit) = { addonId ->
Toast.makeText(this.requireContext(), "Installing add-on $addonId", Toast.LENGTH_SHORT)
.show()
}
private val onNegativeButtonClicked = {
Toast.makeText(this.requireContext(), "Cancel button clicked", Toast.LENGTH_SHORT)
.show()
}
companion object {
private const val PERMISSIONS_DIALOG_FRAGMENT_TAG = "ADDONS_PERMISSIONS_DIALOG_FRAGMENT"
}
}
/* 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 org.mozilla.samples.browser.addons
import android.annotation.SuppressLint
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.view.LayoutInflater
import android.view.View
import android.view.Window
import android.widget.TextView
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout.LayoutParams
import androidx.annotation.ColorRes
import androidx.appcompat.app.AppCompatDialogFragment
import androidx.core.content.ContextCompat
import org.mozilla.samples.browser.R
internal const val KEY_ADD_ON_ID = "KEY_ADD_ON_ID"
internal const val KEY_TITLE = "KEY_TITLE"
private const val KEY_DIALOG_GRAVITY = "KEY_DIALOG_GRAVITY"
private const val KEY_DIALOG_WIDTH_MATCH_PARENT = "KEY_DIALOG_WIDTH_MATCH_PARENT"
private const val KEY_PERMISSIONS = "KEY_PERMISSIONS"
private const val KEY_POSITIVE_BUTTON_BACKGROUND_COLOR = "KEY_POSITIVE_BUTTON_BACKGROUND_COLOR"
private const val KEY_POSITIVE_BUTTON_TEXT_COLOR = "KEY_POSITIVE_BUTTON_TEXT_COLOR"
private const val KEY_POSITIVE_BUTTON_RADIUS = "KEY_POSITIVE_BUTTON_RADIUS"
private const val DEFAULT_VALUE = Int.MAX_VALUE
internal class PermissionsDialogFragment : AppCompatDialogFragment() {
internal var onPositiveButtonClicked: ((String) -> Unit)? = null
internal var onNegativeButtonClicked: (() -> Unit)? = null
private val safeArguments get() = requireNotNull(arguments)
internal val addOnId get() = safeArguments.getString(KEY_ADD_ON_ID, "")
internal val positiveButtonRadius
get() =
safeArguments.getFloat(KEY_POSITIVE_BUTTON_RADIUS, DEFAULT_VALUE.toFloat())
internal val permissions: IntArray
get() =
safeArguments.getIntArray(KEY_PERMISSIONS) ?: intArrayOf()
internal val title: String
get() =
safeArguments.getString(KEY_TITLE, "")
internal val dialogGravity: Int
get() =
safeArguments.getInt(KEY_DIALOG_GRAVITY, DEFAULT_VALUE)
internal val dialogShouldWidthMatchParent: Boolean
get() =
safeArguments.getBoolean(KEY_DIALOG_WIDTH_MATCH_PARENT)
internal val positiveButtonBackgroundColor
get() =
safeArguments.getInt(KEY_POSITIVE_BUTTON_BACKGROUND_COLOR, DEFAULT_VALUE)
internal val positiveButtonTextColor
get() =
safeArguments.getInt(KEY_POSITIVE_BUTTON_TEXT_COLOR, DEFAULT_VALUE)
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val sheetDialog = Dialog(requireContext())
sheetDialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
sheetDialog.setCanceledOnTouchOutside(true)
val rootView = createContainer()
sheetDialog.setContainerView(rootView)
sheetDialog.window?.apply {
if (dialogGravity != DEFAULT_VALUE) {
setGravity(dialogGravity)
}
if (dialogShouldWidthMatchParent) {
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)
}
}
return sheetDialog
}
override fun onDismiss(dialog: DialogInterface) {
super.onDismiss(dialog)
onNegativeButtonClicked?.invoke()
}
private fun Dialog.setContainerView(rootView: View) {
if (dialogShouldWidthMatchParent) {
setContentView(rootView)
} else {
addContentView(
rootView,
LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT
)
)
}
}
@SuppressLint("InflateParams")
private fun createContainer(): View {
val rootView = LayoutInflater.from(requireContext()).inflate(
R.layout.fragment_dialog_addon_permissions,
null,
false
)
rootView.findViewById<TextView>(R.id.title).text =
requireContext().getString(R.string.addon_permissions_dialog_title, title)
rootView.findViewById<TextView>(R.id.permissions).text = buildPermissionsText()
val positiveButton = rootView.findViewById<Button>(R.id.allow_button)
val negativeButton = rootView.findViewById<Button>(R.id.deny_button)
positiveButton.setOnClickListener {
onPositiveButtonClicked?.invoke(addOnId)
dismiss()
}
if (positiveButtonBackgroundColor != DEFAULT_VALUE) {
val backgroundTintList =
ContextCompat.getColorStateList(requireContext(), positiveButtonBackgroundColor)
positiveButton.backgroundTintList = backgroundTintList
}
if (positiveButtonTextColor != DEFAULT_VALUE) {
val color = ContextCompat.getColor(requireContext(), positiveButtonTextColor)
positiveButton.setTextColor(color)
}
if (positiveButtonRadius != DEFAULT_VALUE.toFloat()) {
val shape = GradientDrawable()
shape.shape = GradientDrawable.RECTANGLE
shape.setColor(
ContextCompat.getColor(
requireContext(),
positiveButtonBackgroundColor
)
)
shape.cornerRadius = positiveButtonRadius
positiveButton.background = shape
}
negativeButton.setOnClickListener {
onNegativeButtonClicked?.invoke()
dismiss()
}
return rootView
}
private fun buildPermissionsText(): String {
var permissionsText = getString(R.string.addon_permissions_dialog_subtitle) + "\n\n"
permissions.forEachIndexed { index, item ->
val brakeLine = if (index + 1 != permissions.size) "\n\n" else ""
val permissionText = requireContext().getString(item)
permissionsText += "• $permissionText $brakeLine"
}
return permissionsText
}
@Suppress("LongParameterList")
companion object {
fun newInstance(
addOnId: String,
title: String,
permissions: List<Int>,
promptsStyling: PromptsStyling? = null,
onPositiveButtonClicked: ((String) -> Unit)? = null,
onNegativeButtonClicked: (() -> Unit)? = null
): PermissionsDialogFragment {
val fragment = PermissionsDialogFragment()
val arguments = fragment.arguments ?: Bundle()
arguments.apply {
putString(KEY_ADD_ON_ID, addOnId)
putString(KEY_TITLE, title)
putIntArray(KEY_PERMISSIONS, permissions.toIntArray())
promptsStyling?.gravity?.apply {
putInt(KEY_DIALOG_GRAVITY, this)
}
promptsStyling?.shouldWidthMatchParent?.apply {
putBoolean(KEY_DIALOG_WIDTH_MATCH_PARENT, this)
}
promptsStyling?.positiveButtonBackgroundColor?.apply {
putInt(KEY_POSITIVE_BUTTON_BACKGROUND_COLOR, this)
}
promptsStyling?.positiveButtonTextColor?.apply {
putInt(KEY_POSITIVE_BUTTON_TEXT_COLOR, this)
}
println(permissions)
}
fragment.onPositiveButtonClicked = onPositiveButtonClicked
fragment.onNegativeButtonClicked = onNegativeButtonClicked
fragment.arguments = arguments
return fragment
}
}
/**
* Styling for the permissions dialog.
*/
data class PromptsStyling(
val gravity: Int,
val shouldWidthMatchParent: Boolean = false,
@ColorRes
val positiveButtonBackgroundColor: Int? = null,
@ColorRes
val positiveButtonTextColor: Int? = null,
val positiveButtonRadius: Float? = null
)
}
<!-- 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:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:windowBackground"
android:orientation="vertical"
tools:ignore="Overdraw">
<ImageView
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"
android:src="@drawable/mozac_ic_extensions"
android:tint="?android:attr/textColorPrimary" />
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@id/icon"
android:layout_alignParentTop="true"
android:layout_marginStart="3dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="11dp"
android:layout_toEndOf="@id/icon"
android:paddingStart="5dp"
android:paddingTop="4dp"
android:paddingEnd="5dp"
android:textColor="?android:attr/textColorPrimary"
android:textSize="16sp"
tools:text="Add Ublock Origin?"
tools:textColor="#000000" />
<TextView
android:id="@+id/permissions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/title"
android:layout_alignStart="@id/title"
android:layout_marginTop="16dp"
android:paddingStart="5dp"
android:paddingTop="4dp"
android:paddingEnd="5dp"
android:textColor="?android:attr/textColorPrimary"
tools:text="It requires your permission to: \n\n • Access your data for all websites. \n\n • Acess browser tabs. \n\n • Access browser activity during navigation." />
<Button
android:id="@+id/deny_button"
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/permissions"
android:layout_marginTop="16dp"
android:layout_toStartOf="@id/allow_button"
android:text="@string/addon_permissions_dialog_cancel"
android:textAllCaps="false" />
<Button
android:id="@+id/allow_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/permissions"
android:layout_alignParentEnd="true"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:text="@string/addon_permissions_dialog_add"
android:textAllCaps="false" />
</RelativeLayout>
\ No newline at end of file
......@@ -22,4 +22,8 @@
<string name="add_on_details">Details</string>
<string name="add_on_permissions">Permissions</string>
<string name="add_on_remove">Remove add-on</string>
<string name="addon_permissions_dialog_subtitle">It requires your permission to:</string>
<string name="addon_permissions_dialog_title">Add %1$s?</string>
<string name="addon_permissions_dialog_add">Add</string>
<string name="addon_permissions_dialog_cancel">Cancel</string>
</resources>
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment