Provide add-on support (#8064)
Closes #5630, #6069, #6092, #6091, #6124, and #6147. Co-authored-by: Simon Chae <chaesmn@gmail.com> Co-authored-by: Arturo Mejia <arturomejiamarmol@gmail.com> Co-authored-by: Christian Sadilek <christian.sadilek@gmail.com> Co-authored-by: Gabriel Luong <gabriel.luong@gmail.com>fennec/nightly
parent
4eb71ce235
commit
64a4a7f422
@ -0,0 +1,109 @@
|
||||
/* 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.fenix.addons
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import kotlinx.android.synthetic.main.fragment_add_on_details.view.*
|
||||
import mozilla.components.feature.addons.Addon
|
||||
import mozilla.components.feature.addons.ui.translate
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.showToolbar
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* A fragment to show the details of an add-on.
|
||||
*/
|
||||
class AddonDetailsFragment : Fragment() {
|
||||
private val addon: Addon by lazy {
|
||||
AddonDetailsFragmentArgs.fromBundle(requireNotNull(arguments)).addon
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return inflater.inflate(R.layout.fragment_add_on_details, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
bind(addon, view)
|
||||
}
|
||||
|
||||
private fun bind(addon: Addon, view: View) {
|
||||
val title = addon.translatableName.translate()
|
||||
showToolbar(title)
|
||||
|
||||
bindDetails(addon, view)
|
||||
bindAuthors(addon, view)
|
||||
bindVersion(addon, view)
|
||||
bindLastUpdated(addon, view)
|
||||
bindWebsite(addon, view)
|
||||
bindRating(addon, view)
|
||||
}
|
||||
|
||||
private fun bindRating(addon: Addon, view: View) {
|
||||
addon.rating?.let {
|
||||
val ratingView = view.rating_view
|
||||
val userCountView = view.users_count
|
||||
|
||||
val ratingContentDescription =
|
||||
getString(R.string.mozac_feature_addons_rating_content_description)
|
||||
ratingView.contentDescription = String.format(ratingContentDescription, it.average)
|
||||
ratingView.rating = it.average
|
||||
|
||||
userCountView.text = getFormattedAmount(it.reviews)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindWebsite(addon: Addon, view: View) {
|
||||
view.home_page_text.setOnClickListener {
|
||||
val intent =
|
||||
Intent(Intent.ACTION_VIEW).setData(Uri.parse(addon.siteUrl))
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindLastUpdated(addon: Addon, view: View) {
|
||||
view.last_updated_text.text = formatDate(addon.updatedAt)
|
||||
}
|
||||
|
||||
private fun bindVersion(addon: Addon, view: View) {
|
||||
view.version_text.text = addon.version
|
||||
}
|
||||
|
||||
private fun bindAuthors(addon: Addon, view: View) {
|
||||
view.author_text.text = addon.authors.joinToString { author ->
|
||||
author.name + " \n"
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindDetails(addon: Addon, view: View) {
|
||||
val detailsView = view.details
|
||||
val detailsText = addon.translatableDescription.translate()
|
||||
|
||||
val parsedText = detailsText.replace("\n", "<br/>")
|
||||
val text = HtmlCompat.fromHtml(parsedText, HtmlCompat.FROM_HTML_MODE_COMPACT)
|
||||
|
||||
detailsView.text = text
|
||||
detailsView.movementMethod = LinkMovementMethod.getInstance()
|
||||
}
|
||||
|
||||
private fun formatDate(text: String): String {
|
||||
val formatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault())
|
||||
return DateFormat.getDateInstance().format(formatter.parse(text)!!)
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/* 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.fenix.addons
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import kotlinx.android.synthetic.main.fragment_add_on_internal_settings.*
|
||||
import mozilla.components.concept.engine.EngineSession
|
||||
import mozilla.components.feature.addons.Addon
|
||||
import mozilla.components.feature.addons.ui.translate
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.showToolbar
|
||||
|
||||
/**
|
||||
* A fragment to show the internal settings of an add-on.
|
||||
*/
|
||||
class AddonInternalSettingsFragment : Fragment() {
|
||||
private val addon: Addon by lazy {
|
||||
AddonDetailsFragmentArgs.fromBundle(requireNotNull(arguments)).addon
|
||||
}
|
||||
private lateinit var engineSession: EngineSession
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
engineSession = requireComponents.core.engine.createSession()
|
||||
|
||||
return inflater.inflate(R.layout.fragment_add_on_internal_settings, container, false)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
showToolbar(addon.translatableName.translate())
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
addonSettingsEngineView.render(engineSession)
|
||||
engineSession.loadUrl(addon.installedState!!.optionsPageUrl)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
engineSession.close()
|
||||
super.onDestroyView()
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/* 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.fenix.addons
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.android.synthetic.main.fragment_add_on_permissions.view.*
|
||||
import mozilla.components.feature.addons.Addon
|
||||
import mozilla.components.feature.addons.ui.AddonPermissionsAdapter
|
||||
import mozilla.components.feature.addons.ui.translate
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.showToolbar
|
||||
|
||||
private const val LEARN_MORE_URL =
|
||||
"https://support.mozilla.org/kb/permission-request-messages-firefox-extensions"
|
||||
|
||||
/**
|
||||
* A fragment to show the permissions of an add-on.
|
||||
*/
|
||||
class AddonPermissionsDetailsFragment : Fragment(), View.OnClickListener {
|
||||
private val addon: Addon by lazy {
|
||||
AddonDetailsFragmentArgs.fromBundle(requireNotNull(arguments)).addon
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return inflater.inflate(R.layout.fragment_add_on_permissions, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
showToolbar(addon.translatableName.translate())
|
||||
|
||||
bindPermissions(addon, view)
|
||||
bindLearnMore(view)
|
||||
}
|
||||
|
||||
private fun bindPermissions(addon: Addon, view: View) {
|
||||
view.add_ons_permissions.apply {
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
val sortedPermissions = addon.translatePermissions().map {
|
||||
@StringRes val stringId = it
|
||||
getString(stringId)
|
||||
}.sorted()
|
||||
adapter = AddonPermissionsAdapter(sortedPermissions)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindLearnMore(view: View) {
|
||||
view.learn_more_label.setOnClickListener(this)
|
||||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
val intent =
|
||||
Intent(Intent.ACTION_VIEW).setData(Uri.parse(LEARN_MORE_URL))
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
@ -0,0 +1,172 @@
|
||||
/* 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.fenix.addons
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.Navigation
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.android.synthetic.main.fragment_add_ons_management.*
|
||||
import kotlinx.android.synthetic.main.fragment_add_ons_management.view.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.feature.addons.Addon
|
||||
import mozilla.components.feature.addons.AddonManagerException
|
||||
import mozilla.components.feature.addons.ui.AddonsManagerAdapter
|
||||
import mozilla.components.feature.addons.ui.AddonsManagerAdapterDelegate
|
||||
import mozilla.components.feature.addons.ui.PermissionsDialogFragment
|
||||
import mozilla.components.feature.addons.ui.translatedName
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.showToolbar
|
||||
|
||||
/**
|
||||
* Fragment use for managing add-ons.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
class AddonsManagementFragment : Fragment(), AddonsManagerAdapterDelegate {
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return inflater.inflate(R.layout.fragment_add_ons_management, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
bindRecyclerView(view)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
showToolbar(getString(R.string.preferences_addons))
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
findPreviousDialogFragment()?.let { dialog ->
|
||||
dialog.onPositiveButtonClicked = onPositiveButtonClicked
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAddonItemClicked(addon: Addon) {
|
||||
if (addon.isInstalled()) {
|
||||
showInstalledAddonDetailsFragment(addon)
|
||||
} else {
|
||||
showDetailsFragment(addon)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInstallAddonButtonClicked(addon: Addon) {
|
||||
showPermissionDialog(addon)
|
||||
}
|
||||
|
||||
override fun onNotYetSupportedSectionClicked(unsupportedAddons: ArrayList<Addon>) {
|
||||
showNotYetSupportedAddonFragment(unsupportedAddons)
|
||||
}
|
||||
|
||||
private fun bindRecyclerView(view: View) {
|
||||
val recyclerView = view.add_ons_list
|
||||
recyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
scope.launch {
|
||||
try {
|
||||
val addons = requireContext().components.addonManager.getAddons()
|
||||
|
||||
scope.launch(Dispatchers.Main) {
|
||||
val adapter = AddonsManagerAdapter(
|
||||
requireContext().components.addonCollectionProvider,
|
||||
this@AddonsManagementFragment,
|
||||
addons
|
||||
)
|
||||
recyclerView.adapter = adapter
|
||||
}
|
||||
} catch (e: AddonManagerException) {
|
||||
scope.launch(Dispatchers.Main) {
|
||||
showSnackBar(view, getString(R.string.mozac_feature_addons_failed_to_query_add_ons))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showInstalledAddonDetailsFragment(addon: Addon) {
|
||||
val directions =
|
||||
AddonsManagementFragmentDirections.actionAddonsManagementFragmentToInstalledAddonDetails(
|
||||
addon
|
||||
)
|
||||
Navigation.findNavController(requireView()).navigate(directions)
|
||||
}
|
||||
|
||||
private fun showDetailsFragment(addon: Addon) {
|
||||
val directions =
|
||||
AddonsManagementFragmentDirections.actionAddonsManagementFragmentToAddonDetailsFragment(
|
||||
addon
|
||||
)
|
||||
Navigation.findNavController(requireView()).navigate(directions)
|
||||
}
|
||||
|
||||
private fun showNotYetSupportedAddonFragment(unsupportedAddons: ArrayList<Addon>) {
|
||||
val directions =
|
||||
AddonsManagementFragmentDirections.actionAddonsManagementFragmentToNotYetSupportedAddonFragment(
|
||||
unsupportedAddons.toTypedArray()
|
||||
)
|
||||
Navigation.findNavController(requireView()).navigate(directions)
|
||||
}
|
||||
|
||||
private fun findPreviousDialogFragment(): PermissionsDialogFragment? {
|
||||
return parentFragmentManager.findFragmentByTag(PERMISSIONS_DIALOG_FRAGMENT_TAG) as? PermissionsDialogFragment
|
||||
}
|
||||
|
||||
private fun hasExistingPermissionDialogFragment(): Boolean {
|
||||
return findPreviousDialogFragment() != null
|
||||
}
|
||||
|
||||
private fun showPermissionDialog(addon: Addon) {
|
||||
if (!hasExistingPermissionDialogFragment()) {
|
||||
val dialog = PermissionsDialogFragment.newInstance(
|
||||
addon = addon,
|
||||
onPositiveButtonClicked = onPositiveButtonClicked
|
||||
)
|
||||
dialog.show(parentFragmentManager, PERMISSIONS_DIALOG_FRAGMENT_TAG)
|
||||
}
|
||||
}
|
||||
|
||||
private val onPositiveButtonClicked: ((Addon) -> Unit) = { addon ->
|
||||
addonProgressOverlay.visibility = View.VISIBLE
|
||||
requireContext().components.addonManager.installAddon(
|
||||
addon,
|
||||
onSuccess = {
|
||||
this@AddonsManagementFragment.view?.let { view ->
|
||||
showSnackBar(
|
||||
view,
|
||||
getString(
|
||||
R.string.mozac_feature_addons_successfully_installed,
|
||||
it.translatedName
|
||||
)
|
||||
)
|
||||
bindRecyclerView(view)
|
||||
}
|
||||
|
||||
addonProgressOverlay?.visibility = View.GONE
|
||||
},
|
||||
onError = { _, _ ->
|
||||
this@AddonsManagementFragment.view?.let { view ->
|
||||
showSnackBar(view, getString(R.string.mozac_feature_addons_failed_to_install, addon.translatedName))
|
||||
}
|
||||
addonProgressOverlay?.visibility = View.GONE
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PERMISSIONS_DIALOG_FRAGMENT_TAG = "ADDONS_PERMISSIONS_DIALOG_FRAGMENT"
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/* 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.fenix.addons
|
||||
|
||||
import android.view.View
|
||||
import org.mozilla.fenix.components.FenixSnackbar
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* Get the formatted number amount for the current default locale.
|
||||
*
|
||||
* @param amount The number of addons to be formatted for the current default locale..
|
||||
*/
|
||||
internal fun getFormattedAmount(amount: Int): String {
|
||||
return NumberFormat.getNumberInstance(Locale.getDefault()).format(amount)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the Fenix Snackbar in the given view along with the provided text.
|
||||
*
|
||||
* @param view A [View] used to determine a parent for the [FenixSnackbar].
|
||||
* @param text The text to display in the [FenixSnackbar].
|
||||
*/
|
||||
internal fun showSnackBar(view: View, text: String) {
|
||||
FenixSnackbar.make(view, FenixSnackbar.LENGTH_SHORT)
|
||||
.setText(text)
|
||||
.show()
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
/* 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.fenix.addons
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Switch
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.Navigation
|
||||
import androidx.navigation.findNavController
|
||||
import kotlinx.android.synthetic.main.fragment_installed_add_on_details.view.*
|
||||
import mozilla.components.feature.addons.Addon
|
||||
import mozilla.components.feature.addons.ui.translate
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.showToolbar
|
||||
import mozilla.components.feature.addons.ui.translatedName
|
||||
|
||||
/**
|
||||
* An activity to show the details of a installed add-on.
|
||||
*/
|
||||
class InstalledAddonDetailsFragment : Fragment() {
|
||||
private lateinit var addon: Addon
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
if (!::addon.isInitialized) {
|
||||
addon = AddonDetailsFragmentArgs.fromBundle(requireNotNull(arguments)).addon
|
||||
}
|
||||
|
||||
return inflater.inflate(R.layout.fragment_installed_add_on_details, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
bind(view)
|
||||
}
|
||||
|
||||
private fun bind(view: View) {
|
||||
val title = addon.translatableName.translate()
|
||||
showToolbar(title)
|
||||
|
||||
bindEnableSwitch(view)
|
||||
bindSettings(view)
|
||||
bindDetails(view)
|
||||
bindPermissions(view)
|
||||
bindRemoveButton(view)
|
||||
}
|
||||
|
||||
private fun bindEnableSwitch(view: View) {
|
||||
val switch = view.enable_switch
|
||||
switch.setState(addon.isEnabled())
|
||||
switch.setOnCheckedChangeListener { _, isChecked ->
|
||||
if (isChecked) {
|
||||
requireContext().components.addonManager.enableAddon(
|
||||
addon,
|
||||
onSuccess = {
|
||||
switch.setState(true)
|
||||
this.addon = it
|
||||
showSnackBar(
|
||||
view,
|
||||
getString(R.string.mozac_feature_addons_successfully_enabled, addon.translatedName)
|
||||
)
|
||||
},
|
||||
onError = {
|
||||
showSnackBar(
|
||||
view,
|
||||
getString(R.string.mozac_feature_addons_failed_to_enable, addon.translatedName)
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
requireContext().components.addonManager.disableAddon(
|
||||
addon,
|
||||
onSuccess = {
|
||||
switch.setState(false)
|
||||
this.addon = it
|
||||
showSnackBar(
|
||||
view,
|
||||
getString(R.string.mozac_feature_addons_successfully_disabled, addon.translatedName)
|
||||
)
|
||||
},
|
||||
onError = {
|
||||
showSnackBar(
|
||||
view,
|
||||
getString(R.string.mozac_feature_addons_failed_to_disable, addon.translatedName)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindSettings(view: View) {
|
||||
view.settings.apply {
|
||||
isEnabled = addon.installedState?.optionsPageUrl != null
|
||||
setOnClickListener {
|
||||
val directions =
|
||||
InstalledAddonDetailsFragmentDirections.actionInstalledAddonFragmentToAddonInternalSettingsFragment(
|
||||
addon
|
||||
)
|
||||
Navigation.findNavController(this).navigate(directions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindDetails(view: View) {
|
||||
view.details.setOnClickListener {
|
||||
val directions =
|
||||
InstalledAddonDetailsFragmentDirections.actionInstalledAddonFragmentToAddonDetailsFragment(
|
||||
addon
|
||||
)
|
||||
Navigation.findNavController(view).navigate(directions)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindPermissions(view: View) {
|
||||
view.permissions.setOnClickListener {
|
||||
val directions =
|
||||
InstalledAddonDetailsFragmentDirections.actionInstalledAddonFragmentToAddonPermissionsDetailsFragment(
|
||||
addon
|
||||
)
|
||||
Navigation.findNavController(view).navigate(directions)
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindRemoveButton(view: View) {
|
||||
view.remove_add_on.setOnClickListener {
|
||||
requireContext().components.addonManager.uninstallAddon(
|
||||
addon,
|
||||
onSuccess = {
|
||||
showSnackBar(
|
||||
view,
|
||||
getString(R.string.mozac_feature_addons_successfully_uninstalled, addon.translatedName)
|
||||
)
|
||||
view.findNavController().popBackStack()
|
||||
},
|
||||
onError = { _, _ ->
|
||||
showSnackBar(
|
||||
view,
|
||||
getString(
|
||||
R.string.mozac_feature_addons_failed_to_uninstall,
|
||||
addon.translatedName
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Switch.setState(checked: Boolean) {
|
||||
val text = if (checked) {
|
||||
R.string.mozac_feature_addons_settings_on
|
||||
} else {
|
||||
R.string.mozac_feature_addons_settings_off
|
||||
}
|
||||
setText(text)
|
||||
isChecked = checked
|
||||
}
|
||||
}
|
@ -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/. */
|
||||
|
||||
package org.mozilla.fenix.addons
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.android.synthetic.main.fragment_not_yet_supported_addons.view.*
|
||||
import mozilla.components.feature.addons.Addon
|
||||
import mozilla.components.feature.addons.ui.UnsupportedAddonsAdapter
|
||||
import mozilla.components.feature.addons.ui.UnsupportedAddonsAdapterDelegate
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.showToolbar
|
||||
|
||||
private const val LEARN_MORE_URL =
|
||||
"https://support.mozilla.org/kb/add-compatibility-firefox-preview"
|
||||
|
||||
/**
|
||||
* Fragment for displaying and managing add-ons that are not yet supported by the browser.
|
||||
*/
|
||||
class NotYetSupportedAddonFragment : Fragment(), UnsupportedAddonsAdapterDelegate {
|
||||
private val addons: List<Addon> by lazy {
|
||||
NotYetSupportedAddonFragmentArgs.fromBundle(requireNotNull(arguments)).addons.toList()
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.fragment_not_yet_supported_addons, container, false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
view.unsupported_add_ons_list.apply {
|
||||
layoutManager = LinearLayoutManager(requireContext())
|
||||
adapter = UnsupportedAddonsAdapter(
|
||||
addonManager = requireContext().components.addonManager,
|
||||
unsupportedAddonsAdapterDelegate = this@NotYetSupportedAddonFragment,
|
||||
unsupportedAddons = addons
|
||||
)
|
||||
}
|
||||
|
||||
view.learn_more_label.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_VIEW).setData(Uri.parse(LEARN_MORE_URL))
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
showToolbar(getString(R.string.mozac_feature_addons_unsupported_section))
|
||||
}
|
||||
|
||||
override fun onUninstallError(addonId: String, throwable: Throwable) {
|
||||
this@NotYetSupportedAddonFragment.view?.let { view ->
|
||||
showSnackBar(view, getString(R.string.mozac_feature_addons_failed_to_remove, ""))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUninstallSuccess() {
|
||||
this@NotYetSupportedAddonFragment.view?.let { view ->
|
||||
showSnackBar(view, getString(R.string.mozac_feature_addons_successfully_removed, ""))
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/* 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.fenix.addons
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import kotlinx.android.synthetic.main.fragment_add_on_internal_settings.*
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import mozilla.components.browser.state.action.WebExtensionAction
|
||||
import mozilla.components.concept.engine.EngineSession
|
||||
import mozilla.components.concept.engine.EngineView
|
||||
import mozilla.components.concept.engine.window.WindowRequest
|
||||
import mozilla.components.lib.state.ext.consumeFrom
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.showToolbar
|
||||
|
||||
/**
|
||||
* A fragment to show the web extension action popup with [EngineView].
|
||||
*/
|
||||
class WebExtensionActionPopupFragment : Fragment(), EngineSession.Observer {
|
||||
private val webExtensionTitle: String? by lazy {
|
||||
WebExtensionActionPopupFragmentArgs.fromBundle(requireNotNull(arguments)).webExtensionTitle
|
||||
}
|
||||
private val webExtensionId: String by lazy {
|
||||
WebExtensionActionPopupFragmentArgs.fromBundle(requireNotNull(arguments)).webExtensionId
|
||||
}
|
||||
private var engineSession: EngineSession? = null
|
||||
private val coreComponents by lazy { requireComponents.core }
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
// Grab the [EngineSession] from the store when the view is created if it is available.
|
||||
if (engineSession == null) {
|
||||
engineSession = coreComponents.store.state.extensions[webExtensionId]?.popupSession
|
||||
}
|
||||
|
||||
return inflater.inflate(R.layout.fragment_add_on_internal_settings, container, false)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val title = webExtensionTitle ?: webExtensionId
|
||||
showToolbar(title)
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
engineSession?.register(this)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
engineSession?.unregister(this)
|
||||
}
|
||||
|
||||
override fun onWindowRequest(windowRequest: WindowRequest) {
|
||||
if (windowRequest.type == WindowRequest.Type.CLOSE) {
|
||||
activity?.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val session = engineSession
|
||||
// If we have the session, render it otherwise consume it from the store.
|
||||
if (session != null) {
|
||||
addonSettingsEngineView.render(session)
|
||||
consumePopupSession()
|
||||
} else {
|
||||
consumeFrom(coreComponents.store) { state ->
|
||||
state.extensions[webExtensionId]?.let { extState ->
|
||||
extState.popupSession?.let {
|
||||
if (engineSession == null) {
|
||||
addonSettingsEngineView.render(it)
|
||||
it.register(this)
|
||||
consumePopupSession()
|
||||
engineSession = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun consumePopupSession() {
|
||||
coreComponents.store.dispatch(
|
||||
WebExtensionAction.UpdatePopupSessionAction(webExtensionId, popupSession = null)
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_enabled="true" android:color="?android:attr/textColorPrimary" />
|
||||
<item android:state_checked="false" android:color="@color/photonGrey40" />
|
||||
</selector>
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="16"
|
||||
android:viewportHeight="16">
|
||||
<path
|
||||
android:fillColor="?android:attr/textColorPrimary"
|
||||
android:pathData="M14.5,8c-0.971,0 -1,1 -1.75,1a0.765,0.765 0,0 1,-0.75 -0.75V5a1,1 0,0 0,-1 -1H7.75A0.765,0.765 0,0 1,7 3.25c0,-0.75 1,-0.779 1,-1.75C8,0.635 7.1,0 6,0S4,0.635 4,1.5c0,0.971 1,1 1,1.75a0.765,0.765 0,0 1,-0.75 0.75H1a1,1 0,0 0,-1 1v2.25A0.765,0.765 0,0 0,0.75 8c0.75,0 0.779,-1 1.75,-1C3.365,7 4,7.9 4,9s-0.635,2 -1.5,2c-0.971,0 -1,-1 -1.75,-1a0.765,0.765 0,0 0,-0.75 0.75V15a1,1 0,0 0,1 1h3.25a0.765,0.765 0,0 0,0.75 -0.75c0,-0.75 -1,-0.779 -1,-1.75 0,-0.865 0.9,-1.5 2,-1.5s2,0.635 2,1.5c0,0.971 -1,1 -1,1.75a0.765,0.765 0,0 0,0.75 0.75H11a1,1 0,0 0,1 -1v-3.25a0.765,0.765 0,0 1,0.75 -0.75c0.75,0 0.779,1 1.75,1 0.865,0 1.5,-0.9 1.5,-2s-0.635,-2 -1.5,-2z" />
|
||||
</vector>
|
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="16dp"
|
||||
android:height="16dp"
|
||||
android:autoMirrored="true"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M2,1h20c1.1,0 2,0.9 2,2v18c0,1.1 -0.9,2 -2,2H2c-1.1,0 -2,-0.9 -2,-2V3c0,-1.1 0.9,-2 2,-2z" />
|
||||
<path
|
||||
android:fillColor="#FFF"
|
||||
android:pathData="M12,3h9c0.6,0 1,0.4 1,1v16c0,0.6 -0.4,1 -1,1h-9L12,3zM5.5,12.5l2.7,-3.7c0.2,-0.3 0.6,-0.3 0.8,-0.1l0.7,0.5c0.2,0.2 0.2,0.5 0,0.7L5.8,15c-0.2,0.2 -0.5,0.3 -0.8,0.1l-2.2,-2.2c-0.2,-0.2 -0.2,-0.5 0,-0.7l0.8,-0.8c0.2,-0.2 0.5,-0.2 0.7,0l1.2,1.1z" />
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M15,9l-1,1 2,2 -2,2 1,1 2,-2 2,2 1,-1 -2,-2 2,-2 -1,-1 -2,2.01L15,9z" />
|
||||
</vector>
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:ignore="MergeRootFrame" />
|
@ -0,0 +1,154 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<ScrollView 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:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="6dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="20dp"
|
||||
tools:text="@tools:sample/lorem/random" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/details"
|
||||
android:text="@string/mozac_feature_addons_authors" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/author_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/details"
|
||||
android:layout_alignParentEnd="true"
|
||||
tools:text="@tools:sample/full_names" />
|
||||
|
||||
<View
|
||||
android:id="@+id/author_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/author_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/version_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/author_divider"
|
||||
android:text="@string/mozac_feature_addons_version" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/version_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/author_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
tools:text="1.2.3" />
|
||||
|
||||
<View
|
||||
android:id="@+id/version_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/version_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/last_updated_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/version_divider"
|
||||
android:text="@string/mozac_feature_addons_last_updated" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/last_updated_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/version_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
tools:text="Oct 16, 2019" />
|
||||
|
||||
<View
|
||||
android:id="@+id/last_updated_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/last_updated_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/home_page_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/last_updated_divider"
|
||||
android:text="@string/mozac_feature_addons_home_page" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/home_page_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/last_updated_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:contentDescription="@string/mozac_feature_addons_home_page"
|
||||
android:src="@drawable/mozac_ic_link"
|
||||
android:tint="?android:attr/textColorPrimary" />
|
||||
|
||||
<View
|
||||
android:id="@+id/home_page_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_below="@+id/home_page_label"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@color/photonGrey40"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/rating_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/home_page_divider"
|
||||
android:text="@string/mozac_feature_addons_rating" />
|
||||
|
||||
<RatingBar
|
||||
android:id="@+id/rating_view"
|
||||
style="@style/Widget.AppCompat.RatingBar.Small"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_below="@+id/home_page_divider"
|
||||
android:layout_toStartOf="@+id/users_count"
|
||||
android:isIndicator="true"
|
||||
android:numStars="5" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/users_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/home_page_divider"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="6dp"
|
||||
tools:text="591,642" />
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<mozilla.components.concept.engine.EngineView
|
||||
android:id="@+id/addonSettingsEngineView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/add_ons_permissions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/learn_more_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/add_ons_permissions"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:drawableEnd="@drawable/mozac_ic_link"
|
||||
android:padding="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/mozac_feature_addons_learn_more"
|
||||
app:drawableTint="?android:attr/textColorPrimary" />
|
||||
</RelativeLayout>
|
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/add_ons_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".BrowserActivity" />
|
||||
|
||||
<include
|
||||
android:id="@+id/addonProgressOverlay"
|
||||
layout="@layout/overlay_add_on_progress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:visibility="gone" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
@ -0,0 +1,82 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="6dp">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
|
||||
|
||||
<Switch
|
||||
android:id="@+id/enable_switch"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|end"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:checked="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="16dp"
|
||||
android:text="@string/mozac_feature_addons_settings_on"
|
||||
android:textSize="18sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/settings"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/enable_switch"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:drawablePadding="10dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/mozac_feature_addons_settings"
|
||||
android:textColor="@drawable/addon_textview_selector"
|
||||
android:textSize="18sp"
|
||||
app:drawableStartCompat="@drawable/mozac_ic_preferences"
|
||||
app:drawableTint="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/details"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/settings"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:drawablePadding="6dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/mozac_feature_addons_details"
|
||||
android:textColor="@drawable/addon_textview_selector"
|
||||
android:textSize="18sp"
|
||||
app:drawableStartCompat="@drawable/mozac_ic_information"
|
||||
app:drawableTint="?android:attr/textColorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/permissions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/details"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:drawablePadding="6dp"
|
||||
android:padding="16dp"
|
||||
android:text="@string/mozac_feature_addons_permissions"
|
||||
android:textColor="@drawable/addon_textview_selector"
|
||||
android:textSize="18sp"
|
||||
app:drawableStartCompat="@drawable/mozac_ic_permissions" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/remove_add_on"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/permissions"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/mozac_feature_addons_remove"
|
||||
android:textColor="@color/photonRed50" />
|
||||
</RelativeLayout>
|
||||
</ScrollView>
|
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/mozac_feature_addons_not_yet_supported_caption" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/learn_more_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:text="@string/mozac_feature_addons_unsupported_learn_more" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:background="@color/photonGrey30" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/unsupported_add_ons_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".BrowserActivity"/>
|
||||
</LinearLayout>
|
@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 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/. -->
|
||||
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:elevation="1dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="16dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:drawableStart="@drawable/mozac_ic_extensions_black"
|
||||
android:drawablePadding="8dp"
|
||||
android:text="@string/mozac_add_on_install_progress_caption"/>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
Loading…
Reference in New Issue