Merge remote-tracking branch 'upstream/master' into fork

pull/35/head
Adam Novak 4 years ago
commit 94ce9bb335

@ -480,7 +480,6 @@ dependencies {
implementation Deps.google_ads_id // Required for the Google Advertising ID
implementation Deps.google_play_store // Required for in-app reviews
implementation Deps.google_play_core_ktx // Required for in-app reviews
androidTestImplementation Deps.uiautomator
// Removed pending AndroidX fixes

@ -135,6 +135,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
}
initializeWebExtensionSupport()
restoreDownloads()
// Just to make sure it is impossible for any application-services pieces
// to invoke parts of itself that require complete megazord initialization
// before that process completes, we wait here, if necessary.
@ -161,6 +163,12 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
components.appStartupTelemetry.onFenixApplicationOnCreate()
}
private fun restoreDownloads() {
if (FeatureFlags.viewDownloads) {
components.useCases.downloadUseCases.restoreDownloads()
}
}
private fun initVisualCompletenessQueueAndQueueTasks() {
val queue = components.performance.visualCompletenessQueue.queue

@ -65,7 +65,6 @@ import mozilla.components.feature.session.SwipeRefreshFeature
import mozilla.components.feature.session.behavior.EngineViewBottomBehavior
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissionsFeature
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.service.sync.logins.DefaultLoginValidationDelegate
import mozilla.components.support.base.feature.PermissionsFeature
@ -82,6 +81,7 @@ import org.mozilla.fenix.OnBackLongPressedListener
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.readermode.DefaultReaderModeController
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.FindInPageIntegration
import org.mozilla.fenix.components.StoreProvider
@ -92,6 +92,7 @@ import org.mozilla.fenix.components.toolbar.BrowserInteractor
import org.mozilla.fenix.components.toolbar.BrowserToolbarView
import org.mozilla.fenix.components.toolbar.BrowserToolbarViewInteractor
import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarController
import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarMenuController
import org.mozilla.fenix.components.toolbar.SwipeRefreshScrollingViewBehavior
import org.mozilla.fenix.components.toolbar.ToolbarIntegration
import org.mozilla.fenix.components.toolbar.ToolbarPosition
@ -123,8 +124,10 @@ import java.lang.ref.WeakReference
@Suppress("TooManyFunctions", "LargeClass")
abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, SessionManager.Observer,
OnBackLongPressedListener, AccessibilityManager.AccessibilityStateChangeListener {
private lateinit var browserFragmentStore: BrowserFragmentStore
private lateinit var browserAnimator: BrowserAnimator
private lateinit var components: Components
private var _browserInteractor: BrowserToolbarViewInteractor? = null
protected val browserInteractor: BrowserToolbarViewInteractor
@ -170,8 +173,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
require(arguments != null)
customTabSessionId = arguments?.getString(EXTRA_SESSION_ID)
customTabSessionId = requireArguments().getString(EXTRA_SESSION_ID)
// Diagnostic breadcrumb for "Display already aquired" crash:
// https://github.com/mozilla-mobile/android-components/issues/7960
@ -193,6 +195,8 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
)
}
components = requireComponents
return view
}
@ -207,6 +211,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
val context = requireContext()
val sessionManager = context.components.core.sessionManager
val store = context.components.core.store
val activity = requireActivity() as HomeActivity
val toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height)
@ -215,6 +220,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
engineView = WeakReference(engineView),
swipeRefresh = WeakReference(swipeRefresh),
viewLifecycleScope = WeakReference(viewLifecycleOwner.lifecycleScope),
settings = context.components.settings,
firstContentfulHappened = ::didFirstContentfulHappen
).apply {
beginAnimateInIfNecessary()
@ -226,25 +232,20 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
putExtra(HomeActivity.OPEN_TO_BROWSER, true)
}
val readerMenuController = DefaultReaderModeController(
readerViewFeature,
view.readerViewControlsBar,
isPrivate = activity.browsingModeManager.mode.isPrivate
)
val browserToolbarController = DefaultBrowserToolbarController(
activity = requireActivity() as HomeActivity,
activity = activity,
navController = findNavController(),
readerModeController = DefaultReaderModeController(
readerViewFeature,
view.readerViewControlsBar,
isPrivate = (activity as HomeActivity).browsingModeManager.mode.isPrivate
),
metrics = requireComponents.analytics.metrics,
readerModeController = readerMenuController,
sessionManager = requireComponents.core.sessionManager,
sessionFeature = sessionFeature,
findInPageLauncher = { findInPageIntegration.withFeature { it.launch() } },
engineView = engineView,
swipeRefresh = swipeRefresh,
browserAnimator = browserAnimator,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
openInFenixIntent = openInFenixIntent,
bookmarkTapped = { viewLifecycleOwner.lifecycleScope.launch { bookmarkTapped(it) } },
scope = viewLifecycleOwner.lifecycleScope,
tabCollectionStorage = requireComponents.core.tabCollectionStorage,
onTabCounterClicked = {
thumbnailsFeature.get()?.requestScreenshot()
findNavController().nav(
@ -279,9 +280,27 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
)
}
)
val browserToolbarMenuController = DefaultBrowserToolbarMenuController(
activity = activity,
navController = findNavController(),
metrics = requireComponents.analytics.metrics,
settings = context.settings(),
readerModeController = readerMenuController,
sessionManager = requireComponents.core.sessionManager,
sessionFeature = sessionFeature,
findInPageLauncher = { findInPageIntegration.withFeature { it.launch() } },
swipeRefresh = swipeRefresh,
browserAnimator = browserAnimator,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
openInFenixIntent = openInFenixIntent,
bookmarkTapped = { viewLifecycleOwner.lifecycleScope.launch { bookmarkTapped(it) } },
scope = viewLifecycleOwner.lifecycleScope,
tabCollectionStorage = requireComponents.core.tabCollectionStorage
)
_browserInteractor = BrowserInteractor(
browserToolbarController = browserToolbarController
browserToolbarController,
browserToolbarMenuController
)
_browserToolbarView = BrowserToolbarView(
@ -573,9 +592,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
context.settings().setSitePermissionSettingListener(viewLifecycleOwner) {
// If the user connects to WIFI while on the BrowserFragment, this will update the
// SitePermissionsRules (specifically autoplay) accordingly
assignSitePermissionsRules(context)
assignSitePermissionsRules()
}
assignSitePermissionsRules(context)
assignSitePermissionsRules()
fullScreenFeature.set(
feature = FullScreenFeature(
@ -716,7 +735,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
return
}
val onTryAgain: (Long) -> Unit = {
val onTryAgain: (String) -> Unit = {
savedDownloadState.first?.let { dlState ->
store.dispatch(
ContentAction.UpdateDownloadAction(
@ -927,15 +946,13 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
* Returns the layout [android.view.Gravity] for the quick settings and ETP dialog.
*/
protected fun getAppropriateLayoutGravity(): Int =
context?.settings()?.toolbarPosition?.androidGravity ?: Gravity.BOTTOM
components.settings.toolbarPosition.androidGravity
/**
* Updates the site permissions rules based on user settings.
*/
private fun assignSitePermissionsRules(context: Context) {
val settings = context.settings()
val rules: SitePermissionsRules = settings.getSitePermissionsCustomSettingsRules()
private fun assignSitePermissionsRules() {
val rules = components.settings.getSitePermissionsCustomSettingsRules()
sitePermissionsFeature.withFeature {
it.sitePermissionsRules = rules
@ -981,7 +998,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
* Returns the current session.
*/
protected fun getSessionById(): Session? {
val sessionManager = context?.components?.core?.sessionManager ?: return null
val sessionManager = components.core.sessionManager
val localCustomTabId = customTabSessionId
return if (localCustomTabId != null) {
sessionManager.findSessionById(localCustomTabId)
@ -1092,10 +1109,12 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
}
private fun didFirstContentfulHappen() =
if (!requireContext().settings().waitToShowPageUntilFirstPaint) true else
context?.components?.core?.store?.state?.findTabOrCustomTabOrSelectedTab(
customTabSessionId
)?.content?.firstContentfulPaint ?: false
if (components.settings.waitToShowPageUntilFirstPaint) {
val tab = components.core.store.state.findTabOrCustomTabOrSelectedTab(customTabSessionId)
tab?.content?.firstContentfulPaint ?: false
} else {
true
}
/*
* Dereference these views when the fragment view is destroyed to prevent memory leaks

@ -18,6 +18,7 @@ import mozilla.components.concept.engine.EngineView
import org.mozilla.fenix.R
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings
import java.lang.ref.WeakReference
/**
@ -29,6 +30,7 @@ class BrowserAnimator(
private val engineView: WeakReference<EngineView>,
private val swipeRefresh: WeakReference<View>,
private val viewLifecycleScope: WeakReference<LifecycleCoroutineScope>,
private val settings: Settings,
private val firstContentfulHappened: () -> Boolean
) {
@ -39,7 +41,7 @@ class BrowserAnimator(
get() = swipeRefresh.get()
fun beginAnimateInIfNecessary() {
if (unwrappedSwipeRefresh?.context?.settings()?.waitToShowPageUntilFirstPaint == true) {
if (settings.waitToShowPageUntilFirstPaint) {
if (firstContentfulHappened()) {
viewLifecycleScope.get()?.launch {
delay(ANIMATION_DELAY)

@ -7,8 +7,6 @@ package org.mozilla.fenix.components
import android.app.Activity
import android.content.Context
import androidx.annotation.VisibleForTesting
import com.google.android.play.core.ktx.launchReview
import com.google.android.play.core.ktx.requestReview
import com.google.android.play.core.review.ReviewManagerFactory
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext
@ -46,12 +44,16 @@ class ReviewPromptController(
private val context: Context,
private val reviewSettings: ReviewSettings,
private val timeNowInMillis: () -> Long = { System.currentTimeMillis() },
private val tryPromptReview: suspend (Activity) -> Unit = {
private val tryPromptReview: suspend (Activity) -> Unit = { activity ->
val manager = ReviewManagerFactory.create(context)
val reviewInfo = manager.requestReview()
val flow = manager.requestReviewFlow()
withContext(Main) {
manager.launchReview(it, reviewInfo)
flow.addOnCompleteListener {
if (it.isSuccessful) {
manager.launchReviewFlow(activity, it.result)
}
}
}
}
) {

@ -0,0 +1,31 @@
/* 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.components.settings
import androidx.core.content.edit
import mozilla.components.support.ktx.android.content.PreferencesHolder
class CounterPreference(
private val holder: PreferencesHolder,
private val key: String,
private val maxCount: Int
) {
val value get() = holder.preferences.getInt(key, 0)
fun underMaxCount() = value < maxCount
fun increment() {
holder.preferences.edit {
putInt(key, value + 1)
}
}
}
/**
* Property delegate for getting and an int shared preference and incrementing it.
*/
fun PreferencesHolder.counterPreference(key: String, maxCount: Int = -1) =
CounterPreference(this, key, maxCount)

@ -0,0 +1,25 @@
/* 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.components.settings
import mozilla.components.support.ktx.android.content.PreferencesHolder
import mozilla.components.support.ktx.android.content.booleanPreference
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
private class DummyProperty : ReadWriteProperty<PreferencesHolder, Boolean> {
override fun getValue(thisRef: PreferencesHolder, property: KProperty<*>) = false
override fun setValue(thisRef: PreferencesHolder, property: KProperty<*>, value: Boolean) = Unit
}
/**
* Property delegate for getting and setting a boolean shared preference gated by a feature flag.
*/
fun featureFlagPreference(key: String, default: Boolean, featureFlag: Boolean) =
if (featureFlag) {
booleanPreference(key, default)
} else {
DummyProperty()
}

@ -5,14 +5,15 @@
package org.mozilla.fenix.components.toolbar
open class BrowserInteractor(
private val browserToolbarController: BrowserToolbarController
private val browserToolbarController: BrowserToolbarController,
private val menuController: BrowserToolbarMenuController
) : BrowserToolbarViewInteractor {
override fun onTabCounterClicked() {
browserToolbarController.handleTabCounterClick()
}
override fun onTabCounterMenuItemTapped(item: TabCounterMenuItem) {
override fun onTabCounterMenuItemTapped(item: TabCounterMenu.Item) {
browserToolbarController.handleTabCounterItemInteraction(item)
}
@ -29,7 +30,7 @@ open class BrowserInteractor(
}
override fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item) {
browserToolbarController.handleToolbarItemInteraction(item)
menuController.handleToolbarItemInteraction(item)
}
override fun onScrolled(offset: Int) {

@ -4,46 +4,24 @@
package org.mozilla.fenix.components.toolbar
import android.content.Intent
import androidx.annotation.VisibleForTesting
import androidx.navigation.NavController
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.EngineSession.LoadUrlFlags
import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.session.SessionFeature
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.kotlin.isUrl
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator
import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions
import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.readermode.ReaderModeController
import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.navigateSafe
import org.mozilla.fenix.ext.sessionsOfType
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
import org.mozilla.fenix.utils.Do
/**
* An interface that handles the view manipulation of the BrowserToolbar, triggered by the Interactor
@ -52,52 +30,38 @@ interface BrowserToolbarController {
fun handleScroll(offset: Int)
fun handleToolbarPaste(text: String)
fun handleToolbarPasteAndGo(text: String)
fun handleToolbarItemInteraction(item: ToolbarMenu.Item)
fun handleToolbarClick()
fun handleTabCounterClick()
fun handleTabCounterItemInteraction(item: TabCounterMenuItem)
fun handleTabCounterItemInteraction(item: TabCounterMenu.Item)
fun handleReaderModePressed(enabled: Boolean)
}
@Suppress("LargeClass", "TooManyFunctions")
class DefaultBrowserToolbarController(
private val activity: HomeActivity,
private val navController: NavController,
private val metrics: MetricController,
private val readerModeController: ReaderModeController,
private val sessionFeature: ViewBoundFeatureWrapper<SessionFeature>,
private val sessionManager: SessionManager,
private val findInPageLauncher: () -> Unit,
private val engineView: EngineView,
private val browserAnimator: BrowserAnimator,
private val swipeRefresh: SwipeRefreshLayout,
private val customTabSession: Session?,
private val openInFenixIntent: Intent,
private val bookmarkTapped: (Session) -> Unit,
private val scope: CoroutineScope,
private val tabCollectionStorage: TabCollectionStorage,
private val useNewSearchExperience: Boolean = FeatureFlags.newSearchExperience,
private val onTabCounterClicked: () -> Unit,
private val onCloseTab: (Session) -> Unit
) : BrowserToolbarController {
private val useNewSearchExperience
get() = FeatureFlags.newSearchExperience
private val currentSession
get() = customTabSession ?: activity.components.core.sessionManager.selectedSession
// We hold onto a reference of the inner scope so that we can override this with the
// TestCoroutineScope to ensure sequential execution. If we didn't have this, our tests
// would fail intermittently due to the async nature of coroutine scheduling.
@VisibleForTesting
internal var ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
get() = customTabSession ?: sessionManager.selectedSession
override fun handleToolbarPaste(text: String) {
if (useNewSearchExperience) {
navController.nav(
R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = currentSession?.id,
pastedText = text
), getToolbarNavOptions(activity)
),
getToolbarNavOptions(activity)
)
} else {
browserAnimator.captureEngineViewAndDrawStatically {
@ -128,15 +92,15 @@ class DefaultBrowserToolbarController(
}
override fun handleToolbarClick() {
activity.components.analytics.metrics.track(
Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)
)
metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER))
if (useNewSearchExperience) {
navController.nav(
R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalSearchDialog(
currentSession?.id
), getToolbarNavOptions(activity)
),
getToolbarNavOptions(activity)
)
} else {
browserAnimator.captureEngineViewAndDrawStatically {
@ -158,16 +122,16 @@ class DefaultBrowserToolbarController(
override fun handleReaderModePressed(enabled: Boolean) {
if (enabled) {
readerModeController.showReaderView()
activity.components.analytics.metrics.track(Event.ReaderModeOpened)
metrics.track(Event.ReaderModeOpened)
} else {
readerModeController.hideReaderView()
activity.components.analytics.metrics.track(Event.ReaderModeClosed)
metrics.track(Event.ReaderModeClosed)
}
}
override fun handleTabCounterItemInteraction(item: TabCounterMenuItem) {
override fun handleTabCounterItemInteraction(item: TabCounterMenu.Item) {
when (item) {
is TabCounterMenuItem.CloseTab -> {
is TabCounterMenu.Item.CloseTab -> {
sessionManager.selectedSession?.let {
// When closing the last tab we must show the undo snackbar in the home fragment
if (sessionManager.sessionsOfType(it.private).count() == 1) {
@ -184,8 +148,8 @@ class DefaultBrowserToolbarController(
}
}
}
is TabCounterMenuItem.NewTab -> {
activity.browsingModeManager.mode = BrowsingMode.fromBoolean(item.isPrivate)
is TabCounterMenu.Item.NewTab -> {
activity.browsingModeManager.mode = item.mode
navController.popBackStack(R.id.homeFragment, false)
}
}
@ -195,239 +159,6 @@ class DefaultBrowserToolbarController(
engineView.setVerticalClipping(offset)
}
@ExperimentalCoroutinesApi
@Suppress("ComplexMethod", "LongMethod")
override fun handleToolbarItemInteraction(item: ToolbarMenu.Item) {
val sessionUseCases = activity.components.useCases.sessionUseCases
trackToolbarItemInteraction(item)
Do exhaustive when (item) {
is ToolbarMenu.Item.Back -> {
if (item.viewHistory) {
navController.navigate(R.id.action_global_tabHistoryDialogFragment)
} else {
sessionUseCases.goBack.invoke(currentSession)
}
}
is ToolbarMenu.Item.Forward -> {
if (item.viewHistory) {
navController.navigate(R.id.action_global_tabHistoryDialogFragment)
} else {
sessionUseCases.goForward.invoke(currentSession)
}
}
is ToolbarMenu.Item.Reload -> {
val flags = if (item.bypassCache) {
LoadUrlFlags.select(LoadUrlFlags.BYPASS_CACHE)
} else {
LoadUrlFlags.none()
}
sessionUseCases.reload.invoke(currentSession, flags = flags)
}
ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession)
ToolbarMenu.Item.Settings -> browserAnimator.captureEngineViewAndDrawStatically {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
navController.nav(R.id.browserFragment, directions)
}
ToolbarMenu.Item.SyncedTabs -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment()
)
}
is ToolbarMenu.Item.RequestDesktop -> sessionUseCases.requestDesktopSite.invoke(
item.isChecked,
currentSession
)
ToolbarMenu.Item.AddToTopSites -> {
scope.launch {
ioScope.launch {
currentSession?.let {
with(activity.components.useCases.topSitesUseCase) {
addPinnedSites(it.title, it.url)
}
}
}.join()
FenixSnackbar.make(
view = swipeRefresh,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true
)
.setText(
swipeRefresh.context.getString(R.string.snackbar_added_to_top_sites)
)
.show()
}
}
ToolbarMenu.Item.AddToHomeScreen, ToolbarMenu.Item.InstallToHomeScreen -> {
activity.settings().installPwaOpened = true
MainScope().launch {
with(activity.components.useCases.webAppUseCases) {
if (isInstallable()) {
addToHomescreen()
} else {
val directions =
BrowserFragmentDirections.actionBrowserFragmentToCreateShortcutFragment()
navController.navigateSafe(R.id.browserFragment, directions)
}
}
}
}
ToolbarMenu.Item.Share -> {
val directions = NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(
ShareData(
url = currentSession?.url,
title = currentSession?.title
)
),
showPage = true
)
navController.navigate(directions)
}
ToolbarMenu.Item.FindInPage -> {
findInPageLauncher()
activity.components.analytics.metrics.track(Event.FindInPageOpened)
}
ToolbarMenu.Item.AddonsManager -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalAddonsManagementFragment()
)
}
ToolbarMenu.Item.SaveToCollection -> {
activity.components.analytics.metrics
.track(Event.CollectionSaveButtonPressed(TELEMETRY_BROWSER_IDENTIFIER))
currentSession?.let { currentSession ->
val directions =
BrowserFragmentDirections.actionGlobalCollectionCreationFragment(
tabIds = arrayOf(currentSession.id),
selectedTabIds = arrayOf(currentSession.id),
saveCollectionStep = if (tabCollectionStorage.cachedTabCollections.isEmpty()) {
SaveCollectionStep.NameCollection
} else {
SaveCollectionStep.SelectCollection
}
)
navController.nav(R.id.browserFragment, directions)
}
}
ToolbarMenu.Item.OpenInFenix -> {
// Stop the SessionFeature from updating the EngineView and let it release the session
// from the EngineView so that it can immediately be rendered by a different view once
// we switch to the actual browser.
sessionFeature.get()?.release()
// Strip the CustomTabConfig to turn this Session into a regular tab and then select it
customTabSession!!.customTabConfig = null
sessionManager.select(customTabSession)
// Switch to the actual browser which should now display our new selected session
activity.startActivity(openInFenixIntent)
// Close this activity since it is no longer displaying any session
activity.finish()
}
ToolbarMenu.Item.Quit -> {
// We need to show the snackbar while the browsing data is deleting (if "Delete
// browsing data on quit" is activated). After the deletion is over, the snackbar
// is dismissed.
val snackbar: FenixSnackbar? = activity.getRootView()?.let { v ->
FenixSnackbar.make(
view = v,
duration = Snackbar.LENGTH_LONG,
isDisplayedWithBrowserToolbar = true
)
.setText(v.context.getString(R.string.deleting_browsing_data_in_progress))
}
deleteAndQuit(activity, scope, snackbar)
}
ToolbarMenu.Item.ReaderModeAppearance -> {
readerModeController.showControls()
activity.components.analytics.metrics.track(Event.ReaderModeAppearanceOpened)
}
ToolbarMenu.Item.OpenInApp -> {
activity.settings().openInAppOpened = true
val appLinksUseCases =
activity.components.useCases.appLinksUseCases
val getRedirect = appLinksUseCases.appLinkRedirect
currentSession?.let {
val redirect = getRedirect.invoke(it.url)
redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
appLinksUseCases.openAppLink.invoke(redirect.appIntent)
}
}
ToolbarMenu.Item.Bookmark -> {
sessionManager.selectedSession?.let {
bookmarkTapped(it)
}
}
ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id)
)
}
ToolbarMenu.Item.History -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalHistoryFragment()
)
}
ToolbarMenu.Item.Downloads -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalDownloadsFragment()
)
}
}
}
@Suppress("ComplexMethod")
private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) {
val eventItem = when (item) {
is ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK
is ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD
is ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD
ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP
ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS
is ToolbarMenu.Item.RequestDesktop ->
if (item.isChecked) {
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON
} else {
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF
}
ToolbarMenu.Item.FindInPage -> Event.BrowserMenuItemTapped.Item.FIND_IN_PAGE
ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX
ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE
ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION
ToolbarMenu.Item.AddToTopSites -> Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES
ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
ToolbarMenu.Item.SyncedTabs -> Event.BrowserMenuItemTapped.Item.SYNC_TABS
ToolbarMenu.Item.InstallToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT
ToolbarMenu.Item.ReaderModeAppearance ->
Event.BrowserMenuItemTapped.Item.READER_MODE_APPEARANCE
ToolbarMenu.Item.OpenInApp -> Event.BrowserMenuItemTapped.Item.OPEN_IN_APP
ToolbarMenu.Item.Bookmark -> Event.BrowserMenuItemTapped.Item.BOOKMARK
ToolbarMenu.Item.AddonsManager -> Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER
ToolbarMenu.Item.Bookmarks -> Event.BrowserMenuItemTapped.Item.BOOKMARKS
ToolbarMenu.Item.History -> Event.BrowserMenuItemTapped.Item.HISTORY
ToolbarMenu.Item.Downloads -> Event.BrowserMenuItemTapped.Item.DOWNLOADS
}
activity.components.analytics.metrics.track(Event.BrowserMenuItemTapped(eventItem))
}
companion object {
internal const val TELEMETRY_BROWSER_IDENTIFIER = "browserMenu"
}

@ -0,0 +1,315 @@
/* 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.components.toolbar
import android.content.Intent
import androidx.annotation.VisibleForTesting
import androidx.navigation.NavController
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.EngineSession.LoadUrlFlags
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.session.SessionFeature
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator
import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.readermode.ReaderModeController
import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.navigateSafe
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
import org.mozilla.fenix.utils.Do
import org.mozilla.fenix.utils.Settings
/**
* An interface that handles events from the BrowserToolbar menu, triggered by the Interactor
*/
interface BrowserToolbarMenuController {
fun handleToolbarItemInteraction(item: ToolbarMenu.Item)
}
@Suppress("LargeClass")
class DefaultBrowserToolbarMenuController(
private val activity: HomeActivity,
private val navController: NavController,
private val metrics: MetricController,
private val settings: Settings,
private val readerModeController: ReaderModeController,
private val sessionFeature: ViewBoundFeatureWrapper<SessionFeature>,
private val sessionManager: SessionManager,
private val findInPageLauncher: () -> Unit,
private val browserAnimator: BrowserAnimator,
private val swipeRefresh: SwipeRefreshLayout,
private val customTabSession: Session?,
private val openInFenixIntent: Intent,
private val bookmarkTapped: (Session) -> Unit,
private val scope: CoroutineScope,
private val tabCollectionStorage: TabCollectionStorage
) : BrowserToolbarMenuController {
private val currentSession
get() = customTabSession ?: sessionManager.selectedSession
// We hold onto a reference of the inner scope so that we can override this with the
// TestCoroutineScope to ensure sequential execution. If we didn't have this, our tests
// would fail intermittently due to the async nature of coroutine scheduling.
@VisibleForTesting
internal var ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO)
@Suppress("ComplexMethod", "LongMethod")
override fun handleToolbarItemInteraction(item: ToolbarMenu.Item) {
val sessionUseCases = activity.components.useCases.sessionUseCases
trackToolbarItemInteraction(item)
Do exhaustive when (item) {
is ToolbarMenu.Item.Back -> {
if (item.viewHistory) {
navController.navigate(
BrowserFragmentDirections.actionGlobalTabHistoryDialogFragment()
)
} else {
sessionUseCases.goBack.invoke(currentSession)
}
}
is ToolbarMenu.Item.Forward -> {
if (item.viewHistory) {
navController.navigate(
BrowserFragmentDirections.actionGlobalTabHistoryDialogFragment()
)
} else {
sessionUseCases.goForward.invoke(currentSession)
}
}
is ToolbarMenu.Item.Reload -> {
val flags = if (item.bypassCache) {
LoadUrlFlags.select(LoadUrlFlags.BYPASS_CACHE)
} else {
LoadUrlFlags.none()
}
sessionUseCases.reload.invoke(currentSession, flags = flags)
}
ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession)
ToolbarMenu.Item.Settings -> browserAnimator.captureEngineViewAndDrawStatically {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
navController.nav(R.id.browserFragment, directions)
}
ToolbarMenu.Item.SyncedTabs -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionBrowserFragmentToSyncedTabsFragment()
)
}
is ToolbarMenu.Item.RequestDesktop -> sessionUseCases.requestDesktopSite.invoke(
item.isChecked,
currentSession
)
ToolbarMenu.Item.AddToTopSites -> {
scope.launch {
ioScope.launch {
currentSession?.let {
with(activity.components.useCases.topSitesUseCase) {
addPinnedSites(it.title, it.url)
}
}
}.join()
FenixSnackbar.make(
view = swipeRefresh,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true
)
.setText(
swipeRefresh.context.getString(R.string.snackbar_added_to_top_sites)
)
.show()
}
}
ToolbarMenu.Item.AddToHomeScreen, ToolbarMenu.Item.InstallToHomeScreen -> {
settings.installPwaOpened = true
MainScope().launch {
with(activity.components.useCases.webAppUseCases) {
if (isInstallable()) {
addToHomescreen()
} else {
val directions =
BrowserFragmentDirections.actionBrowserFragmentToCreateShortcutFragment()
navController.navigateSafe(R.id.browserFragment, directions)
}
}
}
}
ToolbarMenu.Item.Share -> {
val directions = NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(
ShareData(
url = currentSession?.url,
title = currentSession?.title
)
),
showPage = true
)
navController.navigate(directions)
}
ToolbarMenu.Item.FindInPage -> {
findInPageLauncher()
metrics.track(Event.FindInPageOpened)
}
ToolbarMenu.Item.AddonsManager -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalAddonsManagementFragment()
)
}
ToolbarMenu.Item.SaveToCollection -> {
metrics
.track(Event.CollectionSaveButtonPressed(TELEMETRY_BROWSER_IDENTIFIER))
currentSession?.let { currentSession ->
val directions =
BrowserFragmentDirections.actionGlobalCollectionCreationFragment(
tabIds = arrayOf(currentSession.id),
selectedTabIds = arrayOf(currentSession.id),
saveCollectionStep = if (tabCollectionStorage.cachedTabCollections.isEmpty()) {
SaveCollectionStep.NameCollection
} else {
SaveCollectionStep.SelectCollection
}
)
navController.nav(R.id.browserFragment, directions)
}
}
ToolbarMenu.Item.OpenInFenix -> {
// Stop the SessionFeature from updating the EngineView and let it release the session
// from the EngineView so that it can immediately be rendered by a different view once
// we switch to the actual browser.
sessionFeature.get()?.release()
// Strip the CustomTabConfig to turn this Session into a regular tab and then select it
customTabSession!!.customTabConfig = null
sessionManager.select(customTabSession)
// Switch to the actual browser which should now display our new selected session
activity.startActivity(openInFenixIntent)
// Close this activity since it is no longer displaying any session
activity.finish()
}
ToolbarMenu.Item.Quit -> {
// We need to show the snackbar while the browsing data is deleting (if "Delete
// browsing data on quit" is activated). After the deletion is over, the snackbar
// is dismissed.
val snackbar: FenixSnackbar? = activity.getRootView()?.let { v ->
FenixSnackbar.make(
view = v,
duration = Snackbar.LENGTH_LONG,
isDisplayedWithBrowserToolbar = true
)
.setText(v.context.getString(R.string.deleting_browsing_data_in_progress))
}
deleteAndQuit(activity, scope, snackbar)
}
ToolbarMenu.Item.ReaderModeAppearance -> {
readerModeController.showControls()
metrics.track(Event.ReaderModeAppearanceOpened)
}
ToolbarMenu.Item.OpenInApp -> {
settings.openInAppOpened = true
val appLinksUseCases = activity.components.useCases.appLinksUseCases
val getRedirect = appLinksUseCases.appLinkRedirect
currentSession?.let {
val redirect = getRedirect.invoke(it.url)
redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
appLinksUseCases.openAppLink.invoke(redirect.appIntent)
}
}
ToolbarMenu.Item.Bookmark -> {
sessionManager.selectedSession?.let {
bookmarkTapped(it)
}
}
ToolbarMenu.Item.Bookmarks -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id)
)
}
ToolbarMenu.Item.History -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalHistoryFragment()
)
}
ToolbarMenu.Item.Downloads -> browserAnimator.captureEngineViewAndDrawStatically {
navController.nav(
R.id.browserFragment,
BrowserFragmentDirections.actionGlobalDownloadsFragment()
)
}
}
}
@Suppress("ComplexMethod")
private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) {
val eventItem = when (item) {
is ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK
is ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD
is ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD
ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP
ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS
is ToolbarMenu.Item.RequestDesktop ->
if (item.isChecked) {
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON
} else {
Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF
}
ToolbarMenu.Item.FindInPage -> Event.BrowserMenuItemTapped.Item.FIND_IN_PAGE
ToolbarMenu.Item.OpenInFenix -> Event.BrowserMenuItemTapped.Item.OPEN_IN_FENIX
ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE
ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION
ToolbarMenu.Item.AddToTopSites -> Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES
ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
ToolbarMenu.Item.SyncedTabs -> Event.BrowserMenuItemTapped.Item.SYNC_TABS
ToolbarMenu.Item.InstallToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT
ToolbarMenu.Item.ReaderModeAppearance ->
Event.BrowserMenuItemTapped.Item.READER_MODE_APPEARANCE
ToolbarMenu.Item.OpenInApp -> Event.BrowserMenuItemTapped.Item.OPEN_IN_APP
ToolbarMenu.Item.Bookmark -> Event.BrowserMenuItemTapped.Item.BOOKMARK
ToolbarMenu.Item.AddonsManager -> Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER
ToolbarMenu.Item.Bookmarks -> Event.BrowserMenuItemTapped.Item.BOOKMARKS
ToolbarMenu.Item.History -> Event.BrowserMenuItemTapped.Item.HISTORY
ToolbarMenu.Item.Downloads -> Event.BrowserMenuItemTapped.Item.DOWNLOADS
}
metrics.track(Event.BrowserMenuItemTapped(eventItem))
}
companion object {
internal const val TELEMETRY_BROWSER_IDENTIFIER = "browserMenu"
}
}

