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

Commit

Permalink
[components] For mozilla-mobile/android-components#7833: Generate con…
Browse files Browse the repository at this point in the history
…cept-menu items for addons
  • Loading branch information
NotWoods authored and mergify[bot] committed Oct 20, 2020
1 parent a626122 commit f506f74
Show file tree
Hide file tree
Showing 19 changed files with 584 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package mozilla.components.browser.menu

import android.content.Context
import androidx.annotation.ColorRes
import mozilla.components.browser.menu.item.BackPressMenuItem
import mozilla.components.browser.menu.item.BrowserMenuDivider
import mozilla.components.browser.menu.item.BrowserMenuImageText
Expand Down Expand Up @@ -32,7 +33,7 @@ class WebExtensionBrowserMenuBuilder(
extras: Map<String, Any> = emptyMap(),
endOfMenuAlwaysVisible: Boolean = false,
private val store: BrowserStore,
private val webExtIconTintColorResource: Int = NO_ID,
@ColorRes private val webExtIconTintColorResource: Int = NO_ID,
private val onAddonsManagerTapped: () -> Unit = {},
private val appendExtensionSubMenuAtStart: Boolean = false
) : BrowserMenuBuilder(items, extras, endOfMenuAlwaysVisible) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ data class Action(
) {
/**
* Returns a copy of this [Action] with the provided override applied e.g. for tab-specific overrides.
* If the override is null, the original class is returned without making a new instance.
*
* @param override the action to use for overriding properties. Note that only the provided
* (non-null) properties of the override will be applied, all other properties will remain
* unchanged. An extension can send a tab-specific action and only include the properties
* it wants to override for the tab.
*/
fun copyWithOverride(override: Action) =
fun copyWithOverride(override: Action?) = if (override != null) {
Action(
title = override.title ?: title,
enabled = override.enabled ?: enabled,
Expand All @@ -44,6 +45,9 @@ data class Action(
loadIcon = override.loadIcon ?: loadIcon,
onClick = override.onClick
)
} else {
this
}
}

typealias WebExtensionBrowserAction = Action
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.concept.engine.webextension

import android.graphics.Color
import org.junit.Assert.assertEquals
import org.junit.Test

class ActionTest {

private val onClick: () -> Unit = {}
private val baseAction = Action(
title = "title",
enabled = false,
loadIcon = null,
badgeText = "badge",
badgeTextColor = Color.BLACK,
badgeBackgroundColor = Color.BLUE,
onClick = onClick
)

@Test
fun `override using non-null attributes`() {
val overridden = baseAction.copyWithOverride(Action(
title = "other",
enabled = null,
loadIcon = null,
badgeText = null,
badgeTextColor = Color.WHITE,
badgeBackgroundColor = null,
onClick = onClick
))

assertEquals(
Action(
title = "other",
enabled = false,
loadIcon = null,
badgeText = "badge",
badgeTextColor = Color.WHITE,
badgeBackgroundColor = Color.BLUE,
onClick = onClick
),
overridden
)
}

@Test
fun `override using null action`() {
val overridden = baseAction.copyWithOverride(null)

assertEquals(baseAction, overridden)
}
}
1 change: 1 addition & 0 deletions android-components/components/feature/addons/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ dependencies {
implementation project(':browser-state')
implementation project(':concept-engine')
implementation project(':concept-fetch')
implementation project(':concept-menu')
implementation project(':support-base')
implementation project(':support-ktx')
implementation project(':support-webextensions')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.feature.addons.menu

import android.content.Context
import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.core.graphics.drawable.toDrawable
import mozilla.components.concept.engine.webextension.Action
import mozilla.components.concept.menu.candidate.AsyncDrawableMenuIcon
import mozilla.components.concept.menu.candidate.ContainerStyle
import mozilla.components.concept.menu.candidate.TextMenuCandidate
import mozilla.components.concept.menu.candidate.TextMenuIcon
import mozilla.components.concept.menu.candidate.TextStyle
import mozilla.components.feature.addons.R

/**
* Create a browser menu item for displaying a web extension action.
*
* @param onClick a callback to be invoked when this menu item is clicked.
*/
fun Action.createMenuCandidate(
context: Context,
onClick: () -> Unit = this.onClick
): TextMenuCandidate {
return TextMenuCandidate(
title.orEmpty(),
start = loadIcon?.let { loadIcon ->
val defaultIcon = getDrawable(context, R.drawable.mozac_ic_web_extension_default_icon)
AsyncDrawableMenuIcon(
loadDrawable = { _, height ->
loadIcon(height)?.toDrawable(context.resources)
},
loadingDrawable = defaultIcon,
fallbackDrawable = defaultIcon
)
},
end = badgeText?.let { badgeText ->
TextMenuIcon(
badgeText,
backgroundTint = badgeBackgroundColor,
textStyle = TextStyle(
color = badgeTextColor
)
)
},
containerStyle = ContainerStyle(
isVisible = true,
isEnabled = enabled ?: false
),
onClick = onClick
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.feature.addons.menu

import android.content.Context
import androidx.annotation.ColorInt
import mozilla.components.browser.state.selector.findTabOrCustomTabOrSelectedTab
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.state.WebExtensionState
import mozilla.components.concept.menu.Side
import mozilla.components.concept.menu.candidate.DividerMenuCandidate
import mozilla.components.concept.menu.candidate.DrawableMenuIcon
import mozilla.components.concept.menu.candidate.MenuCandidate
import mozilla.components.concept.menu.candidate.NestedMenuCandidate
import mozilla.components.concept.menu.candidate.TextMenuCandidate
import mozilla.components.feature.addons.R

private fun createBackMenuItem(
context: Context,
@ColorInt webExtIconTintColor: Int?
) = NestedMenuCandidate(
id = R.drawable.mozac_ic_back,
text = context.getString(R.string.mozac_feature_addons_addons),
start = DrawableMenuIcon(
context,
R.drawable.mozac_ic_back,
tint = webExtIconTintColor
),
subMenuItems = null
)

private fun createAddonsManagerItem(
context: Context,
@ColorInt webExtIconTintColor: Int?,
onAddonsManagerTapped: () -> Unit
) = TextMenuCandidate(
text = context.getString(R.string.mozac_feature_addons_addons_manager),
start = DrawableMenuIcon(
context,
R.drawable.mozac_ic_extensions,
tint = webExtIconTintColor
),
onClick = onAddonsManagerTapped
)

private fun createWebExtensionSubMenuItems(
context: Context,
extensions: Collection<WebExtensionState>,
tab: SessionState?,
onAddonsItemTapped: (String) -> Unit
): List<MenuCandidate> {
val menuItems = mutableListOf<MenuCandidate>()

extensions
.filter { it.enabled }
.filterNot { !it.allowedInPrivateBrowsing && tab?.content?.private == true }
.sortedBy { it.name }
.forEach { extension ->
val tabExtensionState = tab?.extensionState?.get(extension.id)
extension.browserAction?.let { browserAction ->
menuItems.add(
browserAction.copyWithOverride(tabExtensionState?.browserAction).createMenuCandidate(
context
) {
onAddonsItemTapped(extension.id)
browserAction.onClick()
}
)
}

extension.pageAction?.let { pageAction ->
menuItems.add(
pageAction.copyWithOverride(tabExtensionState?.pageAction).createMenuCandidate(
context
) {
onAddonsItemTapped(extension.id)
pageAction.onClick()
}
)
}
}

return menuItems
}

/**
* Create a browser menu item for displaying a list of web extensions.
*
* @param tabId ID of tab used to load tab-specific extension state.
* @param webExtIconTintColor Optional color used to tint the icons of back and add-ons manager menu items.
* @param appendExtensionSubMenuAt If web extension sub menu should appear at the top (start) of
* the menu, or if web extensions should appear at the bottom of the menu (end).
* @param onAddonsItemTapped Callback to be invoked when a web extension action item is selected.
* Can be used to emit telemetry.
* @param onAddonsManagerTapped Callback to be invoked when add-ons manager menu item is selected.
*/
@Suppress("LongParameterList")
fun BrowserState.createWebExtensionMenuCandidate(
context: Context,
tabId: String? = null,
@ColorInt webExtIconTintColor: Int? = null,
appendExtensionSubMenuAt: Side = Side.END,
onAddonsItemTapped: (String) -> Unit = {},
onAddonsManagerTapped: () -> Unit = {}
): MenuCandidate {
val items = createWebExtensionSubMenuItems(
context,
extensions = extensions.values,
tab = findTabOrCustomTabOrSelectedTab(tabId),
onAddonsItemTapped = onAddonsItemTapped
)

val addonsManagerItem = createAddonsManagerItem(
context,
webExtIconTintColor = webExtIconTintColor,
onAddonsManagerTapped = onAddonsManagerTapped
)

return if (items.isNotEmpty()) {
val firstItem: MenuCandidate
val lastItem: MenuCandidate
when (appendExtensionSubMenuAt) {
Side.START -> {
firstItem = createBackMenuItem(context, webExtIconTintColor)
lastItem = addonsManagerItem
}
Side.END -> {
firstItem = addonsManagerItem
lastItem = createBackMenuItem(context, webExtIconTintColor)
}
}

NestedMenuCandidate(
id = R.string.mozac_feature_addons_addons,
text = context.getString(R.string.mozac_feature_addons_addons),
start = addonsManagerItem.start,
subMenuItems = listOf(firstItem, DividerMenuCandidate()) +
items + listOf(DividerMenuCandidate(), lastItem)
)
} else {
addonsManagerItem.copy(
text = context.getString(R.string.mozac_feature_addons_addons)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@
<string name="mozac_feature_addons_rating_content_description">%1$.02f/5</string>
<!-- This is the title of page where all the add-ons are listed-->
<string name="mozac_feature_addons_addons">Add-ons</string>
<!-- Label for add-ons sub menu item for add-ons manager-->
<string name="mozac_feature_addons_addons_manager">Add-ons Manager</string>
<!-- The label of the allow button, this will be shown to the user when an add-on needs new permissions, with the button the user will indicate that they want to accept the new permissions and update the add-on-->
<string name="mozac_feature_addons_updater_notification_allow_button">Allow</string>
<!-- The label of the deny button on a notification, this will be shown to the user when an add-on needs new permissions. Indicates the user denies the new permissions and prevents the add-on from be updated-->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.feature.addons.amo
package mozilla.components.feature.addons

import android.graphics.Bitmap
import androidx.test.ext.junit.runners.AndroidJUnit4
Expand All @@ -23,11 +23,7 @@ import mozilla.components.concept.engine.webextension.DisabledFlags
import mozilla.components.concept.engine.webextension.EnableSource
import mozilla.components.concept.engine.webextension.Metadata
import mozilla.components.concept.engine.webextension.WebExtension
import mozilla.components.feature.addons.Addon
import mozilla.components.feature.addons.AddonManager
import mozilla.components.feature.addons.AddonManager.Companion.TEMPORARY_ADDON_ICON_SIZE
import mozilla.components.feature.addons.AddonManagerException
import mozilla.components.feature.addons.AddonsProvider
import mozilla.components.feature.addons.update.AddonUpdater.Status
import mozilla.components.support.test.any
import mozilla.components.support.test.argumentCaptor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.feature.addons.amo
package mozilla.components.feature.addons

import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.feature.addons.Addon
import mozilla.components.feature.addons.R
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
Expand Down Expand Up @@ -347,4 +345,4 @@ class AddonTest {
assertEquals(R.string.mozac_feature_addons_permissions_all_urls_description, stringId)
}
}
}
}
Loading

0 comments on commit f506f74

Please sign in to comment.