1
0
Fork 0

Copione merged onto master
continuous-integration/drone/push Build is failing Details

master
blallo 2020-07-23 00:00:40 +02:00
commit 53660d7a41
62 changed files with 1662 additions and 267 deletions

View File

@ -174,19 +174,12 @@ tasks:
- index.mobile.v2.${project}.branch.${short_head_branch}.latest.taskgraph.decision
- index.mobile.v2.${project}.branch.${short_head_branch}.revision.${head_sha}.taskgraph.decision
- index.mobile.v2.${project}.revision.${head_sha}.taskgraph.decision
# TODO Bug 1631839: Remove the following routes once all consumers have migrated
- index.project.mobile.${project}.v2.branch.${short_head_branch}.latest.taskgraph.decision
- index.project.mobile.${project}.v2.branch.${short_head_branch}.revision.${head_sha}.taskgraph.decision
- $if: 'tasks_for == "cron"'
then:
# cron context provides ${head_branch} as a short one
- index.mobile.v2.${project}.branch.${head_branch}.latest.taskgraph.decision-${cron.job_name}
- index.mobile.v2.${project}.branch.${head_branch}.revision.${head_sha}.taskgraph.decision-${cron.job_name}
- index.mobile.v2.${project}.branch.${head_branch}.revision.${head_sha}.taskgraph.cron.${ownTaskId}
# TODO Bug 1631839: Remove the following routes once all consumers have migrated
- index.project.mobile.${project}.v2.branch.${head_branch}.latest.taskgraph.decision-${cron.job_name}
- index.project.mobile.${project}.v2.branch.${head_branch}.revision.${head_sha}.taskgraph.decision-${cron.job_name}
- index.project.mobile.${project}.v2.branch.${head_branch}.revision.${head_sha}.taskgraph.cron.${ownTaskId}
scopes:
$if: 'tasks_for == "github-push"'
then:

View File