@ -44,7 +44,7 @@ interface BrowserToolbarViewInteractor {
fun onBrowserToolbarClicked()
fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item)
fun onTabCounterClicked()
fun onTabCounterMenuItemTapped(item: TabCounterMenuItem)
fun onTabCounterMenuItemTapped(item: TabCounterMenu.Item)
fun onScrolled(offset: Int)
fun onReaderModePressed(enabled: Boolean)
}

@ -0,0 +1,100 @@
/* 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.components.toolbar
import android.content.Context
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.menu2.BrowserMenuController
import mozilla.components.concept.menu.MenuController
import mozilla.components.concept.menu.candidate.DividerMenuCandidate
import mozilla.components.concept.menu.candidate.DrawableMenuIcon
import mozilla.components.concept.menu.candidate.MenuCandidate
import mozilla.components.concept.menu.candidate.TextMenuCandidate
import mozilla.components.concept.menu.candidate.TextStyle
import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
class TabCounterMenu(
context: Context,
private val metrics: MetricController,
private val onItemTapped: (Item) -> Unit
) {
sealed class Item {
object CloseTab : Item()
data class NewTab(val mode: BrowsingMode) : Item()
}
val menuController: MenuController by lazy { BrowserMenuController() }
private val newTabItem: TextMenuCandidate
private val newPrivateTabItem: TextMenuCandidate
private val closeTabItem: TextMenuCandidate
init {
val primaryTextColor = context.getColorFromAttr(R.attr.primaryText)
val textStyle = TextStyle(color = primaryTextColor)
newTabItem = TextMenuCandidate(
text = context.getString(R.string.browser_menu_new_tab),
start = DrawableMenuIcon(
context,
R.drawable.ic_new,
tint = primaryTextColor
),
textStyle = textStyle
) {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB))
onItemTapped(Item.NewTab(BrowsingMode.Normal))
}
newPrivateTabItem = TextMenuCandidate(
text = context.getString(R.string.home_screen_shortcut_open_new_private_tab_2),
start = DrawableMenuIcon(
context,
R.drawable.ic_private_browsing,
tint = primaryTextColor
),
textStyle = textStyle
) {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB))
onItemTapped(Item.NewTab(BrowsingMode.Private))
}
closeTabItem = TextMenuCandidate(
text = context.getString(R.string.close_tab),
start = DrawableMenuIcon(
context,
R.drawable.ic_close,
tint = primaryTextColor
),
textStyle = textStyle
) {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.CLOSE_TAB))
onItemTapped(Item.CloseTab)
}
}
@VisibleForTesting
internal fun menuItems(showOnly: BrowsingMode?): List<MenuCandidate> {
return when (showOnly) {
BrowsingMode.Normal -> listOf(newTabItem)
BrowsingMode.Private -> listOf(newPrivateTabItem)
null -> listOf(
newTabItem,
newPrivateTabItem,
DividerMenuCandidate(),
closeTabItem
)
}
}
fun updateMenu(showOnly: BrowsingMode? = null) {
menuController.submitList(menuItems(showOnly))
}
}

@ -4,27 +4,18 @@
package org.mozilla.fenix.components.toolbar
import android.content.Context
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import mozilla.components.browser.menu.BrowserMenu
import mozilla.components.browser.menu.BrowserMenuBuilder
import mozilla.components.browser.menu.item.BrowserMenuDivider
import mozilla.components.browser.menu.item.BrowserMenuImageText
import mozilla.components.browser.state.selector.getNormalOrPrivateTabs
import mozilla.components.concept.toolbar.Toolbar
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.ktx.android.content.res.resolveAttribute
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.theme.ThemeManager
import java.lang.ref.WeakReference
/**
@ -34,18 +25,25 @@ import java.lang.ref.WeakReference
class TabCounterToolbarButton(
private val lifecycleOwner: LifecycleOwner,
private val isPrivate: Boolean,
private val onItemTapped: (TabCounterMenuItem) -> Unit = {},
private val onItemTapped: (TabCounterMenu.Item) -> Unit = {},
private val showTabs: () -> Unit
) : Toolbar.Action {
private var reference: WeakReference<TabCounter> = WeakReference<TabCounter>(null)
override fun createView(parent: ViewGroup): View {
parent.context.components.core.store.flowScoped(lifecycleOwner) { flow ->
val store = parent.context.components.core.store
val metrics = parent.context.components.analytics.metrics
store.flowScoped(lifecycleOwner) { flow ->
flow.map { state -> state.getNormalOrPrivateTabs(isPrivate).size }
.ifChanged()
.collect { tabs -> updateCount(tabs) }
}
val menu = TabCounterMenu(parent.context, metrics, onItemTapped)
menu.updateMenu()
val view = TabCounter(parent.context).apply {
reference = WeakReference(this)
setOnClickListener {
@ -53,28 +51,23 @@ class TabCounterToolbarButton(
}
setOnLongClickListener {
getTabContextMenu(it.context).show(it)
menu.menuController.show(anchor = it)
true
}
addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) {
setCount(context.components.core.store.state.getNormalOrPrivateTabs(isPrivate).size)
setCount(store.state.getNormalOrPrivateTabs(isPrivate).size)
}
override fun onViewDetachedFromWindow(v: View?) { /* no-op */
}
override fun onViewDetachedFromWindow(v: View?) { /* no-op */ }
})
}
// Set selectableItemBackgroundBorderless
val outValue = TypedValue()
parent.context.theme.resolveAttribute(
android.R.attr.selectableItemBackgroundBorderless,
outValue,
true
)
view.setBackgroundResource(outValue.resourceId)
view.setBackgroundResource(parent.context.theme.resolveAttribute(
android.R.attr.selectableItemBackgroundBorderless
))
return view
}
@ -83,46 +76,4 @@ class TabCounterToolbarButton(
private fun updateCount(count: Int) {
reference.get()?.setCountWithAnimation(count)
}
private fun getTabContextMenu(context: Context): BrowserMenu {
val primaryTextColor = ThemeManager.resolveAttribute(R.attr.primaryText, context)
val metrics = context.components.analytics.metrics
val menuItems = listOf(
BrowserMenuImageText(
label = context.getString(R.string.browser_menu_new_tab),
imageResource = R.drawable.ic_new,
iconTintColorResource = primaryTextColor,
textColorResource = primaryTextColor
) {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB))
onItemTapped(TabCounterMenuItem.NewTab(false))
},
BrowserMenuImageText(
label = context.getString(R.string.home_screen_shortcut_open_new_private_tab_2),
imageResource = R.drawable.ic_private_browsing,
iconTintColorResource = primaryTextColor,
textColorResource = primaryTextColor
) {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB))
onItemTapped(TabCounterMenuItem.NewTab(true))
},
BrowserMenuDivider(),
BrowserMenuImageText(
label = context.getString(R.string.close_tab),
imageResource = R.drawable.ic_close,
iconTintColorResource = primaryTextColor,
textColorResource = primaryTextColor
) {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.CLOSE_TAB))
onItemTapped(TabCounterMenuItem.CloseTab)
}
)
return BrowserMenuBuilder(
when (context.settings().toolbarPosition) {
ToolbarPosition.BOTTOM -> menuItems.reversed()
ToolbarPosition.TOP -> menuItems
}
).build(context)
}
}

@ -132,14 +132,17 @@ class DefaultToolbarIntegration(
)
}
val onTabCounterMenuItemTapped = { item: TabCounterMenuItem ->
interactor.onTabCounterMenuItemTapped(item)
}
val tabsAction =
TabCounterToolbarButton(lifecycleOwner, isPrivate, onTabCounterMenuItemTapped) {
val tabsAction = TabCounterToolbarButton(
lifecycleOwner,
isPrivate,
onItemTapped = {
interactor.onTabCounterMenuItemTapped(it)
},
showTabs = {
toolbar.hideKeyboard()
interactor.onTabCounterClicked()
}
)
toolbar.addBrowserAction(tabsAction)
val engineForSpeculativeConnects = if (!isPrivate) engine else null

@ -27,7 +27,7 @@ class DynamicDownloadDialog(
private val container: ViewGroup,
private val downloadState: DownloadState?,
private val didFail: Boolean,
private val tryAgain: (Long) -> Unit,
private val tryAgain: (String) -> Unit,
private val onCannotOpenFile: () -> Unit,
private val view: View,
private val toolbarHeight: Int,

@ -52,9 +52,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.menu.BrowserMenu
import mozilla.components.browser.menu.BrowserMenuBuilder
import mozilla.components.browser.menu.item.BrowserMenuImageText
import mozilla.components.browser.menu.view.MenuButton
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
@ -86,6 +83,7 @@ import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.tips.FenixTipManager
import org.mozilla.fenix.components.tips.providers.MigrationTipProvider
import org.mozilla.fenix.components.toolbar.TabCounterMenu
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.hideToolbar
@ -311,7 +309,7 @@ class HomeFragment : Fragment() {
}
}
@SuppressWarnings("LongMethod")
@Suppress("LongMethod", "ComplexMethod")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -348,8 +346,21 @@ class HomeFragment : Fragment() {
}
createHomeMenu(requireContext(), WeakReference(view.menuButton))
val tabCounterMenu = TabCounterMenu(
view.context,
metrics = view.context.components.analytics.metrics
) {
if (it is TabCounterMenu.Item.NewTab) {
(activity as HomeActivity).browsingModeManager.mode = it.mode
}
}
val inverseBrowsingMode = when ((activity as HomeActivity).browsingModeManager.mode) {
BrowsingMode.Normal -> BrowsingMode.Private
BrowsingMode.Private -> BrowsingMode.Normal
}
tabCounterMenu.updateMenu(showOnly = inverseBrowsingMode)
view.tab_button.setOnLongClickListener {
createTabCounterMenu(requireContext()).show(view.tab_button)
tabCounterMenu.menuController.show(anchor = it)
true
}
@ -710,50 +721,6 @@ class HomeFragment : Fragment() {
nav(R.id.homeFragment, directions, getToolbarNavOptions(requireContext()))
}
private fun openInNormalTab(url: String) {
(activity as HomeActivity).openToBrowserAndLoad(
searchTermOrURL = url,
newTab = true,
from = BrowserDirection.FromHome
)
}
private fun createTabCounterMenu(context: Context): BrowserMenu {
val primaryTextColor = ThemeManager.resolveAttribute(R.attr.primaryText, context)
val isPrivate = (activity as HomeActivity).browsingModeManager.mode == BrowsingMode.Private
val menuItems = listOf(
BrowserMenuImageText(
label = context.getString(
if (isPrivate) {
R.string.browser_menu_new_tab
} else {
R.string.home_screen_shortcut_open_new_private_tab_2
}
),
imageResource = if (isPrivate) {
R.drawable.ic_new
} else {
R.drawable.ic_private_browsing
},
iconTintColorResource = primaryTextColor,
textColorResource = primaryTextColor
) {
requireComponents.analytics.metrics.track(
Event.TabCounterMenuItemTapped(
if (isPrivate) {
Event.TabCounterMenuItemTapped.Item.NEW_TAB
} else {
Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB
}
)
)
(activity as HomeActivity).browsingModeManager.mode =
BrowsingMode.fromBoolean(!isPrivate)
}
)
return BrowserMenuBuilder(menuItems).build(context)
}
@SuppressWarnings("ComplexMethod", "LongMethod")
private fun createHomeMenu(context: Context, menuButtonView: WeakReference<MenuButton>) =
HomeMenu(

@ -113,8 +113,11 @@ class SearchFragment : Fragment(), UserInteractionHandler {
searchController
)
awesomeBarView = AwesomeBarView(requireContext(), searchInteractor,
view.findViewById(R.id.awesomeBar))
awesomeBarView = AwesomeBarView(
activity,
searchInteractor,
view.findViewById(R.id.awesomeBar)
)
setShortcutsChangedListener(CustomSearchEngineStore.PREF_FILE_SEARCH_ENGINES)
setShortcutsChangedListener(FenixSearchEngineProvider.PREF_FILE_SEARCH_ENGINES)

@ -4,7 +4,6 @@
package org.mozilla.fenix.search.awesomebar
import android.content.Context
import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.core.graphics.BlendModeColorFilterCompat.createBlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat.SRC_IN
@ -25,7 +24,7 @@ import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.support.ktx.android.content.getColorFromAttr
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.search.SearchEngineSource
import org.mozilla.fenix.search.SearchFragmentState
@ -34,7 +33,7 @@ import org.mozilla.fenix.search.SearchFragmentState
* View that contains and configures the BrowserAwesomeBar
*/
class AwesomeBarView(
private val context: Context,
private val activity: HomeActivity,
val interactor: AwesomeBarInteractor,
val view: BrowserAwesomeBar
) {
@ -90,21 +89,20 @@ class AwesomeBarView(
init {
view.itemAnimator = null
val components = context.components
val primaryTextColor = context.getColorFromAttr(R.attr.primaryText)
val components = activity.components
val primaryTextColor = activity.getColorFromAttr(R.attr.primaryText)
val draw = getDrawable(context, R.drawable.ic_link)!!
draw.colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
val engineForSpeculativeConnects =
if (!isBrowsingModePrivate()) components.core.engine else null
val engineForSpeculativeConnects = when (activity.browsingModeManager.mode) {
BrowsingMode.Normal -> components.core.engine
BrowsingMode.Private -> null
}
sessionProvider =
SessionSuggestionProvider(
context.resources,
activity.resources,
components.core.store,
selectTabUseCase,
components.core.icons,
getDrawable(context, R.drawable.ic_search_results_tab),
getDrawable(activity, R.drawable.ic_search_results_tab),
excludeSelectedSession = true
)
@ -121,17 +119,17 @@ class AwesomeBarView(
bookmarksStorage = components.core.bookmarksStorage,
loadUrlUseCase = loadUrlUseCase,
icons = components.core.icons,
indicatorIcon = getDrawable(context, R.drawable.ic_search_results_bookmarks),
indicatorIcon = getDrawable(activity, R.drawable.ic_search_results_bookmarks),
engine = engineForSpeculativeConnects
)
val searchBitmap = getDrawable(context, R.drawable.ic_search)!!.apply {
val searchBitmap = getDrawable(activity, R.drawable.ic_search)!!.apply {
colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
}.toBitmap()
defaultSearchSuggestionProvider =
SearchSuggestionProvider(
context = context,
context = activity,
searchEngineManager = components.search.searchEngineManager,
searchUseCase = searchUseCase,
fetchClient = components.core.client,
@ -146,7 +144,7 @@ class AwesomeBarView(
defaultSearchActionProvider =
SearchActionProvider(
searchEngineGetter = suspend {
components.search.searchEngineManager.getDefaultSearchEngineAsync(context)
components.search.searchEngineManager.getDefaultSearchEngineAsync(activity)
},
searchUseCase = searchUseCase,
icon = searchBitmap,
@ -156,7 +154,7 @@ class AwesomeBarView(
shortcutsEnginePickerProvider =
ShortcutsSuggestionProvider(
searchEngineProvider = components.search.provider,
context = context,
context = activity,
selectShortcutEngine = interactor::onSearchShortcutEngineSelected,
selectShortcutEngineSettings = interactor::onClickSearchEngineSettings
)
@ -221,7 +219,7 @@ class AwesomeBarView(
providersToAdd.addAll(getSelectedSearchSuggestionProvider(state))
}
if (!isBrowsingModePrivate()) {
if (activity.browsingModeManager.mode == BrowsingMode.Normal) {
providersToAdd.add(sessionProvider)
}
@ -245,18 +243,13 @@ class AwesomeBarView(
providersToRemove.addAll(getSelectedSearchSuggestionProvider(state))
}
if (isBrowsingModePrivate()) {
if (activity.browsingModeManager.mode == BrowsingMode.Private) {
providersToRemove.add(sessionProvider)
}
return providersToRemove
}
private fun isBrowsingModePrivate(): Boolean {
return (context.asActivity() as? HomeActivity)?.browsingModeManager?.mode?.isPrivate
?: false
}
private fun getSelectedSearchSuggestionProvider(state: SearchFragmentState): List<AwesomeBar.SuggestionProvider> {
return when (state.searchEngineSource) {
is SearchEngineSource.Default -> listOf(
@ -278,18 +271,20 @@ class AwesomeBarView(
private fun getSuggestionProviderForEngine(engine: SearchEngine): List<AwesomeBar.SuggestionProvider> {
return searchSuggestionProviderMap.getOrPut(engine) {
val components = context.components
val primaryTextColor = context.getColorFromAttr(R.attr.primaryText)
val components = activity.components
val primaryTextColor = activity.getColorFromAttr(R.attr.primaryText)
val searchBitmap = getDrawable(context, R.drawable.ic_search)?.apply {
val searchBitmap = getDrawable(activity, R.drawable.ic_search)!!.apply {
colorFilter = createBlendModeColorFilterCompat(primaryTextColor, SRC_IN)
}?.toBitmap()
}.toBitmap()
val engineForSpeculativeConnects =
if (!isBrowsingModePrivate()) components.core.engine else null
val engineForSpeculativeConnects = when (activity.browsingModeManager.mode) {
BrowsingMode.Normal -> components.core.engine
BrowsingMode.Private -> null
}
val searchEngine =
components.search.provider.installedSearchEngines(context).list.find { it.name == engine.name }
?: components.search.provider.getDefaultEngine(context)
components.search.provider.installedSearchEngines(activity).list.find { it.name == engine.name }
?: components.search.provider.getDefaultEngine(activity)
listOf(
SearchActionProvider(

@ -99,12 +99,13 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
): View? {
val args by navArgs<SearchDialogFragmentArgs>()
val view = inflater.inflate(R.layout.fragment_search_dialog, container, false)
val activity = requireActivity() as HomeActivity
requireComponents.analytics.metrics.track(Event.InteractWithSearchURLArea)
store = SearchDialogFragmentStore(
createInitialSearchFragmentState(
activity as HomeActivity,
activity,
requireComponents,
tabId = args.sessionId,
pastedText = args.pastedText,
@ -114,7 +115,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
interactor = SearchDialogInteractor(
SearchDialogController(
activity = requireActivity() as HomeActivity,
activity = activity,
sessionManager = requireComponents.core.sessionManager,
store = store,
navController = findNavController(),
@ -137,7 +138,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
).also(::addSearchButton)
awesomeBarView = AwesomeBarView(
requireContext(),
activity,
interactor,
view.awesome_bar
)

@ -5,14 +5,8 @@
package org.mozilla.fenix.settings
import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference
import com.google.android.play.core.ktx.launchReview
import com.google.android.play.core.ktx.requestReview
import com.google.android.play.core.review.ReviewManagerFactory
import kotlinx.coroutines.launch
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
@ -48,16 +42,5 @@ class SecretSettingsFragment : PreferenceFragmentCompat() {
isChecked = context.settings().syncedTabsInTabsTray
onPreferenceChangeListener = SharedPreferenceUpdater()
}
requirePreference<Preference>(R.string.pref_key_temp_review_prompt).apply {
setOnPreferenceClickListener {
viewLifecycleOwner.lifecycleScope.launch {
val manager = ReviewManagerFactory.create(requireContext())
val reviewInfo = manager.requestReview()
manager.launchReview(requireActivity(), reviewInfo)
}
true
}
}
}
}

@ -26,6 +26,7 @@ fun deleteAndQuit(activity: Activity, coroutineScope: CoroutineScope, snackbar:
activity.components.useCases.tabsUseCases.removeAllTabs,
activity.components.core.historyStorage,
activity.components.core.permissionStorage,
activity.components.core.icons,
activity.components.core.engine,
coroutineContext
)
@ -53,7 +54,7 @@ fun deleteAndQuit(activity: Activity, coroutineScope: CoroutineScope, snackbar:
private suspend fun DeleteBrowsingDataController.deleteType(type: DeleteBrowsingDataOnQuitType) {
when (type) {
DeleteBrowsingDataOnQuitType.TABS -> deleteTabs()
DeleteBrowsingDataOnQuitType.HISTORY -> deleteHistoryAndDOMStorages()
DeleteBrowsingDataOnQuitType.HISTORY -> deleteBrowsingData()
DeleteBrowsingDataOnQuitType.COOKIES -> deleteCookies()
DeleteBrowsingDataOnQuitType.CACHE -> deleteCachedFiles()
DeleteBrowsingDataOnQuitType.PERMISSIONS -> withContext(IO) {

@ -6,6 +6,7 @@ package org.mozilla.fenix.settings.deletebrowsingdata
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.storage.HistoryStorage
import mozilla.components.feature.tabs.TabsUseCases
@ -15,7 +16,6 @@ import kotlin.coroutines.CoroutineContext
interface DeleteBrowsingDataController {
suspend fun deleteTabs()
suspend fun deleteBrowsingData()
suspend fun deleteHistoryAndDOMStorages()
suspend fun deleteCookies()
suspend fun deleteCachedFiles()
suspend fun deleteSitePermissions()
@ -25,6 +25,7 @@ class DefaultDeleteBrowsingDataController(
private val removeAllTabs: TabsUseCases.RemoveAllTabsUseCase,
private val historyStorage: HistoryStorage,
private val permissionStorage: PermissionStorage,
private val iconsStorage: BrowserIcons,
private val engine: Engine,
private val coroutineContext: CoroutineContext = Dispatchers.Main
) : DeleteBrowsingDataController {
@ -36,14 +37,11 @@ class DefaultDeleteBrowsingDataController(
}
override suspend fun deleteBrowsingData() {
deleteHistoryAndDOMStorages()
}
override suspend fun deleteHistoryAndDOMStorages() {
withContext(coroutineContext) {
engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES))
historyStorage.deleteEverything()
iconsStorage.clear()
}
historyStorage.deleteEverything()
}
override suspend fun deleteCookies() {

@ -45,6 +45,7 @@ class DeleteBrowsingDataFragment : Fragment(R.layout.fragment_delete_browsing_da
requireContext().components.useCases.tabsUseCases.removeAllTabs,
requireContext().components.core.historyStorage,
requireContext().components.core.permissionStorage,
requireContext().components.core.icons,
requireContext().components.core.engine
)
settings = requireContext().settings()

@ -118,8 +118,8 @@ class SitePermissionsExceptionsFragment :
}
}
override fun onClick(view: View?) {
val sitePermissions = view?.tag as SitePermissions
override fun onClick(view: View) {
val sitePermissions = view.tag as SitePermissions
val directions = SitePermissionsExceptionsFragmentDirections
.actionSitePermissionsToExceptionsToSitePermissionsDetails(sitePermissions)
nav(R.id.sitePermissionsExceptionsFragment, directions)

@ -1,29 +0,0 @@
/* 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.utils
import mozilla.components.support.ktx.android.content.PreferencesHolder
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
fun featureFlagPreference(
key: String,
default: Boolean,
featureFlag: Boolean
): ReadWriteProperty<PreferencesHolder, Boolean> =
FeatureFlagPreferencePreference(key, default, featureFlag)
private class FeatureFlagPreferencePreference(
private val key: String,
private val default: Boolean,
private val featureFlag: Boolean
) : ReadWriteProperty<PreferencesHolder, Boolean> {
override fun getValue(thisRef: PreferencesHolder, property: KProperty<*>): Boolean =
featureFlag && thisRef.preferences.getBoolean(key, default)
override fun setValue(thisRef: PreferencesHolder, property: KProperty<*>, value: Boolean) =
thisRef.preferences.edit().putBoolean(key, value).apply()
}

@ -30,6 +30,8 @@ import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.metrics.MozillaProductDetector
import org.mozilla.fenix.components.settings.counterPreference
import org.mozilla.fenix.components.settings.featureFlagPreference
import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
@ -50,14 +52,9 @@ private const val AUTOPLAY_USER_SETTING = "AUTOPLAY_USER_SETTING"
class Settings(private val appContext: Context) : PreferencesHolder {
companion object {
const val showLoginsSecureWarningSyncMaxCount = 1
const val showLoginsSecureWarningMaxCount = 1
const val trackingProtectionOnboardingMaximumCount = 1
const val pwaVisitsToShowPromptMaxCount = 3
const val topSitesMaxCount = 16
const val FENIX_PREFERENCES = "fenix_preferences"
private const val showSearchWidgetCFRMaxCount = 3
private const val BLOCKED_INT = 0
private const val ASK_TO_ALLOW_INT = 1
private const val ALLOWED_INT = 2
@ -177,38 +174,26 @@ class Settings(private val appContext: Context) : PreferencesHolder {
true
)
private val activeSearchCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_search_count),
default = 0
private val activeSearchCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_search_count)
)
fun incrementActiveSearchCount() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_search_count),
activeSearchCount + 1
).apply()
}
fun incrementActiveSearchCount() = activeSearchCount.increment()
private val isActiveSearcher: Boolean
get() = activeSearchCount > 2
get() = activeSearchCount.value > 2
fun shouldDisplaySearchWidgetCFR(): Boolean =
isActiveSearcher &&
searchWidgetCFRDismissCount < showSearchWidgetCFRMaxCount &&
searchWidgetCFRDismissCount.underMaxCount() &&
!searchWidgetInstalled &&
!searchWidgetCFRManuallyDismissed
private val searchWidgetCFRDisplayCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count),
default = 0
private val searchWidgetCFRDisplayCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count)
)
fun incrementSearchWidgetCFRDisplayed() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_display_count),
searchWidgetCFRDisplayCount + 1
).apply()
}
fun incrementSearchWidgetCFRDisplayed() = searchWidgetCFRDisplayCount.increment()
private val searchWidgetCFRManuallyDismissed by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_manually_dismissed),
@ -222,17 +207,12 @@ class Settings(private val appContext: Context) : PreferencesHolder {
).apply()
}
private val searchWidgetCFRDismissCount by intPreference(
private val searchWidgetCFRDismissCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_dismiss_count),
default = 0
maxCount = 3
)
fun incrementSearchWidgetCFRDismissed() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_search_widget_cfr_dismiss_count),
searchWidgetCFRDismissCount + 1
).apply()
}
fun incrementSearchWidgetCFRDismissed() = searchWidgetCFRDismissCount.increment()
val isInSearchWidgetExperiment by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_is_in_search_widget_experiment),
@ -298,17 +278,16 @@ class Settings(private val appContext: Context) : PreferencesHolder {
val shouldShowTrackingProtectionOnboarding: Boolean
get() = !isOverrideTPPopupsForPerformanceTest &&
(trackingProtectionOnboardingCount < trackingProtectionOnboardingMaximumCount &&
(trackingProtectionOnboardingCount.underMaxCount() &&
!trackingProtectionOnboardingShownThisSession)
var showSecretDebugMenuThisSession = false
var showNotificationsSetting = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
val shouldShowSecurityPinWarningSync: Boolean
get() = loginsSecureWarningSyncCount < showLoginsSecureWarningSyncMaxCount
get() = loginsSecureWarningSyncCount.underMaxCount()
val shouldShowSecurityPinWarning: Boolean
get() = loginsSecureWarningCount < showLoginsSecureWarningMaxCount
get() = loginsSecureWarningCount.underMaxCount()
var shouldUseLightTheme by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_light_theme),
@ -549,12 +528,6 @@ class Settings(private val appContext: Context) : PreferencesHolder {
return touchExplorationIsEnabled || switchServiceIsEnabled
}
val toolbarSettingString: String
get() = when {
shouldUseBottomToolbar -> appContext.getString(R.string.preference_bottom_toolbar)
else -> appContext.getString(R.string.preference_top_toolbar)
}
fun getDeleteDataOnQuit(type: DeleteBrowsingDataOnQuitType): Boolean =
preferences.getBoolean(type.getPreferenceKey(appContext), false)
@ -576,30 +549,20 @@ class Settings(private val appContext: Context) : PreferencesHolder {
).apply()
@VisibleForTesting(otherwise = PRIVATE)
internal val loginsSecureWarningSyncCount by intPreference(
internal val loginsSecureWarningSyncCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning_sync),
default = 0
maxCount = 1
)
@VisibleForTesting(otherwise = PRIVATE)
internal val loginsSecureWarningCount by intPreference(
internal val loginsSecureWarningCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning),
default = 0
maxCount = 1
)
fun incrementShowLoginsSecureWarningCount() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning),
loginsSecureWarningCount + 1
).apply()
}
fun incrementShowLoginsSecureWarningCount() = loginsSecureWarningCount.increment()
fun incrementShowLoginsSecureWarningSyncCount() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning_sync),
loginsSecureWarningSyncCount + 1
).apply()
}
fun incrementShowLoginsSecureWarningSyncCount() = loginsSecureWarningSyncCount.increment()
val shouldShowSearchSuggestions by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_show_search_suggestions),
@ -621,21 +584,16 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default = false
)
fun incrementVisitedInstallableCount() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_install_pwa_visits),
pwaInstallableVisitCount + 1
).apply()
}
fun incrementVisitedInstallableCount() = pwaInstallableVisitCount.increment()
@VisibleForTesting(otherwise = PRIVATE)
internal val pwaInstallableVisitCount by intPreference(
internal val pwaInstallableVisitCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_install_pwa_visits),
default = 0
maxCount = 3
)
private val userNeedsToVisitInstallableSites: Boolean
get() = pwaInstallableVisitCount < pwaVisitsToShowPromptMaxCount
get() = pwaInstallableVisitCount.underMaxCount()
val shouldShowPwaOnboarding: Boolean
get() {
@ -644,9 +602,8 @@ class Settings(private val appContext: Context) : PreferencesHolder {
// ShortcutManager::pinnedShortcuts is only available on Oreo+
if (!userKnowsAboutPwas && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val alreadyHavePwaInstalled =
appContext.getSystemService(ShortcutManager::class.java)
.pinnedShortcuts.size > 0
val manager = appContext.getSystemService(ShortcutManager::class.java)
val alreadyHavePwaInstalled = manager != null && manager.pinnedShortcuts.size > 0
// Users know about PWAs onboarding if they already have PWAs installed.
userKnowsAboutPwas = alreadyHavePwaInstalled
@ -661,17 +618,14 @@ class Settings(private val appContext: Context) : PreferencesHolder {
)
@VisibleForTesting(otherwise = PRIVATE)
internal val trackingProtectionOnboardingCount by intPreference(
internal val trackingProtectionOnboardingCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_onboarding),
0
maxCount = 1
)
fun incrementTrackingProtectionOnboardingCount() {
trackingProtectionOnboardingShownThisSession = true
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_tracking_protection_onboarding),
trackingProtectionOnboardingCount + 1
).apply()
trackingProtectionOnboardingCount.increment()
}
fun getSitePermissionsPhoneFeatureAction(
@ -708,7 +662,7 @@ class Settings(private val appContext: Context) : PreferencesHolder {
default: Int
) = preferences.getInt(AUTOPLAY_USER_SETTING, default)
fun getSitePermissionsPhoneFeatureAutoplayAction(
private fun getSitePermissionsPhoneFeatureAutoplayAction(
feature: PhoneFeature,
default: AutoplayAction = AutoplayAction.BLOCKED
) = preferences.getInt(feature.getPreferenceKey(appContext), default.toInt()).toAutoplayAction()
@ -790,23 +744,16 @@ class Settings(private val appContext: Context) : PreferencesHolder {
0
)
fun incrementNumTimesPrivateModeOpened() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_private_mode_opened),
numTimesPrivateModeOpened + 1
).apply()
}
fun incrementNumTimesPrivateModeOpened() = numTimesPrivateModeOpened.increment()
private var showedPrivateModeContextualFeatureRecommender by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_showed_private_mode_cfr),
default = false
)
private val numTimesPrivateModeOpened: Int
get() = preferences.getInt(
appContext.getPreferenceKey(R.string.pref_key_private_mode_opened),
0
)
private val numTimesPrivateModeOpened = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_private_mode_opened)
)
val showPrivateModeContextualFeatureRecommender: Boolean
get() {
@ -814,9 +761,11 @@ class Settings(private val appContext: Context) : PreferencesHolder {
.getInstalledMozillaProducts(appContext as Application)
.contains(MozillaProductDetector.MozillaProducts.FOCUS.productName)
val showCondition =
(numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_INSTALLED && focusInstalled) ||
(numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED && !focusInstalled)
val showCondition = if (focusInstalled) {
numTimesPrivateModeOpened.value == CFR_COUNT_CONDITION_FOCUS_INSTALLED
} else {
numTimesPrivateModeOpened.value == CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED
}
if (showCondition && !showedPrivateModeContextualFeatureRecommender) {
showedPrivateModeContextualFeatureRecommender = true

@ -4,5 +4,5 @@
<vector android:height="32dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:fillType="evenOdd" android:pathData="M17,11v-1a1,1 0,0 1,2 0v1a7,7 0,0 1,-6 6.93L13,21a1,1 0,0 1,-2 0v-3.07A7,7 0,0 1,5 11v-1a1,1 0,0 1,2 0v1a5,5 0,0 0,10 0zM12,2a3,3 0,0 1,3 3v6a3,3 0,0 1,-6 0L9,5a3,3 0,0 1,3 -3z"/>
<path android:fillColor="@color/search_widget_mic_fill_color" android:fillType="evenOdd" android:pathData="M17,11v-1a1,1 0,0 1,2 0v1a7,7 0,0 1,-6 6.93L13,21a1,1 0,0 1,-2 0v-3.07A7,7 0,0 1,5 11v-1a1,1 0,0 1,2 0v1a5,5 0,0 0,10 0zM12,2a3,3 0,0 1,3 3v6a3,3 0,0 1,-6 0L9,5a3,3 0,0 1,3 -3z"/>
</vector>

@ -4,6 +4,6 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white_color" />
<solid android:color="@color/search_widget_background" />
<corners android:radius="@dimen/tab_corner_radius"/>
</shape>

@ -86,7 +86,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
app:tint="?primaryText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"

@ -8,7 +8,7 @@
android:id="@id/button_search_widget_new_tab"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/rounded_white_corners"
android:background="@drawable/rounded_search_widget_background"
android:layout_gravity="center">
<ImageView

@ -8,7 +8,7 @@
android:id="@id/button_search_widget_new_tab"
android:layout_width="64dp"
android:layout_height="50dp"
android:background="@drawable/rounded_white_corners"
android:background="@drawable/rounded_search_widget_background"
android:layout_gravity="center">
<ImageView

@ -7,7 +7,7 @@
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_gravity="center"
android:background="@drawable/rounded_white_corners">
android:background="@drawable/rounded_search_widget_background">
<ImageView
android:id="@+id/button_search_widget_new_tab_icon"

@ -7,7 +7,7 @@
android:layout_width="192dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:background="@drawable/rounded_white_corners">
android:background="@drawable/rounded_search_widget_background">
<ImageView
android:id="@+id/button_search_widget_new_tab_icon"

@ -6,7 +6,7 @@
android:layout_width="100dp"
android:layout_height="50dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@drawable/rounded_white_corners"
android:background="@drawable/rounded_search_widget_background"
android:orientation="horizontal">
<ImageView

@ -7,7 +7,7 @@
android:id="@id/button_search_widget_new_tab"
android:layout_width="100dp"
android:layout_height="50dp"
android:background="@drawable/rounded_white_corners"
android:background="@drawable/rounded_search_widget_background"
android:layout_gravity="center"
android:orientation="vertical">

@ -78,6 +78,28 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Agora no</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">Puetz configurar Firefox pa que ubra automaticament los vinclos en as aplicacions.</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">Ir ta los achustes</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Descartar</string>
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
<string name="camera_permissions_needed_message">La camara precisa acceso. Ves ta los achustes d\'Android, toca &quot;permisos&quot; y toca &quot;permitir&quot;.</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">Ir ta los achustes</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Descartar</string>
<!-- Text for the banner message to tell users about our auto close feature. -->
<string name="tab_tray_close_tabs_banner_message">Configura las pestanyas ubiertas pa que se zarren automaticament si no s\'han mirau en os zaguers días, semanas u meses.</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Veyer las opcions</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
<string name="tab_tray_close_tabs_banner_negative_button_text">Descartar</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nueva pestanya</string>
@ -258,6 +280,8 @@
<string name="preferences_toolbar">Barra de ferramientas</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Inicio</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Personalizar</string>
<!-- Preference description for banner about signing in -->
@ -523,6 +547,8 @@
<string name="collection_open_tabs">Ubrir pestanyas</string>
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Borrar</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Borrar de l\'historial</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (modo privau)</string>
@ -727,10 +753,8 @@
<string name="collections_header">Coleccions</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Menú da colección</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header1">Colecciona las cosetas que timportan</string>
<!-- Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Agrupa busquedas, puestos y pestanyas semellants pa un acceso rapido mas tarde.</string>
<string name="no_collections_description2">Replega lo que t\'importa.\nAlza chuntas las busquedas similars, puestos y pestanyas pa acceder mas rapidament dimpués.</string>
<!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Triar pestanyas</string>
<!-- Title for the "select collection" step of the collection creator -->
@ -974,8 +998,8 @@
<string name="onboarding_whats_new_description">Tiens preguntas sobre lo redisenyo de %s? Quiers saber qué ha cambiau?</string>
<!-- text for underlined clickable link that is part of "what's new" onboarding card description that links to an FAQ -->
<string name="onboarding_whats_new_description_linktext">Obtiene respuestas aquí</string>
<!-- text for the firefox account onboarding card header -->
<string name="onboarding_firefox_account_header">Quita-le lo millor provecho a %s.</string>
<!-- text for the Firefox account onboarding sign in card header -->
<string name="onboarding_account_sign_in_header">Encomienza la sincronización d\'as adrezas d\'interés, las contrasenyas y muito mas con a tuya cuenta d\'o Firefox.</string>
<!-- Text for the button to learn more about signing in to your Firefox account -->
<string name="onboarding_manual_sign_in_learn_more">Saber-ne mas</string>
<!-- text for the firefox account onboarding card header when we detect you're already signed in to
@ -1466,5 +1490,18 @@
<string name="top_sites_max_limit_content">Pa anyadir un nuevo puesto principal, has de borrar-ne belatro. Mantiene pretau lo puesto y selecciona borrar.</string>
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Vale, entendiu</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Amostrar los puestos mas visitaus</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Eliminar</string>
</resources>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Quita-le lo millor provecho a %s.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Colecciona las cosetas que timportan</string>
<!-- Deprecated: Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Agrupa busquedas, puestos y pestanyas semellants pa un acceso rapido mas tarde.</string>
</resources>

@ -433,6 +433,22 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Zarrar</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">%d llingüetes</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">%d llingüeta</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">A mano</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">Dempués d\'un día</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">Dempués d\'una selmana</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">Dempués d\'un mes</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Llingüetes abiertes</string>
@ -449,6 +465,8 @@
<string name="tab_tray_menu_item_save">Guardar nuna coleición</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Compartir toles llingüetes</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">Llingüetes zarraes apocayá</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Zarrar toles llingüetes</string>
<!-- Shortcut action to open new tab -->
@ -657,10 +675,6 @@
<!-- Collections -->
<!-- Collections header on home fragment -->
<string name="collections_header">Coleiciones</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header1">Coleiciona les coses que t\'importen</string>
<!-- Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Agrupa guetes, sitios y llingüetes similares p\'acceder aína a ellos dempués.</string>
<!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Esbilla de llingüetes</string>
<!-- Title for the "select collection" step of the collection creator -->
@ -907,8 +921,6 @@
<string name="onboarding_whats_new_description">¿Tienes entrugues tocante al rediseñu de %s?¿Quies saber qué camudó?</string>
<!-- text for underlined clickable link that is part of "what's new" onboarding card description that links to an FAQ -->
<string name="onboarding_whats_new_description_linktext">Consigui rempuestes equí</string>
<!-- text for the firefox account onboarding card header -->
<string name="onboarding_firefox_account_header">Aprovecha %s al máximu.</string>
<!-- text for the automatic sign-in button while signing in is in process -->
<string name="onboarding_firefox_account_signing_in">Aniciando sesión…</string>
<!-- text for the button to manually sign into Firefox account. The word "Firefox" should not be translated -->
@ -1386,4 +1398,12 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Val, entendílo</string>
</resources>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Aprovecha %s al máximu.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Coleiciona les coses que t\'importen</string>
<!-- Deprecated: Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Agrupa guetes, sitios y llingüetes similares p\'acceder aína a ellos dempués.</string>
</resources>

@ -25,6 +25,30 @@
<!-- Message announced to the user when tab tray is selected with 0 or 2+ tabs -->
<string name="open_tab_tray_plural">%1$s unghjette aperte. Picchichjà per cambià dunghjetta.</string>
<!-- Tab tray multi select title in app bar. The first parameter is the number of tabs selected -->
<string name="tab_tray_multi_select_title">%1$d selezziunatu(i)</string>
<!-- Label of button in create collection dialog for creating a new collection -->
<string name="tab_tray_add_new_collection">Aghjunghje una nova cullezzione</string>
<!-- Label of editable text in create collection dialog for naming a new collection -->
<string name="tab_tray_add_new_collection_name">Nome</string>
<!-- Label of button in save to collection dialog for selecting a current collection -->
<string name="tab_tray_select_collection">Selezziunà a cullezzione</string>
<!-- Content description for close button while in multiselect mode in tab tray -->
<string name="tab_tray_close_multiselect_content_description">Piantà u modu di selezzione multiple</string>
<!-- Content description for save to collection button while in multiselect mode in tab tray -->
<string name="tab_tray_collection_button_multiselect_content_description">Arregistrà lunghjette selezziunate in una cullezzione</string>
<!-- Content description for checkmark while tab is selected while in multiselect mode in tab tray. The first parameter is the title of the tab selected -->
<string name="tab_tray_item_selected_multiselect_content_description">%1$s selezziunatu</string>
<!-- Content description when tab is unselected while in multiselect mode in tab tray. The first parameter is the title of the tab unselected -->
<string name="tab_tray_item_unselected_multiselect_content_description">%1$s diselezziunatu</string>
<!-- Content description announcement when exiting multiselect mode in tab tray -->
<string name="tab_tray_exit_multiselect_content_description">Fine di u modu di selezzione multiple</string>
<!-- Content description announcement when entering multiselect mode in tab tray -->
<string name="tab_tray_enter_multiselect_content_description">Modu di selezzione multiple attivatu, selezziunate lunghjette à arregistrà in una cullezzione</string>
<!-- Content description on checkmark while tab is selected in multiselect mode in tab tray -->
<string name="tab_tray_multiselect_selected_content_description">Selezziunatu</string>
<!-- About content. The first parameter is the name of the application. (For example: Fenix) -->
<string name="about_content">%1$s hè sviluppatu da Adam Novak.</string>
@ -58,6 +82,12 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Micca subitu</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Ricusà</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Ricusà</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nova unghjetta</string>
@ -150,8 +180,8 @@
<!-- Search Fragment -->
<!-- Button in the search view that lets a user search by scanning a QR code -->
<string name="search_scan_button">Numerizà</string>
<!-- Button in the search view that lets a user search by using a shortcut -->
<string name="search_engines_shortcut_button">Mutore di ricerca</string>
<!-- Button in the search view that lets a user change their search engine -->
<string name="search_engine_button">Mutore di ricerca</string>
<!-- Button in the search view when shortcuts are displayed that takes a user to the search engine settings -->
<string name="search_shortcuts_engine_settings">Preferenze di u mutore di ricerca</string>
<!-- Header displayed when selecting a shortcut search engine -->
@ -284,9 +314,14 @@
<!-- Preference for open links in third party apps -->
<string name="preferences_open_links_in_apps">Apre i liami in appiecazioni</string>
<!-- Preference for open download with an external download manager app -->
<string name="preferences_external_download_manager">Ghjestiunariu esternu di scaricamentu</string>
<!-- Preference for add_ons -->
<string name="preferences_addons">Moduli addiziunali</string>
<!-- Preference for notifications -->
<string name="preferences_notifications">Nutificazioni</string>
<!-- Account Preferences -->
<!-- Preference for triggering sync -->
<string name="preferences_sync_now">Sincrunizà avà</string>
@ -513,6 +548,9 @@
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (navigazione privata)</string>
<!-- Button in the current tab tray header in multiselect mode. Saved the selected tabs to a collection when pressed. -->
<string name="tab_tray_save_to_collection">Arregistrà</string>
<!-- History -->
<!-- Text for the button to clear all history -->
<string name="history_delete_all">Squassà a crunulogia</string>
@ -551,6 +589,13 @@
<!-- Text shown when no history exists -->
<string name="history_empty_message">Alcuna crunulogia</string>
<!-- Downloads -->
<!-- Text shown when no download exists -->
<string name="download_empty_message">Alcunu scaricamentu quì</string>
<!-- History multi select title in app bar
The first parameter is the number of downloads selected -->
<string name="download_multi_select_title">%1$d selezziunatu(i)</string>
<!-- Crashes -->
<!-- Title text displayed on the tab crash page. This first parameter is the name of the application (For example: Fenix) -->
<string name="tab_crash_title_2">Per disgrazia, %1$s ùn pò micca caricà a pagina.</string>
@ -708,10 +753,8 @@
<string name="collections_header">Cullezzioni</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Listinu di a cullezzione</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header1">Racuglite ciò chì conta per voi</string>
<!-- Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Gruppate inseme ricerche simile, i siti è lunghjette per un accessu future più prestu.</string>
<string name="no_collections_description2">Racuglite ciò chì conta per voi.\nGruppate inseme ricerche simile, i siti è lunghjette per un accessu future più prestu.</string>
<!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Selezziunà unghjette</string>
<!-- Title for the "select collection" step of the collection creator -->
@ -961,9 +1004,10 @@
<string name="onboarding_whats_new_description">Avete dumande apprupositu di u rinnovu di %s ? Vulete sapè ciò chì hà cambiatu ?</string>
<!-- text for underlined clickable link that is part of "what's new" onboarding card description that links to an FAQ -->
<string name="onboarding_whats_new_description_linktext">Truvate risposte quì</string>
<!-- text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Ottene u più bellu da %s.</string>
<!-- text for the Firefox account onboarding sign in card header -->
<string name="onboarding_account_sign_in_header">Sincrunizate lindette, e parolle dentrata è ancu di più cù u vostru contu Firefox.</string>
<!-- Text for the button to learn more about signing in to your Firefox account -->
<string name="onboarding_manual_sign_in_learn_more">Sapene di più</string>
<!-- text for the firefox account onboarding card header when we detect you're already signed in to
another Firefox browser. (The word `Firefox` should not be translated)
The first parameter is the email of the detected user's account -->
@ -1443,9 +1487,7 @@
<string name="saved_login_duplicate">Lidentificazione di cunnessione cù stu nome dutilizatore esiste dighjà</string>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">Cunnettatevi cù un contu Firefox.</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">Cunnettate un altru apparechju.</string>
<!-- Text displayed asking user to re-authenticate -->
<string name="synced_tabs_reauth">Ci vole à autenticassi torna.</string>
@ -1460,6 +1502,9 @@
<!-- Text displayed on a button in the synced tabs screen to link users to sign in when a user is not signed in to Firefox Sync -->
<string name="synced_tabs_sign_in_button">Cunnettassi à Sync</string>
<!-- The text displayed when a synced device has no tabs to show in the list of Synced Tabs. -->
<string name="synced_tabs_no_open_tabs">Alcuna unghjetta aperta</string>
<!-- Top Sites -->
<!-- Title text displayed in the dialog when top sites limit is reached. -->
<string name="top_sites_max_limit_title">Cunfina di siti principale tocca</string>
@ -1468,13 +1513,15 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Iè, aghju capitu</string>
<!-- DEPRECATED STRINGS -->
<!-- Button in the search view that lets a user search by using a shortcut -->
<string name="search_shortcuts_button">Accurtatoghji</string>
<!-- DEPRECATED: Header displayed when selecting a shortcut search engine -->
<string name="search_shortcuts_search_with">Circà cù</string>
<!-- Header displayed when selecting a shortcut search engine -->
<string name="search_shortcuts_search_with_2">Per sta volta, circà cù :</string>
<!-- Preference title for switch preference to show search shortcuts -->
<string name="preferences_show_search_shortcuts">Affissà laccurtatoghji di ricerca</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Caccià</string>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Ottene u più bellu da %s.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Racuglite ciò chì conta per voi</string>
<!-- Deprecated: Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Gruppate inseme ricerche simile, i siti è lunghjette per un accessu future più prestu.</string>
</resources>

@ -291,6 +291,8 @@
<string name="preferences_toolbar">Symbolleiste</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Theme</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Startseite</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Anpassen</string>
<!-- Preference description for banner about signing in -->
@ -1549,6 +1551,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Ok, verstanden</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Meistbesuchte Seiten anzeigen</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Entfernen</string>

@ -80,6 +80,28 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Nic něnto</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">Móžośo Firefox tak nastajiś, aby se wótkazy awtomatiski w nałoženjach wócynili.</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">K nastajenjam</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Zachyśiś</string>
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
<string name="camera_permissions_needed_message">Pśistup ku kamerje trjebny. Wócyńśo nastajenja Android, pótusniśo zapisk Pšawa a pótusniśo zapisk Dowóliś.</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">K nastajenjam</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Zachyśiś</string>
<!-- Text for the banner message to tell users about our auto close feature. -->
<string name="tab_tray_close_tabs_banner_message">Nastajśo te wócynjone rejtariki tak, aby se awtomatiski zacynili, kótarež njejsu se slědny źeń, slědny tyźeń abo slědny mjasec woglědali.</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Nastajenja pokazaś</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
<string name="tab_tray_close_tabs_banner_negative_button_text">Zachyśiś</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nowy rejtarik</string>
@ -264,6 +286,8 @@
<string name="preferences_toolbar">Symbolowa rědka</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Drastwa</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Startowy bok</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Pśiměriś</string>
<!-- Preference description for banner about signing in -->
@ -470,6 +494,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Zacyniś</string>
<!-- Option in library for Recently Closed Tabs -->
<string name="library_recently_closed_tabs">Rowno zacynjone rejtariki</string>
<!-- Option in library to open Recently Closed Tabs page -->
<string name="recently_closed_show_full_history">Wšu historiju pokazaś</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">Rejtariki: %d</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">Rejtariki: %d</string>
<!-- Recently closed tabs screen message when there are no recently closed tabs -->
<string name="recently_closed_empty_message">How njejsu rowno zacynjone rejtarki</string>
<!-- Tab Management -->
<!-- Title of preference that allows a user to auto close tabs after a specified amount of time -->
<string name="preferences_close_tabs">Rejtariki zacyniś</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">Manuelnje</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">Pó jadnom dnju</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">Pó jadnom tyźenju</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">Pó jadnom mjasecu</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Wócynjone rejtariki</string>
@ -489,6 +538,10 @@
<string name="tab_tray_menu_item_save">Do zběrki składowaś</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Wšykne rejtariki źěliś</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">Rowno zacynjone rejtariki</string>
<!-- Text shown in the menu to view tab settings -->
<string name="tab_tray_menu_tab_settings">Nastajenja rejtarikow</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Wšykne rejtariki zacyniś</string>
<!-- Shortcut action to open new tab -->
@ -535,6 +588,8 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Wótwónoźeś</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Z historije lašowaś</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (priwatny modus)</string>
@ -1497,6 +1552,18 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">W pórěźe, som zrozměł</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Nejcesćej woglědane sedła pokazaś</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Wótwónoźeś</string>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Wuwónoźćo nejlěpše z %s.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Gromaźćo wěcy, kótarež su wam wažne</string>
<!-- Deprecated: Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Zrědujśo pódobne pytanja, sedła a rejtariki za póznjejšy malsny pśistup.</string>
</resources>

@ -284,6 +284,8 @@
<string name="preferences_toolbar">Toolbar</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Theme</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Home</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Customize</string>
<!-- Preference description for banner about signing in -->
@ -488,6 +490,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Close</string>
<!-- Option in library for Recently Closed Tabs -->
<string name="library_recently_closed_tabs">Recently closed tabs</string>
<!-- Option in library to open Recently Closed Tabs page -->
<string name="recently_closed_show_full_history">Show full history</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">%d tabs</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">%d tab</string>
<!-- Recently closed tabs screen message when there are no recently closed tabs -->
<string name="recently_closed_empty_message">No recently closed tabs here</string>
<!-- Tab Management -->
<!-- Title of preference that allows a user to auto close tabs after a specified amount of time -->
<string name="preferences_close_tabs">Close tabs</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">Manually</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">After one day</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">After one week</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">After one month</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Open tabs</string>
@ -507,6 +534,10 @@
<string name="tab_tray_menu_item_save">Save to collection</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Share all tabs</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">Recently closed tabs</string>
<!-- Text shown in the menu to view tab settings -->
<string name="tab_tray_menu_tab_settings">Tab settings</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Close all tabs</string>
<!-- Shortcut action to open new tab -->
@ -1506,6 +1537,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">OK, Got It</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Show most visited sites</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Remove</string>

@ -78,6 +78,28 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Ahora no</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">Puedes configurar Firefox para que abra automáticamente enlaces en aplicaciones.</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">Ir a ajustes</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Ocultar</string>
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
<string name="camera_permissions_needed_message">Se necesita acceso a la cámara. Ve a los ajustes de Android, toca en permisos y luego en permitir.</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">Ir a ajustes</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Ocultar</string>
<!-- Text for the banner message to tell users about our auto close feature. -->
<string name="tab_tray_close_tabs_banner_message">Configura que las pestañas abiertas se cierren automáticamente si no han sido vistas en el último día, semana o mes.</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Ver opciones</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
<string name="tab_tray_close_tabs_banner_negative_button_text">Ocultar</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nueva pestaña</string>
@ -533,6 +555,8 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Eliminar</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Eliminar del historial</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (modo privado)</string>
@ -1493,4 +1517,13 @@
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Eliminar</string>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Saca el máximo provecho a %s.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Recolecta lo que te importa</string>
<!-- Deprecated: Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Agrupa búsquedas, sitios y pestañas similares para acceder a ellos rápidamente.</string>
</resources>

@ -81,6 +81,28 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Orain ez</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">Loturak aplikazioetan automatikoki irekitzeko ezar dezakezu Firefox</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">Joan ezarpenetara</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Baztertu</string>
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
<string name="camera_permissions_needed_message">Kamerarako sarbidea behar da. Joan Androideko ezarpenetara, sakatu baimenetan eta sakatu baimendu.</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">Joan ezarpenetara</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Baztertu</string>
<!-- Text for the banner message to tell users about our auto close feature. -->
<string name="tab_tray_close_tabs_banner_message">Ezarri irekitako fitxak automatikoki ixtea, azken egun, aste edo hilabetean ikusi ez badira</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Ikusi aukerak</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
<string name="tab_tray_close_tabs_banner_negative_button_text">Baztertu</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Fitxa berria</string>
@ -545,6 +567,8 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Kendu</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Ezabatu historiatik</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (modu pribatua)</string>
@ -752,10 +776,8 @@
<string name="collections_header">Bildumak</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Bildumaren menua</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header1">Bildu zuretzat garrantzizkoa dena</string>
<!-- Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Multzokatu antzerako bilaketak, guneak eta fitxak sarbide azkarrago baterako.</string>
<string name="no_collections_description2">Bildu zure interesekoa dena.\nMultzokatu antzerako bilaketak, guneak eta fitxak geroago sarbide zuzena izateko.</string>
<!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Hautatu fitxak</string>
<!-- Title for the "select collection" step of the collection creator -->
@ -1004,8 +1026,8 @@
<string name="onboarding_whats_new_description">Birdiseinatutako %s(r)i buruzko galderak dituzu? Zer aldatu den jakin nahi duzu?</string>
<!-- text for underlined clickable link that is part of "what's new" onboarding card description that links to an FAQ -->
<string name="onboarding_whats_new_description_linktext">Eskuratu erantzunak hemen</string>
<!-- text for the firefox account onboarding card header -->
<string name="onboarding_firefox_account_header">Atera %s(r)i ahalik eta zuku gehiena.</string>
<!-- text for the Firefox account onboarding sign in card header -->
<string name="onboarding_account_sign_in_header">Hasi sinkronizatzen laster-markak, historia eta gehiago zure Firefox kontua erabiliz.</string>
<!-- Text for the button to learn more about signing in to your Firefox account -->
<string name="onboarding_manual_sign_in_learn_more">Argibide gehiago</string>
<!-- text for the firefox account onboarding card header when we detect you're already signed in to
@ -1509,4 +1531,15 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Ados, ulertuta</string>
</resources>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Kendu</string>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Atera %s(r)i ahalik eta zuku gehiena.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Bildu zuretzat garrantzizkoa dena</string>
<!-- Deprecated: Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Multzokatu antzerako bilaketak, guneak eta fitxak sarbide azkarrago baterako.</string>
</resources>

@ -290,6 +290,8 @@
<string name="preferences_toolbar">Työkalupalkki</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Teema</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Koti</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Mukauta</string>
<!-- Preference description for banner about signing in -->
@ -1532,6 +1534,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Selvä</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Näytä vierailluimmat sivustot</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Poista</string>

@ -79,6 +79,29 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Plus tard</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">Vous pouvez configurer Firefox pour ouvrir automatiquement les liens dans des applications.</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">Ouvrir les paramètres</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Ignorer</string>
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
<string name="camera_permissions_needed_message">Accès à la caméra nécessaire. Accédez aux paramètres Android, appuyez sur Autorisations, puis sur Autoriser.</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">Ouvrir les paramètres</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Ignorer</string>
<!-- Text for the banner message to tell users about our auto close feature. -->
<string name="tab_tray_close_tabs_banner_message">Configurez les onglets ouverts pour quils se ferment automatiquement lorsquils nont pas été vus depuis les derniers jours, semaines ou mois.</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Afficher les options</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
<string name="tab_tray_close_tabs_banner_negative_button_text">Ignorer</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nouvel onglet</string>
@ -543,6 +566,8 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Supprimer</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Supprimer de lhistorique</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (navigation privée)</string>
@ -1527,4 +1552,13 @@ Cependant, il peut être moins stable. Téléchargez la version bêta de notre n
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Supprimer</string>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Tirez le meilleur parti de %s.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Rassemblez ce qui compte pour vous</string>
<!-- Deprecated: Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Regroupez des recherches, des sites et des onglets similaires pour y accéder rapidement plus tard.</string>
</resources>

@ -81,6 +81,28 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">No net</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">Jo kinne Firefox ynstelle om keppelingen automatysk yn apps te iepenjen.</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">Nei ynstellingen</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Slute</string>
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
<string name="camera_permissions_needed_message">Kameratagong fereaske. Gean nei jo Android-ynstellingen, tik op machtigingen en tik op toestaan.</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">Nei ynstellingen</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Slute</string>
<!-- Text for the banner message to tell users about our auto close feature. -->
<string name="tab_tray_close_tabs_banner_message">Stel iepen ljepblêden yn om automatysk te sluten as se net yn de ôfrûne dei, wike of moanne besjoen binne.</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Byldopsjes</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
<string name="tab_tray_close_tabs_banner_negative_button_text">Slute</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nij ljepblêd</string>
@ -535,6 +557,8 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Fuortsmite</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Fuortsmite út skiednis</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (priveemodus)</string>
@ -1495,4 +1519,13 @@
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Fuortsmite</string>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Helje it measte út %s.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Sammelje de dingen dy\'t wichtich foar jo binne</string>
<!-- Deprecated: Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Groepearje fergelykbere sykopdrachten, websites en ljepblêden foar flugge tagong letter.</string>
</resources>

@ -82,6 +82,24 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Ani koág̃a</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">Embohekokuaa Firefox ombojuruja hag̃ua ijehegui juajuha tembipuruípe.</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">Eho ñembohekópe</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Mboyke</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">Eho ñembohekópe</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Mboyke</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Ehecha jeporavorã</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
<string name="tab_tray_close_tabs_banner_negative_button_text">Mboyke</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Tendayke pyahu</string>
@ -541,6 +559,8 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Mboguete</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Emboguete tembiasakuégui</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (Ayvu Ñemigua)</string>
@ -1522,4 +1542,9 @@
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Mboguete</string>
</resources>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Eguenohẽ %s-gui eikotevẽva.</string>
</resources>

@ -80,16 +80,23 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Nic nětko</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">Móžeće Firefox tak nastajić, zo bychu so wotkazy awtomatisce w nałoženjach wočinili.</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">K nastajenjam</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Zaćisnyć</string>
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
<string name="camera_permissions_needed_message">Přistup ke kamerje trěbny. Wočińće nastajenja Android, podótkńće so zapiska Prawa a podótkńće so zapiska Dowolić.</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">K nastajenjam</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Zaćisnyć</string>
<!-- Text for the banner message to tell users about our auto close feature. -->
<string name="tab_tray_close_tabs_banner_message">Nastajće te wočinjene rajtarki takle, zo bychu so awtomatisce začinili, kotrež njejsu sej posledni dźeń, posledni tydźeń abo posledni měsac wobhladali.</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Nastajenja pokazać</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
@ -279,6 +286,8 @@
<string name="preferences_toolbar">Symbolowa lajsta</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Drasta</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Startowa strona</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Přiměrić</string>
<!-- Preference description for banner about signing in -->
@ -485,6 +494,32 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Začinić</string>
<!-- Option in library for Recently Closed Tabs -->
<string name="library_recently_closed_tabs">Runje začinjene rajtarki</string>
<!-- Option in library to open Recently Closed Tabs page -->
<string name="recently_closed_show_full_history">Wšu historiju pokazać</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">Rajtarki: %d</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">Rajtarki: %d</string>
<!-- Recently closed tabs screen message when there are no recently closed tabs -->
<string name="recently_closed_empty_message">Tu žane runje začinjene rajtarki njejsu</string>
<!-- Tab Management -->
<!-- Title of preference that allows a user to auto close tabs after a specified amount of time -->
<string name="preferences_close_tabs">Rajtarki začinić</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">Manuelnje</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">Po jednym dnju</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">Po jednym tydźenju</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">Po jednym měsacu</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Wočinjene rajtarki</string>
@ -504,6 +539,10 @@
<string name="tab_tray_menu_item_save">Do zběrki składować</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Wšě rajtarki dźělić</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">Runje začinjene rajtarki</string>
<!-- Text shown in the menu to view tab settings -->
<string name="tab_tray_menu_tab_settings">Nastajenja rajtarkow</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Wšě rajtarki začinić</string>
<!-- Shortcut action to open new tab -->
@ -1512,6 +1551,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">W porjadku, sym zrozumił</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Najhusćišo wopytowane sydła pokazać</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Wotstronić</string>

@ -287,6 +287,8 @@
<string name="preferences_toolbar">Eszköztár</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Téma</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Kezdőlap</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Testreszabás</string>
<!-- Preference description for banner about signing in -->
@ -1529,6 +1531,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Rendben, értem</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">A leglátogatottabb oldalak megjelenítése</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Eltávolítás</string>

@ -81,6 +81,28 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Jangan sekarang</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">Anda dapat mengatur Firefox untuk membuka tautan secara otomatis dalam aplikasi.</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">Buka pengaturan</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Tutup</string>
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
<string name="camera_permissions_needed_message">Akses kamera diperlukan. Buka pengaturan Android, ketuk izin, dan ketuk izinkan.</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">Buka pengaturan</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Tutup</string>
<!-- Text for the banner message to tell users about our auto close feature. -->
<string name="tab_tray_close_tabs_banner_message">Setel tab yang terbuka untuk menutup secara otomatis yang belum pernah dilihat pada beberapa hari, minggu, atau bulan terakhir.</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Lihat pengaturan</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
<string name="tab_tray_close_tabs_banner_negative_button_text">Tutup</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Tab baru</string>
@ -271,6 +293,8 @@
<string name="preferences_toolbar">Bilah alat</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Beranda</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Ubahsuai</string>
<!-- Preference description for banner about signing in -->
@ -548,6 +572,8 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Hapus</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Hapus dari riwayat</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (Mode Privat)</string>
@ -756,10 +782,8 @@
<string name="collections_header">Koleksi</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Menu koleksi</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header1">Kumpulkan hal-hal yang penting bagi Anda</string>
<!-- Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Kelompokkan pencarian, situs, dan tab yang serupa agar dapat diakses cepat nanti.</string>
<string name="no_collections_description2">Kumpulkan hal-hal yang penting bagi Anda.\nKelompokkan pencarian, situs, dan tab serupa untuk akses cepat nanti.</string>
<!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Pilih Tab</string>
<!-- Title for the "select collection" step of the collection creator -->
@ -1012,8 +1036,8 @@
<string name="onboarding_whats_new_description">Punya pertanyaan mengenai desain ulang %s? Ingin tahu apa saja yang berubah?</string>
<!-- text for underlined clickable link that is part of "what's new" onboarding card description that links to an FAQ -->
<string name="onboarding_whats_new_description_linktext">Dapatkan jawabannya di sini</string>
<!-- text for the firefox account onboarding card header -->
<string name="onboarding_firefox_account_header">Dapatkan hasil maksimal dari %s.</string>
<!-- text for the Firefox account onboarding sign in card header -->
<string name="onboarding_account_sign_in_header">Mulai sinkronkan markah, kata sandi, dan lainnya dengan akun Firefox Anda.</string>
<!-- Text for the button to learn more about signing in to your Firefox account -->
<string name="onboarding_manual_sign_in_learn_more">Pelajari lebih lanjut</string>
<!-- text for the firefox account onboarding card header when we detect you're already signed in to
@ -1513,4 +1537,18 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Oke, Paham!</string>
</resources>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Tampilkan situs yang paling sering dikunjungi</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Hapus</string>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Dapatkan hasil maksimal dari %s.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Kumpulkan hal-hal yang penting bagi Anda</string>
<!-- Deprecated: Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Kelompokkan pencarian, situs, dan tab yang serupa agar dapat diakses cepat nanti.</string>
</resources>

@ -288,6 +288,8 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
<string name="preferences_toolbar">Afeggag n yifecka</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Asentel</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Asebter agejdan</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Sagen</string>
<!-- Preference description for banner about signing in -->
@ -497,6 +499,31 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Mdel</string>
<!-- Option in library for Recently Closed Tabs -->
<string name="library_recently_closed_tabs">Iccaren imedlen melmi kan</string>
<!-- Option in library to open Recently Closed Tabs page -->
<string name="recently_closed_show_full_history">Sken amazray meṛṛa</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">%d n yiccaren</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">%d n yiccer</string>
<!-- Recently closed tabs screen message when there are no recently closed tabs -->
<string name="recently_closed_empty_message">Ulac iccaren imedlen melmi kan</string>
<!-- Tab Management -->
<!-- Title of preference that allows a user to auto close tabs after a specified amount of time -->
<string name="preferences_close_tabs">Mdel iccaren</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">S ufus</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">Seld yiwen n wass</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">Seld yiwen n umalas</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">Seld yiwen n waggur</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Iccaren yeldin</string>
@ -516,6 +543,10 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
<string name="tab_tray_menu_item_save">Sekles ɣer tagrumma</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Bḍu akk accaren</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">Iccaren imedlen melmi kan</string>
<!-- Text shown in the menu to view tab settings -->
<string name="tab_tray_menu_tab_settings">Iɣewwaren n yiccer</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Mdel akk iccaren</string>
<!-- Shortcut action to open new tab -->
@ -1531,6 +1562,9 @@ Tiktiwin tigejdanin yuzzlen ur nṣeḥḥi ara
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">IH, awi-t-id</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Sken-d ismal ittwarzan aṭas</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Kkes</string>

@ -296,6 +296,8 @@
<string name="preferences_toolbar">도구 모음</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">테마</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home"></string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">사용자 지정</string>
<!-- Preference description for banner about signing in -->
@ -506,6 +508,32 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">닫기</string>
<!-- Option in library for Recently Closed Tabs -->
<string name="library_recently_closed_tabs">최근에 닫은 탭</string>
<!-- Option in library to open Recently Closed Tabs page -->
<string name="recently_closed_show_full_history">모든 기록 보기</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">%d개 탭</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">%d개 탭</string>
<!-- Recently closed tabs screen message when there are no recently closed tabs -->
<string name="recently_closed_empty_message">최근에 닫은 탭 없음</string>
<!-- Tab Management -->
<!-- Title of preference that allows a user to auto close tabs after a specified amount of time -->
<string name="preferences_close_tabs">탭 닫기</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">수동</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">하루 후</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">일주일 후</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">한 달 후</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">열린 탭</string>
@ -525,6 +553,10 @@
<string name="tab_tray_menu_item_save">모음집에 저장</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">모든 탭 공유</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">최근에 닫은 탭</string>
<!-- Text shown in the menu to view tab settings -->
<string name="tab_tray_menu_tab_settings">탭 설정</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">모든 탭 닫기</string>
<!-- Shortcut action to open new tab -->
@ -967,7 +999,7 @@
<!-- Title for the tabs item in Delete browsing data -->
<string name="preferences_delete_browsing_data_tabs_title_2">열린 탭</string>
<!-- Subtitle for the tabs item in Delete browsing data, parameter will be replaced with the number of open tabs -->
<string name="preferences_delete_browsing_data_tabs_subtitle">%d개</string>
<string name="preferences_delete_browsing_data_tabs_subtitle">%d개</string>
<!-- Title for the data and history items in Delete browsing data -->
<string name="preferences_delete_browsing_data_browsing_data_title">방문 기록 및 사이트 데이터</string>
@ -1567,6 +1599,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">확인</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">자주 방문한 사이트 표시</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">삭제</string>

@ -288,6 +288,8 @@
<string name="preferences_toolbar">Verktøylinje</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Hjem</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Tilpass</string>
<!-- Preference description for banner about signing in -->
@ -497,6 +499,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Lukk</string>
<!-- Option in library for Recently Closed Tabs -->
<string name="library_recently_closed_tabs">Nylig lukkede faner</string>
<!-- Option in library to open Recently Closed Tabs page -->
<string name="recently_closed_show_full_history">Vis full historikk</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">%d faner</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">%d fane</string>
<!-- Recently closed tabs screen message when there are no recently closed tabs -->
<string name="recently_closed_empty_message">Ingen nylig lukkede faner her</string>
<!-- Tab Management -->
<!-- Title of preference that allows a user to auto close tabs after a specified amount of time -->
<string name="preferences_close_tabs">Lukk faner</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">Manuelt</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">Etter en dag</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">Etter en uke</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">Etter en måned</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Åpne faner</string>
@ -517,6 +544,10 @@
<string name="tab_tray_menu_item_save">Lagre i samling</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Del alle faner</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">Nylig lukkede faner</string>
<!-- Text shown in the menu to view tab settings -->
<string name="tab_tray_menu_tab_settings">Fane-innstillinger</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Lukk alle faner</string>
<!-- Shortcut action to open new tab -->
@ -1540,6 +1571,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">OK, jeg skjønner</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Vis mest besøkte nettsteder</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Fjern</string>

@ -87,6 +87,11 @@
<color name="collection_icon_color_green">@color/collection_icon_color_green_dark_theme</color>
<color name="collection_icon_color_yellow">@color/collection_icon_color_yellow_dark_theme</color>
<!-- Search Widget -->
<color name="search_widget_background">@color/inset_dark_theme</color>
<color name="search_widget_mic_fill_color">@color/primary_text_dark_theme</color>
<color name="search_widget_text">@color/primary_text_dark_theme</color>
<!-- Reader View colors -->
<color name="mozac_feature_readerview_text_color">@color/primary_text_dark_theme</color>

@ -84,6 +84,28 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Niet nu</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">U kunt Firefox instellen om koppelingen automatisch in apps te openen.</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">Naar instellingen</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Sluiten</string>
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
<string name="camera_permissions_needed_message">Cameratoegang vereist. Ga naar uw Android-instellingen, tik op machtigingen en tik op toestaan.</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">Naar instellingen</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Sluiten</string>
<!-- Text for the banner message to tell users about our auto close feature. -->
<string name="tab_tray_close_tabs_banner_message">Stel open tabbladen in om automatisch te sluiten als ze niet in de afgelopen dag, week of maand zijn bekeken.</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Beeldopties</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
<string name="tab_tray_close_tabs_banner_negative_button_text">Sluiten</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nieuw tabblad</string>
@ -543,6 +565,8 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Verwijderen</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Verwijderen uit geschiedenis</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (privémodus)</string>
@ -1506,4 +1530,13 @@
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Verwijderen</string>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Haal het meeste uit %s.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Verzamel de dingen die belangrijk voor u zijn</string>
<!-- Deprecated: Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Groepeer vergelijkbare zoekopdrachten, websites en tabbladen voor snelle toegang later.</string>
</resources>

@ -82,6 +82,19 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Ikkje no</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Ignorer</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">Gå til Innstillingar</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Ignorer</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Vis alternativ</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
<string name="tab_tray_close_tabs_banner_negative_button_text">Ignorer</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Ny fane</string>
@ -267,6 +280,8 @@
<string name="preferences_toolbar">Verktøylinje</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Heim</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Tilpass</string>
<!-- Preference description for banner about signing in -->
@ -474,6 +489,27 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Lat att</string>
<!-- Option in library for Recently Closed Tabs -->
<string name="library_recently_closed_tabs">Nyleg attlatne faner</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">%d faner</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">%d fane</string>
<!-- Tab Management -->
<!-- Title of preference that allows a user to auto close tabs after a specified amount of time -->
<string name="preferences_close_tabs">Lat att faner</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">Manuelt</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">Etter ein dag</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">Etter ei veke</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">Etter ein månad</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Opne faner</string>
@ -493,6 +529,10 @@
<string name="tab_tray_menu_item_save">Lagre i samling</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Del alle faner</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">Nyleg attlatne faner</string>
<!-- Text shown in the menu to view tab settings -->
<string name="tab_tray_menu_tab_settings">Fane-innstillinger</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Lat att alle faner</string>
<!-- Shortcut action to open new tab -->
@ -540,6 +580,8 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Fjern</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Slett frå historikk</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (privatmodus)</string>
@ -1502,4 +1544,5 @@
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Fjern</string>
</resources>
</resources>

@ -286,6 +286,8 @@
<string name="preferences_toolbar">Barra de ferramentas</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Tela inicial</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Personalizar</string>
<!-- Preference description for banner about signing in -->
@ -492,6 +494,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Fechar</string>
<!-- Option in library for Recently Closed Tabs -->
<string name="library_recently_closed_tabs">Abas fechadas recentemente</string>
<!-- Option in library to open Recently Closed Tabs page -->
<string name="recently_closed_show_full_history">Mostrar todo o histórico</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">%d abas</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">%d aba</string>
<!-- Recently closed tabs screen message when there are no recently closed tabs -->
<string name="recently_closed_empty_message">Nenhuma aba fechada recentemente aqui</string>
<!-- Tab Management -->
<!-- Title of preference that allows a user to auto close tabs after a specified amount of time -->
<string name="preferences_close_tabs">Fechar abas</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">Manualmente</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">Após um dia</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">Após uma semana</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">Após um mês</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Abas abertas</string>
@ -511,6 +538,10 @@
<string name="tab_tray_menu_item_save">Salvar em coleção</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Compartilhar todas as abas</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">Abas fechadas recentemente</string>
<!-- Text shown in the menu to view tab settings -->
<string name="tab_tray_menu_tab_settings">Configurações de abas</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Fechar todas as abas</string>
<!-- Shortcut action to open new tab -->
@ -1520,6 +1551,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">OK, entendi</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Mostrar os sites mais visitados</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Remover</string>

@ -82,6 +82,15 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Не сейчас</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">Перейти в настройки</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">Перейти в настройки</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Открыть настройки</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Новая вкладка</string>
@ -545,6 +554,8 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Удалить</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Удалить из истории</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (Приватный просмотр)</string>
@ -1525,4 +1536,9 @@
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Убрать</string>
</resources>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Получите максимум от %s.</string>
</resources>

@ -23,6 +23,29 @@
<!-- Message announced to the user when tab tray is selected with 0 or 2+ tabs -->
<string name="open_tab_tray_plural">%1$s skeda të hapura. Prekeni që të ndërroni skeda.</string>
<!-- Tab tray multi select title in app bar. The first parameter is the number of tabs selected -->
<string name="tab_tray_multi_select_title">%1$d të përzgjedhura</string>
<!-- Label of button in create collection dialog for creating a new collection -->
<string name="tab_tray_add_new_collection">Shtoni koleksion të ri</string>
<!-- Label of editable text in create collection dialog for naming a new collection -->
<string name="tab_tray_add_new_collection_name">Emër</string>
<!-- Label of button in save to collection dialog for selecting a current collection -->
<string name="tab_tray_select_collection">Përzgjidhni koleksion</string>
<!-- Content description for close button while in multiselect mode in tab tray -->
<string name="tab_tray_close_multiselect_content_description">Dil nga mënyra përzgjedhje e shumëfishtë</string>
<!-- Content description for save to collection button while in multiselect mode in tab tray -->
<string name="tab_tray_collection_button_multiselect_content_description">Ruaji te koleksioni skedat e përzgjedhura </string>
<!-- Content description for checkmark while tab is selected while in multiselect mode in tab tray. The first parameter is the title of the tab selected -->
<string name="tab_tray_item_selected_multiselect_content_description">U përzgjodh %1$s</string>
<!-- Content description when tab is unselected while in multiselect mode in tab tray. The first parameter is the title of the tab unselected -->
<string name="tab_tray_item_unselected_multiselect_content_description">U shpërzgjodh %1$s</string>
<!-- Content description announcement when exiting multiselect mode in tab tray -->
<string name="tab_tray_exit_multiselect_content_description">U dol nga mënyra përzgjedhje e shumëfishtë</string>
<!-- Content description announcement when entering multiselect mode in tab tray -->
<string name="tab_tray_enter_multiselect_content_description">U hy në mënyrën përzgjedhje e shumëfishtë, përzgjidhni skeda që të ruhen te një koleksion</string>
<!-- Content description on checkmark while tab is selected in multiselect mode in tab tray -->
<string name="tab_tray_multiselect_selected_content_description">E përzgjedhur</string>
<!-- About content. The first parameter is the name of the application. (For example: Fenix) -->
<string name="about_content">%1$s prodhohet nga Adam Novak.</string>
@ -54,6 +77,28 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Jo tani</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">Mund ta ujdisni Firefox-in të hapë automatikisht lidhje në aplikacione.</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">Shko te rregullimet</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Hidhe tej</string>
<!-- Text for the info dialog when camera permissions have been denied but user tries to access a camera feature. -->
<string name="camera_permissions_needed_message">Lypset hyrje në kamera. Kaloni te rregullimet e Android-it, prekni Leje, dhe prekni Lejoje.</string>
<!-- Text for the positive action button to go to Android Settings to grant permissions. -->
<string name="camera_permissions_needed_positive_button_text">Shko te rregullimet</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Hidhe tej</string>
<!-- Text for the banner message to tell users about our auto close feature. -->
<string name="tab_tray_close_tabs_banner_message">Caktoni mbyllje automatike skedash të hapura që nuk janë parë ditën, javën ose muajin e shkuar.</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">Shihni mundësitë</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
<string name="tab_tray_close_tabs_banner_negative_button_text">Hidhe tej</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Skedë e re</string>
@ -146,8 +191,8 @@
<!-- Search Fragment -->
<!-- Button in the search view that lets a user search by scanning a QR code -->
<string name="search_scan_button">Skanoje</string>
<!-- Button in the search view that lets a user search by using a shortcut -->
<string name="search_engines_shortcut_button">Motor Kërkimesh</string>
<!-- Button in the search view that lets a user change their search engine -->
<string name="search_engine_button">Motor kërkimesh</string>
<!-- Button in the search view when shortcuts are displayed that takes a user to the search engine settings -->
<string name="search_shortcuts_engine_settings">Rregullime motorësh kërkimesh</string>
<!-- Header displayed when selecting a shortcut search engine -->
@ -278,9 +323,14 @@
<!-- Preference for open links in third party apps -->
<string name="preferences_open_links_in_apps">Hapi lidhjet në aplikacione</string>
<!-- Preference for open download with an external download manager app -->
<string name="preferences_external_download_manager">Përgjegjës i jashtëm shkarkimesh</string>
<!-- Preference for add_ons -->
<string name="preferences_addons">Shtesa</string>
<!-- Preference for notifications -->
<string name="preferences_notifications">Njoftime</string>
<!-- Account Preferences -->
<!-- Preference for triggering sync -->
<string name="preferences_sync_now">Njëkohësoje tani</string>
@ -505,9 +555,14 @@
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Hiqe</string>
<!-- Text for the menu button to delete a top site from history -->
<string name="delete_from_history">Fshije prej historiku</string>
<!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (Mënyrë Private)</string>
<!-- Button in the current tab tray header in multiselect mode. Saved the selected tabs to a collection when pressed. -->
<string name="tab_tray_save_to_collection">Ruaje</string>
<!-- History -->
<!-- Text for the button to clear all history -->
<string name="history_delete_all">Fshije historikun</string>
@ -546,6 +601,13 @@
<!-- Text shown when no history exists -->
<string name="history_empty_message">Ska historik këtu</string>
<!-- Downloads -->
<!-- Text shown when no download exists -->
<string name="download_empty_message">Ska shkarkime këtu</string>
<!-- History multi select title in app bar
The first parameter is the number of downloads selected -->
<string name="download_multi_select_title">%1$d të përzgjedhura</string>
<!-- Crashes -->
<!-- Title text displayed on the tab crash page. This first parameter is the name of the application (For example: Fenix) -->
<string name="tab_crash_title_2">Na ndjeni. %1$s smund ta ngarkojë atë faqe.</string>
@ -700,10 +762,8 @@
<string name="collections_header">Koleksione</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Menu koleksionesh</string>
<!-- No Open Tabs Message Header -->
<string name="no_collections_header1">Koleksiononi gjërat që kanë rëndësi për ju</string>
<!-- Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Gruponi tok kërkime, sajte dhe skeda të ngjashme, për përdorim të shpejtë më pas.</string>
<string name="no_collections_description2">Koleksiononi gjërat që kanë rëndësi për ju.\nGruponi tok kërkime të ngjashme, sajte, dhe skeda, për përdorim më të shpejtë më pas.</string>
<!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Përzgjidhni Skeda</string>
<!-- Title for the "select collection" step of the collection creator -->
@ -952,9 +1012,10 @@
<string name="onboarding_whats_new_description">Keni pyetje rreth %s-it të rikonceptuar? Doni të dini se çështë ndryshuar?</string>
<!-- text for underlined clickable link that is part of "what's new" onboarding card description that links to an FAQ -->
<string name="onboarding_whats_new_description_linktext">Merrni përgjigje këtu</string>
<!-- text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Përfitoni maksimumin nga %s.</string>
<!-- text for the Firefox account onboarding sign in card header -->
<string name="onboarding_account_sign_in_header">Filloni të njëkohësoni faqerojtës, fjalëkalime, etj, me llogarinë tuaj Firefox.</string>
<!-- Text for the button to learn more about signing in to your Firefox account -->
<string name="onboarding_manual_sign_in_learn_more">Mësoni më tepër</string>
<!-- text for the firefox account onboarding card header when we detect you're already signed in to
another Firefox browser. (The word `Firefox` should not be translated)
The first parameter is the email of the detected user's account -->
@ -1434,9 +1495,7 @@
<string name="saved_login_duplicate">Ka tashmë një palë kredenciale me këtë emër përdoruesi</string>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">Lidhuni me Llogaritë Firefox.</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">Lidhni pajisje tjetër.</string>
<!-- Text displayed asking user to re-authenticate -->
<string name="synced_tabs_reauth">Ju lutemi, ribëni mirëfilltësimin.</string>
@ -1449,6 +1508,9 @@
<!-- Text displayed on a button in the synced tabs screen to link users to sign in when a user is not signed in to Firefox Sync -->
<string name="synced_tabs_sign_in_button">Bëni hyrjen që të sjëkohësoni</string>
<!-- The text displayed when a synced device has no tabs to show in the list of Synced Tabs. -->
<string name="synced_tabs_no_open_tabs">Ska skeda të hapura</string>
<!-- Top Sites -->
<!-- Title text displayed in the dialog when top sites limit is reached. -->
<string name="top_sites_max_limit_title">U mbërrit te kufi sajtesh</string>
@ -1457,13 +1519,15 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">OK, e mora vesh</string>
<!-- DEPRECATED STRINGS -->
<!-- Button in the search view that lets a user search by using a shortcut -->
<string name="search_shortcuts_button">Shkurtore</string>
<!-- DEPRECATED: Header displayed when selecting a shortcut search engine -->
<string name="search_shortcuts_search_with">Kërkoni me</string>
<!-- Header displayed when selecting a shortcut search engine -->
<string name="search_shortcuts_search_with_2">Këtë herë kërko me:</string>
<!-- Preference title for switch preference to show search shortcuts -->
<string name="preferences_show_search_shortcuts">Shfaqni shkurtore kërkimi</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Hiqe</string>
<!-- depcrecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Përfitoni maksimumin nga %s.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Koleksiononi gjërat që kanë rëndësi për ju</string>
<!-- Deprecated: Label to describe what collections are to a new user without any collections -->
<string name="no_collections_description1">Gruponi tok kërkime, sajte dhe skeda të ngjashme, për përdorim të shpejtë më pas.</string>
</resources>

@ -283,6 +283,8 @@
<string name="preferences_toolbar">Verktygsfält</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Hem</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Anpassa</string>
<!-- Preference description for banner about signing in -->
@ -493,6 +495,29 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Stäng</string>
<!-- Option in library for Recently Closed Tabs -->
<string name="library_recently_closed_tabs">Nyligen stängda flikar</string>
<!-- Option in library to open Recently Closed Tabs page -->
<string name="recently_closed_show_full_history">Visa fullständig historik</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">%d flikar</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">%d flik</string>
<!-- Tab Management -->
<!-- Title of preference that allows a user to auto close tabs after a specified amount of time -->
<string name="preferences_close_tabs">Stäng flikar</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">Manuellt</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">Efter en dag</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">Efter en vecka</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">Efter en månad</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Öppna flikar</string>
@ -512,6 +537,8 @@
<string name="tab_tray_menu_item_save">Spara i samling</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Dela alla flikar</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">Nyligen stängda flikar</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Stäng alla flikar</string>
<!-- Shortcut action to open new tab -->
@ -1527,6 +1554,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Ok, jag förstår</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Visa mest besökta webbplatser</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Ta bort</string>
@ -1534,4 +1564,6 @@
The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Få ut det mesta av %s.</string>
<!-- Deprecated: No Open Tabs Message Header -->
<string name="no_collections_header1">Samla de saker som är viktiga för dig</string>
</resources>

@ -289,6 +289,8 @@
<string name="preferences_toolbar">Панель інструментів</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Тема</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Домівка</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Пристосування</string>
<!-- Preference description for banner about signing in -->
@ -499,6 +501,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Закрити</string>
<!-- Option in library for Recently Closed Tabs -->
<string name="library_recently_closed_tabs">Недавно закриті вкладки</string>
<!-- Option in library to open Recently Closed Tabs page -->
<string name="recently_closed_show_full_history">Показати всю історію</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">%d вкладок</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">%d вкладка</string>
<!-- Recently closed tabs screen message when there are no recently closed tabs -->
<string name="recently_closed_empty_message">Немає нещодавно закритих вкладок</string>
<!-- Tab Management -->
<!-- Title of preference that allows a user to auto close tabs after a specified amount of time -->
<string name="preferences_close_tabs">Закрити вкладки</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">Вручну</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">Через день</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">Через тиждень</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">Через місяць</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Відкриті вкладки</string>
@ -518,6 +545,10 @@
<string name="tab_tray_menu_item_save">Зберегти до збірки</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Поділитися всіма вкладками</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">Недавно закриті вкладки</string>
<!-- Text shown in the menu to view tab settings -->
<string name="tab_tray_menu_tab_settings">Налаштування вкладок</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Закрити всі вкладки</string>
<!-- Shortcut action to open new tab -->
@ -1532,6 +1563,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Гаразд, зрозуміло</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">Показати найвідвідуваніші сайти</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Вилучити</string>

@ -83,6 +83,11 @@
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">现在不要</string>
<!-- Open in App "contextual feature recommendation" (CFR) -->
<!-- Text for the info message. 'Firefox' intentionally hardcoded here.-->
<string name="open_in_app_cfr_info_message">您可以将 Firefox 设为链接的打开方式。</string>
<!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">转至设置</string>
<!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">知道了</string>
@ -93,6 +98,8 @@
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">知道了</string>
<!-- Text for the banner message to tell users about our auto close feature. -->
<string name="tab_tray_close_tabs_banner_message">设置自动关闭过去一天、一周或一个月未查看的已打开标签页。</string>
<!-- Text for the positive action button to go to Settings for auto close tabs. -->
<string name="tab_tray_close_tabs_banner_positive_button_text">查看选项</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
@ -326,7 +333,7 @@
<string name="preferences_account_settings">账户设置</string>
<!-- Preference for open links in third party apps -->
<string name="preferences_open_links_in_apps">在应用程序中打开链接</string>
<string name="preferences_open_links_in_apps">用外部应用打开链接</string>
<!-- Preference for open download with an external download manager app -->
<string name="preferences_external_download_manager">外部下载管理器</string>

@ -290,6 +290,8 @@
<string name="preferences_toolbar">工具列</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">佈景主題</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">主畫面</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">自訂</string>
<!-- Preference description for banner about signing in -->
@ -1543,6 +1545,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">好,知道了!</string>
<!-- Label for the show most visited sites preference -->
<string name="top_sites_toggle_top_frecent_sites">顯示最常造訪的網站</string>
<!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">移除</string>

@ -389,6 +389,8 @@
<color name="button_text_color">#312A65</color>
<!-- Search Widget -->
<color name="search_widget_background">@color/white_color</color>
<color name="search_widget_mic_fill_color">#FF000000</color>
<color name="search_widget_text">#737373</color>
<!-- Private Browsing Mode Persistent Notification -->

@ -207,5 +207,4 @@
<string name="pref_key_login_exceptions" translatable="false">pref_key_login_exceptions</string>
<string name="pref_key_show_collections_placeholder_home" translatable="false">pref_key_show_collections_home</string>
<string name="pref_key_temp_review_prompt">pref_key_temp_review_prompt</string>
</resources>

@ -483,6 +483,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Close</string>
<!-- Option in library for Recently Closed Tabs -->
<string name="library_recently_closed_tabs">Recently closed tabs</string>
<!-- Option in library to open Recently Closed Tabs page -->
<string name="recently_closed_show_full_history">Show full history</string>
<!-- Text to show users they have multiple tabs saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tabs">%d tabs</string>
<!-- Text to show users they have one tab saved in the Recently Closed Tabs section of history.
%d is a placeholder for the number of tabs selected. -->
<string name="recently_closed_tab">%d tab</string>
<!-- Recently closed tabs screen message when there are no recently closed tabs -->
<string name="recently_closed_empty_message">No recently closed tabs here</string>
<!-- Tab Management -->
<!-- Title of preference that allows a user to auto close tabs after a specified amount of time -->
<string name="preferences_close_tabs">Close tabs</string>
<!-- Option for auto closing tabs that will never auto close tabs, always allows user to manually close tabs -->
<string name="close_tabs_manually">Manually</string>
<!-- Option for auto closing tabs that will auto close tabs after one day -->
<string name="close_tabs_after_one_day">After one day</string>
<!-- Option for auto closing tabs that will auto close tabs after one week -->
<string name="close_tabs_after_one_week">After one week</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">After one month</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Open tabs</string>
@ -502,6 +527,10 @@
<string name="tab_tray_menu_item_save">Save to collection</string>
<!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Share all tabs</string>
<!-- Text shown in the menu to view recently closed tabs -->
<string name="tab_tray_menu_recently_closed">Recently closed tabs</string>
<!-- Text shown in the menu to view tab settings -->
<string name="tab_tray_menu_tab_settings">Tab settings</string>
<!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Close all tabs</string>
<!-- Shortcut action to open new tab -->

@ -19,6 +19,4 @@
android:key="@string/pref_key_synced_tabs_tabs_tray"
android:title="@string/preferences_debug_synced_tabs_tabs_tray"
app:iconSpaceReserved="false" />
<Preference android:title="Prompt for review"
android:key="@string/pref_key_temp_review_prompt" />
</PreferenceScreen>

@ -2,9 +2,8 @@
* 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.components.toolbar
package org.mozilla.fenix.browser.browsingmode
sealed class TabCounterMenuItem {
object CloseTab : TabCounterMenuItem()
class NewTab(val isPrivate: Boolean) : TabCounterMenuItem()
}
data class SimpleBrowsingModeManager(
override var mode: BrowsingMode
) : BrowsingModeManager

@ -0,0 +1,63 @@
/* 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.components.settings
import android.content.SharedPreferences
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.verify
import mozilla.components.support.ktx.android.content.PreferencesHolder
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
class CounterPreferenceTest {
@MockK private lateinit var prefs: SharedPreferences
@MockK private lateinit var editor: SharedPreferences.Editor
@Before
fun setup() {
MockKAnnotations.init(this)
every { prefs.getInt("key", 0) } returns 0
every { prefs.edit() } returns editor
every { editor.putInt("key", any()) } returns editor
every { editor.apply() } just Runs
}
@Test
fun `update value after increment`() {
val holder = CounterHolder()
assertEquals(0, holder.property.value)
holder.property.increment()
verify { editor.putInt("key", 1) }
}
@Test
fun `check if value is under max count`() {
val holder = CounterHolder(maxCount = 2)
every { prefs.getInt("key", 0) } returns 0
assertEquals(0, holder.property.value)
assertTrue(holder.property.underMaxCount())
every { prefs.getInt("key", 0) } returns 2
assertEquals(2, holder.property.value)
assertFalse(holder.property.underMaxCount())
}
private inner class CounterHolder(maxCount: Int = -1) : PreferencesHolder {
override val preferences = prefs
val property = counterPreference("key", maxCount)
}
}

@ -0,0 +1,67 @@
/* 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.components.settings
import android.content.SharedPreferences
import io.mockk.Called
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.just
import io.mockk.verify
import mozilla.components.support.ktx.android.content.PreferencesHolder
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
class FeatureFlagPreferenceTest {
@MockK private lateinit var prefs: SharedPreferences
@MockK private lateinit var editor: SharedPreferences.Editor
@Before
fun setup() {
MockKAnnotations.init(this)
every { prefs.getBoolean("key", false) } returns true
every { prefs.edit() } returns editor
every { editor.putBoolean("key", any()) } returns editor
every { editor.apply() } just Runs
}
@Test
fun `acts like boolean preference if feature flag is true`() {
val holder = FeatureFlagHolder(featureFlag = true)
assertTrue(holder.property)
verify { prefs.getBoolean("key", false) }
holder.property = false
verify { editor.putBoolean("key", false) }
}
@Test
fun `no-op if feature flag is false`() {
val holder = FeatureFlagHolder(featureFlag = false)
assertFalse(holder.property)
holder.property = true
holder.property = false
verify { prefs wasNot Called }
verify { editor wasNot Called }
}
private inner class FeatureFlagHolder(featureFlag: Boolean) : PreferencesHolder {
override val preferences = prefs
var property by featureFlagPreference(
"key",
default = false,
featureFlag = featureFlag
)
}
}

@ -1,5 +1,7 @@
package org.mozilla.fenix.components.toolbar
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.mockk
import io.mockk.verify
import org.junit.Before
@ -7,14 +9,16 @@ import org.junit.Test
class BrowserInteractorTest {
lateinit var browserToolbarController: BrowserToolbarController
@RelaxedMockK lateinit var browserToolbarController: BrowserToolbarController
@RelaxedMockK lateinit var browserToolbarMenuController: BrowserToolbarMenuController
lateinit var interactor: BrowserInteractor
@Before
fun setup() {
browserToolbarController = mockk(relaxed = true)
MockKAnnotations.init(this)
interactor = BrowserInteractor(
browserToolbarController
browserToolbarController,
browserToolbarMenuController
)
}
@ -26,7 +30,7 @@ class BrowserInteractorTest {
@Test
fun onTabCounterMenuItemTapped() {
val item: TabCounterMenuItem = mockk()
val item: TabCounterMenu.Item = mockk()
interactor.onTabCounterMenuItemTapped(item)
verify { browserToolbarController.handleTabCounterItemInteraction(item) }
@ -60,6 +64,6 @@ class BrowserInteractorTest {
interactor.onBrowserToolbarMenuItemTapped(item)
verify { browserToolbarController.handleToolbarItemInteraction(item) }
verify { browserToolbarMenuController.handleToolbarItemInteraction(item) }
}
}

@ -4,9 +4,8 @@
package org.mozilla.fenix.components.toolbar
import android.content.Intent
import androidx.navigation.NavController
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.navigation.NavOptions
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
@ -14,149 +13,101 @@ import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.unmockkStatic
import io.mockk.verify
import io.mockk.verifyOrder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.ReaderState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionFeature
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.feature.top.sites.TopSitesUseCases
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator
import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
import org.mozilla.fenix.browser.browsingmode.SimpleBrowsingModeManager
import org.mozilla.fenix.browser.readermode.ReaderModeController
import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.components.Analytics
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.directionsEq
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(FenixRobolectricTestRunner::class)
class DefaultBrowserToolbarControllerTest {
@get:Rule
val coroutinesTestRule = MainCoroutineRule()
@MockK private lateinit var swipeRefreshLayout: SwipeRefreshLayout
@RelaxedMockK private lateinit var activity: HomeActivity
@RelaxedMockK private lateinit var analytics: Analytics
@RelaxedMockK private lateinit var navController: NavController
@RelaxedMockK private lateinit var findInPageLauncher: () -> Unit
@RelaxedMockK private lateinit var bookmarkTapped: (Session) -> Unit
@MockK(relaxUnitFun = true) private lateinit var navController: NavController
@RelaxedMockK private lateinit var onTabCounterClicked: () -> Unit
@RelaxedMockK private lateinit var onCloseTab: (Session) -> Unit
@RelaxedMockK private lateinit var sessionManager: SessionManager
@RelaxedMockK private lateinit var engineView: EngineView
@RelaxedMockK private lateinit var currentSession: Session
@RelaxedMockK private lateinit var openInFenixIntent: Intent
@MockK(relaxUnitFun = true) private lateinit var engineView: EngineView
@MockK private lateinit var currentSession: Session
@RelaxedMockK private lateinit var metrics: MetricController
@RelaxedMockK private lateinit var searchUseCases: SearchUseCases
@RelaxedMockK private lateinit var sessionUseCases: SessionUseCases
@RelaxedMockK private lateinit var browserAnimator: BrowserAnimator
@RelaxedMockK private lateinit var snackbar: FenixSnackbar
@RelaxedMockK private lateinit var tabCollectionStorage: TabCollectionStorage
@RelaxedMockK private lateinit var topSitesUseCase: TopSitesUseCases
@RelaxedMockK private lateinit var readerModeController: ReaderModeController
@RelaxedMockK private lateinit var sessionFeatureWrapper: ViewBoundFeatureWrapper<SessionFeature>
@RelaxedMockK private lateinit var sessionFeature: SessionFeature
private val store: BrowserStore = BrowserStore(initialState = BrowserState(
listOf(
createTab("https://www.mozilla.org", id = "reader-inactive-tab"),
createTab("https://www.mozilla.org", id = "reader-active-tab", readerState = ReaderState(active = true))
))
)
@Before
fun setUp() {
MockKAnnotations.init(this)
mockkStatic(
"org.mozilla.fenix.settings.deletebrowsingdata.DeleteAndQuitKt"
)
every { deleteAndQuit(any(), any(), any()) } just Runs
mockkObject(FenixSnackbar.Companion)
every { FenixSnackbar.make(any(), any(), any(), any()) } returns snackbar
every { activity.components.analytics } returns analytics
every { analytics.metrics } returns metrics
every { activity.components.useCases.sessionUseCases } returns sessionUseCases
every { activity.components.useCases.searchUseCases } returns searchUseCases
every { activity.components.useCases.topSitesUseCase } returns topSitesUseCase
every { activity.components.core.sessionManager } returns sessionManager
every { activity.components.core.store } returns store
every { sessionManager.selectedSession } returns currentSession
every { sessionFeatureWrapper.get() } returns sessionFeature
every { navController.currentDestination } returns mockk {
every { id } returns R.id.browserFragment
}
every { currentSession.id } returns "1"
every { currentSession.private } returns false
every { currentSession.searchTerms = any() } just Runs
val onComplete = slot<() -> Unit>()
every { browserAnimator.captureEngineViewAndDrawStatically(capture(onComplete)) } answers { onComplete.captured.invoke() }
}
@After
fun tearDown() {
unmockkStatic("org.mozilla.fenix.settings.deletebrowsingdata.DeleteAndQuitKt")
@Test
fun handleBrowserToolbarPaste() {
val pastedText = "Mozilla"
val controller = createController(useNewSearchExperience = false)
controller.handleToolbarPaste(pastedText)
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
sessionId = "1",
pastedText = pastedText
)
verify { navController.navigate(directions, any<NavOptions>()) }
}
@Test
fun handleBrowserToolbarPaste() = runBlockingTest {
every { currentSession.id } returns "1"
fun handleBrowserToolbarPaste_useNewSearchExperience() {
val pastedText = "Mozilla"
val controller = createController(scope = this)
val controller = createController(useNewSearchExperience = true)
controller.handleToolbarPaste(pastedText)
verify {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
sessionId = "1",
pastedText = pastedText
)
navController.nav(R.id.browserFragment, directions)
}
val directions = BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = "1",
pastedText = pastedText
)
verify { navController.navigate(directions, any<NavOptions>()) }
}
@Test
fun handleBrowserToolbarPasteAndGoSearch() = runBlockingTest {
fun handleBrowserToolbarPasteAndGoSearch() {
val pastedText = "Mozilla"
val controller = createController(scope = this)
val controller = createController()
controller.handleToolbarPasteAndGo(pastedText)
verifyOrder {
currentSession.searchTerms = "Mozilla"
@ -165,10 +116,10 @@ class DefaultBrowserToolbarControllerTest {
}
@Test
fun handleBrowserToolbarPasteAndGoUrl() = runBlockingTest {
fun handleBrowserToolbarPasteAndGoUrl() {
val pastedText = "https://mozilla.org"
val controller = createController(scope = this)
val controller = createController()
controller.handleToolbarPasteAndGo(pastedText)
verifyOrder {
currentSession.searchTerms = ""
@ -177,391 +128,59 @@ class DefaultBrowserToolbarControllerTest {
}
@Test
fun handleTabCounterClick() = runBlockingTest {
val controller = createController(scope = this)
fun handleTabCounterClick() {
val controller = createController()
controller.handleTabCounterClick()
verify { onTabCounterClicked() }
}
@Test
fun `handle reader mode enabled`() = runBlockingTest {
val controller = createController(scope = this)
fun `handle reader mode enabled`() {
val controller = createController()
controller.handleReaderModePressed(enabled = true)
verify { readerModeController.showReaderView() }
}
@Test
fun `handle reader mode disabled`() = runBlockingTest {
val controller = createController(scope = this)
fun `handle reader mode disabled`() {
val controller = createController()
controller.handleReaderModePressed(enabled = false)
verify { readerModeController.hideReaderView() }
}
@Test
fun handleToolbarClick() = runBlockingTest {
every { currentSession.id } returns "1"
val controller = createController(scope = this)
fun handleToolbarClick() {
val controller = createController(useNewSearchExperience = false)
controller.handleToolbarClick()
verify { metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) }
verify {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
sessionId = "1"
)
navController.nav(R.id.browserFragment, directions)
}
}
@Test
fun handleToolbarBackPress() = runBlockingTest {
val item = ToolbarMenu.Item.Back(false)
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BACK)) }
verify { sessionUseCases.goBack(currentSession) }
}
@Test
fun handleToolbarBackLongPress() = runBlockingTest {
val item = ToolbarMenu.Item.Back(true)
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BACK)) }
verify { navController.navigate(R.id.action_global_tabHistoryDialogFragment) }
}
@Test
fun handleToolbarForwardPress() = runBlockingTest {
val item = ToolbarMenu.Item.Forward(false)
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.FORWARD)) }
verify { sessionUseCases.goForward(currentSession) }
}
@Test
fun handleToolbarForwardLongPress() = runBlockingTest {
val item = ToolbarMenu.Item.Forward(true)
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.FORWARD)) }
verify { navController.navigate(R.id.action_global_tabHistoryDialogFragment) }
}
@Test
fun handleToolbarReloadPress() = runBlockingTest {
val item = ToolbarMenu.Item.Reload(false)
every { activity.components.useCases.sessionUseCases } returns sessionUseCases
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.RELOAD)) }
verify { sessionUseCases.reload(currentSession) }
}
@Test
fun handleToolbarReloadLongPress() = runBlockingTest {
val item = ToolbarMenu.Item.Reload(true)
every { activity.components.useCases.sessionUseCases } returns sessionUseCases
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.RELOAD)) }
verify {
sessionUseCases.reload(
currentSession,
EngineSession.LoadUrlFlags.select(EngineSession.LoadUrlFlags.BYPASS_CACHE)
)
}
}
@Test
fun handleToolbarStopPress() = runBlockingTest {
val item = ToolbarMenu.Item.Stop
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.STOP)) }
verify { sessionUseCases.stopLoading(currentSession) }
}
@Test
fun handleToolbarSettingsPress() = runBlockingTest {
val item = ToolbarMenu.Item.Settings
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SETTINGS)) }
verify {
val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
navController.nav(R.id.browserFragment, directions)
}
}
@Test
fun handleToolbarBookmarkPress() = runBlockingTest {
val item = ToolbarMenu.Item.Bookmark
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARK)) }
verify { bookmarkTapped(currentSession) }
}
@Test
fun handleToolbarBookmarksPress() = runBlockingTest {
val item = ToolbarMenu.Item.Bookmarks
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARKS)) }
verify {
val directions = BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id)
navController.nav(R.id.browserFragment, directions)
}
}
@Test
fun handleToolbarHistoryPress() = runBlockingTest {
val item = ToolbarMenu.Item.History
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.HISTORY)) }
verify {
val directions = BrowserFragmentDirections.actionGlobalHistoryFragment()
navController.nav(R.id.browserFragment, directions)
}
}
@Test
fun handleToolbarRequestDesktopOnPress() = runBlockingTest {
val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase =
mockk(relaxed = true)
val item = ToolbarMenu.Item.RequestDesktop(true)
every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON)) }
verify {
requestDesktopSiteUseCase.invoke(
true,
currentSession
)
}
}
@Test
fun handleToolbarRequestDesktopOffPress() = runBlockingTest {
val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase =
mockk(relaxed = true)
val item = ToolbarMenu.Item.RequestDesktop(false)
every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF)) }
verify {
requestDesktopSiteUseCase.invoke(
false,
currentSession
)
}
}
@Test
fun handleToolbarAddToTopSitesPressed() = runBlockingTest {
val item = ToolbarMenu.Item.AddToTopSites
val addPinnedSiteUseCase: TopSitesUseCases.AddPinnedSiteUseCase = mockk(relaxed = true)
every { topSitesUseCase.addPinnedSites } returns addPinnedSiteUseCase
every {
swipeRefreshLayout.context.getString(R.string.snackbar_added_to_top_sites)
} returns "Added to top sites!"
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { addPinnedSiteUseCase.invoke(currentSession.title, currentSession.url) }
verify { snackbar.setText("Added to top sites!") }
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES)) }
}
@Test
fun handleToolbarAddonsManagerPress() = runBlockingTest {
val item = ToolbarMenu.Item.AddonsManager
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER)) }
}
@Test
fun handleToolbarAddToHomeScreenPress() = runBlockingTest {
val item = ToolbarMenu.Item.AddToHomeScreen
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN)) }
}
@Test
fun handleToolbarSharePress() = runBlockingTest {
val item = ToolbarMenu.Item.Share
every { currentSession.url } returns "https://mozilla.org"
every { currentSession.title } returns "Mozilla"
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SHARE)) }
verify {
navController.navigate(
directionsEq(NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(ShareData(url = "https://mozilla.org", title = "Mozilla")),
showPage = true
))
)
}
}
@Test
fun handleToolbarFindInPagePress() = runBlockingTest {
val item = ToolbarMenu.Item.FindInPage
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { findInPageLauncher() }
verify { metrics.track(Event.FindInPageOpened) }
}
@Test
fun handleToolbarSaveToCollectionPressWhenAtLeastOneCollectionExists() = runBlockingTest {
val item = ToolbarMenu.Item.SaveToCollection
val cachedTabCollections: List<TabCollection> = mockk(relaxed = true)
every { activity.components.useCases.sessionUseCases } returns sessionUseCases
every { activity.components.core.tabCollectionStorage.cachedTabCollections } returns cachedTabCollections
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify {
metrics.track(
Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION)
)
}
verify {
metrics.track(
Event.CollectionSaveButtonPressed(DefaultBrowserToolbarController.TELEMETRY_BROWSER_IDENTIFIER)
)
}
verify {
val directions =
BrowserFragmentDirections.actionGlobalCollectionCreationFragment(
saveCollectionStep = SaveCollectionStep.SelectCollection,
tabIds = arrayOf(currentSession.id),
selectedTabIds = arrayOf(currentSession.id)
)
navController.nav(R.id.browserFragment, directions)
}
}
@Test
fun handleToolbarSaveToCollectionPressWhenNoCollectionsExists() = runBlockingTest {
val item = ToolbarMenu.Item.SaveToCollection
val cachedTabCollectionsEmpty: List<TabCollection> = emptyList()
every { activity.components.useCases.sessionUseCases } returns sessionUseCases
every { activity.components.core.tabCollectionStorage.cachedTabCollections } returns cachedTabCollectionsEmpty
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION)) }
verify {
metrics.track(
Event.CollectionSaveButtonPressed(
DefaultBrowserToolbarController.TELEMETRY_BROWSER_IDENTIFIER
)
)
}
verify {
val directions =
BrowserFragmentDirections.actionGlobalCollectionCreationFragment(
saveCollectionStep = SaveCollectionStep.NameCollection,
tabIds = arrayOf(currentSession.id),
selectedTabIds = arrayOf(currentSession.id)
)
navController.nav(R.id.browserFragment, directions)
}
}
@Test
fun handleToolbarOpenInFenixPress() = runBlockingTest {
val controller = createController(scope = this, customTabSession = currentSession)
val item = ToolbarMenu.Item.OpenInFenix
every { currentSession.customTabConfig } returns mockk()
every { activity.startActivity(any()) } just Runs
controller.handleToolbarItemInteraction(item)
val expected = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
sessionId = "1"
)
verify { sessionFeature.release() }
verify { currentSession.customTabConfig = null }
verify { sessionManager.select(currentSession) }
verify { activity.startActivity(openInFenixIntent) }
verify { activity.finish() }
verify { metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) }
verify { navController.navigate(expected, any<NavOptions>()) }
}
@Test
fun handleToolbarQuitPress() = runBlockingTest {
val item = ToolbarMenu.Item.Quit
val testScope = this
val controller = createController(scope = testScope)
fun handleToolbarClick_useNewSearchExperience() {
val controller = createController(useNewSearchExperience = true)
controller.handleToolbarClick()
controller.handleToolbarItemInteraction(item)
val expected = BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = "1"
)
verify { deleteAndQuit(activity, testScope, null) }
verify { metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) }
verify { navController.navigate(expected, any<NavOptions>()) }
}
@Test
fun handleToolbarCloseTabPressWithLastPrivateSession() = runBlockingTest {
every { currentSession.id } returns "1"
val browsingModeManager = object : BrowsingModeManager {
override var mode = BrowsingMode.Private
}
val item = TabCounterMenuItem.CloseTab
fun handleToolbarCloseTabPressWithLastPrivateSession() {
val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Private)
val item = TabCounterMenu.Item.CloseTab
val sessions = listOf(
mockk<Session> {
every { private } returns true
@ -572,81 +191,77 @@ class DefaultBrowserToolbarControllerTest {
every { sessionManager.sessions } returns sessions
every { activity.browsingModeManager } returns browsingModeManager
val controller = createController(scope = this)
val controller = createController()
controller.handleTabCounterItemInteraction(item)
verify { navController.navigate(BrowserFragmentDirections.actionGlobalHome(sessionToDelete = "1")) }
assertEquals(BrowsingMode.Normal, browsingModeManager.mode)
}
@Test
fun handleToolbarCloseTabPress() = runBlockingTest {
fun handleToolbarCloseTabPress() {
val tabsUseCases: TabsUseCases = mockk(relaxed = true)
val removeTabUseCase: TabsUseCases.RemoveTabUseCase = mockk(relaxed = true)
val item = TabCounterMenuItem.CloseTab
val item = TabCounterMenu.Item.CloseTab
every { sessionManager.sessions } returns emptyList()
every { activity.components.useCases.tabsUseCases } returns tabsUseCases
every { tabsUseCases.removeTab } returns removeTabUseCase
val controller = createController(scope = this)
val controller = createController()
controller.handleTabCounterItemInteraction(item)
verify { removeTabUseCase.invoke(currentSession) }
}
@Test
fun handleToolbarNewTabPress() = runBlockingTest {
val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager(
BrowsingMode.Private,
mockk(relaxed = true)
) {}
val item = TabCounterMenuItem.NewTab(false)
fun handleToolbarNewTabPress() {
val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Private)
val item = TabCounterMenu.Item.NewTab(BrowsingMode.Normal)
every { activity.browsingModeManager } returns browsingModeManager
every { navController.popBackStack(R.id.homeFragment, any()) } returns true
val controller = createController(scope = this)
val controller = createController()
controller.handleTabCounterItemInteraction(item)
assertEquals(BrowsingMode.Normal, activity.browsingModeManager.mode)
assertEquals(BrowsingMode.Normal, browsingModeManager.mode)
verify { navController.popBackStack(R.id.homeFragment, false) }
}
@Test
fun handleToolbarNewPrivateTabPress() = runBlockingTest {
val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager(
BrowsingMode.Normal,
mockk(relaxed = true)
) {}
val item = TabCounterMenuItem.NewTab(true)
fun handleToolbarNewPrivateTabPress() {
val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Normal)
val item = TabCounterMenu.Item.NewTab(BrowsingMode.Private)
every { activity.browsingModeManager } returns browsingModeManager
every { navController.popBackStack(R.id.homeFragment, any()) } returns true
val controller = createController(scope = this)
val controller = createController()
controller.handleTabCounterItemInteraction(item)
assertEquals(BrowsingMode.Private, activity.browsingModeManager.mode)
assertEquals(BrowsingMode.Private, browsingModeManager.mode)
verify { navController.popBackStack(R.id.homeFragment, false) }
}
@Test
fun handleScroll() {
val controller = createController()
controller.handleScroll(10)
verify { engineView.setVerticalClipping(10) }
}
private fun createController(
scope: CoroutineScope,
activity: HomeActivity = this.activity,
customTabSession: Session? = null
customTabSession: Session? = null,
useNewSearchExperience: Boolean = false
) = DefaultBrowserToolbarController(
activity = activity,
navController = navController,
findInPageLauncher = findInPageLauncher,
metrics = metrics,
engineView = engineView,
browserAnimator = browserAnimator,
customTabSession = customTabSession,
openInFenixIntent = openInFenixIntent,
scope = scope,
swipeRefresh = swipeRefreshLayout,
tabCollectionStorage = tabCollectionStorage,
bookmarkTapped = bookmarkTapped,
readerModeController = readerModeController,
sessionManager = sessionManager,
sessionFeature = sessionFeatureWrapper,
useNewSearchExperience = useNewSearchExperience,
onTabCounterClicked = onTabCounterClicked,
onCloseTab = onCloseTab
).apply {
ioScope = scope
}
)
}

@ -0,0 +1,494 @@
/* 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.components.toolbar
import android.content.Intent
import androidx.navigation.NavController
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import io.mockk.MockKAnnotations
import io.mockk.Runs
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.unmockkObject
import io.mockk.unmockkStatic
import io.mockk.verify
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionFeature
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSitesUseCases
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator
import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.readermode.ReaderModeController
import org.mozilla.fenix.collections.SaveCollectionStep
import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.directionsEq
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
import org.mozilla.fenix.utils.Settings
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(FenixRobolectricTestRunner::class)
class DefaultBrowserToolbarMenuControllerTest {
@get:Rule
val coroutinesTestRule = MainCoroutineRule()
@MockK private lateinit var swipeRefreshLayout: SwipeRefreshLayout
@RelaxedMockK private lateinit var activity: HomeActivity
@RelaxedMockK private lateinit var navController: NavController
@RelaxedMockK private lateinit var findInPageLauncher: () -> Unit
@RelaxedMockK private lateinit var bookmarkTapped: (Session) -> Unit
@RelaxedMockK private lateinit var sessionManager: SessionManager
@RelaxedMockK private lateinit var currentSession: Session
@RelaxedMockK private lateinit var openInFenixIntent: Intent
@RelaxedMockK private lateinit var metrics: MetricController
@RelaxedMockK private lateinit var settings: Settings
@RelaxedMockK private lateinit var searchUseCases: SearchUseCases
@RelaxedMockK private lateinit var sessionUseCases: SessionUseCases
@RelaxedMockK private lateinit var browserAnimator: BrowserAnimator
@RelaxedMockK private lateinit var snackbar: FenixSnackbar
@RelaxedMockK private lateinit var tabCollectionStorage: TabCollectionStorage
@RelaxedMockK private lateinit var topSitesUseCase: TopSitesUseCases
@RelaxedMockK private lateinit var readerModeController: ReaderModeController
@MockK private lateinit var sessionFeatureWrapper: ViewBoundFeatureWrapper<SessionFeature>
@RelaxedMockK private lateinit var sessionFeature: SessionFeature
@Before
fun setUp() {
MockKAnnotations.init(this)
mockkStatic(
"org.mozilla.fenix.settings.deletebrowsingdata.DeleteAndQuitKt"
)
every { deleteAndQuit(any(), any(), any()) } just Runs
mockkObject(FenixSnackbar.Companion)
every { FenixSnackbar.make(any(), any(), any(), any()) } returns snackbar
every { activity.components.useCases.sessionUseCases } returns sessionUseCases
every { activity.components.useCases.searchUseCases } returns searchUseCases
every { activity.components.useCases.topSitesUseCase } returns topSitesUseCase
every { sessionManager.selectedSession } returns currentSession
every { sessionFeatureWrapper.get() } returns sessionFeature
every { navController.currentDestination } returns mockk {
every { id } returns R.id.browserFragment
}
every { currentSession.id } returns "1"
val onComplete = slot<() -> Unit>()
every { browserAnimator.captureEngineViewAndDrawStatically(capture(onComplete)) } answers { onComplete.captured.invoke() }
}
@After
fun tearDown() {
unmockkStatic("org.mozilla.fenix.settings.deletebrowsingdata.DeleteAndQuitKt")
unmockkObject(FenixSnackbar.Companion)
}
@Test
fun handleToolbarBackPress() = runBlockingTest {
val item = ToolbarMenu.Item.Back(false)
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BACK)) }
verify { sessionUseCases.goBack(currentSession) }
}
@Test
fun handleToolbarBackLongPress() = runBlockingTest {
val item = ToolbarMenu.Item.Back(true)
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
val directions = BrowserFragmentDirections.actionGlobalTabHistoryDialogFragment()
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BACK)) }
verify { navController.navigate(directions) }
}
@Test
fun handleToolbarForwardPress() = runBlockingTest {
val item = ToolbarMenu.Item.Forward(false)
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.FORWARD)) }
verify { sessionUseCases.goForward(currentSession) }
}
@Test
fun handleToolbarForwardLongPress() = runBlockingTest {
val item = ToolbarMenu.Item.Forward(true)
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
val directions = BrowserFragmentDirections.actionGlobalTabHistoryDialogFragment()
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.FORWARD)) }
verify { navController.navigate(directions) }
}
@Test
fun handleToolbarReloadPress() = runBlockingTest {
val item = ToolbarMenu.Item.Reload(false)
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.RELOAD)) }
verify { sessionUseCases.reload(currentSession) }
}
@Test
fun handleToolbarReloadLongPress() = runBlockingTest {
val item = ToolbarMenu.Item.Reload(true)
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.RELOAD)) }
verify {
sessionUseCases.reload(
currentSession,
EngineSession.LoadUrlFlags.select(EngineSession.LoadUrlFlags.BYPASS_CACHE)
)
}
}
@Test
fun handleToolbarStopPress() = runBlockingTest {
val item = ToolbarMenu.Item.Stop
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.STOP)) }
verify { sessionUseCases.stopLoading(currentSession) }
}
@Test
fun handleToolbarSettingsPress() = runBlockingTest {
val item = ToolbarMenu.Item.Settings
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
val directions = BrowserFragmentDirections.actionBrowserFragmentToSettingsFragment()
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SETTINGS)) }
verify { navController.navigate(directions, null) }
}
@Test
fun handleToolbarBookmarkPress() = runBlockingTest {
val item = ToolbarMenu.Item.Bookmark
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARK)) }
verify { bookmarkTapped(currentSession) }
}
@Test
fun handleToolbarBookmarksPress() = runBlockingTest {
val item = ToolbarMenu.Item.Bookmarks
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
val directions = BrowserFragmentDirections.actionGlobalBookmarkFragment(BookmarkRoot.Mobile.id)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.BOOKMARKS)) }
verify { navController.navigate(directions, null) }
}
@Test
fun handleToolbarHistoryPress() = runBlockingTest {
val item = ToolbarMenu.Item.History
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
val directions = BrowserFragmentDirections.actionGlobalHistoryFragment()
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.HISTORY)) }
verify { navController.navigate(directions, null) }
}
@Test
fun handleToolbarRequestDesktopOnPress() = runBlockingTest {
val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase =
mockk(relaxed = true)
val item = ToolbarMenu.Item.RequestDesktop(true)
every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_ON)) }
verify {
requestDesktopSiteUseCase.invoke(
true,
currentSession
)
}
}
@Test
fun handleToolbarRequestDesktopOffPress() = runBlockingTest {
val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase =
mockk(relaxed = true)
val item = ToolbarMenu.Item.RequestDesktop(false)
every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.DESKTOP_VIEW_OFF)) }
verify {
requestDesktopSiteUseCase.invoke(
false,
currentSession
)
}
}
@Test
fun handleToolbarAddToTopSitesPressed() = runBlockingTest {
val item = ToolbarMenu.Item.AddToTopSites
val addPinnedSiteUseCase: TopSitesUseCases.AddPinnedSiteUseCase = mockk(relaxed = true)
every { topSitesUseCase.addPinnedSites } returns addPinnedSiteUseCase
every {
swipeRefreshLayout.context.getString(R.string.snackbar_added_to_top_sites)
} returns "Added to top sites!"
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { addPinnedSiteUseCase.invoke(currentSession.title, currentSession.url) }
verify { snackbar.setText("Added to top sites!") }
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADD_TO_TOP_SITES)) }
}
@Test
fun handleToolbarAddonsManagerPress() = runBlockingTest {
val item = ToolbarMenu.Item.AddonsManager
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADDONS_MANAGER)) }
}
@Test
fun handleToolbarAddToHomeScreenPress() = runBlockingTest {
val item = ToolbarMenu.Item.AddToHomeScreen
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN)) }
}
@Test
fun handleToolbarSharePress() = runBlockingTest {
val item = ToolbarMenu.Item.Share
every { currentSession.url } returns "https://mozilla.org"
every { currentSession.title } returns "Mozilla"
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SHARE)) }
verify {
navController.navigate(
directionsEq(NavGraphDirections.actionGlobalShareFragment(
data = arrayOf(ShareData(url = "https://mozilla.org", title = "Mozilla")),
showPage = true
))
)
}
}
@Test
fun handleToolbarFindInPagePress() = runBlockingTest {
val item = ToolbarMenu.Item.FindInPage
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { findInPageLauncher() }
verify { metrics.track(Event.FindInPageOpened) }
}
@Test
fun handleToolbarSaveToCollectionPressWhenAtLeastOneCollectionExists() = runBlockingTest {
val item = ToolbarMenu.Item.SaveToCollection
val cachedTabCollections: List<TabCollection> = mockk(relaxed = true)
every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollections
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify {
metrics.track(
Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION)
)
}
verify {
metrics.track(
Event.CollectionSaveButtonPressed(DefaultBrowserToolbarController.TELEMETRY_BROWSER_IDENTIFIER)
)
}
val directions = BrowserFragmentDirections.actionGlobalCollectionCreationFragment(
saveCollectionStep = SaveCollectionStep.SelectCollection,
tabIds = arrayOf(currentSession.id),
selectedTabIds = arrayOf(currentSession.id)
)
verify { navController.navigate(directionsEq(directions), null) }
}
@Test
fun handleToolbarSaveToCollectionPressWhenNoCollectionsExists() = runBlockingTest {
val item = ToolbarMenu.Item.SaveToCollection
val cachedTabCollectionsEmpty: List<TabCollection> = emptyList()
every { tabCollectionStorage.cachedTabCollections } returns cachedTabCollectionsEmpty
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION)) }
verify {
metrics.track(
Event.CollectionSaveButtonPressed(
DefaultBrowserToolbarController.TELEMETRY_BROWSER_IDENTIFIER
)
)
}
val directions = BrowserFragmentDirections.actionGlobalCollectionCreationFragment(
saveCollectionStep = SaveCollectionStep.NameCollection,
tabIds = arrayOf(currentSession.id),
selectedTabIds = arrayOf(currentSession.id)
)
verify { navController.navigate(directionsEq(directions), null) }
}
@Test
fun handleToolbarOpenInFenixPress() = runBlockingTest {
val controller = createController(scope = this, customTabSession = currentSession)
val item = ToolbarMenu.Item.OpenInFenix
every { currentSession.customTabConfig } returns mockk()
every { activity.startActivity(any()) } just Runs
controller.handleToolbarItemInteraction(item)
verify { sessionFeature.release() }
verify { currentSession.customTabConfig = null }
verify { sessionManager.select(currentSession) }
verify { activity.startActivity(openInFenixIntent) }
verify { activity.finish() }
}
@Test
fun handleToolbarQuitPress() = runBlockingTest {
val item = ToolbarMenu.Item.Quit
val testScope = this
val controller = createController(scope = testScope)
controller.handleToolbarItemInteraction(item)
verify { deleteAndQuit(activity, testScope, null) }
}
@Test
fun handleToolbarReaderModeAppearancePress() = runBlockingTest {
val item = ToolbarMenu.Item.ReaderModeAppearance
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { readerModeController.showControls() }
verify { metrics.track(Event.ReaderModeAppearanceOpened) }
}
@Test
fun handleToolbarOpenInAppPress() = runBlockingTest {
val item = ToolbarMenu.Item.OpenInApp
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
verify { settings.openInAppOpened = true }
}
private fun createController(
scope: CoroutineScope,
activity: HomeActivity = this.activity,
customTabSession: Session? = null
) = DefaultBrowserToolbarMenuController(
activity = activity,
navController = navController,
metrics = metrics,
settings = settings,
findInPageLauncher = findInPageLauncher,
browserAnimator = browserAnimator,
customTabSession = customTabSession,
openInFenixIntent = openInFenixIntent,
scope = scope,
swipeRefresh = swipeRefreshLayout,
tabCollectionStorage = tabCollectionStorage,
bookmarkTapped = bookmarkTapped,
readerModeController = readerModeController,
sessionManager = sessionManager,
sessionFeature = sessionFeatureWrapper
).apply {
ioScope = scope
}
}

@ -0,0 +1,95 @@
/* 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.components.toolbar
import android.content.Context
import androidx.appcompat.view.ContextThemeWrapper
import io.mockk.mockk
import io.mockk.verifyAll
import mozilla.components.concept.menu.candidate.DividerMenuCandidate
import mozilla.components.concept.menu.candidate.DrawableMenuIcon
import mozilla.components.concept.menu.candidate.TextMenuCandidate
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class TabCounterMenuTest {
private lateinit var context: Context
private lateinit var metrics: MetricController
private lateinit var onItemTapped: (TabCounterMenu.Item) -> Unit
private lateinit var menu: TabCounterMenu
@Before
fun setup() {
context = ContextThemeWrapper(testContext, R.style.NormalTheme)
metrics = mockk(relaxed = true)
onItemTapped = mockk(relaxed = true)
menu = TabCounterMenu(context, metrics, onItemTapped)
}
@Test
fun `all items use primary text color styling`() {
val items = menu.menuItems(showOnly = null)
assertEquals(4, items.size)
val textItems = items.mapNotNull { it as? TextMenuCandidate }
assertEquals(3, textItems.size)
val primaryTextColor = context.getColor(R.color.primary_text_normal_theme)
for (item in textItems) {
assertEquals(primaryTextColor, item.textStyle.color)
assertEquals(primaryTextColor, (item.start as DrawableMenuIcon).tint)
}
}
@Test
fun `return only the new tab item`() {
val items = menu.menuItems(showOnly = BrowsingMode.Normal)
assertEquals(1, items.size)
val item = items[0] as TextMenuCandidate
assertEquals("New tab", item.text)
item.onClick()
verifyAll {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_TAB))
onItemTapped(TabCounterMenu.Item.NewTab(BrowsingMode.Normal))
}
}
@Test
fun `return only the new private tab item`() {
val items = menu.menuItems(showOnly = BrowsingMode.Private)
assertEquals(1, items.size)
val item = items[0] as TextMenuCandidate
assertEquals("New private tab", item.text)
item.onClick()
verifyAll {
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB))
onItemTapped(TabCounterMenu.Item.NewTab(BrowsingMode.Private))
}
}
@Test
fun `return two new tab items and a close button`() {
val (newTab, newPrivateTab, divider, closeTab) = menu.menuItems(showOnly = null)
assertEquals("New tab", (newTab as TextMenuCandidate).text)
assertEquals("New private tab", (newPrivateTab as TextMenuCandidate).text)
assertEquals("Close tab", (closeTab as TextMenuCandidate).text)
assertEquals(DividerMenuCandidate(), divider)
}
}

@ -4,13 +4,15 @@
package org.mozilla.fenix.settings.deletebrowsingdata
import io.mockk.coVerify
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope.coroutineContext
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.concept.engine.Engine
import mozilla.components.concept.storage.HistoryStorage
import mozilla.components.feature.tabs.TabsUseCases
@ -29,6 +31,7 @@ class DefaultDeleteBrowsingDataControllerTest {
private var removeAllTabs: TabsUseCases.RemoveAllTabsUseCase = mockk(relaxed = true)
private var historyStorage: HistoryStorage = mockk(relaxed = true)
private var permissionStorage: PermissionStorage = mockk(relaxed = true)
private var iconsStorage: BrowserIcons = mockk(relaxed = true)
private val engine: Engine = mockk(relaxed = true)
private lateinit var controller: DefaultDeleteBrowsingDataController
@ -38,6 +41,7 @@ class DefaultDeleteBrowsingDataControllerTest {
removeAllTabs = removeAllTabs,
historyStorage = historyStorage,
permissionStorage = permissionStorage,
iconsStorage = iconsStorage,
engine = engine,
coroutineContext = coroutineContext
)
@ -55,12 +59,14 @@ class DefaultDeleteBrowsingDataControllerTest {
@Test
fun deleteBrowsingData() = runBlockingTest {
controller = spyk(controller)
controller.deleteBrowsingData()
verify {
coVerify {
engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES))
historyStorage.deleteEverything()
iconsStorage.clear()
}
verify { launch { historyStorage.deleteEverything() } }
}
@Test

@ -6,6 +6,7 @@
package org.mozilla.fenix.settings.deletebrowsingdata
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
@ -13,6 +14,7 @@ import io.mockk.verifyOrder
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.browser.storage.sync.PlacesHistoryStorage
import mozilla.components.concept.engine.Engine
import mozilla.components.feature.tabs.TabsUseCases
@ -41,6 +43,7 @@ class DeleteAndQuitTest {
private val tabUseCases: TabsUseCases = mockk(relaxed = true)
private val historyStorage: PlacesHistoryStorage = mockk(relaxed = true)
private val permissionStorage: PermissionStorage = mockk(relaxed = true)
private val iconsStorage: BrowserIcons = mockk()
private val engine: Engine = mockk(relaxed = true)
private val removeAllTabsUseCases: TabsUseCases.RemoveAllTabsUseCase = mockk(relaxed = true)
private val snackbar = mockk<FenixSnackbar>(relaxed = true)
@ -53,6 +56,7 @@ class DeleteAndQuitTest {
every { tabUseCases.removeAllTabs } returns removeAllTabsUseCases
every { activity.components.core.engine } returns engine
every { activity.components.settings } returns settings
every { activity.components.core.icons } returns iconsStorage
}
@Test
@ -69,8 +73,6 @@ class DeleteAndQuitTest {
}
verify(exactly = 0) {
historyStorage
engine.clearData(
Engine.BrowsingData.select(
Engine.BrowsingData.COOKIES
@ -81,6 +83,11 @@ class DeleteAndQuitTest {
engine.clearData(Engine.BrowsingData.allCaches())
}
coVerify(exactly = 0) {
historyStorage.deleteEverything()
iconsStorage.clear()
}
}
@Ignore("Intermittently failing; will be fixed with #5406.")
@ -115,9 +122,12 @@ class DeleteAndQuitTest {
engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES))
historyStorage
activity.finish()
}
coVerify {
historyStorage.deleteEverything()
iconsStorage.clear()
}
}
}

@ -128,13 +128,13 @@ class SettingsTest {
fun showLoginsDialogWarningSync() {
// When just created
// Then
assertEquals(0, settings.loginsSecureWarningSyncCount)
assertEquals(0, settings.loginsSecureWarningSyncCount.value)
// When
settings.incrementShowLoginsSecureWarningSyncCount()
// Then
assertEquals(1, settings.loginsSecureWarningSyncCount)
assertEquals(1, settings.loginsSecureWarningSyncCount.value)
}
@Test
@ -154,13 +154,13 @@ class SettingsTest {
fun showLoginsDialogWarning() {
// When just created
// Then
assertEquals(0, settings.loginsSecureWarningCount)
assertEquals(0, settings.loginsSecureWarningCount.value)
// When
settings.incrementShowLoginsSecureWarningCount()
// Then
assertEquals(1, settings.loginsSecureWarningCount)
assertEquals(1, settings.loginsSecureWarningCount.value)
}
@Test

@ -3,5 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
object AndroidComponents {
const val VERSION = "57.0.20200826190111"
const val VERSION = "57.0.20200829130559"
}

@ -43,7 +43,6 @@ object Versions {
const val google_ads_id_version = "16.0.0"
const val google_play_store_version = "1.8.0"
const val google_play_core_ktx_version = "1.8.1"
const val airbnb_lottie = "3.4.0"
}
@ -214,7 +213,6 @@ object Deps {
// Required for in-app reviews
const val google_play_store = "com.google.android.play:core:${Versions.google_play_store_version}"
const val google_play_core_ktx = "com.google.android.play:core-ktx:${Versions.google_play_core_ktx_version}"
const val lottie = "com.airbnb.android:lottie:${Versions.airbnb_lottie}"

Loading…
Cancel
Save