Bug 1940636 - Add an initial custom tab toolbar implementation r=android-reviewers,jonalmeida
Differential Revision: https://phabricator.services.mozilla.com/D233612
This commit is contained in:
@@ -10,6 +10,7 @@ import mozilla.components.browser.state.helper.Target
|
||||
import mozilla.components.browser.state.state.SessionState
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarStore
|
||||
import mozilla.components.compose.browser.toolbar.store.Mode
|
||||
import mozilla.components.lib.state.ext.observeAsState
|
||||
|
||||
/**
|
||||
@@ -52,7 +53,8 @@ fun BrowserToolbar(
|
||||
else -> editText
|
||||
}
|
||||
|
||||
if (uiState.editMode) {
|
||||
when (uiState.mode) {
|
||||
Mode.EDIT -> {
|
||||
BrowserEditToolbar(
|
||||
url = input,
|
||||
colors = colors.editToolbarColors,
|
||||
@@ -61,7 +63,9 @@ fun BrowserToolbar(
|
||||
onUrlCommitted = { text -> onTextCommit(text) },
|
||||
onUrlEdit = { text -> onTextEdit(text) },
|
||||
)
|
||||
} else {
|
||||
}
|
||||
|
||||
Mode.DISPLAY -> {
|
||||
BrowserDisplayToolbar(
|
||||
url = selectedTab?.content?.url ?: uiState.displayState.hint,
|
||||
colors = colors.displayToolbarColors,
|
||||
@@ -73,4 +77,16 @@ fun BrowserToolbar(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Mode.CUSTOM_TAB -> {
|
||||
CustomTabToolbar(
|
||||
url = selectedTab?.content?.url ?: "",
|
||||
title = selectedTab?.content?.title ?: "",
|
||||
colors = colors.customTabToolbarColor,
|
||||
navigationActions = uiState.displayState.navigationActions,
|
||||
pageActions = uiState.displayState.pageActions,
|
||||
browserActions = uiState.displayState.browserActions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,14 +9,29 @@ import androidx.compose.ui.graphics.Color
|
||||
/**
|
||||
* Represents the colors used by the browser toolbar.
|
||||
*
|
||||
* @property customTabToolbarColor The color scheme to use in the custom tab toolbar.
|
||||
* @property displayToolbarColors The color scheme to use in browser display toolbar.
|
||||
* @property editToolbarColors The color scheme to use in the browser edit toolbar.
|
||||
*/
|
||||
data class BrowserToolbarColors(
|
||||
val customTabToolbarColor: CustomTabToolbarColors,
|
||||
val displayToolbarColors: BrowserDisplayToolbarColors,
|
||||
val editToolbarColors: BrowserEditToolbarColors,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the colors used by the custom tab toolbar.
|
||||
*
|
||||
* @property background Background color of the toolbar.
|
||||
* @property title Text color of the title.
|
||||
* @property url Text color of the URL.
|
||||
*/
|
||||
data class CustomTabToolbarColors(
|
||||
val background: Color,
|
||||
val title: Color,
|
||||
val url: Color,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents the colors used by the browser display toolbar.
|
||||
*
|
||||
|
||||
@@ -18,6 +18,11 @@ object BrowserToolbarDefaults {
|
||||
*/
|
||||
@Composable
|
||||
fun colors(
|
||||
customTabToolbarColors: CustomTabToolbarColors = CustomTabToolbarColors(
|
||||
background = AcornTheme.colors.layer1,
|
||||
title = AcornTheme.colors.textPrimary,
|
||||
url = AcornTheme.colors.textPrimary,
|
||||
),
|
||||
displayToolbarColors: BrowserDisplayToolbarColors = BrowserDisplayToolbarColors(
|
||||
background = AcornTheme.colors.layer1,
|
||||
urlBackground = AcornTheme.colors.layer3,
|
||||
@@ -30,6 +35,7 @@ object BrowserToolbarDefaults {
|
||||
clearButton = AcornTheme.colors.iconPrimary,
|
||||
),
|
||||
) = BrowserToolbarColors(
|
||||
customTabToolbarColor = customTabToolbarColors,
|
||||
displayToolbarColors = displayToolbarColors,
|
||||
editToolbarColors = editToolbarColors,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/* 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
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
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.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import mozilla.components.compose.base.theme.AcornTheme
|
||||
import mozilla.components.compose.browser.toolbar.concept.Action
|
||||
import mozilla.components.ui.icons.R as iconsR
|
||||
|
||||
/**
|
||||
* Sub-component of the [BrowserToolbar] responsible for displaying the custom tab.
|
||||
*
|
||||
* @param url The URL to be displayed.
|
||||
* @param title The title to be displayed.
|
||||
* @param colors The color scheme to use in the custom tab toolbar.
|
||||
* @param navigationActions List of navigation [Action]s to be displayed on left side of the
|
||||
* display toolbar (outside of the URL bounding box).
|
||||
* @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)
|
||||
* @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)
|
||||
*/
|
||||
@Composable
|
||||
fun CustomTabToolbar(
|
||||
url: String,
|
||||
title: String,
|
||||
colors: CustomTabToolbarColors,
|
||||
navigationActions: List<Action> = emptyList(),
|
||||
pageActions: List<Action> = emptyList(),
|
||||
browserActions: List<Action> = emptyList(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(56.dp)
|
||||
.background(color = colors.background),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
ActionContainer(actions = navigationActions)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
if (title.isNotEmpty()) {
|
||||
Text(
|
||||
text = title,
|
||||
color = colors.title,
|
||||
maxLines = 1,
|
||||
style = AcornTheme.typography.headline8,
|
||||
)
|
||||
}
|
||||
|
||||
if (url.isNotEmpty()) {
|
||||
Text(
|
||||
text = url,
|
||||
color = colors.url,
|
||||
maxLines = 1,
|
||||
style = AcornTheme.typography.caption,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ActionContainer(actions = pageActions)
|
||||
|
||||
ActionContainer(actions = browserActions)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun CustomTabToolbarPreview() {
|
||||
AcornTheme {
|
||||
CustomTabToolbar(
|
||||
url = "http://www.mozilla.org",
|
||||
title = "Mozilla",
|
||||
colors = CustomTabToolbarColors(
|
||||
background = AcornTheme.colors.layer1,
|
||||
title = AcornTheme.colors.textPrimary,
|
||||
url = AcornTheme.colors.textSecondary,
|
||||
),
|
||||
navigationActions = listOf(
|
||||
Action.ActionButton(
|
||||
icon = iconsR.drawable.mozac_ic_cross_24,
|
||||
contentDescription = null,
|
||||
tint = AcornTheme.colors.iconPrimary.toArgb(),
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
browserActions = listOf(
|
||||
Action.ActionButton(
|
||||
icon = iconsR.drawable.mozac_ic_arrow_clockwise_24,
|
||||
contentDescription = null,
|
||||
tint = AcornTheme.colors.iconPrimary.toArgb(),
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -10,15 +10,42 @@ import mozilla.components.lib.state.State
|
||||
/**
|
||||
* The state of the browser toolbar.
|
||||
*
|
||||
* @property mode The display [Mode] of the browser toolbar.
|
||||
* @property displayState Wrapper containing the toolbar display state.
|
||||
* @property editState Wrapper containing the toolbar edit state.
|
||||
* @property editMode Whether the toolbar is in "edit" or "display" mode.
|
||||
*/
|
||||
data class BrowserToolbarState(
|
||||
val mode: Mode = Mode.DISPLAY,
|
||||
val displayState: DisplayState = DisplayState(),
|
||||
val editState: EditState = EditState(),
|
||||
val editMode: Boolean = false,
|
||||
) : State
|
||||
|
||||
) : State {
|
||||
|
||||
/**
|
||||
* Returns true if the browser toolbar is in edit mode and false otherwise.
|
||||
*/
|
||||
fun isEditMode() = this.mode == Mode.EDIT
|
||||
}
|
||||
|
||||
/**
|
||||
* The various display mode of the browser toolbar.
|
||||
*/
|
||||
enum class Mode {
|
||||
/**
|
||||
* Display mode - Shows the URL and related toolbar actions.
|
||||
*/
|
||||
DISPLAY,
|
||||
|
||||
/**
|
||||
* Edit mode - Allows the user to edit the URL.
|
||||
*/
|
||||
EDIT,
|
||||
|
||||
/**
|
||||
* Custom tab - Displays the URL and title of a custom tab.
|
||||
*/
|
||||
CUSTOM_TAB,
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper containing the toolbar display state.
|
||||
|
||||
@@ -19,7 +19,7 @@ class BrowserToolbarStore(
|
||||
private fun reduce(state: BrowserToolbarState, action: BrowserToolbarAction): BrowserToolbarState {
|
||||
return when (action) {
|
||||
is BrowserToolbarAction.ToggleEditMode -> state.copy(
|
||||
editMode = action.editMode,
|
||||
mode = if (action.editMode) Mode.EDIT else Mode.DISPLAY,
|
||||
editState = state.editState.copy(
|
||||
editText = if (action.editMode) null else state.editState.editText,
|
||||
),
|
||||
|
||||
@@ -9,9 +9,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import mozilla.components.compose.browser.toolbar.concept.Action.ActionButton
|
||||
import mozilla.components.support.test.rule.MainCoroutineRule
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
@@ -24,15 +22,15 @@ class BrowserToolbarStoreTest {
|
||||
val coroutineTestRule = MainCoroutineRule()
|
||||
|
||||
@Test
|
||||
fun `WHEN toggle edit mode action is dispatched THEN update the edit mode and states`() {
|
||||
fun `WHEN toggle edit mode action is dispatched THEN update the mode and edit text states`() {
|
||||
val store = BrowserToolbarStore()
|
||||
val editMode = true
|
||||
|
||||
assertFalse(store.state.editMode)
|
||||
assertEquals(Mode.DISPLAY, store.state.mode)
|
||||
|
||||
store.dispatch(BrowserToolbarAction.ToggleEditMode(editMode = editMode))
|
||||
|
||||
assertTrue(store.state.editMode)
|
||||
assertEquals(Mode.EDIT, store.state.mode)
|
||||
assertNull(store.state.editState.editText)
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ fun BrowserScreen(navController: NavController) {
|
||||
|
||||
val loadUrl = components().sessionUseCases.loadUrl
|
||||
|
||||
BackHandler(enabled = toolbarState.editMode) {
|
||||
BackHandler(enabled = toolbarState.isEditMode()) {
|
||||
toolbarStore.dispatch(BrowserToolbarAction.ToggleEditMode(false))
|
||||
}
|
||||
|
||||
@@ -126,7 +126,7 @@ fun BrowserScreen(navController: NavController) {
|
||||
)
|
||||
|
||||
val url = toolbarState.editState.editText
|
||||
if (toolbarState.editMode && url != null) {
|
||||
if (toolbarState.isEditMode() && url != null) {
|
||||
Suggestions(
|
||||
url,
|
||||
onSuggestionClicked = { suggestion ->
|
||||
|
||||
@@ -32,6 +32,7 @@ enum class ToolbarConfiguration(val label: String) {
|
||||
FENIX("Fenix"),
|
||||
FENIX_CUSTOMTAB("Fenix (Custom Tab)"),
|
||||
COMPOSE_TOOLBAR("Compose Toolbar"),
|
||||
COMPOSE_CUSTOMTAB("Compose Custom Tab"),
|
||||
}
|
||||
|
||||
class ConfigurationAdapter(
|
||||
|
||||
@@ -41,6 +41,9 @@ import mozilla.components.browser.menu2.BrowserMenuController
|
||||
import mozilla.components.browser.toolbar.BrowserToolbar
|
||||
import mozilla.components.browser.toolbar.display.DisplayToolbar
|
||||
import mozilla.components.compose.base.theme.AcornTheme
|
||||
import mozilla.components.compose.browser.toolbar.BrowserToolbarDefaults
|
||||
import mozilla.components.compose.browser.toolbar.CustomTabToolbarColors
|
||||
import mozilla.components.compose.browser.toolbar.concept.Action.ActionButton
|
||||
import mozilla.components.compose.browser.toolbar.concept.Action.CustomAction
|
||||
import mozilla.components.compose.browser.toolbar.store.BrowserEditToolbarAction
|
||||
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarAction
|
||||
@@ -48,6 +51,7 @@ import mozilla.components.compose.browser.toolbar.store.BrowserToolbarState
|
||||
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarStore
|
||||
import mozilla.components.compose.browser.toolbar.store.DisplayState
|
||||
import mozilla.components.compose.browser.toolbar.store.EditState
|
||||
import mozilla.components.compose.browser.toolbar.store.Mode
|
||||
import mozilla.components.compose.browser.toolbar.ui.SearchSelector
|
||||
import mozilla.components.concept.menu.Side
|
||||
import mozilla.components.concept.menu.candidate.DecorativeTextMenuCandidate
|
||||
@@ -97,6 +101,7 @@ class ToolbarActivity : AppCompatActivity() {
|
||||
ToolbarConfiguration.FENIX -> setupFenixToolbar()
|
||||
ToolbarConfiguration.FENIX_CUSTOMTAB -> setupFenixCustomTabToolbar()
|
||||
ToolbarConfiguration.COMPOSE_TOOLBAR -> setupComposeToolbar()
|
||||
ToolbarConfiguration.COMPOSE_CUSTOMTAB -> setupComposeCustomTabToolbar()
|
||||
}
|
||||
|
||||
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
|
||||
@@ -531,7 +536,7 @@ class ToolbarActivity : AppCompatActivity() {
|
||||
displayState = DisplayState(
|
||||
hint = "Search or enter address",
|
||||
pageActions = listOf(
|
||||
Action.ActionButton(
|
||||
ActionButton(
|
||||
icon = iconsR.drawable.mozac_ic_arrow_clockwise_24,
|
||||
contentDescription = null,
|
||||
tint = iconPrimaryTint,
|
||||
@@ -576,6 +581,58 @@ class ToolbarActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupComposeCustomTabToolbar() {
|
||||
showToolbar(isCompose = true)
|
||||
|
||||
binding.composeToolbar.setContent {
|
||||
AcornTheme {
|
||||
val iconPrimaryTint = AcornTheme.colors.iconPrimary.toArgb()
|
||||
|
||||
val store = remember {
|
||||
BrowserToolbarStore(
|
||||
initialState = BrowserToolbarState(
|
||||
mode = Mode.CUSTOM_TAB,
|
||||
displayState = DisplayState(
|
||||
navigationActions = listOf(
|
||||
ActionButton(
|
||||
icon = iconsR.drawable.mozac_ic_cross_24,
|
||||
contentDescription = null,
|
||||
tint = iconPrimaryTint,
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
browserActions = listOf(
|
||||
ActionButton(
|
||||
icon = iconsR.drawable.mozac_ic_arrow_clockwise_24,
|
||||
contentDescription = null,
|
||||
tint = iconPrimaryTint,
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
BrowserToolbar(
|
||||
store = store,
|
||||
onDisplayToolbarClick = {},
|
||||
onTextEdit = {},
|
||||
onTextCommit = {},
|
||||
colors = BrowserToolbarDefaults.colors(
|
||||
customTabToolbarColors = CustomTabToolbarColors(
|
||||
background = AcornTheme.colors.layer1,
|
||||
title = AcornTheme.colors.textPrimary,
|
||||
url = AcornTheme.colors.textSecondary,
|
||||
),
|
||||
),
|
||||
url = "https://www.mozilla.org/en-US/firefox/mobile/",
|
||||
title = "Mozilla",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showToolbar(isCompose: Boolean = false) {
|
||||
binding.toolbar.isVisible = !isCompose
|
||||
binding.composeToolbar.isVisible = isCompose
|
||||
|
||||
@@ -10,7 +10,9 @@ import mozilla.components.compose.browser.toolbar.BrowserDisplayToolbar
|
||||
import mozilla.components.compose.browser.toolbar.BrowserEditToolbar
|
||||
import mozilla.components.compose.browser.toolbar.BrowserToolbarColors
|
||||
import mozilla.components.compose.browser.toolbar.BrowserToolbarDefaults
|
||||
import mozilla.components.compose.browser.toolbar.CustomTabToolbar
|
||||
import mozilla.components.compose.browser.toolbar.store.BrowserToolbarStore
|
||||
import mozilla.components.compose.browser.toolbar.store.Mode
|
||||
import mozilla.components.lib.state.ext.observeAsState
|
||||
|
||||
/**
|
||||
@@ -35,6 +37,7 @@ fun BrowserToolbar(
|
||||
onTextCommit: (String) -> Unit,
|
||||
colors: BrowserToolbarColors = BrowserToolbarDefaults.colors(),
|
||||
url: String = "",
|
||||
title: String = "",
|
||||
) {
|
||||
val uiState by store.observeAsState(initialValue = store.state) { it }
|
||||
|
||||
@@ -43,7 +46,7 @@ fun BrowserToolbar(
|
||||
else -> editText
|
||||
}
|
||||
|
||||
if (uiState.editMode) {
|
||||
if (uiState.isEditMode()) {
|
||||
BrowserEditToolbar(
|
||||
url = input,
|
||||
colors = colors.editToolbarColors,
|
||||
@@ -64,4 +67,41 @@ fun BrowserToolbar(
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
when (uiState.mode) {
|
||||
Mode.EDIT -> {
|
||||
BrowserEditToolbar(
|
||||
url = input,
|
||||
colors = colors.editToolbarColors,
|
||||
editActionsStart = uiState.editState.editActionsStart,
|
||||
editActionsEnd = uiState.editState.editActionsEnd,
|
||||
onUrlCommitted = { text -> onTextCommit(text) },
|
||||
onUrlEdit = { text -> onTextEdit(text) },
|
||||
)
|
||||
}
|
||||
|
||||
Mode.DISPLAY -> {
|
||||
BrowserDisplayToolbar(
|
||||
url = url.takeIf { it.isNotEmpty() } ?: uiState.displayState.hint,
|
||||
colors = colors.displayToolbarColors,
|
||||
navigationActions = uiState.displayState.navigationActions,
|
||||
pageActions = uiState.displayState.pageActions,
|
||||
browserActions = uiState.displayState.browserActions,
|
||||
onUrlClicked = {
|
||||
onDisplayToolbarClick()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Mode.CUSTOM_TAB -> {
|
||||
CustomTabToolbar(
|
||||
url = url,
|
||||
title = title,
|
||||
colors = colors.customTabToolbarColor,
|
||||
navigationActions = uiState.displayState.navigationActions,
|
||||
pageActions = uiState.displayState.pageActions,
|
||||
browserActions = uiState.displayState.browserActions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user