Merge branch 'releases_v114' into mergify/bp/releases_v114/pr-1906

fenix/114.1.0
t-p-white 11 months ago committed by GitHub
commit 355301cae6

@ -57,11 +57,6 @@ object FeatureFlags {
*/
const val composeTabsTray = false
/**
* Enables the wallpaper v2 enhancements.
*/
const val wallpaperV2Enabled = true
/**
* Enables the save to PDF feature.
*/

@ -90,6 +90,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.metrics.BreadcrumbsRecorder
import org.mozilla.fenix.components.metrics.GrowthDataWorker
import org.mozilla.fenix.databinding.ActivityHomeBinding
import org.mozilla.fenix.exceptions.trackingprotection.TrackingProtectionExceptionsFragmentDirections
import org.mozilla.fenix.experiments.ResearchSurfaceDialogFragment
@ -452,6 +453,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
Events.defaultBrowserChanged.record(NoExtras())
}
GrowthDataWorker.sendActivatedSignalIfNeeded(applicationContext)
ReEngagementNotificationWorker.setReEngagementNotificationIfNeeded(applicationContext)
MessageNotificationWorker.setMessageNotificationWorker(applicationContext)
}

@ -88,9 +88,13 @@ class AdjustMetricsService(
override fun track(event: Event) {
CoroutineScope(dispatcher).launch {
try {
if (event is Event.GrowthData && storage.shouldTrack(event)) {
Adjust.trackEvent(AdjustEvent(event.tokenName))
storage.updateSentState(event)
if (event is Event.GrowthData) {
if (storage.shouldTrack(event)) {
Adjust.trackEvent(AdjustEvent(event.tokenName))
storage.updateSentState(event)
} else {
storage.updatePersistentState(event)
}
}
} catch (e: Exception) {
crashReporter.submitCaughtException(e)

@ -46,5 +46,12 @@ sealed class Event {
* Event recording the first time a URI is loaded in Firefox in a 24 hour period.
*/
object FirstUriLoadForDay : GrowthData("ja86ek")
/**
* Event recording when User is "activated" in first week of usage.
* Activated = if the user is active 3 days in their first week and
* if they search once in the latter half of that week (days 4-7).
*/
data class UserActivated(val fromSearch: Boolean) : GrowthData("imgpmr")
}
}

@ -0,0 +1,74 @@
package org.mozilla.fenix.components.metrics
import android.content.Context
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.WorkerParameters
import mozilla.components.support.utils.ext.getPackageInfoCompat
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.settings
import java.util.concurrent.TimeUnit
/**
* Worker that will send the User Activated event at the end of the first week.
*/
class GrowthDataWorker(
context: Context,
workerParameters: WorkerParameters,
) : Worker(context, workerParameters) {
override fun doWork(): Result {
val settings = applicationContext.settings()
if (!System.currentTimeMillis().isAfterFirstWeekFromInstall(applicationContext) ||
settings.growthUserActivatedSent
) {
return Result.success()
}
applicationContext.metrics.track(Event.GrowthData.UserActivated(fromSearch = false))
return Result.success()
}
companion object {
private const val GROWTH_USER_ACTIVATED_WORK_NAME = "org.mozilla.fenix.growth.work"
private const val DAY_MILLIS: Long = 1000 * 60 * 60 * 24
private const val FULL_WEEK_MILLIS: Long = DAY_MILLIS * 7
/**
* Schedules the Activated User event if needed.
*/
fun sendActivatedSignalIfNeeded(context: Context) {
val instanceWorkManager = WorkManager.getInstance(context)
if (context.settings().growthUserActivatedSent) {
return
}
val growthSignalWork = OneTimeWorkRequest.Builder(GrowthDataWorker::class.java)
.setInitialDelay(FULL_WEEK_MILLIS, TimeUnit.MILLISECONDS)
.build()
instanceWorkManager.beginUniqueWork(
GROWTH_USER_ACTIVATED_WORK_NAME,
ExistingWorkPolicy.KEEP,
growthSignalWork,
).enqueue()
}
/**
* Returns [Boolean] value signaling if current time is after the first week after install.
*/
private fun Long.isAfterFirstWeekFromInstall(context: Context): Boolean {
val timeDifference = this - getInstalledTime(context)
return (FULL_WEEK_MILLIS <= timeDifference)
}
private fun getInstalledTime(context: Context): Long = context.packageManager
.getPackageInfoCompat(context.packageName, 0)
.firstInstallTime
}
}

@ -277,6 +277,7 @@ internal class ReleaseMetricController(
}
Component.FEATURE_SEARCH to InContentTelemetry.IN_CONTENT_SEARCH -> {
BrowserSearch.inContent[value!!].add()
track(Event.GrowthData.UserActivated(fromSearch = true))
}
Component.SUPPORT_WEBEXTENSIONS to WebExtensionFacts.Items.WEB_EXTENSIONS_INITIALIZED -> {
metadata?.get("installed")?.let { installedAddons ->

@ -30,6 +30,7 @@ class MetricsMiddleware(
metrics.track(Event.GrowthData.FirstAppOpenForDay)
metrics.track(Event.GrowthData.FirstWeekSeriesActivity)
metrics.track(Event.GrowthData.UsageThreshold)
metrics.track(Event.GrowthData.UserActivated(fromSearch = false))
}
else -> Unit
}

@ -33,6 +33,11 @@ interface MetricsStorage {
*/
suspend fun updateSentState(event: Event)
/**
* Updates locally-stored data related to an [event] that has just been sent.
*/
suspend fun updatePersistentState(event: Event)
/**
* Will try to register this as a recorder of app usage based on whether usage recording is still
* needed. It will measure usage by to monitoring lifecycle callbacks from [application]'s
@ -60,6 +65,7 @@ internal class DefaultMetricsStorage(
/**
* Checks local state to see whether the [event] should be sent.
*/
@Suppress("ComplexMethod", "CyclomaticComplexMethod")
override suspend fun shouldTrack(event: Event): Boolean =
withContext(dispatcher) {
// The side-effect of storing days of use always needs to happen.
@ -91,6 +97,9 @@ internal class DefaultMetricsStorage(
currentTime.duringFirstMonth() &&
settings.uriLoadGrowthLastSent.hasBeenMoreThanDaySince()
}
is Event.GrowthData.UserActivated -> {
hasUserReachedActivatedThreshold()
}
}
}
@ -114,6 +123,23 @@ internal class DefaultMetricsStorage(
Event.GrowthData.FirstUriLoadForDay -> {
settings.uriLoadGrowthLastSent = System.currentTimeMillis()
}
is Event.GrowthData.UserActivated -> {
settings.growthUserActivatedSent = true
}
}
}
override suspend fun updatePersistentState(event: Event) {
when (event) {
is Event.GrowthData.UserActivated -> {
if (event.fromSearch && shouldUpdateSearchUsage()) {
settings.growthEarlySearchUsed = true
} else if (!event.fromSearch && shouldUpdateUsageCount()) {
settings.growthEarlyUseCount.increment()
settings.growthEarlyUseCountLastIncrement = System.currentTimeMillis()
}
}
else -> Unit
}
}
@ -176,6 +202,8 @@ internal class DefaultMetricsStorage(
private fun Long.duringFirstDay() = this < getInstalledTime() + dayMillis
private fun Long.afterThirdDay() = this > getInstalledTime() + threeDayMillis
private fun Long.duringFirstWeek() = this < getInstalledTime() + fullWeekMillis
private fun Long.duringFirstMonth() = this < getInstalledTime() + shortestMonthMillis
@ -184,6 +212,25 @@ internal class DefaultMetricsStorage(
calendar.add(Calendar.DAY_OF_MONTH, 1)
}
private fun hasUserReachedActivatedThreshold(): Boolean {
return !settings.growthUserActivatedSent &&
settings.growthEarlyUseCount.value >= daysActivatedThreshold &&
settings.growthEarlySearchUsed
}
private fun shouldUpdateUsageCount(): Boolean {
val currentTime = System.currentTimeMillis()
return currentTime.afterFirstDay() &&
currentTime.duringFirstWeek() &&
settings.growthEarlyUseCountLastIncrement.hasBeenMoreThanDaySince()
}
private fun shouldUpdateSearchUsage(): Boolean {
val currentTime = System.currentTimeMillis()
return currentTime.afterThirdDay() &&
currentTime.duringFirstWeek()
}
/**
* This will store app usage time to disk, based on Resume and Pause lifecycle events. Currently,
* there is only interest in usage during the first day after install.
@ -208,6 +255,7 @@ internal class DefaultMetricsStorage(
companion object {
private const val dayMillis: Long = 1000 * 60 * 60 * 24
private const val threeDayMillis: Long = 3 * dayMillis
private const val shortestMonthMillis: Long = dayMillis * 28
// Note this is 8 so that recording of FirstWeekSeriesActivity happens throughout the length
@ -217,6 +265,9 @@ internal class DefaultMetricsStorage(
// The usage threshold we are interested in is currently 340 seconds.
private const val usageThresholdMillis = 1000 * 340
// The usage threshold for "activated" growth users.
private const val daysActivatedThreshold = 3
/**
* Determines whether events should be tracked based on some general criteria:
* - user has installed as a result of a campaign

@ -86,7 +86,7 @@ private fun createOnboardingPageState(
onNegativeButtonClick: () -> Unit,
onUrlClick: (String) -> Unit = {},
): OnboardingPageState = OnboardingPageState(
image = onboardingPageUiData.imageRes,
imageRes = onboardingPageUiData.imageRes,
title = onboardingPageUiData.title,
description = onboardingPageUiData.description,
linkTextState = onboardingPageUiData.linkText?.let {

@ -28,7 +28,7 @@ fun NotificationPermissionDialogScreen(
) {
OnboardingPage(
pageState = OnboardingPageState(
image = R.drawable.ic_notification_permission,
imageRes = R.drawable.ic_notification_permission,
title = stringResource(
id = R.string.onboarding_home_enable_notifications_title,
formatArgs = arrayOf(stringResource(R.string.app_name)),

@ -70,7 +70,7 @@ private const val URL_TAG = "URL_TAG"
* @param modifier The modifier to be applied to the Composable.
* @param onDismiss Invoked when the user clicks the close button. This defaults to null. When null,
* it doesn't show the close button.
* @param imageResContentScale The [ContentScale] for the [OnboardingPageState.image].
* @param imageResContentScale The [ContentScale] for the [OnboardingPageState.imageRes].
*/
@Composable
@Suppress("LongMethod")
@ -114,7 +114,7 @@ fun OnboardingPage(
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image(
painter = painterResource(id = pageState.image),
painter = painterResource(id = pageState.imageRes),
contentDescription = null,
contentScale = imageResContentScale,
modifier = Modifier
@ -171,7 +171,7 @@ private fun DescriptionText(
description: String,
linkTextState: LinkTextState?,
) {
if (linkTextState != null) {
if (linkTextState != null && description.contains(linkTextState.text, ignoreCase = true)) {
LinkText(
text = description,
linkTextState = linkTextState,
@ -198,7 +198,7 @@ private fun LinkText(
linkTextState: LinkTextState,
) {
val annotatedString = buildAnnotatedString {
val startIndex = text.indexOf(linkTextState.text)
val startIndex = text.indexOf(linkTextState.text, ignoreCase = true)
val endIndex = startIndex + linkTextState.text.length
append(text)
addStyle(
@ -249,7 +249,7 @@ private fun OnboardingPagePreview() {
FirefoxTheme {
OnboardingPage(
pageState = OnboardingPageState(
image = R.drawable.ic_notification_permission,
imageRes = R.drawable.ic_notification_permission,
title = stringResource(
id = R.string.onboarding_home_enable_notifications_title,
formatArgs = arrayOf(stringResource(R.string.app_name)),

@ -9,16 +9,16 @@ import androidx.annotation.DrawableRes
/**
* Model containing data for [OnboardingPage].
*
* @param image [DrawableRes] displayed on the page.
* @param title [String] title of the page.
* @param description [String] description of the page.
* @param linkTextState [LinkTextState] part of description text with a link.
* @param primaryButton [Action] action for the primary button.
* @param secondaryButton [Action] action for the secondary button.
* @param onRecordImpressionEvent Callback for recording impression event.
* @property imageRes [DrawableRes] displayed on the page.
* @property title [String] title of the page.
* @property description [String] description of the page.
* @property linkTextState [LinkTextState] part of description text with a link.
* @property primaryButton [Action] action for the primary button.
* @property secondaryButton [Action] action for the secondary button.
* @property onRecordImpressionEvent Callback for recording impression event.
*/
data class OnboardingPageState(
@DrawableRes val image: Int,
@DrawableRes val imageRes: Int,
val title: String,
val description: String,
val linkTextState: LinkTextState? = null,

@ -69,7 +69,7 @@ fun UpgradeOnboarding(
OnboardingPage(
pageState = when (onboardingState) {
UpgradeOnboardingState.Welcome -> OnboardingPageState(
image = R.drawable.ic_onboarding_welcome,
imageRes = R.drawable.ic_onboarding_welcome,
title = stringResource(id = R.string.onboarding_home_welcome_title_2),
description = stringResource(id = R.string.onboarding_home_welcome_description),
primaryButton = Action(
@ -88,7 +88,7 @@ fun UpgradeOnboarding(
},
)
UpgradeOnboardingState.SyncSignIn -> OnboardingPageState(
image = R.drawable.ic_onboarding_sync,
imageRes = R.drawable.ic_onboarding_sync,
title = stringResource(id = R.string.onboarding_home_sync_title_3),
description = stringResource(id = R.string.onboarding_home_sync_description),
primaryButton = Action(

@ -26,7 +26,7 @@ object SupportUtils {
const val WIKIPEDIA_URL = "https://www.wikipedia.org/"
const val FENIX_PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=${BuildConfig.APPLICATION_ID}"
const val GOOGLE_URL = "https://www.google.com/"
const val BAIDU_URL = "https://m.baidu.com/?from=1000969a"
const val BAIDU_URL = "https://m.baidu.com/"
const val JD_URL = "https://union-click.jd.com/jdc" +
"?e=&p=AyIGZRprFDJWWA1FBCVbV0IUWVALHFRBEwQAQB1AWQkFVUVXfFkAF14lRFRbJXstVWR3WQ1rJ08AZnhS" +
"HDJBYh4LZR9eEAMUBlccWCUBEQZRGFoXCxc3ZRteJUl8BmUZWhQ" +

@ -19,7 +19,6 @@ import kotlinx.coroutines.launch
import mozilla.components.lib.state.ext.observeAsComposableState
import mozilla.components.service.glean.private.NoExtras
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.GleanMetrics.Wallpapers
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
@ -59,11 +58,7 @@ class WallpaperSettingsFragment : Fragment() {
val coroutineScope = rememberCoroutineScope()
WallpaperSettings(
wallpaperGroups = if (FeatureFlags.wallpaperV2Enabled) {
wallpapers.groupByDisplayableCollection()
} else {
mapOf(Wallpaper.ClassicFirefoxCollection to wallpapers)
},
wallpaperGroups = wallpapers.groupByDisplayableCollection(),
defaultWallpaper = Wallpaper.Default,
selectedWallpaper = currentWallpaper,
loadWallpaperResource = {

@ -1735,4 +1735,33 @@ class Settings(private val appContext: Context) : PreferencesHolder {
key = appContext.getPreferenceKey(R.string.pref_key_enable_tabs_tray_to_compose),
default = FeatureFlags.composeTabsTray,
)
/**
* Adjust Activated User sent
*/
var growthUserActivatedSent by booleanPreference(
key = appContext.getPreferenceKey(R.string.pref_key_growth_user_activated_sent),
default = false,
)
/**
* Indicates how many days in the first week user opened the app.
*/
val growthEarlyUseCount = counterPreference(
appContext.getPreferenceKey(R.string.pref_key_growth_early_browse_count),
maxCount = 3,
)
var growthEarlyUseCountLastIncrement by longPreference(
key = appContext.getPreferenceKey(R.string.pref_key_growth_early_browse_count_last_increment),
default = 0L,
)
/**
* Indicates how many days in the first week user searched in the app.
*/
var growthEarlySearchUsed by booleanPreference(
key = appContext.getPreferenceKey(R.string.pref_key_growth_early_search),
default = false,
)
}

@ -1,94 +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.wallpapers
import android.content.Context
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mozilla.components.concept.fetch.Client
import mozilla.components.concept.fetch.Request
import mozilla.components.concept.fetch.isSuccess
import mozilla.components.support.base.log.logger.Logger
import org.mozilla.fenix.BuildConfig
import java.io.File
/**
* Can download wallpapers from a remote host.
*
* @param context Required for writing files to local storage.
* @param client Required for fetching files from network.
*/
class LegacyWallpaperDownloader(
private val context: Context,
private val client: Client,
) {
private val logger = Logger("WallpaperDownloader")
private val remoteHost = BuildConfig.WALLPAPER_URL
/**
* Downloads a wallpaper from the network. Will try to fetch 4 versions of each wallpaper:
* portrait/light - portrait/dark - landscape/light - landscape/dark. These are expected to be
* found at a remote path in the form:
* <WALLPAPER_URL>/<resolution>/<orientation>/<app theme>/<wallpaper theme>/<wallpaper name>.png
*/
suspend fun downloadWallpaper(wallpaper: Wallpaper) = withContext(Dispatchers.IO) {
if (remoteHost.isNullOrEmpty()) {
return@withContext
}
for (metadata in wallpaper.toMetadata(context)) {
val localFile = File(context.filesDir.absolutePath, metadata.localPath)
if (localFile.exists()) continue
val request = Request(
url = "$remoteHost/${metadata.remotePath}",
method = Request.Method.GET,
)
Result.runCatching {
val response = client.fetch(request)
if (!response.isSuccess) {
logger.error("Download response failure code: ${response.status}")
return@withContext
}
File(localFile.path.substringBeforeLast("/")).mkdirs()
response.body.useStream { input ->
input.copyTo(localFile.outputStream())
}
}.onFailure {
Result.runCatching {
if (localFile.exists()) {
localFile.delete()
}
}.onFailure { e ->
logger.error("Failed to delete stale wallpaper bitmaps while downloading", e)
}
logger.error(it.message ?: "Download failed: no throwable message included.", it)
}
}
}
private data class WallpaperMetadata(val remotePath: String, val localPath: String)
private fun Wallpaper.toMetadata(context: Context): List<WallpaperMetadata> =
listOf("landscape", "portrait").flatMap { orientation ->
listOf("light", "dark").map { theme ->
val localPath = "wallpapers/$orientation/$theme/$name.png"
val remotePath = "${context.resolutionSegment()}/" +
"$orientation/" +
"$theme/" +
"${collection.name}/" +
"$name.png"
WallpaperMetadata(remotePath, localPath)
}
}
@Suppress("MagicNumber")
private fun Context.resolutionSegment(): String = when (resources.displayMetrics.densityDpi) {
// targeting hdpi and greater density resolutions https://developer.android.com/training/multiscreen/screendensities
in 0..240 -> "low"
in 240..320 -> "medium"
else -> "high"
}
}

@ -1,74 +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.wallpapers
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
/**
* Manages various functions related to the locally-stored wallpaper assets.
*
* @property rootDirectory The top level app-local storage directory.
* @param coroutineDispatcher Dispatcher used to execute suspending functions. Default parameter
* should be likely be used except for when under test.
*/
class LegacyWallpaperFileManager(
private val rootDirectory: File,
coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO,
) {
private val scope = CoroutineScope(coroutineDispatcher)
private val portraitDirectory = File(rootDirectory, "wallpapers/portrait")
private val landscapeDirectory = File(rootDirectory, "wallpapers/landscape")
/**
* Lookup all the files for a wallpaper name. This lookup will fail if there are not
* files for each of the following orientation and theme combinations:
* light/portrait - light/landscape - dark/portrait - dark/landscape
*/
suspend fun lookupExpiredWallpaper(name: String): Wallpaper? = withContext(Dispatchers.IO) {
if (getAllLocalWallpaperPaths(name).all { File(rootDirectory, it).exists() }) {
Wallpaper(
name = name,
collection = Wallpaper.DefaultCollection,
textColor = null,
cardColorLight = null,
cardColorDark = null,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
)
} else {
null
}
}
private fun getAllLocalWallpaperPaths(name: String): List<String> =
listOf("landscape", "portrait").flatMap { orientation ->
listOf("light", "dark").map { theme ->
Wallpaper.legacyGetLocalPath(orientation, theme, name)
}
}
/**
* Remove all wallpapers that are not the [currentWallpaper] or in [availableWallpapers].
*/
fun clean(currentWallpaper: Wallpaper, availableWallpapers: List<Wallpaper>) {
scope.launch {
val wallpapersToKeep = (listOf(currentWallpaper) + availableWallpapers).map { it.name }
cleanChildren(portraitDirectory, wallpapersToKeep)
cleanChildren(landscapeDirectory, wallpapersToKeep)
}
}
private fun cleanChildren(dir: File, wallpapersToKeep: List<String>) {
for (file in dir.walkTopDown()) {
if (file.isDirectory || file.nameWithoutExtension in wallpapersToKeep) continue
file.delete()
}
}
}

@ -12,11 +12,8 @@ import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mozilla.components.concept.fetch.Client
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.components.AppStore
import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.ext.isSystemInDarkTheme
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings
import java.io.File
@ -46,57 +43,33 @@ class WallpapersUseCases(
private val downloader = WallpaperDownloader(storageRootDirectory, client)
private val fileManager = WallpaperFileManager(storageRootDirectory)
val initialize: InitializeWallpapersUseCase by lazy {
if (FeatureFlags.wallpaperV2Enabled) {
val metadataFetcher = WallpaperMetadataFetcher(client)
val migrationHelper = LegacyWallpaperMigration(
storageRootDirectory = storageRootDirectory,
settings = context.settings(),
selectWallpaper::invoke,
)
DefaultInitializeWallpaperUseCase(
appStore = appStore,
downloader = downloader,
fileManager = fileManager,
metadataFetcher = metadataFetcher,
migrationHelper = migrationHelper,
settings = context.settings(),
currentLocale = currentLocale,
)
} else {
val fileManager = LegacyWallpaperFileManager(storageRootDirectory)
val downloader = LegacyWallpaperDownloader(context, client)
LegacyInitializeWallpaperUseCase(
appStore = appStore,
downloader = downloader,
fileManager = fileManager,
settings = context.settings(),
currentLocale = currentLocale,
)
}
val metadataFetcher = WallpaperMetadataFetcher(client)
val migrationHelper = LegacyWallpaperMigration(
storageRootDirectory = storageRootDirectory,
settings = context.settings(),
selectWallpaper::invoke,
)
DefaultInitializeWallpaperUseCase(
appStore = appStore,
downloader = downloader,
fileManager = fileManager,
metadataFetcher = metadataFetcher,
migrationHelper = migrationHelper,
settings = context.settings(),
currentLocale = currentLocale,
)
}
val loadBitmap: LoadBitmapUseCase by lazy {
if (FeatureFlags.wallpaperV2Enabled) {
DefaultLoadBitmapUseCase(
filesDir = context.filesDir,
getOrientation = { context.resources.configuration.orientation },
)
} else {
LegacyLoadBitmapUseCase(context)
}
DefaultLoadBitmapUseCase(
filesDir = context.filesDir,
getOrientation = { context.resources.configuration.orientation },
)
}
val loadThumbnail: LoadThumbnailUseCase by lazy {
if (FeatureFlags.wallpaperV2Enabled) {
DefaultLoadThumbnailUseCase(storageRootDirectory)
} else {
LegacyLoadThumbnailUseCase(context)
}
DefaultLoadThumbnailUseCase(storageRootDirectory)
}
val selectWallpaper: SelectWallpaperUseCase by lazy {
if (FeatureFlags.wallpaperV2Enabled) {
DefaultSelectWallpaperUseCase(context.settings(), appStore, fileManager, downloader)
} else {
LegacySelectWallpaperUseCase(context.settings(), appStore)
}
DefaultSelectWallpaperUseCase(context.settings(), appStore, fileManager, downloader)
}
/**
@ -110,133 +83,6 @@ class WallpapersUseCases(
suspend operator fun invoke()
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class LegacyInitializeWallpaperUseCase(
private val appStore: AppStore,
private val downloader: LegacyWallpaperDownloader,
private val fileManager: LegacyWallpaperFileManager,
private val settings: Settings,
private val currentLocale: String,
private val possibleWallpapers: List<Wallpaper> = allWallpapers,
) : InitializeWallpapersUseCase {
/**
* Downloads the currently available wallpaper metadata from a remote source.
* Updates the [appStore] with that metadata and with the selected wallpaper found in storage.
* Removes any unused promotional or time-limited assets from local storage.
* Should usually be called early the app's lifetime to ensure that metadata and thumbnails
* are available as soon as they are needed.
*/
override suspend operator fun invoke() {
// Quite a bit of code needs to be executed off the main thread in some of this setup.
// This should be cleaned up as improvements are made to the storage, file management,
// and download utilities.
withContext(Dispatchers.IO) {
val dispatchedCurrent = Wallpaper.getCurrentWallpaperFromSettings(settings)?.let {
// Dispatch this ASAP so the home screen can render.
appStore.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(it))
true
} ?: false
val availableWallpapers = possibleWallpapers.getAvailableWallpapers()
val currentWallpaperName = settings.currentWallpaperName
val currentWallpaper = possibleWallpapers.find { it.name == currentWallpaperName }
?: fileManager.lookupExpiredWallpaper(currentWallpaperName)
?: Wallpaper.Default
fileManager.clean(
currentWallpaper,
possibleWallpapers,
)
downloadAllRemoteWallpapers(availableWallpapers)
appStore.dispatch(AppAction.WallpaperAction.UpdateAvailableWallpapers(availableWallpapers))
if (!dispatchedCurrent) {
appStore.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(currentWallpaper))
}
}
}
private fun List<Wallpaper>.getAvailableWallpapers() =
this.filter { !it.isExpired() && it.isAvailableInLocale() }
private suspend fun downloadAllRemoteWallpapers(availableWallpapers: List<Wallpaper>) {
for (wallpaper in availableWallpapers) {
if (wallpaper != Wallpaper.Default) {
downloader.downloadWallpaper(wallpaper)
}
}
}
private fun Wallpaper.isExpired(): Boolean {
val expired = this.collection.endDate?.let { Date().after(it) } ?: false
return expired && this.name != settings.currentWallpaperName
}
private fun Wallpaper.isAvailableInLocale(): Boolean =
this.collection.availableLocales?.contains(currentLocale) ?: true
companion object {
private val firefoxClassicCollection = Wallpaper.Collection(
name = Wallpaper.firefoxCollectionName,
heading = null,
description = null,
availableLocales = null,
startDate = null,
endDate = null,
learnMoreUrl = null,
)
private val localWallpapers: List<Wallpaper> = listOf(
Wallpaper(
name = Wallpaper.amethystName,
collection = firefoxClassicCollection,
textColor = null,
cardColorLight = null,
cardColorDark = null,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
),
Wallpaper(
name = Wallpaper.ceruleanName,
collection = firefoxClassicCollection,
textColor = null,
cardColorLight = null,
cardColorDark = null,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
),
Wallpaper(
name = Wallpaper.sunriseName,
collection = firefoxClassicCollection,
textColor = null,
cardColorLight = null,
cardColorDark = null,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
),
)
private val remoteWallpapers: List<Wallpaper> = listOf(
Wallpaper(
name = Wallpaper.twilightHillsName,
collection = firefoxClassicCollection,
textColor = null,
cardColorLight = null,
cardColorDark = null,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
),
Wallpaper(
name = Wallpaper.beachVibeName,
collection = firefoxClassicCollection,
textColor = null,
cardColorLight = null,
cardColorDark = null,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
),
)
val allWallpapers = listOf(Wallpaper.Default) + localWallpapers + remoteWallpapers
}
}
@Suppress("LongParameterList")
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class DefaultInitializeWallpaperUseCase(
@ -316,64 +162,6 @@ class WallpapersUseCases(
suspend operator fun invoke(wallpaper: Wallpaper): Bitmap?
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class LegacyLoadBitmapUseCase(private val context: Context) : LoadBitmapUseCase {
/**
* Load the bitmap for a [wallpaper], if available.
*
* @param wallpaper The wallpaper to load a bitmap for.
*/
override suspend operator fun invoke(wallpaper: Wallpaper): Bitmap? = when (wallpaper.name) {
Wallpaper.amethystName, Wallpaper.ceruleanName, Wallpaper.sunriseName -> {
loadWallpaperFromDrawable(context, wallpaper)
}
Wallpaper.twilightHillsName, Wallpaper.beachVibeName -> {
loadWallpaperFromDisk(context, wallpaper)
}
else -> null
}
private suspend fun loadWallpaperFromDrawable(
context: Context,
wallpaper: Wallpaper,
): Bitmap? = Result.runCatching {
val drawableRes = when (wallpaper.name) {
Wallpaper.amethystName -> R.drawable.amethyst
Wallpaper.ceruleanName -> R.drawable.cerulean
Wallpaper.sunriseName -> R.drawable.sunrise
else -> return@runCatching null
}
withContext(Dispatchers.IO) {
BitmapFactory.decodeResource(context.resources, drawableRes)
}
}.getOrNull()
private suspend fun loadWallpaperFromDisk(
context: Context,
wallpaper: Wallpaper,
): Bitmap? = Result.runCatching {
val path = wallpaper.getLocalPathFromContext(context)
withContext(Dispatchers.IO) {
val file = File(context.filesDir, path)
BitmapFactory.decodeStream(file.inputStream())
}
}.getOrNull()
/**
* Get the expected local path on disk for a wallpaper. This will differ depending
* on orientation and app theme.
*/
private fun Wallpaper.getLocalPathFromContext(context: Context): String {
val orientation = if (context.isLandscape()) "landscape" else "portrait"
val theme = if (context.isSystemInDarkTheme()) "dark" else "light"
return Wallpaper.legacyGetLocalPath(orientation, theme, name)
}
private fun Context.isLandscape(): Boolean {
return resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class DefaultLoadBitmapUseCase(
private val filesDir: File,
@ -422,12 +210,6 @@ class WallpapersUseCases(
suspend operator fun invoke(wallpaper: Wallpaper): Bitmap?
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class LegacyLoadThumbnailUseCase(private val context: Context) : LoadThumbnailUseCase {
override suspend fun invoke(wallpaper: Wallpaper): Bitmap? =
LegacyLoadBitmapUseCase(context).invoke(wallpaper)
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class DefaultLoadThumbnailUseCase(private val filesDir: File) : LoadThumbnailUseCase {
override suspend fun invoke(wallpaper: Wallpaper): Bitmap? = withContext(Dispatchers.IO) {
@ -453,26 +235,6 @@ class WallpapersUseCases(
suspend operator fun invoke(wallpaper: Wallpaper): Wallpaper.ImageFileState
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class LegacySelectWallpaperUseCase(
private val settings: Settings,
private val appStore: AppStore,
) : SelectWallpaperUseCase {
/**
* Select a new wallpaper. Storage and the app store will be updated appropriately.
*
* @param wallpaper The selected wallpaper.
*/
override suspend fun invoke(wallpaper: Wallpaper): Wallpaper.ImageFileState {
settings.currentWallpaperName = wallpaper.name
settings.currentWallpaperTextColor = wallpaper.textColor ?: 0
settings.currentWallpaperCardColorLight = wallpaper.cardColorLight ?: 0
settings.currentWallpaperCardColorDark = wallpaper.cardColorDark ?: 0
appStore.dispatch(AppAction.WallpaperAction.UpdateCurrentWallpaper(wallpaper))
return Wallpaper.ImageFileState.Downloaded
}
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal class DefaultSelectWallpaperUseCase(
private val settings: Settings,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 269 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

@ -125,6 +125,9 @@
<!-- Shortcut action to open new private tab -->
<string name="home_screen_shortcut_open_new_private_tab_2">Nyt privat faneblad</string>
<!-- Shortcut action to open Passwords screens -->
<string name="home_screen_shortcut_open_password_screen">Genvej til adgangskoder</string>
<!-- Recent Tabs -->
<!-- Header text for jumping back into the recent tab in the home screen -->
<string name="recent_tabs_header">Hop tilbage til</string>
@ -406,15 +409,23 @@
<!-- Text for cancel button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_cancel_button">Annuller</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button">Anmod om understøttelse</string>
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button" moz:RemovedIn="115" tools:ignore="UnusedResources">Anmod om understøttelse</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button_2">Send anmodning</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title" moz:RemovedIn="114" tools:ignore="UnusedResources">Reduktion af cookie-bannere</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title">Reduktion af cookie-bannere</string>
<string name="cookie_banner_handling_details_site_is_not_supported_title_2">Anmod om understøttelse af dette websted?</string>
<!-- Label for the snackBar, after the user reports with success a website where cookie banner reducer did not work -->
<string name="cookie_banner_handling_report_site_snack_bar_text">Anmodning om understøttelse af websted indsendt.</string>
<string name="cookie_banner_handling_report_site_snack_bar_text" moz:RemovedIn="114" tools:ignore="UnusedResources">Anmodning om understøttelse af websted indsendt.</string>
<!-- Label for the snackBar, after the user reports with success a website where cookie banner reducer did not work -->
<string name="cookie_banner_handling_report_site_snack_bar_text_2">Anmodning sendt</string>
<!-- Text for indicating cookie banner handling is on this site, this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_on_for_site">Slået til for dette websted</string>
<!-- Text for indicating that a request for unsupported site was sent to Nimbus (it's a Mozilla library for experiments), this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site_request_submitted">Anmodning om understøttelse af websted indsendt</string>
<string name="reduce_cookie_banner_unsupported_site_request_submitted" moz:RemovedIn="114" tools:ignore="UnusedResources">Anmodning om understøttelse af websted indsendt</string>
<!-- Text for indicating that a request for unsupported site was sent to Nimbus (it's a Mozilla library for experiments), this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site_request_submitted_2">Anmodning om understøttelse sendt</string>
<!-- Text for indicating cookie banner handling is currently not supported for this site, this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site">Webstedet understøttes ikke i øjeblikket</string>
<!-- Title text for a detail explanation indicating cookie banner handling is on this site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is a shortened URL of the current site-->
@ -423,38 +434,22 @@
<string name="reduce_cookie_banner_details_panel_title_off_for_site">Slå reduktion af cookie-bannere fra for %1$s?</string>
<!-- Title text for a detail explanation indicating cookie banner reducer didn't work for the current site, this is shown as part of the cookie banner panel in the toolbar.-->
<string name="reduce_cookie_banner_details_panel_title_unsupported_site_request">Dette websted understøttes i øjeblikket ikke af reduktion af cookie-bannere. Vil du have vores team til at gennemgå dette websted for at det kan understøttes i fremtiden?</string>
<string name="reduce_cookie_banner_details_panel_title_unsupported_site_request" moz:RemovedIn="114" tools:ignore="UnusedResources">Dette websted understøttes i øjeblikket ikke af reduktion af cookie-bannere. Vil du have vores team til at gennemgå dette websted for at det kan understøttes i fremtiden?</string>
<!-- Title text for a detail explanation indicating cookie banner reducer didn't work for the current site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is the application name-->
<string name="reduce_cookie_banner_details_panel_title_unsupported_site_request_2">%1$s kan ikke afvise cookie-anmodninger automatisk på dette websted. Du kan sende en anmodning om understøttelse af dette websted i fremtiden.</string>
<!-- Long text for a detail explanation indicating what will happen if cookie banner handling is off for a site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is the application name -->
<string name="reduce_cookie_banner_details_panel_description_off_for_site">%1$s vil rydde dette websteds cookies og genindlæse siden. Rydning af alle cookies kan logge dig ud eller tømme indkøbskurve.</string>
<!-- Long text for a detail explanation indicating what will happen if cookie banner handling is on for a site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is the application name -->
<string name="reduce_cookie_banner_details_panel_description_on_for_site_2">%1$s forsøger automatisk at afvise alle cookie-anmodninger på understøttede websteder.</string>
<!-- Title text for the dialog use on the control branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_control_experiment_dialog_title" moz:RemovedIn="112" tools:ignore="UnusedResources">Slut med cookie-bannere!</string>
<!-- Title text for the cookie banner re-engagement dialog. The first parameter is the application name. -->
<string name="reduce_cookie_banner_dialog_title">Tillad %1$s at afvise cookie-bannere?</string>
<!-- Body text for the dialog use on the control branch of the experiment to determine which context users engaged the most.The first parameter is the application name -->
<string name="reduce_cookie_banner_control_experiment_dialog_body_2" moz:RemovedIn="112" tools:ignore="UnusedResources">Tillad %1$s at automatisk afvise cookie-anmodninger, når det er muligt?</string>
<!-- Body text for the cookie banner re-engagement dialog use. The first parameter is the application name. -->
<string name="reduce_cookie_banner_dialog_body">%1$s kan automatisk afvise mange cookie-banner-anmodninger.</string>
<!-- Remind me later text button for the onboarding dialog -->
<string name="reduce_cookie_banner_dialog_not_now_button">Ikke nu</string>
<!-- Change setting text button, for the dialog use on the control branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_control_experiment_dialog_change_setting_button" moz:RemovedIn="112" tools:ignore="UnusedResources">Avis bannere</string>
<!-- Snack text for the cookie banner dialog, after user hit the dismiss banner button -->
<string name="reduce_cookie_banner_dialog_snackbar_text">Du vil få vist færre cookie-anmodninger</string>
<!-- Title text for the dialog use on the variant 1 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_1_experiment_dialog_title" moz:RemovedIn="112" tools:ignore="UnusedResources">Se færre pop op-beskeder om cookies</string>
<!-- Body text for the dialog use on the variant 1 branch of the experiment to determine which context users engaged the most. The first parameter is the application name. -->
<string name="reduce_cookie_banner_variant_1_experiment_dialog_body_1" moz:RemovedIn="112" tools:ignore="UnusedResources">Besvar automatisk cookie pop op-beskeder, så du kan bruge nettet uden forstyrrelser. %1$s vil afvise alle forespørgsler, når det er muligt.</string>
<!-- Change setting text button, for the onboarding dialog use on the variant 1 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_1_experiment_dialog_change_setting_button" moz:RemovedIn="112" tools:ignore="UnusedResources">Afvis pop op-beskeder om cookies</string>
<!-- Title text for the dialog use on the variant 2 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_2_experiment_dialog_title" moz:RemovedIn="112" tools:ignore="UnusedResources">Reduktion af cookie-bannere</string>
<!-- Body text for the dialog use on the variant 2 branch of the experiment to determine which context users engaged the most. The first parameter is the application name. -->
<string name="reduce_cookie_banner_variant_2_experiment_dialog_body_1" moz:RemovedIn="112" tools:ignore="UnusedResources">Tillad %1$s at afvise et websteds anmodning om samtykke til anvendelse af cookies, når det er muligt?</string>
<!-- Change setting text button, for the dialog use on the variant 2 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_2_experiment_dialog_change_setting_button" moz:RemovedIn="112" tools:ignore="UnusedResources">Tillad</string>
<!-- Change setting text button, for the cookie banner re-engagement dialog -->
<string name="reduce_cookie_banner_dialog_change_setting_button">Tillad</string>
@ -1358,7 +1353,9 @@
<!-- Title text of a delete browsing data dialog. -->
<string name="delete_history_prompt_title">Tidsinterval der skal slettes</string>
<!-- Body text of a delete browsing data dialog. -->
<string name="delete_history_prompt_body">Fjerner historik (herunder historik synkroniseret fra andre enheder), cookies og andre browserdata.</string>
<string name="delete_history_prompt_body" moz:RemovedIn="130" tools:ignore="UnusedResources">Fjerner historik (herunder historik synkroniseret fra andre enheder), cookies og andre browserdata.</string>
<!-- Body text of a delete browsing data dialog. -->
<string name="delete_history_prompt_body_2">Fjerner historik (herunder historik synkroniseret fra andre enheder)</string>
<!-- Radio button in the delete browsing data dialog to delete history items for the last hour. -->
<string name="delete_history_prompt_button_last_hour">Den sidste time</string>
<!-- Radio button in the delete browsing data dialog to delete history items for today and yesterday. -->

@ -125,6 +125,9 @@
<!-- Shortcut action to open new private tab -->
<string name="home_screen_shortcut_open_new_private_tab_2">Nowy priwatny rejtarik</string>
<!-- Shortcut action to open Passwords screens -->
<string name="home_screen_shortcut_open_password_screen">Skrotconka gronidłow</string>
<!-- Recent Tabs -->
<!-- Header text for jumping back into the recent tab in the home screen -->
<string name="recent_tabs_header">Slědk skócyś</string>
@ -406,15 +409,23 @@
<!-- Text for cancel button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_cancel_button">Pśetergnuś</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button">Pomoc póžedaś</string>
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button" moz:RemovedIn="115" tools:ignore="UnusedResources">Pomoc póžedaś</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button_2">Napšašowanje pósłaś</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title" moz:RemovedIn="114" tools:ignore="UnusedResources">Reducěrowanje cookiejowych chórgojow</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title">Reducěrowanje cookiejowych chórgojow</string>
<string name="cookie_banner_handling_details_site_is_not_supported_title_2">Wó pódpěru za toś to sedło pšosyś?</string>
<!-- Label for the snackBar, after the user reports with success a website where cookie banner reducer did not work -->
<string name="cookie_banner_handling_report_site_snack_bar_text">Napšašowanje na sedło pomocy jo se wótpósłało.</string>
<string name="cookie_banner_handling_report_site_snack_bar_text" moz:RemovedIn="114" tools:ignore="UnusedResources">Napšašowanje na sedło pomocy jo se wótpósłało.</string>
<!-- Label for the snackBar, after the user reports with success a website where cookie banner reducer did not work -->
<string name="cookie_banner_handling_report_site_snack_bar_text_2">Napšašowanje pósłane</string>
<!-- Text for indicating cookie banner handling is on this site, this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_on_for_site">Za toś to sedło zmóžnjony</string>
<!-- Text for indicating that a request for unsupported site was sent to Nimbus (it's a Mozilla library for experiments), this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site_request_submitted">Napšašowanje na sedło pomocy jo se wótpósłało</string>
<string name="reduce_cookie_banner_unsupported_site_request_submitted" moz:RemovedIn="114" tools:ignore="UnusedResources">Napšašowanje na sedło pomocy jo se wótpósłało</string>
<!-- Text for indicating that a request for unsupported site was sent to Nimbus (it's a Mozilla library for experiments), this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site_request_submitted_2">Pšosba wó pódpěru pósłana</string>
<!-- Text for indicating cookie banner handling is currently not supported for this site, this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site">Sedło se tuchylu njepódpěra</string>
<!-- Title text for a detail explanation indicating cookie banner handling is on this site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is a shortened URL of the current site-->
@ -422,37 +433,21 @@
<!-- Title text for a detail explanation indicating cookie banner handling is off this site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is a shortened URL of the current site-->
<string name="reduce_cookie_banner_details_panel_title_off_for_site">Reducěrowanje cookiejowych chórgojow za %1$s znjemóžniś?</string>
<!-- Title text for a detail explanation indicating cookie banner reducer didn't work for the current site, this is shown as part of the cookie banner panel in the toolbar.-->
<string name="reduce_cookie_banner_details_panel_title_unsupported_site_request">Toś to sedło se tuchylu pśez redukciju cookiejowych chórgojow. Cośo, až naš team toś to websedło pśeglědujo a pomoc w pśichoźe pśidawa?</string>
<string name="reduce_cookie_banner_details_panel_title_unsupported_site_request" moz:RemovedIn="114" tools:ignore="UnusedResources">Toś to sedło se tuchylu pśez redukciju cookiejowych chórgojow. Cośo, až naš team toś to websedło pśeglědujo a pomoc w pśichoźe pśidawa?</string>
<!-- Title text for a detail explanation indicating cookie banner reducer didn't work for the current site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is the application name-->
<string name="reduce_cookie_banner_details_panel_title_unsupported_site_request_2">%1$s njamóžo cookiejowe napšašowanja na toś tom sedle awtomatiski wótpokazaś. Móžośo pšosbu wó pódpěru toś togo sedła w pśichoźe pósłaś.</string>
<!-- Long text for a detail explanation indicating what will happen if cookie banner handling is off for a site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is the application name -->
<string name="reduce_cookie_banner_details_panel_description_off_for_site">%1$s cookieje sedła lašujo a buźo bok aktualizěrowaś. Lašowanje wšych cookiejow móžo was pśizjawiś abo nakupowańske wózyki wuprozniś.</string>
<!-- Long text for a detail explanation indicating what will happen if cookie banner handling is on for a site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is the application name -->
<string name="reduce_cookie_banner_details_panel_description_on_for_site_2">%1$s wopytujo wšykne cookiejowe napšašowanja na pódprětych sedłach awtomatiski wótpokazaś.</string>
<!-- Title text for the dialog use on the control branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_control_experiment_dialog_title" moz:RemovedIn="112" tools:ignore="UnusedResources">Firefox dowóliś, cookiejowe chórgoji wótpokazaś?</string>
<!-- Title text for the cookie banner re-engagement dialog. The first parameter is the application name. -->
<string name="reduce_cookie_banner_dialog_title">%1$s dowóliś, cookiejowe chórgoji wótpokazaś?</string>
<!-- Body text for the dialog use on the control branch of the experiment to determine which context users engaged the most.The first parameter is the application name -->
<string name="reduce_cookie_banner_control_experiment_dialog_body_2" moz:RemovedIn="112" tools:ignore="UnusedResources">%1$s dowóliś, cookiejowe napšašowanja awtomatiski wótpokazaś, jolic móžno?</string>
<!-- Body text for the cookie banner re-engagement dialog use. The first parameter is the application name. -->
<string name="reduce_cookie_banner_dialog_body">%1$s móžo wjele napšašowanjow wó cookiejowych chórgojach awtomatiski wótpokazaś.</string>
<!-- Remind me later text button for the onboarding dialog -->
<string name="reduce_cookie_banner_dialog_not_now_button">Nic něnto</string>
<!-- Change setting text button, for the dialog use on the control branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_control_experiment_dialog_change_setting_button" moz:RemovedIn="112" tools:ignore="UnusedResources">Chórgoje zachyśiś</string>
<!-- Snack text for the cookie banner dialog, after user hit the dismiss banner button -->
<string name="reduce_cookie_banner_dialog_snackbar_text">Buźośo mjenjej cookiejowych napšašowanjow wiźeś</string>
<!-- Title text for the dialog use on the variant 1 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_1_experiment_dialog_title" moz:RemovedIn="112" tools:ignore="UnusedResources">Mjenjej cookiejowych wuskokujucych woknow wiźeś</string>
<!-- Body text for the dialog use on the variant 1 branch of the experiment to determine which context users engaged the most. The first parameter is the application name. -->
<string name="reduce_cookie_banner_variant_1_experiment_dialog_body_1" moz:RemovedIn="112" tools:ignore="UnusedResources">Wótegrońśo awtomatiski na cookiejowe wuskokujuce wokna za pśeglědowanje bźez wótchylenja. %1$s wšykne napšašowanja wótpokažo, jolic móžno.</string>
<!-- Change setting text button, for the onboarding dialog use on the variant 1 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_1_experiment_dialog_change_setting_button" moz:RemovedIn="112" tools:ignore="UnusedResources">Wuskokujuce wokna zachyśiś</string>
<!-- Title text for the dialog use on the variant 2 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_2_experiment_dialog_title" moz:RemovedIn="112" tools:ignore="UnusedResources">Reducěrowanje cookiejowych chórgojow</string>
<!-- Body text for the dialog use on the variant 2 branch of the experiment to determine which context users engaged the most. The first parameter is the application name. -->
<string name="reduce_cookie_banner_variant_2_experiment_dialog_body_1" moz:RemovedIn="112" tools:ignore="UnusedResources">%1$s dowóliś, napšašowanje sedła za cookiejowym pśizwólenim wótpokazaś, jolic trjeba?</string>
<!-- Change setting text button, for the dialog use on the variant 2 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_2_experiment_dialog_change_setting_button" moz:RemovedIn="112" tools:ignore="UnusedResources">Dowóliś</string>
<!-- Change setting text button, for the cookie banner re-engagement dialog -->
<string name="reduce_cookie_banner_dialog_change_setting_button">Dowóliś</string>
@ -1365,7 +1360,9 @@
<!-- Title text of a delete browsing data dialog. -->
<string name="delete_history_prompt_title">Casowy wótrězk za lašowanje</string>
<!-- Body text of a delete browsing data dialog. -->
<string name="delete_history_prompt_body">Wótwónoźijo historiju (mjazy njeju historiju, kótaraž jo se synchronizěrowała z drugich rědow), cookieje a druge pśeglědowańske daty.</string>
<string name="delete_history_prompt_body" moz:RemovedIn="130" tools:ignore="UnusedResources">Wótwónoźijo historiju (mjazy njeju historiju, kótaraž jo se synchronizěrowała z drugich rědow), cookieje a druge pśeglědowańske daty.</string>
<!-- Body text of a delete browsing data dialog. -->
<string name="delete_history_prompt_body_2">Wótwónoźijo historiju (mjazy njeju historiju, kótaraž jo se synchronizěrowała z drugimi rědami)</string>
<!-- Radio button in the delete browsing data dialog to delete history items for the last hour. -->
<string name="delete_history_prompt_button_last_hour">Zachadna góźina</string>
<!-- Radio button in the delete browsing data dialog to delete history items for today and yesterday. -->

@ -408,7 +408,9 @@
<!-- Text for cancel button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_cancel_button">Cancel</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button">Request support</string>
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button" moz:RemovedIn="115" tools:ignore="UnusedResources">Request support</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button_2">Send request</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title" moz:RemovedIn="114" tools:ignore="UnusedResources">Cookie Banner Reduction</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->

@ -409,7 +409,9 @@
<!-- Text for cancel button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_cancel_button">Cancelar</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button">Pedir que funcione</string>
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button" moz:RemovedIn="115" tools:ignore="UnusedResources">Pedir que funcione</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button_2">Enviar solicitud</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title" moz:RemovedIn="114" tools:ignore="UnusedResources">Reducción de anuncios de cookies</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->

@ -1636,7 +1636,7 @@
<string name="add_to_homescreen_text_placeholder">نام میانبر</string>
<!-- Describes the add to homescreen functionality -->
<string name="add_to_homescreen_description_2">شما به راحتی می‌توانید این وبگاه را به صفحهٔ خانگی تلفن خود اضافه کنید تا دسترسی مستقیم به آن داشته باشید و مرور سریع‌تری را مانند تجربه کردن یک برنامه داشته باشید.</string>
<string name="add_to_homescreen_description_2">به‌راحتی می‌توانید این وبگاه را به صفحهٔ خانگی تلفن خود اضافه کنید تا بی‌درنگ به آن دسترسی داشته باشید و همچون کار با یک برنامه مرور سریع‌تری را تجربه کنید.</string>
<!-- Preference for managing the settings for logins and passwords in Fenix -->
<string name="preferences_passwords_logins_and_passwords">ورودها و گذرواژه‌ها</string>

@ -406,7 +406,9 @@
<string name="cookie_banner_handling_details_site_is_not_supported_cancel_button">Cancelar</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button">Pedir soporte</string>
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button" moz:RemovedIn="115" tools:ignore="UnusedResources">Pedir soporte</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button_2">Enviar solicitude</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title" moz:RemovedIn="114" tools:ignore="UnusedResources">Redución do aviso de cookies</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->

@ -416,16 +416,20 @@
<!-- Text for cancel button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_cancel_button">キャンセル</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button">対処を依頼</string>
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button" moz:RemovedIn="115" tools:ignore="UnusedResources">対処を依頼</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title">Cookie バナーの削減</string>
<string name="cookie_banner_handling_details_site_is_not_supported_title" moz:RemovedIn="114" tools:ignore="UnusedResources">Cookie バナーの削減</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title_2">このサイトのサポートをリクエストしますか?</string>
<!-- Label for the snackBar, after the user reports with success a website where cookie banner reducer did not work -->
<string name="cookie_banner_handling_report_site_snack_bar_text" moz:RemovedIn="114" tools:ignore="UnusedResources">サイトへの対処依頼が送信されました。</string>
<!-- Label for the snackBar, after the user reports with success a website where cookie banner reducer did not work -->
<string name="cookie_banner_handling_report_site_snack_bar_text">サイトへの対処依頼が送信されました。</string>
<string name="cookie_banner_handling_report_site_snack_bar_text_2">リクエストを送信しました</string>
<!-- Text for indicating cookie banner handling is on this site, this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_on_for_site">このサイトでオン</string>
<!-- Text for indicating that a request for unsupported site was sent to Nimbus (it's a Mozilla library for experiments), this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site_request_submitted">対処を依頼済み</string>
<string name="reduce_cookie_banner_unsupported_site_request_submitted" moz:RemovedIn="114" tools:ignore="UnusedResources">対処を依頼済み</string>
<!-- Text for indicating cookie banner handling is currently not supported for this site, this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site">現在サポートされていないサイトです</string>
<!-- Title text for a detail explanation indicating cookie banner handling is on this site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is a shortened URL of the current site-->
@ -433,40 +437,21 @@
<!-- Title text for a detail explanation indicating cookie banner handling is off this site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is a shortened URL of the current site-->
<string name="reduce_cookie_banner_details_panel_title_off_for_site">%1$s で Cookie バナー削減を無効にしますか?</string>
<!-- Title text for a detail explanation indicating cookie banner reducer didn't work for the current site, this is shown as part of the cookie banner panel in the toolbar.-->
<string name="reduce_cookie_banner_details_panel_title_unsupported_site_request">現在、Cookie バナー削減機能はこのサイトに対応していません。このウェブサイトに対処するためのレビューを開発チームに依頼しますか?</string>
<string name="reduce_cookie_banner_details_panel_title_unsupported_site_request" moz:RemovedIn="114" tools:ignore="UnusedResources">現在、Cookie バナー削減機能はこのサイトに対応していません。このウェブサイトに対処するためのレビューを開発チームに依頼しますか?</string>
<!-- Long text for a detail explanation indicating what will happen if cookie banner handling is off for a site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is the application name -->
<string name="reduce_cookie_banner_details_panel_description_off_for_site">%1$s はこのサイトの Cookie を消去してページを読み込み直します。すべての Cookie を消去すると、ログアウトしたり、ショッピングカートが空になったりする場合があります。</string>
<!-- Long text for a detail explanation indicating what will happen if cookie banner handling is on for a site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is the application name -->
<string name="reduce_cookie_banner_details_panel_description_on_for_site_2">%1$s はサポートされたサイト上の Cookie 要求を自動的に拒否しようとします。</string>
<!-- Title text for the dialog use on the control branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_control_experiment_dialog_title" moz:RemovedIn="112" tools:ignore="UnusedResources">Cookie バナーよ、去れ!</string>
<!-- Title text for the cookie banner re-engagement dialog. The first parameter is the application name. -->
<string name="reduce_cookie_banner_dialog_title">%1$s に Cookie バナーを拒否させますか?</string>
<!-- Body text for the dialog use on the control branch of the experiment to determine which context users engaged the most.The first parameter is the application name -->
<string name="reduce_cookie_banner_control_experiment_dialog_body_2" moz:RemovedIn="112" tools:ignore="UnusedResources">可能な場合、%1$s に自動的に Cookie 要求を拒否させますか?</string>
<!-- Body text for the cookie banner re-engagement dialog use. The first parameter is the application name. -->
<string name="reduce_cookie_banner_dialog_body">%1$s は多くの Cookie バナーの同意確認を自動的に拒否できます。</string>
<!-- Remind me later text button for the onboarding dialog -->
<string name="reduce_cookie_banner_dialog_not_now_button">後で</string>
<!-- Change setting text button, for the dialog use on the control branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_control_experiment_dialog_change_setting_button" moz:RemovedIn="112" tools:ignore="UnusedResources">バナーを閉じる</string>
<!-- Snack text for the cookie banner dialog, after user hit the dismiss banner button -->
<string name="reduce_cookie_banner_dialog_snackbar_text">Cookie 要求が少なくなります</string>
<!-- Title text for the dialog use on the variant 1 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_1_experiment_dialog_title" moz:RemovedIn="112" tools:ignore="UnusedResources">Cookie ポップアップを減らす</string>
<!-- Body text for the dialog use on the variant 1 branch of the experiment to determine which context users engaged the most. The first parameter is the application name. -->
<string name="reduce_cookie_banner_variant_1_experiment_dialog_body_1" moz:RemovedIn="112" tools:ignore="UnusedResources">目障りな Cookie ポップアップに自動的に応答します。%1$s が可能な限りすべての要求を拒否します。</string>
<!-- Change setting text button, for the onboarding dialog use on the variant 1 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_1_experiment_dialog_change_setting_button" moz:RemovedIn="112" tools:ignore="UnusedResources">ポップアップを閉じる</string>
<!-- Title text for the dialog use on the variant 2 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_2_experiment_dialog_title" moz:RemovedIn="112" tools:ignore="UnusedResources">Cookie バナーの削減</string>
<!-- Body text for the dialog use on the variant 2 branch of the experiment to determine which context users engaged the most. The first parameter is the application name. -->
<string name="reduce_cookie_banner_variant_2_experiment_dialog_body_1" moz:RemovedIn="112" tools:ignore="UnusedResources">可能な場合、サイトの Cookie 同意要求を %1$s に拒否させますか?</string>
<!-- Change setting text button, for the dialog use on the variant 2 branch of the experiment to determine which context users engaged the most -->
<string name="reduce_cookie_banner_variant_2_experiment_dialog_change_setting_button" moz:RemovedIn="112" tools:ignore="UnusedResources">許可</string>
<!-- Change setting text button, for the cookie banner re-engagement dialog -->
<string name="reduce_cookie_banner_dialog_change_setting_button">許可</string>
@ -1391,7 +1376,7 @@
<!-- Title text of a delete browsing data dialog. -->
<string name="delete_history_prompt_title">削除する期間</string>
<!-- Body text of a delete browsing data dialog. -->
<string name="delete_history_prompt_body">履歴 (他の端末から同期されたものを含む)、Cookie および他のブラウジングデータを削除します。</string>
<string name="delete_history_prompt_body" moz:RemovedIn="130" tools:ignore="UnusedResources">履歴 (他の端末から同期されたものを含む)、Cookie および他のブラウジングデータを削除します。</string>
<!-- Radio button in the delete browsing data dialog to delete history items for the last hour. -->
<string name="delete_history_prompt_button_last_hour">1 時間以内</string>
<!-- Radio button in the delete browsing data dialog to delete history items for today and yesterday. -->

@ -406,7 +406,9 @@
<!-- Text for cancel button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_cancel_button">გაუქმება</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button">მხარდაჭერის მოთხოვნა</string>
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button" moz:RemovedIn="115" tools:ignore="UnusedResources">მხარდაჭერის მოთხოვნა</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button_2">მოთხოვნის გაგზავნა</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title" moz:RemovedIn="114" tools:ignore="UnusedResources">ფუნთუშის მოთხოვნების შემცირება</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->

@ -407,7 +407,9 @@
<!-- Text for cancel button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_cancel_button">Бас тарту</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button">Қолдау сұрау</string>
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button" moz:RemovedIn="115" tools:ignore="UnusedResources">Қолдау сұрау</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button_2">Сұранымды жіберу</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title" moz:RemovedIn="114" tools:ignore="UnusedResources">Cookie баннерлерін азайту</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->

@ -36,4 +36,162 @@
<string name="browser_menu_find_in_page">ପୃଷ୍ଠାରେ ଖୋଜି ପାଆନ୍ତୁ</string>
<!-- Browser menu button shown in custom tabs that opens the current tab in Fenix
The first parameter is the name of the app defined in app_name (for example: Fenix) -->
<string name="browser_menu_open_in_fenix">%1$sରେ ଖୋଲନ୍ତୁ</string>
<!-- Browser menu label for adding a bookmark -->
<string name="browser_menu_add">ଯୋଡ଼ନ୍ତୁ</string>
<!-- Browser menu label for editing a bookmark -->
<string name="browser_menu_edit">ସମ୍ପାଦନା</string>
<!-- Locale Settings Fragment -->
<!-- Content description for tick mark on selected language -->
<string name="a11y_selected_locale_content_description">ମନୋନୀତ ଭାଷା</string>
<!-- Text for default locale item -->
<string name="default_locale_text">ଡିଭାଇସ୍ ର ଭାଷାକୁ ଅନୁସରଣ କରନ୍ତୁ</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">ସନ୍ଧାନ ଇଞ୍ଜିନ୍ ସେଟିଂସ୍</string>
<!-- Search Widget -->
<!-- Content description for searching with a widget. The first parameter is the name of the application.-->
<string name="search_widget_content_description_2">ଗୋଟିଏ ନୂଆ %1$s ଟ୍ୟାବ୍ ଖୋଲନ୍ତୁ</string>
<!-- Preferences -->
<!-- Title for the settings page-->
<string name="settings">ସେଟିଂସମୂହ</string>
<!-- Preference category for general settings -->
<string name="preferences_category_general">ସାଧାରଣ</string>
<!-- Preference for settings related to changing the default search engine -->
<string name="preferences_default_search_engine">ଡିଫଲ୍ଟ ସନ୍ଧାନ ଇଞ୍ଜିନ୍</string>
<!-- Preference for settings related to Search -->
<string name="preferences_search">ସନ୍ଧାନ</string>
<!-- Preference for settings related to Search address bar -->
<string name="preferences_search_address_bar">ଠିକଣା ଦଣ୍ଡିକା</string>
<!-- Preference category for advanced settings -->
<string name="preferences_category_advanced">ବିକଶିତ</string>
<!-- Preference category for privacy and security settings -->
<string name="preferences_category_privacy_security">ଗୋପନୀୟତା ଏବଂ ସୁରକ୍ଷା</string>
<!-- Summary of https only preference if https only is set to off -->
<string name="preferences_https_only_off">ବନ୍ଦ</string>
<!-- Text displayed that links to website containing documentation about "HTTPS-Only" mode -->
<string name="preferences_http_only_learn_more">ଅଧିକ ଜାଣନ୍ତୁ</string>
<!-- Preference for accessibility -->
<string name="preferences_accessibility">ଅଭିଗମ୍ୟତା</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">ଥିମ୍</string>
<!-- Preference for language -->
<string name="preferences_language">ଭାଷା</string>
<!-- Preference for syncing history -->
<string name="preferences_sync_history">ଇତିହାସ</string>
<!-- Theme Preferences -->
<!-- Preference for using light theme -->
<string name="preference_light_theme">ହାଲୁକା</string>
<!-- Preference for using dark theme -->
<string name="preference_dark_theme">ଗାଢ଼</string>
<!-- Preference for using following device theme -->
<string name="preference_follow_device_theme">ଡିଭାଇସ୍ ର ଥିମକୁ ଅନୁସରଣ କରନ୍ତୁ</string>
<!-- Option in Library to open History page -->
<string name="library_history">ଇତିହାସ</string>
<!-- Option in Library to open a new tab -->
<string name="library_new_tab">ନୂଆ ଟ୍ୟାବ୍</string>
<!-- Settings Page Title -->
<string name="settings_title">ସେଟିଂସମୂହ</string>
<!-- Option for auto closing tabs that will auto close tabs after one month -->
<string name="close_tabs_after_one_month">ଗୋଟେ ମାସ ପରେ</string>
<!-- Title of the active section on the studies list -->
<string name="studies_active">ସକ୍ରିୟ</string>
<!-- Content description (not visible, for screen readers etc.): Add tab button. Adds a news tab when pressed -->
<string name="add_tab">ଟ୍ୟାବ ଯୋଡ଼ନ୍ତୁ</string>
<!-- Content description (not visible, for screen readers etc.): Add tab button. Adds a news tab when pressed -->
<string name="add_private_tab">ବ୍ୟକ୍ତିଗତ ଟ୍ୟାବ୍ ଯୋଡ଼ନ୍ତୁ</string>
<!-- Text for the new tab button to indicate adding a new private tab in the tab -->
<string name="tab_drawer_fab_content">ବ୍ୟକ୍ତିଗତ</string>
<!-- Text for the header that groups the history for today -->
<string name="history_today">ଆଜି</string>
<!-- Text for the header that groups the history for yesterday -->
<string name="history_yesterday">ଗତକାଲି</string>
<!-- Text for the header that groups the history the past 7 days -->
<string name="history_7_days">ଗତ ୭ ଦିନ</string>
<!-- Text for the header that groups the history the past 30 days -->
<string name="history_30_days">ଗତ ୩୦ ଦିନ</string>
<!-- Bookmark overflow menu open in new tab button -->
<string name="bookmark_menu_open_in_new_tab_button">ନୂଆ ଟ୍ୟାବରେ ଖୋଲନ୍ତୁ</string>
<!-- Button to add new collection for the "select collection" step of the collection creator -->
<string name="create_collection_add_new_collection">ନୂଆ ସଂଗ୍ରହ ଯୋଡ଼ନ୍ତୁ</string>
<!-- Text for the copy link action in the share screen. -->
<string name="share_copy_link_to_clipboard">କ୍ଲିପବୋର୍ଡରେ କପି କରିନିଅନ୍ତୁ</string>
<!-- Toast shown after copying link to clipboard -->
<string name="toast_copy_link_to_clipboard">କ୍ଲିପବୋର୍ଡରେ କପି କରିନିଆଗଲା</string>
<!-- Add new device screen title -->
<string name="sync_add_new_device_title">ଡିଭାଇସକୁ ପଠାନ୍ତୁ</string>
<!-- Onboarding theme -->
<!-- text for the theme picker onboarding card header -->
<string name="onboarding_theme_picker_header">ଆପଣଙ୍କ ଥିମ୍ ବାଛନ୍ତୁ</string>
<!-- Automatic theme setting (will follow device setting) -->
<string name="onboarding_theme_automatic_title">ସ୍ୱୟଂଚାଳିତ</string>
<!-- Summary of automatic theme setting (will follow device setting) -->
<string name="onboarding_theme_automatic_summary">ଆପଣଙ୍କ ଡିଭାଇସ୍ ସେଟିଂସ୍ ସହ ଖାପ ଖାଇଥାଏ</string>
<!-- Theme setting for dark mode -->
<string name="onboarding_theme_dark_title">ଗାଢ଼ ଥିମ୍</string>
<!-- Theme setting for light mode -->
<string name="onboarding_theme_light_title">ହାଲୁକା ଥିମ୍</string>
<!-- Text shown for settings option for sign with email -->
<string name="sign_in_with_email">ଏହା ପରିବର୍ତ୍ତେ ଇମେଲ୍ ବ୍ୟବହାର କରନ୍ତୁ</string>
<!-- Enhanced Tracking Protection -->
<!-- Link displayed in enhanced tracking protection panel to access tracking protection settings -->
<string name="etp_settings">ସୁରକ୍ଷା ସେଟିଂସ୍</string>
<!-- Preference for enhanced tracking protection for the standard protection settings -->
<string name="preference_enhanced_tracking_protection_standard_default_1">ମାନକ (ଡିଫଲ୍ଟ)</string>
<!-- Preference description for enhanced tracking protection for the standard protection settings -->
<string name="preference_enhanced_tracking_protection_standard_description_4" moz:removedIn="114" tools:ignore="UnusedResources">ଗୋପନୀୟତା ଏବଂ କାର୍ଯ୍ୟଦକ୍ଷତା ପାଇଁ ସନ୍ତୁଳିତ। ପୃଷ୍ଠାଗୁଡିକ ସାଧାରଣ ଭାବରେ ଲୋଡ୍ ହୁଏ।</string>
<!-- Preference for enhanced tracking protection for the strict protection settings -->
<string name="preference_enhanced_tracking_protection_strict">କଡା</string>
<!-- Cancel button text for the Add to Homescreen dialog -->
<string name="add_to_homescreen_cancel">ବାତିଲ୍ କରନ୍ତୁ</string>
<!-- Add button text for the Add to Homescreen dialog -->
<string name="add_to_homescreen_add">ଯୋଡ଼ନ୍ତୁ</string>
<!-- Preference to access list of login exceptions that we never save logins for -->
<string name="preferences_passwords_exceptions">ବ୍ୟତିକ୍ରମଗୁଡ଼ିକ</string>
<!-- Text on button to remove all saved login exceptions -->
<string name="preferences_passwords_exceptions_remove_all">ସବୁ ବ୍ୟତିକ୍ରମ ବିଲୋପ କରନ୍ତୁ</string>
<!-- The header for the password for a login -->
<string name="preferences_passwords_saved_logins_password">ପାସ୍‍ୱାର୍ଡ଼</string>
<!-- Shown in snackbar to tell user that the password has been copied -->
<string name="logins_password_copied">ପାସ୍‍ୱାର୍ଡ଼ କ୍ଲିପବୋର୍ଡରେ କପି କରିନିଆଗଲା</string>
<!-- Content Description (for screenreaders etc) read for the button to copy a password in logins-->
<string name="saved_logins_copy_password">ପାସ୍‍ୱାର୍ଡ଼ କପିକରନ୍ତୁ</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 -->
<string name="saved_login_hide_password">ପାସ୍‍ୱାର୍ଡ଼ ଲୁଚାନ୍ତୁ</string>
<!-- label shown when there are not site exceptions to show in the site exception settings -->
<string name="no_site_exceptions">କୌଣସି ସାଇଟ୍ ବ୍ୟତିକ୍ରମ ହୋଇନାହିଁ</string>
<!-- The error message in add/edit login view when password field is blank. -->
<string name="saved_login_password_required">ପାସ୍‍ୱାର୍ଡ଼ ଆବଶ୍ୟକ</string>
<!-- Button caption to confirm the renaming of the top site. -->
<string name="top_sites_rename_dialog_ok">ଠିକ୍ ଅଛି</string>
<!-- Caption for describing a certain feature. The placeholder is for a clickable text (eg: Learn more) which will load an url in a new tab when clicked. -->
<string name="pocket_stories_feature_caption">Firefox ପରିବାରର ଏକ ଅଂଶ। %s</string>
</resources>

@ -419,7 +419,9 @@
<!-- Text for cancel button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_cancel_button">ਰੱਦ ਕਰੋ</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button">ਸਹਿਯੋਗ ਲਈ ਬੇਨਤੀ</string>
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button" moz:RemovedIn="115" tools:ignore="UnusedResources">ਸਹਿਯੋਗ ਲਈ ਬੇਨਤੀ</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button_2">ਬੇਨਤੀ ਭੇਜੀ</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title" moz:RemovedIn="114" tools:ignore="UnusedResources">ਕੂਕੀ ਬੈਨਰ ਘਟਾਉਣਾ</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->

@ -400,6 +400,10 @@
<string name="cookie_banner_handling_report_site_snack_bar_text_2">ඉල්ලීම යැවිණි</string>
<!-- Text for indicating cookie banner handling is on this site, this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_on_for_site">මෙම අඩවියට සක්‍රියයි</string>
<!-- Text for indicating that a request for unsupported site was sent to Nimbus (it's a Mozilla library for experiments), this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site_request_submitted" moz:RemovedIn="114" tools:ignore="UnusedResources">අඩවිය සඳහා සහාය ඉල්ලීම යොමු කෙරිණි</string>
<!-- Text for indicating that a request for unsupported site was sent to Nimbus (it's a Mozilla library for experiments), this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site_request_submitted_2">සහාය ඉල්ලීම යැවිණි</string>
<!-- Text for indicating cookie banner handling is currently not supported for this site, this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site">අඩවියට සහාය නොදක්වයි</string>
<!-- Title text for a detail explanation indicating cookie banner handling is on this site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is a shortened URL of the current site-->
@ -497,6 +501,8 @@
<string name="preferences_open_links_in_apps">යෙදුම්වල සබැඳි අරින්න</string>
<!-- Preference for open links in third party apps always open in apps option -->
<string name="preferences_open_links_in_apps_always">සැමවිටම</string>
<!-- Preference for open links in third party apps ask before opening option -->
<string name="preferences_open_links_in_apps_ask">ඇරීමට පෙර අසන්න</string>
<!-- Preference for open links in third party apps never open in apps option -->
<string name="preferences_open_links_in_apps_never">කවදාවත්</string>
<!-- Preference for open download with an external download manager app -->
@ -1193,6 +1199,9 @@
<!-- Name of the marketing notification channel. Displayed in the "App notifications" system settings for the app -->
<string name="notification_marketing_channel_name">අළෙවිකරණය</string>
<!-- Title shown in the notification that pops up to remind the user to set fenix as default browser.
The app name is in the text, due to limitations with localizing Nimbus experiments -->
<string name="nimbus_notification_default_browser_title" tools:ignore="UnusedResources">ෆයර්ෆොක්ස් වේගවත් හා පෞද්ගලිකයි</string>
<!-- Text shown in the notification that pops up to remind the user to set fenix as default browser.
The app name is in the text, due to limitations with localizing Nimbus experiments -->
<string name="nimbus_notification_default_browser_text" tools:ignore="UnusedResources">ෆයර්ෆොක්ස් පෙරනිමි අතිරික්සුව කරන්න</string>
@ -1201,8 +1210,15 @@
<!-- Text shown in the notification that pops up to re-engage the user.
%1$s is a placeholder that will be replaced by the app name. -->
<string name="notification_re_engagement_text">%1$s හි සුරැකින ලද දත්තකඩ හෝ ඉතිහාසයක් නැතිව පිරික්සන්න</string>
<string name="notification_re_engagement_text">%1$s හි සුරැකෙන දත්තකඩ හෝ ඉතිහාසයකින් තොරව පිරික්සන්න</string>
<!-- Title A shown in the notification that pops up to re-engage the user -->
<string name="notification_re_engagement_A_title">හෝඩුවාවකින් තොරව පිරික්සන්න</string>
<!-- Title B shown in the notification that pops up to re-engage the user -->
<string name="notification_re_engagement_B_title">ප්‍රථම සෙවීම අරඹන්න</string>
<!-- Preference for taking the short survey. -->
<string name="preferences_take_survey">සමීක්‍ෂණය ගන්න</string>
<!-- Preference for not taking the short survey. -->
<string name="preferences_not_take_survey">එපා, ස්තුතියි</string>
@ -1433,6 +1449,8 @@
<string name="preference_enhanced_tracking_protection_standard_default_1">සම්මත (පෙරනිමි)</string>
<!-- Preference description for enhanced tracking protection for the standard protection settings -->
<string name="preference_enhanced_tracking_protection_standard_description_4" moz:removedIn="114" tools:ignore="UnusedResources">ආරක්‍ෂාව හා කාර්ය සාධනය අතර සංතුලිතයි. පිටු සාමාන්‍ය ලෙස පූරණය වේ.</string>
<!-- Preference description for enhanced tracking protection for the standard protection settings -->
<string name="preference_enhanced_tracking_protection_standard_description_5">පිටු සුපුරුදු පරිදි පූරණය වුවත් ලුහුබැඳීම් අඩුවෙන් අවහිර කරයි.</string>
<!-- Accessibility text for the Standard protection information icon -->
<string name="preference_enhanced_tracking_protection_standard_info_button">සම්මත ලුහුබැඳීමේ රැකවරණය මගින් අවහිර කළ දෑ</string>
<!-- Preference for enhanced tracking protection for the strict protection settings -->

@ -122,6 +122,9 @@
<!-- Shortcut action to open new private tab -->
<string name="home_screen_shortcut_open_new_private_tab_2">แท็บส่วนตัวใหม่</string>
<!-- Shortcut action to open Passwords screens -->
<string name="home_screen_shortcut_open_password_screen">ทางลัดรหัสผ่าน</string>
<!-- Recent Tabs -->
<!-- Header text for jumping back into the recent tab in the home screen -->
<string name="recent_tabs_header">กลับเข้าไป</string>
@ -407,15 +410,23 @@
<string name="cookie_banner_handling_details_site_is_not_supported_cancel_button">ยกเลิก</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button" moz:RemovedIn="115" tools:ignore="UnusedResources">ขอการรองรับ</string>
<!-- Text for request support button indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_request_support_button_2">ส่งคำขอ</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title" moz:RemovedIn="114" tools:ignore="UnusedResources">การลดแบนเนอร์คุกกี้</string>
<!-- Text for title indicating that cookie banner reduction is not supported for the current site, this is shown as part of the cookie banner details view. -->
<string name="cookie_banner_handling_details_site_is_not_supported_title_2">ขอการรองรับไซต์นี้ไหม?</string>
<!-- Label for the snackBar, after the user reports with success a website where cookie banner reducer did not work -->
<string name="cookie_banner_handling_report_site_snack_bar_text" moz:RemovedIn="114" tools:ignore="UnusedResources">ส่งคำขอรองรับไซต์แล้ว</string>
<!-- Label for the snackBar, after the user reports with success a website where cookie banner reducer did not work -->
<string name="cookie_banner_handling_report_site_snack_bar_text_2">ส่งคำขอแล้ว</string>
<!-- Text for indicating cookie banner handling is on this site, this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_on_for_site">เปิดสำหรับไซต์นี้</string>
<!-- Text for indicating that a request for unsupported site was sent to Nimbus (it's a Mozilla library for experiments), this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site_request_submitted" moz:RemovedIn="114" tools:ignore="UnusedResources">ส่งคำขอรองรับไซต์แล้ว</string>
<!-- Text for indicating that a request for unsupported site was sent to Nimbus (it's a Mozilla library for experiments), this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site_request_submitted_2">ส่งคำขอรองรับแล้ว</string>
<!-- Text for indicating cookie banner handling is currently not supported for this site, this is shown as part of the protections panel with the tracking protection toggle -->
<string name="reduce_cookie_banner_unsupported_site">ไม่รองรับไซต์ในขณะนี้</string>
<!-- Title text for a detail explanation indicating cookie banner handling is on this site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is a shortened URL of the current site-->
@ -424,6 +435,8 @@
<string name="reduce_cookie_banner_details_panel_title_off_for_site">ต้องการปิดการลดแบนเนอร์คุกกี้สำหรับ %1$s หรือไม่?</string>
<!-- Title text for a detail explanation indicating cookie banner reducer didn't work for the current site, this is shown as part of the cookie banner panel in the toolbar.-->
<string name="reduce_cookie_banner_details_panel_title_unsupported_site_request" moz:RemovedIn="114" tools:ignore="UnusedResources">ขณะนี้ไซต์นี้ไม่รองรับการลดแบนเนอร์คุกกี้ คุณต้องการให้ทีมงานของเราตรวจสอบเว็บไซต์นี้และเพิ่มการรองรับในอนาคตหรือไม่?</string>
<!-- Title text for a detail explanation indicating cookie banner reducer didn't work for the current site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is the application name-->
<string name="reduce_cookie_banner_details_panel_title_unsupported_site_request_2">%1$s ไม่สามารถปฏิเสธคำขอคุกกี้โดยอัตโนมัติบนไซต์นี้ คุณสามารถส่งคำขอให้รองรับไซต์นี้ในอนาคตได้</string>
<!-- Long text for a detail explanation indicating what will happen if cookie banner handling is off for a site, this is shown as part of the cookie banner panel in the toolbar. The first parameter is the application name -->
<string name="reduce_cookie_banner_details_panel_description_off_for_site">%1$s จะล้างคุกกี้ของไซต์นี้และรีเฟรชหน้า การล้างคุกกี้ทั้งหมดอาจนำคุณออกจากระบบหรือล้างรถเข็นช็อปปิ้ง</string>
@ -1349,6 +1362,8 @@
<string name="delete_history_prompt_title">ช่วงเวลาที่จะลบ</string>
<!-- Body text of a delete browsing data dialog. -->
<string name="delete_history_prompt_body" moz:RemovedIn="130" tools:ignore="UnusedResources">ลบประวัติ (รวมถึงประวัติที่ซิงค์จากอุปกรณ์อื่น) คุกกี้ และข้อมูลการเรียกดูอื่น ๆ</string>
<!-- Body text of a delete browsing data dialog. -->
<string name="delete_history_prompt_body_2">ลบประวัติ (รวมถึงประวัติที่ซิงค์จากอุปกรณ์อื่น)</string>
<!-- Radio button in the delete browsing data dialog to delete history items for the last hour. -->
<string name="delete_history_prompt_button_last_hour">ชั่วโมงที่แล้ว</string>
<!-- Radio button in the delete browsing data dialog to delete history items for today and yesterday. -->

@ -290,6 +290,12 @@
<string name="pref_key_show_collections_placeholder_home" translatable="false">pref_key_show_collections_home</string>
<!-- Adjust Activated User values-->
<string name="pref_key_growth_user_activated_sent" translatable="false">pref_key_growth_user_activated_sent</string>
<string name="pref_key_growth_early_browse_count" translatable="false">pref_key_growth_early_browse_count</string>
<string name="pref_key_growth_early_browse_count_last_increment" translatable="false">pref_key_growth_early_browse_count_last_increment</string>
<string name="pref_key_growth_early_search" translatable="false">pref_key_growth_early_search</string>
<!-- Tabs Settings -->
<!-- pref_key_tab_view_list_do_not_use is needed for UI implementation only and should be expected to be useful. -->
<string name="pref_key_tab_view_list_do_not_use" translatable="false">pref_key_tab_view_list</string>

@ -6,7 +6,9 @@ package org.mozilla.fenix.components.metrics
import android.app.Activity
import android.app.Application
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.slot
import io.mockk.verify
@ -382,6 +384,114 @@ class DefaultMetricsStorageTest {
assertTrue(updateSlot.captured > 0)
}
@Test
fun `GIVEN first week activated days of use and search use thresholds reached THEN will be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 5)
every { settings.growthEarlyUseCount.value } returns 3
every { settings.growthEarlySearchUsed } returns true
every { settings.growthUserActivatedSent } returns false
val result = storage.shouldTrack(Event.GrowthData.UserActivated(fromSearch = false))
assertTrue(result)
}
@Test
fun `GIVEN first week activated days of use threshold not reached THEN will not be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 5)
every { settings.growthEarlyUseCount.value } returns 1
every { settings.growthEarlySearchUsed } returns true
every { settings.growthUserActivatedSent } returns false
val result = storage.shouldTrack(Event.GrowthData.UserActivated(fromSearch = false))
assertFalse(result)
}
@Test
fun `GIVEN first week activated search use threshold not reached THEN will not be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 5)
every { settings.growthEarlyUseCount.value } returns 3
every { settings.growthEarlySearchUsed } returns false
every { settings.growthUserActivatedSent } returns false
val result = storage.shouldTrack(Event.GrowthData.UserActivated(fromSearch = false))
assertFalse(result)
}
@Test
fun `GIVEN first week activated already sent WHEN first week activated signal sent THEN userActivated will not be sent`() = runTest(dispatcher) {
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 5)
every { settings.growthEarlyUseCount.value } returns 3
every { settings.growthEarlySearchUsed } returns true
every { settings.growthUserActivatedSent } returns true
val result = storage.shouldTrack(Event.GrowthData.UserActivated(fromSearch = false))
assertFalse(result)
}
@Test
fun `WHEN first week usage signal is sent a full day after last sent THEN settings will be updated accordingly`() = runTest(dispatcher) {
val captureSent = slot<Long>()
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 3)
every { settings.growthEarlyUseCount.value } returns 1
every { settings.growthEarlyUseCount.increment() } just Runs
every { settings.growthEarlyUseCountLastIncrement } returns 0L
every { settings.growthEarlyUseCountLastIncrement = capture(captureSent) } returns Unit
storage.updatePersistentState(Event.GrowthData.UserActivated(fromSearch = false))
assertTrue(captureSent.captured > 0L)
}
@Test
fun `WHEN first week usage signal is sent less than a full day after last sent THEN settings will not be updated`() = runTest(dispatcher) {
val captureSent = slot<Long>()
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 3)
val lastUsageIncrementTime = currentTime - (dayMillis / 2)
every { settings.growthEarlyUseCount.value } returns 1
every { settings.growthEarlyUseCountLastIncrement } returns lastUsageIncrementTime
every { settings.growthEarlyUseCountLastIncrement = capture(captureSent) } returns Unit
storage.updatePersistentState(Event.GrowthData.UserActivated(fromSearch = false))
assertFalse(captureSent.isCaptured)
}
@Test
fun `WHEN first week search activity is sent in second half of first week THEN settings will be updated`() = runTest(dispatcher) {
val captureSent = slot<Boolean>()
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 3) - 100
every { settings.growthEarlySearchUsed } returns false
every { settings.growthEarlySearchUsed = capture(captureSent) } returns Unit
storage.updatePersistentState(Event.GrowthData.UserActivated(fromSearch = true))
assertTrue(captureSent.captured)
}
@Test
fun `WHEN first week search activity is sent in first half of first week THEN settings will not be updated`() = runTest(dispatcher) {
val captureSent = slot<Boolean>()
val currentTime = System.currentTimeMillis()
installTime = currentTime - (dayMillis * 3) + 100
every { settings.growthEarlySearchUsed } returns false
every { settings.growthEarlySearchUsed = capture(captureSent) } returns Unit
storage.updatePersistentState(Event.GrowthData.UserActivated(fromSearch = true))
assertFalse(captureSent.isCaptured)
}
private fun Calendar.copy() = clone() as Calendar
private fun Calendar.createNextDay() = copy().apply {
add(Calendar.DAY_OF_MONTH, 1)

@ -15,7 +15,7 @@ class JunoOnboardingMapperTest {
@Test
fun `GIVEN a default browser page WHEN mapToOnboardingPageState is called THEN creates the expected OnboardingPageState`() {
val expected = OnboardingPageState(
image = R.drawable.ic_onboarding_welcome,
imageRes = R.drawable.ic_onboarding_welcome,
title = "default browser title",
description = "default browser body with link text",
linkTextState = LinkTextState(
@ -54,7 +54,7 @@ class JunoOnboardingMapperTest {
@Test
fun `GIVEN a sync page WHEN mapToOnboardingPageState is called THEN creates the expected OnboardingPageState`() {
val expected = OnboardingPageState(
image = R.drawable.ic_onboarding_sync,
imageRes = R.drawable.ic_onboarding_sync,
title = "sync title",
description = "sync body",
primaryButton = Action("sync primary button text", unitLambda),
@ -88,7 +88,7 @@ class JunoOnboardingMapperTest {
@Test
fun `GIVEN a notification page WHEN mapToOnboardingPageState is called THEN creates the expected OnboardingPageState`() {
val expected = OnboardingPageState(
image = R.drawable.ic_notification_permission,
imageRes = R.drawable.ic_notification_permission,
title = "notification title",
description = "notification body",
primaryButton = Action("notification primary button text", unitLambda),

@ -1,114 +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.wallpapers
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File
class LegacyWallpaperFileManagerTest {
@Rule
@JvmField
val tempFolder = TemporaryFolder()
private lateinit var portraitLightFolder: File
private lateinit var portraitDarkFolder: File
private lateinit var landscapeLightFolder: File
private lateinit var landscapeDarkFolder: File
private val dispatcher = UnconfinedTestDispatcher()
private lateinit var fileManager: LegacyWallpaperFileManager
@Before
fun setup() {
portraitLightFolder = tempFolder.newFolder("wallpapers", "portrait", "light")
portraitDarkFolder = tempFolder.newFolder("wallpapers", "portrait", "dark")
landscapeLightFolder = tempFolder.newFolder("wallpapers", "landscape", "light")
landscapeDarkFolder = tempFolder.newFolder("wallpapers", "landscape", "dark")
fileManager = LegacyWallpaperFileManager(
rootDirectory = tempFolder.root,
coroutineDispatcher = dispatcher,
)
}
@Test
fun `GIVEN files exist in all directories WHEN expired wallpaper looked up THEN expired wallpaper returned`() = runTest {
val wallpaperName = "name"
createAllFiles(wallpaperName)
val result = fileManager.lookupExpiredWallpaper(wallpaperName)
val expected = generateWallpaper(name = wallpaperName)
assertEquals(expected, result)
}
@Test
fun `GIVEN any missing file in directories WHEN expired wallpaper looked up THEN null returned`() = runTest {
val wallpaperName = "name"
File(landscapeLightFolder, "$wallpaperName.png").createNewFile()
File(landscapeDarkFolder, "$wallpaperName.png").createNewFile()
val result = fileManager.lookupExpiredWallpaper(wallpaperName)
assertEquals(null, result)
}
@Test
fun `WHEN cleaned THEN current wallpaper and available wallpapers kept`() {
val currentName = "current"
val currentWallpaper = generateWallpaper(name = currentName)
val availableName = "available"
val available = generateWallpaper(name = availableName)
val unavailableName = "unavailable"
createAllFiles(currentName)
createAllFiles(availableName)
createAllFiles(unavailableName)
fileManager.clean(currentWallpaper, listOf(available))
assertTrue(getAllFiles(currentName).all { it.exists() })
assertTrue(getAllFiles(availableName).all { it.exists() })
assertTrue(getAllFiles(unavailableName).none { it.exists() })
}
private fun createAllFiles(name: String) {
for (file in getAllFiles(name)) {
file.createNewFile()
}
}
private fun getAllFiles(name: String): List<File> {
return listOf(
File(portraitLightFolder, "$name.png"),
File(portraitDarkFolder, "$name.png"),
File(landscapeLightFolder, "$name.png"),
File(landscapeDarkFolder, "$name.png"),
)
}
private fun generateWallpaper(name: String) = Wallpaper(
name = name,
textColor = null,
cardColorLight = null,
cardColorDark = null,
thumbnailFileState = Wallpaper.ImageFileState.Unavailable,
assetsFileState = Wallpaper.ImageFileState.Downloaded,
collection = Wallpaper.Collection(
name = Wallpaper.defaultName,
heading = null,
description = null,
availableLocales = null,
startDate = null,
endDate = null,
learnMoreUrl = null,
),
)
}

@ -48,10 +48,6 @@ class WallpapersUseCasesTest {
every { shouldMigrateLegacyWallpaperCardColors } returns false
every { shouldMigrateLegacyWallpaperCardColors = any() } just Runs
}
private val mockLegacyDownloader = mockk<LegacyWallpaperDownloader>(relaxed = true)
private val mockLegacyFileManager = mockk<LegacyWallpaperFileManager> {
every { clean(any(), any()) } just runs
}
private lateinit var mockMigrationHelper: LegacyWallpaperMigration
private val mockMetadataFetcher = mockk<WallpaperMetadataFetcher>()
@ -76,187 +72,6 @@ class WallpapersUseCasesTest {
)
}
@Test
fun `GIVEN legacy use case WHEN initializing THEN the default wallpaper is not downloaded`() = runTest {
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
}
every { mockSettings.currentWallpaperName } returns ""
coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore,
mockLegacyDownloader,
mockLegacyFileManager,
mockSettings,
"en-US",
possibleWallpapers = listOf(Wallpaper.Default) + fakeRemoteWallpapers,
).invoke()
appStore.waitUntilIdle()
coVerify(exactly = 0) { mockLegacyDownloader.downloadWallpaper(Wallpaper.Default) }
}
@Test
fun `GIVEN legacy use case WHEN initializing THEN default wallpaper is included in available wallpapers`() = runTest {
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
}
every { mockSettings.currentWallpaperName } returns ""
coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore,
mockLegacyDownloader,
mockLegacyFileManager,
mockSettings,
"en-US",
possibleWallpapers = listOf(Wallpaper.Default) + fakeRemoteWallpapers,
).invoke()
appStore.waitUntilIdle()
assertTrue(appStore.state.wallpaperState.availableWallpapers.contains(Wallpaper.Default))
}
@Test
fun `GIVEN legacy use case and wallpapers that expired WHEN invoking initialize use case THEN expired wallpapers are filtered out and cleaned up`() = runTest {
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
}
val fakeExpiredRemoteWallpapers = listOf("expired").map { name ->
makeFakeRemoteWallpaper(TimeRelation.BEFORE, name)
}
val possibleWallpapers = fakeRemoteWallpapers + fakeExpiredRemoteWallpapers
every { mockSettings.currentWallpaperName } returns ""
coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore,
mockLegacyDownloader,
mockLegacyFileManager,
mockSettings,
"en-US",
possibleWallpapers = possibleWallpapers,
).invoke()
val expectedFilteredWallpaper = fakeExpiredRemoteWallpapers[0]
appStore.waitUntilIdle()
assertFalse(appStore.state.wallpaperState.availableWallpapers.contains(expectedFilteredWallpaper))
verify { mockLegacyFileManager.clean(Wallpaper.Default, possibleWallpapers) }
}
@Test
fun `GIVEN legacy use case and wallpapers that expired and an expired one is selected WHEN invoking initialize use case THEN selected wallpaper is not filtered out`() = runTest {
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
}
val expiredWallpaper = makeFakeRemoteWallpaper(TimeRelation.BEFORE, "expired")
every { mockSettings.currentWallpaperName } returns expiredWallpaper.name
coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore,
mockLegacyDownloader,
mockLegacyFileManager,
mockSettings,
"en-US",
possibleWallpapers = fakeRemoteWallpapers + listOf(expiredWallpaper),
).invoke()
appStore.waitUntilIdle()
assertTrue(appStore.state.wallpaperState.availableWallpapers.contains(expiredWallpaper))
assertEquals(expiredWallpaper, appStore.state.wallpaperState.currentWallpaper)
}
@Test
fun `GIVEN legacy use case and wallpapers that are in promotions outside of locale WHEN invoking initialize use case THEN promotional wallpapers are filtered out`() = runTest {
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
}
val locale = "en-CA"
every { mockSettings.currentWallpaperName } returns ""
coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore,
mockLegacyDownloader,
mockLegacyFileManager,
mockSettings,
locale,
possibleWallpapers = fakeRemoteWallpapers,
).invoke()
appStore.waitUntilIdle()
assertTrue(appStore.state.wallpaperState.availableWallpapers.isEmpty())
}
@Test
fun `GIVEN legacy use case and available wallpapers WHEN invoking initialize use case THEN available wallpapers downloaded`() = runTest {
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
}
every { mockSettings.currentWallpaperName } returns ""
coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore,
mockLegacyDownloader,
mockLegacyFileManager,
mockSettings,
"en-US",
possibleWallpapers = fakeRemoteWallpapers,
).invoke()
for (fakeRemoteWallpaper in fakeRemoteWallpapers) {
coVerify { mockLegacyDownloader.downloadWallpaper(fakeRemoteWallpaper) }
}
}
@Test
fun `GIVEN legacy use case and a wallpaper has not been selected WHEN invoking initialize use case THEN app store contains default`() = runTest {
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
}
every { mockSettings.currentWallpaperName } returns ""
coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore,
mockLegacyDownloader,
mockLegacyFileManager,
mockSettings,
"en-US",
possibleWallpapers = fakeRemoteWallpapers,
).invoke()
appStore.waitUntilIdle()
assertTrue(appStore.state.wallpaperState.currentWallpaper == Wallpaper.Default)
}
@Test
fun `GIVEN legacy use case a wallpaper is selected and there are available wallpapers WHEN invoking initialize use case THEN these are dispatched to the app store`() = runTest {
val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected")
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
makeFakeRemoteWallpaper(TimeRelation.LATER, name)
}
val possibleWallpapers = listOf(selectedWallpaper) + fakeRemoteWallpapers
every { mockSettings.currentWallpaperName } returns selectedWallpaper.name
coEvery { mockLegacyFileManager.lookupExpiredWallpaper(any()) } returns null
WallpapersUseCases.LegacyInitializeWallpaperUseCase(
appStore,
mockLegacyDownloader,
mockLegacyFileManager,
mockSettings,
"en-US",
possibleWallpapers = possibleWallpapers,
).invoke()
appStore.waitUntilIdle()
assertEquals(selectedWallpaper, appStore.state.wallpaperState.currentWallpaper)
assertEquals(possibleWallpapers, appStore.state.wallpaperState.availableWallpapers)
}
@Test
fun `WHEN initializing THEN the default wallpaper is not downloaded`() = runTest {
val fakeRemoteWallpapers = listOf("first", "second", "third").map { name ->
@ -558,24 +373,6 @@ class WallpapersUseCasesTest {
assertEquals(expectedWallpapers, appStore.state.wallpaperState.availableWallpapers)
}
@Test
fun `WHEN legacy selected wallpaper usecase invoked THEN storage updated and app store receives dispatch`() = runTest {
val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected")
every { mockSettings.currentWallpaperName = any() } just Runs
val wallpaperFileState = WallpapersUseCases.LegacySelectWallpaperUseCase(
mockSettings,
appStore,
).invoke(selectedWallpaper)
appStore.waitUntilIdle()
verify { mockSettings.currentWallpaperName = selectedWallpaper.name }
verify { mockSettings.currentWallpaperTextColor = selectedWallpaper.textColor!! }
assertEquals(selectedWallpaper, appStore.state.wallpaperState.currentWallpaper)
assertEquals(wallpaperFileState, Wallpaper.ImageFileState.Downloaded)
}
@Test
fun `GIVEN wallpaper downloaded WHEN selecting a wallpaper THEN storage updated and app store receives dispatch`() = runTest {
val selectedWallpaper = makeFakeRemoteWallpaper(TimeRelation.LATER, "selected")

Loading…
Cancel
Save