@ -503,6 +503,7 @@ dependencies {
implementation Deps.androidx_lifecycle_viewmodel
implementation Deps.androidx_core
implementation Deps.androidx_core_ktx
implementation Deps.androidx_dynamic_animation
implementation Deps.androidx_transition
implementation Deps.androidx_work_ktx
implementation Deps.google_material

View File

@ -85,10 +85,10 @@ private fun assertShowSearchSuggestions() {
private fun assertShowSearchShortcuts() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Show search shortcuts"))
hasDescendant(withText("Show search engines"))
)
)
onView(withText("Show search shortcuts"))
onView(withText("Show search engines"))
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
@ -146,11 +146,11 @@ private fun toggleShowSearchSuggestions() {
private fun toggleShowSearchShortcuts() {
onView(withId(androidx.preference.R.id.recycler_view)).perform(
RecyclerViewActions.scrollTo<RecyclerView.ViewHolder>(
hasDescendant(withText("Show search shortcuts"))
hasDescendant(withText("Show search engines"))
)
)
onView(withText("Show search shortcuts"))
onView(withText("Show search engines"))
.perform(click())
}

View File

@ -5,7 +5,9 @@
import android.content.Context
import android.os.Bundle
import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper
import mozilla.components.browser.engine.gecko.ext.toContentBlockingSetting
import mozilla.components.browser.engine.gecko.glean.GeckoAdapter
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy
import mozilla.components.concept.storage.LoginsStorage
import mozilla.components.lib.crash.handler.CrashHandlerService
import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
@ -21,10 +23,11 @@ object GeckoProvider {
@Synchronized
fun getOrCreateRuntime(
context: Context,
storage: Lazy<LoginsStorage>
storage: Lazy<LoginsStorage>,
trackingProtectionPolicy: TrackingProtectionPolicy
): GeckoRuntime {
if (runtime == null) {
runtime = createRuntime(context, storage)
runtime = createRuntime(context, storage, trackingProtectionPolicy)
}
return runtime!!
@ -32,7 +35,8 @@ object GeckoProvider {
private fun createRuntime(
context: Context,
storage: Lazy<LoginsStorage>
storage: Lazy<LoginsStorage>,
policy: TrackingProtectionPolicy
): GeckoRuntime {
val builder = GeckoRuntimeSettings.Builder()
@ -44,6 +48,7 @@ object GeckoProvider {
val runtimeSettings = builder
.crashHandler(CrashHandlerService::class.java)
.telemetryDelegate(GeckoAdapter())
.contentBlocking(policy.toContentBlockingSetting())
.aboutConfigEnabled(Config.channel.isBeta)
.debugLogging(Config.channel.isDebug)
.build()

View File

@ -5,7 +5,9 @@
import android.content.Context
import android.os.Bundle
import mozilla.components.browser.engine.gecko.autofill.GeckoLoginDelegateWrapper
import mozilla.components.browser.engine.gecko.ext.toContentBlockingSetting
import mozilla.components.browser.engine.gecko.glean.GeckoAdapter
import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy
import mozilla.components.concept.storage.LoginsStorage
import mozilla.components.lib.crash.handler.CrashHandlerService
import mozilla.components.service.sync.logins.GeckoLoginStorageDelegate
@ -21,10 +23,11 @@ object GeckoProvider {
@Synchronized
fun getOrCreateRuntime(
context: Context,
storage: Lazy<LoginsStorage>
storage: Lazy<LoginsStorage>,
trackingProtectionPolicy: TrackingProtectionPolicy
): GeckoRuntime {
if (runtime == null) {
runtime = createRuntime(context, storage)
runtime = createRuntime(context, storage, trackingProtectionPolicy)
}
return runtime!!
@ -32,7 +35,8 @@ object GeckoProvider {
private fun createRuntime(
context: Context,
storage: Lazy<LoginsStorage>
storage: Lazy<LoginsStorage>,
policy: TrackingProtectionPolicy
): GeckoRuntime {
val builder = GeckoRuntimeSettings.Builder()
@ -44,6 +48,7 @@ object GeckoProvider {
val runtimeSettings = builder
.crashHandler(CrashHandlerService::class.java)
.telemetryDelegate(GeckoAdapter())
.contentBlocking(policy.toContentBlockingSetting())
.debugLogging(Config.channel.isDebug)
.aboutConfigEnabled(true)
.build()

View File

@ -24,4 +24,9 @@ object FeatureFlags {
* Enables new tab tray pref
*/
val tabTray = Config.channel.isNightlyOrDebug
/**
* Enables swipe on toolbar to switch tabs
*/
val swipeToSwitchTabs = Config.channel.isNightlyOrDebug
}

View File

@ -139,14 +139,7 @@ open class FenixApplication : LocaleAwareApplication() {
prefetchForHomeFragment()
setupLeakCanary()
if (settings().isTelemetryEnabled) {
components.analytics.metrics.start(MetricServiceType.Data)
}
if (settings().isMarketingTelemetryEnabled) {
components.analytics.metrics.start(MetricServiceType.Marketing)
}
startMetricsIfEnabled()
setupPush()
visibilityLifecycleCallback = VisibilityLifecycleCallback(getSystemService())
@ -158,49 +151,73 @@ open class FenixApplication : LocaleAwareApplication() {
// runStorageMaintenance()
// }
val taskQueue = components.performance.visualCompletenessQueue
registerActivityLifecycleCallbacks(
PerformanceActivityLifecycleCallbacks(taskQueue)
)
initVisualCompletenessQueueAndQueueTasks()
}
// Enable the service-experiments component to be initialized after visual completeness
// for performance wins.
if (settings().isExperimentationEnabled) {
taskQueue.runIfReadyOrQueue {
Experiments.initialize(
applicationContext = applicationContext,
onExperimentsUpdated = {
ExperimentsManager.initSearchWidgetExperiment(this)
},
configuration = mozilla.components.service.experiments.Configuration(
httpClient = components.core.client,
kintoEndpoint = KINTO_ENDPOINT_PROD
)
)
ExperimentsManager.initSearchWidgetExperiment(this)
}
} else {
// We should make a better way to opt out for when we have more experiments
// See https://github.com/mozilla-mobile/fenix/issues/6278
ExperimentsManager.optOutSearchWidgetExperiment(this)
private fun initVisualCompletenessQueueAndQueueTasks() {
val taskQueue = components.performance.visualCompletenessQueue
fun initQueue() {
registerActivityLifecycleCallbacks(PerformanceActivityLifecycleCallbacks(taskQueue))
}
components.performance.visualCompletenessQueue.runIfReadyOrQueue {
GlobalScope.launch(Dispatchers.IO) {
logger.info("Running post-visual completeness tasks...")
logElapsedTime(logger, "Storage initialization") {
components.core.historyStorage.warmUp()
components.core.bookmarksStorage.warmUp()
components.core.passwordsStorage.warmUp()
}
}
// Account manager initialization needs to happen on the main thread.
GlobalScope.launch(Dispatchers.Main) {
logElapsedTime(logger, "Kicking-off account manager") {
components.backgroundServices.accountManager
fun queueInitExperiments() {
if (settings().isExperimentationEnabled) {
taskQueue.runIfReadyOrQueue {
Experiments.initialize(
applicationContext = applicationContext,
onExperimentsUpdated = {
ExperimentsManager.initSearchWidgetExperiment(this)
},
configuration = mozilla.components.service.experiments.Configuration(
httpClient = components.core.client,
kintoEndpoint = KINTO_ENDPOINT_PROD
)
)
ExperimentsManager.initSearchWidgetExperiment(this)
}
} else {
// We should make a better way to opt out for when we have more experiments
// See https://github.com/mozilla-mobile/fenix/issues/6278
ExperimentsManager.optOutSearchWidgetExperiment(this)
}
}
fun queueInitStorageAndServices() {
components.performance.visualCompletenessQueue.runIfReadyOrQueue {
GlobalScope.launch(Dispatchers.IO) {
logger.info("Running post-visual completeness tasks...")
logElapsedTime(logger, "Storage initialization") {
components.core.historyStorage.warmUp()
components.core.bookmarksStorage.warmUp()
components.core.passwordsStorage.warmUp()
}
}
// Account manager initialization needs to happen on the main thread.
GlobalScope.launch(Dispatchers.Main) {
logElapsedTime(logger, "Kicking-off account manager") {
components.backgroundServices.accountManager
}
}
}
}
initQueue()
// We init these items in the visual completeness queue to avoid them initing in the critical
// startup path, before the UI finishes drawing (i.e. visual completeness).
queueInitExperiments()
queueInitStorageAndServices()
}
private fun startMetricsIfEnabled() {
if (settings().isTelemetryEnabled) {
components.analytics.metrics.start(MetricServiceType.Data)
}
if (settings().isMarketingTelemetryEnabled) {
components.analytics.metrics.start(MetricServiceType.Marketing)
}
}
// See https://github.com/mozilla-mobile/fenix/issues/7227 for context.
@ -231,6 +248,7 @@ open class FenixApplication : LocaleAwareApplication() {
components.core.topSiteStorage.prefetch()
}
}
private fun setupPush() {
// Sets the PushFeature as the singleton instance for push messages to go to.
// We need the push feature setup here to deliver messages in the case where the service

View File

@ -6,11 +6,14 @@ package org.mozilla.fenix
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.os.StrictMode
import android.text.format.DateUtils
import android.util.AttributeSet
import android.view.KeyEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.WindowManager
import androidx.annotation.CallSuper
import androidx.annotation.IdRes
@ -30,6 +33,8 @@ import kotlinx.android.synthetic.main.activity_home.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.session.SessionManager
@ -52,6 +57,7 @@ import mozilla.components.support.ktx.android.content.share
import mozilla.components.support.ktx.kotlin.isUrl
import mozilla.components.support.ktx.kotlin.toNormalizedUrl
import mozilla.components.support.locale.LocaleAwareAppCompatActivity
import mozilla.components.support.utils.RunWhenReadyQueue
import mozilla.components.support.utils.SafeIntent
import mozilla.components.support.utils.toSafeIntent
import mozilla.components.support.webextensions.WebExtensionPopupFeature
@ -97,7 +103,6 @@ import org.mozilla.fenix.theme.DefaultThemeManager
import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.trackingprotectionexceptions.TrackingProtectionExceptionsFragmentDirections
import org.mozilla.fenix.utils.BrowsersCache
import org.mozilla.fenix.utils.RunWhenReadyQueue
/**
* The main activity of the application. The application is primarily a single Activity (this one)
@ -139,6 +144,9 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
)
}
// See onKeyDown for why this is necessary
private var backLongPressJob: Job? = null
private lateinit var navigationToolbar: Toolbar
final override fun onCreate(savedInstanceState: Bundle?) {
@ -349,6 +357,50 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
super.onBackPressed()
}
private fun isAndroidN(): Boolean =
Build.VERSION.SDK_INT == Build.VERSION_CODES.N || Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1
private fun handleBackLongPress(): Boolean {
supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach {
if (it is OnBackLongPressedListener && it.onBackLongPressed()) {
return true
}
}
return false
}
final override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
// Inspired by https://searchfox.org/mozilla-esr68/source/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java#584-613
// Android N has broken passing onKeyLongPress events for the back button, so we
// instead implement the long press behavior ourselves
// - For short presses, we cancel the callback in onKeyUp
// - For long presses, the normal keypress is marked as cancelled, hence won't be handled elsewhere
// (but Android still provides the haptic feedback), and the long press action is run
if (isAndroidN() && keyCode == KeyEvent.KEYCODE_BACK) {
backLongPressJob = lifecycleScope.launch {
delay(ViewConfiguration.getLongPressTimeout().toLong())
handleBackLongPress()
}
}
return super.onKeyDown(keyCode, event)
}
final override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
if (isAndroidN() && keyCode == KeyEvent.KEYCODE_BACK) {
backLongPressJob?.cancel()
}
return super.onKeyUp(keyCode, event)
}
final override fun onKeyLongPress(keyCode: Int, event: KeyEvent?): Boolean {
// onKeyLongPress is broken in Android N so we don't handle back button long presses here
// for N. The version check ensures we don't handle back button long presses twice.
if (!isAndroidN() && keyCode == KeyEvent.KEYCODE_BACK) {
return handleBackLongPress()
}
return super.onKeyLongPress(keyCode, event)
}
final override fun onUserLeaveHint() {
supportFragmentManager.primaryNavigationFragment?.childFragmentManager?.fragments?.forEach {
if (it is UserInteractionHandler && it.onHomePressed()) {

View File

@ -0,0 +1,21 @@
/* 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
/**
* Interface for features and fragments that want to handle long presses of the system back button
*/
interface OnBackLongPressedListener {
/**
* Called when the system back button is long pressed.
*
* Note: This cannot be called when gesture navigation is enabled on Android 10+ due to system
* limitations.
*
* @return true if the event was handled
*/
fun onBackLongPressed(): Boolean
}

View File

@ -77,6 +77,7 @@ import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.OnBackLongPressedListener
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.readermode.DefaultReaderModeController
@ -116,7 +117,8 @@ import java.lang.ref.WeakReference
*/
@ExperimentalCoroutinesApi
@Suppress("TooManyFunctions", "LargeClass")
abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, SessionManager.Observer {
abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, SessionManager.Observer,
OnBackLongPressedListener {
private lateinit var browserFragmentStore: BrowserFragmentStore
private lateinit var browserAnimator: BrowserAnimator
@ -383,7 +385,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
tryAgain = downloadFeature::tryAgain,
onCannotOpenFile = {
FenixSnackbar.make(
view = view,
view = view.browserLayout,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true
)
@ -647,7 +649,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
val onCannotOpenFile = {
FenixSnackbar.make(
view = view,
view = view.browserLayout,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true
)
@ -757,6 +759,11 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
removeSessionIfNeeded()
}
override fun onBackLongPressed(): Boolean {
findNavController().navigate(R.id.action_global_tabHistoryDialogFragment)
return true
}
/**
* Saves the external app session ID to be restored later in [onViewStateRestored].
*/
@ -925,7 +932,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
view?.let { view ->
FenixSnackbar.make(
view = view,
view = view.browserLayout,
duration = FenixSnackbar.LENGTH_LONG,
isDisplayedWithBrowserToolbar = true
)
@ -971,7 +978,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Session
// Close find in page bar if opened
findInPageIntegration.onBackPressed()
FenixSnackbar.make(
view = requireView(),
view = requireView().browserLayout,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = false
)

View File

@ -15,6 +15,7 @@ import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_browser.*
import kotlinx.android.synthetic.main.fragment_browser.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session
@ -29,6 +30,7 @@ import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tabs.WindowFeature
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.addons.runIfFragmentIsAttached
import org.mozilla.fenix.components.FenixSnackbar
@ -66,11 +68,24 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
return view
}
@Suppress("LongMethod")
override fun initializeUI(view: View): Session? {
val context = requireContext()
val components = context.components
return super.initializeUI(view)?.also {
if (FeatureFlags.swipeToSwitchTabs) {
gestureLayout.addGestureListener(
ToolbarGestureHandler(
activity = requireActivity(),
contentLayout = browserLayout,
tabPreview = tabPreview,
toolbarLayout = browserToolbarView.view,
sessionManager = components.core.sessionManager
)
)
}
val readerModeAction =
BrowserToolbar.ToggleButton(
image = ContextCompat.getDrawable(requireContext(), R.drawable.ic_readermode)!!,
@ -243,7 +258,7 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
}
}
FenixSnackbar.make(
view = view,
view = view.browserLayout,
duration = Snackbar.LENGTH_SHORT,
isDisplayedWithBrowserToolbar = true
)

View File

@ -0,0 +1,136 @@
/* 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.browser
import android.content.Context
import android.graphics.PointF
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.MotionEvent
import android.widget.FrameLayout
import androidx.core.view.GestureDetectorCompat
/**
* Interface that allows intercepting and handling swipe gestures received in a [SwipeGestureLayout].
*/
interface SwipeGestureListener {
/**
* Called when the [SwipeGestureLayout] detects the start of a swipe gesture. The listener
* should return true if it wants to handle the swipe gesture. If the listener returns false
* it will not receive any callbacks for future events that the swipe produces.
*
* @param start the initial point where the gesture started
* @param next the next point in the gesture
*/
fun onSwipeStarted(start: PointF, next: PointF): Boolean
/**
* Called when the swipe gesture receives a new event.
*
* @param distanceX the change along the x-axis since the last swipe update
* @param distanceY the change along the y-axis since the last swipe update
*/
fun onSwipeUpdate(distanceX: Float, distanceY: Float)
/**
* Called when the user finishes the swipe gesture (ie lifts their finger off the screen)
*
* @param velocityX the velocity of the swipe along the x-axis
* @param velocityY the velocity of the swipe along the y-axis
*/
fun onSwipeFinished(velocityX: Float, velocityY: Float)
}
/**
* A [FrameLayout] that allows listeners to intercept and handle swipe events.
*
* Listeners are called in the order they are added and the first listener to intercept a swipe event
* is the only listener that will receive events for the duration of that swipe.
*/
class SwipeGestureLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
override fun onDown(e: MotionEvent?): Boolean {
return true
}
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent?,
distanceX: Float,
distanceY: Float
): Boolean {
val start = e1?.let { event -> PointF(event.rawX, event.rawY) } ?: return false
val next = e2?.let { event -> PointF(event.rawX, event.rawY) } ?: return false
if (activeListener == null && !handledInitialScroll) {
activeListener = listeners.firstOrNull { listener ->
listener.onSwipeStarted(start, next)
}
handledInitialScroll = true
}
activeListener?.onSwipeUpdate(distanceX, distanceY)
return activeListener != null
}
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent?,
velocityX: Float,
velocityY: Float
): Boolean {
activeListener?.onSwipeFinished(velocityX, velocityY)
return if (activeListener != null) {
activeListener = null
true
} else {
false
}
}
}
private val gestureDetector = GestureDetectorCompat(context, gestureListener)
private val listeners = mutableListOf<SwipeGestureListener>()
private var activeListener: SwipeGestureListener? = null
private var handledInitialScroll = false
fun addGestureListener(listener: SwipeGestureListener) {
listeners.add(listener)
}
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
return when (event?.actionMasked) {
MotionEvent.ACTION_DOWN -> {
handledInitialScroll = false
gestureDetector.onTouchEvent(event)
false
}
else -> gestureDetector.onTouchEvent(event)
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
return when (event?.actionMasked) {
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
gestureDetector.onTouchEvent(event)
// If the active listener is not null here, then we haven't detected a fling
// so notify the listener that the swipe was finished with 0 velocity
activeListener?.onSwipeFinished(
velocityX = 0f,
velocityY = 0f
)
activeListener = null
false
}
else -> gestureDetector.onTouchEvent(event)
}
}
}

View File

@ -0,0 +1,70 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.browser
import android.content.Context
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.doOnNextLayout
import androidx.core.view.updateLayoutParams
import kotlinx.android.synthetic.main.mozac_ui_tabcounter_layout.view.*
import kotlinx.android.synthetic.main.tab_preview.view.*
import mozilla.components.browser.thumbnails.loader.ThumbnailLoader
import mozilla.components.support.images.ImageLoadRequest
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.theme.ThemeManager
import kotlin.math.max
class TabPreview @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : FrameLayout(context, attrs, defStyle) {
private val thumbnailLoader = ThumbnailLoader(context.components.core.thumbnailStorage)
init {
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.tab_preview, this, true)
if (!context.settings().shouldUseBottomToolbar) {
fakeToolbar.updateLayoutParams<LayoutParams> {
gravity = Gravity.TOP
}
fakeToolbar.background = ResourcesCompat.getDrawable(
resources,
ThemeManager.resolveAttribute(R.attr.bottomBarBackgroundTop, context),
null
)
}
// Change view properties to avoid confusing the UI tests
tab_button.counter_box.id = View.NO_ID
tab_button.counter_text.id = View.NO_ID
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
previewThumbnail.translationY = if (!context.settings().shouldUseBottomToolbar) {
fakeToolbar.height.toFloat()
} else {
0f
}
}
fun loadPreviewThumbnail(thumbnailId: String) {
doOnNextLayout {
val thumbnailSize = max(previewThumbnail.height, previewThumbnail.width)
thumbnailLoader.loadIntoView(previewThumbnail, ImageLoadRequest(thumbnailId, thumbnailSize))
}
}
}

View File

@ -0,0 +1,356 @@
/* 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.browser
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.app.Activity
import android.graphics.PointF
import android.graphics.Rect
import android.os.Build
import android.util.TypedValue
import android.view.View
import android.view.ViewConfiguration
import androidx.annotation.Dimension
import androidx.annotation.Dimension.DP
import androidx.core.graphics.contains
import androidx.core.graphics.toPoint
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FlingAnimation
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.support.ktx.android.util.dpToPx
import mozilla.components.support.ktx.android.view.getRectWithViewLocation
import org.mozilla.fenix.ext.sessionsOfType
import org.mozilla.fenix.ext.settings
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
/**
* Handles intercepting touch events on the toolbar for swipe gestures and executes the
* necessary animations.
*/
@Suppress("LargeClass", "TooManyFunctions")
class ToolbarGestureHandler(
private val activity: Activity,
private val contentLayout: View,
private val tabPreview: TabPreview,
private val toolbarLayout: View,
private val sessionManager: SessionManager
) : SwipeGestureListener {
private enum class GestureDirection {
LEFT_TO_RIGHT, RIGHT_TO_LEFT
}
private sealed class Destination {
data class Tab(val session: Session) : Destination()
object None : Destination()
}
private val windowWidth: Int
get() = activity.resources.displayMetrics.widthPixels
private val windowInsets: WindowInsetsCompat?
get() =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
// In theory, the rootWindowInsets should exist at this point but if the decorView is
// not attached for some reason we'll get a NullPointerException without the check.
activity.window.decorView.rootWindowInsets?.let {
WindowInsetsCompat.toWindowInsetsCompat(it)
}
} else {
null
}
private val previewOffset = PREVIEW_OFFSET.dpToPx(activity.resources.displayMetrics)
private val touchSlop = ViewConfiguration.get(activity).scaledTouchSlop
private val minimumFlingVelocity = ViewConfiguration.get(activity).scaledMinimumFlingVelocity
private val defaultVelocity = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
MINIMUM_ANIMATION_VELOCITY,
activity.resources.displayMetrics
)
private var gestureDirection = GestureDirection.LEFT_TO_RIGHT
override fun onSwipeStarted(start: PointF, next: PointF): Boolean {
val dx = next.x - start.x
val dy = next.y - start.y
gestureDirection = if (dx < 0) {
GestureDirection.RIGHT_TO_LEFT
} else {
GestureDirection.LEFT_TO_RIGHT
}
return if (start.isInToolbar() && abs(dx) > touchSlop && abs(dy) < abs(dx)) {
preparePreview(getDestination())
true
} else {
false
}
}
override fun onSwipeUpdate(distanceX: Float, distanceY: Float) {
when (getDestination()) {
is Destination.Tab -> {
// Restrict the range of motion for the views so you can't start a swipe in one direction
// then move your finger far enough in the other direction and make the content visually
// start sliding off screen the other way.
tabPreview.translationX = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> min(
windowWidth.toFloat() + previewOffset,
tabPreview.translationX - distanceX
)
GestureDirection.LEFT_TO_RIGHT -> max(
-windowWidth.toFloat() - previewOffset,
tabPreview.translationX - distanceX
)
}
contentLayout.translationX = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> min(
0f,
contentLayout.translationX - distanceX
)
GestureDirection.LEFT_TO_RIGHT -> max(
0f,
contentLayout.translationX - distanceX
)
}
}
is Destination.None -> {
// If there is no "next" tab to swipe to in the gesture direction, only do a
// partial animation to show that we are at the end of the tab list
val maxContentHidden = contentLayout.width * OVERSCROLL_HIDE_PERCENT
contentLayout.translationX = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> max(
-maxContentHidden.toFloat(),
contentLayout.translationX - distanceX
).coerceAtMost(0f)
GestureDirection.LEFT_TO_RIGHT -> min(
maxContentHidden.toFloat(),
contentLayout.translationX - distanceX
).coerceAtLeast(0f)
}
}
}
}
override fun onSwipeFinished(
velocityX: Float,
velocityY: Float
) {
val destination = getDestination()
if (destination is Destination.Tab && isGestureComplete(velocityX)) {
animateToNextTab(velocityX, destination.session)
} else {
animateCanceledGesture(velocityX)
}
}
private fun createFlingAnimation(
view: View,
minValue: Float,
maxValue: Float,
startVelocity: Float
): FlingAnimation =
FlingAnimation(view, DynamicAnimation.TRANSLATION_X).apply {
setMinValue(minValue)
setMaxValue(maxValue)
setStartVelocity(startVelocity)
friction = ViewConfiguration.getScrollFriction()
}
private fun getDestination(): Destination {
val isLtr = activity.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_LTR
val currentSession = sessionManager.selectedSession ?: return Destination.None
val currentIndex = sessionManager.sessionsOfType(currentSession.private).indexOfFirst {
it.id == currentSession.id
}
return if (currentIndex == -1) {
Destination.None
} else {
val sessions = sessionManager.sessionsOfType(currentSession.private)
val index = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> if (isLtr) {
currentIndex + 1
} else {
currentIndex - 1
}
GestureDirection.LEFT_TO_RIGHT -> if (isLtr) {
currentIndex - 1
} else {
currentIndex + 1
}
}
if (index < sessions.count() && index >= 0) {
Destination.Tab(sessions.elementAt(index))
} else {
Destination.None
}
}
}
private fun preparePreview(destination: Destination) {
val thumbnailId = when (destination) {
is Destination.Tab -> destination.session.id
is Destination.None -> return
}
tabPreview.loadPreviewThumbnail(thumbnailId)
tabPreview.alpha = 1f
tabPreview.translationX = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> windowWidth.toFloat() + previewOffset
GestureDirection.LEFT_TO_RIGHT -> -windowWidth.toFloat() - previewOffset
}
tabPreview.isVisible = true
}
/**
* Checks if the gesture is complete based on the position of tab preview and the velocity of
* the gesture. A completed gesture means the user has indicated they want to swipe to the next
* tab. The gesture is considered complete if one of the following is true:
*
* 1. The user initiated a fling in the same direction as the initial movement
* 2. There is no fling initiated, but the percentage of the tab preview shown is at least
* [GESTURE_FINISH_PERCENT]
*
* If the user initiated a fling in the opposite direction of the initial movement, the
* gesture is always considered incomplete.
*/
private fun isGestureComplete(velocityX: Float): Boolean {
val previewWidth = tabPreview.getRectWithViewLocation().visibleWidth.toDouble()
val velocityMatchesDirection = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> velocityX <= 0
GestureDirection.LEFT_TO_RIGHT -> velocityX >= 0
}
val reverseFling =
abs(velocityX) >= minimumFlingVelocity && !velocityMatchesDirection
return !reverseFling && (previewWidth / windowWidth >= GESTURE_FINISH_PERCENT ||
abs(velocityX) >= minimumFlingVelocity)
}
private fun getVelocityFromFling(velocityX: Float): Float {
return max(abs(velocityX), defaultVelocity)
}
private fun animateToNextTab(velocityX: Float, session: Session) {
val browserFinalXCoordinate: Float = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> -windowWidth.toFloat() - previewOffset
GestureDirection.LEFT_TO_RIGHT -> windowWidth.toFloat() + previewOffset
}
val animationVelocity = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> -getVelocityFromFling(velocityX)
GestureDirection.LEFT_TO_RIGHT -> getVelocityFromFling(velocityX)
}
// Finish animating the contentLayout off screen and tabPreview on screen
createFlingAnimation(
view = contentLayout,
minValue = min(0f, browserFinalXCoordinate),
maxValue = max(0f, browserFinalXCoordinate),
startVelocity = animationVelocity
).addUpdateListener { _, value, _ ->
tabPreview.translationX = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset
GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset
}
}.addEndListener { _, _, _, _ ->
contentLayout.translationX = 0f
sessionManager.select(session)
// Fade out the tab preview to prevent flickering
val shortAnimationDuration =
activity.resources.getInteger(android.R.integer.config_shortAnimTime)
tabPreview.animate()
.alpha(0f)
.setDuration(shortAnimationDuration.toLong())
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator?) {
tabPreview.isVisible = false
}
})
}.start()
}
private fun animateCanceledGesture(gestureVelocity: Float) {
val velocity = if (getDestination() is Destination.None) {
defaultVelocity
} else {
getVelocityFromFling(gestureVelocity)
}.let { v ->
when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> v
GestureDirection.LEFT_TO_RIGHT -> -v
}
}
createFlingAnimation(
view = contentLayout,
minValue = min(0f, contentLayout.translationX),
maxValue = max(0f, contentLayout.translationX),
startVelocity = velocity
).addUpdateListener { _, value, _ ->
tabPreview.translationX = when (gestureDirection) {
GestureDirection.RIGHT_TO_LEFT -> value + windowWidth + previewOffset
GestureDirection.LEFT_TO_RIGHT -> value - windowWidth - previewOffset
}
}.addEndListener { _, _, _, _ ->
tabPreview.isVisible = false
}.start()
}
private fun PointF.isInToolbar(): Boolean {
val toolbarLocation = toolbarLayout.getRectWithViewLocation()
// In Android 10, the system gesture touch area overlaps the bottom of the toolbar, so
// lets make our swipe area taller by that amount
windowInsets?.let { insets ->
if (activity.settings().shouldUseBottomToolbar) {
toolbarLocation.top -= (insets.mandatorySystemGestureInsets.bottom - insets.stableInsetBottom)
}
}
return toolbarLocation.contains(toPoint())
}
private val Rect.visibleWidth: Int
get() = if (left < 0) {
right
} else {
windowWidth - left
}
companion object {
/**
* The percentage of the tab preview that needs to be visible to consider the
* tab switching gesture complete.
*/
private const val GESTURE_FINISH_PERCENT = 0.25
/**
* The percentage of the content view that can be hidden by the tab switching gesture if
* there is not tab available to switch to
*/
private const val OVERSCROLL_HIDE_PERCENT = 0.20
/**
* The speed of the fling animation (in dp per second).
*/
@Dimension(unit = DP)
private const val MINIMUM_ANIMATION_VELOCITY = 1500f
/**
* The size of the gap between the tab preview and content layout.
*/
@Dimension(unit = DP)
private const val PREVIEW_OFFSET = 48
}
}

