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:
@@ -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,
|
||||
|
||||
@@ -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) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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).
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) },
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user