Bug 1948633 - part 3 - Render pageOrigin from the state r=android-reviewers,android-l10n-reviewers,delphine,tchoh

Because of a Jetpack Compose bug which prevents properly anchoring
Dropdowns to the bottom of the screen in order to support showing
the long press menu from a bottom toolbar I had to use a View for
the origin instead of a Composable.

Implementation is to change after Google fixes the Compose bug.

Differential Revision: https://phabricator.services.mozilla.com/D245953
This commit is contained in:
Mugurell
2025-04-23 17:55:46 +00:00
parent aea8745dac
commit b7c528df3b
11 changed files with 527 additions and 35 deletions

View File

@@ -5,30 +5,29 @@
package mozilla.components.compose.browser.toolbar
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.unit.dp
import mozilla.components.compose.base.progressbar.AnimatedProgressBar
import mozilla.components.compose.base.theme.AcornTheme
import mozilla.components.compose.browser.toolbar.concept.Action
import mozilla.components.compose.browser.toolbar.concept.PageOrigin
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarInteraction.BrowserToolbarEvent
import mozilla.components.compose.browser.toolbar.store.ProgressBarConfig
import mozilla.components.compose.browser.toolbar.store.ProgressBarGravity.Bottom
import mozilla.components.compose.browser.toolbar.store.ProgressBarGravity.Top
import mozilla.components.compose.browser.toolbar.ui.Origin
private val ROUNDED_CORNER_SHAPE = RoundedCornerShape(8.dp)
@@ -36,36 +35,37 @@ private val ROUNDED_CORNER_SHAPE = RoundedCornerShape(8.dp)
* Sub-component of the [BrowserToolbar] responsible for displaying the URL and related
* controls ("display mode").
*
* @param url The URL to be displayed.
* @param pageOrigin Details about the website origin.
* @param colors The color scheme to use in the browser display toolbar.
* @param progressBarConfig [ProgressBarConfig] configuration for the progress bar.
* If `null` a progress bar will not be displayed.
* @param textStyle [TextStyle] configuration for the URL text.
* @param browserActionsStart List of browser [Action]s to be displayed at the start of the
* toolbar, outside of the URL bounding box.
* These should be actions relevant to the browser as a whole.
* See [MDN docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/browserAction).
* @param pageActionsStart List of navigation [Action]s to be displayed between [browserActionsStart]
* and the current webpage's details, inside of the URL bounding box.
* and [pageOrigin], inside of the URL bounding box.
* These should be actions relevant to specific webpages as opposed to [browserActionsStart].
* See [MDN docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/pageAction).
* @param browserActions List of browser [Action]s to be displayed on the right side of the
* display toolbar (outside of the URL bounding box). Also see:
* [MDN docs](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/user_interface/Browser_action)
* @param onUrlClicked Will be called when the user clicks on the URL.
* @param pageActionsEnd List of page [Action]s to be displayed between [pageOrigin] and [browserActionsEnd],
* inside of the URL bounding box.
* These should be actions relevant to specific webpages as opposed to [browserActionsStart].
* See [MDN docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/pageAction).
* @param browserActionsEnd List of browser [Action]s to be displayed at the end of the toolbar,
* outside of the URL bounding box.
* These should be actions relevant to the browser as a whole.
* See [MDN docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/browserAction).
* @param onInteraction Callback for handling [BrowserToolbarEvent]s on user interactions.
*/
@Composable
fun BrowserDisplayToolbar(
url: String,
pageOrigin: PageOrigin,
colors: BrowserDisplayToolbarColors,
progressBarConfig: ProgressBarConfig?,
textStyle: TextStyle = LocalTextStyle.current,
browserActionsStart: List<Action> = emptyList(),
pageActionsStart: List<Action> = emptyList(),
pageActions: List<Action> = emptyList(),
browserActions: List<Action> = emptyList(),
onUrlClicked: () -> Unit = {},
onInteraction: (BrowserToolbarEvent) -> Unit,
) {
Box(
@@ -99,15 +99,18 @@ fun BrowserDisplayToolbar(
onInteraction = onInteraction,
)
}
Text(
url,
color = colors.text,
Origin(
hint = pageOrigin.hint,
modifier = Modifier
.clickable { onUrlClicked() }
.padding(8.dp)
.height(48.dp)
.weight(1f),
maxLines = 1,
style = textStyle,
url = pageOrigin.url,
title = pageOrigin.title,
onClick = pageOrigin.onClick,
onLongClick = pageOrigin.onLongClick,
onInteraction = onInteraction,
fadeDirection = pageOrigin.fadeDirection,
textGravity = pageOrigin.textGravity,
)
ActionContainer(
@@ -144,7 +147,12 @@ fun BrowserDisplayToolbar(
private fun BrowserDisplayToolbarPreview() {
AcornTheme {
BrowserDisplayToolbar(
url = "http://www.mozilla.org",
pageOrigin = PageOrigin(
hint = R.string.mozac_browser_toolbar_search_hint,
title = null,
url = null,
onClick = object : BrowserToolbarEvent {},
),
colors = BrowserDisplayToolbarColors(
background = AcornTheme.colors.layer1,
urlBackground = AcornTheme.colors.layer3,

View File

@@ -27,8 +27,6 @@ import mozilla.components.lib.state.ext.observeAsState
* "edit" mode.
* @param onTextCommit Function to get executed when the user has finished editing the URL and wants
* to load the entered text.
* @param onDisplayToolbarClick Function to get executed when the user clicks on the URL in "display"
* mode.
* @param colors The color scheme the browser toolbar will use for the UI.
*/
@Composable
@@ -38,7 +36,6 @@ fun BrowserToolbar(
target: Target,
onTextEdit: (String) -> Unit,
onTextCommit: (String) -> Unit,
onDisplayToolbarClick: () -> Unit,
colors: BrowserToolbarColors = BrowserToolbarDefaults.colors(),
) {
val uiState by store.observeAsState(initialValue = store.state) { it }
@@ -66,16 +63,13 @@ fun BrowserToolbar(
)
} else {
BrowserDisplayToolbar(
url = selectedTab?.content?.url ?: uiState.displayState.hint,
pageOrigin = uiState.displayState.pageOrigin,
colors = colors.displayToolbarColors,
progressBarConfig = progressBarConfig,
browserActionsStart = uiState.displayState.browserActionsStart,
pageActionsStart = uiState.displayState.pageActionsStart,
pageActions = uiState.displayState.pageActions,
browserActions = uiState.displayState.browserActions,
onUrlClicked = {
onDisplayToolbarClick()
},
onInteraction = { store.dispatch(it) },
)
}

View File

@@ -0,0 +1,61 @@
/* 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.compose.browser.toolbar.concept
import androidx.annotation.IntDef
import androidx.annotation.StringRes
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarInteraction
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarInteraction.BrowserToolbarEvent
/**
* Details about the website origin.
*
* @property title The title of the website.
* @property url The URL of the website.
* @property hint Text displayed in the toolbar when there's no URL to display (e.g.: no tab or empty URL)
* @property onClick [BrowserToolbarInteraction] describing how to handle any page origin detail being clicked.
* @property onLongClick Optional [BrowserToolbarInteraction] describing how to handle any page origin detail
* being long clicked.
* @property fadeDirection The direction in which the text should fade.
* @property textGravity The gravity of the text - whether to show the start or end of long text
* that does not fit the available space.
*/
data class PageOrigin(
@StringRes val hint: Int,
val title: String?,
val url: String?,
val onClick: BrowserToolbarEvent,
val onLongClick: BrowserToolbarInteraction? = null,
@FadeDirection val fadeDirection: Int = FADE_DIRECTION_END,
@TextGravity val textGravity: Int = TEXT_GRAVITY_START,
) {
/**
* Static values used in the configuration of the [PageOrigin] class.
*/
companion object {
const val FADE_DIRECTION_NONE = -1
const val FADE_DIRECTION_START = 0
const val FADE_DIRECTION_END = 1
const val TEXT_GRAVITY_START = 0
const val TEXT_GRAVITY_END = 1
/**
* The direction in which the text should fade.
* Values: [FADE_DIRECTION_NONE], [FADE_DIRECTION_START], [FADE_DIRECTION_END].
*/
@IntDef(value = [FADE_DIRECTION_NONE, FADE_DIRECTION_START, FADE_DIRECTION_END])
@Retention(AnnotationRetention.SOURCE)
annotation class FadeDirection
/**
* The gravity of the text - whether to show the start or end of long text.
* Values: [TEXT_GRAVITY_START], [TEXT_GRAVITY_END].
*/
@IntDef(value = [TEXT_GRAVITY_START, TEXT_GRAVITY_END])
@Retention(AnnotationRetention.SOURCE)
annotation class TextGravity
}
}

View File

@@ -4,6 +4,7 @@
package mozilla.components.compose.browser.toolbar.store
import mozilla.components.compose.browser.toolbar.concept.PageOrigin
import mozilla.components.lib.state.Action
import mozilla.components.compose.browser.toolbar.concept.Action as ToolbarAction
@@ -52,6 +53,13 @@ sealed class BrowserDisplayToolbarAction : BrowserToolbarAction {
*/
data class PageActionsStartUpdated(val actions: List<ToolbarAction>) : BrowserDisplayToolbarAction()
/**
* Replace the currently displayed details of the page with the newly provided details.
*
* @property pageOrigin The new details of the current webpage.
*/
data class PageOriginUpdated(val pageOrigin: PageOrigin) : BrowserDisplayToolbarAction()
/**
* Adds a browser [Action] to be displayed to the right side of the URL of the display toolbar
* (outside of the URL bounding box).

View File

@@ -6,7 +6,10 @@ package mozilla.components.compose.browser.toolbar.store
import androidx.annotation.IntRange
import androidx.compose.ui.graphics.Color
import mozilla.components.compose.browser.toolbar.R
import mozilla.components.compose.browser.toolbar.concept.Action
import mozilla.components.compose.browser.toolbar.concept.PageOrigin
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarInteraction.BrowserToolbarEvent
import mozilla.components.lib.state.State
/**
@@ -56,6 +59,10 @@ enum class Mode {
* and the current webpage's details, inside of the URL bounding box.
* These should be actions relevant to specific webpages as opposed to [browserActionsStart].
* See [MDN docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/pageAction).
* @param pageOrigin Details about the current website.
* @param pageActions List of page [Action]s to be displayed to the right side of the URL of the
* display toolbar. Also see:
* [MDN docs](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/pageAction)
* @property browserActions List of browser [Action]s to be displayed on the right side of the
* display toolbar (outside of the URL bounding box). Also see:
* [MDN docs](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/user_interface/Browser_action)
@@ -66,6 +73,12 @@ data class DisplayState(
val hint: String = "",
val browserActionsStart: List<Action> = emptyList(),
val pageActionsStart: List<Action> = emptyList(),
val pageOrigin: PageOrigin = PageOrigin(
hint = R.string.mozac_browser_toolbar_search_hint,
title = null,
url = null,
onClick = object : BrowserToolbarEvent {},
),
val pageActions: List<Action> = emptyList(),
val browserActions: List<Action> = emptyList(),
val progressBarConfig: ProgressBarConfig? = null,

View File

@@ -6,6 +6,7 @@ package mozilla.components.compose.browser.toolbar.store
import mozilla.components.compose.browser.toolbar.store.BrowserDisplayToolbarAction.BrowserActionsStartUpdated
import mozilla.components.compose.browser.toolbar.store.BrowserDisplayToolbarAction.PageActionsStartUpdated
import mozilla.components.compose.browser.toolbar.store.BrowserDisplayToolbarAction.PageOriginUpdated
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarInteraction.BrowserToolbarEvent
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.UiStore
@@ -66,6 +67,12 @@ private fun reduce(state: BrowserToolbarState, action: BrowserToolbarAction): Br
),
)
is PageOriginUpdated -> state.copy(
displayState = state.displayState.copy(
pageOrigin = action.pageOrigin,
),
)
is BrowserDisplayToolbarAction.AddBrowserAction -> state.copy(
displayState = state.displayState.copy(
browserActions = state.displayState.browserActions + action.action,

View File

@@ -0,0 +1,73 @@
/* 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.compose.browser.toolbar.ui
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.widget.TextView
import androidx.appcompat.widget.AppCompatTextView
import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.FADE_DIRECTION_END
import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.FADE_DIRECTION_START
import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.FadeDirection
import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.TEXT_GRAVITY_END
import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.TextGravity
import kotlin.math.max
import kotlin.math.roundToInt
/**
* Custom [TextView] meant to only be used programatically with special support for longer texts that:
* - can be faded at the start or end of the screen or not at all
* - can have the start text shown with the end clipped or the end text shown with the start clipped.
*
* @param context [Context] used to instantiate this `View`.
* @param fadeDirection [FadeDirection] The direction in which the text should be faded.
* @param textGravity [TextGravity] The gravity of the text.
*/
@SuppressLint("ViewConstructor")
internal class CustomFadeAndGravityTextView(
context: Context,
@FadeDirection private val fadeDirection: Int,
@TextGravity private val textGravity: Int,
) : AppCompatTextView(context) {
override fun getLeftFadingEdgeStrength() = when (fadeDirection) {
FADE_DIRECTION_START -> getFadeStrength()
else -> 0f
}
override fun getRightFadingEdgeStrength() = when (fadeDirection) {
FADE_DIRECTION_END -> getFadeStrength()
else -> 0f
}
override fun draw(canvas: Canvas) {
if (textGravity == TEXT_GRAVITY_END) {
scrollToEnd()
}
super.draw(canvas)
}
private fun getFadeStrength() =
when (computeHorizontalScrollOffset() + computeHorizontalScrollExtent() < computeHorizontalScrollRange()) {
true -> FADE_STRENGTH_FULL
false -> FADE_STRENGTH_NONE
}
private fun scrollToEnd() {
if (layout != null) {
val textWidth = layout.getLineWidth(0)
val availableWidth = measuredWidth - paddingLeft - paddingRight
val textOverflow = max(textWidth - availableWidth - SCROLL_OFFSET_TO_FORCE_FADE, 0f)
scrollTo(textOverflow.roundToInt(), 0)
}
}
companion object {
const val FADE_STRENGTH_FULL = 1f
const val FADE_STRENGTH_NONE = 0f
const val SCROLL_OFFSET_TO_FORCE_FADE = 2
}
}

View File

@@ -0,0 +1,298 @@
/* 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.compose.browser.toolbar.ui
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.content.res.Configuration.UI_MODE_TYPE_NORMAL
import android.view.Gravity
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.LinearLayout
import androidx.annotation.StringRes
import androidx.appcompat.widget.AppCompatTextView
import androidx.compose.foundation.background
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.isVisible
import mozilla.components.browser.menu2.BrowserMenuController
import mozilla.components.compose.base.theme.AcornTheme
import mozilla.components.compose.browser.toolbar.R
import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.FADE_DIRECTION_END
import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.FADE_DIRECTION_START
import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.FadeDirection
import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.TEXT_GRAVITY_END
import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.TEXT_GRAVITY_START
import mozilla.components.compose.browser.toolbar.concept.PageOrigin.Companion.TextGravity
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarInteraction
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarInteraction.BrowserToolbarEvent
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarInteraction.BrowserToolbarMenu
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarInteraction.CombinedEventAndMenu
private const val URL_TEXT_SIZE_ALONE = 15
private const val URL_TEXT_SIZE_WITH_TITLE = 12
/**
* Custom layout for showing the origin - title and url of a webpage.
*
* @param hint [StringRes] the string to show when the URL is not available
* @param modifier [Modifier] to apply to this composable for further customisation.
* @param url the URL of the webpage. Can be `null` or empty, in which vase the [hint] will be shown.
* @param title the title of the webpage. Can be `null` or empty, in which case only [url] or [hint] will be shown.
* @param onClick [BrowserToolbarEvent] to be dispatched when this layout is clicked.
* @param onLongClick Optional [BrowserToolbarInteraction] describing how to handle this layout being long clicked.
* @param onInteraction [BrowserToolbarInteraction] to be dispatched when this layout is interacted with.
* @param onInteraction Callback for handling [BrowserToolbarEvent]s on user interactions.
* @param fadeDirection [FadeDirection] How the displayed text should be faded.
* @param textGravity [TextGravity] How the displayed text should be aligned.
*/
@Composable
internal fun Origin(
@StringRes hint: Int,
modifier: Modifier = Modifier,
url: String? = null,
title: String? = null,
onClick: BrowserToolbarEvent,
onLongClick: BrowserToolbarInteraction?,
onInteraction: (BrowserToolbarEvent) -> Unit,
@FadeDirection fadeDirection: Int,
@TextGravity textGravity: Int,
) {
OriginView(
hint = stringResource(hint),
url = url,
title = title,
fadeDirection = fadeDirection,
textGravity = textGravity,
onClick = onClick,
onLongClick = onLongClick,
onInteraction = onInteraction,
modifier = modifier,
)
}
@Composable
@Suppress("LongMethod", "LongParameterList")
private fun OriginView(
hint: String,
url: String?,
title: String?,
@FadeDirection fadeDirection: Int,
@TextGravity textGravity: Int,
onClick: BrowserToolbarEvent,
onLongClick: BrowserToolbarInteraction?,
onInteraction: (BrowserToolbarEvent) -> Unit,
modifier: Modifier = Modifier,
) {
val shouldShowTitle = remember { title != null && title.isNotEmpty() }
val urlTextSize = remember(shouldShowTitle) {
when (shouldShowTitle) {
true -> URL_TEXT_SIZE_WITH_TITLE.sp
false -> URL_TEXT_SIZE_ALONE.sp
}
}
val urlGravity = remember(shouldShowTitle) {
when (shouldShowTitle) {
true -> Gravity.TOP or Gravity.LEFT
false -> Gravity.CENTER_VERTICAL or Gravity.LEFT
}
}
val urlToShow = remember(url) {
when (url == null || url.isBlank()) {
true -> hint
else -> url
}
}
val longClickMenu = key(onLongClick) { onLongClick.buildMenu(onInteraction) }
val textColor = AcornTheme.colors.textPrimary
AndroidView(
modifier = modifier,
factory = { context ->
LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
gravity = Gravity.CENTER_VERTICAL
isClickable = true
isFocusable = true
addView(
CustomFadeAndGravityTextView(context, fadeDirection, textGravity).apply {
text = title
gravity = Gravity.BOTTOM or Gravity.LEFT
setSingleLine()
isVisible = shouldShowTitle
textSize = URL_TEXT_SIZE_ALONE.sp.value
setTextColor(textColor.toArgb())
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, 0, 1f)
},
)
addView(
CustomFadeAndGravityTextView(context, fadeDirection, textGravity).apply {
text = urlToShow
gravity = urlGravity
setSingleLine()
textSize = urlTextSize.value
setTextColor(textColor.toArgb())
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, 0, 1f)
},
)
setOnClickListener { onInteraction(onClick) }
setLongClickHandling(onLongClick, longClickMenu, onInteraction)
}
},
update = { container ->
(container.getChildAt(0) as? AppCompatTextView)?.apply {
text = title
isVisible = shouldShowTitle
}
(container.getChildAt(1) as? AppCompatTextView)?.apply {
text = urlToShow
gravity = urlGravity
textSize = urlTextSize.value
}
container.setLongClickHandling(onLongClick, longClickMenu, onInteraction)
},
)
}
private fun View.setLongClickHandling(
onLongClick: BrowserToolbarInteraction?,
longClickMenu: BrowserMenuController?,
onInteraction: (BrowserToolbarEvent) -> Unit,
) {
if (onLongClick is BrowserToolbarEvent) {
setOnLongClickListener {
onInteraction(onLongClick)
true
}
} else if (onLongClick is BrowserToolbarMenu && longClickMenu != null) {
setOnLongClickListener {
longClickMenu.show(anchor = this)
true
}
} else if (onLongClick is CombinedEventAndMenu && longClickMenu != null) {
setOnLongClickListener {
onInteraction(onLongClick.event)
longClickMenu.show(anchor = this)
true
}
} else {
setOnLongClickListener(null)
}
}
@Preview(showBackground = true)
@Composable
private fun OriginPreviewWithJustTheHint() {
AcornTheme {
Origin(
hint = R.string.mozac_browser_toolbar_search_hint,
url = null,
title = null,
onClick = object : BrowserToolbarEvent {},
onLongClick = null,
onInteraction = {},
fadeDirection = FADE_DIRECTION_END,
textGravity = TEXT_GRAVITY_START,
)
}
}
@Preview(uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL)
@Composable
private fun OriginPreviewWithTitleAndURL() {
AcornTheme {
Origin(
hint = R.string.mozac_browser_toolbar_search_hint,
modifier = Modifier.background(AcornTheme.colors.layer1),
url = "https://mozilla.com",
title = "Test title",
onClick = object : BrowserToolbarEvent {},
onLongClick = null,
onInteraction = {},
fadeDirection = FADE_DIRECTION_END,
textGravity = TEXT_GRAVITY_START,
)
}
}
@Preview(showBackground = true, widthDp = 160)
@Composable
private fun OriginPreviewWithTitleAndURLStart() {
AcornTheme {
Origin(
hint = R.string.mozac_browser_toolbar_search_hint,
url = "https://mozilla.com/firefox-browser",
title = "Test title",
onClick = object : BrowserToolbarEvent {},
onLongClick = null,
onInteraction = {},
fadeDirection = FADE_DIRECTION_END,
textGravity = TEXT_GRAVITY_START,
)
}
}
@Preview(widthDp = 160, uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL)
@Composable
private fun OriginPreviewWithTitleAndURLEnd() {
AcornTheme {
Origin(
hint = R.string.mozac_browser_toolbar_search_hint,
modifier = Modifier.background(AcornTheme.colors.layer1),
url = "https://mozilla.com/firefox-browser",
title = "Test title",
onClick = object : BrowserToolbarEvent {},
onLongClick = null,
onInteraction = {},
fadeDirection = FADE_DIRECTION_END,
textGravity = TEXT_GRAVITY_END,
)
}
}
@Preview(widthDp = 160)
@Composable
private fun OriginPreviewWithJustURLStart() {
AcornTheme {
Origin(
hint = R.string.mozac_browser_toolbar_search_hint,
modifier = Modifier.background(AcornTheme.colors.layer1),
url = "https://mozilla.com/firefox-browser",
title = null,
onClick = object : BrowserToolbarEvent {},
onLongClick = null,
onInteraction = {},
fadeDirection = FADE_DIRECTION_END,
textGravity = TEXT_GRAVITY_START,
)
}
}
@Preview(widthDp = 160, uiMode = UI_MODE_NIGHT_YES or UI_MODE_TYPE_NORMAL)
@Composable
private fun OriginPreviewWithJustURLEnd() {
AcornTheme {
Origin(
hint = R.string.mozac_browser_toolbar_search_hint,
modifier = Modifier.background(AcornTheme.colors.layer1),
url = "https://mozilla.com/firefox-browser",
title = null,
onClick = object : BrowserToolbarEvent {},
onLongClick = null,
onInteraction = {},
fadeDirection = FADE_DIRECTION_START,
textGravity = TEXT_GRAVITY_END,
)
}
}

View File

@@ -5,4 +5,6 @@
<resources>
<!-- Content description for the clear URL text button. -->
<string name="mozac_clear_button_description">Clear</string>
<!-- Placeholder text shown in the search bar before a user enters text for the default engine -->
<string name="mozac_browser_toolbar_search_hint">Search or enter address</string>
</resources>

View File

@@ -5,9 +5,12 @@
package mozilla.components.compose.browser.toolbar.store
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.compose.browser.toolbar.R
import mozilla.components.compose.browser.toolbar.concept.Action.ActionButton
import mozilla.components.compose.browser.toolbar.concept.PageOrigin
import mozilla.components.compose.browser.toolbar.store.BrowserDisplayToolbarAction.BrowserActionsStartUpdated
import mozilla.components.compose.browser.toolbar.store.BrowserDisplayToolbarAction.PageActionsStartUpdated
import mozilla.components.compose.browser.toolbar.store.BrowserDisplayToolbarAction.PageOriginUpdated
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarInteraction.BrowserToolbarEvent
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.Assert.assertEquals
@@ -138,6 +141,29 @@ class BrowserToolbarStoreTest {
assertEquals(0, store.state.displayState.pageActionsStart.size)
}
@Test
fun `WHEN updating the page origin details THEN replace the old details with the new ones`() {
val store = BrowserToolbarStore()
val defaultPageDetails = PageOrigin(
hint = R.string.mozac_browser_toolbar_search_hint,
title = null,
url = null,
onClick = object : BrowserToolbarEvent {},
)
val newPageDetails = PageOrigin(
hint = Random.nextInt(),
title = "test",
url = "https://firefox.com",
onClick = object : BrowserToolbarEvent {},
onLongClick = object : BrowserToolbarEvent {},
)
assertPageOriginEquals(defaultPageDetails, store.state.displayState.pageOrigin)
store.dispatch(PageOriginUpdated(newPageDetails))
assertEquals(newPageDetails, store.state.displayState.pageOrigin)
}
@Test
fun `WHEN add browser action is dispatched THEN update display browser actions state`() {
val store = BrowserToolbarStore()
@@ -164,4 +190,11 @@ class BrowserToolbarStoreTest {
tint = Random.nextInt(),
onClick = object : BrowserToolbarEvent {},
)
private fun assertPageOriginEquals(expected: PageOrigin, actual: PageOrigin) {
assertEquals(expected.hint, actual.hint)
assertEquals(expected.title, actual.title)
assertEquals(expected.url, actual.url)
// Cannot check the onClick and onLongClick anonymous object
}
}

View File

@@ -24,14 +24,12 @@ import mozilla.components.lib.state.ext.observeAsState
* @param onTextEdit Invoked when the user edits the text in the toolbar in "edit" mode.
* @param onTextCommit Invoked when the user has finished editing the URL and wants
* to commit the entered text.
* @param onDisplayToolbarClick Invoked when the user clicks on the URL in "display" mode.
* @param colors The color scheme the browser toolbar will use for the UI.
*/
@Suppress("MagicNumber")
@Composable
fun BrowserToolbar(
store: BrowserToolbarStore,
onDisplayToolbarClick: () -> Unit,
onTextEdit: (String) -> Unit,
onTextCommit: (String) -> Unit,
colors: BrowserToolbarColors = BrowserToolbarDefaults.colors(),
@@ -57,16 +55,13 @@ fun BrowserToolbar(
)
} else {
BrowserDisplayToolbar(
url = url.takeIf { it.isNotEmpty() } ?: uiState.displayState.hint,
pageOrigin = uiState.displayState.pageOrigin,
colors = colors.displayToolbarColors,
progressBarConfig = progressBarConfig,
browserActionsStart = uiState.displayState.browserActionsStart,
pageActionsStart = uiState.displayState.pageActionsStart,
pageActions = uiState.displayState.pageActions,
browserActions = uiState.displayState.browserActions,
onUrlClicked = {
onDisplayToolbarClick()
},
onInteraction = { store.dispatch(it) },
)
}