View File

@ -30,6 +30,7 @@ import mozilla.components.service.fxa.manager.SCOPE_SYNC
import mozilla.components.service.fxa.manager.SyncEnginesStorage
import mozilla.components.service.fxa.sync.GlobalSyncableStoreProvider
import mozilla.components.service.sync.logins.SyncableLoginsStorage
import mozilla.components.support.utils.RunWhenReadyQueue
import org.mozilla.fenix.Config
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
@ -39,7 +40,6 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.sync.SyncedTabsIntegration
import org.mozilla.fenix.utils.Mockable
import org.mozilla.fenix.utils.RunWhenReadyQueue
import org.mozilla.fenix.utils.Settings
/**

View File

@ -86,7 +86,11 @@ class Core(private val context: Context) {
GeckoEngine(
context,
defaultSettings,
GeckoProvider.getOrCreateRuntime(context, lazyPasswordsStorage)
GeckoProvider.getOrCreateRuntime(
context,
lazyPasswordsStorage,
trackingProtectionPolicyFactory.createTrackingProtectionPolicy()
)
).also {
WebCompatFeature.install(it)
@ -108,7 +112,11 @@ class Core(private val context: Context) {
val client: Client by lazy {
GeckoViewFetchClient(
context,
GeckoProvider.getOrCreateRuntime(context, lazyPasswordsStorage)
GeckoProvider.getOrCreateRuntime(
context,
lazyPasswordsStorage,
trackingProtectionPolicyFactory.createTrackingProtectionPolicy()
)
)
}

View File

@ -4,7 +4,7 @@
package org.mozilla.fenix.components
import org.mozilla.fenix.utils.RunWhenReadyQueue
import mozilla.components.support.utils.RunWhenReadyQueue
/**
* Component group for all functionality related to performance.

View File

@ -170,7 +170,13 @@ class DefaultBrowserToolbarController(
Do exhaustive when (item) {
ToolbarMenu.Item.Back -> sessionUseCases.goBack.invoke(currentSession)
ToolbarMenu.Item.Forward -> sessionUseCases.goForward.invoke(currentSession)
is ToolbarMenu.Item.Forward -> {
if (item.viewHistory) {
navController.navigate(R.id.action_global_tabHistoryDialogFragment)
} else {
sessionUseCases.goForward.invoke(currentSession)
}
}
ToolbarMenu.Item.Reload -> sessionUseCases.reload.invoke(currentSession)
ToolbarMenu.Item.Stop -> sessionUseCases.stopLoading.invoke(currentSession)
ToolbarMenu.Item.Settings -> browserAnimator.captureEngineViewAndDrawStatically {
@ -333,7 +339,7 @@ class DefaultBrowserToolbarController(
private fun trackToolbarItemInteraction(item: ToolbarMenu.Item) {
val eventItem = when (item) {
ToolbarMenu.Item.Back -> Event.BrowserMenuItemTapped.Item.BACK
ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD
is ToolbarMenu.Item.Forward -> Event.BrowserMenuItemTapped.Item.FORWARD
ToolbarMenu.Item.Reload -> Event.BrowserMenuItemTapped.Item.RELOAD
ToolbarMenu.Item.Stop -> Event.BrowserMenuItemTapped.Item.STOP
ToolbarMenu.Item.Settings -> Event.BrowserMenuItemTapped.Item.SETTINGS

View File

@ -82,9 +82,10 @@ class DefaultToolbarMenu(
session?.canGoForward ?: true
},
secondaryImageTintResource = ThemeManager.resolveAttribute(R.attr.disabled, context),
disableInSecondaryState = true
disableInSecondaryState = true,
longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = true)) }
) {
onItemTapped.invoke(ToolbarMenu.Item.Forward)
onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = false))
}
val refresh = BrowserMenuItemToolbar.TwoStateButton(

View File

@ -14,7 +14,7 @@ interface ToolbarMenu {
object FindInPage : Item()
object Share : Item()
object Back : Item()
object Forward : Item()
data class Forward(val viewHistory: Boolean) : Item()
object Reload : Item()
object Stop : Item()
object OpenInFenix : Item()

View File

@ -75,9 +75,10 @@ class CustomTabToolbarMenu(
R.attr.disabled,
context
),
disableInSecondaryState = true
disableInSecondaryState = true,
longClickListener = { onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = true)) }
) {
onItemTapped.invoke(ToolbarMenu.Item.Forward)
onItemTapped.invoke(ToolbarMenu.Item.Forward(viewHistory = false))
}
val refresh = BrowserMenuItemToolbar.TwoStateButton(

View File

@ -119,10 +119,10 @@ class AwesomeBarView(
bookmarksStorageSuggestionProvider =
BookmarksStorageSuggestionProvider(
components.core.bookmarksStorage,
loadUrlUseCase,
components.core.icons,
engineForSpeculativeConnects
bookmarksStorage = components.core.bookmarksStorage,
loadUrlUseCase = loadUrlUseCase,
icons = components.core.icons,
engine = engineForSpeculativeConnects
)
val searchBitmap = getDrawable(context, R.drawable.ic_search)!!.apply {

View File

@ -7,11 +7,11 @@ package org.mozilla.fenix.session
import android.app.Activity
import android.app.Application
import android.os.Bundle
import mozilla.components.support.utils.RunWhenReadyQueue
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.IntentReceiverActivity
import org.mozilla.fenix.browser.BrowserPerformanceTestActivity
import org.mozilla.fenix.settings.account.AuthIntentReceiverActivity
import org.mozilla.fenix.utils.RunWhenReadyQueue
import org.mozilla.fenix.widget.VoiceSearchActivity
/**

View File

@ -10,13 +10,11 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.core.content.pm.PackageInfoCompat
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DividerItemDecoration
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
import kotlinx.android.synthetic.main.fragment_about.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.HomeActivity
@ -39,10 +37,9 @@ import org.mozilla.geckoview.BuildConfig as GeckoViewBuildConfig
* Displays the logo and information about the app, including library versions.
*/
class AboutFragment : Fragment(), AboutPageListener {
private lateinit var appName: String
private val aboutPageAdapter: AboutPageAdapter = AboutPageAdapter(this)
private var secretDebugMenuClicks = 0
private var lastDebugMenuToast: Toast? = null
override fun onCreateView(
inflater: LayoutInflater,
@ -56,15 +53,7 @@ class AboutFragment : Fragment(), AboutPageListener {
return rootView
}
override fun onResume() {
super.onResume()
secretDebugMenuClicks = 0
}
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
about_list.run {
adapter = aboutPageAdapter
addItemDecoration(
@ -75,33 +64,10 @@ class AboutFragment : Fragment(), AboutPageListener {
)
}
// 5 taps on the logo activate the "secret" debug menu.
wordmark.setOnClickListener {
// Because the user will mostly likely tap the logo in rapid succession,
// we ensure only 1 toast is shown at any given time.
lastDebugMenuToast?.let { toast -> toast.cancel() }
secretDebugMenuClicks += 1
when (secretDebugMenuClicks) {
in 2 until SECRET_DEBUG_MENU_CLICKS -> {
val clicksLeft = SECRET_DEBUG_MENU_CLICKS - secretDebugMenuClicks
val toast = Toast.makeText(
context,
getString(R.string.about_debug_menu_toast_progress, clicksLeft),
Toast.LENGTH_SHORT
)
toast.show()
lastDebugMenuToast = toast
}
SECRET_DEBUG_MENU_CLICKS -> {
Toast.makeText(
context,
getString(R.string.about_debug_menu_toast_done),
Toast.LENGTH_LONG
).show()
requireContext().settings().showSecretDebugMenuThisSession = true
}
}
}
lifecycle.addObserver(SecretDebugMenuTrigger(
logoView = wordmark,
settings = view.context.settings()
))
populateAboutHeader()
aboutPageAdapter.submitList(populateAboutList())
@ -233,7 +199,5 @@ class AboutFragment : Fragment(), AboutPageListener {
companion object {
private const val ABOUT_LICENSE_URL = "about:license"
// Number of clicks on the app logo to enable the "secret" debug menu.
private const val SECRET_DEBUG_MENU_CLICKS = 5
}
}

View File

@ -0,0 +1,71 @@
/* 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.settings.about
import android.view.View
import android.widget.Toast
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import org.mozilla.fenix.R
import org.mozilla.fenix.utils.Settings
/**
* Triggers the "secret" debug menu when logoView is tapped 5 times.
*/
class SecretDebugMenuTrigger(
logoView: View,
private val settings: Settings
) : View.OnClickListener, LifecycleObserver {
private var secretDebugMenuClicks = 0
private var lastDebugMenuToast: Toast? = null
init {
if (!settings.showSecretDebugMenuThisSession) {
logoView.setOnClickListener(this)
}
}
/**
* Reset the [secretDebugMenuClicks] counter.
*/
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun clearClickCounter() {
secretDebugMenuClicks = 0
}
override fun onClick(v: View) {
// Because the user will mostly likely tap the logo in rapid succession,
// we ensure only 1 toast is shown at any given time.
lastDebugMenuToast?.cancel()
secretDebugMenuClicks += 1
when (secretDebugMenuClicks) {
in 2 until SECRET_DEBUG_MENU_CLICKS -> {
val clicksLeft = SECRET_DEBUG_MENU_CLICKS - secretDebugMenuClicks
val toast = Toast.makeText(
v.context,
v.context.getString(R.string.about_debug_menu_toast_progress, clicksLeft),
Toast.LENGTH_SHORT
)
toast.show()
lastDebugMenuToast = toast
}
SECRET_DEBUG_MENU_CLICKS -> {
Toast.makeText(
v.context,
R.string.about_debug_menu_toast_done,
Toast.LENGTH_LONG
).show()
settings.showSecretDebugMenuThisSession = true
}
}
}
companion object {
// Number of clicks on the app logo to enable the "secret" debug menu.
private const val SECRET_DEBUG_MENU_CLICKS = 5
}
}

View File

@ -37,6 +37,7 @@ class SyncedTabsFragment : LibraryPageFragment<Tab>() {
syncedTabsFeature.set(
feature = SyncedTabsFeature(
context = requireContext(),
storage = backgroundServices.syncedTabsStorage,
accountManager = backgroundServices.accountManager,
view = synced_tabs_layout,

View File

@ -0,0 +1,40 @@
/* 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.tabhistory
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.R
data class TabHistoryItem(
val title: String,
val url: String,
val index: Int,
val isSelected: Boolean
)
class TabHistoryAdapter(
private val interactor: TabHistoryViewInteractor
) : RecyclerView.Adapter<TabHistoryViewHolder>() {
var historyList: List<TabHistoryItem> = emptyList()
set(value) {
field = value
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TabHistoryViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.history_list_item, parent, false)
return TabHistoryViewHolder(view, interactor)
}
override fun onBindViewHolder(holder: TabHistoryViewHolder, position: Int) {
holder.bind(historyList[position])
}
override fun getItemCount(): Int = historyList.size
}

View File

@ -0,0 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.tabhistory
import androidx.navigation.NavController
import mozilla.components.feature.session.SessionUseCases
import org.mozilla.fenix.R
interface TabHistoryController {
fun handleGoToHistoryItem(item: TabHistoryItem)
}
class DefaultTabHistoryController(
private val navController: NavController,
private val goToHistoryIndexUseCase: SessionUseCases.GoToHistoryIndexUseCase
) : TabHistoryController {
override fun handleGoToHistoryItem(item: TabHistoryItem) {
navController.popBackStack(R.id.browserFragment, false)
goToHistoryIndexUseCase.invoke(item.index)
}
}

View File

@ -0,0 +1,56 @@
/* 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.tabhistory
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.navigation.fragment.findNavController
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.android.synthetic.main.fragment_tab_history_dialog.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.lib.state.ext.consumeFrom
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents
class TabHistoryDialogFragment : BottomSheetDialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, R.style.BottomSheet)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_tab_history_dialog, container, false)
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val controller = DefaultTabHistoryController(
navController = findNavController(),
goToHistoryIndexUseCase = requireComponents.useCases.sessionUseCases.goToHistoryIndex
)
val tabHistoryView = TabHistoryView(
container = tabHistoryLayout,
expandDialog = ::expand,
interactor = TabHistoryInteractor(controller)
)
consumeFrom(requireComponents.core.store) {
tabHistoryView.updateState(it)
}
}
private fun expand() {
(dialog as BottomSheetDialog).behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}

View File

@ -0,0 +1,14 @@
/* 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.tabhistory
class TabHistoryInteractor(
private val controller: TabHistoryController
) : TabHistoryViewInteractor {
override fun goToHistoryItem(item: TabHistoryItem) {
controller.handleGoToHistoryItem(item)
}
}

View File

@ -0,0 +1,79 @@
/* 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.tabhistory
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.component_tabhistory.*
import mozilla.components.browser.state.selector.selectedTab
import mozilla.components.browser.state.state.BrowserState
import org.mozilla.fenix.R
interface TabHistoryViewInteractor {
/**
* Jump to a specific index in the tab's history.
*/
fun goToHistoryItem(item: TabHistoryItem)
}
class TabHistoryView(
private val container: ViewGroup,
private val expandDialog: () -> Unit,
interactor: TabHistoryViewInteractor
) : LayoutContainer {
override val containerView: View?
get() = container
val view: View = LayoutInflater.from(container.context)
.inflate(R.layout.component_tabhistory, container, true)
private val adapter = TabHistoryAdapter(interactor)
private val layoutManager = object : LinearLayoutManager(view.context) {
override fun onLayoutCompleted(state: RecyclerView.State?) {
super.onLayoutCompleted(state)
currentIndex?.let { index ->
// Force expansion of the dialog, otherwise scrolling to the current history item
// won't work when its position is near the bottom of the recyclerview.
expandDialog.invoke()
// Also, attempt to center the current history item.
val itemView = tabHistoryRecyclerView.findViewHolderForLayoutPosition(
findFirstCompletelyVisibleItemPosition()
)?.itemView
val offset = tabHistoryRecyclerView.height / 2 - (itemView?.height ?: 0) / 2
scrollToPositionWithOffset(index, offset)
}
}
}.apply {
reverseLayout = true
}
private var currentIndex: Int? = null
init {
tabHistoryRecyclerView.adapter = adapter
tabHistoryRecyclerView.layoutManager = layoutManager
}
fun updateState(state: BrowserState) {
state.selectedTab?.content?.history?.let { historyState ->
currentIndex = historyState.currentIndex
val items = historyState.items.mapIndexed { index, historyItem ->
TabHistoryItem(
title = historyItem.title,
url = historyItem.uri,
index = index,
isSelected = index == historyState.currentIndex
)
}
adapter.historyList = items
}
}
}

View File

@ -0,0 +1,34 @@
/* 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.tabhistory
import android.view.View
import androidx.core.text.bold
import androidx.core.text.buildSpannedString
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.history_list_item.view.*
class TabHistoryViewHolder(
private val view: View,
private val interactor: TabHistoryViewInteractor
) : RecyclerView.ViewHolder(view) {
fun bind(item: TabHistoryItem) {
view.history_layout.overflowView.isVisible = false
view.history_layout.urlView.text = item.url
view.history_layout.loadFavicon(item.url)
view.history_layout.titleView.text = if (item.isSelected) {
buildSpannedString {
bold { append(item.title) }
}
} else {
item.title
}
view.setOnClickListener { interactor.goToHistoryItem(item) }
}
}

View File

@ -1,57 +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 kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicBoolean
/**
* A queue that acts as a gate, either executing tasks right away if the queue is marked as "ready",
* i.e. gate is open, or queues them to be executed whenever the queue is marked as ready in the
* future, i.e. gate becomes open.
*/
class RunWhenReadyQueue {
private val tasks = CopyOnWriteArrayList<() -> Unit>()
private val isReady = AtomicBoolean(false)
/**
* Was this queue ever marked as 'ready' via a call to [ready]?
*
* @return Boolean value indicating if this queue is 'ready'.
*/
fun isReady(): Boolean = isReady.get()
/**
* Runs the [task] if this queue is marked as ready, or queues it for later execution.
* Task will be executed on the main thread.
*
* @param task: The task to run now if queue is ready or queue for later execution.
*/
fun runIfReadyOrQueue(task: () -> Unit) {
if (isReady.get()) {
CoroutineScope(Dispatchers.Main).launch { task.invoke() }
} else {
tasks.add(task)
}
}
/**
* Mark queue as ready. Pending tasks will execute, and all tasks passed to [runIfReadyOrQueue]
* after this point will be executed immediately.
*/
fun ready() {
// Make sure that calls to `ready` are idempotent.
if (!isReady.compareAndSet(false, true)) {
return
}
CoroutineScope(Dispatchers.Main).launch {
tasks.forEach { it.invoke() }.also { tasks.clear() }
}
}
}

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/tabHistoryWrapper"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<View
android:id="@+id/handle"
android:layout_width="0dp"
android:layout_height="3dp"
android:layout_marginTop="8dp"
android:background="@color/secondary_text_normal_theme"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.1" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/tabHistoryRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="19dp"
tools:listitem="@layout/history_list_item" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -2,51 +2,66 @@
<!-- 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/. -->
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<org.mozilla.fenix.browser.SwipeGestureLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/browserLayout"
android:id="@+id/gestureLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="browser.BrowserFragment">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/browserLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<mozilla.components.concept.engine.EngineView
android:id="@+id/engineView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<ViewStub
android:id="@+id/stubFindInPage"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_gravity="bottom"
android:inflatedId="@+id/findInPageView"
android:layout="@layout/stub_find_in_page" />
<include
android:id="@+id/viewDynamicDownloadDialog"
layout="@layout/download_dialog_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:visibility="gone" />
<mozilla.components.feature.readerview.view.ReaderViewControlsBar
android:id="@+id/readerViewControlsBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?foundation"
android:elevation="24dp"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<org.mozilla.fenix.browser.TabPreview
android:id="@+id/tabPreview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<mozilla.components.concept.engine.EngineView
android:id="@+id/engineView"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<ViewStub
android:id="@+id/stubFindInPage"
android:inflatedId="@+id/findInPageView"
android:layout="@layout/stub_find_in_page"
android:layout_gravity="bottom"
android:layout_width="match_parent"
android:layout_height="56dp" />
<include
android:id="@+id/viewDynamicDownloadDialog"
layout="@layout/download_dialog_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:visibility="gone"/>
<mozilla.components.feature.readerview.view.ReaderViewControlsBar
android:id="@+id/readerViewControlsBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="?foundation"
android:elevation="24dp"
android:clickable="false"
android:focusable="false"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</org.mozilla.fenix.browser.SwipeGestureLayout>

View File

@ -147,7 +147,7 @@
android:layout_marginTop="@dimen/search_fragment_shortcuts_label_margin_vertical"
android:layout_marginEnd="@dimen/search_fragment_shortcuts_label_margin_horizontal"
android:visibility="gone"
android:text="@string/search_shortcuts_search_with_2"
android:text="@string/search_engines_search_with"
app:layout_constraintStart_toStartOf="@id/scrollable_area"
app:layout_constraintTop_toBottomOf="@id/awesomeBar_barrier"
tools:text="This time, search with:" />
@ -197,8 +197,8 @@
<ToggleButton
android:id="@+id/search_shortcuts_button"
style="@style/search_pill"
android:textOff="@string/search_shortcuts_button"
android:textOn="@string/search_shortcuts_button"
android:textOff="@string/search_engines_shortcut_button"
android:textOn="@string/search_engines_shortcut_button"
app:drawableStartCompat="@drawable/ic_search" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tabHistoryLayout"
android:layout_width="match_parent"
android:layout_height="match_parent" />

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<mozilla.components.browser.tabstray.thumbnail.TabThumbnailView
android:id="@+id/previewThumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?tabTrayThumbnailItemBackground" />
<LinearLayout
android:id="@+id/fakeToolbar"
android:layout_width="match_parent"
android:layout_height="@dimen/browser_toolbar_height"
android:layout_gravity="bottom"
android:background="?bottomBarBackground"
android:elevation="5dp"
android:foregroundGravity="bottom"
android:orientation="horizontal">
<View
android:id="@+id/toolbar_wrapper"
android:layout_width="0dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginStart="8dp"
android:layout_marginEnd="0dp"
android:layout_weight="1"
android:background="@drawable/home_search_background" />
<org.mozilla.fenix.components.toolbar.TabCounter
android:id="@+id/tab_button"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/menuButton"
android:layout_width="36dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:scaleType="center"
app:srcCompat="@drawable/ic_menu"
app:tint="?primaryText" />
</LinearLayout>
</merge>

View File

@ -102,6 +102,9 @@
<action
android:id="@+id/action_global_savedLoginsAuthFragment"
app:destination="@id/savedLoginsAuthFragment" />
<action
android:id="@+id/action_global_tabHistoryDialogFragment"
app:destination="@id/tabHistoryDialogFragment" />
<dialog
android:id="@+id/tabTrayDialogFragment"
@ -859,4 +862,7 @@
app:argType="string"
app:nullable="true" />
</fragment>
<dialog
android:id="@+id/tabHistoryDialogFragment"
android:name="org.mozilla.fenix.tabhistory.TabHistoryDialogFragment" />
</navigation>

View File

@ -573,6 +573,8 @@
<string name="bookmark_select_folder">Select folder</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">Are you sure you want to delete this folder?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">%s will delete the selected items.</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">Deleted %1$s</string>
<!-- Screen title for adding a bookmarks folder -->
@ -627,8 +629,10 @@
<!-- Bookmark snackbar message on deletion
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">Deleted %1$s</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">Bookmarks deleted</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">Deleting selected folders</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">UNDO</string>
@ -721,6 +725,8 @@
<string name="create_collection_save_to_collection_tab_selected">%d tab selected</string>
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">Tabs saved!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">Collection saved!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">Tab saved!</string>
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
@ -825,6 +831,10 @@
<string name="qr_scanner_dialog_negative">DENY</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">Are you sure you want to delete %1$s?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">Deleting this tab will delete the entire collection. You can create new collections at any time.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">Delete %1$s?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">Delete</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -879,8 +889,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">Automatically deletes browsing data when you select &quot;Quit&quot; from the main menu</string>
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">Automatically deletes browsing data when you select \&quot;Quit\&quot; from the main menu</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Browsing history</string>
<!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Quit</string>
@ -1223,6 +1231,8 @@
<string name="preferences_passwords_exceptions_description_empty">Logins and passwords that are not saved will be shown here.</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">Logins and passwords will not be saved for these sites.</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">Delete all exceptions</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">Search logins</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1261,6 +1271,8 @@
<string name="saved_login_copy_username">Copy username</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Copy site</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">Open site in browser</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Show password</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -593,6 +593,8 @@
<string name="bookmark_select_folder">Seleccionar carpeta</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">¿Estás seguro de que querés eliminar eliminar esta carpeta?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">%s va a eliminar los elementos seleccionados.</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">Se eliminó %1$s</string>
<!-- Screen title for adding a bookmarks folder -->
@ -650,8 +652,10 @@
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">Se eliminó %1$s</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">Marcadores eliminados</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">Eliminar carpetas seleccionadas</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">UNDO</string>
@ -746,6 +750,8 @@
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">¡Pestañas guardadas!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">¡Colección guardada!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">¡Pestaña guardada!</string>
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
@ -852,6 +858,10 @@
<string name="qr_scanner_dialog_negative">DENY</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">¿Estás seguro de que querés eliminar %1$s?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">Eliminar esta pestaña va a eliminar toda la colección. Podés crear nuevas colecciones en cualquier momento.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">¿Eliminar %1$s?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">Eliminar</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -1249,6 +1259,8 @@
<string name="preferences_passwords_exceptions_description_empty">Los inicios de sesión y las contraseñas que no se guardan se mostrarán aquí.</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">Los inicios de sesión y las contraseñas no se van a guardar para estos sitios.</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">Eliminar todas las excepciones</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">Buscar inicios de sesión</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1287,6 +1299,8 @@
<string name="saved_login_copy_username">Copiar nombre de usuario</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Copiar sitio</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">Abrir sitio en el navegador</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Mostrar contraseña</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -581,6 +581,8 @@
<string name="bookmark_select_folder">Válasszon mappát</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">Biztos, hogy törölni szeretné ezt a mappát?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">A(z) %s törölni fogja a kiválasztott elemeket.</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">%1$s törölve</string>
<!-- Screen title for adding a bookmarks folder -->
@ -636,8 +638,10 @@
<!-- Bookmark snackbar message on deletion
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">%1$s törölve</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">Könyvjelzők törölve</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">Kiválasztott mappák törlése</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">VISSZAVONÁS</string>
@ -731,6 +735,8 @@
<string name="create_collection_save_to_collection_tab_selected">%d lap kiválasztva</string>
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">Lapok mentve.</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">Gyűjtemény mentve.</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">Lap mentve.</string>
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
@ -838,6 +844,10 @@
<string name="qr_scanner_dialog_negative">ELUTASÍTÁS</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">Biztos, hogy törli ezt: %1$s?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">A lap törlésével törli az egész gyűjteményt. Bármikor létrehozhat új gyűjteményeket.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">Törli ezt: %1$s?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">Törlés</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -893,8 +903,6 @@
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">Automatikusan törli a böngészési adatokat, ha a főmenüben a „Kilépés” lehetőséget választja</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Böngészési előzmények</string>
<!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Kilépés</string>
@ -1244,6 +1252,8 @@
<string name="preferences_passwords_exceptions_description_empty">Itt jelennek meg a nem mentett bejelentkezések és jelszavak.</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">A bejelentkezéseket és a jelszavak nem lesznek elmentve ezeknél a webhelyeknél.</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">Összes kivétel törlése</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">Bejelentkezések keresése</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1282,6 +1292,8 @@
<string name="saved_login_copy_username">Felhasználónév másolása</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Oldal másolása</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">Oldal megnyitása böngészőben</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Jelszó megjelenítése</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -569,6 +569,8 @@
<string name="bookmark_select_folder">Буманы таңдау</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">Бұл буманы өшіруді шынымен қалайсыз ба?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">%s таңдалған элементтерді өшіреді.</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">%1$s өшірілді</string>
<!-- Screen title for adding a bookmarks folder -->
@ -623,8 +625,10 @@
<!-- Bookmark snackbar message on deletion
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">%1$s өшірілді</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">Бетбелгілер өшірілді</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">Таңдалған бумаларды өшіру</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">БОЛДЫРМАУ</string>
@ -718,6 +722,8 @@
<string name="create_collection_save_to_collection_tab_selected">%d бет таңдалды</string>
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">Беттер сақталды!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">Жинақ сақталды!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">Бет сақталды!</string>
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
@ -822,6 +828,10 @@
<string name="qr_scanner_dialog_negative">ТЫЙЫМ САЛУ</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">%1$s өшіруді шынымен қалайсыз ба?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">Бұл бетті өшіру жинақты толығымен өшіреді. Жаңа жинақтарды кез келген уақытта жасауға болады.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">%1$s өшіру керек пе?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">Өшіру</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -878,8 +888,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">Негізгі мәзірден &quot;Шығу&quot; таңдау кезінде, шолу деректерін автоматты түрде өшіреді</string>
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">Негізгі мәзірден \&quot;Шығу\&quot; таңдау кезінде, шолу деректерін автоматты түрде өшіреді</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Шолу тарихы</string>
<!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Шығу</string>
@ -1227,6 +1235,8 @@
<string name="preferences_passwords_exceptions_description_empty">Сақталмаған логиндер мен парольдер осында көрсетіледі.</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">Бұл сайттар үшін логиндер мен парольдер сақталмайды.</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">Барлық ережеден тыс жағдайларды өшіру</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">Логиндерден іздеу</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1265,6 +1275,8 @@
<string name="saved_login_copy_username">Пайдаланушы атын көшіріп алу</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Сайтты көшіріп алу</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">Сайтты браузерде ашу</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Парольді көрсету</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -593,6 +593,8 @@
<string name="bookmark_select_folder">폴더 선택</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">이 폴더를 삭제하시겠습니까?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">%s가 선택된 항목들을 삭제합니다.</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">%1$s 삭제됨</string>
<!-- Screen title for adding a bookmarks folder -->
@ -652,8 +654,10 @@
<!-- Bookmark snackbar message on deletion
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">%1$s 삭제됨</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">북마크 삭제됨</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">선택한 폴더 삭제 중</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">실행 취소</string>
@ -757,6 +761,8 @@
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">탭이 저장되었습니다!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">모음집 저장됨!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">탭이 저장되었습니다!</string>
@ -869,6 +875,10 @@
<string name="qr_scanner_dialog_negative">거부</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">%1$s 파일을 삭제하시겠습니까?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">이 탭을 삭제하면 전체 모음집이 삭제됩니다. 언제든지 새 모음집을 만들 수 있습니다.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">%1$s 모음집을 삭제하시겠습니까?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">삭제</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -1276,6 +1286,8 @@
<string name="preferences_passwords_exceptions_description_empty">저장되지 않은 로그인과 비밀번호가 여기에 표시됩니다.</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">이 사이트에 대한 로그인과 비밀번호는 저장되지 않습니다.</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">모든 예외 삭제</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">로그인 검색</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1314,6 +1326,8 @@
<string name="saved_login_copy_username">사용자 이름 복사</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">사이트 복사</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">브라우저에서 사이트 열기</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">비밀번호 보이기</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -582,6 +582,8 @@
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">Ar tikrai norite pašalinti šį aplanką?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">„%s“ pašalins pasirinktus elementus.</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">Pašalintas „%1$s“</string>
<!-- Screen title for adding a bookmarks folder -->
@ -637,8 +639,10 @@
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">Pašalintas %1$s</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">Adresyno įrašai pašalinti</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">Šalinami pasirinkti aplankai</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">Atšaukti</string>
@ -732,6 +736,8 @@
<string name="create_collection_save_to_collection_tab_selected">Pažymėta %d kortelė</string>
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">Kortelės įrašytos!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">Rinkinys įrašytas!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">Kortelė įrašyta!</string>
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
@ -837,6 +843,10 @@
<string name="qr_scanner_dialog_negative">Drausti</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">Ar tikrai norite pašalinti „%1$s“?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">Pašalindami šią kortelę, pašalinsite visą rinkinį. Naujus rinkinius galite sukurti bet kada.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">Pašalinti „%1$s“?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">Pašalinti</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -894,8 +904,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">Naršymo duomenys bus pašalinami automatiškai, pagrindiniame meniu pasirinkus „Išeiti“</string>
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">Naršymo duomenys bus pašalinami automatiškai, pagrindiniame meniu pasirinkus „Išeiti“</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Naršymo žurnalas</string>
<!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Išeiti</string>
@ -1281,6 +1289,8 @@
<string name="saved_login_copy_username">Kopijuoti naudotojo vardą</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Kopijuoti svetainę</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">Atverti svetainę naršyklėje</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Rodyti slaptažodį</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -582,6 +582,8 @@
<string name="bookmark_select_folder">Velg mappe</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">Er du sikker på at du vil slette denne mappen?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">%s vil slette de valgte elementene.</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">Slettet %1$s</string>
<!-- Screen title for adding a bookmarks folder -->
@ -637,8 +639,10 @@
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">Slettet %1$s</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">Bokmerker slettet</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">Sletter valgte mapper</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">ANGRE</string>
@ -734,6 +738,8 @@
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">Faner lagret!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">Samling lagret!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">Fane lagret!</string>
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
@ -838,6 +844,10 @@
<string name="qr_scanner_dialog_negative">AVSLÅ</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">Er du sikker på at du vil slette %1$s?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">Hvis du sletter denne fanen, blir hele samlingen slettet. Du kan når som helst lage nye samlinger.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">Vil du slette %1$s?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">Slett</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -894,8 +904,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">Sletter nettleserdata automatisk når du velger «Avslutt» fra hovedmenyen</string>
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">Sletter nettleserdata automatisk når du velger «Avslutt» fra hovedmenyen</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Nettleserhistorikk</string>
<!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Avslutt</string>
@ -1250,6 +1258,8 @@
<string name="preferences_passwords_exceptions_description_empty">Innlogginger og passord som ikke er lagret vil vises her.</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">Innlogginger og passord vil ikke bli lagret for disse nettstedene.</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">Slett alle unntak</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">Søk innlogginger</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1289,6 +1299,8 @@
<string name="saved_login_copy_username">Kopier brukernavn</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Kopier nettsted</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">Åpne nettsted i nettleseren</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Vis passord</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -584,6 +584,8 @@
<string name="bookmark_select_folder">Map selecteren</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">Weet u zeker dat u deze map wilt verwijderen?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">%s zal de geselecteerde items verwijderen.</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">%1$s verwijderd</string>
<!-- Screen title for adding a bookmarks folder -->
@ -638,8 +640,10 @@
<!-- Bookmark snackbar message on deletion
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">%1$s verwijderd</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">Bladwijzers verwijderd</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">Geselecteerde mappen verwijderen</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">ONGEDAAN MAKEN</string>
@ -733,6 +737,8 @@
<string name="create_collection_save_to_collection_tab_selected">%d tabblad geselecteerd</string>
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">Tabbladen opgeslagen!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">Collectie opgeslagen!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">Tabblad opgeslagen!</string>
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
@ -840,6 +846,10 @@
<string name="qr_scanner_dialog_negative">WEIGEREN</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">Weet u zeker dat u %1$s wilt verwijderen?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">Als u dit tabblad verwijdert, wordt de hele collectie verwijderd. U kunt op elk moment nieuwe collecties maken.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">%1$s verwijderen?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">Verwijderen</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -895,8 +905,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">Verwijdert automatisch navigatiegegevens wanneer u in het hoofdmenu Afsluiten selecteert</string>
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">Verwijdert automatisch navigatiegegevens wanneer u in het hoofdmenu Afsluiten selecteert</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Navigatiegeschiedenis</string>
<!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Afsluiten</string>
@ -1243,6 +1251,8 @@
<string name="preferences_passwords_exceptions_description_empty">Niet-opgeslagen aanmeldingen en wachtwoorden worden hier weergegeven.</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">Aanmeldingen en wachtwoorden worden voor deze websites niet opgeslagen.</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">Alle uitzonderingen verwijderen</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">Aanmeldingen zoeken</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1281,6 +1291,8 @@
<string name="saved_login_copy_username">Gebruikersnaam kopiëren</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Website kopiëren</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">Website openen in browser</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Wachtwoord tonen</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -576,6 +576,8 @@
<string name="bookmark_select_folder">Selecionar pasta</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">Tem certeza que deseja excluir esta pasta?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">O %s excluirá os itens selecionados.</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">%1$s excluída</string>
<!-- Screen title for adding a bookmarks folder -->
@ -630,8 +632,10 @@
<!-- Bookmark snackbar message on deletion
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">Excluiu %1$s</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">Favoritos excluídos</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">Excluindo pastas selecionadas</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">DESFAZER</string>
@ -726,6 +730,8 @@
<string name="create_collection_save_to_collection_tab_selected">%d aba selecionada</string>
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">Abas salvas!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">Coleção salva!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">Aba salva!</string>
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
@ -830,6 +836,10 @@
<string name="qr_scanner_dialog_negative">NEGAR</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">Tem certeza que deseja excluir %1$s?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">Excluir esta aba também excluirá toda a coleção. Você pode criar novas coleções quando quiser.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">Excluir %1$s?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">Excluir</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -1234,6 +1244,8 @@
<string name="preferences_passwords_exceptions_description_empty">Contas e senhas que não são salvas são mostradas aqui.</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">Contas e senhas desses sites não serão salvas.</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">Excluir todas as exceções</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">Pesquisar contas</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1272,6 +1284,8 @@
<string name="saved_login_copy_username">Copiar nome de usuário</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Copiar site</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">Abrir site no navegador</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Exibir senha</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -586,6 +586,8 @@
<string name="bookmark_select_folder">Välj mapp</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">Är du säker på att du vill ta bort den här mappen?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">%s kommer att radera de markerade objekten.</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">Tog bort %1$s</string>
<!-- Screen title for adding a bookmarks folder -->
@ -640,8 +642,10 @@
<!-- Bookmark snackbar message on deletion
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">%1$s har tagits bort</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">Bokmärken borttagna</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">Tar bort valda mappar</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">ÅNGRA</string>
@ -735,6 +739,8 @@
<string name="create_collection_save_to_collection_tab_selected">%d flik vald</string>
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">Flikar sparade!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">Samling sparad!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">Flik sparad!</string>
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
@ -843,6 +849,10 @@
<string name="qr_scanner_dialog_negative">NEKA</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">Är du säker att du vill ta bort %1$s?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">Om du tar bort den här fliken raderas hela samlingen. Du kan skapa nya samlingar när som helst.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">Tog bort %1$s?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">Ta bort</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -899,8 +909,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">Tar automatiskt bort surfdata när du väljer &quot;Avsluta&quot; från huvudmenyn</string>
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
<string name="preference_summary_delete_browsing_data_on_quit_2">Tar automatiskt bort surfdata när du väljer \&quot;Avsluta\&quot; från huvudmenyn</string>
<!-- Category for history items to delete on quit in delete browsing data on quit -->
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Webbläsarhistorik</string>
<!-- Action item in menu for the Delete browsing data on quit feature -->
<string name="delete_browsing_data_on_quit_action">Avsluta</string>
@ -1249,6 +1257,8 @@
<string name="preferences_passwords_exceptions_description_empty">Inloggningar och lösenord som inte sparas visas här.</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">Inloggningar och lösenord sparas inte för dessa webbplatser.</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">Ta bort alla undantag</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">Sök inloggningar</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1287,6 +1297,8 @@
<string name="saved_login_copy_username">Kopiera användarnamn</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Kopiera webbplats</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">Öppna webbplatsen i webbläsaren</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Visa lösenord</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -117,7 +117,7 @@
<string name="browser_menu_open_in_fenix">%1$s ile aç</string>
<!-- Browser menu text shown in custom tabs to indicate this is a Fenix tab
The first parameter is the name of the app defined in app_name (for example: Fenix) -->
<string name="browser_menu_powered_by">%1$s TARAFINDAN GELİŞTİRİLDİ</string>
<string name="browser_menu_powered_by">%1$s SEKMESİ</string>
<!-- Browser menu text shown in custom tabs to indicate this is a Fenix tab
The first parameter is the name of the app defined in app_name (for example: Fenix) -->

View File

@ -575,6 +575,8 @@
<string name="bookmark_select_folder">Chọn thư mục</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">Bạn có chắc chắn muốn xóa thư mục này?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">%s sẽ xóa các mục đã chọn.</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">Đã xóa %1$s</string>
<!-- Screen title for adding a bookmarks folder -->
@ -629,8 +631,10 @@
<!-- Bookmark snackbar message on deletion
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">Đã xóa %1$s</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">Đã xóa dấu trang</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">Đang xóa các thư mục đã chọn</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">HOÀN TÁC</string>
@ -724,6 +728,8 @@
<string name="create_collection_save_to_collection_tab_selected">%d thẻ được chọn</string>
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">Đã lưu các thẻ!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">Đã lưu bộ sưu tập!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">Đã lưu thẻ!</string>
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
@ -828,6 +834,10 @@
<string name="qr_scanner_dialog_negative">TỪ CHỐI</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">Bạn có chắc chắn muốn xóa %1$s không?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">Xóa thẻ này sẽ xóa bộ sưu tập này. Bạn có thể tạo bộ sưu tập mới bất cứ lúc nào.</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">Xóa %1$s?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">Xóa</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -1224,6 +1234,8 @@
<string name="preferences_passwords_exceptions_description_empty">Đăng nhập và mật khẩu không được lưu sẽ được hiển thị ở đây.</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">Đăng nhập và mật khẩu sẽ không được lưu cho các trang web này.</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">Xóa tất cả các ngoại lệ</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">Tìm thông tin đăng nhập</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1262,6 +1274,8 @@
<string name="saved_login_copy_username">Sao chép tên người dùng</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">Sao chép URL trang web</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">Mở trang web trong trình duyệt</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">Hiện mật khẩu</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -593,6 +593,8 @@
<string name="bookmark_select_folder">选择文件夹</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">您确定要删除这个文件夹吗?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">%s 将删除所选项目。</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">已删除 %1$s</string>
<!-- Screen title for adding a bookmarks folder -->
@ -650,8 +652,10 @@
<!-- Bookmark snackbar message on deletion
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">已删除 %1$s 条书签</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">书签已删除</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">正在删除所选文件夹</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">撤销</string>
@ -756,6 +760,8 @@
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">标签页已保存!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">收藏集已保存!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">标签页已保存!</string>
@ -867,6 +873,10 @@
<string name="qr_scanner_dialog_negative">拒绝</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">您确定要删除“%1$s”吗</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">删除此标签页将删除整个收藏集。您可以随时新建收藏集。</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">要删除 %1$s 吗?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">删除</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -1268,6 +1278,8 @@
<string name="preferences_passwords_exceptions_description_empty">不保存登录名和密码的网站将显示于此处。</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">将不保存这些网站的登录名和密码。</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">删除所有例外</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">搜索登录信息</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1306,6 +1318,8 @@
<string name="saved_login_copy_username">复制用户名</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">复制网站</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">在浏览器中打开网站</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">显示密码</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -588,6 +588,8 @@
<string name="bookmark_select_folder">選擇資料夾</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete the selected folder -->
<string name="bookmark_delete_folder_confirmation_dialog">您確定要刪除這個資料夾嗎?</string>
<!-- Confirmation message for a dialog confirming if the user wants to delete multiple items including folders. Parameter will be replaced by app name. -->
<string name="bookmark_delete_multiple_folders_confirmation_dialog">%s 將刪除選擇的項目。</string>
<!-- Snackbar title shown after a folder has been deleted. This first parameter is the name of the deleted folder -->
<string name="bookmark_delete_folder_snackbar">已刪除 %1$s</string>
<!-- Screen title for adding a bookmarks folder -->
@ -643,8 +645,10 @@
<!-- Bookmark snackbar message on deletion
The first parameter is the host part of the URL of the bookmark deleted, if any -->
<string name="bookmark_deletion_snackbar_message">已刪除書籤 %1$s</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks -->
<!-- Bookmark snackbar message on deleting multiple bookmarks not including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_2">已刪除書籤</string>
<!-- Bookmark snackbar message on deleting multiple bookmarks including folders-->
<string name="bookmark_deletion_multiple_snackbar_message_3">刪除選擇的資料夾</string>
<!-- Bookmark undo button for deletion snackbar action -->
<string name="bookmark_undo_deletion">還原</string>
@ -748,6 +752,8 @@
<!-- Text shown in snackbar when multiple tabs have been saved in a collection -->
<string name="create_collection_tabs_saved">已儲存分頁!</string>
<!-- Text shown in snackbar when one or multiple tabs have been saved in a new collection -->
<string name="create_collection_tabs_saved_new_collection">已儲存收藏集!</string>
<!-- Text shown in snackbar when one tab has been saved in a collection -->
<string name="create_collection_tab_saved">已儲存分頁!</string>
@ -859,6 +865,10 @@
<string name="qr_scanner_dialog_negative">拒絕</string>
<!-- Tab collection deletion prompt dialog message. Placeholder will be replaced with the collection name -->
<string name="tab_collection_dialog_message">您確定要刪除 %1$s 嗎?</string>
<!-- Collection and tab deletion prompt dialog message. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_message">刪除此分頁也會同時刪除整個收藏集,您可以之後再建立新的收藏集。</string>
<!-- Collection and tab deletion prompt dialog title. Placeholder will be replaced with the collection name. This will show when the last tab from a collection is deleted -->
<string name="delete_tab_and_collection_dialog_title">要刪除 %1$s 嗎?</string>
<!-- Tab collection deletion prompt dialog option to delete the collection -->
<string name="tab_collection_dialog_positive">刪除</string>
<!-- Tab collection deletion prompt dialog option to cancel deleting the collection -->
@ -1258,6 +1268,8 @@
<string name="preferences_passwords_exceptions_description_empty">不儲存登入資訊與密碼的網站將顯示於此處。</string>
<!-- Description of list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions_description">將不儲存這些網站的登入資訊與密碼。</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">刪除所有例外</string>
<!-- Hint for search box in logins list -->
<string name="preferences_passwords_saved_logins_search">搜尋登入資訊</string>
<!-- Option to sort logins list A-Z, alphabetically -->
@ -1296,6 +1308,8 @@
<string name="saved_login_copy_username">複製使用者名稱</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a site in logins -->
<string name="saved_login_copy_site">複製網站</string>
<!-- Content Description (for screenreaders etc) read for the button to open a site in logins -->
<string name="saved_login_open_site">用瀏覽器開啟</string>
<!-- Content Description (for screenreaders etc) read for the button to reveal a password in logins -->
<string name="saved_login_reveal_password">顯示密碼</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->

View File

@ -146,13 +146,11 @@
<!-- Button in the search view that lets a user search by scanning a QR code -->
<string name="search_scan_button">Scan</string>
<!-- Button in the search view that lets a user search by using a shortcut -->
<string name="search_shortcuts_button">Shortcuts</string>
<string name="search_engines_shortcut_button">Search Engine</string>
<!-- Button in the search view when shortcuts are displayed that takes a user to the search engine settings -->
<string name="search_shortcuts_engine_settings">Search engine settings</string>
<!-- DEPRECATED: Header displayed when selecting a shortcut search engine -->
<string name="search_shortcuts_search_with">Search with</string>
<!-- Header displayed when selecting a shortcut search engine -->
<string name="search_shortcuts_search_with_2">This time, search with:</string>
<string name="search_engines_search_with">This time, search with:</string>
<!-- Button in the search view that lets a user navigate to the site in their clipboard -->
<string name="awesomebar_clipboard_title">Fill link from clipboard</string>
<!-- Button in the search suggestions onboarding that allows search suggestions in private sessions -->
@ -258,8 +256,8 @@
<string name="developer_tools_category">Developer tools</string>
<!-- Preference for developers -->
<string name="preferences_remote_debugging">Remote debugging via USB</string>
<!-- Preference title for switch preference to show search shortcuts -->
<string name="preferences_show_search_shortcuts">Show search shortcuts</string>
<!-- Preference title for switch preference to show search engines -->
<string name="preferences_show_search_engines">Show search engines</string>
<!-- Preference title for switch preference to show search suggestions -->
<string name="preferences_show_search_suggestions">Show search suggestions</string>
<!-- Preference title for switch preference to show voice search button -->
@ -1432,4 +1430,14 @@
<string name="top_sites_max_limit_content">To add a new top site, remove one. Long press the site and select remove.</string>
<!-- Confirmation dialog button text when top sites limit is reached. -->
<string name="top_sites_max_limit_confirmation_button">OK, Got It</string>
<!-- DEPRECATED STRINGS -->
<!-- Button in the search view that lets a user search by using a shortcut -->
<string name="search_shortcuts_button">Shortcuts</string>
<!-- DEPRECATED: Header displayed when selecting a shortcut search engine -->
<string name="search_shortcuts_search_with">Search with</string>
<!-- Header displayed when selecting a shortcut search engine -->
<string name="search_shortcuts_search_with_2">This time, search with:</string>
<!-- Preference title for switch preference to show search shortcuts -->
<string name="preferences_show_search_shortcuts">Show search shortcuts</string>
</resources>

View File

@ -27,7 +27,7 @@
<SwitchPreference
android:defaultValue="true"
android:key="@string/pref_key_show_search_shortcuts"
android:title="@string/preferences_show_search_shortcuts" />
android:title="@string/preferences_show_search_engines" />
<SwitchPreference
android:defaultValue="true"
android:key="@string/pref_key_show_clipboard_suggestions"

View File

@ -220,7 +220,7 @@ class DefaultBrowserToolbarControllerTest {
@Test
fun handleToolbarForwardPress() = runBlockingTest {
val item = ToolbarMenu.Item.Forward
val item = ToolbarMenu.Item.Forward(false)
val controller = createController(scope = this)
controller.handleToolbarItemInteraction(item)
@ -229,6 +229,17 @@ class DefaultBrowserToolbarControllerTest {
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

View File

@ -0,0 +1,114 @@
/* 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.settings.about
import android.content.Context
import android.view.View
import android.widget.Toast
import io.mockk.CapturingSlot
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.mockkStatic
import io.mockk.slot
import io.mockk.unmockkStatic
import io.mockk.verify
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.utils.Settings
class SecretDebugMenuTriggerTest {
@MockK private lateinit var logoView: View
@MockK private lateinit var context: Context
@MockK private lateinit var settings: Settings
@MockK(relaxUnitFun = true) private lateinit var toast: Toast
private lateinit var clickListener: CapturingSlot<View.OnClickListener>
@Before
fun setup() {
MockKAnnotations.init(this)
mockkStatic(Toast::class)
clickListener = slot()
every { logoView.setOnClickListener(capture(clickListener)) } just Runs
every { logoView.context } returns context
every {
context.getString(R.string.about_debug_menu_toast_progress, any())
} returns "Debug menu: x click(s) left to enable"
every { settings.showSecretDebugMenuThisSession } returns false
every { settings.showSecretDebugMenuThisSession = any() } just Runs
every { Toast.makeText(context, any<Int>(), any()) } returns toast
every { Toast.makeText(context, any<String>(), any()) } returns toast
}
@After
fun teardown() {
unmockkStatic(Toast::class)
}
@Test
fun `toast is not displayed on first click`() {
SecretDebugMenuTrigger(logoView, settings)
clickListener.captured.onClick(logoView)
verify(inverse = true) { Toast.makeText(context, any<String>(), any()) }
verify(inverse = true) { toast.show() }
}
@Test
fun `toast is displayed on second click`() {
SecretDebugMenuTrigger(logoView, settings)
clickListener.captured.onClick(logoView)
clickListener.captured.onClick(logoView)
verify { context.getString(R.string.about_debug_menu_toast_progress, 3) }
verify { Toast.makeText(context, any<String>(), Toast.LENGTH_SHORT) }
verify { toast.show() }
}
@Test
fun `clearClickCounter resets counter`() {
val trigger = SecretDebugMenuTrigger(logoView, settings)
clickListener.captured.onClick(logoView)
trigger.clearClickCounter()
clickListener.captured.onClick(logoView)
verify(inverse = true) { Toast.makeText(context, any<String>(), any()) }
verify(inverse = true) { toast.show() }
}
@Test
fun `toast is displayed on fifth click`() {
SecretDebugMenuTrigger(logoView, settings)
clickListener.captured.onClick(logoView)
clickListener.captured.onClick(logoView)
clickListener.captured.onClick(logoView)
clickListener.captured.onClick(logoView)
clickListener.captured.onClick(logoView)
verify { Toast.makeText(
context,
R.string.about_debug_menu_toast_done,
Toast.LENGTH_LONG
) }
verify { toast.show() }
verify { settings.showSecretDebugMenuThisSession = true }
}
@Test
fun `don't register click listener if menu is already shown`() {
every { settings.showSecretDebugMenuThisSession } returns true
SecretDebugMenuTrigger(logoView, settings)
verify(inverse = true) { logoView.setOnClickListener(any()) }
}
}

View File

@ -0,0 +1,38 @@
/* 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.tabhistory
import androidx.navigation.NavController
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.browser.session.SessionManager
import mozilla.components.feature.session.SessionUseCases
import org.junit.Test
class TabHistoryControllerTest {
val sessionManager: SessionManager = mockk(relaxed = true)
val navController: NavController = mockk(relaxed = true)
val sessionUseCases = SessionUseCases(sessionManager)
val goToHistoryIndexUseCase = sessionUseCases.goToHistoryIndex
val controller = DefaultTabHistoryController(
navController = navController,
goToHistoryIndexUseCase = goToHistoryIndexUseCase
)
val currentItem = TabHistoryItem(
index = 0,
title = "",
url = "",
isSelected = true
)
@Test
fun handleGoToHistoryIndex() {
controller.handleGoToHistoryItem(currentItem)
verify { goToHistoryIndexUseCase.invoke(currentItem.index) }
}
}

View File

@ -0,0 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.tabhistory
import io.mockk.mockk
import io.mockk.verify
import org.junit.Test
class TabHistoryInteractorTest {
val controller: TabHistoryController = mockk(relaxed = true)
val interactor = TabHistoryInteractor(controller)
@Test
fun onGoToHistoryItem() {
val item: TabHistoryItem = mockk()
interactor.goToHistoryItem(item)
verify { controller.handleGoToHistoryItem(item) }
}
}

View File

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

View File

@ -28,6 +28,7 @@ object Versions {
const val androidx_paging = "2.1.0"
const val androidx_transition = "1.3.0"
const val androidx_work = "2.2.0"
const val androidx_dynamic_animation = "1.0.0"
const val google_material = "1.1.0"
const val google_flexbox = "2.0.1"
@ -170,6 +171,7 @@ object Deps {
const val androidx_recyclerview = "androidx.recyclerview:recyclerview:${Versions.androidx_recyclerview}"
const val androidx_core = "androidx.core:core:${Versions.androidx_core}"
const val androidx_core_ktx = "androidx.core:core-ktx:${Versions.androidx_core}"
const val androidx_dynamic_animation = "androidx.dynamicanimation:dynamicanimation:${Versions.androidx_dynamic_animation}"
const val androidx_transition = "androidx.transition:transition:${Versions.androidx_transition}"
const val androidx_work_ktx = "androidx.work:work-runtime-ktx:${Versions.androidx_work}"
const val androidx_work_testing = "androidx.work:work-testing:${Versions.androidx_work}"

View File

@ -16,11 +16,6 @@ SIGNING_ROUTE_TEMPLATES = [
"index.{trust-domain}.v2.{project}.{variant}.{build_date}.revision.{head_rev}.{abi}",
"index.{trust-domain}.v2.{project}.{variant}.{build_date}.latest.{abi}",
"index.{trust-domain}.v2.{project}.{variant}.revision.{head_rev}.{abi}",
# TODO Bug 1631839: Remove the following scopes once all consumers have migrated
"index.project.{trust-domain}.{project}.v2.{variant}.{build_date}.revision.{head_rev}",
"index.project.{trust-domain}.{project}.v2.{variant}.{build_date}.latest",
"index.project.{trust-domain}.{project}.v2.{variant}.latest",
]