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>master
parent
4eb71ce235
commit
64a4a7f422
|
@ -396,6 +396,9 @@ dependencies {
|
|||
implementation Deps.mozilla_browser_storage_sync
|
||||
implementation Deps.mozilla_browser_toolbar
|
||||
|
||||
implementation Deps.mozilla_support_extensions
|
||||
implementation Deps.mozilla_feature_addons
|
||||
|
||||
implementation Deps.mozilla_feature_accounts
|
||||
implementation Deps.mozilla_feature_app_links
|
||||
implementation Deps.mozilla_feature_awesomebar
|
||||
|
|
|
@ -82,7 +82,7 @@ events:
|
|||
A string containing the name of the item the user tapped. These items include:
|
||||
Settings, Library, Help, Desktop Site toggle on/off, Find in Page, New Tab,
|
||||
Private Tab, Share, Report Site Issue, Back/Forward button, Reload Button, Quit,
|
||||
Reader Mode On, Reader Mode Off, Open In App, Add to Firefox Home
|
||||
Reader Mode On, Reader Mode Off, Open In App, Add to Firefox Home, Add-ons Manager
|
||||
bugs:
|
||||
- https://github.com/mozilla-mobile/fenix/issues/1024
|
||||
data_reviews:
|
||||
|
|
|
@ -18,6 +18,7 @@ import kotlinx.coroutines.async
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import mozilla.appservices.Megazord
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.concept.push.PushProcessor
|
||||
import mozilla.components.service.experiments.Experiments
|
||||
import mozilla.components.service.glean.Glean
|
||||
|
@ -31,6 +32,7 @@ import mozilla.components.support.ktx.android.content.runOnlyInMainProcess
|
|||
import mozilla.components.support.locale.LocaleAwareApplication
|
||||
import mozilla.components.support.rusthttp.RustHttpConfig
|
||||
import mozilla.components.support.rustlog.RustLog
|
||||
import mozilla.components.support.webextensions.WebExtensionSupport
|
||||
import org.mozilla.fenix.components.Components
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.session.NotificationSessionObserver
|
||||
|
@ -116,6 +118,8 @@ open class FenixApplication : LocaleAwareApplication() {
|
|||
// Make sure the engine is initialized and ready to use.
|
||||
components.core.engine.warmUp()
|
||||
|
||||
initializeWebExtensionSupport()
|
||||
|
||||
// Just to make sure it is impossible for any application-services pieces
|
||||
// to invoke parts of itself that require complete megazord initialization
|
||||
// before that process completes, we wait here, if necessary.
|
||||
|
@ -277,4 +281,29 @@ open class FenixApplication : LocaleAwareApplication() {
|
|||
StrictMode.setVmPolicy(builder.build())
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeWebExtensionSupport() {
|
||||
try {
|
||||
WebExtensionSupport.initialize(
|
||||
components.core.engine,
|
||||
components.core.store,
|
||||
onNewTabOverride = {
|
||||
_, engineSession, url ->
|
||||
val session = Session(url)
|
||||
components.core.sessionManager.add(session, true, engineSession)
|
||||
session.id
|
||||
},
|
||||
onCloseTabOverride = {
|
||||
_, sessionId -> components.tabsUseCases.removeTab(sessionId)
|
||||
},
|
||||
onSelectTabOverride = {
|
||||
_, sessionId ->
|
||||
val selected = components.core.sessionManager.findSessionById(sessionId)
|
||||
selected?.let { components.tabsUseCases.selectTab(it) }
|
||||
}
|
||||
)
|
||||
} catch (e: UnsupportedOperationException) {
|
||||
Logger.error("Failed to initialize web extension support", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,10 +23,12 @@ import androidx.navigation.fragment.NavHostFragment
|
|||
import androidx.navigation.ui.AppBarConfiguration
|
||||
import androidx.navigation.ui.NavigationUI
|
||||
import kotlinx.android.synthetic.main.activity_home.navigationToolbarStub
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.browser.session.Session
|
||||
import mozilla.components.browser.session.SessionManager
|
||||
import mozilla.components.browser.state.state.WebExtensionState
|
||||
import mozilla.components.concept.engine.EngineView
|
||||
import mozilla.components.service.fxa.sync.SyncReason
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
|
@ -35,6 +37,7 @@ import mozilla.components.support.ktx.kotlin.toNormalizedUrl
|
|||
import mozilla.components.support.locale.LocaleAwareAppCompatActivity
|
||||
import mozilla.components.support.utils.SafeIntent
|
||||
import mozilla.components.support.utils.toSafeIntent
|
||||
import mozilla.components.support.webextensions.WebExtensionPopupFeature
|
||||
import org.mozilla.fenix.browser.UriOpenedObserver
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
|
||||
|
@ -70,6 +73,7 @@ import org.mozilla.fenix.utils.BrowsersCache
|
|||
@SuppressWarnings("TooManyFunctions", "LargeClass")
|
||||
open class HomeActivity : LocaleAwareAppCompatActivity() {
|
||||
|
||||
private var webExtScope: CoroutineScope? = null
|
||||
lateinit var themeManager: ThemeManager
|
||||
lateinit var browsingModeManager: BrowsingModeManager
|
||||
|
||||
|
@ -79,6 +83,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
|||
|
||||
private var isToolbarInflated = false
|
||||
|
||||
private val webExtensionPopupFeature by lazy {
|
||||
WebExtensionPopupFeature(components.core.store, ::openPopup)
|
||||
}
|
||||
|
||||
private val navHost by lazy {
|
||||
supportFragmentManager.findFragmentById(R.id.container) as NavHostFragment
|
||||
}
|
||||
|
@ -126,6 +134,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
|||
?.also { components.analytics.metrics.track(Event.OpenedApp(it)) }
|
||||
}
|
||||
supportActionBar?.hide()
|
||||
|
||||
lifecycle.addObserver(webExtensionPopupFeature)
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
|
@ -377,6 +387,14 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
|||
return DefaultThemeManager(browsingModeManager.mode, this)
|
||||
}
|
||||
|
||||
private fun openPopup(webExtensionState: WebExtensionState) {
|
||||
val action = NavGraphDirections.actionGlobalWebExtensionActionPopupFragment(
|
||||
webExtensionId = webExtensionState.id,
|
||||
webExtensionTitle = webExtensionState.name
|
||||
)
|
||||
navHost.navController.navigate(action)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val OPEN_TO_BROWSER = "open_to_browser"
|
||||
const val OPEN_TO_BROWSER_AND_LOAD = "open_to_browser_and_load"
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
|
@ -5,10 +5,18 @@
|
|||
package org.mozilla.fenix.components
|
||||
|
||||
import android.content.Context
|
||||
import mozilla.components.feature.addons.AddonManager
|
||||
import mozilla.components.feature.addons.amo.AddonCollectionProvider
|
||||
import mozilla.components.feature.addons.update.AddonUpdater
|
||||
import mozilla.components.feature.addons.update.DefaultAddonUpdater
|
||||
import mozilla.components.feature.tabs.TabsUseCases
|
||||
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||
import mozilla.components.support.migration.state.MigrationStore
|
||||
import org.mozilla.fenix.test.Mockable
|
||||
import org.mozilla.fenix.utils.ClipboardHandler
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
private const val DAY_IN_MINUTES = 24 * 60L
|
||||
|
||||
/**
|
||||
* Provides access to all components.
|
||||
|
@ -49,6 +57,24 @@ class Components(private val context: Context) {
|
|||
migrationStore
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add-on
|
||||
*/
|
||||
val addonCollectionProvider by lazy {
|
||||
AddonCollectionProvider(context, core.client, maxCacheAgeInMinutes = DAY_IN_MINUTES)
|
||||
}
|
||||
|
||||
val addonUpdater by lazy {
|
||||
DefaultAddonUpdater(context, AddonUpdater.Frequency(1, TimeUnit.DAYS))
|
||||
}
|
||||
|
||||
val addonManager by lazy {
|
||||
AddonManager(core.store, core.engine, addonCollectionProvider, addonUpdater)
|
||||
}
|
||||
|
||||
val tabsUseCases: TabsUseCases by lazy { TabsUseCases(core.sessionManager) }
|
||||
|
||||
val analytics by lazy { Analytics(context) }
|
||||
val publicSuffixList by lazy { PublicSuffixList(context) }
|
||||
val clipboardHandler by lazy { ClipboardHandler(context) }
|
||||
|
|
|
@ -353,7 +353,7 @@ sealed class Event {
|
|||
SETTINGS, LIBRARY, HELP, DESKTOP_VIEW_ON, DESKTOP_VIEW_OFF, FIND_IN_PAGE, NEW_TAB,
|
||||
NEW_PRIVATE_TAB, SHARE, REPORT_SITE_ISSUE, BACK, FORWARD, RELOAD, STOP, OPEN_IN_FENIX,
|
||||
SAVE_TO_COLLECTION, ADD_TO_FIREFOX_HOME, ADD_TO_HOMESCREEN, QUIT, READER_MODE_ON,
|
||||
READER_MODE_OFF, OPEN_IN_APP, BOOKMARK, READER_MODE_APPEARANCE
|
||||
READER_MODE_OFF, OPEN_IN_APP, BOOKMARK, READER_MODE_APPEARANCE, ADDONS_MANAGER
|
||||
}
|
||||
|
||||
override val extras: Map<Events.browserMenuActionKeys, String>?
|
||||
|
|
|
@ -207,6 +207,13 @@ class DefaultBrowserToolbarController(
|
|||
ToolbarMenu.Item.Help -> {
|
||||
activity.components.useCases.tabsUseCases.addTab.invoke(getSupportUrl())
|
||||
}
|
||||
ToolbarMenu.Item.AddonsManager -> {
|
||||
navController.nav(
|
||||
R.id.browserFragment,
|
||||
BrowserFragmentDirections
|
||||
.actionBrowserFragmentToAddonsManagementFragment()
|
||||
)
|
||||
}
|
||||
ToolbarMenu.Item.SaveToCollection -> {
|
||||
activity.components.analytics.metrics
|
||||
.track(Event.CollectionSaveButtonPressed(TELEMETRY_BROWSER_IDENTIFIER))
|
||||
|
@ -340,6 +347,7 @@ class DefaultBrowserToolbarController(
|
|||
Event.BrowserMenuItemTapped.Item.READER_MODE_APPEARANCE
|
||||
ToolbarMenu.Item.OpenInApp -> Event.BrowserMenuItemTapped.Item.OPEN_IN_APP
|
||||
ToolbarMenu.Item.Bookmark -> Event.BrowserMenuItemTapped.Item.BOOKMARK
|
||||
ToolbarMenu.Item.AddonsManager -> Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER
|
||||
}
|
||||
|
||||
activity.components.analytics.metrics.track(Event.BrowserMenuItemTapped(eventItem))
|
||||
|
|
|
@ -10,8 +10,8 @@ import androidx.lifecycle.LifecycleOwner
|
|||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.menu.BrowserMenuBuilder
|
||||
import mozilla.components.browser.menu.BrowserMenuHighlight
|
||||
import mozilla.components.browser.menu.WebExtensionBrowserMenuBuilder
|
||||
import mozilla.components.browser.menu.item.BrowserMenuDivider
|
||||
import mozilla.components.browser.menu.item.BrowserMenuHighlightableItem
|
||||
import mozilla.components.browser.menu.item.BrowserMenuHighlightableSwitch
|
||||
|
@ -45,7 +45,14 @@ class DefaultToolbarMenu(
|
|||
private var currentUrlIsBookmarked = false
|
||||
private var isBookmarkedJob: Job? = null
|
||||
|
||||
override val menuBuilder by lazy { BrowserMenuBuilder(menuItems, endOfMenuAlwaysVisible = true) }
|
||||
override val menuBuilder by lazy {
|
||||
WebExtensionBrowserMenuBuilder(
|
||||
menuItems,
|
||||
endOfMenuAlwaysVisible = true,
|
||||
store = context.components.core.store,
|
||||
appendExtensionActionAtStart = true
|
||||
)
|
||||
}
|
||||
|
||||
override val menuToolbar by lazy {
|
||||
val forward = BrowserMenuItemToolbar.TwoStateButton(
|
||||
|
@ -157,6 +164,7 @@ class DefaultToolbarMenu(
|
|||
desktopMode,
|
||||
addToFirefoxHome,
|
||||
addToHomescreen.apply { visible = ::shouldShowAddToHomescreen },
|
||||
addons,
|
||||
findInPage,
|
||||
privateTab,
|
||||
newTab,
|
||||
|
@ -173,6 +181,14 @@ class DefaultToolbarMenu(
|
|||
if (shouldReverseItems) { menuItems.reversed() } else { menuItems }
|
||||
}
|
||||
|
||||
private val addons = BrowserMenuImageText(
|
||||
context.getString(R.string.browser_menu_addon_manager),
|
||||
R.drawable.mozac_ic_extensions,
|
||||
primaryTextColor()
|
||||
) {
|
||||
onItemTapped.invoke(ToolbarMenu.Item.AddonsManager)
|
||||
}
|
||||
|
||||
private val help = BrowserMenuImageText(
|
||||
context.getString(R.string.browser_menu_help),
|
||||
R.drawable.ic_help,
|
||||
|
|
|
@ -26,6 +26,7 @@ interface ToolbarMenu {
|
|||
object SaveToCollection : Item()
|
||||
object AddToFirefoxHome : Item()
|
||||
object AddToHomeScreen : Item()
|
||||
object AddonsManager : Item()
|
||||
object Quit : Item()
|
||||
data class ReaderMode(val isChecked: Boolean) : Item()
|
||||
object OpenInApp : Item()
|
||||
|
|
|
@ -51,6 +51,7 @@ import org.mozilla.fenix.R.string.pref_key_theme
|
|||
import org.mozilla.fenix.R.string.pref_key_toolbar
|
||||
import org.mozilla.fenix.R.string.pref_key_tracking_protection_settings
|
||||
import org.mozilla.fenix.R.string.pref_key_your_rights
|
||||
import org.mozilla.fenix.R.string.pref_key_addons
|
||||
import org.mozilla.fenix.components.PrivateShortcutCreateManager
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.ext.application
|
||||
|
@ -185,7 +186,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
findPreference<Preference>(getPreferenceKey(pref_key_passwords))?.apply {
|
||||
isVisible = FeatureFlags.logins
|
||||
}
|
||||
findPreference<PreferenceCategory>(getPreferenceKey(R.string.pref_key_advanced))?.apply {
|
||||
findPreference<Preference>(getPreferenceKey(pref_key_language))?.apply {
|
||||
isVisible = FeatureFlags.fenixLanguagePicker
|
||||
}
|
||||
}
|
||||
|
@ -214,6 +215,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
resources.getString(pref_key_language) -> {
|
||||
SettingsFragmentDirections.actionSettingsFragmentToLocaleSettingsFragment()
|
||||
}
|
||||
resources.getString(pref_key_addons) -> {
|
||||
SettingsFragmentDirections.actionSettingsFragmentToAddonsFragment()
|
||||
}
|
||||
|
||||
resources.getString(pref_key_make_default_browser) -> {
|
||||
SettingsFragmentDirections.actionSettingsFragmentToDefaultBrowserSettingsFragment()
|
||||
}
|
||||
|
|
|
@ -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>
|
File diff suppressed because one or more lines are too long
|
@ -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>
|
|
@ -51,6 +51,10 @@
|
|||
android:id="@+id/action_global_homeFragment"
|
||||
app:destination="@id/homeFragment" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_global_webExtensionActionPopupFragment"
|
||||
app:destination="@id/webExtensionActionPopupFragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/homeFragment"
|
||||
android:name="org.mozilla.fenix.home.HomeFragment"
|
||||
|
@ -201,6 +205,9 @@
|
|||
<action
|
||||
android:id="@+id/action_browserFragment_to_trackingProtectionPanelDialogFragment"
|
||||
app:destination="@id/trackingProtectionPanelDialogFragment" />
|
||||
<action
|
||||
android:id="@+id/action_browserFragment_to_addonsManagementFragment"
|
||||
app:destination="@id/addonsManagementFragment" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
|
@ -409,6 +416,9 @@
|
|||
<action
|
||||
android:id="@+id/action_settingsFragment_to_localeSettingsFragment"
|
||||
app:destination="@id/localeSettingsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_settingsFragment_to_addonsFragment"
|
||||
app:destination="@id/addonsManagementFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/dataChoicesFragment"
|
||||
|
@ -702,4 +712,72 @@
|
|||
android:id="@+id/saveLoginSettingFragment"
|
||||
android:name="org.mozilla.fenix.settings.logins.SaveLoginSettingFragment"
|
||||
android:label="SaveLoginSettingFragment" />
|
||||
<fragment
|
||||
android:id="@+id/addonsManagementFragment"
|
||||
android:name="org.mozilla.fenix.addons.AddonsManagementFragment">
|
||||
<action
|
||||
android:id="@+id/action_addonsManagementFragment_to_addonDetailsFragment"
|
||||
app:destination="@id/addonDetailsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_addonsManagementFragment_to_installedAddonDetails"
|
||||
app:destination="@id/installedAddonDetailsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_addonsManagementFragment_to_notYetSupportedAddonFragment"
|
||||
app:destination="@id/notYetSupportedAddonFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/addonDetailsFragment"
|
||||
android:name="org.mozilla.fenix.addons.AddonDetailsFragment">
|
||||
<argument
|
||||
android:name="addon"
|
||||
app:argType="mozilla.components.feature.addons.Addon" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/installedAddonDetailsFragment"
|
||||
android:name="org.mozilla.fenix.addons.InstalledAddonDetailsFragment">
|
||||
<action
|
||||
android:id="@+id/action_installedAddonFragment_to_addonInternalSettingsFragment"
|
||||
app:destination="@id/addonInternalSettingsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_installedAddonFragment_to_addonDetailsFragment"
|
||||
app:destination="@id/addonDetailsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_installedAddonFragment_to_addonPermissionsDetailsFragment"
|
||||
app:destination="@id/addonPermissionsDetailFragment" />
|
||||
<argument
|
||||
android:name="addon"
|
||||
app:argType="mozilla.components.feature.addons.Addon" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/notYetSupportedAddonFragment"
|
||||
android:name="org.mozilla.fenix.addons.NotYetSupportedAddonFragment">
|
||||
<argument
|
||||
android:name="addons"
|
||||
app:argType="mozilla.components.feature.addons.Addon[]" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/addonInternalSettingsFragment"
|
||||
android:name="org.mozilla.fenix.addons.AddonInternalSettingsFragment">
|
||||
<argument
|
||||
android:name="addon"
|
||||
app:argType="mozilla.components.feature.addons.Addon" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/addonPermissionsDetailFragment"
|
||||
android:name="org.mozilla.fenix.addons.AddonPermissionsDetailsFragment">
|
||||
<argument
|
||||
android:name="addon"
|
||||
app:argType="mozilla.components.feature.addons.Addon" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/webExtensionActionPopupFragment"
|
||||
android:name="org.mozilla.fenix.addons.WebExtensionActionPopupFragment">
|
||||
<argument
|
||||
android:name="webExtensionId"
|
||||
app:argType="string" />
|
||||
<argument
|
||||
android:name="webExtensionTitle"
|
||||
app:argType="string"
|
||||
app:nullable="true"/>
|
||||
</fragment>
|
||||
</navigation>
|
||||
|
|
|
@ -28,8 +28,7 @@
|
|||
<string name="pref_key_delete_permissions_on_quit" translatable="false">pref_key_delete_permissions_on_quit</string>
|
||||
<string name="pref_key_delete_browsing_data_on_quit_categories" translatable="false">pref_key_delete_browsing_data_on_quit_categories</string>
|
||||
<string name="pref_key_last_known_mode_private" translatable="false">pref_key_last_known_mode_private</string>
|
||||
|
||||
|
||||
<string name="pref_key_addons" translatable="false">pref_key_addons</string>
|
||||
<string name="pref_key_last_maintenance" translatable="false">pref_key_last_maintenance</string>
|
||||
<string name="pref_key_help" translatable="false">pref_key_help</string>
|
||||
<string name="pref_key_rate" translatable="false">pref_key_rate</string>
|
||||
|
|
|
@ -65,6 +65,8 @@
|
|||
<!-- Content description (not visible, for screen readers etc.): Un-bookmark the current page -->
|
||||
<string name="browser_menu_edit_bookmark">Edit bookmark</string>
|
||||
<!-- Browser menu button that sends a user to help articles -->
|
||||
<string name="browser_menu_addon_manager">Add-ons Manager</string>
|
||||
<!-- Browser menu button that sends a user to help articles -->
|
||||
<string name="browser_menu_help">Help</string>
|
||||
<!-- Browser menu button that sends a to a the what's new article -->
|
||||
<string name="browser_menu_whats_new">What’s New</string>
|
||||
|
@ -242,6 +244,8 @@
|
|||
<string name="preferences_account_settings">Account settings</string>
|
||||
<!-- Preference for open links in third party apps -->
|
||||
<string name="preferences_open_links_in_apps">Open links in apps</string>
|
||||
<!-- Preference for add_ons -->
|
||||
<string name="preferences_addons">Add-ons</string>
|
||||
|
||||
<!-- Account Preferences -->
|
||||
<!-- Preference for triggering sync -->
|
||||
|
|
|
@ -104,12 +104,16 @@
|
|||
<PreferenceCategory
|
||||
android:title="@string/preferences_category_advanced"
|
||||
app:iconSpaceReserved="false"
|
||||
android:key="@string/pref_key_advanced"
|
||||
app:isPreferenceVisible="false">
|
||||
android:key="@string/pref_key_advanced">
|
||||
<androidx.preference.Preference
|
||||
android:icon="@drawable/mozac_ic_extensions_black"
|
||||
android:key="@string/pref_key_addons"
|
||||
android:title="@string/preferences_addons" />
|
||||
<androidx.preference.Preference
|
||||
android:icon="@drawable/ic_language"
|
||||
android:key="@string/pref_key_language"
|
||||
android:title="@string/preferences_language" />
|
||||
android:title="@string/preferences_language"
|
||||
app:isPreferenceVisible="false" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
|
|
|
@ -325,6 +325,15 @@ class DefaultBrowserToolbarControllerTest {
|
|||
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADD_TO_FIREFOX_HOME)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleToolbarAddonsManagerPress() = runBlockingTest {
|
||||
val item = ToolbarMenu.Item.AddonsManager
|
||||
|
||||
controller.handleToolbarItemInteraction(item)
|
||||
|
||||
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun handleToolbarAddToHomeScreenPress() {
|
||||
val item = ToolbarMenu.Item.AddToHomeScreen
|
||||
|
|
|
@ -100,6 +100,9 @@ object Deps {
|
|||
const val mozilla_browser_errorpages = "org.mozilla.components:browser-errorpages:${Versions.mozilla_android_components}"
|
||||
const val mozilla_browser_storage_sync = "org.mozilla.components:browser-storage-sync:${Versions.mozilla_android_components}"
|
||||
|
||||
const val mozilla_feature_addons = "org.mozilla.components:feature-addons:${Versions.mozilla_android_components}"
|
||||
const val mozilla_support_extensions = "org.mozilla.components:support-webextensions:${Versions.mozilla_android_components}"
|
||||
|
||||
const val mozilla_feature_accounts = "org.mozilla.components:feature-accounts:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_app_links = "org.mozilla.components:feature-app-links:${Versions.mozilla_android_components}"
|
||||
const val mozilla_feature_awesomebar = "org.mozilla.components:feature-awesomebar:${Versions.mozilla_android_components}"
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue