Merge remote-tracking branch 'origin/fenix/123.0' into iceraven
commit
d2da775dfd
@ -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 org.mozilla.fenix.ui
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mozilla.fenix.customannotations.SmokeTest
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.helpers.AppAndSystemHelper.runWithCondition
|
||||
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
|
||||
import org.mozilla.fenix.helpers.TestHelper.appContext
|
||||
import org.mozilla.fenix.ui.robots.homeScreen
|
||||
import org.mozilla.fenix.ui.robots.navigationToolbar
|
||||
|
||||
/**
|
||||
* Tests for verifying the new Cookie banner blocker option and functionality.
|
||||
*/
|
||||
class CookieBannerBlockerTest {
|
||||
@get:Rule
|
||||
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
|
||||
|
||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2419260
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun verifyCookieBannerBlockerSettingsOptionTest() {
|
||||
runWithCondition(appContext.settings().shouldUseCookieBannerPrivateMode) {
|
||||
homeScreen {
|
||||
}.openThreeDotMenu {
|
||||
}.openSettings {
|
||||
verifyCookieBannerBlockerButton(enabled = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2419273
|
||||
@SmokeTest
|
||||
@Test
|
||||
fun verifyCFRAfterBlockingTheCookieBanner() {
|
||||
runWithCondition(appContext.settings().shouldUseCookieBannerPrivateMode) {
|
||||
homeScreen {
|
||||
}.togglePrivateBrowsingMode()
|
||||
|
||||
navigationToolbar {
|
||||
}.enterURLAndEnterToBrowser("voetbal24.be".toUri()) {
|
||||
waitForPageToLoad()
|
||||
verifyCookieBannerExists(exists = false)
|
||||
verifyCookieBannerBlockerCFRExists(exists = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.addons
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.support.webextensions.ExtensionsProcessDisabledPromptObserver
|
||||
import org.mozilla.fenix.components.AppStore
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* Controller for handling extensions process spawning disabled events. This is for when the app is
|
||||
* in background, the app is killed to prevent extensions from being disabled and network requests
|
||||
* continuing.
|
||||
*
|
||||
* @param browserStore The [BrowserStore] which holds the state for showing the dialog.
|
||||
* @param appStore The [AppStore] containing the application state.
|
||||
* @param onExtensionsProcessDisabled Invoked when the app is in background and extensions process
|
||||
* is disabled.
|
||||
*/
|
||||
class ExtensionsProcessDisabledBackgroundController(
|
||||
browserStore: BrowserStore,
|
||||
appStore: AppStore,
|
||||
onExtensionsProcessDisabled: () -> Unit = { killApp() },
|
||||
) : ExtensionsProcessDisabledPromptObserver(
|
||||
store = browserStore,
|
||||
shouldCancelOnStop = false,
|
||||
onShowExtensionsProcessDisabledPrompt = {
|
||||
if (!appStore.state.isForeground) {
|
||||
onExtensionsProcessDisabled()
|
||||
}
|
||||
},
|
||||
) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* When a dialog can't be shown because the app is in the background, instead the app will
|
||||
* be killed to prevent leaking network data without extensions enabled.
|
||||
*/
|
||||
private fun killApp() {
|
||||
Handler(Looper.getMainLooper()).post {
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.debugsettings.navigation
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
/**
|
||||
* A navigation destination for screens within the Debug Drawer.
|
||||
*
|
||||
* @property route The unique route used to navigate to the destination. This string can also contain
|
||||
* optional parameters for arguments or deep linking.
|
||||
* @property title The string ID of the destination's title.
|
||||
* @property onClick Invoked when the destination is clicked to be navigated to.
|
||||
* @property content The destination's [Composable].
|
||||
*/
|
||||
data class DebugDrawerDestination(
|
||||
val route: String,
|
||||
@StringRes val title: Int,
|
||||
val onClick: () -> Unit,
|
||||
val content: @Composable () -> Unit,
|
||||
)
|
@ -0,0 +1,70 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.debugsettings.navigation
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.Composable
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.debugsettings.store.DebugDrawerAction
|
||||
import org.mozilla.fenix.debugsettings.store.DebugDrawerStore
|
||||
import org.mozilla.fenix.debugsettings.tabs.TabTools as TabToolsScreen
|
||||
|
||||
/**
|
||||
* The navigation routes for screens within the Debug Drawer.
|
||||
*
|
||||
* @property route The unique route used to navigate to the destination. This string can also contain
|
||||
* optional parameters for arguments or deep linking.
|
||||
* @property title The string ID of the destination's title.
|
||||
*/
|
||||
enum class DebugDrawerRoute(val route: String, @StringRes val title: Int) {
|
||||
/**
|
||||
* The navigation route for [TabToolsScreen].
|
||||
*/
|
||||
TabTools(
|
||||
route = "tab_tools",
|
||||
title = R.string.debug_drawer_tab_tools_title,
|
||||
),
|
||||
;
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Transforms the values of [DebugDrawerRoute] into a list of [DebugDrawerDestination]s.
|
||||
*
|
||||
* @param debugDrawerStore [DebugDrawerStore] used to dispatch navigation actions.
|
||||
* @param browserStore [BrowserStore] used to add tabs in [TabToolsScreen].
|
||||
* @param inactiveTabsEnabled Whether the inactive tabs feature is enabled.
|
||||
*/
|
||||
fun generateDebugDrawerDestinations(
|
||||
debugDrawerStore: DebugDrawerStore,
|
||||
browserStore: BrowserStore,
|
||||
inactiveTabsEnabled: Boolean,
|
||||
): List<DebugDrawerDestination> =
|
||||
DebugDrawerRoute.values().map { debugDrawerRoute ->
|
||||
val onClick: () -> Unit
|
||||
val content: @Composable () -> Unit
|
||||
when (debugDrawerRoute) {
|
||||
TabTools -> {
|
||||
onClick = {
|
||||
debugDrawerStore.dispatch(DebugDrawerAction.NavigateTo.TabTools)
|
||||
}
|
||||
content = {
|
||||
TabToolsScreen(
|
||||
store = browserStore,
|
||||
inactiveTabsEnabled = inactiveTabsEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugDrawerDestination(
|
||||
route = debugDrawerRoute.route,
|
||||
title = debugDrawerRoute.title,
|
||||
onClick = onClick,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.debugsettings.store
|
||||
|
||||
import mozilla.components.lib.state.Action
|
||||
import org.mozilla.fenix.debugsettings.ui.DebugDrawerHome
|
||||
import org.mozilla.fenix.debugsettings.tabs.TabTools as TabToolsScreen
|
||||
|
||||
/**
|
||||
* [Action] implementation related to [DebugDrawerStore].
|
||||
*/
|
||||
sealed class DebugDrawerAction : Action {
|
||||
|
||||
/**
|
||||
* [DebugDrawerAction] fired when the user opens the drawer.
|
||||
*/
|
||||
object DrawerOpened : DebugDrawerAction()
|
||||
|
||||
/**
|
||||
* [DebugDrawerAction] fired when the user closes the drawer.
|
||||
*/
|
||||
object DrawerClosed : DebugDrawerAction()
|
||||
|
||||
/**
|
||||
* [DebugDrawerAction] fired when a navigation event occurs for a specific destination.
|
||||
*/
|
||||
sealed class NavigateTo : DebugDrawerAction() {
|
||||
|
||||
/**
|
||||
* [NavigateTo] action fired when the debug drawer needs to navigate to [DebugDrawerHome].
|
||||
*/
|
||||
object Home : NavigateTo()
|
||||
|
||||
/**
|
||||
* [NavigateTo] action fired when the debug drawer needs to navigate to [TabToolsScreen].
|
||||
*/
|
||||
object TabTools : NavigateTo()
|
||||
}
|
||||
|
||||
/**
|
||||
* [DebugDrawerAction] fired when a back navigation event occurs.
|
||||
*/
|
||||
object OnBackPressed : DebugDrawerAction()
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.debugsettings.store
|
||||
|
||||
import androidx.navigation.NavHostController
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.MiddlewareContext
|
||||
import org.mozilla.fenix.debugsettings.navigation.DebugDrawerRoute
|
||||
import org.mozilla.fenix.debugsettings.ui.DEBUG_DRAWER_HOME_ROUTE
|
||||
|
||||
/**
|
||||
* Middleware that handles navigation events for the Debug Drawer feature.
|
||||
*
|
||||
* @param navController [NavHostController] used to execute any navigation actions on the UI.
|
||||
* @param scope [CoroutineScope] used to make calls to the main thread.
|
||||
*/
|
||||
class DebugDrawerNavigationMiddleware(
|
||||
private val navController: NavHostController,
|
||||
private val scope: CoroutineScope,
|
||||
) : Middleware<DebugDrawerState, DebugDrawerAction> {
|
||||
|
||||
override fun invoke(
|
||||
context: MiddlewareContext<DebugDrawerState, DebugDrawerAction>,
|
||||
next: (DebugDrawerAction) -> Unit,
|
||||
action: DebugDrawerAction,
|
||||
) {
|
||||
next(action)
|
||||
scope.launch {
|
||||
when (action) {
|
||||
is DebugDrawerAction.NavigateTo.Home -> navController.popBackStack(
|
||||
route = DEBUG_DRAWER_HOME_ROUTE,
|
||||
inclusive = false,
|
||||
)
|
||||
is DebugDrawerAction.NavigateTo.TabTools ->
|
||||
navController.navigate(route = DebugDrawerRoute.TabTools.route)
|
||||
is DebugDrawerAction.OnBackPressed -> navController.popBackStack()
|
||||
is DebugDrawerAction.DrawerOpened, DebugDrawerAction.DrawerClosed -> Unit // no-op
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.debugsettings.store
|
||||
|
||||
import mozilla.components.lib.state.State
|
||||
|
||||
/**
|
||||
* UI state of the debug drawer feature.
|
||||
*
|
||||
* @property drawerStatus The [DrawerStatus] indicating the physical state of the drawer.
|
||||
*/
|
||||
data class DebugDrawerState(
|
||||
val drawerStatus: DrawerStatus = DrawerStatus.Closed,
|
||||
) : State
|
@ -0,0 +1,27 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.debugsettings.store
|
||||
|
||||
import mozilla.components.lib.state.Middleware
|
||||
import mozilla.components.lib.state.Store
|
||||
|
||||
/**
|
||||
* A [Store] that holds the [DebugDrawerState] for the Debug Drawer and reduces [DebugDrawerAction]s
|
||||
* dispatched to the store.
|
||||
*/
|
||||
class DebugDrawerStore(
|
||||
initialState: DebugDrawerState = DebugDrawerState(),
|
||||
middlewares: List<Middleware<DebugDrawerState, DebugDrawerAction>> = emptyList(),
|
||||
) : Store<DebugDrawerState, DebugDrawerAction>(
|
||||
initialState,
|
||||
::reduce,
|
||||
middlewares,
|
||||
)
|
||||
|
||||
private fun reduce(state: DebugDrawerState, action: DebugDrawerAction): DebugDrawerState = when (action) {
|
||||
is DebugDrawerAction.DrawerOpened -> state.copy(drawerStatus = DrawerStatus.Open)
|
||||
is DebugDrawerAction.DrawerClosed -> state.copy(drawerStatus = DrawerStatus.Closed)
|
||||
is DebugDrawerAction.NavigateTo, DebugDrawerAction.OnBackPressed -> state // handled by [DebugDrawerNavigationMiddleware]
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.debugsettings.store
|
||||
|
||||
/**
|
||||
* Possible values of the debug drawer's physical state.
|
||||
*/
|
||||
enum class DrawerStatus {
|
||||
/**
|
||||
* The state of the drawer when it is closed.
|
||||
*/
|
||||
Closed,
|
||||
|
||||
/**
|
||||
* The state of the drawer when it is open.
|
||||
*/
|
||||
Open,
|
||||
}
|
@ -0,0 +1,328 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.debugsettings.tabs
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.material.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.text.isDigitsOnly
|
||||
import mozilla.components.browser.state.action.TabListAction
|
||||
import mozilla.components.browser.state.state.createTab
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.lib.state.ext.observeAsState
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.compose.Divider
|
||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||
import org.mozilla.fenix.compose.button.PrimaryButton
|
||||
import org.mozilla.fenix.debugsettings.ui.DebugDrawer
|
||||
import org.mozilla.fenix.ext.maxActiveTime
|
||||
import org.mozilla.fenix.tabstray.ext.isNormalTabInactive
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
|
||||
/**
|
||||
* Tab Tools UI for [DebugDrawer] that displays the tab counts and allows easy bulk-opening of tabs.
|
||||
*
|
||||
* @param store [BrowserStore] used to obtain the tab counts and fire any tab creation actions.
|
||||
* @param inactiveTabsEnabled Whether the inactive tabs feature is enabled.
|
||||
*/
|
||||
@Composable
|
||||
fun TabTools(
|
||||
store: BrowserStore,
|
||||
inactiveTabsEnabled: Boolean,
|
||||
) {
|
||||
val tabs by store.observeAsState(initialValue = emptyList()) { state -> state.tabs }
|
||||
val totalTabCount = remember(tabs) { tabs.size }
|
||||
val privateTabCount = remember(tabs) { tabs.filter { it.content.private }.size }
|
||||
val inactiveTabCount = remember(tabs) {
|
||||
if (inactiveTabsEnabled) {
|
||||
tabs.filter { it.isNormalTabInactive(maxActiveTime) }.size
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
val activeTabCount = remember(tabs) { totalTabCount - privateTabCount - inactiveTabCount }
|
||||
|
||||
TabToolsContent(
|
||||
activeTabCount = activeTabCount,
|
||||
inactiveTabCount = inactiveTabCount,
|
||||
privateTabCount = privateTabCount,
|
||||
totalTabCount = totalTabCount,
|
||||
inactiveTabsEnabled = inactiveTabsEnabled,
|
||||
onCreateTabsClick = { quantity, isInactive, isPrivate ->
|
||||
store.dispatch(
|
||||
TabListAction.AddMultipleTabsAction(
|
||||
tabs = generateTabList(
|
||||
quantity = quantity,
|
||||
isInactive = isInactive,
|
||||
isPrivate = isPrivate,
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun generateTabList(
|
||||
quantity: Int,
|
||||
isInactive: Boolean = false,
|
||||
isPrivate: Boolean = false,
|
||||
) = List(quantity) {
|
||||
createTab(
|
||||
url = "www.example.com",
|
||||
private = isPrivate,
|
||||
createdAt = if (isInactive) 0L else System.currentTimeMillis(),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TabToolsContent(
|
||||
activeTabCount: Int,
|
||||
inactiveTabCount: Int,
|
||||
privateTabCount: Int,
|
||||
totalTabCount: Int,
|
||||
inactiveTabsEnabled: Boolean,
|
||||
onCreateTabsClick: ((quantity: Int, isInactive: Boolean, isPrivate: Boolean) -> Unit),
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(all = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
TabCounter(
|
||||
activeTabCount = activeTabCount,
|
||||
inactiveTabCount = inactiveTabCount,
|
||||
privateTabCount = privateTabCount,
|
||||
totalTabCount = totalTabCount,
|
||||
inactiveTabsEnabled = inactiveTabsEnabled,
|
||||
)
|
||||
|
||||
TabCreationTool(
|
||||
inactiveTabsEnabled = inactiveTabsEnabled,
|
||||
onCreateTabsClick = onCreateTabsClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TabCounter(
|
||||
activeTabCount: Int,
|
||||
inactiveTabCount: Int,
|
||||
privateTabCount: Int,
|
||||
totalTabCount: Int,
|
||||
inactiveTabsEnabled: Boolean,
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.debug_drawer_tab_tools_tab_count_title),
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
style = FirefoxTheme.typography.headline5,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
TabCountRow(
|
||||
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_normal),
|
||||
count = activeTabCount.toString(),
|
||||
)
|
||||
|
||||
if (inactiveTabsEnabled) {
|
||||
TabCountRow(
|
||||
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_inactive),
|
||||
count = inactiveTabCount.toString(),
|
||||
)
|
||||
}
|
||||
|
||||
TabCountRow(
|
||||
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_private),
|
||||
count = privateTabCount.toString(),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Divider()
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
TabCountRow(
|
||||
tabType = stringResource(R.string.debug_drawer_tab_tools_tab_count_total),
|
||||
count = totalTabCount.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TabCountRow(
|
||||
tabType: String,
|
||||
count: String,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = tabType,
|
||||
color = FirefoxTheme.colors.textSecondary,
|
||||
style = FirefoxTheme.typography.headline6,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = count,
|
||||
color = FirefoxTheme.colors.textSecondary,
|
||||
style = FirefoxTheme.typography.headline6,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val DEFAULT_TABS_TO_ADD = "1"
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
@Composable
|
||||
private fun TabCreationTool(
|
||||
inactiveTabsEnabled: Boolean,
|
||||
onCreateTabsClick: ((quantity: Int, isInactive: Boolean, isPrivate: Boolean) -> Unit),
|
||||
) {
|
||||
var tabQuantityToCreate by rememberSaveable { mutableStateOf(DEFAULT_TABS_TO_ADD) }
|
||||
var hasError by rememberSaveable { mutableStateOf(false) }
|
||||
val keyboardController = LocalSoftwareKeyboardController.current
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = stringResource(R.string.debug_drawer_tab_tools_tab_creation_tool_title),
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
style = FirefoxTheme.typography.headline5,
|
||||
)
|
||||
|
||||
TextField(
|
||||
value = tabQuantityToCreate,
|
||||
onValueChange = {
|
||||
tabQuantityToCreate = it
|
||||
hasError = it.isEmpty() || !it.isDigitsOnly() || it.toInt() == 0
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textStyle = FirefoxTheme.typography.subtitle1,
|
||||
label = {
|
||||
Text(
|
||||
text = stringResource(R.string.debug_drawer_tab_tools_tab_creation_tool_text_field_label),
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
style = FirefoxTheme.typography.caption,
|
||||
)
|
||||
},
|
||||
isError = hasError,
|
||||
keyboardOptions = KeyboardOptions(
|
||||
keyboardType = KeyboardType.Number,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onDone = {
|
||||
keyboardController?.hide()
|
||||
},
|
||||
),
|
||||
singleLine = true,
|
||||
colors = TextFieldDefaults.textFieldColors(
|
||||
textColor = FirefoxTheme.colors.textPrimary,
|
||||
backgroundColor = Color.Transparent,
|
||||
cursorColor = FirefoxTheme.colors.borderFormDefault,
|
||||
errorCursorColor = FirefoxTheme.colors.borderWarning,
|
||||
focusedIndicatorColor = FirefoxTheme.colors.borderPrimary,
|
||||
unfocusedIndicatorColor = FirefoxTheme.colors.borderPrimary,
|
||||
errorIndicatorColor = FirefoxTheme.colors.borderWarning,
|
||||
),
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
PrimaryButton(
|
||||
text = stringResource(id = R.string.debug_drawer_tab_tools_tab_creation_tool_button_text_active),
|
||||
enabled = !hasError,
|
||||
onClick = {
|
||||
onCreateTabsClick(tabQuantityToCreate.toInt(), false, false)
|
||||
},
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
if (inactiveTabsEnabled) {
|
||||
PrimaryButton(
|
||||
text = stringResource(id = R.string.debug_drawer_tab_tools_tab_creation_tool_button_text_inactive),
|
||||
enabled = !hasError,
|
||||
onClick = {
|
||||
onCreateTabsClick(tabQuantityToCreate.toInt(), true, false)
|
||||
},
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
||||
PrimaryButton(
|
||||
text = stringResource(id = R.string.debug_drawer_tab_tools_tab_creation_tool_button_text_private),
|
||||
enabled = !hasError,
|
||||
onClick = {
|
||||
onCreateTabsClick(tabQuantityToCreate.toInt(), false, true)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private data class TabToolsPreviewModel(
|
||||
val inactiveTabsEnabled: Boolean = true,
|
||||
)
|
||||
|
||||
private class TabToolsPreviewParameterProvider : PreviewParameterProvider<TabToolsPreviewModel> {
|
||||
override val values: Sequence<TabToolsPreviewModel>
|
||||
get() = sequenceOf(
|
||||
TabToolsPreviewModel(
|
||||
inactiveTabsEnabled = true,
|
||||
),
|
||||
TabToolsPreviewModel(
|
||||
inactiveTabsEnabled = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@LightDarkPreview
|
||||
private fun TabToolsPreview(
|
||||
@PreviewParameter(TabToolsPreviewParameterProvider::class) model: TabToolsPreviewModel,
|
||||
) {
|
||||
FirefoxTheme {
|
||||
Box(
|
||||
modifier = Modifier.background(color = FirefoxTheme.colors.layer1),
|
||||
) {
|
||||
TabTools(
|
||||
store = BrowserStore(),
|
||||
inactiveTabsEnabled = model.inactiveTabsEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,138 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.debugsettings.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||
import org.mozilla.fenix.debugsettings.navigation.DebugDrawerDestination
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
|
||||
/**
|
||||
* The debug drawer UI.
|
||||
*
|
||||
* @param navController [NavHostController] used to perform navigation actions on the [NavHost].
|
||||
* @param destinations The list of [DebugDrawerDestination]s (excluding home) used to populate
|
||||
* the [NavHost] with screens.
|
||||
* @param onBackButtonClick Invoked when the user taps on the back button in the app bar.
|
||||
*/
|
||||
@Composable
|
||||
fun DebugDrawer(
|
||||
navController: NavHostController,
|
||||
destinations: List<DebugDrawerDestination>,
|
||||
onBackButtonClick: () -> Unit,
|
||||
) {
|
||||
var backButtonVisible by remember { mutableStateOf(false) }
|
||||
var toolbarTitle by remember { mutableStateOf("") }
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
text = toolbarTitle,
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
style = FirefoxTheme.typography.headline6,
|
||||
)
|
||||
},
|
||||
navigationIcon = if (backButtonVisible) {
|
||||
topBarBackButton(onClick = onBackButtonClick)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
backgroundColor = FirefoxTheme.colors.layer1,
|
||||
elevation = 5.dp,
|
||||
)
|
||||
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = DEBUG_DRAWER_HOME_ROUTE,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
composable(route = DEBUG_DRAWER_HOME_ROUTE) {
|
||||
toolbarTitle = stringResource(id = R.string.debug_drawer_title)
|
||||
backButtonVisible = false
|
||||
DebugDrawerHome(destinations = destinations)
|
||||
}
|
||||
|
||||
destinations.forEach { destination ->
|
||||
composable(route = destination.route) {
|
||||
toolbarTitle = stringResource(id = destination.title)
|
||||
backButtonVisible = true
|
||||
|
||||
destination.content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun topBarBackButton(onClick: () -> Unit): @Composable () -> Unit = {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.drawable.mozac_ic_back_24),
|
||||
contentDescription = stringResource(R.string.debug_drawer_back_button_content_description),
|
||||
tint = FirefoxTheme.colors.iconPrimary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@LightDarkPreview
|
||||
private fun DebugDrawerPreview() {
|
||||
val navController = rememberNavController()
|
||||
val destinations = remember {
|
||||
List(size = 15) { index ->
|
||||
DebugDrawerDestination(
|
||||
route = "screen_$index",
|
||||
title = R.string.debug_drawer_title,
|
||||
onClick = {
|
||||
navController.navigate(route = "screen_$index")
|
||||
},
|
||||
content = {
|
||||
Text(
|
||||
text = "Tool $index",
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
style = FirefoxTheme.typography.headline6,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
FirefoxTheme {
|
||||
Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) {
|
||||
DebugDrawer(
|
||||
navController = navController,
|
||||
destinations = destinations,
|
||||
onBackButtonClick = {
|
||||
navController.popBackStack()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.debugsettings.ui
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.Snackbar
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.support.ktx.android.content.appName
|
||||
import mozilla.components.support.ktx.android.content.appVersionName
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.compose.Divider
|
||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||
import org.mozilla.fenix.compose.inComposePreview
|
||||
import org.mozilla.fenix.compose.list.TextListItem
|
||||
import org.mozilla.fenix.debugsettings.navigation.DebugDrawerDestination
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
|
||||
/**
|
||||
* The navigation route for [DebugDrawerHome].
|
||||
*/
|
||||
const val DEBUG_DRAWER_HOME_ROUTE = "debug_drawer_home"
|
||||
|
||||
/**
|
||||
* The home screen of the [DebugDrawer].
|
||||
*
|
||||
* @param destinations The list of [DebugDrawerDestination]s to display.
|
||||
*/
|
||||
@Composable
|
||||
fun DebugDrawerHome(
|
||||
destinations: List<DebugDrawerDestination>,
|
||||
) {
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
||||
val appName: String
|
||||
val appVersion: String
|
||||
if (inComposePreview) {
|
||||
appName = "App Name Preview"
|
||||
appVersion = "100.00.000"
|
||||
} else {
|
||||
appName = LocalContext.current.appName
|
||||
appVersion = LocalContext.current.appVersionName
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = FirefoxTheme.colors.layer1),
|
||||
state = lazyListState,
|
||||
) {
|
||||
item(key = "home_header") {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(all = 16.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = appName,
|
||||
color = FirefoxTheme.colors.textPrimary,
|
||||
style = FirefoxTheme.typography.headline5,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = appVersion,
|
||||
color = FirefoxTheme.colors.textSecondary,
|
||||
style = FirefoxTheme.typography.headline5,
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
}
|
||||
|
||||
items(
|
||||
items = destinations,
|
||||
key = { destination ->
|
||||
destination.route
|
||||
},
|
||||
) { destination ->
|
||||
TextListItem(
|
||||
label = stringResource(id = destination.title),
|
||||
onClick = destination.onClick,
|
||||
)
|
||||
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@LightDarkPreview
|
||||
private fun DebugDrawerHomePreview() {
|
||||
val scope = rememberCoroutineScope()
|
||||
val snackbarState = remember { SnackbarHostState() }
|
||||
|
||||
FirefoxTheme {
|
||||
Box {
|
||||
DebugDrawerHome(
|
||||
destinations = List(size = 30) {
|
||||
DebugDrawerDestination(
|
||||
route = "screen_$it",
|
||||
title = R.string.debug_drawer_title,
|
||||
onClick = {
|
||||
scope.launch {
|
||||
snackbarState.showSnackbar("item $it clicked")
|
||||
}
|
||||
},
|
||||
content = {},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
SnackbarHost(
|
||||
hostState = snackbarState,
|
||||
modifier = Modifier.align(Alignment.BottomCenter),
|
||||
) { snackbarData ->
|
||||
Snackbar(
|
||||
snackbarData = snackbarData,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.fenix.debugsettings.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import mozilla.components.browser.state.store.BrowserStore
|
||||
import mozilla.components.lib.state.ext.observeAsState
|
||||
import org.mozilla.fenix.compose.annotation.LightDarkPreview
|
||||
import org.mozilla.fenix.debugsettings.navigation.DebugDrawerRoute
|
||||
import org.mozilla.fenix.debugsettings.store.DebugDrawerAction
|
||||
import org.mozilla.fenix.debugsettings.store.DebugDrawerNavigationMiddleware
|
||||
import org.mozilla.fenix.debugsettings.store.DebugDrawerStore
|
||||
import org.mozilla.fenix.debugsettings.store.DrawerStatus
|
||||
import org.mozilla.fenix.debugsettings.tabs.TabTools
|
||||
import org.mozilla.fenix.theme.FirefoxTheme
|
||||
import org.mozilla.fenix.theme.Theme
|
||||
|
||||
/**
|
||||
* Overlay for presenting Fenix-wide debugging content.
|
||||
*
|
||||
* @param browserStore [BrowserStore] used to access tab data for [TabTools].
|
||||
* @param inactiveTabsEnabled Whether the inactive tabs feature is enabled.
|
||||
*/
|
||||
@Composable
|
||||
fun FenixOverlay(
|
||||
browserStore: BrowserStore,
|
||||
inactiveTabsEnabled: Boolean,
|
||||
) {
|
||||
val navController = rememberNavController()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val debugDrawerStore = remember {
|
||||
DebugDrawerStore(
|
||||
middlewares = listOf(
|
||||
DebugDrawerNavigationMiddleware(
|
||||
navController = navController,
|
||||
scope = coroutineScope,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
val debugDrawerDestinations = remember {
|
||||
DebugDrawerRoute.generateDebugDrawerDestinations(
|
||||
debugDrawerStore = debugDrawerStore,
|
||||
browserStore = browserStore,
|
||||
inactiveTabsEnabled = inactiveTabsEnabled,
|
||||
)
|
||||
}
|
||||
val drawerStatus by debugDrawerStore.observeAsState(initialValue = DrawerStatus.Closed) { state ->
|
||||
state.drawerStatus
|
||||
}
|
||||
|
||||
FirefoxTheme(theme = Theme.getTheme(allowPrivateTheme = false)) {
|
||||
DebugOverlay(
|
||||
navController = navController,
|
||||
drawerStatus = drawerStatus,
|
||||
debugDrawerDestinations = debugDrawerDestinations,
|
||||
onDrawerOpen = {
|
||||
debugDrawerStore.dispatch(DebugDrawerAction.DrawerOpened)
|
||||
},
|
||||
onDrawerClose = {
|
||||
debugDrawerStore.dispatch(DebugDrawerAction.DrawerClosed)
|
||||
},
|
||||
onDrawerBackButtonClick = {
|
||||
debugDrawerStore.dispatch(DebugDrawerAction.OnBackPressed)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@LightDarkPreview
|
||||
@Composable
|
||||
private fun FenixOverlayPreview() {
|
||||
FenixOverlay(
|
||||
browserStore = BrowserStore(),
|
||||
inactiveTabsEnabled = true,
|
||||
)
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue