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_ads_id // Required for the Google Advertising ID
implementation Deps.google_play_store // Required for in-app reviews implementation Deps.google_play_store // Required for in-app reviews
implementation Deps.google_play_core_ktx // Required for in-app reviews
androidTestImplementation Deps.uiautomator androidTestImplementation Deps.uiautomator
// Removed pending AndroidX fixes // Removed pending AndroidX fixes

@ -135,6 +135,8 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
} }
initializeWebExtensionSupport() initializeWebExtensionSupport()
restoreDownloads()
// Just to make sure it is impossible for any application-services pieces // Just to make sure it is impossible for any application-services pieces
// to invoke parts of itself that require complete megazord initialization // to invoke parts of itself that require complete megazord initialization
// before that process completes, we wait here, if necessary. // before that process completes, we wait here, if necessary.
@ -161,6 +163,12 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
components.appStartupTelemetry.onFenixApplicationOnCreate() components.appStartupTelemetry.onFenixApplicationOnCreate()
} }
private fun restoreDownloads() {
if (FeatureFlags.viewDownloads) {
components.useCases.downloadUseCases.restoreDownloads()
}
}
private fun initVisualCompletenessQueueAndQueueTasks() { private fun initVisualCompletenessQueueAndQueueTasks() {
val queue = components.performance.visualCompletenessQueue.queue 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.session.behavior.EngineViewBottomBehavior
import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissionsFeature import mozilla.components.feature.sitepermissions.SitePermissionsFeature
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.lib.state.ext.flowScoped import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.service.sync.logins.DefaultLoginValidationDelegate import mozilla.components.service.sync.logins.DefaultLoginValidationDelegate
import mozilla.components.support.base.feature.PermissionsFeature 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.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.readermode.DefaultReaderModeController import org.mozilla.fenix.browser.readermode.DefaultReaderModeController
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.FindInPageIntegration import org.mozilla.fenix.components.FindInPageIntegration
import org.mozilla.fenix.components.StoreProvider 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.BrowserToolbarView
import org.mozilla.fenix.components.toolbar.BrowserToolbarViewInteractor import org.mozilla.fenix.components.toolbar.BrowserToolbarViewInteractor
import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarController 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.SwipeRefreshScrollingViewBehavior
import org.mozilla.fenix.components.toolbar.ToolbarIntegration import org.mozilla.fenix.components.toolbar.ToolbarIntegration
import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.components.toolbar.ToolbarPosition
@ -123,8 +124,10 @@ import java.lang.ref.WeakReference
@Suppress("TooManyFunctions", "LargeClass") @Suppress("TooManyFunctions", "LargeClass")
abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, SessionManager.Observer, abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, SessionManager.Observer,
OnBackLongPressedListener, AccessibilityManager.AccessibilityStateChangeListener { OnBackLongPressedListener, AccessibilityManager.AccessibilityStateChangeListener {
private lateinit var browserFragmentStore: BrowserFragmentStore private lateinit var browserFragmentStore: BrowserFragmentStore
private lateinit var browserAnimator: BrowserAnimator private lateinit var browserAnimator: BrowserAnimator
private lateinit var components: Components
private var _browserInteractor: BrowserToolbarViewInteractor? = null private var _browserInteractor: BrowserToolbarViewInteractor? = null
protected val browserInteractor: BrowserToolbarViewInteractor protected val browserInteractor: BrowserToolbarViewInteractor
@ -170,8 +173,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
require(arguments != null) customTabSessionId = requireArguments().getString(EXTRA_SESSION_ID)
customTabSessionId = arguments?.getString(EXTRA_SESSION_ID)
// Diagnostic breadcrumb for "Display already aquired" crash: // Diagnostic breadcrumb for "Display already aquired" crash:
// https://github.com/mozilla-mobile/android-components/issues/7960 // https://github.com/mozilla-mobile/android-components/issues/7960
@ -193,6 +195,8 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
) )
} }
components = requireComponents
return view return view
} }
@ -207,6 +211,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
val context = requireContext() val context = requireContext()
val sessionManager = context.components.core.sessionManager val sessionManager = context.components.core.sessionManager
val store = context.components.core.store val store = context.components.core.store
val activity = requireActivity() as HomeActivity
val toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height) val toolbarHeight = resources.getDimensionPixelSize(R.dimen.browser_toolbar_height)
@ -215,6 +220,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
engineView = WeakReference(engineView), engineView = WeakReference(engineView),
swipeRefresh = WeakReference(swipeRefresh), swipeRefresh = WeakReference(swipeRefresh),
viewLifecycleScope = WeakReference(viewLifecycleOwner.lifecycleScope), viewLifecycleScope = WeakReference(viewLifecycleOwner.lifecycleScope),
settings = context.components.settings,
firstContentfulHappened = ::didFirstContentfulHappen firstContentfulHappened = ::didFirstContentfulHappen
).apply { ).apply {
beginAnimateInIfNecessary() beginAnimateInIfNecessary()
@ -226,25 +232,20 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
putExtra(HomeActivity.OPEN_TO_BROWSER, true) putExtra(HomeActivity.OPEN_TO_BROWSER, true)
} }
val readerMenuController = DefaultReaderModeController(
readerViewFeature,
view.readerViewControlsBar,
isPrivate = activity.browsingModeManager.mode.isPrivate
)
val browserToolbarController = DefaultBrowserToolbarController( val browserToolbarController = DefaultBrowserToolbarController(
activity = requireActivity() as HomeActivity, activity = activity,
navController = findNavController(), navController = findNavController(),
readerModeController = DefaultReaderModeController( metrics = requireComponents.analytics.metrics,
readerViewFeature, readerModeController = readerMenuController,
view.readerViewControlsBar,
isPrivate = (activity as HomeActivity).browsingModeManager.mode.isPrivate
),
sessionManager = requireComponents.core.sessionManager, sessionManager = requireComponents.core.sessionManager,
sessionFeature = sessionFeature,
findInPageLauncher = { findInPageIntegration.withFeature { it.launch() } },
engineView = engineView, engineView = engineView,
swipeRefresh = swipeRefresh,
browserAnimator = browserAnimator, browserAnimator = browserAnimator,
customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) }, customTabSession = customTabSessionId?.let { sessionManager.findSessionById(it) },
openInFenixIntent = openInFenixIntent,
bookmarkTapped = { viewLifecycleOwner.lifecycleScope.launch { bookmarkTapped(it) } },
scope = viewLifecycleOwner.lifecycleScope,
tabCollectionStorage = requireComponents.core.tabCollectionStorage,
onTabCounterClicked = { onTabCounterClicked = {
thumbnailsFeature.get()?.requestScreenshot() thumbnailsFeature.get()?.requestScreenshot()
findNavController().nav( 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( _browserInteractor = BrowserInteractor(
browserToolbarController = browserToolbarController browserToolbarController,
browserToolbarMenuController
) )
_browserToolbarView = BrowserToolbarView( _browserToolbarView = BrowserToolbarView(
@ -573,9 +592,9 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
context.settings().setSitePermissionSettingListener(viewLifecycleOwner) { context.settings().setSitePermissionSettingListener(viewLifecycleOwner) {
// If the user connects to WIFI while on the BrowserFragment, this will update the // If the user connects to WIFI while on the BrowserFragment, this will update the
// SitePermissionsRules (specifically autoplay) accordingly // SitePermissionsRules (specifically autoplay) accordingly
assignSitePermissionsRules(context) assignSitePermissionsRules()
} }
assignSitePermissionsRules(context) assignSitePermissionsRules()
fullScreenFeature.set( fullScreenFeature.set(
feature = FullScreenFeature( feature = FullScreenFeature(
@ -716,7 +735,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
return return
} }
val onTryAgain: (Long) -> Unit = { val onTryAgain: (String) -> Unit = {
savedDownloadState.first?.let { dlState -> savedDownloadState.first?.let { dlState ->
store.dispatch( store.dispatch(
ContentAction.UpdateDownloadAction( 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. * Returns the layout [android.view.Gravity] for the quick settings and ETP dialog.
*/ */
protected fun getAppropriateLayoutGravity(): Int = protected fun getAppropriateLayoutGravity(): Int =
context?.settings()?.toolbarPosition?.androidGravity ?: Gravity.BOTTOM components.settings.toolbarPosition.androidGravity
/** /**
* Updates the site permissions rules based on user settings. * Updates the site permissions rules based on user settings.
*/ */
private fun assignSitePermissionsRules(context: Context) { private fun assignSitePermissionsRules() {
val settings = context.settings() val rules = components.settings.getSitePermissionsCustomSettingsRules()
val rules: SitePermissionsRules = settings.getSitePermissionsCustomSettingsRules()
sitePermissionsFeature.withFeature { sitePermissionsFeature.withFeature {
it.sitePermissionsRules = rules it.sitePermissionsRules = rules
@ -981,7 +998,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
* Returns the current session. * Returns the current session.
*/ */
protected fun getSessionById(): Session? { protected fun getSessionById(): Session? {
val sessionManager = context?.components?.core?.sessionManager ?: return null val sessionManager = components.core.sessionManager
val localCustomTabId = customTabSessionId val localCustomTabId = customTabSessionId
return if (localCustomTabId != null) { return if (localCustomTabId != null) {
sessionManager.findSessionById(localCustomTabId) sessionManager.findSessionById(localCustomTabId)
@ -1092,10 +1109,12 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
} }
private fun didFirstContentfulHappen() = private fun didFirstContentfulHappen() =
if (!requireContext().settings().waitToShowPageUntilFirstPaint) true else if (components.settings.waitToShowPageUntilFirstPaint) {
context?.components?.core?.store?.state?.findTabOrCustomTabOrSelectedTab( val tab = components.core.store.state.findTabOrCustomTabOrSelectedTab(customTabSessionId)
customTabSessionId tab?.content?.firstContentfulPaint ?: false
)?.content?.firstContentfulPaint ?: false } else {
true
}
/* /*
* Dereference these views when the fragment view is destroyed to prevent memory leaks * 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.R
import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
/** /**
@ -29,6 +30,7 @@ class BrowserAnimator(
private val engineView: WeakReference<EngineView>, private val engineView: WeakReference<EngineView>,
private val swipeRefresh: WeakReference<View>, private val swipeRefresh: WeakReference<View>,
private val viewLifecycleScope: WeakReference<LifecycleCoroutineScope>, private val viewLifecycleScope: WeakReference<LifecycleCoroutineScope>,
private val settings: Settings,
private val firstContentfulHappened: () -> Boolean private val firstContentfulHappened: () -> Boolean
) { ) {
@ -39,7 +41,7 @@ class BrowserAnimator(
get() = swipeRefresh.get() get() = swipeRefresh.get()
fun beginAnimateInIfNecessary() { fun beginAnimateInIfNecessary() {
if (unwrappedSwipeRefresh?.context?.settings()?.waitToShowPageUntilFirstPaint == true) { if (settings.waitToShowPageUntilFirstPaint) {
if (firstContentfulHappened()) { if (firstContentfulHappened()) {
viewLifecycleScope.get()?.launch { viewLifecycleScope.get()?.launch {
delay(ANIMATION_DELAY) delay(ANIMATION_DELAY)

@ -7,8 +7,6 @@ package org.mozilla.fenix.components
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import androidx.annotation.VisibleForTesting 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 com.google.android.play.core.review.ReviewManagerFactory
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -46,12 +44,16 @@ class ReviewPromptController(
private val context: Context, private val context: Context,
private val reviewSettings: ReviewSettings, private val reviewSettings: ReviewSettings,
private val timeNowInMillis: () -> Long = { System.currentTimeMillis() }, 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 manager = ReviewManagerFactory.create(context)
val reviewInfo = manager.requestReview() val flow = manager.requestReviewFlow()
withContext(Main) { 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 package org.mozilla.fenix.components.toolbar
open class BrowserInteractor( open class BrowserInteractor(
private val browserToolbarController: BrowserToolbarController private val browserToolbarController: BrowserToolbarController,
private val menuController: BrowserToolbarMenuController
) : BrowserToolbarViewInteractor { ) : BrowserToolbarViewInteractor {
override fun onTabCounterClicked() { override fun onTabCounterClicked() {
browserToolbarController.handleTabCounterClick() browserToolbarController.handleTabCounterClick()
} }
override fun onTabCounterMenuItemTapped(item: TabCounterMenuItem) { override fun onTabCounterMenuItemTapped(item: TabCounterMenu.Item) {
browserToolbarController.handleTabCounterItemInteraction(item) browserToolbarController.handleTabCounterItemInteraction(item)
} }
@ -29,7 +30,7 @@ open class BrowserInteractor(
} }
override fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item) { override fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item) {
browserToolbarController.handleToolbarItemInteraction(item) menuController.handleToolbarItemInteraction(item)
} }
override fun onScrolled(offset: Int) { override fun onScrolled(offset: Int) {

@ -4,46 +4,24 @@
package org.mozilla.fenix.components.toolbar package org.mozilla.fenix.components.toolbar
import android.content.Intent
import androidx.annotation.VisibleForTesting
import androidx.navigation.NavController 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.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.EngineSession.LoadUrlFlags
import mozilla.components.concept.engine.EngineView 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 mozilla.components.support.ktx.kotlin.isUrl
import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator import org.mozilla.fenix.browser.BrowserAnimator
import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions import org.mozilla.fenix.browser.BrowserAnimator.Companion.getToolbarNavOptions
import org.mozilla.fenix.browser.BrowserFragmentDirections import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.readermode.ReaderModeController 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.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.navigateSafe
import org.mozilla.fenix.ext.sessionsOfType 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 * 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 handleScroll(offset: Int)
fun handleToolbarPaste(text: String) fun handleToolbarPaste(text: String)
fun handleToolbarPasteAndGo(text: String) fun handleToolbarPasteAndGo(text: String)
fun handleToolbarItemInteraction(item: ToolbarMenu.Item)
fun handleToolbarClick() fun handleToolbarClick()
fun handleTabCounterClick() fun handleTabCounterClick()
fun handleTabCounterItemInteraction(item: TabCounterMenuItem) fun handleTabCounterItemInteraction(item: TabCounterMenu.Item)
fun handleReaderModePressed(enabled: Boolean) fun handleReaderModePressed(enabled: Boolean)
} }
@Suppress("LargeClass", "TooManyFunctions")
class DefaultBrowserToolbarController( class DefaultBrowserToolbarController(
private val activity: HomeActivity, private val activity: HomeActivity,
private val navController: NavController, private val navController: NavController,
private val metrics: MetricController,
private val readerModeController: ReaderModeController, private val readerModeController: ReaderModeController,
private val sessionFeature: ViewBoundFeatureWrapper<SessionFeature>,
private val sessionManager: SessionManager, private val sessionManager: SessionManager,
private val findInPageLauncher: () -> Unit,
private val engineView: EngineView, private val engineView: EngineView,
private val browserAnimator: BrowserAnimator, private val browserAnimator: BrowserAnimator,
private val swipeRefresh: SwipeRefreshLayout,
private val customTabSession: Session?, private val customTabSession: Session?,
private val openInFenixIntent: Intent, private val useNewSearchExperience: Boolean = FeatureFlags.newSearchExperience,
private val bookmarkTapped: (Session) -> Unit,
private val scope: CoroutineScope,
private val tabCollectionStorage: TabCollectionStorage,
private val onTabCounterClicked: () -> Unit, private val onTabCounterClicked: () -> Unit,
private val onCloseTab: (Session) -> Unit private val onCloseTab: (Session) -> Unit
) : BrowserToolbarController { ) : BrowserToolbarController {
private val useNewSearchExperience
get() = FeatureFlags.newSearchExperience
private val currentSession private val currentSession
get() = customTabSession ?: activity.components.core.sessionManager.selectedSession 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)
override fun handleToolbarPaste(text: String) { override fun handleToolbarPaste(text: String) {
if (useNewSearchExperience) { if (useNewSearchExperience) {
navController.nav( navController.nav(
R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog( R.id.browserFragment,
BrowserFragmentDirections.actionGlobalSearchDialog(
sessionId = currentSession?.id, sessionId = currentSession?.id,
pastedText = text pastedText = text
), getToolbarNavOptions(activity) ),
getToolbarNavOptions(activity)
) )
} else { } else {
browserAnimator.captureEngineViewAndDrawStatically { browserAnimator.captureEngineViewAndDrawStatically {
@ -128,15 +92,15 @@ class DefaultBrowserToolbarController(
} }
override fun handleToolbarClick() { override fun handleToolbarClick() {
activity.components.analytics.metrics.track( metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER))
Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)
)
if (useNewSearchExperience) { if (useNewSearchExperience) {
navController.nav( navController.nav(
R.id.browserFragment, BrowserFragmentDirections.actionGlobalSearchDialog( R.id.browserFragment,
BrowserFragmentDirections.actionGlobalSearchDialog(
currentSession?.id currentSession?.id
), getToolbarNavOptions(activity) ),
getToolbarNavOptions(activity)
) )
} else { } else {
browserAnimator.captureEngineViewAndDrawStatically { browserAnimator.captureEngineViewAndDrawStatically {
@ -158,16 +122,16 @@ class DefaultBrowserToolbarController(
override fun handleReaderModePressed(enabled: Boolean) { override fun handleReaderModePressed(enabled: Boolean) {
if (enabled) { if (enabled) {
readerModeController.showReaderView() readerModeController.showReaderView()
activity.components.analytics.metrics.track(Event.ReaderModeOpened) metrics.track(Event.ReaderModeOpened)
} else { } else {
readerModeController.hideReaderView() 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) { when (item) {
is TabCounterMenuItem.CloseTab -> { is TabCounterMenu.Item.CloseTab -> {
sessionManager.selectedSession?.let { sessionManager.selectedSession?.let {
// When closing the last tab we must show the undo snackbar in the home fragment // When closing the last tab we must show the undo snackbar in the home fragment
if (sessionManager.sessionsOfType(it.private).count() == 1) { if (sessionManager.sessionsOfType(it.private).count() == 1) {
@ -184,8 +148,8 @@ class DefaultBrowserToolbarController(
} }
} }
} }
is TabCounterMenuItem.NewTab -> { is TabCounterMenu.Item.NewTab -> {
activity.browsingModeManager.mode = BrowsingMode.fromBoolean(item.isPrivate) activity.browsingModeManager.mode = item.mode
navController.popBackStack(R.id.homeFragment, false) navController.popBackStack(R.id.homeFragment, false)
} }
} }
@ -195,239 +159,6 @@ class DefaultBrowserToolbarController(
engineView.setVerticalClipping(offset) 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 { companion object {
internal const val TELEMETRY_BROWSER_IDENTIFIER = "browserMenu" 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 onBrowserToolbarClicked()
fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item) fun onBrowserToolbarMenuItemTapped(item: ToolbarMenu.Item)
fun onTabCounterClicked() fun onTabCounterClicked()
fun onTabCounterMenuItemTapped(item: TabCounterMenuItem) fun onTabCounterMenuItemTapped(item: TabCounterMenu.Item)
fun onScrolled(offset: Int) fun onScrolled(offset: Int)
fun onReaderModePressed(enabled: Boolean) 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 package org.mozilla.fenix.components.toolbar
import android.content.Context
import android.util.TypedValue
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map 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.browser.state.selector.getNormalOrPrivateTabs
import mozilla.components.concept.toolbar.Toolbar import mozilla.components.concept.toolbar.Toolbar
import mozilla.components.lib.state.ext.flowScoped 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 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.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.theme.ThemeManager
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
/** /**
@ -34,18 +25,25 @@ import java.lang.ref.WeakReference
class TabCounterToolbarButton( class TabCounterToolbarButton(
private val lifecycleOwner: LifecycleOwner, private val lifecycleOwner: LifecycleOwner,
private val isPrivate: Boolean, private val isPrivate: Boolean,
private val onItemTapped: (TabCounterMenuItem) -> Unit = {}, private val onItemTapped: (TabCounterMenu.Item) -> Unit = {},
private val showTabs: () -> Unit private val showTabs: () -> Unit
) : Toolbar.Action { ) : Toolbar.Action {
private var reference: WeakReference<TabCounter> = WeakReference<TabCounter>(null) private var reference: WeakReference<TabCounter> = WeakReference<TabCounter>(null)
override fun createView(parent: ViewGroup): View { 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 } flow.map { state -> state.getNormalOrPrivateTabs(isPrivate).size }
.ifChanged() .ifChanged()
.collect { tabs -> updateCount(tabs) } .collect { tabs -> updateCount(tabs) }
} }
val menu = TabCounterMenu(parent.context, metrics, onItemTapped)
menu.updateMenu()
val view = TabCounter(parent.context).apply { val view = TabCounter(parent.context).apply {
reference = WeakReference(this) reference = WeakReference(this)
setOnClickListener { setOnClickListener {
@ -53,28 +51,23 @@ class TabCounterToolbarButton(
} }
setOnLongClickListener { setOnLongClickListener {
getTabContextMenu(it.context).show(it) menu.menuController.show(anchor = it)
true true
} }
addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
override fun onViewAttachedToWindow(v: View?) { 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 // Set selectableItemBackgroundBorderless
val outValue = TypedValue() view.setBackgroundResource(parent.context.theme.resolveAttribute(
parent.context.theme.resolveAttribute( android.R.attr.selectableItemBackgroundBorderless
android.R.attr.selectableItemBackgroundBorderless, ))
outValue,
true
)
view.setBackgroundResource(outValue.resourceId)
return view return view
} }
@ -83,46 +76,4 @@ class TabCounterToolbarButton(
private fun updateCount(count: Int) { private fun updateCount(count: Int) {
reference.get()?.setCountWithAnimation(count) 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 -> val tabsAction = TabCounterToolbarButton(
interactor.onTabCounterMenuItemTapped(item) lifecycleOwner,
} isPrivate,
val tabsAction = onItemTapped = {
TabCounterToolbarButton(lifecycleOwner, isPrivate, onTabCounterMenuItemTapped) { interactor.onTabCounterMenuItemTapped(it)
},
showTabs = {
toolbar.hideKeyboard() toolbar.hideKeyboard()
interactor.onTabCounterClicked() interactor.onTabCounterClicked()
} }
)
toolbar.addBrowserAction(tabsAction) toolbar.addBrowserAction(tabsAction)
val engineForSpeculativeConnects = if (!isPrivate) engine else null val engineForSpeculativeConnects = if (!isPrivate) engine else null

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

@ -52,9 +52,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import mozilla.appservices.places.BookmarkRoot 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.menu.view.MenuButton
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager 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.metrics.Event
import org.mozilla.fenix.components.tips.FenixTipManager import org.mozilla.fenix.components.tips.FenixTipManager
import org.mozilla.fenix.components.tips.providers.MigrationTipProvider 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.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.hideToolbar 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?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -348,8 +346,21 @@ class HomeFragment : Fragment() {
} }
createHomeMenu(requireContext(), WeakReference(view.menuButton)) 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 { view.tab_button.setOnLongClickListener {
createTabCounterMenu(requireContext()).show(view.tab_button) tabCounterMenu.menuController.show(anchor = it)
true true
} }
@ -710,50 +721,6 @@ class HomeFragment : Fragment() {
nav(R.id.homeFragment, directions, getToolbarNavOptions(requireContext())) 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") @SuppressWarnings("ComplexMethod", "LongMethod")
private fun createHomeMenu(context: Context, menuButtonView: WeakReference<MenuButton>) = private fun createHomeMenu(context: Context, menuButtonView: WeakReference<MenuButton>) =
HomeMenu( HomeMenu(

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

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

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

@ -5,14 +5,8 @@
package org.mozilla.fenix.settings package org.mozilla.fenix.settings
import android.os.Bundle import android.os.Bundle
import androidx.lifecycle.lifecycleScope
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreference 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.FeatureFlags
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
@ -48,16 +42,5 @@ class SecretSettingsFragment : PreferenceFragmentCompat() {
isChecked = context.settings().syncedTabsInTabsTray isChecked = context.settings().syncedTabsInTabsTray
onPreferenceChangeListener = SharedPreferenceUpdater() 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.useCases.tabsUseCases.removeAllTabs,
activity.components.core.historyStorage, activity.components.core.historyStorage,
activity.components.core.permissionStorage, activity.components.core.permissionStorage,
activity.components.core.icons,
activity.components.core.engine, activity.components.core.engine,
coroutineContext coroutineContext
) )
@ -53,7 +54,7 @@ fun deleteAndQuit(activity: Activity, coroutineScope: CoroutineScope, snackbar:
private suspend fun DeleteBrowsingDataController.deleteType(type: DeleteBrowsingDataOnQuitType) { private suspend fun DeleteBrowsingDataController.deleteType(type: DeleteBrowsingDataOnQuitType) {
when (type) { when (type) {
DeleteBrowsingDataOnQuitType.TABS -> deleteTabs() DeleteBrowsingDataOnQuitType.TABS -> deleteTabs()
DeleteBrowsingDataOnQuitType.HISTORY -> deleteHistoryAndDOMStorages() DeleteBrowsingDataOnQuitType.HISTORY -> deleteBrowsingData()
DeleteBrowsingDataOnQuitType.COOKIES -> deleteCookies() DeleteBrowsingDataOnQuitType.COOKIES -> deleteCookies()
DeleteBrowsingDataOnQuitType.CACHE -> deleteCachedFiles() DeleteBrowsingDataOnQuitType.CACHE -> deleteCachedFiles()
DeleteBrowsingDataOnQuitType.PERMISSIONS -> withContext(IO) { DeleteBrowsingDataOnQuitType.PERMISSIONS -> withContext(IO) {

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

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

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

@ -4,5 +4,5 @@
<vector android:height="32dp" android:viewportHeight="24" <vector android:height="32dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="32dp" xmlns:android="http://schemas.android.com/apk/res/android"> 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> </vector>

@ -4,6 +4,6 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. --> - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <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"/> <corners android:radius="@dimen/tab_corner_radius"/>
</shape> </shape>

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

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

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

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

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

@ -6,7 +6,7 @@
android:layout_width="100dp" android:layout_width="100dp"
android:layout_height="50dp" android:layout_height="50dp"
xmlns:app="http://schemas.android.com/apk/res-auto" 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"> android:orientation="horizontal">
<ImageView <ImageView

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

@ -78,6 +78,28 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Agora no</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nueva pestanya</string> <string name="home_screen_shortcut_open_new_tab_2">Nueva pestanya</string>
@ -258,6 +280,8 @@
<string name="preferences_toolbar">Barra de ferramientas</string> <string name="preferences_toolbar">Barra de ferramientas</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Personalizar</string> <string name="preferences_customize">Personalizar</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -523,6 +547,8 @@
<string name="collection_open_tabs">Ubrir pestanyas</string> <string name="collection_open_tabs">Ubrir pestanyas</string>
<!-- Text for the menu button to remove a top site --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Borrar</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (modo privau)</string> <string name="pwa_site_controls_title_private">%1$s (modo privau)</string>
@ -727,10 +753,8 @@
<string name="collections_header">Coleccions</string> <string name="collections_header">Coleccions</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed --> <!-- 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> <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 --> <!-- 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 --> <!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Triar pestanyas</string> <string name="create_collection_select_tabs">Triar pestanyas</string>
<!-- Title for the "select collection" step of the collection creator --> <!-- 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> <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 --> <!-- 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> <string name="onboarding_whats_new_description_linktext">Obtiene respuestas aquí</string>
<!-- text for the firefox account onboarding card header --> <!-- text for the Firefox account onboarding sign in card header -->
<string name="onboarding_firefox_account_header">Quita-le lo millor provecho a %s.</string> <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 --> <!-- 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> <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 <!-- 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> <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. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Vale, entendiu</string> <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" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Zarrar</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">Llingüetes abiertes</string> <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> <string name="tab_tray_menu_item_save">Guardar nuna coleición</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Compartir toles llingüetes</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Zarrar toles llingüetes</string> <string name="tab_tray_menu_item_close">Zarrar toles llingüetes</string>
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
@ -657,10 +675,6 @@
<!-- Collections --> <!-- Collections -->
<!-- Collections header on home fragment --> <!-- Collections header on home fragment -->
<string name="collections_header">Coleiciones</string> <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 --> <!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Esbilla de llingüetes</string> <string name="create_collection_select_tabs">Esbilla de llingüetes</string>
<!-- Title for the "select collection" step of the collection creator --> <!-- 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> <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 --> <!-- 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> <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 --> <!-- text for the automatic sign-in button while signing in is in process -->
<string name="onboarding_firefox_account_signing_in">Aniciando sesión…</string> <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 --> <!-- 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. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Val, entendílo</string> <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 --> <!-- 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> <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) --> <!-- 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> <string name="about_content">%1$s hè sviluppatu da Adam Novak.</string>
@ -58,6 +82,12 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Micca subitu</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nova unghjetta</string> <string name="home_screen_shortcut_open_new_tab_2">Nova unghjetta</string>
@ -150,8 +180,8 @@
<!-- Search Fragment --> <!-- Search Fragment -->
<!-- Button in the search view that lets a user search by scanning a QR code --> <!-- Button in the search view that lets a user search by scanning a QR code -->
<string name="search_scan_button">Numerizà</string> <string name="search_scan_button">Numerizà</string>
<!-- Button in the search view that lets a user search by using a shortcut --> <!-- Button in the search view that lets a user change their search engine -->
<string name="search_engines_shortcut_button">Mutore di ricerca</string> <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 --> <!-- 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> <string name="search_shortcuts_engine_settings">Preferenze di u mutore di ricerca</string>
<!-- Header displayed when selecting a shortcut search engine --> <!-- Header displayed when selecting a shortcut search engine -->
@ -284,9 +314,14 @@
<!-- Preference for open links in third party apps --> <!-- Preference for open links in third party apps -->
<string name="preferences_open_links_in_apps">Apre i liami in appiecazioni</string> <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 --> <!-- Preference for add_ons -->
<string name="preferences_addons">Moduli addiziunali</string> <string name="preferences_addons">Moduli addiziunali</string>
<!-- Preference for notifications -->
<string name="preferences_notifications">Nutificazioni</string>
<!-- Account Preferences --> <!-- Account Preferences -->
<!-- Preference for triggering sync --> <!-- Preference for triggering sync -->
<string name="preferences_sync_now">Sincrunizà avà</string> <string name="preferences_sync_now">Sincrunizà avà</string>
@ -513,6 +548,9 @@
<!-- Postfix for private WebApp titles, placeholder is replaced with app name --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (navigazione privata)</string> <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 --> <!-- History -->
<!-- Text for the button to clear all history --> <!-- Text for the button to clear all history -->
<string name="history_delete_all">Squassà a crunulogia</string> <string name="history_delete_all">Squassà a crunulogia</string>
@ -551,6 +589,13 @@
<!-- Text shown when no history exists --> <!-- Text shown when no history exists -->
<string name="history_empty_message">Alcuna crunulogia</string> <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 --> <!-- Crashes -->
<!-- Title text displayed on the tab crash page. This first parameter is the name of the application (For example: Fenix) --> <!-- 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> <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> <string name="collections_header">Cullezzioni</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed --> <!-- 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> <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 --> <!-- 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 --> <!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Selezziunà unghjette</string> <string name="create_collection_select_tabs">Selezziunà unghjette</string>
<!-- Title for the "select collection" step of the collection creator --> <!-- 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> <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 --> <!-- 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> <string name="onboarding_whats_new_description_linktext">Truvate risposte quì</string>
<!-- text for the firefox account onboarding card header <!-- text for the Firefox account onboarding sign in card header -->
The first parameter is the name of the app (e.g. Firefox Preview) --> <string name="onboarding_account_sign_in_header">Sincrunizate lindette, e parolle dentrata è ancu di più cù u vostru contu Firefox.</string>
<string name="onboarding_firefox_account_header">Ottene u più bellu da %s.</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 <!-- 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) another Firefox browser. (The word `Firefox` should not be translated)
The first parameter is the email of the detected user's account --> 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> <string name="saved_login_duplicate">Lidentificazione di cunnessione cù stu nome dutilizatore esiste dighjà</string>
<!-- Synced Tabs --> <!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account --> <!-- Text displayed to ask user to connect another device as no devices found with 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 -->
<string name="synced_tabs_connect_another_device">Cunnettate un altru apparechju.</string> <string name="synced_tabs_connect_another_device">Cunnettate un altru apparechju.</string>
<!-- Text displayed asking user to re-authenticate --> <!-- Text displayed asking user to re-authenticate -->
<string name="synced_tabs_reauth">Ci vole à autenticassi torna.</string> <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 --> <!-- 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> <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 --> <!-- Top Sites -->
<!-- Title text displayed in the dialog when top sites limit is reached. --> <!-- 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> <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. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Iè, aghju capitu</string> <string name="top_sites_max_limit_confirmation_button">Iè, aghju capitu</string>
<!-- DEPRECATED STRINGS --> <!-- Content description for close button in collection placeholder. -->
<!-- Button in the search view that lets a user search by using a shortcut --> <string name="remove_home_collection_placeholder_content_description">Caccià</string>
<string name="search_shortcuts_button">Accurtatoghji</string>
<!-- DEPRECATED: Header displayed when selecting a shortcut search engine --> <!-- depcrecated: text for the firefox account onboarding card header
<string name="search_shortcuts_search_with">Circà cù</string> The first parameter is the name of the app (e.g. Firefox Preview) -->
<!-- Header displayed when selecting a shortcut search engine --> <string name="onboarding_firefox_account_header">Ottene u più bellu da %s.</string>
<string name="search_shortcuts_search_with_2">Per sta volta, circà cù :</string>
<!-- Preference title for switch preference to show search shortcuts --> <!-- Deprecated: No Open Tabs Message Header -->
<string name="preferences_show_search_shortcuts">Affissà laccurtatoghji di ricerca</string> <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> </resources>

@ -291,6 +291,8 @@
<string name="preferences_toolbar">Symbolleiste</string> <string name="preferences_toolbar">Symbolleiste</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Theme</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Anpassen</string> <string name="preferences_customize">Anpassen</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -1549,6 +1551,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Ok, verstanden</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Entfernen</string> <string name="remove_home_collection_placeholder_content_description">Entfernen</string>

@ -80,6 +80,28 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Nic něnto</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nowy rejtarik</string> <string name="home_screen_shortcut_open_new_tab_2">Nowy rejtarik</string>
@ -264,6 +286,8 @@
<string name="preferences_toolbar">Symbolowa rědka</string> <string name="preferences_toolbar">Symbolowa rědka</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Drastwa</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Pśiměriś</string> <string name="preferences_customize">Pśiměriś</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -470,6 +494,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Zacyniś</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">Wócynjone rejtariki</string> <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> <string name="tab_tray_menu_item_save">Do zběrki składowaś</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Wšykne rejtariki źěliś</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Wšykne rejtariki zacyniś</string> <string name="tab_tray_menu_item_close">Wšykne rejtariki zacyniś</string>
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
@ -535,6 +588,8 @@
<!-- Text for the menu button to remove a top site --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Wótwónoźeś</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (priwatny modus)</string> <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. --> <!-- 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> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Wótwónoźeś</string> <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> </resources>

@ -284,6 +284,8 @@
<string name="preferences_toolbar">Toolbar</string> <string name="preferences_toolbar">Toolbar</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Theme</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Customize</string> <string name="preferences_customize">Customize</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -488,6 +490,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Close</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">Open tabs</string> <string name="tab_header_label">Open tabs</string>
@ -507,6 +534,10 @@
<string name="tab_tray_menu_item_save">Save to collection</string> <string name="tab_tray_menu_item_save">Save to collection</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Share all tabs</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Close all tabs</string> <string name="tab_tray_menu_item_close">Close all tabs</string>
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
@ -1506,6 +1537,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">OK, Got It</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Remove</string> <string name="remove_home_collection_placeholder_content_description">Remove</string>

@ -78,6 +78,28 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Ahora no</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nueva pestaña</string> <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 --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Eliminar</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (modo privado)</string> <string name="pwa_site_controls_title_private">%1$s (modo privado)</string>
@ -1493,4 +1517,13 @@
<!-- Content description for close button in collection placeholder. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Eliminar</string> <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> </resources>

@ -81,6 +81,28 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Orain ez</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Fitxa berria</string> <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 --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Kendu</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (modu pribatua)</string> <string name="pwa_site_controls_title_private">%1$s (modu pribatua)</string>
@ -752,10 +776,8 @@
<string name="collections_header">Bildumak</string> <string name="collections_header">Bildumak</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed --> <!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Bildumaren menua</string> <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 --> <!-- 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 --> <!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Hautatu fitxak</string> <string name="create_collection_select_tabs">Hautatu fitxak</string>
<!-- Title for the "select collection" step of the collection creator --> <!-- 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> <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 --> <!-- 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> <string name="onboarding_whats_new_description_linktext">Eskuratu erantzunak hemen</string>
<!-- text for the firefox account onboarding card header --> <!-- text for the Firefox account onboarding sign in card header -->
<string name="onboarding_firefox_account_header">Atera %s(r)i ahalik eta zuku gehiena.</string> <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 --> <!-- 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> <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 <!-- 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. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Ados, ulertuta</string> <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> <string name="preferences_toolbar">Työkalupalkki</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Teema</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Mukauta</string> <string name="preferences_customize">Mukauta</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -1532,6 +1534,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Selvä</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Poista</string> <string name="remove_home_collection_placeholder_content_description">Poista</string>

@ -79,6 +79,29 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Plus tard</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nouvel onglet</string> <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 --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Supprimer</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (navigation privée)</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Supprimer</string> <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> </resources>

@ -81,6 +81,28 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">No net</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nij ljepblêd</string> <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 --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Fuortsmite</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (priveemodus)</string> <string name="pwa_site_controls_title_private">%1$s (priveemodus)</string>
@ -1495,4 +1519,13 @@
<!-- Content description for close button in collection placeholder. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Fuortsmite</string> <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> </resources>

@ -82,6 +82,24 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Ani koág̃a</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Tendayke pyahu</string> <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 --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Mboguete</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (Ayvu Ñemigua)</string> <string name="pwa_site_controls_title_private">%1$s (Ayvu Ñemigua)</string>
@ -1522,4 +1542,9 @@
<!-- Content description for close button in collection placeholder. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Mboguete</string> <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 --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Nic nětko</string> <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 --> <!-- Text for the positive action button -->
<string name="open_in_app_cfr_positive_button_text">K nastajenjam</string> <string name="open_in_app_cfr_positive_button_text">K nastajenjam</string>
<!-- Text for the negative action button --> <!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">Zaćisnyć</string> <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. --> <!-- 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> <string name="camera_permissions_needed_positive_button_text">K nastajenjam</string>
<!-- Text for the negative action button to dismiss the dialog. --> <!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">Zaćisnyć</string> <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. --> <!-- 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> <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. --> <!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
@ -279,6 +286,8 @@
<string name="preferences_toolbar">Symbolowa lajsta</string> <string name="preferences_toolbar">Symbolowa lajsta</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Drasta</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Přiměrić</string> <string name="preferences_customize">Přiměrić</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -485,6 +494,32 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Začinić</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">Wočinjene rajtarki</string> <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> <string name="tab_tray_menu_item_save">Do zběrki składować</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Wšě rajtarki dźělić</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Wšě rajtarki začinić</string> <string name="tab_tray_menu_item_close">Wšě rajtarki začinić</string>
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
@ -1512,6 +1551,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">W porjadku, sym zrozumił</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Wotstronić</string> <string name="remove_home_collection_placeholder_content_description">Wotstronić</string>

@ -287,6 +287,8 @@
<string name="preferences_toolbar">Eszköztár</string> <string name="preferences_toolbar">Eszköztár</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Téma</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Testreszabás</string> <string name="preferences_customize">Testreszabás</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -1529,6 +1531,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Rendben, értem</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Eltávolítás</string> <string name="remove_home_collection_placeholder_content_description">Eltávolítás</string>

@ -81,6 +81,28 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Jangan sekarang</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Tab baru</string> <string name="home_screen_shortcut_open_new_tab_2">Tab baru</string>
@ -271,6 +293,8 @@
<string name="preferences_toolbar">Bilah alat</string> <string name="preferences_toolbar">Bilah alat</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Ubahsuai</string> <string name="preferences_customize">Ubahsuai</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -548,6 +572,8 @@
<!-- Text for the menu button to remove a top site --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Hapus</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (Mode Privat)</string> <string name="pwa_site_controls_title_private">%1$s (Mode Privat)</string>
@ -756,10 +782,8 @@
<string name="collections_header">Koleksi</string> <string name="collections_header">Koleksi</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed --> <!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Menu koleksi</string> <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 --> <!-- 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 --> <!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Pilih Tab</string> <string name="create_collection_select_tabs">Pilih Tab</string>
<!-- Title for the "select collection" step of the collection creator --> <!-- 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> <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 --> <!-- 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> <string name="onboarding_whats_new_description_linktext">Dapatkan jawabannya di sini</string>
<!-- text for the firefox account onboarding card header --> <!-- text for the Firefox account onboarding sign in card header -->
<string name="onboarding_firefox_account_header">Dapatkan hasil maksimal dari %s.</string> <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 --> <!-- 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> <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 <!-- 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. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Oke, Paham!</string> <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> <string name="preferences_toolbar">Afeggag n yifecka</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Asentel</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Sagen</string> <string name="preferences_customize">Sagen</string>
<!-- Preference description for banner about signing in --> <!-- 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" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Mdel</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">Iccaren yeldin</string> <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> <string name="tab_tray_menu_item_save">Sekles ɣer tagrumma</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Bḍu akk accaren</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Mdel akk iccaren</string> <string name="tab_tray_menu_item_close">Mdel akk iccaren</string>
<!-- Shortcut action to open new tab --> <!-- 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. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">IH, awi-t-id</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Kkes</string> <string name="remove_home_collection_placeholder_content_description">Kkes</string>

@ -296,6 +296,8 @@
<string name="preferences_toolbar">도구 모음</string> <string name="preferences_toolbar">도구 모음</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">테마</string> <string name="preferences_theme">테마</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home"></string>
<!-- Preference for settings related to visual options --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">사용자 지정</string> <string name="preferences_customize">사용자 지정</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -506,6 +508,32 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">닫기</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">열린 탭</string> <string name="tab_header_label">열린 탭</string>
@ -525,6 +553,10 @@
<string name="tab_tray_menu_item_save">모음집에 저장</string> <string name="tab_tray_menu_item_save">모음집에 저장</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">모든 탭 공유</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">모든 탭 닫기</string> <string name="tab_tray_menu_item_close">모든 탭 닫기</string>
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
@ -967,7 +999,7 @@
<!-- Title for the tabs item in Delete browsing data --> <!-- Title for the tabs item in Delete browsing data -->
<string name="preferences_delete_browsing_data_tabs_title_2">열린 탭</string> <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 --> <!-- 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 --> <!-- Title for the data and history items in Delete browsing data -->
<string name="preferences_delete_browsing_data_browsing_data_title">방문 기록 및 사이트 데이터</string> <string name="preferences_delete_browsing_data_browsing_data_title">방문 기록 및 사이트 데이터</string>
@ -1567,6 +1599,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">확인</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">삭제</string> <string name="remove_home_collection_placeholder_content_description">삭제</string>

@ -288,6 +288,8 @@
<string name="preferences_toolbar">Verktøylinje</string> <string name="preferences_toolbar">Verktøylinje</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Tilpass</string> <string name="preferences_customize">Tilpass</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -497,6 +499,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Lukk</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">Åpne faner</string> <string name="tab_header_label">Åpne faner</string>
@ -517,6 +544,10 @@
<string name="tab_tray_menu_item_save">Lagre i samling</string> <string name="tab_tray_menu_item_save">Lagre i samling</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Del alle faner</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Lukk alle faner</string> <string name="tab_tray_menu_item_close">Lukk alle faner</string>
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
@ -1540,6 +1571,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">OK, jeg skjønner</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Fjern</string> <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_green">@color/collection_icon_color_green_dark_theme</color>
<color name="collection_icon_color_yellow">@color/collection_icon_color_yellow_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 --> <!-- Reader View colors -->
<color name="mozac_feature_readerview_text_color">@color/primary_text_dark_theme</color> <color name="mozac_feature_readerview_text_color">@color/primary_text_dark_theme</color>

@ -84,6 +84,28 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Niet nu</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Nieuw tabblad</string> <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 --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Verwijderen</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (privémodus)</string> <string name="pwa_site_controls_title_private">%1$s (privémodus)</string>
@ -1506,4 +1530,13 @@
<!-- Content description for close button in collection placeholder. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Verwijderen</string> <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> </resources>

@ -82,6 +82,19 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Ikkje no</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Ny fane</string> <string name="home_screen_shortcut_open_new_tab_2">Ny fane</string>
@ -267,6 +280,8 @@
<string name="preferences_toolbar">Verktøylinje</string> <string name="preferences_toolbar">Verktøylinje</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Tilpass</string> <string name="preferences_customize">Tilpass</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -474,6 +489,27 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Lat att</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">Opne faner</string> <string name="tab_header_label">Opne faner</string>
@ -493,6 +529,10 @@
<string name="tab_tray_menu_item_save">Lagre i samling</string> <string name="tab_tray_menu_item_save">Lagre i samling</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Del alle faner</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Lat att alle faner</string> <string name="tab_tray_menu_item_close">Lat att alle faner</string>
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
@ -540,6 +580,8 @@
<!-- Text for the menu button to remove a top site --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Fjern</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (privatmodus)</string> <string name="pwa_site_controls_title_private">%1$s (privatmodus)</string>
@ -1502,4 +1544,5 @@
<!-- Content description for close button in collection placeholder. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Fjern</string> <string name="remove_home_collection_placeholder_content_description">Fjern</string>
</resources>
</resources>

@ -286,6 +286,8 @@
<string name="preferences_toolbar">Barra de ferramentas</string> <string name="preferences_toolbar">Barra de ferramentas</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Personalizar</string> <string name="preferences_customize">Personalizar</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -492,6 +494,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Fechar</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">Abas abertas</string> <string name="tab_header_label">Abas abertas</string>
@ -511,6 +538,10 @@
<string name="tab_tray_menu_item_save">Salvar em coleção</string> <string name="tab_tray_menu_item_save">Salvar em coleção</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Compartilhar todas as abas</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Fechar todas as abas</string> <string name="tab_tray_menu_item_close">Fechar todas as abas</string>
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
@ -1520,6 +1551,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">OK, entendi</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Remover</string> <string name="remove_home_collection_placeholder_content_description">Remover</string>

@ -82,6 +82,15 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Не сейчас</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Новая вкладка</string> <string name="home_screen_shortcut_open_new_tab_2">Новая вкладка</string>
@ -545,6 +554,8 @@
<!-- Text for the menu button to remove a top site --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Удалить</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (Приватный просмотр)</string> <string name="pwa_site_controls_title_private">%1$s (Приватный просмотр)</string>
@ -1525,4 +1536,9 @@
<!-- Content description for close button in collection placeholder. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Убрать</string> <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 --> <!-- 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> <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) --> <!-- 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> <string name="about_content">%1$s prodhohet nga Adam Novak.</string>
@ -54,6 +77,28 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Jo tani</string> <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 --> <!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Skedë e re</string> <string name="home_screen_shortcut_open_new_tab_2">Skedë e re</string>
@ -146,8 +191,8 @@
<!-- Search Fragment --> <!-- Search Fragment -->
<!-- Button in the search view that lets a user search by scanning a QR code --> <!-- Button in the search view that lets a user search by scanning a QR code -->
<string name="search_scan_button">Skanoje</string> <string name="search_scan_button">Skanoje</string>
<!-- Button in the search view that lets a user search by using a shortcut --> <!-- Button in the search view that lets a user change their search engine -->
<string name="search_engines_shortcut_button">Motor Kërkimesh</string> <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 --> <!-- 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> <string name="search_shortcuts_engine_settings">Rregullime motorësh kërkimesh</string>
<!-- Header displayed when selecting a shortcut search engine --> <!-- Header displayed when selecting a shortcut search engine -->
@ -278,9 +323,14 @@
<!-- Preference for open links in third party apps --> <!-- Preference for open links in third party apps -->
<string name="preferences_open_links_in_apps">Hapi lidhjet në aplikacione</string> <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 --> <!-- Preference for add_ons -->
<string name="preferences_addons">Shtesa</string> <string name="preferences_addons">Shtesa</string>
<!-- Preference for notifications -->
<string name="preferences_notifications">Njoftime</string>
<!-- Account Preferences --> <!-- Account Preferences -->
<!-- Preference for triggering sync --> <!-- Preference for triggering sync -->
<string name="preferences_sync_now">Njëkohësoje tani</string> <string name="preferences_sync_now">Njëkohësoje tani</string>
@ -505,9 +555,14 @@
<!-- Text for the menu button to remove a top site --> <!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Hiqe</string> <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 --> <!-- Postfix for private WebApp titles, placeholder is replaced with app name -->
<string name="pwa_site_controls_title_private">%1$s (Mënyrë Private)</string> <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 --> <!-- History -->
<!-- Text for the button to clear all history --> <!-- Text for the button to clear all history -->
<string name="history_delete_all">Fshije historikun</string> <string name="history_delete_all">Fshije historikun</string>
@ -546,6 +601,13 @@
<!-- Text shown when no history exists --> <!-- Text shown when no history exists -->
<string name="history_empty_message">Ska historik këtu</string> <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 --> <!-- Crashes -->
<!-- Title text displayed on the tab crash page. This first parameter is the name of the application (For example: Fenix) --> <!-- 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> <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> <string name="collections_header">Koleksione</string>
<!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed --> <!-- Content description (not visible, for screen readers etc.): Opens the collection menu when pressed -->
<string name="collection_menu_button_content_description">Menu koleksionesh</string> <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 --> <!-- 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 --> <!-- Title for the "select tabs" step of the collection creator -->
<string name="create_collection_select_tabs">Përzgjidhni Skeda</string> <string name="create_collection_select_tabs">Përzgjidhni Skeda</string>
<!-- Title for the "select collection" step of the collection creator --> <!-- 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> <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 --> <!-- 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> <string name="onboarding_whats_new_description_linktext">Merrni përgjigje këtu</string>
<!-- text for the firefox account onboarding card header <!-- text for the Firefox account onboarding sign in card header -->
The first parameter is the name of the app (e.g. Firefox Preview) --> <string name="onboarding_account_sign_in_header">Filloni të njëkohësoni faqerojtës, fjalëkalime, etj, me llogarinë tuaj Firefox.</string>
<string name="onboarding_firefox_account_header">Përfitoni maksimumin nga %s.</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 <!-- 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) another Firefox browser. (The word `Firefox` should not be translated)
The first parameter is the email of the detected user's account --> 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> <string name="saved_login_duplicate">Ka tashmë një palë kredenciale me këtë emër përdoruesi</string>
<!-- Synced Tabs --> <!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account --> <!-- Text displayed to ask user to connect another device as no devices found with 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 -->
<string name="synced_tabs_connect_another_device">Lidhni pajisje tjetër.</string> <string name="synced_tabs_connect_another_device">Lidhni pajisje tjetër.</string>
<!-- Text displayed asking user to re-authenticate --> <!-- Text displayed asking user to re-authenticate -->
<string name="synced_tabs_reauth">Ju lutemi, ribëni mirëfilltësimin.</string> <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 --> <!-- 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> <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 --> <!-- Top Sites -->
<!-- Title text displayed in the dialog when top sites limit is reached. --> <!-- 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> <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. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">OK, e mora vesh</string> <string name="top_sites_max_limit_confirmation_button">OK, e mora vesh</string>
<!-- DEPRECATED STRINGS --> <!-- Content description for close button in collection placeholder. -->
<!-- Button in the search view that lets a user search by using a shortcut --> <string name="remove_home_collection_placeholder_content_description">Hiqe</string>
<string name="search_shortcuts_button">Shkurtore</string>
<!-- DEPRECATED: Header displayed when selecting a shortcut search engine --> <!-- depcrecated: text for the firefox account onboarding card header
<string name="search_shortcuts_search_with">Kërkoni me</string> The first parameter is the name of the app (e.g. Firefox Preview) -->
<!-- Header displayed when selecting a shortcut search engine --> <string name="onboarding_firefox_account_header">Përfitoni maksimumin nga %s.</string>
<string name="search_shortcuts_search_with_2">Këtë herë kërko me:</string>
<!-- Preference title for switch preference to show search shortcuts --> <!-- Deprecated: No Open Tabs Message Header -->
<string name="preferences_show_search_shortcuts">Shfaqni shkurtore kërkimi</string> <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> </resources>

@ -283,6 +283,8 @@
<string name="preferences_toolbar">Verktygsfält</string> <string name="preferences_toolbar">Verktygsfält</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Tema</string> <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 --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Anpassa</string> <string name="preferences_customize">Anpassa</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -493,6 +495,29 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Stäng</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">Öppna flikar</string> <string name="tab_header_label">Öppna flikar</string>
@ -512,6 +537,8 @@
<string name="tab_tray_menu_item_save">Spara i samling</string> <string name="tab_tray_menu_item_save">Spara i samling</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Dela alla flikar</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Stäng alla flikar</string> <string name="tab_tray_menu_item_close">Stäng alla flikar</string>
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
@ -1527,6 +1554,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Ok, jag förstår</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Ta bort</string> <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) --> 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> <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> </resources>

@ -289,6 +289,8 @@
<string name="preferences_toolbar">Панель інструментів</string> <string name="preferences_toolbar">Панель інструментів</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Тема</string> <string name="preferences_theme">Тема</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">Домівка</string>
<!-- Preference for settings related to visual options --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">Пристосування</string> <string name="preferences_customize">Пристосування</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -499,6 +501,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Закрити</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">Відкриті вкладки</string> <string name="tab_header_label">Відкриті вкладки</string>
@ -518,6 +545,10 @@
<string name="tab_tray_menu_item_save">Зберегти до збірки</string> <string name="tab_tray_menu_item_save">Зберегти до збірки</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Поділитися всіма вкладками</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Закрити всі вкладки</string> <string name="tab_tray_menu_item_close">Закрити всі вкладки</string>
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->
@ -1532,6 +1563,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">Гаразд, зрозуміло</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Вилучити</string> <string name="remove_home_collection_placeholder_content_description">Вилучити</string>

@ -83,6 +83,11 @@
<!-- Text for the negative button --> <!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">现在不要</string> <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 --> <!-- Text for the negative action button -->
<string name="open_in_app_cfr_negative_button_text">知道了</string> <string name="open_in_app_cfr_negative_button_text">知道了</string>
@ -93,6 +98,8 @@
<!-- Text for the negative action button to dismiss the dialog. --> <!-- Text for the negative action button to dismiss the dialog. -->
<string name="camera_permissions_needed_negative_button_text">知道了</string> <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. --> <!-- 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> <string name="tab_tray_close_tabs_banner_positive_button_text">查看选项</string>
<!-- Text for the negative action button to dismiss the Close Tabs Banner. --> <!-- Text for the negative action button to dismiss the Close Tabs Banner. -->
@ -326,7 +333,7 @@
<string name="preferences_account_settings">账户设置</string> <string name="preferences_account_settings">账户设置</string>
<!-- Preference for open links in third party apps --> <!-- 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 --> <!-- Preference for open download with an external download manager app -->
<string name="preferences_external_download_manager">外部下载管理器</string> <string name="preferences_external_download_manager">外部下载管理器</string>

@ -290,6 +290,8 @@
<string name="preferences_toolbar">工具列</string> <string name="preferences_toolbar">工具列</string>
<!-- Preference for changing default theme to dark or light mode --> <!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">佈景主題</string> <string name="preferences_theme">佈景主題</string>
<!-- Preference for customizing the home screen -->
<string name="preferences_home">主畫面</string>
<!-- Preference for settings related to visual options --> <!-- Preference for settings related to visual options -->
<string name="preferences_customize">自訂</string> <string name="preferences_customize">自訂</string>
<!-- Preference description for banner about signing in --> <!-- Preference description for banner about signing in -->
@ -1543,6 +1545,9 @@
<!-- Confirmation dialog button text when top sites limit is reached. --> <!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">好,知道了!</string> <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. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">移除</string> <string name="remove_home_collection_placeholder_content_description">移除</string>

@ -389,6 +389,8 @@
<color name="button_text_color">#312A65</color> <color name="button_text_color">#312A65</color>
<!-- Search Widget --> <!-- 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> <color name="search_widget_text">#737373</color>
<!-- Private Browsing Mode Persistent Notification --> <!-- 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_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_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> </resources>

@ -483,6 +483,31 @@
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" --> <!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Close</string> <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 --> <!-- Sessions -->
<!-- Title for the list of tabs --> <!-- Title for the list of tabs -->
<string name="tab_header_label">Open tabs</string> <string name="tab_header_label">Open tabs</string>
@ -502,6 +527,10 @@
<string name="tab_tray_menu_item_save">Save to collection</string> <string name="tab_tray_menu_item_save">Save to collection</string>
<!-- Text shown in the menu for sharing all tabs --> <!-- Text shown in the menu for sharing all tabs -->
<string name="tab_tray_menu_item_share">Share all tabs</string> <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 --> <!-- Text shown in the menu for closing all tabs -->
<string name="tab_tray_menu_item_close">Close all tabs</string> <string name="tab_tray_menu_item_close">Close all tabs</string>
<!-- Shortcut action to open new tab --> <!-- Shortcut action to open new tab -->

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

@ -2,9 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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 { data class SimpleBrowsingModeManager(
object CloseTab : TabCounterMenuItem() override var mode: BrowsingMode
class NewTab(val isPrivate: Boolean) : TabCounterMenuItem() ) : 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 package org.mozilla.fenix.components.toolbar
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.mockk import io.mockk.mockk
import io.mockk.verify import io.mockk.verify
import org.junit.Before import org.junit.Before
@ -7,14 +9,16 @@ import org.junit.Test
class BrowserInteractorTest { class BrowserInteractorTest {
lateinit var browserToolbarController: BrowserToolbarController @RelaxedMockK lateinit var browserToolbarController: BrowserToolbarController
@RelaxedMockK lateinit var browserToolbarMenuController: BrowserToolbarMenuController
lateinit var interactor: BrowserInteractor lateinit var interactor: BrowserInteractor
@Before @Before
fun setup() { fun setup() {
browserToolbarController = mockk(relaxed = true) MockKAnnotations.init(this)
interactor = BrowserInteractor( interactor = BrowserInteractor(
browserToolbarController browserToolbarController,
browserToolbarMenuController
) )
} }
@ -26,7 +30,7 @@ class BrowserInteractorTest {
@Test @Test
fun onTabCounterMenuItemTapped() { fun onTabCounterMenuItemTapped() {
val item: TabCounterMenuItem = mockk() val item: TabCounterMenu.Item = mockk()
interactor.onTabCounterMenuItemTapped(item) interactor.onTabCounterMenuItemTapped(item)
verify { browserToolbarController.handleTabCounterItemInteraction(item) } verify { browserToolbarController.handleTabCounterItemInteraction(item) }
@ -60,6 +64,6 @@ class BrowserInteractorTest {
interactor.onBrowserToolbarMenuItemTapped(item) interactor.onBrowserToolbarMenuItemTapped(item)
verify { browserToolbarController.handleToolbarItemInteraction(item) } verify { browserToolbarMenuController.handleToolbarItemInteraction(item) }
} }
} }

@ -4,9 +4,8 @@
package org.mozilla.fenix.components.toolbar package org.mozilla.fenix.components.toolbar
import android.content.Intent
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.navigation.NavOptions
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
import io.mockk.Runs import io.mockk.Runs
import io.mockk.every import io.mockk.every
@ -14,149 +13,101 @@ import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.just import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.mockkStatic
import io.mockk.slot import io.mockk.slot
import io.mockk.unmockkStatic
import io.mockk.verify import io.mockk.verify
import io.mockk.verifyOrder 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.Session
import mozilla.components.browser.session.SessionManager 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.EngineView
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.feature.search.SearchUseCases import mozilla.components.feature.search.SearchUseCases
import mozilla.components.feature.session.SessionFeature
import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.feature.top.sites.TopSitesUseCases 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.Assert.assertEquals
import org.junit.Before import org.junit.Before
import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BrowserAnimator import org.mozilla.fenix.browser.BrowserAnimator
import org.mozilla.fenix.browser.BrowserFragmentDirections import org.mozilla.fenix.browser.BrowserFragmentDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.browser.browsingmode.SimpleBrowsingModeManager
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
import org.mozilla.fenix.browser.readermode.ReaderModeController 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.Event
import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components 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.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
class DefaultBrowserToolbarControllerTest { class DefaultBrowserToolbarControllerTest {
@get:Rule
val coroutinesTestRule = MainCoroutineRule()
@MockK private lateinit var swipeRefreshLayout: SwipeRefreshLayout
@RelaxedMockK private lateinit var activity: HomeActivity @RelaxedMockK private lateinit var activity: HomeActivity
@RelaxedMockK private lateinit var analytics: Analytics @MockK(relaxUnitFun = true) private lateinit var navController: NavController
@RelaxedMockK private lateinit var navController: NavController
@RelaxedMockK private lateinit var findInPageLauncher: () -> Unit
@RelaxedMockK private lateinit var bookmarkTapped: (Session) -> Unit
@RelaxedMockK private lateinit var onTabCounterClicked: () -> Unit @RelaxedMockK private lateinit var onTabCounterClicked: () -> Unit
@RelaxedMockK private lateinit var onCloseTab: (Session) -> Unit @RelaxedMockK private lateinit var onCloseTab: (Session) -> Unit
@RelaxedMockK private lateinit var sessionManager: SessionManager @RelaxedMockK private lateinit var sessionManager: SessionManager
@RelaxedMockK private lateinit var engineView: EngineView @MockK(relaxUnitFun = true) private lateinit var engineView: EngineView
@RelaxedMockK private lateinit var currentSession: Session @MockK private lateinit var currentSession: Session
@RelaxedMockK private lateinit var openInFenixIntent: Intent
@RelaxedMockK private lateinit var metrics: MetricController @RelaxedMockK private lateinit var metrics: MetricController
@RelaxedMockK private lateinit var searchUseCases: SearchUseCases @RelaxedMockK private lateinit var searchUseCases: SearchUseCases
@RelaxedMockK private lateinit var sessionUseCases: SessionUseCases @RelaxedMockK private lateinit var sessionUseCases: SessionUseCases
@RelaxedMockK private lateinit var browserAnimator: BrowserAnimator @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 topSitesUseCase: TopSitesUseCases
@RelaxedMockK private lateinit var readerModeController: ReaderModeController @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 @Before
fun setUp() { fun setUp() {
MockKAnnotations.init(this) 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.sessionUseCases } returns sessionUseCases
every { activity.components.useCases.searchUseCases } returns searchUseCases every { activity.components.useCases.searchUseCases } returns searchUseCases
every { activity.components.useCases.topSitesUseCase } returns topSitesUseCase 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 { 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>() val onComplete = slot<() -> Unit>()
every { browserAnimator.captureEngineViewAndDrawStatically(capture(onComplete)) } answers { onComplete.captured.invoke() } every { browserAnimator.captureEngineViewAndDrawStatically(capture(onComplete)) } answers { onComplete.captured.invoke() }
} }
@After @Test
fun tearDown() { fun handleBrowserToolbarPaste() {
unmockkStatic("org.mozilla.fenix.settings.deletebrowsingdata.DeleteAndQuitKt") 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 @Test
fun handleBrowserToolbarPaste() = runBlockingTest { fun handleBrowserToolbarPaste_useNewSearchExperience() {
every { currentSession.id } returns "1"
val pastedText = "Mozilla" val pastedText = "Mozilla"
val controller = createController(scope = this) val controller = createController(useNewSearchExperience = true)
controller.handleToolbarPaste(pastedText) controller.handleToolbarPaste(pastedText)
verify { val directions = BrowserFragmentDirections.actionGlobalSearchDialog(
val directions = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment( sessionId = "1",
sessionId = "1", pastedText = pastedText
pastedText = pastedText )
)
navController.nav(R.id.browserFragment, directions) verify { navController.navigate(directions, any<NavOptions>()) }
}
} }
@Test @Test
fun handleBrowserToolbarPasteAndGoSearch() = runBlockingTest { fun handleBrowserToolbarPasteAndGoSearch() {
val pastedText = "Mozilla" val pastedText = "Mozilla"
val controller = createController(scope = this) val controller = createController()
controller.handleToolbarPasteAndGo(pastedText) controller.handleToolbarPasteAndGo(pastedText)
verifyOrder { verifyOrder {
currentSession.searchTerms = "Mozilla" currentSession.searchTerms = "Mozilla"
@ -165,10 +116,10 @@ class DefaultBrowserToolbarControllerTest {
} }
@Test @Test
fun handleBrowserToolbarPasteAndGoUrl() = runBlockingTest { fun handleBrowserToolbarPasteAndGoUrl() {
val pastedText = "https://mozilla.org" val pastedText = "https://mozilla.org"
val controller = createController(scope = this) val controller = createController()
controller.handleToolbarPasteAndGo(pastedText) controller.handleToolbarPasteAndGo(pastedText)
verifyOrder { verifyOrder {
currentSession.searchTerms = "" currentSession.searchTerms = ""
@ -177,391 +128,59 @@ class DefaultBrowserToolbarControllerTest {
} }
@Test @Test
fun handleTabCounterClick() = runBlockingTest { fun handleTabCounterClick() {
val controller = createController(scope = this) val controller = createController()
controller.handleTabCounterClick() controller.handleTabCounterClick()
verify { onTabCounterClicked() } verify { onTabCounterClicked() }
} }
@Test @Test
fun `handle reader mode enabled`() = runBlockingTest { fun `handle reader mode enabled`() {
val controller = createController(scope = this) val controller = createController()
controller.handleReaderModePressed(enabled = true) controller.handleReaderModePressed(enabled = true)
verify { readerModeController.showReaderView() } verify { readerModeController.showReaderView() }
} }
@Test @Test
fun `handle reader mode disabled`() = runBlockingTest { fun `handle reader mode disabled`() {
val controller = createController(scope = this) val controller = createController()
controller.handleReaderModePressed(enabled = false) controller.handleReaderModePressed(enabled = false)
verify { readerModeController.hideReaderView() } verify { readerModeController.hideReaderView() }
} }
@Test @Test
fun handleToolbarClick() = runBlockingTest { fun handleToolbarClick() {
every { currentSession.id } returns "1" val controller = createController(useNewSearchExperience = false)
val controller = createController(scope = this)
controller.handleToolbarClick() controller.handleToolbarClick()
verify { metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) } val expected = BrowserFragmentDirections.actionBrowserFragmentToSearchFragment(
verify { sessionId = "1"
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)
verify { sessionFeature.release() } verify { metrics.track(Event.SearchBarTapped(Event.SearchBarTapped.Source.BROWSER)) }
verify { currentSession.customTabConfig = null } verify { navController.navigate(expected, any<NavOptions>()) }
verify { sessionManager.select(currentSession) }
verify { activity.startActivity(openInFenixIntent) }
verify { activity.finish() }
} }
@Test @Test
fun handleToolbarQuitPress() = runBlockingTest { fun handleToolbarClick_useNewSearchExperience() {
val item = ToolbarMenu.Item.Quit val controller = createController(useNewSearchExperience = true)
val testScope = this controller.handleToolbarClick()
val controller = createController(scope = testScope)
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 @Test
fun handleToolbarCloseTabPressWithLastPrivateSession() = runBlockingTest { fun handleToolbarCloseTabPressWithLastPrivateSession() {
every { currentSession.id } returns "1" val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Private)
val browsingModeManager = object : BrowsingModeManager { val item = TabCounterMenu.Item.CloseTab
override var mode = BrowsingMode.Private
}
val item = TabCounterMenuItem.CloseTab
val sessions = listOf( val sessions = listOf(
mockk<Session> { mockk<Session> {
every { private } returns true every { private } returns true
@ -572,81 +191,77 @@ class DefaultBrowserToolbarControllerTest {
every { sessionManager.sessions } returns sessions every { sessionManager.sessions } returns sessions
every { activity.browsingModeManager } returns browsingModeManager every { activity.browsingModeManager } returns browsingModeManager
val controller = createController(scope = this) val controller = createController()
controller.handleTabCounterItemInteraction(item) controller.handleTabCounterItemInteraction(item)
verify { navController.navigate(BrowserFragmentDirections.actionGlobalHome(sessionToDelete = "1")) } verify { navController.navigate(BrowserFragmentDirections.actionGlobalHome(sessionToDelete = "1")) }
assertEquals(BrowsingMode.Normal, browsingModeManager.mode) assertEquals(BrowsingMode.Normal, browsingModeManager.mode)
} }
@Test @Test
fun handleToolbarCloseTabPress() = runBlockingTest { fun handleToolbarCloseTabPress() {
val tabsUseCases: TabsUseCases = mockk(relaxed = true) val tabsUseCases: TabsUseCases = mockk(relaxed = true)
val removeTabUseCase: TabsUseCases.RemoveTabUseCase = 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 { sessionManager.sessions } returns emptyList()
every { activity.components.useCases.tabsUseCases } returns tabsUseCases every { activity.components.useCases.tabsUseCases } returns tabsUseCases
every { tabsUseCases.removeTab } returns removeTabUseCase every { tabsUseCases.removeTab } returns removeTabUseCase
val controller = createController(scope = this) val controller = createController()
controller.handleTabCounterItemInteraction(item) controller.handleTabCounterItemInteraction(item)
verify { removeTabUseCase.invoke(currentSession) } verify { removeTabUseCase.invoke(currentSession) }
} }
@Test @Test
fun handleToolbarNewTabPress() = runBlockingTest { fun handleToolbarNewTabPress() {
val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager( val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Private)
BrowsingMode.Private, val item = TabCounterMenu.Item.NewTab(BrowsingMode.Normal)
mockk(relaxed = true)
) {}
val item = TabCounterMenuItem.NewTab(false)
every { activity.browsingModeManager } returns browsingModeManager 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) controller.handleTabCounterItemInteraction(item)
assertEquals(BrowsingMode.Normal, activity.browsingModeManager.mode) assertEquals(BrowsingMode.Normal, browsingModeManager.mode)
verify { navController.popBackStack(R.id.homeFragment, false) } verify { navController.popBackStack(R.id.homeFragment, false) }
} }
@Test @Test
fun handleToolbarNewPrivateTabPress() = runBlockingTest { fun handleToolbarNewPrivateTabPress() {
val browsingModeManager: BrowsingModeManager = DefaultBrowsingModeManager( val browsingModeManager = SimpleBrowsingModeManager(BrowsingMode.Normal)
BrowsingMode.Normal, val item = TabCounterMenu.Item.NewTab(BrowsingMode.Private)
mockk(relaxed = true)
) {}
val item = TabCounterMenuItem.NewTab(true)
every { activity.browsingModeManager } returns browsingModeManager 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) controller.handleTabCounterItemInteraction(item)
assertEquals(BrowsingMode.Private, activity.browsingModeManager.mode) assertEquals(BrowsingMode.Private, browsingModeManager.mode)
verify { navController.popBackStack(R.id.homeFragment, false) } verify { navController.popBackStack(R.id.homeFragment, false) }
} }
@Test
fun handleScroll() {
val controller = createController()
controller.handleScroll(10)
verify { engineView.setVerticalClipping(10) }
}
private fun createController( private fun createController(
scope: CoroutineScope,
activity: HomeActivity = this.activity, activity: HomeActivity = this.activity,
customTabSession: Session? = null customTabSession: Session? = null,
useNewSearchExperience: Boolean = false
) = DefaultBrowserToolbarController( ) = DefaultBrowserToolbarController(
activity = activity, activity = activity,
navController = navController, navController = navController,
findInPageLauncher = findInPageLauncher, metrics = metrics,
engineView = engineView, engineView = engineView,
browserAnimator = browserAnimator, browserAnimator = browserAnimator,
customTabSession = customTabSession, customTabSession = customTabSession,
openInFenixIntent = openInFenixIntent,
scope = scope,
swipeRefresh = swipeRefreshLayout,
tabCollectionStorage = tabCollectionStorage,
bookmarkTapped = bookmarkTapped,
readerModeController = readerModeController, readerModeController = readerModeController,
sessionManager = sessionManager, sessionManager = sessionManager,
sessionFeature = sessionFeatureWrapper, useNewSearchExperience = useNewSearchExperience,
onTabCounterClicked = onTabCounterClicked, onTabCounterClicked = onTabCounterClicked,
onCloseTab = onCloseTab 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 package org.mozilla.fenix.settings.deletebrowsingdata
import io.mockk.coVerify
import io.mockk.mockk import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope.coroutineContext import kotlinx.coroutines.GlobalScope.coroutineContext
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.TestCoroutineDispatcher import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.runBlockingTest import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.Engine
import mozilla.components.concept.storage.HistoryStorage import mozilla.components.concept.storage.HistoryStorage
import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.feature.tabs.TabsUseCases
@ -29,6 +31,7 @@ class DefaultDeleteBrowsingDataControllerTest {
private var removeAllTabs: TabsUseCases.RemoveAllTabsUseCase = mockk(relaxed = true) private var removeAllTabs: TabsUseCases.RemoveAllTabsUseCase = mockk(relaxed = true)
private var historyStorage: HistoryStorage = mockk(relaxed = true) private var historyStorage: HistoryStorage = mockk(relaxed = true)
private var permissionStorage: PermissionStorage = 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 val engine: Engine = mockk(relaxed = true)
private lateinit var controller: DefaultDeleteBrowsingDataController private lateinit var controller: DefaultDeleteBrowsingDataController
@ -38,6 +41,7 @@ class DefaultDeleteBrowsingDataControllerTest {
removeAllTabs = removeAllTabs, removeAllTabs = removeAllTabs,
historyStorage = historyStorage, historyStorage = historyStorage,
permissionStorage = permissionStorage, permissionStorage = permissionStorage,
iconsStorage = iconsStorage,
engine = engine, engine = engine,
coroutineContext = coroutineContext coroutineContext = coroutineContext
) )
@ -55,12 +59,14 @@ class DefaultDeleteBrowsingDataControllerTest {
@Test @Test
fun deleteBrowsingData() = runBlockingTest { fun deleteBrowsingData() = runBlockingTest {
controller = spyk(controller)
controller.deleteBrowsingData() controller.deleteBrowsingData()
verify { coVerify {
engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES)) engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES))
historyStorage.deleteEverything()
iconsStorage.clear()
} }
verify { launch { historyStorage.deleteEverything() } }
} }
@Test @Test

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

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

@ -3,5 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
object AndroidComponents { 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_ads_id_version = "16.0.0"
const val google_play_store_version = "1.8.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" const val airbnb_lottie = "3.4.0"
} }
@ -214,7 +213,6 @@ object Deps {
// Required for in-app reviews // Required for in-app reviews
const val google_play_store = "com.google.android.play:core:${Versions.google_play_store_version}" 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}" const val lottie = "com.airbnb.android:lottie:${Versions.airbnb_lottie}"

Loading…
Cancel
Save