feat(downloads): warn about Android background restrictions

This commit is contained in:
edde746
2026-07-26 04:24:55 +02:00
parent 1b3c74550f
commit e251273322
61 changed files with 4388 additions and 46 deletions
+30
View File
@@ -417,6 +417,36 @@ android {
}
}
// BackgroundWorkDiagnostics routes users to background_downloader's private
// notification channel. Fail the build if an upstream ref changes that ID.
val verifyBackgroundDownloaderNotificationChannel = tasks.register("verifyBackgroundDownloaderNotificationChannel") {
val expectedChannelId = "background_downloader"
val downloaderProject = rootProject.findProject(":background_downloader")
val notificationsSource = downloaderProject?.projectDir?.resolve(
"src/main/kotlin/com/bbflight/background_downloader/Notifications.kt"
)
notificationsSource?.let(inputs::file)
doLast {
if (notificationsSource == null) {
logger.lifecycle("Skipping downloader channel verification: Flutter plugin project is not configured")
return@doLast
}
val actualChannelId = Regex(
"""private const val notificationChannelId\s*=\s*"([^"]+)""""
).find(notificationsSource.readText())?.groupValues?.get(1)
?: throw GradleException("Could not locate background_downloader's notification channel ID")
if (actualChannelId != expectedChannelId) {
throw GradleException(
"background_downloader channel ID changed from $expectedChannelId to $actualChannelId; " +
"update BackgroundWorkDiagnostics and its tests"
)
}
}
}
tasks.named("preBuild").configure {
dependsOn(verifyBackgroundDownloaderNotificationChannel)
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
@@ -0,0 +1,256 @@
package com.edde746.plezy
import android.app.ActivityManager
import android.app.NotificationManager
import android.app.usage.UsageStatsManager
import android.content.Context
import android.content.Intent
import android.net.ConnectivityManager
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.provider.Settings
/**
* Raw per-signal snapshot of everything the OS is willing to tell us about
* whether our own background work will be allowed to run.
*
* Every field is nullable: a null means "this API level cannot answer", not
* "unrestricted". [BackgroundWorkClassifier] treats unknown as OK, because a
* warning we cannot substantiate is worse than no warning at all.
*/
internal data class BackgroundWorkSignals(
val sdkInt: Int? = null,
val backgroundRestricted: Boolean? = null,
val standbyBucket: Int? = null,
val notificationsEnabled: Boolean? = null,
val downloadChannelBlocked: Boolean? = null,
val dataSaverRestricted: Boolean? = null,
val ignoringBatteryOptimizations: Boolean? = null
)
/**
* Turns [BackgroundWorkSignals] into a verdict plus ordered reasons.
*
* Deliberately pure and free of `Build.VERSION` reads so the whole decision
* table is exercisable from plain JVM unit tests.
*/
internal object BackgroundWorkClassifier {
const val VERDICT_OK = "ok"
const val VERDICT_DEGRADED = "degraded"
const val VERDICT_BLOCKED = "blocked"
const val REASON_BACKGROUND_RESTRICTED = "background_restricted"
const val REASON_STANDBY_RESTRICTED = "standby_restricted"
const val REASON_DOWNLOAD_CHANNEL_BLOCKED = "download_channel_blocked"
const val REASON_NOTIFICATIONS_DISABLED = "notifications_disabled"
const val REASON_DATA_SAVER = "data_saver"
/**
* `STANDBY_BUCKET_RESTRICTED` (API 30) and `STANDBY_BUCKET_NEVER`. Declared
* literally so the classifier stays compilable and testable below those API
* levels. `RARE` (40) is intentionally absent — it is reached by ordinary
* disuse and says nothing about a user-imposed restriction, so warning on it
* would fire on healthy devices.
*/
const val BUCKET_RESTRICTED = 45
const val BUCKET_NEVER = 50
private val blockingReasons = setOf(
REASON_BACKGROUND_RESTRICTED,
REASON_STANDBY_RESTRICTED
)
/** Ordered most- to least-actionable; the UI leads with the first entry. */
fun reasons(signals: BackgroundWorkSignals): List<String> = buildList {
if (signals.backgroundRestricted == true) add(REASON_BACKGROUND_RESTRICTED)
if (signals.standbyBucket != null && isRestrictedBucket(signals.standbyBucket)) add(REASON_STANDBY_RESTRICTED)
// App-wide denial is more fundamental than a muted channel and routes to
// a screen that also exists before the channel has been created.
if (signals.notificationsEnabled == false) {
add(REASON_NOTIFICATIONS_DISABLED)
} else if (signals.downloadChannelBlocked == true) {
add(REASON_DOWNLOAD_CHANNEL_BLOCKED)
}
if (signals.dataSaverRestricted == true) add(REASON_DATA_SAVER)
}
fun verdict(signals: BackgroundWorkSignals, reasons: List<String> = reasons(signals)): String = when {
reasons.any(blockingReasons::contains) -> VERDICT_BLOCKED
signals.sdkInt != null &&
signals.sdkInt >= Build.VERSION_CODES.TIRAMISU &&
signals.notificationsEnabled == false -> VERDICT_BLOCKED
reasons.isNotEmpty() -> VERDICT_DEGRADED
else -> VERDICT_OK
}
fun isRestrictedBucket(bucket: Int): Boolean = bucket == BUCKET_RESTRICTED || bucket == BUCKET_NEVER
fun toMap(signals: BackgroundWorkSignals): Map<String, Any?> {
val reasons = reasons(signals)
return mapOf(
"verdict" to verdict(signals, reasons),
"reasons" to reasons,
"sdkInt" to signals.sdkInt,
"backgroundRestricted" to signals.backgroundRestricted,
"standbyBucket" to signals.standbyBucket,
"notificationsEnabled" to signals.notificationsEnabled,
"downloadChannelBlocked" to signals.downloadChannelBlocked,
"dataSaverRestricted" to signals.dataSaverRestricted,
"ignoringBatteryOptimizations" to signals.ignoringBatteryOptimizations
)
}
}
/** Settings screen a remedy button can send the user to. */
internal enum class BackgroundSettingsTarget(val id: String) {
APP_DETAILS("app_details"),
APP_NOTIFICATIONS("app_notifications"),
NOTIFICATION_CHANNEL("notification_channel");
companion object {
fun fromId(raw: String?): BackgroundSettingsTarget? = entries.firstOrNull { it.id == raw }
}
}
/** Declarative intent description, kept Intent-free so it unit-tests on the JVM. */
internal data class SettingsIntentSpec(
val action: String,
val data: String? = null,
val stringExtras: Map<String, String> = emptyMap()
)
/**
* Ordered candidate intents per target, each falling back to the app details
* page — the one Settings screen that exists on every Android build and that
* reaches Battery → Unrestricted on One UI, Pixel, and AOSP alike.
*
* Samsung's own "Background usage limits" screen lives in an unexported
* `com.samsung.android.lool` activity that moves between One UI releases, so
* it is deliberately not targeted; the user gets written directions instead.
*/
internal object BackgroundSettingsIntents {
fun specsFor(
target: BackgroundSettingsTarget,
packageName: String,
sdkInt: Int
): List<SettingsIntentSpec> {
val appDetails = SettingsIntentSpec(
action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
data = "package:$packageName"
)
if (sdkInt < Build.VERSION_CODES.O) return listOf(appDetails)
return when (target) {
BackgroundSettingsTarget.APP_DETAILS -> listOf(appDetails)
BackgroundSettingsTarget.APP_NOTIFICATIONS -> listOf(
SettingsIntentSpec(
action = Settings.ACTION_APP_NOTIFICATION_SETTINGS,
stringExtras = mapOf(Settings.EXTRA_APP_PACKAGE to packageName)
),
appDetails
)
BackgroundSettingsTarget.NOTIFICATION_CHANNEL -> listOf(
SettingsIntentSpec(
action = Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS,
stringExtras = mapOf(
Settings.EXTRA_APP_PACKAGE to packageName,
Settings.EXTRA_CHANNEL_ID to BackgroundWorkDiagnostics.DOWNLOAD_NOTIFICATION_CHANNEL_ID
)
),
SettingsIntentSpec(
action = Settings.ACTION_APP_NOTIFICATION_SETTINGS,
stringExtras = mapOf(Settings.EXTRA_APP_PACKAGE to packageName)
),
appDetails
)
}
}
}
/**
* Reads the OS-visible reasons background downloads may be blocked.
*
* Every read is for our own package and needs no permission. Notably absent is
* `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS`: it is a Play-policy-restricted
* permission whose qualifying use cases do not include media downloading.
* Whitelist state is reported for support diagnostics only.
*/
internal object BackgroundWorkDiagnostics {
/** `background_downloader`'s channel id (`Notifications.kt`, `nId`). */
const val DOWNLOAD_NOTIFICATION_CHANNEL_ID = "background_downloader"
fun read(context: Context): BackgroundWorkSignals = BackgroundWorkSignals(
sdkInt = Build.VERSION.SDK_INT,
backgroundRestricted = readBackgroundRestricted(context),
standbyBucket = readStandbyBucket(context),
notificationsEnabled = readNotificationsEnabled(context),
downloadChannelBlocked = readDownloadChannelBlocked(context),
dataSaverRestricted = readDataSaverRestricted(context),
ignoringBatteryOptimizations = readIgnoringBatteryOptimizations(context)
)
private fun readBackgroundRestricted(context: Context): Boolean? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return null
return runCatching {
(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).isBackgroundRestricted
}.getOrNull()
}
private fun readStandbyBucket(context: Context): Int? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) return null
return runCatching {
(context.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager).appStandbyBucket
}.getOrNull()
}
private fun readNotificationsEnabled(context: Context): Boolean? = runCatching {
(context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).areNotificationsEnabled()
}.getOrNull()
/**
* Null until the plugin has created its channel (first download), which is
* correct — there is nothing to warn about before then.
*/
private fun readDownloadChannelBlocked(context: Context): Boolean? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null
return runCatching {
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = manager.getNotificationChannel(DOWNLOAD_NOTIFICATION_CHANNEL_ID) ?: return null
channel.importance == NotificationManager.IMPORTANCE_NONE
}.getOrNull()
}
private fun readDataSaverRestricted(context: Context): Boolean? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) return null
return runCatching {
val connectivity = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivity.restrictBackgroundStatus == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED
}.getOrNull()
}
private fun readIgnoringBatteryOptimizations(context: Context): Boolean? = runCatching {
(context.getSystemService(Context.POWER_SERVICE) as PowerManager)
.isIgnoringBatteryOptimizations(context.packageName)
}.getOrNull()
/**
* Launches the first candidate Settings screen that resolves. Returns false
* when the device has none of them — some TV and Fire OS builds ship without
* a battery settings activity entirely.
*/
fun openSettings(context: Context, target: BackgroundSettingsTarget): Boolean {
for (spec in BackgroundSettingsIntents.specsFor(target, context.packageName, Build.VERSION.SDK_INT)) {
val launched = runCatching {
val intent = Intent(spec.action).apply {
spec.data?.let { data = Uri.parse(it) }
spec.stringExtras.forEach { (key, value) -> putExtra(key, value) }
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(intent)
true
}.getOrDefault(false)
if (launched) return true
}
return false
}
}
@@ -573,6 +573,13 @@ class MainActivity : FlutterActivity() {
"getTvDetection" -> result.success(getAndroidTvDetection())
"getDeviceName" -> result.success(getDeviceName())
"getPerformanceSignals" -> result.success(getPerformanceSignals())
"getBackgroundWorkSignals" -> result.success(
BackgroundWorkClassifier.toMap(BackgroundWorkDiagnostics.read(this))
)
"openBackgroundSettings" -> {
val target = BackgroundSettingsTarget.fromId(call.arguments as? String)
result.success(target != null && BackgroundWorkDiagnostics.openSettings(this, target))
}
"getPreviousExit" -> handlePreviousExit(result)
"setStartupPhase" -> queueStartupPhase(call.arguments as? String, result)
"setRuntimeUiState" -> {
@@ -0,0 +1,279 @@
package com.edde746.plezy
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class BackgroundWorkDiagnosticsTest {
@Test
fun unknownSignalsAreTreatedAsHealthy() {
val signals = BackgroundWorkSignals()
assertEquals(emptyList<String>(), BackgroundWorkClassifier.reasons(signals))
assertEquals(BackgroundWorkClassifier.VERDICT_OK, verdictFor(signals))
}
@Test
fun unrestrictedDeviceIsHealthy() {
val signals = BackgroundWorkSignals(
backgroundRestricted = false,
standbyBucket = 10,
notificationsEnabled = true,
downloadChannelBlocked = false,
dataSaverRestricted = false,
ignoringBatteryOptimizations = false
)
assertEquals(emptyList<String>(), BackgroundWorkClassifier.reasons(signals))
assertEquals(BackgroundWorkClassifier.VERDICT_OK, verdictFor(signals))
}
@Test
fun missingBatteryWhitelistAloneNeverWarns() {
// The overwhelming majority of healthy installs are not whitelisted; a
// warning keyed off this would fire on essentially every device.
val signals = BackgroundWorkSignals(
backgroundRestricted = false,
standbyBucket = 20,
notificationsEnabled = true,
ignoringBatteryOptimizations = false
)
assertEquals(BackgroundWorkClassifier.VERDICT_OK, verdictFor(signals))
}
@Test
fun rareBucketIsNotRestricted() {
// RARE is reached by ordinary disuse and is not a user-imposed limit.
assertFalse(BackgroundWorkClassifier.isRestrictedBucket(10))
assertFalse(BackgroundWorkClassifier.isRestrictedBucket(20))
assertFalse(BackgroundWorkClassifier.isRestrictedBucket(30))
assertFalse(BackgroundWorkClassifier.isRestrictedBucket(40))
val signals = BackgroundWorkSignals(standbyBucket = 40, notificationsEnabled = true)
assertEquals(BackgroundWorkClassifier.VERDICT_OK, verdictFor(signals))
}
@Test
fun restrictedAndNeverBucketsBlock() {
assertTrue(BackgroundWorkClassifier.isRestrictedBucket(BackgroundWorkClassifier.BUCKET_RESTRICTED))
assertTrue(BackgroundWorkClassifier.isRestrictedBucket(BackgroundWorkClassifier.BUCKET_NEVER))
for (bucket in listOf(BackgroundWorkClassifier.BUCKET_RESTRICTED, BackgroundWorkClassifier.BUCKET_NEVER)) {
val signals = BackgroundWorkSignals(standbyBucket = bucket, notificationsEnabled = true)
assertEquals(
listOf(BackgroundWorkClassifier.REASON_STANDBY_RESTRICTED),
BackgroundWorkClassifier.reasons(signals)
)
assertEquals(BackgroundWorkClassifier.VERDICT_BLOCKED, verdictFor(signals))
}
}
@Test
fun backgroundRestrictionBlocksAndLeadsTheReasons() {
// The Samsung "Background usage limits" case that motivated this: both the
// AOSP restriction flag and the standby bucket flip together.
val signals = BackgroundWorkSignals(
backgroundRestricted = true,
standbyBucket = BackgroundWorkClassifier.BUCKET_RESTRICTED,
notificationsEnabled = true,
downloadChannelBlocked = false
)
assertEquals(
listOf(
BackgroundWorkClassifier.REASON_BACKGROUND_RESTRICTED,
BackgroundWorkClassifier.REASON_STANDBY_RESTRICTED
),
BackgroundWorkClassifier.reasons(signals)
)
assertEquals(BackgroundWorkClassifier.VERDICT_BLOCKED, verdictFor(signals))
}
@Test
fun disabledNotificationsBlockOnAndroid13AndNewer() {
val signals = BackgroundWorkSignals(
sdkInt = 33,
backgroundRestricted = false,
notificationsEnabled = false
)
assertEquals(
listOf(BackgroundWorkClassifier.REASON_NOTIFICATIONS_DISABLED),
BackgroundWorkClassifier.reasons(signals)
)
assertEquals(BackgroundWorkClassifier.VERDICT_BLOCKED, verdictFor(signals))
}
@Test
fun disabledNotificationsOnlyDegradeBeforeAndroid13() {
val signals = BackgroundWorkSignals(
sdkInt = 32,
backgroundRestricted = false,
notificationsEnabled = false
)
assertEquals(BackgroundWorkClassifier.VERDICT_DEGRADED, verdictFor(signals))
}
@Test
fun mutedDownloadChannelIsDegraded() {
val signals = BackgroundWorkSignals(notificationsEnabled = true, downloadChannelBlocked = true)
assertEquals(
listOf(BackgroundWorkClassifier.REASON_DOWNLOAD_CHANNEL_BLOCKED),
BackgroundWorkClassifier.reasons(signals)
)
assertEquals(BackgroundWorkClassifier.VERDICT_DEGRADED, verdictFor(signals))
}
@Test
fun appWideNotificationDenialSupersedesTheChannelReason() {
val signals = BackgroundWorkSignals(sdkInt = 33, notificationsEnabled = false, downloadChannelBlocked = true)
assertEquals(
listOf(BackgroundWorkClassifier.REASON_NOTIFICATIONS_DISABLED),
BackgroundWorkClassifier.reasons(signals)
)
assertEquals(BackgroundWorkClassifier.VERDICT_BLOCKED, verdictFor(signals))
}
@Test
fun absentChannelIsNotAFault() {
// The plugin creates its channel on first download; before then there is
// nothing to warn about.
val signals = BackgroundWorkSignals(notificationsEnabled = true, downloadChannelBlocked = null)
assertEquals(BackgroundWorkClassifier.VERDICT_OK, verdictFor(signals))
}
@Test
fun dataSaverOnlyDegrades() {
val signals = BackgroundWorkSignals(
backgroundRestricted = false,
standbyBucket = 20,
notificationsEnabled = true,
dataSaverRestricted = true
)
assertEquals(listOf(BackgroundWorkClassifier.REASON_DATA_SAVER), BackgroundWorkClassifier.reasons(signals))
assertEquals(BackgroundWorkClassifier.VERDICT_DEGRADED, verdictFor(signals))
}
@Test
fun blockingReasonOutranksDataSaver() {
val signals = BackgroundWorkSignals(backgroundRestricted = true, dataSaverRestricted = true)
assertEquals(BackgroundWorkClassifier.VERDICT_BLOCKED, verdictFor(signals))
}
@Test
fun mapCarriesVerdictReasonsAndRawSignals() {
val signals = BackgroundWorkSignals(
sdkInt = 33,
backgroundRestricted = true,
standbyBucket = BackgroundWorkClassifier.BUCKET_RESTRICTED,
notificationsEnabled = false,
downloadChannelBlocked = true,
dataSaverRestricted = false,
ignoringBatteryOptimizations = false
)
val map = BackgroundWorkClassifier.toMap(signals)
assertEquals(BackgroundWorkClassifier.VERDICT_BLOCKED, map["verdict"])
assertEquals(
listOf(
BackgroundWorkClassifier.REASON_BACKGROUND_RESTRICTED,
BackgroundWorkClassifier.REASON_STANDBY_RESTRICTED,
BackgroundWorkClassifier.REASON_NOTIFICATIONS_DISABLED
),
map["reasons"]
)
assertEquals(33, map["sdkInt"])
assertEquals(true, map["backgroundRestricted"])
assertEquals(BackgroundWorkClassifier.BUCKET_RESTRICTED, map["standbyBucket"])
assertEquals(false, map["notificationsEnabled"])
assertEquals(true, map["downloadChannelBlocked"])
assertEquals(false, map["dataSaverRestricted"])
assertEquals(false, map["ignoringBatteryOptimizations"])
}
@Test
fun unknownSignalsSurviveAsNullsInTheMap() {
val map = BackgroundWorkClassifier.toMap(BackgroundWorkSignals())
assertEquals(BackgroundWorkClassifier.VERDICT_OK, map["verdict"])
assertTrue(map.containsKey("standbyBucket"))
assertEquals(null, map["standbyBucket"])
assertEquals(null, map["backgroundRestricted"])
}
@Test
fun settingsTargetsParseFromTheirWireIds() {
assertEquals(BackgroundSettingsTarget.APP_DETAILS, BackgroundSettingsTarget.fromId("app_details"))
assertEquals(
BackgroundSettingsTarget.APP_NOTIFICATIONS,
BackgroundSettingsTarget.fromId("app_notifications")
)
assertEquals(
BackgroundSettingsTarget.NOTIFICATION_CHANNEL,
BackgroundSettingsTarget.fromId("notification_channel")
)
assertEquals(null, BackgroundSettingsTarget.fromId("nonsense"))
assertEquals(null, BackgroundSettingsTarget.fromId(null))
}
@Test
fun everyTargetFallsBackToTheAppDetailsPage() {
for (target in BackgroundSettingsTarget.entries) {
val specs = BackgroundSettingsIntents.specsFor(target, "com.edde746.plezy", 35)
assertTrue(specs.isNotEmpty())
val last = specs.last()
assertEquals("android.settings.APPLICATION_DETAILS_SETTINGS", last.action)
assertEquals("package:com.edde746.plezy", last.data)
}
}
@Test
fun notificationChannelTargetAddressesTheDownloaderChannel() {
val first = BackgroundSettingsIntents.specsFor(
BackgroundSettingsTarget.NOTIFICATION_CHANNEL,
"com.edde746.plezy",
35
).first()
assertEquals("android.settings.CHANNEL_NOTIFICATION_SETTINGS", first.action)
assertEquals("com.edde746.plezy", first.stringExtras["android.provider.extra.APP_PACKAGE"])
assertEquals(
BackgroundWorkDiagnostics.DOWNLOAD_NOTIFICATION_CHANNEL_ID,
first.stringExtras["android.provider.extra.CHANNEL_ID"]
)
}
@Test
fun appNotificationTargetDoesNotRequireAnExistingChannel() {
val first = BackgroundSettingsIntents.specsFor(
BackgroundSettingsTarget.APP_NOTIFICATIONS,
"com.edde746.plezy",
35
).first()
assertEquals("android.settings.APP_NOTIFICATION_SETTINGS", first.action)
assertEquals("com.edde746.plezy", first.stringExtras["android.provider.extra.APP_PACKAGE"])
assertEquals(null, first.stringExtras["android.provider.extra.CHANNEL_ID"])
}
@Test
fun preOreoNotificationRemediesFallBackToAppDetails() {
for (target in listOf(BackgroundSettingsTarget.APP_NOTIFICATIONS, BackgroundSettingsTarget.NOTIFICATION_CHANNEL)) {
val first = BackgroundSettingsIntents.specsFor(target, "com.edde746.plezy", 25).first()
assertEquals("android.settings.APPLICATION_DETAILS_SETTINGS", first.action)
assertEquals("package:com.edde746.plezy", first.data)
}
}
private fun verdictFor(signals: BackgroundWorkSignals): String = BackgroundWorkClassifier.verdict(signals)
}
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "Изисква се вход",
"syncRuleNotAvailableForProfile": "Не е налично за текущия профил",
"syncRuleUnknownServer": "Неизвестен сървър",
"syncRuleListCreated": "Правилото за синхронизация е създадено"
"syncRuleListCreated": "Правилото за синхронизация е създадено",
"backgroundWarning": {
"bannerBlocked": "Изтеглянията ще спрат, когато излезете от приложението",
"bannerDegraded": "Изтеглянията във фонов режим може да бъдат ограничени",
"bannerAction": "Подробности",
"sheetTitle": "Изтеглянията във фонов режим са блокирани",
"sheetTitleDegraded": "Изтеглянията във фонов режим може да бъдат ограничени",
"sheetIntro": "Android не позволява на Plezy да изтегля надеждно във фонов режим.",
"sheetIntroDegraded": "Устройството ви ограничава кога Plezy може да изтегля във фонов режим.",
"reasonBackgroundRestricted": "Работата на Plezy във фонов режим е ограничена. Задайте използването на батерията или работата във фонов режим на „Без ограничения“.",
"reasonStandbyRestricted": "Android е поставил Plezy в ограничено състояние на готовност. Задайте използването на батерията на „Без ограничения“.",
"reasonDownloadChannelBlocked": "Известията за изтегляния са изключени, затова напредъкът и контролите може да не са достъпни.",
"reasonNotificationsDisabled": "Известията са изключени. В Android 13 или по-нова версия те са необходими за продължителни изтегляния във фонов режим.",
"reasonDataSaver": "„Икономия на данни“ е включена и блокира изтеглянията във фонов режим през мобилни данни. Изтеглянията би трябвало да продължат през Wi-Fi.",
"reasonOemUnknown": "Изтеглянията спираха многократно, докато Plezy беше във фонов режим. Проверете настройките за батерията или работата на Plezy във фонов режим.",
"openSettings": "Отвори настройките",
"stillNotWorking": "Помощ за конкретното устройство",
"stillNotWorkingDescription": "Вижте стъпките за устройството си или изпратете лог от Настройки › Виж логовете, ако проблемът продължи.",
"dialogTitle": "Изтеглянията може да не завършат",
"dialogDownloadAnyway": "Изтегли въпреки това",
"dialogFixFirst": "Първо отстрани проблема",
"statusTile": "Изтегляния във фонов режим",
"statusOk": "Разрешена е работа във фонов режим",
"statusBlocked": "Блокирани от системните настройки",
"statusDegraded": "Ограничени от системните настройки",
"statusUnknown": "Все още не е проверено",
"settingsUnavailable": "Системните настройки не можаха да се отворят на това устройство",
"linkUnavailable": "dontkillmyapp.com не можа да се отвори на това устройство"
}
},
"shaders": {
"title": "Шейдъри",
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "Login påkrævet",
"syncRuleNotAvailableForProfile": "Ikke tilgængelig for nuværende profil",
"syncRuleUnknownServer": "Ukendt server",
"syncRuleListCreated": "Synkroniseringsregel oprettet"
"syncRuleListCreated": "Synkroniseringsregel oprettet",
"backgroundWarning": {
"bannerBlocked": "Downloads stopper, når du forlader appen",
"bannerDegraded": "Downloads i baggrunden kan være begrænsede",
"bannerAction": "Detaljer",
"sheetTitle": "Downloads i baggrunden er blokeret",
"sheetTitleDegraded": "Downloads i baggrunden kan være begrænsede",
"sheetIntro": "Android forhindrer Plezy i at downloade stabilt i baggrunden.",
"sheetIntroDegraded": "Din enhed begrænser, hvornår Plezy kan downloade i baggrunden.",
"reasonBackgroundRestricted": "Plezys baggrundsaktivitet er begrænset. Indstil batteriforbruget eller baggrundsaktiviteten til \"Ubegrænset\".",
"reasonStandbyRestricted": "Android har sat Plezy i begrænset standbytilstand. Indstil batteriforbruget til \"Ubegrænset\".",
"reasonDownloadChannelBlocked": "Notifikationer om downloads er slået fra, så status og betjeningsknapper muligvis ikke er tilgængelige.",
"reasonNotificationsDisabled": "Notifikationer er slået fra. På Android 13 eller nyere er de nødvendige ved lange downloads i baggrunden.",
"reasonDataSaver": "Datasparefunktionen er slået til, hvilket blokerer downloads i baggrunden via mobildata. Downloads bør stadig køre på Wi-Fi.",
"reasonOemUnknown": "Downloads stoppede gentagne gange, mens Plezy var i baggrunden. Tjek Plezys indstillinger for batteriforbrug eller baggrundsaktivitet.",
"openSettings": "Åbn indstillinger",
"stillNotWorking": "Enhedsspecifik hjælp",
"stillNotWorkingDescription": "Se vejledningen til din enhed, eller send en logfil fra Indstillinger Vis logfiler, hvis problemet fortsætter.",
"dialogTitle": "Downloads bliver muligvis ikke færdige",
"dialogDownloadAnyway": "Download alligevel",
"dialogFixFirst": "Løs dette først",
"statusTile": "Downloads i baggrunden",
"statusOk": "Må køre i baggrunden",
"statusBlocked": "Blokeret af systemindstillinger",
"statusDegraded": "Begrænset af systemindstillinger",
"statusUnknown": "Endnu ikke kontrolleret",
"settingsUnavailable": "Kunne ikke åbne systemindstillingerne på denne enhed",
"linkUnavailable": "Kunne ikke åbne dontkillmyapp.com på denne enhed"
}
},
"shaders": {
"title": "Shadere",
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "Anmeldung erforderlich",
"syncRuleNotAvailableForProfile": "Für das aktuelle Profil nicht verfügbar",
"syncRuleUnknownServer": "Unbekannter Server",
"syncRuleListCreated": "Synchronisierungsregel erstellt"
"syncRuleListCreated": "Synchronisierungsregel erstellt",
"backgroundWarning": {
"bannerBlocked": "Downloads werden gestoppt, wenn du die App verlässt",
"bannerDegraded": "Downloads im Hintergrund sind möglicherweise eingeschränkt",
"bannerAction": "Details",
"sheetTitle": "Downloads im Hintergrund sind blockiert",
"sheetTitleDegraded": "Downloads im Hintergrund sind möglicherweise eingeschränkt",
"sheetIntro": "Android verhindert, dass Plezy zuverlässig im Hintergrund herunterlädt.",
"sheetIntroDegraded": "Dein Gerät schränkt ein, wann Plezy im Hintergrund herunterladen kann.",
"reasonBackgroundRestricted": "Die Hintergrundnutzung von Plezy ist eingeschränkt. Stelle die Akku- oder Hintergrundnutzung auf „Uneingeschränkt“.",
"reasonStandbyRestricted": "Android hat Plezy in einen eingeschränkten Standby-Modus versetzt. Stelle die Akkunutzung auf „Uneingeschränkt“.",
"reasonDownloadChannelBlocked": "Download-Benachrichtigungen sind deaktiviert. Fortschritt und Steuerelemente sind daher möglicherweise nicht verfügbar.",
"reasonNotificationsDisabled": "Benachrichtigungen sind deaktiviert. Ab Android 13 sind sie für lange Downloads im Hintergrund erforderlich.",
"reasonDataSaver": "Der Datensparmodus ist aktiviert und blockiert Downloads im Hintergrund über mobile Daten. Über Wi-Fi sollten Downloads weiterhin funktionieren.",
"reasonOemUnknown": "Downloads wurden wiederholt gestoppt, während Plezy im Hintergrund war. Prüfe die Einstellungen zur Akku- oder Hintergrundnutzung von Plezy.",
"openSettings": "Einstellungen öffnen",
"stillNotWorking": "Gerätespezifische Hilfe",
"stillNotWorkingDescription": "Sieh dir die Schritte für dein Gerät an oder sende bei anhaltendem Problem ein Protokoll über Einstellungen Protokolle anzeigen.",
"dialogTitle": "Downloads werden möglicherweise nicht abgeschlossen",
"dialogDownloadAnyway": "Trotzdem herunterladen",
"dialogFixFirst": "Zuerst beheben",
"statusTile": "Downloads im Hintergrund",
"statusOk": "Ausführung im Hintergrund erlaubt",
"statusBlocked": "Durch Systemeinstellungen blockiert",
"statusDegraded": "Durch Systemeinstellungen eingeschränkt",
"statusUnknown": "Noch nicht geprüft",
"settingsUnavailable": "Die Systemeinstellungen konnten auf diesem Gerät nicht geöffnet werden",
"linkUnavailable": "dontkillmyapp.com konnte auf diesem Gerät nicht geöffnet werden"
}
},
"shaders": {
"title": "Shader",
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "Sign in required",
"syncRuleNotAvailableForProfile": "Not available for current profile",
"syncRuleUnknownServer": "Unknown server",
"syncRuleListCreated": "Sync rule created"
"syncRuleListCreated": "Sync rule created",
"backgroundWarning": {
"bannerBlocked": "Downloads will stop when you leave the app",
"bannerDegraded": "Background downloads may be limited",
"bannerAction": "Details",
"sheetTitle": "Background downloads are blocked",
"sheetTitleDegraded": "Background downloads may be limited",
"sheetIntro": "Android is preventing Plezy from downloading reliably in the background.",
"sheetIntroDegraded": "Your device is limiting when Plezy can download in the background.",
"reasonBackgroundRestricted": "Plezy's background usage is restricted. Set its battery or background usage to \"Unrestricted\".",
"reasonStandbyRestricted": "Android has put Plezy in a restricted standby state. Set its battery usage to \"Unrestricted\".",
"reasonDownloadChannelBlocked": "Download notifications are turned off, so progress and controls may be unavailable.",
"reasonNotificationsDisabled": "Notifications are turned off. On Android 13 or newer, they are required for long background downloads.",
"reasonDataSaver": "Data Saver is on, which blocks background downloads on mobile data. Downloads should still run on Wi-Fi.",
"reasonOemUnknown": "Downloads repeatedly stopped while Plezy was in the background. Check Plezy's battery or background usage settings.",
"openSettings": "Open settings",
"stillNotWorking": "Device-specific help",
"stillNotWorkingDescription": "See steps for your device, or send a log from Settings View Logs if the issue continues.",
"dialogTitle": "Downloads may not finish",
"dialogDownloadAnyway": "Download anyway",
"dialogFixFirst": "Fix this first",
"statusTile": "Background downloads",
"statusOk": "Allowed to run in the background",
"statusBlocked": "Blocked by system settings",
"statusDegraded": "Limited by system settings",
"statusUnknown": "Not checked yet",
"settingsUnavailable": "Couldn't open system settings on this device",
"linkUnavailable": "Couldn't open dontkillmyapp.com on this device"
}
},
"shaders": {
"title": "Shaders",
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "Se requiere iniciar sesión",
"syncRuleNotAvailableForProfile": "No disponible para el perfil actual",
"syncRuleUnknownServer": "Servidor desconocido",
"syncRuleListCreated": "Regla de sincronización creada"
"syncRuleListCreated": "Regla de sincronización creada",
"backgroundWarning": {
"bannerBlocked": "Las descargas se detendrán al salir de la app",
"bannerDegraded": "Las descargas en segundo plano pueden estar limitadas",
"bannerAction": "Detalles",
"sheetTitle": "Las descargas en segundo plano están bloqueadas",
"sheetTitleDegraded": "Las descargas en segundo plano pueden estar limitadas",
"sheetIntro": "Android impide que Plezy descargue de forma fiable en segundo plano.",
"sheetIntroDegraded": "Tu dispositivo limita cuándo puede descargar Plezy en segundo plano.",
"reasonBackgroundRestricted": "El uso en segundo plano de Plezy está restringido. Configura el uso de batería o en segundo plano como \"Sin restricciones\".",
"reasonStandbyRestricted": "Android ha puesto a Plezy en un estado de espera restringido. Configura el uso de batería como \"Sin restricciones\".",
"reasonDownloadChannelBlocked": "Las notificaciones de descargas están desactivadas, por lo que el progreso y los controles podrían no estar disponibles.",
"reasonNotificationsDisabled": "Las notificaciones están desactivadas. En Android 13 o versiones posteriores, son necesarias para las descargas largas en segundo plano.",
"reasonDataSaver": "El Ahorro de datos está activado y bloquea las descargas en segundo plano con datos móviles. Las descargas deberían seguir funcionando con Wi-Fi.",
"reasonOemUnknown": "Las descargas se detuvieron repetidamente mientras Plezy estaba en segundo plano. Revisa la configuración de batería o de uso en segundo plano de Plezy.",
"openSettings": "Abrir configuración",
"stillNotWorking": "Ayuda específica para tu dispositivo",
"stillNotWorkingDescription": "Consulta los pasos para tu dispositivo o envía un registro desde Configuración Ver registros si el problema continúa.",
"dialogTitle": "Es posible que las descargas no finalicen",
"dialogDownloadAnyway": "Descargar de todos modos",
"dialogFixFirst": "Solucionarlo primero",
"statusTile": "Descargas en segundo plano",
"statusOk": "Permitidas en segundo plano",
"statusBlocked": "Bloqueadas por la configuración del sistema",
"statusDegraded": "Limitadas por la configuración del sistema",
"statusUnknown": "Aún no se ha comprobado",
"settingsUnavailable": "No se pudo abrir la configuración del sistema en este dispositivo",
"linkUnavailable": "No se pudo abrir dontkillmyapp.com en este dispositivo"
}
},
"shaders": {
"title": "Shaders",
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "Connexion requise",
"syncRuleNotAvailableForProfile": "Non disponible pour le profil actuel",
"syncRuleUnknownServer": "Serveur inconnu",
"syncRuleListCreated": "Règle de synchronisation créée"
"syncRuleListCreated": "Règle de synchronisation créée",
"backgroundWarning": {
"bannerBlocked": "Les téléchargements sarrêteront lorsque vous quitterez lapplication",
"bannerDegraded": "Les téléchargements en arrière-plan peuvent être limités",
"bannerAction": "Détails",
"sheetTitle": "Les téléchargements en arrière-plan sont bloqués",
"sheetTitleDegraded": "Les téléchargements en arrière-plan peuvent être limités",
"sheetIntro": "Android empêche Plezy de télécharger de façon fiable en arrière-plan.",
"sheetIntroDegraded": "Votre appareil limite les moments où Plezy peut télécharger en arrière-plan.",
"reasonBackgroundRestricted": "Lutilisation de Plezy en arrière-plan est restreinte. Dans les paramètres de batterie ou dutilisation en arrière-plan, sélectionnez « Sans restriction ».",
"reasonStandbyRestricted": "Android a placé Plezy en veille restreinte. Réglez lutilisation de la batterie sur « Sans restriction ».",
"reasonDownloadChannelBlocked": "Les notifications de téléchargement sont désactivées. La progression et les commandes peuvent donc être indisponibles.",
"reasonNotificationsDisabled": "Les notifications sont désactivées. Sur Android 13 ou version ultérieure, elles sont nécessaires pour les longs téléchargements en arrière-plan.",
"reasonDataSaver": "L’Économiseur de données est activé et bloque les téléchargements en arrière-plan via les données mobiles. Ils devraient toujours fonctionner en Wi-Fi.",
"reasonOemUnknown": "Les téléchargements se sont arrêtés plusieurs fois lorsque Plezy était en arrière-plan. Vérifiez les paramètres de batterie ou dutilisation en arrière-plan de Plezy.",
"openSettings": "Ouvrir les paramètres",
"stillNotWorking": "Aide spécifique à lappareil",
"stillNotWorkingDescription": "Consultez les étapes adaptées à votre appareil ou, si le problème persiste, envoyez un journal depuis Paramètres Voir les journaux.",
"dialogTitle": "Les téléchargements risquent de ne pas aboutir",
"dialogDownloadAnyway": "Télécharger quand même",
"dialogFixFirst": "Corriger dabord",
"statusTile": "Téléchargements en arrière-plan",
"statusOk": "Exécution en arrière-plan autorisée",
"statusBlocked": "Bloqués par les paramètres système",
"statusDegraded": "Limités par les paramètres système",
"statusUnknown": "Pas encore vérifié",
"settingsUnavailable": "Impossible douvrir les paramètres système sur cet appareil",
"linkUnavailable": "Impossible douvrir dontkillmyapp.com sur cet appareil"
}
},
"shaders": {
"title": "Shaders",
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "Bejelentkezés szükséges",
"syncRuleNotAvailableForProfile": "Nem érhető el a jelenlegi profilhoz",
"syncRuleUnknownServer": "Ismeretlen szerver",
"syncRuleListCreated": "Szinkronizálási szabály létrehozva"
"syncRuleListCreated": "Szinkronizálási szabály létrehozva",
"backgroundWarning": {
"bannerBlocked": "A letöltések leállnak, ha kilépsz az alkalmazásból",
"bannerDegraded": "A háttérbeli letöltések korlátozottak lehetnek",
"bannerAction": "Részletek",
"sheetTitle": "A háttérbeli letöltések le vannak tiltva",
"sheetTitleDegraded": "A háttérbeli letöltések korlátozottak lehetnek",
"sheetIntro": "Az Android megakadályozza, hogy a Plezy megbízhatóan töltsön le a háttérben.",
"sheetIntroDegraded": "Az eszközöd korlátozza, hogy a Plezy mikor tölthet le a háttérben.",
"reasonBackgroundRestricted": "A Plezy háttérbeli használata korlátozva van. Állítsd az akkumulátor- vagy háttérhasználatát „Korlátlan” értékre.",
"reasonStandbyRestricted": "Az Android korlátozott készenléti állapotba helyezte a Plezyt. Állítsd az akkumulátorhasználatát „Korlátlan” értékre.",
"reasonDownloadChannelBlocked": "A letöltési értesítések ki vannak kapcsolva, ezért előfordulhat, hogy a folyamatjelzés és a vezérlők nem érhetők el.",
"reasonNotificationsDisabled": "Az értesítések ki vannak kapcsolva. Android 13 vagy újabb rendszeren szükségesek a hosszú háttérbeli letöltésekhez.",
"reasonDataSaver": "Az Adatforgalom-csökkentő be van kapcsolva, ezért mobiladat-kapcsolaton nem működnek a háttérbeli letöltések. Wi-Fi-n továbbra is működniük kell.",
"reasonOemUnknown": "A letöltések többször leálltak, miközben a Plezy a háttérben futott. Ellenőrizd a Plezy akkumulátor- vagy háttérhasználati beállításait.",
"openSettings": "Beállítások megnyitása",
"stillNotWorking": "Eszközspecifikus segítség",
"stillNotWorkingDescription": "Nézd meg az eszközödhöz tartozó lépéseket, vagy ha a probléma továbbra is fennáll, küldj naplót a Beállítások Naplók megtekintése menüből.",
"dialogTitle": "A letöltések nem biztos, hogy befejeződnek",
"dialogDownloadAnyway": "Letöltés mégis",
"dialogFixFirst": "Előbb javítom",
"statusTile": "Háttérbeli letöltések",
"statusOk": "Futhat a háttérben",
"statusBlocked": "A rendszerbeállítások blokkolják",
"statusDegraded": "A rendszerbeállítások korlátozzák",
"statusUnknown": "Még nincs ellenőrizve",
"settingsUnavailable": "Ezen az eszközön nem sikerült megnyitni a rendszerbeállításokat",
"linkUnavailable": "Ezen az eszközön nem sikerült megnyitni a dontkillmyapp.com webhelyet"
}
},
"shaders": {
"title": "Shaderek",
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "Accesso richiesto",
"syncRuleNotAvailableForProfile": "Non disponibile per il profilo attuale",
"syncRuleUnknownServer": "Server sconosciuto",
"syncRuleListCreated": "Regola di sincronizzazione creata"
"syncRuleListCreated": "Regola di sincronizzazione creata",
"backgroundWarning": {
"bannerBlocked": "I download si interromperanno quando esci dallapp",
"bannerDegraded": "I download in background potrebbero essere limitati",
"bannerAction": "Dettagli",
"sheetTitle": "I download in background sono bloccati",
"sheetTitleDegraded": "I download in background potrebbero essere limitati",
"sheetIntro": "Android impedisce a Plezy di scaricare in modo affidabile in background.",
"sheetIntroDegraded": "Il dispositivo limita i momenti in cui Plezy può scaricare in background.",
"reasonBackgroundRestricted": "Luso in background di Plezy è limitato. Nelle impostazioni della batteria o delluso in background, seleziona «Senza restrizioni».",
"reasonStandbyRestricted": "Android ha messo Plezy in uno stato di standby con restrizioni. Imposta luso della batteria su «Senza restrizioni».",
"reasonDownloadChannelBlocked": "Le notifiche dei download sono disattivate, quindi lavanzamento e i controlli potrebbero non essere disponibili.",
"reasonNotificationsDisabled": "Le notifiche sono disattivate. Su Android 13 o versioni successive sono necessarie per i download prolungati in background.",
"reasonDataSaver": "Il Risparmio dati è attivo e blocca i download in background tramite dati mobili. I download dovrebbero comunque funzionare su Wi-Fi.",
"reasonOemUnknown": "I download si sono interrotti più volte mentre Plezy era in background. Controlla le impostazioni della batteria o delluso in background di Plezy.",
"openSettings": "Apri le impostazioni",
"stillNotWorking": "Guida specifica per il dispositivo",
"stillNotWorkingDescription": "Consulta la procedura per il tuo dispositivo oppure invia un log da Impostazioni Visualizza i log se il problema persiste.",
"dialogTitle": "I download potrebbero non terminare",
"dialogDownloadAnyway": "Scarica comunque",
"dialogFixFirst": "Risolvi prima",
"statusTile": "Download in background",
"statusOk": "Esecuzione in background consentita",
"statusBlocked": "Bloccati dalle impostazioni di sistema",
"statusDegraded": "Limitati dalle impostazioni di sistema",
"statusUnknown": "Non ancora verificato",
"settingsUnavailable": "Impossibile aprire le impostazioni di sistema su questo dispositivo",
"linkUnavailable": "Impossibile aprire dontkillmyapp.com su questo dispositivo"
}
},
"shaders": {
"title": "Shader",
+29 -1
View File
@@ -1203,7 +1203,35 @@
"syncRuleSignInRequired": "サインインが必要",
"syncRuleNotAvailableForProfile": "現在のプロフィールでは利用できません",
"syncRuleUnknownServer": "不明なサーバー",
"syncRuleListCreated": "同期ルールを作成しました"
"syncRuleListCreated": "同期ルールを作成しました",
"backgroundWarning": {
"bannerBlocked": "アプリの画面を離れると、ダウンロードが停止します",
"bannerDegraded": "バックグラウンドダウンロードが制限される場合があります",
"bannerAction": "詳細",
"sheetTitle": "バックグラウンドダウンロードはブロックされています",
"sheetTitleDegraded": "バックグラウンドダウンロードが制限される場合があります",
"sheetIntro": "Androidにより、Plezyはバックグラウンドで安定してダウンロードできません。",
"sheetIntroDegraded": "端末により、Plezyがバックグラウンドでダウンロードできるタイミングが制限されています。",
"reasonBackgroundRestricted": "Plezyのバックグラウンド使用が制限されています。バッテリー使用量またはバックグラウンド使用を「制限なし」に設定してください。",
"reasonStandbyRestricted": "Androidにより、Plezyが制限付きのスタンバイ状態に設定されています。バッテリー使用量を「制限なし」に設定してください。",
"reasonDownloadChannelBlocked": "ダウンロード通知がオフのため、進行状況や操作ボタンを利用できない場合があります。",
"reasonNotificationsDisabled": "通知がオフです。Android 13以降では、長時間のバックグラウンドダウンロードに通知が必要です。",
"reasonDataSaver": "データセーバーがオンのため、モバイルデータ通信ではバックグラウンドダウンロードがブロックされます。Wi-Fiでは引き続きダウンロードできるはずです。",
"reasonOemUnknown": "Plezyがバックグラウンドで動作中に、ダウンロードが繰り返し停止しました。Plezyのバッテリー使用量またはバックグラウンド使用の設定を確認してください。",
"openSettings": "設定を開く",
"stillNotWorking": "端末別のヘルプ",
"stillNotWorkingDescription": "お使いの端末向けの手順を確認してください。問題が続く場合は、設定 › ログを表示 からログを送信してください。",
"dialogTitle": "ダウンロードが完了しない可能性があります",
"dialogDownloadAnyway": "このままダウンロード",
"dialogFixFirst": "先に設定を修正",
"statusTile": "バックグラウンドダウンロード",
"statusOk": "バックグラウンドで実行可能",
"statusBlocked": "システム設定によりブロック",
"statusDegraded": "システム設定により制限",
"statusUnknown": "未確認",
"settingsUnavailable": "この端末ではシステム設定を開けませんでした",
"linkUnavailable": "この端末ではdontkillmyapp.comを開けませんでした"
}
},
"shaders": {
"title": "シェーダー",
+29 -1
View File
@@ -1203,7 +1203,35 @@
"syncRuleSignInRequired": "로그인 필요",
"syncRuleNotAvailableForProfile": "현재 프로필에서 사용할 수 없음",
"syncRuleUnknownServer": "알 수 없는 서버",
"syncRuleListCreated": "동기화 규칙이 생성되었습니다"
"syncRuleListCreated": "동기화 규칙이 생성되었습니다",
"backgroundWarning": {
"bannerBlocked": "앱에서 나가면 다운로드가 중지됩니다",
"bannerDegraded": "백그라운드 다운로드가 제한될 수 있습니다",
"bannerAction": "세부정보",
"sheetTitle": "백그라운드 다운로드가 차단됨",
"sheetTitleDegraded": "백그라운드 다운로드가 제한될 수 있음",
"sheetIntro": "Android가 Plezy의 안정적인 백그라운드 다운로드를 차단하고 있습니다.",
"sheetIntroDegraded": "기기에서 Plezy가 백그라운드로 다운로드할 수 있는 시점을 제한하고 있습니다.",
"reasonBackgroundRestricted": "Plezy의 백그라운드 사용이 제한되어 있습니다. 배터리 또는 백그라운드 사용을 \"제한 없음\"으로 설정하세요.",
"reasonStandbyRestricted": "Android가 Plezy를 제한된 대기 상태로 전환했습니다. 배터리 사용량을 \"제한 없음\"으로 설정하세요.",
"reasonDownloadChannelBlocked": "다운로드 알림이 꺼져 있어 진행 상황과 제어 기능을 사용하지 못할 수 있습니다.",
"reasonNotificationsDisabled": "알림이 꺼져 있습니다. Android 13 이상에서는 장시간 백그라운드 다운로드에 알림이 필요합니다.",
"reasonDataSaver": "데이터 절약 모드가 켜져 있어 모바일 데이터로 백그라운드 다운로드를 할 수 없습니다. Wi-Fi에서는 계속 다운로드할 수 있습니다.",
"reasonOemUnknown": "Plezy가 백그라운드에 있을 때 다운로드가 반복해서 중지되었습니다. Plezy의 배터리 또는 백그라운드 사용 설정을 확인하세요.",
"openSettings": "설정 열기",
"stillNotWorking": "기기별 도움말",
"stillNotWorkingDescription": "기기에 맞는 단계를 확인하세요. 문제가 계속되면 설정 › 로그 보기에서 로그를 보내 주세요.",
"dialogTitle": "다운로드가 완료되지 않을 수 있음",
"dialogDownloadAnyway": "그래도 다운로드",
"dialogFixFirst": "먼저 문제 해결",
"statusTile": "백그라운드 다운로드",
"statusOk": "백그라운드 실행 허용됨",
"statusBlocked": "시스템 설정에 의해 차단됨",
"statusDegraded": "시스템 설정에 의해 제한됨",
"statusUnknown": "아직 확인하지 않음",
"settingsUnavailable": "이 기기에서 시스템 설정을 열 수 없습니다",
"linkUnavailable": "이 기기에서 dontkillmyapp.com을 열 수 없습니다"
}
},
"shaders": {
"title": "셰이더",
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "Innlogging kreves",
"syncRuleNotAvailableForProfile": "Ikke tilgjengelig for gjeldende profil",
"syncRuleUnknownServer": "Ukjent server",
"syncRuleListCreated": "Synkroniseringsregel opprettet"
"syncRuleListCreated": "Synkroniseringsregel opprettet",
"backgroundWarning": {
"bannerBlocked": "Nedlastinger stopper når du forlater appen",
"bannerDegraded": "Bakgrunnsnedlastinger kan være begrenset",
"bannerAction": "Detaljer",
"sheetTitle": "Bakgrunnsnedlastinger er blokkert",
"sheetTitleDegraded": "Bakgrunnsnedlastinger kan være begrenset",
"sheetIntro": "Android hindrer Plezy i å laste ned pålitelig i bakgrunnen.",
"sheetIntroDegraded": "Enheten din begrenser når Plezy kan laste ned i bakgrunnen.",
"reasonBackgroundRestricted": "Bakgrunnsbruken til Plezy er begrenset. Sett batteribruk eller bakgrunnsbruk til «Ubegrenset».",
"reasonStandbyRestricted": "Android har satt Plezy i begrenset hvilemodus. Sett batteribruken til «Ubegrenset».",
"reasonDownloadChannelBlocked": "Varsler om nedlastinger er slått av, så fremdrift og kontroller kan være utilgjengelige.",
"reasonNotificationsDisabled": "Varsler er slått av. På Android 13 eller nyere kreves de for lange bakgrunnsnedlastinger.",
"reasonDataSaver": "Datasparing er slått på og blokkerer bakgrunnsnedlastinger via mobildata. Nedlastinger skal fortsatt fungere på Wi-Fi.",
"reasonOemUnknown": "Nedlastinger har stoppet gjentatte ganger mens Plezy var i bakgrunnen. Sjekk innstillingene for batteribruk eller bakgrunnsbruk for Plezy.",
"openSettings": "Åpne innstillinger",
"stillNotWorking": "Enhetsspesifikk hjelp",
"stillNotWorkingDescription": "Se fremgangsmåten for enheten din, eller send en logg fra Innstillinger Vis logger hvis problemet vedvarer.",
"dialogTitle": "Nedlastinger blir kanskje ikke fullført",
"dialogDownloadAnyway": "Last ned likevel",
"dialogFixFirst": "Løs dette først",
"statusTile": "Bakgrunnsnedlastinger",
"statusOk": "Kan kjøre i bakgrunnen",
"statusBlocked": "Blokkert av systeminnstillinger",
"statusDegraded": "Begrenset av systeminnstillinger",
"statusUnknown": "Ikke sjekket ennå",
"settingsUnavailable": "Kunne ikke åpne systeminnstillingene på denne enheten",
"linkUnavailable": "Kunne ikke åpne dontkillmyapp.com på denne enheten"
}
},
"shaders": {
"title": "Shadere",
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "Inloggen vereist",
"syncRuleNotAvailableForProfile": "Niet beschikbaar voor huidig profiel",
"syncRuleUnknownServer": "Onbekende server",
"syncRuleListCreated": "Synchronisatieregel aangemaakt"
"syncRuleListCreated": "Synchronisatieregel aangemaakt",
"backgroundWarning": {
"bannerBlocked": "Downloads stoppen zodra je de app verlaat",
"bannerDegraded": "Downloads op de achtergrond kunnen beperkt zijn",
"bannerAction": "Details",
"sheetTitle": "Downloads op de achtergrond zijn geblokkeerd",
"sheetTitleDegraded": "Downloads op de achtergrond kunnen beperkt zijn",
"sheetIntro": "Android verhindert dat Plezy betrouwbaar op de achtergrond downloadt.",
"sheetIntroDegraded": "Je apparaat beperkt wanneer Plezy op de achtergrond kan downloaden.",
"reasonBackgroundRestricted": "Het achtergrondgebruik van Plezy is beperkt. Stel het batterij- of achtergrondgebruik in op \"Onbeperkt\".",
"reasonStandbyRestricted": "Android heeft Plezy in een beperkte stand-bymodus geplaatst. Stel het batterijgebruik in op \"Onbeperkt\".",
"reasonDownloadChannelBlocked": "Downloadmeldingen zijn uitgeschakeld, waardoor voortgang en bediening mogelijk niet beschikbaar zijn.",
"reasonNotificationsDisabled": "Meldingen zijn uitgeschakeld. Op Android 13 of nieuwer zijn ze vereist voor langdurige downloads op de achtergrond.",
"reasonDataSaver": "Databesparing is ingeschakeld en blokkeert downloads op de achtergrond via mobiele data. Via Wi-Fi zouden downloads nog wel moeten werken.",
"reasonOemUnknown": "Downloads zijn herhaaldelijk gestopt terwijl Plezy op de achtergrond draaide. Controleer de instellingen voor het batterij- of achtergrondgebruik van Plezy.",
"openSettings": "Instellingen openen",
"stillNotWorking": "Apparaatspecifieke hulp",
"stillNotWorkingDescription": "Bekijk de stappen voor je apparaat of stuur een logbestand vanuit Instellingen Logbestanden bekijken als het probleem aanhoudt.",
"dialogTitle": "Downloads worden mogelijk niet voltooid",
"dialogDownloadAnyway": "Toch downloaden",
"dialogFixFirst": "Dit eerst oplossen",
"statusTile": "Downloads op de achtergrond",
"statusOk": "Mag op de achtergrond worden uitgevoerd",
"statusBlocked": "Geblokkeerd door systeeminstellingen",
"statusDegraded": "Beperkt door systeeminstellingen",
"statusUnknown": "Nog niet gecontroleerd",
"settingsUnavailable": "Kan de systeeminstellingen niet openen op dit apparaat",
"linkUnavailable": "Kan dontkillmyapp.com niet openen op dit apparaat"
}
},
"shaders": {
"title": "Shaders",
+29 -1
View File
@@ -1212,7 +1212,35 @@
"syncRuleSignInRequired": "Wymagane logowanie",
"syncRuleNotAvailableForProfile": "Niedostępne dla bieżącego profilu",
"syncRuleUnknownServer": "Nieznany serwer",
"syncRuleListCreated": "Utworzono regułę synchronizacji"
"syncRuleListCreated": "Utworzono regułę synchronizacji",
"backgroundWarning": {
"bannerBlocked": "Po opuszczeniu aplikacji pobieranie zostanie zatrzymane",
"bannerDegraded": "Pobieranie w tle może być ograniczone",
"bannerAction": "Szczegóły",
"sheetTitle": "Pobieranie w tle jest zablokowane",
"sheetTitleDegraded": "Pobieranie w tle może być ograniczone",
"sheetIntro": "Android uniemożliwia Plezy niezawodne pobieranie plików w tle.",
"sheetIntroDegraded": "Twoje urządzenie ogranicza możliwość pobierania w tle przez Plezy.",
"reasonBackgroundRestricted": "Działanie Plezy w tle jest ograniczone. Ustaw użycie baterii lub działanie w tle na „Bez ograniczeń”.",
"reasonStandbyRestricted": "Android umieścił Plezy w ograniczonym trybie gotowości. Ustaw użycie baterii na „Bez ograniczeń”.",
"reasonDownloadChannelBlocked": "Powiadomienia o pobieraniu są wyłączone, więc postęp i opcje sterowania mogą być niedostępne.",
"reasonNotificationsDisabled": "Powiadomienia są wyłączone. W Android 13 lub nowszym są wymagane przy długim pobieraniu w tle.",
"reasonDataSaver": "Oszczędzanie danych jest włączone, co blokuje pobieranie w tle przez mobilną transmisję danych. Pobieranie powinno nadal działać przez Wi-Fi.",
"reasonOemUnknown": "Pobieranie wielokrotnie przerywało się, gdy Plezy działało w tle. Sprawdź ustawienia baterii lub działania w tle dla Plezy.",
"openSettings": "Otwórz ustawienia",
"stillNotWorking": "Pomoc dla Twojego urządzenia",
"stillNotWorkingDescription": "Zobacz instrukcje dla swojego urządzenia lub, jeśli problem nadal występuje, wyślij log przez Ustawienia Pokaż logi.",
"dialogTitle": "Pobieranie może się nie zakończyć",
"dialogDownloadAnyway": "Pobierz mimo to",
"dialogFixFirst": "Najpierw rozwiąż problem",
"statusTile": "Pobieranie w tle",
"statusOk": "Działanie w tle jest dozwolone",
"statusBlocked": "Zablokowane przez ustawienia systemu",
"statusDegraded": "Ograniczone przez ustawienia systemu",
"statusUnknown": "Jeszcze nie sprawdzono",
"settingsUnavailable": "Nie udało się otworzyć ustawień systemowych na tym urządzeniu",
"linkUnavailable": "Nie udało się otworzyć dontkillmyapp.com na tym urządzeniu"
}
},
"shaders": {
"title": "Shadery",
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "É necessário entrar",
"syncRuleNotAvailableForProfile": "Indisponível para o perfil atual",
"syncRuleUnknownServer": "Servidor desconhecido",
"syncRuleListCreated": "Regra de sincronização criada"
"syncRuleListCreated": "Regra de sincronização criada",
"backgroundWarning": {
"bannerBlocked": "Os downloads serão interrompidos ao sair do app",
"bannerDegraded": "Os downloads em segundo plano podem ser limitados",
"bannerAction": "Detalhes",
"sheetTitle": "Os downloads em segundo plano estão bloqueados",
"sheetTitleDegraded": "Os downloads em segundo plano podem ser limitados",
"sheetIntro": "O Android está impedindo que o Plezy faça downloads de forma confiável em segundo plano.",
"sheetIntroDegraded": "Seu dispositivo está limitando quando o Plezy pode fazer downloads em segundo plano.",
"reasonBackgroundRestricted": "O uso em segundo plano do Plezy está restrito. Defina o uso da bateria ou o uso em segundo plano como \"Sem restrições\".",
"reasonStandbyRestricted": "O Android colocou o Plezy em um modo de espera restrito. Defina o uso da bateria como \"Sem restrições\".",
"reasonDownloadChannelBlocked": "As notificações de download estão desativadas; por isso, o progresso e os controles podem ficar indisponíveis.",
"reasonNotificationsDisabled": "As notificações estão desativadas. No Android 13 ou mais recente, elas são necessárias para downloads longos em segundo plano.",
"reasonDataSaver": "A Economia de dados está ativada e bloqueia downloads em segundo plano usando dados móveis. Os downloads ainda devem funcionar no Wi-Fi.",
"reasonOemUnknown": "Os downloads foram interrompidos várias vezes enquanto o Plezy estava em segundo plano. Verifique as configurações de bateria ou uso em segundo plano do Plezy.",
"openSettings": "Abrir configurações",
"stillNotWorking": "Ajuda específica para o dispositivo",
"stillNotWorkingDescription": "Veja as instruções para seu dispositivo ou, se o problema persistir, envie um log em Configurações Ver Logs.",
"dialogTitle": "Os downloads podem não ser concluídos",
"dialogDownloadAnyway": "Baixar mesmo assim",
"dialogFixFirst": "Corrigir primeiro",
"statusTile": "Downloads em segundo plano",
"statusOk": "Execução em segundo plano permitida",
"statusBlocked": "Bloqueado pelas configurações do sistema",
"statusDegraded": "Limitado pelas configurações do sistema",
"statusUnknown": "Ainda não verificado",
"settingsUnavailable": "Não foi possível abrir as configurações do sistema neste dispositivo",
"linkUnavailable": "Não foi possível abrir dontkillmyapp.com neste dispositivo"
}
},
"shaders": {
"title": "Shaders",
+29 -1
View File
@@ -1212,7 +1212,35 @@
"syncRuleSignInRequired": "Требуется вход",
"syncRuleNotAvailableForProfile": "Недоступно для текущего профиля",
"syncRuleUnknownServer": "Неизвестный сервер",
"syncRuleListCreated": "Правило синхронизации создано"
"syncRuleListCreated": "Правило синхронизации создано",
"backgroundWarning": {
"bannerBlocked": "Загрузки остановятся, когда вы выйдете из приложения",
"bannerDegraded": "Фоновые загрузки могут быть ограничены",
"bannerAction": "Подробнее",
"sheetTitle": "Фоновые загрузки заблокированы",
"sheetTitleDegraded": "Фоновые загрузки могут быть ограничены",
"sheetIntro": "Android не позволяет Plezy стабильно выполнять загрузки в фоновом режиме.",
"sheetIntroDegraded": "Ваше устройство ограничивает время, когда Plezy может выполнять загрузки в фоновом режиме.",
"reasonBackgroundRestricted": "Фоновая работа Plezy ограничена. В настройках расхода заряда батареи или фоновой работы выберите «Без ограничений».",
"reasonStandbyRestricted": "Android перевёл Plezy в режим ожидания с ограничениями. Для расхода заряда батареи выберите «Без ограничений».",
"reasonDownloadChannelBlocked": "Уведомления о загрузках отключены, поэтому ход загрузок и элементы управления могут быть недоступны.",
"reasonNotificationsDisabled": "Уведомления отключены. В Android 13 и более новых версиях они необходимы для длительных фоновых загрузок.",
"reasonDataSaver": "Экономия трафика включена, поэтому фоновые загрузки через мобильную сеть заблокированы. Загрузки по Wi-Fi должны работать.",
"reasonOemUnknown": "Загрузки неоднократно останавливались, пока Plezy работал в фоне. Проверьте настройки расхода заряда батареи или фоновой работы для Plezy.",
"openSettings": "Открыть настройки",
"stillNotWorking": "Помощь для вашего устройства",
"stillNotWorkingDescription": "Посмотрите инструкции для своего устройства. Если проблема не исчезнет, отправьте лог через Настройки › Просмотр логов.",
"dialogTitle": "Загрузки могут не завершиться",
"dialogDownloadAnyway": "Всё равно скачать",
"dialogFixFirst": "Сначала исправить",
"statusTile": "Фоновые загрузки",
"statusOk": "Работа в фоне разрешена",
"statusBlocked": "Заблокировано настройками системы",
"statusDegraded": "Ограничено настройками системы",
"statusUnknown": "Ещё не проверено",
"settingsUnavailable": "Не удалось открыть системные настройки на этом устройстве",
"linkUnavailable": "Не удалось открыть dontkillmyapp.com на этом устройстве"
}
},
"shaders": {
"title": "Шейдеры",
+1 -1
View File
@@ -4,7 +4,7 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 18
/// Strings: 26031 (1446 per locale)
/// Strings: 26499 (1472 per locale)
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+62
View File
@@ -1453,6 +1453,7 @@ class _Translations$downloads$bg extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Не е налично за текущия профил';
@override String get syncRuleUnknownServer => 'Неизвестен сървър';
@override String get syncRuleListCreated => 'Правилото за синхронизация е създадено';
@override late final _Translations$downloads$backgroundWarning$bg backgroundWarning = _Translations$downloads$backgroundWarning$bg._(_root);
}
// Path: shaders
@@ -1983,6 +1984,41 @@ class _Translations$explore$status$bg extends Translations$explore$status$en {
@override String get upcoming => 'Предстоящ';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$bg extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$bg._(TranslationsBg root) : this._root = root, super.internal(root);
final TranslationsBg _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'Изтеглянията ще спрат, когато излезете от приложението';
@override String get bannerDegraded => 'Изтеглянията във фонов режим може да бъдат ограничени';
@override String get bannerAction => 'Подробности';
@override String get sheetTitle => 'Изтеглянията във фонов режим са блокирани';
@override String get sheetTitleDegraded => 'Изтеглянията във фонов режим може да бъдат ограничени';
@override String get sheetIntro => 'Android не позволява на Plezy да изтегля надеждно във фонов режим.';
@override String get sheetIntroDegraded => 'Устройството ви ограничава кога Plezy може да изтегля във фонов режим.';
@override String get reasonBackgroundRestricted => 'Работата на Plezy във фонов режим е ограничена. Задайте използването на батерията или работата във фонов режим на „Без ограничения“.';
@override String get reasonStandbyRestricted => 'Android е поставил Plezy в ограничено състояние на готовност. Задайте използването на батерията на „Без ограничения“.';
@override String get reasonDownloadChannelBlocked => 'Известията за изтегляния са изключени, затова напредъкът и контролите може да не са достъпни.';
@override String get reasonNotificationsDisabled => 'Известията са изключени. В Android 13 или по-нова версия те са необходими за продължителни изтегляния във фонов режим.';
@override String get reasonDataSaver => '„Икономия на данни“ е включена и блокира изтеглянията във фонов режим през мобилни данни. Изтеглянията би трябвало да продължат през Wi-Fi.';
@override String get reasonOemUnknown => 'Изтеглянията спираха многократно, докато Plezy беше във фонов режим. Проверете настройките за батерията или работата на Plezy във фонов режим.';
@override String get openSettings => 'Отвори настройките';
@override String get stillNotWorking => 'Помощ за конкретното устройство';
@override String get stillNotWorkingDescription => 'Вижте стъпките за устройството си или изпратете лог от Настройки › Виж логовете, ако проблемът продължи.';
@override String get dialogTitle => 'Изтеглянията може да не завършат';
@override String get dialogDownloadAnyway => 'Изтегли въпреки това';
@override String get dialogFixFirst => 'Първо отстрани проблема';
@override String get statusTile => 'Изтегляния във фонов режим';
@override String get statusOk => 'Разрешена е работа във фонов режим';
@override String get statusBlocked => 'Блокирани от системните настройки';
@override String get statusDegraded => 'Ограничени от системните настройки';
@override String get statusUnknown => 'Все още не е проверено';
@override String get settingsUnavailable => 'Системните настройки не можаха да се отворят на това устройство';
@override String get linkUnavailable => 'dontkillmyapp.com не можа да се отвори на това устройство';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$bg extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$bg._(TranslationsBg root) : this._root = root, super.internal(root);
@@ -3261,6 +3297,32 @@ extension on TranslationsBg {
'downloads.syncRuleNotAvailableForProfile' => 'Не е налично за текущия профил',
'downloads.syncRuleUnknownServer' => 'Неизвестен сървър',
'downloads.syncRuleListCreated' => 'Правилото за синхронизация е създадено',
'downloads.backgroundWarning.bannerBlocked' => 'Изтеглянията ще спрат, когато излезете от приложението',
'downloads.backgroundWarning.bannerDegraded' => 'Изтеглянията във фонов режим може да бъдат ограничени',
'downloads.backgroundWarning.bannerAction' => 'Подробности',
'downloads.backgroundWarning.sheetTitle' => 'Изтеглянията във фонов режим са блокирани',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Изтеглянията във фонов режим може да бъдат ограничени',
'downloads.backgroundWarning.sheetIntro' => 'Android не позволява на Plezy да изтегля надеждно във фонов режим.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Устройството ви ограничава кога Plezy може да изтегля във фонов режим.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Работата на Plezy във фонов режим е ограничена. Задайте използването на батерията или работата във фонов режим на „Без ограничения“.',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android е поставил Plezy в ограничено състояние на готовност. Задайте използването на батерията на „Без ограничения“.',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Известията за изтегляния са изключени, затова напредъкът и контролите може да не са достъпни.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Известията са изключени. В Android 13 или по-нова версия те са необходими за продължителни изтегляния във фонов режим.',
'downloads.backgroundWarning.reasonDataSaver' => '„Икономия на данни“ е включена и блокира изтеглянията във фонов режим през мобилни данни. Изтеглянията би трябвало да продължат през Wi-Fi.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Изтеглянията спираха многократно, докато Plezy беше във фонов режим. Проверете настройките за батерията или работата на Plezy във фонов режим.',
'downloads.backgroundWarning.openSettings' => 'Отвори настройките',
'downloads.backgroundWarning.stillNotWorking' => 'Помощ за конкретното устройство',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Вижте стъпките за устройството си или изпратете лог от Настройки › Виж логовете, ако проблемът продължи.',
'downloads.backgroundWarning.dialogTitle' => 'Изтеглянията може да не завършат',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Изтегли въпреки това',
'downloads.backgroundWarning.dialogFixFirst' => 'Първо отстрани проблема',
'downloads.backgroundWarning.statusTile' => 'Изтегляния във фонов режим',
'downloads.backgroundWarning.statusOk' => 'Разрешена е работа във фонов режим',
'downloads.backgroundWarning.statusBlocked' => 'Блокирани от системните настройки',
'downloads.backgroundWarning.statusDegraded' => 'Ограничени от системните настройки',
'downloads.backgroundWarning.statusUnknown' => 'Все още не е проверено',
'downloads.backgroundWarning.settingsUnavailable' => 'Системните настройки не можаха да се отворят на това устройство',
'downloads.backgroundWarning.linkUnavailable' => 'dontkillmyapp.com не можа да се отвори на това устройство',
'shaders.title' => 'Шейдъри',
'shaders.noShaderDescription' => 'Без видео подобрение',
'shaders.nvscalerDescription' => 'Мащабиране на изображението чрез NVIDIA за по-рязко видео',
+62
View File
@@ -1453,6 +1453,7 @@ class _Translations$downloads$da extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Ikke tilgængelig for nuværende profil';
@override String get syncRuleUnknownServer => 'Ukendt server';
@override String get syncRuleListCreated => 'Synkroniseringsregel oprettet';
@override late final _Translations$downloads$backgroundWarning$da backgroundWarning = _Translations$downloads$backgroundWarning$da._(_root);
}
// Path: shaders
@@ -1983,6 +1984,41 @@ class _Translations$explore$status$da extends Translations$explore$status$en {
@override String get upcoming => 'Kommende';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$da extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$da._(TranslationsDa root) : this._root = root, super.internal(root);
final TranslationsDa _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'Downloads stopper, når du forlader appen';
@override String get bannerDegraded => 'Downloads i baggrunden kan være begrænsede';
@override String get bannerAction => 'Detaljer';
@override String get sheetTitle => 'Downloads i baggrunden er blokeret';
@override String get sheetTitleDegraded => 'Downloads i baggrunden kan være begrænsede';
@override String get sheetIntro => 'Android forhindrer Plezy i at downloade stabilt i baggrunden.';
@override String get sheetIntroDegraded => 'Din enhed begrænser, hvornår Plezy kan downloade i baggrunden.';
@override String get reasonBackgroundRestricted => 'Plezys baggrundsaktivitet er begrænset. Indstil batteriforbruget eller baggrundsaktiviteten til "Ubegrænset".';
@override String get reasonStandbyRestricted => 'Android har sat Plezy i begrænset standbytilstand. Indstil batteriforbruget til "Ubegrænset".';
@override String get reasonDownloadChannelBlocked => 'Notifikationer om downloads er slået fra, så status og betjeningsknapper muligvis ikke er tilgængelige.';
@override String get reasonNotificationsDisabled => 'Notifikationer er slået fra. På Android 13 eller nyere er de nødvendige ved lange downloads i baggrunden.';
@override String get reasonDataSaver => 'Datasparefunktionen er slået til, hvilket blokerer downloads i baggrunden via mobildata. Downloads bør stadig køre på Wi-Fi.';
@override String get reasonOemUnknown => 'Downloads stoppede gentagne gange, mens Plezy var i baggrunden. Tjek Plezys indstillinger for batteriforbrug eller baggrundsaktivitet.';
@override String get openSettings => 'Åbn indstillinger';
@override String get stillNotWorking => 'Enhedsspecifik hjælp';
@override String get stillNotWorkingDescription => 'Se vejledningen til din enhed, eller send en logfil fra Indstillinger Vis logfiler, hvis problemet fortsætter.';
@override String get dialogTitle => 'Downloads bliver muligvis ikke færdige';
@override String get dialogDownloadAnyway => 'Download alligevel';
@override String get dialogFixFirst => 'Løs dette først';
@override String get statusTile => 'Downloads i baggrunden';
@override String get statusOk => 'Må køre i baggrunden';
@override String get statusBlocked => 'Blokeret af systemindstillinger';
@override String get statusDegraded => 'Begrænset af systemindstillinger';
@override String get statusUnknown => 'Endnu ikke kontrolleret';
@override String get settingsUnavailable => 'Kunne ikke åbne systemindstillingerne på denne enhed';
@override String get linkUnavailable => 'Kunne ikke åbne dontkillmyapp.com på denne enhed';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$da extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$da._(TranslationsDa root) : this._root = root, super.internal(root);
@@ -3261,6 +3297,32 @@ extension on TranslationsDa {
'downloads.syncRuleNotAvailableForProfile' => 'Ikke tilgængelig for nuværende profil',
'downloads.syncRuleUnknownServer' => 'Ukendt server',
'downloads.syncRuleListCreated' => 'Synkroniseringsregel oprettet',
'downloads.backgroundWarning.bannerBlocked' => 'Downloads stopper, når du forlader appen',
'downloads.backgroundWarning.bannerDegraded' => 'Downloads i baggrunden kan være begrænsede',
'downloads.backgroundWarning.bannerAction' => 'Detaljer',
'downloads.backgroundWarning.sheetTitle' => 'Downloads i baggrunden er blokeret',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Downloads i baggrunden kan være begrænsede',
'downloads.backgroundWarning.sheetIntro' => 'Android forhindrer Plezy i at downloade stabilt i baggrunden.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Din enhed begrænser, hvornår Plezy kan downloade i baggrunden.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Plezys baggrundsaktivitet er begrænset. Indstil batteriforbruget eller baggrundsaktiviteten til "Ubegrænset".',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android har sat Plezy i begrænset standbytilstand. Indstil batteriforbruget til "Ubegrænset".',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Notifikationer om downloads er slået fra, så status og betjeningsknapper muligvis ikke er tilgængelige.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Notifikationer er slået fra. På Android 13 eller nyere er de nødvendige ved lange downloads i baggrunden.',
'downloads.backgroundWarning.reasonDataSaver' => 'Datasparefunktionen er slået til, hvilket blokerer downloads i baggrunden via mobildata. Downloads bør stadig køre på Wi-Fi.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Downloads stoppede gentagne gange, mens Plezy var i baggrunden. Tjek Plezys indstillinger for batteriforbrug eller baggrundsaktivitet.',
'downloads.backgroundWarning.openSettings' => 'Åbn indstillinger',
'downloads.backgroundWarning.stillNotWorking' => 'Enhedsspecifik hjælp',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Se vejledningen til din enhed, eller send en logfil fra Indstillinger Vis logfiler, hvis problemet fortsætter.',
'downloads.backgroundWarning.dialogTitle' => 'Downloads bliver muligvis ikke færdige',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Download alligevel',
'downloads.backgroundWarning.dialogFixFirst' => 'Løs dette først',
'downloads.backgroundWarning.statusTile' => 'Downloads i baggrunden',
'downloads.backgroundWarning.statusOk' => 'Må køre i baggrunden',
'downloads.backgroundWarning.statusBlocked' => 'Blokeret af systemindstillinger',
'downloads.backgroundWarning.statusDegraded' => 'Begrænset af systemindstillinger',
'downloads.backgroundWarning.statusUnknown' => 'Endnu ikke kontrolleret',
'downloads.backgroundWarning.settingsUnavailable' => 'Kunne ikke åbne systemindstillingerne på denne enhed',
'downloads.backgroundWarning.linkUnavailable' => 'Kunne ikke åbne dontkillmyapp.com på denne enhed',
'shaders.title' => 'Shadere',
'shaders.noShaderDescription' => 'Ingen videoforbedring',
'shaders.nvscalerDescription' => 'NVIDIA-billedskalering for skarpere video',
+62
View File
@@ -1453,6 +1453,7 @@ class _Translations$downloads$de extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Für das aktuelle Profil nicht verfügbar';
@override String get syncRuleUnknownServer => 'Unbekannter Server';
@override String get syncRuleListCreated => 'Synchronisierungsregel erstellt';
@override late final _Translations$downloads$backgroundWarning$de backgroundWarning = _Translations$downloads$backgroundWarning$de._(_root);
}
// Path: shaders
@@ -1983,6 +1984,41 @@ class _Translations$explore$status$de extends Translations$explore$status$en {
@override String get upcoming => 'Demnächst';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$de extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$de._(TranslationsDe root) : this._root = root, super.internal(root);
final TranslationsDe _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'Downloads werden gestoppt, wenn du die App verlässt';
@override String get bannerDegraded => 'Downloads im Hintergrund sind möglicherweise eingeschränkt';
@override String get bannerAction => 'Details';
@override String get sheetTitle => 'Downloads im Hintergrund sind blockiert';
@override String get sheetTitleDegraded => 'Downloads im Hintergrund sind möglicherweise eingeschränkt';
@override String get sheetIntro => 'Android verhindert, dass Plezy zuverlässig im Hintergrund herunterlädt.';
@override String get sheetIntroDegraded => 'Dein Gerät schränkt ein, wann Plezy im Hintergrund herunterladen kann.';
@override String get reasonBackgroundRestricted => 'Die Hintergrundnutzung von Plezy ist eingeschränkt. Stelle die Akku- oder Hintergrundnutzung auf „Uneingeschränkt“.';
@override String get reasonStandbyRestricted => 'Android hat Plezy in einen eingeschränkten Standby-Modus versetzt. Stelle die Akkunutzung auf „Uneingeschränkt“.';
@override String get reasonDownloadChannelBlocked => 'Download-Benachrichtigungen sind deaktiviert. Fortschritt und Steuerelemente sind daher möglicherweise nicht verfügbar.';
@override String get reasonNotificationsDisabled => 'Benachrichtigungen sind deaktiviert. Ab Android 13 sind sie für lange Downloads im Hintergrund erforderlich.';
@override String get reasonDataSaver => 'Der Datensparmodus ist aktiviert und blockiert Downloads im Hintergrund über mobile Daten. Über Wi-Fi sollten Downloads weiterhin funktionieren.';
@override String get reasonOemUnknown => 'Downloads wurden wiederholt gestoppt, während Plezy im Hintergrund war. Prüfe die Einstellungen zur Akku- oder Hintergrundnutzung von Plezy.';
@override String get openSettings => 'Einstellungen öffnen';
@override String get stillNotWorking => 'Gerätespezifische Hilfe';
@override String get stillNotWorkingDescription => 'Sieh dir die Schritte für dein Gerät an oder sende bei anhaltendem Problem ein Protokoll über Einstellungen Protokolle anzeigen.';
@override String get dialogTitle => 'Downloads werden möglicherweise nicht abgeschlossen';
@override String get dialogDownloadAnyway => 'Trotzdem herunterladen';
@override String get dialogFixFirst => 'Zuerst beheben';
@override String get statusTile => 'Downloads im Hintergrund';
@override String get statusOk => 'Ausführung im Hintergrund erlaubt';
@override String get statusBlocked => 'Durch Systemeinstellungen blockiert';
@override String get statusDegraded => 'Durch Systemeinstellungen eingeschränkt';
@override String get statusUnknown => 'Noch nicht geprüft';
@override String get settingsUnavailable => 'Die Systemeinstellungen konnten auf diesem Gerät nicht geöffnet werden';
@override String get linkUnavailable => 'dontkillmyapp.com konnte auf diesem Gerät nicht geöffnet werden';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$de extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$de._(TranslationsDe root) : this._root = root, super.internal(root);
@@ -3261,6 +3297,32 @@ extension on TranslationsDe {
'downloads.syncRuleNotAvailableForProfile' => 'Für das aktuelle Profil nicht verfügbar',
'downloads.syncRuleUnknownServer' => 'Unbekannter Server',
'downloads.syncRuleListCreated' => 'Synchronisierungsregel erstellt',
'downloads.backgroundWarning.bannerBlocked' => 'Downloads werden gestoppt, wenn du die App verlässt',
'downloads.backgroundWarning.bannerDegraded' => 'Downloads im Hintergrund sind möglicherweise eingeschränkt',
'downloads.backgroundWarning.bannerAction' => 'Details',
'downloads.backgroundWarning.sheetTitle' => 'Downloads im Hintergrund sind blockiert',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Downloads im Hintergrund sind möglicherweise eingeschränkt',
'downloads.backgroundWarning.sheetIntro' => 'Android verhindert, dass Plezy zuverlässig im Hintergrund herunterlädt.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Dein Gerät schränkt ein, wann Plezy im Hintergrund herunterladen kann.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Die Hintergrundnutzung von Plezy ist eingeschränkt. Stelle die Akku- oder Hintergrundnutzung auf „Uneingeschränkt“.',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android hat Plezy in einen eingeschränkten Standby-Modus versetzt. Stelle die Akkunutzung auf „Uneingeschränkt“.',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Download-Benachrichtigungen sind deaktiviert. Fortschritt und Steuerelemente sind daher möglicherweise nicht verfügbar.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Benachrichtigungen sind deaktiviert. Ab Android 13 sind sie für lange Downloads im Hintergrund erforderlich.',
'downloads.backgroundWarning.reasonDataSaver' => 'Der Datensparmodus ist aktiviert und blockiert Downloads im Hintergrund über mobile Daten. Über Wi-Fi sollten Downloads weiterhin funktionieren.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Downloads wurden wiederholt gestoppt, während Plezy im Hintergrund war. Prüfe die Einstellungen zur Akku- oder Hintergrundnutzung von Plezy.',
'downloads.backgroundWarning.openSettings' => 'Einstellungen öffnen',
'downloads.backgroundWarning.stillNotWorking' => 'Gerätespezifische Hilfe',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Sieh dir die Schritte für dein Gerät an oder sende bei anhaltendem Problem ein Protokoll über Einstellungen Protokolle anzeigen.',
'downloads.backgroundWarning.dialogTitle' => 'Downloads werden möglicherweise nicht abgeschlossen',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Trotzdem herunterladen',
'downloads.backgroundWarning.dialogFixFirst' => 'Zuerst beheben',
'downloads.backgroundWarning.statusTile' => 'Downloads im Hintergrund',
'downloads.backgroundWarning.statusOk' => 'Ausführung im Hintergrund erlaubt',
'downloads.backgroundWarning.statusBlocked' => 'Durch Systemeinstellungen blockiert',
'downloads.backgroundWarning.statusDegraded' => 'Durch Systemeinstellungen eingeschränkt',
'downloads.backgroundWarning.statusUnknown' => 'Noch nicht geprüft',
'downloads.backgroundWarning.settingsUnavailable' => 'Die Systemeinstellungen konnten auf diesem Gerät nicht geöffnet werden',
'downloads.backgroundWarning.linkUnavailable' => 'dontkillmyapp.com konnte auf diesem Gerät nicht geöffnet werden',
'shaders.title' => 'Shader',
'shaders.noShaderDescription' => 'Keine Videoverbesserung',
'shaders.nvscalerDescription' => 'NVIDIA-Bildskalierung für schärferes Video',
+115
View File
@@ -3497,6 +3497,8 @@ class Translations$downloads$en {
/// en: 'Sync rule created'
String get syncRuleListCreated => 'Sync rule created';
late final Translations$downloads$backgroundWarning$en backgroundWarning = Translations$downloads$backgroundWarning$en.internal(_root);
}
// Path: shaders
@@ -4709,6 +4711,93 @@ class Translations$explore$status$en {
String get upcoming => 'Upcoming';
}
// Path: downloads.backgroundWarning
class Translations$downloads$backgroundWarning$en {
Translations$downloads$backgroundWarning$en.internal(this._root);
final Translations _root; // ignore: unused_field
// Translations
/// en: 'Downloads will stop when you leave the app'
String get bannerBlocked => 'Downloads will stop when you leave the app';
/// en: 'Background downloads may be limited'
String get bannerDegraded => 'Background downloads may be limited';
/// en: 'Details'
String get bannerAction => 'Details';
/// en: 'Background downloads are blocked'
String get sheetTitle => 'Background downloads are blocked';
/// en: 'Background downloads may be limited'
String get sheetTitleDegraded => 'Background downloads may be limited';
/// en: 'Android is preventing Plezy from downloading reliably in the background.'
String get sheetIntro => 'Android is preventing Plezy from downloading reliably in the background.';
/// en: 'Your device is limiting when Plezy can download in the background.'
String get sheetIntroDegraded => 'Your device is limiting when Plezy can download in the background.';
/// en: 'Plezy's background usage is restricted. Set its battery or background usage to "Unrestricted".'
String get reasonBackgroundRestricted => 'Plezy\'s background usage is restricted. Set its battery or background usage to "Unrestricted".';
/// en: 'Android has put Plezy in a restricted standby state. Set its battery usage to "Unrestricted".'
String get reasonStandbyRestricted => 'Android has put Plezy in a restricted standby state. Set its battery usage to "Unrestricted".';
/// en: 'Download notifications are turned off, so progress and controls may be unavailable.'
String get reasonDownloadChannelBlocked => 'Download notifications are turned off, so progress and controls may be unavailable.';
/// en: 'Notifications are turned off. On Android 13 or newer, they are required for long background downloads.'
String get reasonNotificationsDisabled => 'Notifications are turned off. On Android 13 or newer, they are required for long background downloads.';
/// en: 'Data Saver is on, which blocks background downloads on mobile data. Downloads should still run on Wi-Fi.'
String get reasonDataSaver => 'Data Saver is on, which blocks background downloads on mobile data. Downloads should still run on Wi-Fi.';
/// en: 'Downloads repeatedly stopped while Plezy was in the background. Check Plezy's battery or background usage settings.'
String get reasonOemUnknown => 'Downloads repeatedly stopped while Plezy was in the background. Check Plezy\'s battery or background usage settings.';
/// en: 'Open settings'
String get openSettings => 'Open settings';
/// en: 'Device-specific help'
String get stillNotWorking => 'Device-specific help';
/// en: 'See steps for your device, or send a log from Settings View Logs if the issue continues.'
String get stillNotWorkingDescription => 'See steps for your device, or send a log from Settings View Logs if the issue continues.';
/// en: 'Downloads may not finish'
String get dialogTitle => 'Downloads may not finish';
/// en: 'Download anyway'
String get dialogDownloadAnyway => 'Download anyway';
/// en: 'Fix this first'
String get dialogFixFirst => 'Fix this first';
/// en: 'Background downloads'
String get statusTile => 'Background downloads';
/// en: 'Allowed to run in the background'
String get statusOk => 'Allowed to run in the background';
/// en: 'Blocked by system settings'
String get statusBlocked => 'Blocked by system settings';
/// en: 'Limited by system settings'
String get statusDegraded => 'Limited by system settings';
/// en: 'Not checked yet'
String get statusUnknown => 'Not checked yet';
/// en: 'Couldn't open system settings on this device'
String get settingsUnavailable => 'Couldn\'t open system settings on this device';
/// en: 'Couldn't open dontkillmyapp.com on this device'
String get linkUnavailable => 'Couldn\'t open dontkillmyapp.com on this device';
}
// Path: companionRemote.session
class Translations$companionRemote$session$en {
Translations$companionRemote$session$en.internal(this._root);
@@ -6166,6 +6255,32 @@ extension on Translations {
'downloads.syncRuleNotAvailableForProfile' => 'Not available for current profile',
'downloads.syncRuleUnknownServer' => 'Unknown server',
'downloads.syncRuleListCreated' => 'Sync rule created',
'downloads.backgroundWarning.bannerBlocked' => 'Downloads will stop when you leave the app',
'downloads.backgroundWarning.bannerDegraded' => 'Background downloads may be limited',
'downloads.backgroundWarning.bannerAction' => 'Details',
'downloads.backgroundWarning.sheetTitle' => 'Background downloads are blocked',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Background downloads may be limited',
'downloads.backgroundWarning.sheetIntro' => 'Android is preventing Plezy from downloading reliably in the background.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Your device is limiting when Plezy can download in the background.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Plezy\'s background usage is restricted. Set its battery or background usage to "Unrestricted".',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android has put Plezy in a restricted standby state. Set its battery usage to "Unrestricted".',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Download notifications are turned off, so progress and controls may be unavailable.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Notifications are turned off. On Android 13 or newer, they are required for long background downloads.',
'downloads.backgroundWarning.reasonDataSaver' => 'Data Saver is on, which blocks background downloads on mobile data. Downloads should still run on Wi-Fi.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Downloads repeatedly stopped while Plezy was in the background. Check Plezy\'s battery or background usage settings.',
'downloads.backgroundWarning.openSettings' => 'Open settings',
'downloads.backgroundWarning.stillNotWorking' => 'Device-specific help',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'See steps for your device, or send a log from Settings View Logs if the issue continues.',
'downloads.backgroundWarning.dialogTitle' => 'Downloads may not finish',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Download anyway',
'downloads.backgroundWarning.dialogFixFirst' => 'Fix this first',
'downloads.backgroundWarning.statusTile' => 'Background downloads',
'downloads.backgroundWarning.statusOk' => 'Allowed to run in the background',
'downloads.backgroundWarning.statusBlocked' => 'Blocked by system settings',
'downloads.backgroundWarning.statusDegraded' => 'Limited by system settings',
'downloads.backgroundWarning.statusUnknown' => 'Not checked yet',
'downloads.backgroundWarning.settingsUnavailable' => 'Couldn\'t open system settings on this device',
'downloads.backgroundWarning.linkUnavailable' => 'Couldn\'t open dontkillmyapp.com on this device',
'shaders.title' => 'Shaders',
'shaders.noShaderDescription' => 'No video enhancement',
'shaders.nvscalerDescription' => 'NVIDIA image scaling for sharper video',
+62
View File
@@ -1453,6 +1453,7 @@ class _Translations$downloads$es extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'No disponible para el perfil actual';
@override String get syncRuleUnknownServer => 'Servidor desconocido';
@override String get syncRuleListCreated => 'Regla de sincronización creada';
@override late final _Translations$downloads$backgroundWarning$es backgroundWarning = _Translations$downloads$backgroundWarning$es._(_root);
}
// Path: shaders
@@ -1983,6 +1984,41 @@ class _Translations$explore$status$es extends Translations$explore$status$en {
@override String get upcoming => 'Próximamente';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$es extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$es._(TranslationsEs root) : this._root = root, super.internal(root);
final TranslationsEs _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'Las descargas se detendrán al salir de la app';
@override String get bannerDegraded => 'Las descargas en segundo plano pueden estar limitadas';
@override String get bannerAction => 'Detalles';
@override String get sheetTitle => 'Las descargas en segundo plano están bloqueadas';
@override String get sheetTitleDegraded => 'Las descargas en segundo plano pueden estar limitadas';
@override String get sheetIntro => 'Android impide que Plezy descargue de forma fiable en segundo plano.';
@override String get sheetIntroDegraded => 'Tu dispositivo limita cuándo puede descargar Plezy en segundo plano.';
@override String get reasonBackgroundRestricted => 'El uso en segundo plano de Plezy está restringido. Configura el uso de batería o en segundo plano como "Sin restricciones".';
@override String get reasonStandbyRestricted => 'Android ha puesto a Plezy en un estado de espera restringido. Configura el uso de batería como "Sin restricciones".';
@override String get reasonDownloadChannelBlocked => 'Las notificaciones de descargas están desactivadas, por lo que el progreso y los controles podrían no estar disponibles.';
@override String get reasonNotificationsDisabled => 'Las notificaciones están desactivadas. En Android 13 o versiones posteriores, son necesarias para las descargas largas en segundo plano.';
@override String get reasonDataSaver => 'El Ahorro de datos está activado y bloquea las descargas en segundo plano con datos móviles. Las descargas deberían seguir funcionando con Wi-Fi.';
@override String get reasonOemUnknown => 'Las descargas se detuvieron repetidamente mientras Plezy estaba en segundo plano. Revisa la configuración de batería o de uso en segundo plano de Plezy.';
@override String get openSettings => 'Abrir configuración';
@override String get stillNotWorking => 'Ayuda específica para tu dispositivo';
@override String get stillNotWorkingDescription => 'Consulta los pasos para tu dispositivo o envía un registro desde Configuración Ver registros si el problema continúa.';
@override String get dialogTitle => 'Es posible que las descargas no finalicen';
@override String get dialogDownloadAnyway => 'Descargar de todos modos';
@override String get dialogFixFirst => 'Solucionarlo primero';
@override String get statusTile => 'Descargas en segundo plano';
@override String get statusOk => 'Permitidas en segundo plano';
@override String get statusBlocked => 'Bloqueadas por la configuración del sistema';
@override String get statusDegraded => 'Limitadas por la configuración del sistema';
@override String get statusUnknown => 'Aún no se ha comprobado';
@override String get settingsUnavailable => 'No se pudo abrir la configuración del sistema en este dispositivo';
@override String get linkUnavailable => 'No se pudo abrir dontkillmyapp.com en este dispositivo';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$es extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$es._(TranslationsEs root) : this._root = root, super.internal(root);
@@ -3261,6 +3297,32 @@ extension on TranslationsEs {
'downloads.syncRuleNotAvailableForProfile' => 'No disponible para el perfil actual',
'downloads.syncRuleUnknownServer' => 'Servidor desconocido',
'downloads.syncRuleListCreated' => 'Regla de sincronización creada',
'downloads.backgroundWarning.bannerBlocked' => 'Las descargas se detendrán al salir de la app',
'downloads.backgroundWarning.bannerDegraded' => 'Las descargas en segundo plano pueden estar limitadas',
'downloads.backgroundWarning.bannerAction' => 'Detalles',
'downloads.backgroundWarning.sheetTitle' => 'Las descargas en segundo plano están bloqueadas',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Las descargas en segundo plano pueden estar limitadas',
'downloads.backgroundWarning.sheetIntro' => 'Android impide que Plezy descargue de forma fiable en segundo plano.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Tu dispositivo limita cuándo puede descargar Plezy en segundo plano.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'El uso en segundo plano de Plezy está restringido. Configura el uso de batería o en segundo plano como "Sin restricciones".',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android ha puesto a Plezy en un estado de espera restringido. Configura el uso de batería como "Sin restricciones".',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Las notificaciones de descargas están desactivadas, por lo que el progreso y los controles podrían no estar disponibles.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Las notificaciones están desactivadas. En Android 13 o versiones posteriores, son necesarias para las descargas largas en segundo plano.',
'downloads.backgroundWarning.reasonDataSaver' => 'El Ahorro de datos está activado y bloquea las descargas en segundo plano con datos móviles. Las descargas deberían seguir funcionando con Wi-Fi.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Las descargas se detuvieron repetidamente mientras Plezy estaba en segundo plano. Revisa la configuración de batería o de uso en segundo plano de Plezy.',
'downloads.backgroundWarning.openSettings' => 'Abrir configuración',
'downloads.backgroundWarning.stillNotWorking' => 'Ayuda específica para tu dispositivo',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Consulta los pasos para tu dispositivo o envía un registro desde Configuración Ver registros si el problema continúa.',
'downloads.backgroundWarning.dialogTitle' => 'Es posible que las descargas no finalicen',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Descargar de todos modos',
'downloads.backgroundWarning.dialogFixFirst' => 'Solucionarlo primero',
'downloads.backgroundWarning.statusTile' => 'Descargas en segundo plano',
'downloads.backgroundWarning.statusOk' => 'Permitidas en segundo plano',
'downloads.backgroundWarning.statusBlocked' => 'Bloqueadas por la configuración del sistema',
'downloads.backgroundWarning.statusDegraded' => 'Limitadas por la configuración del sistema',
'downloads.backgroundWarning.statusUnknown' => 'Aún no se ha comprobado',
'downloads.backgroundWarning.settingsUnavailable' => 'No se pudo abrir la configuración del sistema en este dispositivo',
'downloads.backgroundWarning.linkUnavailable' => 'No se pudo abrir dontkillmyapp.com en este dispositivo',
'shaders.title' => 'Shaders',
'shaders.noShaderDescription' => 'Sin mejora de video',
'shaders.nvscalerDescription' => 'Escalado de imagen NVIDIA para un video más nítido',
+62
View File
@@ -1453,6 +1453,7 @@ class _Translations$downloads$fr extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Non disponible pour le profil actuel';
@override String get syncRuleUnknownServer => 'Serveur inconnu';
@override String get syncRuleListCreated => 'Règle de synchronisation créée';
@override late final _Translations$downloads$backgroundWarning$fr backgroundWarning = _Translations$downloads$backgroundWarning$fr._(_root);
}
// Path: shaders
@@ -1983,6 +1984,41 @@ class _Translations$explore$status$fr extends Translations$explore$status$en {
@override String get upcoming => 'À venir';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$fr extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$fr._(TranslationsFr root) : this._root = root, super.internal(root);
final TranslationsFr _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'Les téléchargements sarrêteront lorsque vous quitterez lapplication';
@override String get bannerDegraded => 'Les téléchargements en arrière-plan peuvent être limités';
@override String get bannerAction => 'Détails';
@override String get sheetTitle => 'Les téléchargements en arrière-plan sont bloqués';
@override String get sheetTitleDegraded => 'Les téléchargements en arrière-plan peuvent être limités';
@override String get sheetIntro => 'Android empêche Plezy de télécharger de façon fiable en arrière-plan.';
@override String get sheetIntroDegraded => 'Votre appareil limite les moments où Plezy peut télécharger en arrière-plan.';
@override String get reasonBackgroundRestricted => 'Lutilisation de Plezy en arrière-plan est restreinte. Dans les paramètres de batterie ou dutilisation en arrière-plan, sélectionnez « Sans restriction ».';
@override String get reasonStandbyRestricted => 'Android a placé Plezy en veille restreinte. Réglez lutilisation de la batterie sur « Sans restriction ».';
@override String get reasonDownloadChannelBlocked => 'Les notifications de téléchargement sont désactivées. La progression et les commandes peuvent donc être indisponibles.';
@override String get reasonNotificationsDisabled => 'Les notifications sont désactivées. Sur Android 13 ou version ultérieure, elles sont nécessaires pour les longs téléchargements en arrière-plan.';
@override String get reasonDataSaver => 'L’Économiseur de données est activé et bloque les téléchargements en arrière-plan via les données mobiles. Ils devraient toujours fonctionner en Wi-Fi.';
@override String get reasonOemUnknown => 'Les téléchargements se sont arrêtés plusieurs fois lorsque Plezy était en arrière-plan. Vérifiez les paramètres de batterie ou dutilisation en arrière-plan de Plezy.';
@override String get openSettings => 'Ouvrir les paramètres';
@override String get stillNotWorking => 'Aide spécifique à lappareil';
@override String get stillNotWorkingDescription => 'Consultez les étapes adaptées à votre appareil ou, si le problème persiste, envoyez un journal depuis Paramètres Voir les journaux.';
@override String get dialogTitle => 'Les téléchargements risquent de ne pas aboutir';
@override String get dialogDownloadAnyway => 'Télécharger quand même';
@override String get dialogFixFirst => 'Corriger dabord';
@override String get statusTile => 'Téléchargements en arrière-plan';
@override String get statusOk => 'Exécution en arrière-plan autorisée';
@override String get statusBlocked => 'Bloqués par les paramètres système';
@override String get statusDegraded => 'Limités par les paramètres système';
@override String get statusUnknown => 'Pas encore vérifié';
@override String get settingsUnavailable => 'Impossible douvrir les paramètres système sur cet appareil';
@override String get linkUnavailable => 'Impossible douvrir dontkillmyapp.com sur cet appareil';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$fr extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$fr._(TranslationsFr root) : this._root = root, super.internal(root);
@@ -3261,6 +3297,32 @@ extension on TranslationsFr {
'downloads.syncRuleNotAvailableForProfile' => 'Non disponible pour le profil actuel',
'downloads.syncRuleUnknownServer' => 'Serveur inconnu',
'downloads.syncRuleListCreated' => 'Règle de synchronisation créée',
'downloads.backgroundWarning.bannerBlocked' => 'Les téléchargements sarrêteront lorsque vous quitterez lapplication',
'downloads.backgroundWarning.bannerDegraded' => 'Les téléchargements en arrière-plan peuvent être limités',
'downloads.backgroundWarning.bannerAction' => 'Détails',
'downloads.backgroundWarning.sheetTitle' => 'Les téléchargements en arrière-plan sont bloqués',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Les téléchargements en arrière-plan peuvent être limités',
'downloads.backgroundWarning.sheetIntro' => 'Android empêche Plezy de télécharger de façon fiable en arrière-plan.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Votre appareil limite les moments où Plezy peut télécharger en arrière-plan.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Lutilisation de Plezy en arrière-plan est restreinte. Dans les paramètres de batterie ou dutilisation en arrière-plan, sélectionnez « Sans restriction ».',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android a placé Plezy en veille restreinte. Réglez lutilisation de la batterie sur « Sans restriction ».',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Les notifications de téléchargement sont désactivées. La progression et les commandes peuvent donc être indisponibles.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Les notifications sont désactivées. Sur Android 13 ou version ultérieure, elles sont nécessaires pour les longs téléchargements en arrière-plan.',
'downloads.backgroundWarning.reasonDataSaver' => 'L’Économiseur de données est activé et bloque les téléchargements en arrière-plan via les données mobiles. Ils devraient toujours fonctionner en Wi-Fi.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Les téléchargements se sont arrêtés plusieurs fois lorsque Plezy était en arrière-plan. Vérifiez les paramètres de batterie ou dutilisation en arrière-plan de Plezy.',
'downloads.backgroundWarning.openSettings' => 'Ouvrir les paramètres',
'downloads.backgroundWarning.stillNotWorking' => 'Aide spécifique à lappareil',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Consultez les étapes adaptées à votre appareil ou, si le problème persiste, envoyez un journal depuis Paramètres Voir les journaux.',
'downloads.backgroundWarning.dialogTitle' => 'Les téléchargements risquent de ne pas aboutir',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Télécharger quand même',
'downloads.backgroundWarning.dialogFixFirst' => 'Corriger dabord',
'downloads.backgroundWarning.statusTile' => 'Téléchargements en arrière-plan',
'downloads.backgroundWarning.statusOk' => 'Exécution en arrière-plan autorisée',
'downloads.backgroundWarning.statusBlocked' => 'Bloqués par les paramètres système',
'downloads.backgroundWarning.statusDegraded' => 'Limités par les paramètres système',
'downloads.backgroundWarning.statusUnknown' => 'Pas encore vérifié',
'downloads.backgroundWarning.settingsUnavailable' => 'Impossible douvrir les paramètres système sur cet appareil',
'downloads.backgroundWarning.linkUnavailable' => 'Impossible douvrir dontkillmyapp.com sur cet appareil',
'shaders.title' => 'Shaders',
'shaders.noShaderDescription' => 'Aucune amélioration vidéo',
'shaders.nvscalerDescription' => 'Mise à l\'échelle NVIDIA pour une vidéo plus nette',
+62
View File
@@ -1453,6 +1453,7 @@ class _Translations$downloads$hu extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Nem érhető el a jelenlegi profilhoz';
@override String get syncRuleUnknownServer => 'Ismeretlen szerver';
@override String get syncRuleListCreated => 'Szinkronizálási szabály létrehozva';
@override late final _Translations$downloads$backgroundWarning$hu backgroundWarning = _Translations$downloads$backgroundWarning$hu._(_root);
}
// Path: shaders
@@ -1983,6 +1984,41 @@ class _Translations$explore$status$hu extends Translations$explore$status$en {
@override String get upcoming => 'Közelgő';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$hu extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$hu._(TranslationsHu root) : this._root = root, super.internal(root);
final TranslationsHu _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'A letöltések leállnak, ha kilépsz az alkalmazásból';
@override String get bannerDegraded => 'A háttérbeli letöltések korlátozottak lehetnek';
@override String get bannerAction => 'Részletek';
@override String get sheetTitle => 'A háttérbeli letöltések le vannak tiltva';
@override String get sheetTitleDegraded => 'A háttérbeli letöltések korlátozottak lehetnek';
@override String get sheetIntro => 'Az Android megakadályozza, hogy a Plezy megbízhatóan töltsön le a háttérben.';
@override String get sheetIntroDegraded => 'Az eszközöd korlátozza, hogy a Plezy mikor tölthet le a háttérben.';
@override String get reasonBackgroundRestricted => 'A Plezy háttérbeli használata korlátozva van. Állítsd az akkumulátor- vagy háttérhasználatát „Korlátlan” értékre.';
@override String get reasonStandbyRestricted => 'Az Android korlátozott készenléti állapotba helyezte a Plezyt. Állítsd az akkumulátorhasználatát „Korlátlan” értékre.';
@override String get reasonDownloadChannelBlocked => 'A letöltési értesítések ki vannak kapcsolva, ezért előfordulhat, hogy a folyamatjelzés és a vezérlők nem érhetők el.';
@override String get reasonNotificationsDisabled => 'Az értesítések ki vannak kapcsolva. Android 13 vagy újabb rendszeren szükségesek a hosszú háttérbeli letöltésekhez.';
@override String get reasonDataSaver => 'Az Adatforgalom-csökkentő be van kapcsolva, ezért mobiladat-kapcsolaton nem működnek a háttérbeli letöltések. Wi-Fi-n továbbra is működniük kell.';
@override String get reasonOemUnknown => 'A letöltések többször leálltak, miközben a Plezy a háttérben futott. Ellenőrizd a Plezy akkumulátor- vagy háttérhasználati beállításait.';
@override String get openSettings => 'Beállítások megnyitása';
@override String get stillNotWorking => 'Eszközspecifikus segítség';
@override String get stillNotWorkingDescription => 'Nézd meg az eszközödhöz tartozó lépéseket, vagy ha a probléma továbbra is fennáll, küldj naplót a Beállítások Naplók megtekintése menüből.';
@override String get dialogTitle => 'A letöltések nem biztos, hogy befejeződnek';
@override String get dialogDownloadAnyway => 'Letöltés mégis';
@override String get dialogFixFirst => 'Előbb javítom';
@override String get statusTile => 'Háttérbeli letöltések';
@override String get statusOk => 'Futhat a háttérben';
@override String get statusBlocked => 'A rendszerbeállítások blokkolják';
@override String get statusDegraded => 'A rendszerbeállítások korlátozzák';
@override String get statusUnknown => 'Még nincs ellenőrizve';
@override String get settingsUnavailable => 'Ezen az eszközön nem sikerült megnyitni a rendszerbeállításokat';
@override String get linkUnavailable => 'Ezen az eszközön nem sikerült megnyitni a dontkillmyapp.com webhelyet';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$hu extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$hu._(TranslationsHu root) : this._root = root, super.internal(root);
@@ -3261,6 +3297,32 @@ extension on TranslationsHu {
'downloads.syncRuleNotAvailableForProfile' => 'Nem érhető el a jelenlegi profilhoz',
'downloads.syncRuleUnknownServer' => 'Ismeretlen szerver',
'downloads.syncRuleListCreated' => 'Szinkronizálási szabály létrehozva',
'downloads.backgroundWarning.bannerBlocked' => 'A letöltések leállnak, ha kilépsz az alkalmazásból',
'downloads.backgroundWarning.bannerDegraded' => 'A háttérbeli letöltések korlátozottak lehetnek',
'downloads.backgroundWarning.bannerAction' => 'Részletek',
'downloads.backgroundWarning.sheetTitle' => 'A háttérbeli letöltések le vannak tiltva',
'downloads.backgroundWarning.sheetTitleDegraded' => 'A háttérbeli letöltések korlátozottak lehetnek',
'downloads.backgroundWarning.sheetIntro' => 'Az Android megakadályozza, hogy a Plezy megbízhatóan töltsön le a háttérben.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Az eszközöd korlátozza, hogy a Plezy mikor tölthet le a háttérben.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'A Plezy háttérbeli használata korlátozva van. Állítsd az akkumulátor- vagy háttérhasználatát „Korlátlan” értékre.',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Az Android korlátozott készenléti állapotba helyezte a Plezyt. Állítsd az akkumulátorhasználatát „Korlátlan” értékre.',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'A letöltési értesítések ki vannak kapcsolva, ezért előfordulhat, hogy a folyamatjelzés és a vezérlők nem érhetők el.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Az értesítések ki vannak kapcsolva. Android 13 vagy újabb rendszeren szükségesek a hosszú háttérbeli letöltésekhez.',
'downloads.backgroundWarning.reasonDataSaver' => 'Az Adatforgalom-csökkentő be van kapcsolva, ezért mobiladat-kapcsolaton nem működnek a háttérbeli letöltések. Wi-Fi-n továbbra is működniük kell.',
'downloads.backgroundWarning.reasonOemUnknown' => 'A letöltések többször leálltak, miközben a Plezy a háttérben futott. Ellenőrizd a Plezy akkumulátor- vagy háttérhasználati beállításait.',
'downloads.backgroundWarning.openSettings' => 'Beállítások megnyitása',
'downloads.backgroundWarning.stillNotWorking' => 'Eszközspecifikus segítség',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Nézd meg az eszközödhöz tartozó lépéseket, vagy ha a probléma továbbra is fennáll, küldj naplót a Beállítások Naplók megtekintése menüből.',
'downloads.backgroundWarning.dialogTitle' => 'A letöltések nem biztos, hogy befejeződnek',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Letöltés mégis',
'downloads.backgroundWarning.dialogFixFirst' => 'Előbb javítom',
'downloads.backgroundWarning.statusTile' => 'Háttérbeli letöltések',
'downloads.backgroundWarning.statusOk' => 'Futhat a háttérben',
'downloads.backgroundWarning.statusBlocked' => 'A rendszerbeállítások blokkolják',
'downloads.backgroundWarning.statusDegraded' => 'A rendszerbeállítások korlátozzák',
'downloads.backgroundWarning.statusUnknown' => 'Még nincs ellenőrizve',
'downloads.backgroundWarning.settingsUnavailable' => 'Ezen az eszközön nem sikerült megnyitni a rendszerbeállításokat',
'downloads.backgroundWarning.linkUnavailable' => 'Ezen az eszközön nem sikerült megnyitni a dontkillmyapp.com webhelyet',
'shaders.title' => 'Shaderek',
'shaders.noShaderDescription' => 'Nincs videójavítás',
'shaders.nvscalerDescription' => 'NVIDIA képskálázás az élesebb videóért',
+62
View File
@@ -1453,6 +1453,7 @@ class _Translations$downloads$it extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Non disponibile per il profilo attuale';
@override String get syncRuleUnknownServer => 'Server sconosciuto';
@override String get syncRuleListCreated => 'Regola di sincronizzazione creata';
@override late final _Translations$downloads$backgroundWarning$it backgroundWarning = _Translations$downloads$backgroundWarning$it._(_root);
}
// Path: shaders
@@ -1983,6 +1984,41 @@ class _Translations$explore$status$it extends Translations$explore$status$en {
@override String get upcoming => 'In arrivo';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$it extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$it._(TranslationsIt root) : this._root = root, super.internal(root);
final TranslationsIt _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'I download si interromperanno quando esci dallapp';
@override String get bannerDegraded => 'I download in background potrebbero essere limitati';
@override String get bannerAction => 'Dettagli';
@override String get sheetTitle => 'I download in background sono bloccati';
@override String get sheetTitleDegraded => 'I download in background potrebbero essere limitati';
@override String get sheetIntro => 'Android impedisce a Plezy di scaricare in modo affidabile in background.';
@override String get sheetIntroDegraded => 'Il dispositivo limita i momenti in cui Plezy può scaricare in background.';
@override String get reasonBackgroundRestricted => 'Luso in background di Plezy è limitato. Nelle impostazioni della batteria o delluso in background, seleziona «Senza restrizioni».';
@override String get reasonStandbyRestricted => 'Android ha messo Plezy in uno stato di standby con restrizioni. Imposta luso della batteria su «Senza restrizioni».';
@override String get reasonDownloadChannelBlocked => 'Le notifiche dei download sono disattivate, quindi lavanzamento e i controlli potrebbero non essere disponibili.';
@override String get reasonNotificationsDisabled => 'Le notifiche sono disattivate. Su Android 13 o versioni successive sono necessarie per i download prolungati in background.';
@override String get reasonDataSaver => 'Il Risparmio dati è attivo e blocca i download in background tramite dati mobili. I download dovrebbero comunque funzionare su Wi-Fi.';
@override String get reasonOemUnknown => 'I download si sono interrotti più volte mentre Plezy era in background. Controlla le impostazioni della batteria o delluso in background di Plezy.';
@override String get openSettings => 'Apri le impostazioni';
@override String get stillNotWorking => 'Guida specifica per il dispositivo';
@override String get stillNotWorkingDescription => 'Consulta la procedura per il tuo dispositivo oppure invia un log da Impostazioni Visualizza i log se il problema persiste.';
@override String get dialogTitle => 'I download potrebbero non terminare';
@override String get dialogDownloadAnyway => 'Scarica comunque';
@override String get dialogFixFirst => 'Risolvi prima';
@override String get statusTile => 'Download in background';
@override String get statusOk => 'Esecuzione in background consentita';
@override String get statusBlocked => 'Bloccati dalle impostazioni di sistema';
@override String get statusDegraded => 'Limitati dalle impostazioni di sistema';
@override String get statusUnknown => 'Non ancora verificato';
@override String get settingsUnavailable => 'Impossibile aprire le impostazioni di sistema su questo dispositivo';
@override String get linkUnavailable => 'Impossibile aprire dontkillmyapp.com su questo dispositivo';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$it extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$it._(TranslationsIt root) : this._root = root, super.internal(root);
@@ -3261,6 +3297,32 @@ extension on TranslationsIt {
'downloads.syncRuleNotAvailableForProfile' => 'Non disponibile per il profilo attuale',
'downloads.syncRuleUnknownServer' => 'Server sconosciuto',
'downloads.syncRuleListCreated' => 'Regola di sincronizzazione creata',
'downloads.backgroundWarning.bannerBlocked' => 'I download si interromperanno quando esci dallapp',
'downloads.backgroundWarning.bannerDegraded' => 'I download in background potrebbero essere limitati',
'downloads.backgroundWarning.bannerAction' => 'Dettagli',
'downloads.backgroundWarning.sheetTitle' => 'I download in background sono bloccati',
'downloads.backgroundWarning.sheetTitleDegraded' => 'I download in background potrebbero essere limitati',
'downloads.backgroundWarning.sheetIntro' => 'Android impedisce a Plezy di scaricare in modo affidabile in background.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Il dispositivo limita i momenti in cui Plezy può scaricare in background.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Luso in background di Plezy è limitato. Nelle impostazioni della batteria o delluso in background, seleziona «Senza restrizioni».',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android ha messo Plezy in uno stato di standby con restrizioni. Imposta luso della batteria su «Senza restrizioni».',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Le notifiche dei download sono disattivate, quindi lavanzamento e i controlli potrebbero non essere disponibili.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Le notifiche sono disattivate. Su Android 13 o versioni successive sono necessarie per i download prolungati in background.',
'downloads.backgroundWarning.reasonDataSaver' => 'Il Risparmio dati è attivo e blocca i download in background tramite dati mobili. I download dovrebbero comunque funzionare su Wi-Fi.',
'downloads.backgroundWarning.reasonOemUnknown' => 'I download si sono interrotti più volte mentre Plezy era in background. Controlla le impostazioni della batteria o delluso in background di Plezy.',
'downloads.backgroundWarning.openSettings' => 'Apri le impostazioni',
'downloads.backgroundWarning.stillNotWorking' => 'Guida specifica per il dispositivo',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Consulta la procedura per il tuo dispositivo oppure invia un log da Impostazioni Visualizza i log se il problema persiste.',
'downloads.backgroundWarning.dialogTitle' => 'I download potrebbero non terminare',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Scarica comunque',
'downloads.backgroundWarning.dialogFixFirst' => 'Risolvi prima',
'downloads.backgroundWarning.statusTile' => 'Download in background',
'downloads.backgroundWarning.statusOk' => 'Esecuzione in background consentita',
'downloads.backgroundWarning.statusBlocked' => 'Bloccati dalle impostazioni di sistema',
'downloads.backgroundWarning.statusDegraded' => 'Limitati dalle impostazioni di sistema',
'downloads.backgroundWarning.statusUnknown' => 'Non ancora verificato',
'downloads.backgroundWarning.settingsUnavailable' => 'Impossibile aprire le impostazioni di sistema su questo dispositivo',
'downloads.backgroundWarning.linkUnavailable' => 'Impossibile aprire dontkillmyapp.com su questo dispositivo',
'shaders.title' => 'Shader',
'shaders.noShaderDescription' => 'Nessun miglioramento video',
'shaders.nvscalerDescription' => 'Ridimensionamento NVIDIA per video più nitido',
+62
View File
@@ -1450,6 +1450,7 @@ class _Translations$downloads$ja extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => '現在のプロフィールでは利用できません';
@override String get syncRuleUnknownServer => '不明なサーバー';
@override String get syncRuleListCreated => '同期ルールを作成しました';
@override late final _Translations$downloads$backgroundWarning$ja backgroundWarning = _Translations$downloads$backgroundWarning$ja._(_root);
}
// Path: shaders
@@ -1980,6 +1981,41 @@ class _Translations$explore$status$ja extends Translations$explore$status$en {
@override String get upcoming => '放送予定';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$ja extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$ja._(TranslationsJa root) : this._root = root, super.internal(root);
final TranslationsJa _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'アプリの画面を離れると、ダウンロードが停止します';
@override String get bannerDegraded => 'バックグラウンドダウンロードが制限される場合があります';
@override String get bannerAction => '詳細';
@override String get sheetTitle => 'バックグラウンドダウンロードはブロックされています';
@override String get sheetTitleDegraded => 'バックグラウンドダウンロードが制限される場合があります';
@override String get sheetIntro => 'Androidにより、Plezyはバックグラウンドで安定してダウンロードできません。';
@override String get sheetIntroDegraded => '端末により、Plezyがバックグラウンドでダウンロードできるタイミングが制限されています。';
@override String get reasonBackgroundRestricted => 'Plezyのバックグラウンド使用が制限されています。バッテリー使用量またはバックグラウンド使用を「制限なし」に設定してください。';
@override String get reasonStandbyRestricted => 'Androidにより、Plezyが制限付きのスタンバイ状態に設定されています。バッテリー使用量を「制限なし」に設定してください。';
@override String get reasonDownloadChannelBlocked => 'ダウンロード通知がオフのため、進行状況や操作ボタンを利用できない場合があります。';
@override String get reasonNotificationsDisabled => '通知がオフです。Android 13以降では、長時間のバックグラウンドダウンロードに通知が必要です。';
@override String get reasonDataSaver => 'データセーバーがオンのため、モバイルデータ通信ではバックグラウンドダウンロードがブロックされます。Wi-Fiでは引き続きダウンロードできるはずです。';
@override String get reasonOemUnknown => 'Plezyがバックグラウンドで動作中に、ダウンロードが繰り返し停止しました。Plezyのバッテリー使用量またはバックグラウンド使用の設定を確認してください。';
@override String get openSettings => '設定を開く';
@override String get stillNotWorking => '端末別のヘルプ';
@override String get stillNotWorkingDescription => 'お使いの端末向けの手順を確認してください。問題が続く場合は、設定 › ログを表示 からログを送信してください。';
@override String get dialogTitle => 'ダウンロードが完了しない可能性があります';
@override String get dialogDownloadAnyway => 'このままダウンロード';
@override String get dialogFixFirst => '先に設定を修正';
@override String get statusTile => 'バックグラウンドダウンロード';
@override String get statusOk => 'バックグラウンドで実行可能';
@override String get statusBlocked => 'システム設定によりブロック';
@override String get statusDegraded => 'システム設定により制限';
@override String get statusUnknown => '未確認';
@override String get settingsUnavailable => 'この端末ではシステム設定を開けませんでした';
@override String get linkUnavailable => 'この端末ではdontkillmyapp.comを開けませんでした';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$ja extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$ja._(TranslationsJa root) : this._root = root, super.internal(root);
@@ -3258,6 +3294,32 @@ extension on TranslationsJa {
'downloads.syncRuleNotAvailableForProfile' => '現在のプロフィールでは利用できません',
'downloads.syncRuleUnknownServer' => '不明なサーバー',
'downloads.syncRuleListCreated' => '同期ルールを作成しました',
'downloads.backgroundWarning.bannerBlocked' => 'アプリの画面を離れると、ダウンロードが停止します',
'downloads.backgroundWarning.bannerDegraded' => 'バックグラウンドダウンロードが制限される場合があります',
'downloads.backgroundWarning.bannerAction' => '詳細',
'downloads.backgroundWarning.sheetTitle' => 'バックグラウンドダウンロードはブロックされています',
'downloads.backgroundWarning.sheetTitleDegraded' => 'バックグラウンドダウンロードが制限される場合があります',
'downloads.backgroundWarning.sheetIntro' => 'Androidにより、Plezyはバックグラウンドで安定してダウンロードできません。',
'downloads.backgroundWarning.sheetIntroDegraded' => '端末により、Plezyがバックグラウンドでダウンロードできるタイミングが制限されています。',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Plezyのバックグラウンド使用が制限されています。バッテリー使用量またはバックグラウンド使用を「制限なし」に設定してください。',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Androidにより、Plezyが制限付きのスタンバイ状態に設定されています。バッテリー使用量を「制限なし」に設定してください。',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'ダウンロード通知がオフのため、進行状況や操作ボタンを利用できない場合があります。',
'downloads.backgroundWarning.reasonNotificationsDisabled' => '通知がオフです。Android 13以降では、長時間のバックグラウンドダウンロードに通知が必要です。',
'downloads.backgroundWarning.reasonDataSaver' => 'データセーバーがオンのため、モバイルデータ通信ではバックグラウンドダウンロードがブロックされます。Wi-Fiでは引き続きダウンロードできるはずです。',
'downloads.backgroundWarning.reasonOemUnknown' => 'Plezyがバックグラウンドで動作中に、ダウンロードが繰り返し停止しました。Plezyのバッテリー使用量またはバックグラウンド使用の設定を確認してください。',
'downloads.backgroundWarning.openSettings' => '設定を開く',
'downloads.backgroundWarning.stillNotWorking' => '端末別のヘルプ',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'お使いの端末向けの手順を確認してください。問題が続く場合は、設定 › ログを表示 からログを送信してください。',
'downloads.backgroundWarning.dialogTitle' => 'ダウンロードが完了しない可能性があります',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'このままダウンロード',
'downloads.backgroundWarning.dialogFixFirst' => '先に設定を修正',
'downloads.backgroundWarning.statusTile' => 'バックグラウンドダウンロード',
'downloads.backgroundWarning.statusOk' => 'バックグラウンドで実行可能',
'downloads.backgroundWarning.statusBlocked' => 'システム設定によりブロック',
'downloads.backgroundWarning.statusDegraded' => 'システム設定により制限',
'downloads.backgroundWarning.statusUnknown' => '未確認',
'downloads.backgroundWarning.settingsUnavailable' => 'この端末ではシステム設定を開けませんでした',
'downloads.backgroundWarning.linkUnavailable' => 'この端末ではdontkillmyapp.comを開けませんでした',
'shaders.title' => 'シェーダー',
'shaders.noShaderDescription' => '映像補正なし',
'shaders.nvscalerDescription' => 'NVIDIA画像スケーリングで映像をより鮮明にします',
+62
View File
@@ -1450,6 +1450,7 @@ class _Translations$downloads$ko extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => '현재 프로필에서 사용할 수 없음';
@override String get syncRuleUnknownServer => '알 수 없는 서버';
@override String get syncRuleListCreated => '동기화 규칙이 생성되었습니다';
@override late final _Translations$downloads$backgroundWarning$ko backgroundWarning = _Translations$downloads$backgroundWarning$ko._(_root);
}
// Path: shaders
@@ -1980,6 +1981,41 @@ class _Translations$explore$status$ko extends Translations$explore$status$en {
@override String get upcoming => '방영 예정';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$ko extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$ko._(TranslationsKo root) : this._root = root, super.internal(root);
final TranslationsKo _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => '앱에서 나가면 다운로드가 중지됩니다';
@override String get bannerDegraded => '백그라운드 다운로드가 제한될 수 있습니다';
@override String get bannerAction => '세부정보';
@override String get sheetTitle => '백그라운드 다운로드가 차단됨';
@override String get sheetTitleDegraded => '백그라운드 다운로드가 제한될 수 있음';
@override String get sheetIntro => 'Android가 Plezy의 안정적인 백그라운드 다운로드를 차단하고 있습니다.';
@override String get sheetIntroDegraded => '기기에서 Plezy가 백그라운드로 다운로드할 수 있는 시점을 제한하고 있습니다.';
@override String get reasonBackgroundRestricted => 'Plezy의 백그라운드 사용이 제한되어 있습니다. 배터리 또는 백그라운드 사용을 "제한 없음"으로 설정하세요.';
@override String get reasonStandbyRestricted => 'Android가 Plezy를 제한된 대기 상태로 전환했습니다. 배터리 사용량을 "제한 없음"으로 설정하세요.';
@override String get reasonDownloadChannelBlocked => '다운로드 알림이 꺼져 있어 진행 상황과 제어 기능을 사용하지 못할 수 있습니다.';
@override String get reasonNotificationsDisabled => '알림이 꺼져 있습니다. Android 13 이상에서는 장시간 백그라운드 다운로드에 알림이 필요합니다.';
@override String get reasonDataSaver => '데이터 절약 모드가 켜져 있어 모바일 데이터로 백그라운드 다운로드를 할 수 없습니다. Wi-Fi에서는 계속 다운로드할 수 있습니다.';
@override String get reasonOemUnknown => 'Plezy가 백그라운드에 있을 때 다운로드가 반복해서 중지되었습니다. Plezy의 배터리 또는 백그라운드 사용 설정을 확인하세요.';
@override String get openSettings => '설정 열기';
@override String get stillNotWorking => '기기별 도움말';
@override String get stillNotWorkingDescription => '기기에 맞는 단계를 확인하세요. 문제가 계속되면 설정 › 로그 보기에서 로그를 보내 주세요.';
@override String get dialogTitle => '다운로드가 완료되지 않을 수 있음';
@override String get dialogDownloadAnyway => '그래도 다운로드';
@override String get dialogFixFirst => '먼저 문제 해결';
@override String get statusTile => '백그라운드 다운로드';
@override String get statusOk => '백그라운드 실행 허용됨';
@override String get statusBlocked => '시스템 설정에 의해 차단됨';
@override String get statusDegraded => '시스템 설정에 의해 제한됨';
@override String get statusUnknown => '아직 확인하지 않음';
@override String get settingsUnavailable => '이 기기에서 시스템 설정을 열 수 없습니다';
@override String get linkUnavailable => '이 기기에서 dontkillmyapp.com을 열 수 없습니다';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$ko extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$ko._(TranslationsKo root) : this._root = root, super.internal(root);
@@ -3258,6 +3294,32 @@ extension on TranslationsKo {
'downloads.syncRuleNotAvailableForProfile' => '현재 프로필에서 사용할 수 없음',
'downloads.syncRuleUnknownServer' => '알 수 없는 서버',
'downloads.syncRuleListCreated' => '동기화 규칙이 생성되었습니다',
'downloads.backgroundWarning.bannerBlocked' => '앱에서 나가면 다운로드가 중지됩니다',
'downloads.backgroundWarning.bannerDegraded' => '백그라운드 다운로드가 제한될 수 있습니다',
'downloads.backgroundWarning.bannerAction' => '세부정보',
'downloads.backgroundWarning.sheetTitle' => '백그라운드 다운로드가 차단됨',
'downloads.backgroundWarning.sheetTitleDegraded' => '백그라운드 다운로드가 제한될 수 있음',
'downloads.backgroundWarning.sheetIntro' => 'Android가 Plezy의 안정적인 백그라운드 다운로드를 차단하고 있습니다.',
'downloads.backgroundWarning.sheetIntroDegraded' => '기기에서 Plezy가 백그라운드로 다운로드할 수 있는 시점을 제한하고 있습니다.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Plezy의 백그라운드 사용이 제한되어 있습니다. 배터리 또는 백그라운드 사용을 "제한 없음"으로 설정하세요.',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android가 Plezy를 제한된 대기 상태로 전환했습니다. 배터리 사용량을 "제한 없음"으로 설정하세요.',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => '다운로드 알림이 꺼져 있어 진행 상황과 제어 기능을 사용하지 못할 수 있습니다.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => '알림이 꺼져 있습니다. Android 13 이상에서는 장시간 백그라운드 다운로드에 알림이 필요합니다.',
'downloads.backgroundWarning.reasonDataSaver' => '데이터 절약 모드가 켜져 있어 모바일 데이터로 백그라운드 다운로드를 할 수 없습니다. Wi-Fi에서는 계속 다운로드할 수 있습니다.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Plezy가 백그라운드에 있을 때 다운로드가 반복해서 중지되었습니다. Plezy의 배터리 또는 백그라운드 사용 설정을 확인하세요.',
'downloads.backgroundWarning.openSettings' => '설정 열기',
'downloads.backgroundWarning.stillNotWorking' => '기기별 도움말',
'downloads.backgroundWarning.stillNotWorkingDescription' => '기기에 맞는 단계를 확인하세요. 문제가 계속되면 설정 › 로그 보기에서 로그를 보내 주세요.',
'downloads.backgroundWarning.dialogTitle' => '다운로드가 완료되지 않을 수 있음',
'downloads.backgroundWarning.dialogDownloadAnyway' => '그래도 다운로드',
'downloads.backgroundWarning.dialogFixFirst' => '먼저 문제 해결',
'downloads.backgroundWarning.statusTile' => '백그라운드 다운로드',
'downloads.backgroundWarning.statusOk' => '백그라운드 실행 허용됨',
'downloads.backgroundWarning.statusBlocked' => '시스템 설정에 의해 차단됨',
'downloads.backgroundWarning.statusDegraded' => '시스템 설정에 의해 제한됨',
'downloads.backgroundWarning.statusUnknown' => '아직 확인하지 않음',
'downloads.backgroundWarning.settingsUnavailable' => '이 기기에서 시스템 설정을 열 수 없습니다',
'downloads.backgroundWarning.linkUnavailable' => '이 기기에서 dontkillmyapp.com을 열 수 없습니다',
'shaders.title' => '셰이더',
'shaders.noShaderDescription' => '비디오 향상 없음',
'shaders.nvscalerDescription' => '더 선명한 비디오를 위한 NVIDIA 이미지 스케일링',
+62
View File
@@ -1453,6 +1453,7 @@ class _Translations$downloads$nb extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Ikke tilgjengelig for gjeldende profil';
@override String get syncRuleUnknownServer => 'Ukjent server';
@override String get syncRuleListCreated => 'Synkroniseringsregel opprettet';
@override late final _Translations$downloads$backgroundWarning$nb backgroundWarning = _Translations$downloads$backgroundWarning$nb._(_root);
}
// Path: shaders
@@ -1983,6 +1984,41 @@ class _Translations$explore$status$nb extends Translations$explore$status$en {
@override String get upcoming => 'Kommende';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$nb extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$nb._(TranslationsNb root) : this._root = root, super.internal(root);
final TranslationsNb _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'Nedlastinger stopper når du forlater appen';
@override String get bannerDegraded => 'Bakgrunnsnedlastinger kan være begrenset';
@override String get bannerAction => 'Detaljer';
@override String get sheetTitle => 'Bakgrunnsnedlastinger er blokkert';
@override String get sheetTitleDegraded => 'Bakgrunnsnedlastinger kan være begrenset';
@override String get sheetIntro => 'Android hindrer Plezy i å laste ned pålitelig i bakgrunnen.';
@override String get sheetIntroDegraded => 'Enheten din begrenser når Plezy kan laste ned i bakgrunnen.';
@override String get reasonBackgroundRestricted => 'Bakgrunnsbruken til Plezy er begrenset. Sett batteribruk eller bakgrunnsbruk til «Ubegrenset».';
@override String get reasonStandbyRestricted => 'Android har satt Plezy i begrenset hvilemodus. Sett batteribruken til «Ubegrenset».';
@override String get reasonDownloadChannelBlocked => 'Varsler om nedlastinger er slått av, så fremdrift og kontroller kan være utilgjengelige.';
@override String get reasonNotificationsDisabled => 'Varsler er slått av. På Android 13 eller nyere kreves de for lange bakgrunnsnedlastinger.';
@override String get reasonDataSaver => 'Datasparing er slått på og blokkerer bakgrunnsnedlastinger via mobildata. Nedlastinger skal fortsatt fungere på Wi-Fi.';
@override String get reasonOemUnknown => 'Nedlastinger har stoppet gjentatte ganger mens Plezy var i bakgrunnen. Sjekk innstillingene for batteribruk eller bakgrunnsbruk for Plezy.';
@override String get openSettings => 'Åpne innstillinger';
@override String get stillNotWorking => 'Enhetsspesifikk hjelp';
@override String get stillNotWorkingDescription => 'Se fremgangsmåten for enheten din, eller send en logg fra Innstillinger Vis logger hvis problemet vedvarer.';
@override String get dialogTitle => 'Nedlastinger blir kanskje ikke fullført';
@override String get dialogDownloadAnyway => 'Last ned likevel';
@override String get dialogFixFirst => 'Løs dette først';
@override String get statusTile => 'Bakgrunnsnedlastinger';
@override String get statusOk => 'Kan kjøre i bakgrunnen';
@override String get statusBlocked => 'Blokkert av systeminnstillinger';
@override String get statusDegraded => 'Begrenset av systeminnstillinger';
@override String get statusUnknown => 'Ikke sjekket ennå';
@override String get settingsUnavailable => 'Kunne ikke åpne systeminnstillingene på denne enheten';
@override String get linkUnavailable => 'Kunne ikke åpne dontkillmyapp.com på denne enheten';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$nb extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$nb._(TranslationsNb root) : this._root = root, super.internal(root);
@@ -3261,6 +3297,32 @@ extension on TranslationsNb {
'downloads.syncRuleNotAvailableForProfile' => 'Ikke tilgjengelig for gjeldende profil',
'downloads.syncRuleUnknownServer' => 'Ukjent server',
'downloads.syncRuleListCreated' => 'Synkroniseringsregel opprettet',
'downloads.backgroundWarning.bannerBlocked' => 'Nedlastinger stopper når du forlater appen',
'downloads.backgroundWarning.bannerDegraded' => 'Bakgrunnsnedlastinger kan være begrenset',
'downloads.backgroundWarning.bannerAction' => 'Detaljer',
'downloads.backgroundWarning.sheetTitle' => 'Bakgrunnsnedlastinger er blokkert',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Bakgrunnsnedlastinger kan være begrenset',
'downloads.backgroundWarning.sheetIntro' => 'Android hindrer Plezy i å laste ned pålitelig i bakgrunnen.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Enheten din begrenser når Plezy kan laste ned i bakgrunnen.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Bakgrunnsbruken til Plezy er begrenset. Sett batteribruk eller bakgrunnsbruk til «Ubegrenset».',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android har satt Plezy i begrenset hvilemodus. Sett batteribruken til «Ubegrenset».',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Varsler om nedlastinger er slått av, så fremdrift og kontroller kan være utilgjengelige.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Varsler er slått av. På Android 13 eller nyere kreves de for lange bakgrunnsnedlastinger.',
'downloads.backgroundWarning.reasonDataSaver' => 'Datasparing er slått på og blokkerer bakgrunnsnedlastinger via mobildata. Nedlastinger skal fortsatt fungere på Wi-Fi.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Nedlastinger har stoppet gjentatte ganger mens Plezy var i bakgrunnen. Sjekk innstillingene for batteribruk eller bakgrunnsbruk for Plezy.',
'downloads.backgroundWarning.openSettings' => 'Åpne innstillinger',
'downloads.backgroundWarning.stillNotWorking' => 'Enhetsspesifikk hjelp',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Se fremgangsmåten for enheten din, eller send en logg fra Innstillinger Vis logger hvis problemet vedvarer.',
'downloads.backgroundWarning.dialogTitle' => 'Nedlastinger blir kanskje ikke fullført',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Last ned likevel',
'downloads.backgroundWarning.dialogFixFirst' => 'Løs dette først',
'downloads.backgroundWarning.statusTile' => 'Bakgrunnsnedlastinger',
'downloads.backgroundWarning.statusOk' => 'Kan kjøre i bakgrunnen',
'downloads.backgroundWarning.statusBlocked' => 'Blokkert av systeminnstillinger',
'downloads.backgroundWarning.statusDegraded' => 'Begrenset av systeminnstillinger',
'downloads.backgroundWarning.statusUnknown' => 'Ikke sjekket ennå',
'downloads.backgroundWarning.settingsUnavailable' => 'Kunne ikke åpne systeminnstillingene på denne enheten',
'downloads.backgroundWarning.linkUnavailable' => 'Kunne ikke åpne dontkillmyapp.com på denne enheten',
'shaders.title' => 'Shadere',
'shaders.noShaderDescription' => 'Ingen videoforbedring',
'shaders.nvscalerDescription' => 'NVIDIA bildeskalering for skarpere video',
+62
View File
@@ -1453,6 +1453,7 @@ class _Translations$downloads$nl extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Niet beschikbaar voor huidig profiel';
@override String get syncRuleUnknownServer => 'Onbekende server';
@override String get syncRuleListCreated => 'Synchronisatieregel aangemaakt';
@override late final _Translations$downloads$backgroundWarning$nl backgroundWarning = _Translations$downloads$backgroundWarning$nl._(_root);
}
// Path: shaders
@@ -1983,6 +1984,41 @@ class _Translations$explore$status$nl extends Translations$explore$status$en {
@override String get upcoming => 'Binnenkort';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$nl extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$nl._(TranslationsNl root) : this._root = root, super.internal(root);
final TranslationsNl _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'Downloads stoppen zodra je de app verlaat';
@override String get bannerDegraded => 'Downloads op de achtergrond kunnen beperkt zijn';
@override String get bannerAction => 'Details';
@override String get sheetTitle => 'Downloads op de achtergrond zijn geblokkeerd';
@override String get sheetTitleDegraded => 'Downloads op de achtergrond kunnen beperkt zijn';
@override String get sheetIntro => 'Android verhindert dat Plezy betrouwbaar op de achtergrond downloadt.';
@override String get sheetIntroDegraded => 'Je apparaat beperkt wanneer Plezy op de achtergrond kan downloaden.';
@override String get reasonBackgroundRestricted => 'Het achtergrondgebruik van Plezy is beperkt. Stel het batterij- of achtergrondgebruik in op "Onbeperkt".';
@override String get reasonStandbyRestricted => 'Android heeft Plezy in een beperkte stand-bymodus geplaatst. Stel het batterijgebruik in op "Onbeperkt".';
@override String get reasonDownloadChannelBlocked => 'Downloadmeldingen zijn uitgeschakeld, waardoor voortgang en bediening mogelijk niet beschikbaar zijn.';
@override String get reasonNotificationsDisabled => 'Meldingen zijn uitgeschakeld. Op Android 13 of nieuwer zijn ze vereist voor langdurige downloads op de achtergrond.';
@override String get reasonDataSaver => 'Databesparing is ingeschakeld en blokkeert downloads op de achtergrond via mobiele data. Via Wi-Fi zouden downloads nog wel moeten werken.';
@override String get reasonOemUnknown => 'Downloads zijn herhaaldelijk gestopt terwijl Plezy op de achtergrond draaide. Controleer de instellingen voor het batterij- of achtergrondgebruik van Plezy.';
@override String get openSettings => 'Instellingen openen';
@override String get stillNotWorking => 'Apparaatspecifieke hulp';
@override String get stillNotWorkingDescription => 'Bekijk de stappen voor je apparaat of stuur een logbestand vanuit Instellingen Logbestanden bekijken als het probleem aanhoudt.';
@override String get dialogTitle => 'Downloads worden mogelijk niet voltooid';
@override String get dialogDownloadAnyway => 'Toch downloaden';
@override String get dialogFixFirst => 'Dit eerst oplossen';
@override String get statusTile => 'Downloads op de achtergrond';
@override String get statusOk => 'Mag op de achtergrond worden uitgevoerd';
@override String get statusBlocked => 'Geblokkeerd door systeeminstellingen';
@override String get statusDegraded => 'Beperkt door systeeminstellingen';
@override String get statusUnknown => 'Nog niet gecontroleerd';
@override String get settingsUnavailable => 'Kan de systeeminstellingen niet openen op dit apparaat';
@override String get linkUnavailable => 'Kan dontkillmyapp.com niet openen op dit apparaat';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$nl extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$nl._(TranslationsNl root) : this._root = root, super.internal(root);
@@ -3261,6 +3297,32 @@ extension on TranslationsNl {
'downloads.syncRuleNotAvailableForProfile' => 'Niet beschikbaar voor huidig profiel',
'downloads.syncRuleUnknownServer' => 'Onbekende server',
'downloads.syncRuleListCreated' => 'Synchronisatieregel aangemaakt',
'downloads.backgroundWarning.bannerBlocked' => 'Downloads stoppen zodra je de app verlaat',
'downloads.backgroundWarning.bannerDegraded' => 'Downloads op de achtergrond kunnen beperkt zijn',
'downloads.backgroundWarning.bannerAction' => 'Details',
'downloads.backgroundWarning.sheetTitle' => 'Downloads op de achtergrond zijn geblokkeerd',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Downloads op de achtergrond kunnen beperkt zijn',
'downloads.backgroundWarning.sheetIntro' => 'Android verhindert dat Plezy betrouwbaar op de achtergrond downloadt.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Je apparaat beperkt wanneer Plezy op de achtergrond kan downloaden.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Het achtergrondgebruik van Plezy is beperkt. Stel het batterij- of achtergrondgebruik in op "Onbeperkt".',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android heeft Plezy in een beperkte stand-bymodus geplaatst. Stel het batterijgebruik in op "Onbeperkt".',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Downloadmeldingen zijn uitgeschakeld, waardoor voortgang en bediening mogelijk niet beschikbaar zijn.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Meldingen zijn uitgeschakeld. Op Android 13 of nieuwer zijn ze vereist voor langdurige downloads op de achtergrond.',
'downloads.backgroundWarning.reasonDataSaver' => 'Databesparing is ingeschakeld en blokkeert downloads op de achtergrond via mobiele data. Via Wi-Fi zouden downloads nog wel moeten werken.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Downloads zijn herhaaldelijk gestopt terwijl Plezy op de achtergrond draaide. Controleer de instellingen voor het batterij- of achtergrondgebruik van Plezy.',
'downloads.backgroundWarning.openSettings' => 'Instellingen openen',
'downloads.backgroundWarning.stillNotWorking' => 'Apparaatspecifieke hulp',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Bekijk de stappen voor je apparaat of stuur een logbestand vanuit Instellingen Logbestanden bekijken als het probleem aanhoudt.',
'downloads.backgroundWarning.dialogTitle' => 'Downloads worden mogelijk niet voltooid',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Toch downloaden',
'downloads.backgroundWarning.dialogFixFirst' => 'Dit eerst oplossen',
'downloads.backgroundWarning.statusTile' => 'Downloads op de achtergrond',
'downloads.backgroundWarning.statusOk' => 'Mag op de achtergrond worden uitgevoerd',
'downloads.backgroundWarning.statusBlocked' => 'Geblokkeerd door systeeminstellingen',
'downloads.backgroundWarning.statusDegraded' => 'Beperkt door systeeminstellingen',
'downloads.backgroundWarning.statusUnknown' => 'Nog niet gecontroleerd',
'downloads.backgroundWarning.settingsUnavailable' => 'Kan de systeeminstellingen niet openen op dit apparaat',
'downloads.backgroundWarning.linkUnavailable' => 'Kan dontkillmyapp.com niet openen op dit apparaat',
'shaders.title' => 'Shaders',
'shaders.noShaderDescription' => 'Geen videoverbetering',
'shaders.nvscalerDescription' => 'NVIDIA-beeldschaling voor scherpere video',
+62
View File
@@ -1459,6 +1459,7 @@ class _Translations$downloads$pl extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Niedostępne dla bieżącego profilu';
@override String get syncRuleUnknownServer => 'Nieznany serwer';
@override String get syncRuleListCreated => 'Utworzono regułę synchronizacji';
@override late final _Translations$downloads$backgroundWarning$pl backgroundWarning = _Translations$downloads$backgroundWarning$pl._(_root);
}
// Path: shaders
@@ -1989,6 +1990,41 @@ class _Translations$explore$status$pl extends Translations$explore$status$en {
@override String get upcoming => 'Nadchodzący';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$pl extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$pl._(TranslationsPl root) : this._root = root, super.internal(root);
final TranslationsPl _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'Po opuszczeniu aplikacji pobieranie zostanie zatrzymane';
@override String get bannerDegraded => 'Pobieranie w tle może być ograniczone';
@override String get bannerAction => 'Szczegóły';
@override String get sheetTitle => 'Pobieranie w tle jest zablokowane';
@override String get sheetTitleDegraded => 'Pobieranie w tle może być ograniczone';
@override String get sheetIntro => 'Android uniemożliwia Plezy niezawodne pobieranie plików w tle.';
@override String get sheetIntroDegraded => 'Twoje urządzenie ogranicza możliwość pobierania w tle przez Plezy.';
@override String get reasonBackgroundRestricted => 'Działanie Plezy w tle jest ograniczone. Ustaw użycie baterii lub działanie w tle na „Bez ograniczeń”.';
@override String get reasonStandbyRestricted => 'Android umieścił Plezy w ograniczonym trybie gotowości. Ustaw użycie baterii na „Bez ograniczeń”.';
@override String get reasonDownloadChannelBlocked => 'Powiadomienia o pobieraniu są wyłączone, więc postęp i opcje sterowania mogą być niedostępne.';
@override String get reasonNotificationsDisabled => 'Powiadomienia są wyłączone. W Android 13 lub nowszym są wymagane przy długim pobieraniu w tle.';
@override String get reasonDataSaver => 'Oszczędzanie danych jest włączone, co blokuje pobieranie w tle przez mobilną transmisję danych. Pobieranie powinno nadal działać przez Wi-Fi.';
@override String get reasonOemUnknown => 'Pobieranie wielokrotnie przerywało się, gdy Plezy działało w tle. Sprawdź ustawienia baterii lub działania w tle dla Plezy.';
@override String get openSettings => 'Otwórz ustawienia';
@override String get stillNotWorking => 'Pomoc dla Twojego urządzenia';
@override String get stillNotWorkingDescription => 'Zobacz instrukcje dla swojego urządzenia lub, jeśli problem nadal występuje, wyślij log przez Ustawienia Pokaż logi.';
@override String get dialogTitle => 'Pobieranie może się nie zakończyć';
@override String get dialogDownloadAnyway => 'Pobierz mimo to';
@override String get dialogFixFirst => 'Najpierw rozwiąż problem';
@override String get statusTile => 'Pobieranie w tle';
@override String get statusOk => 'Działanie w tle jest dozwolone';
@override String get statusBlocked => 'Zablokowane przez ustawienia systemu';
@override String get statusDegraded => 'Ograniczone przez ustawienia systemu';
@override String get statusUnknown => 'Jeszcze nie sprawdzono';
@override String get settingsUnavailable => 'Nie udało się otworzyć ustawień systemowych na tym urządzeniu';
@override String get linkUnavailable => 'Nie udało się otworzyć dontkillmyapp.com na tym urządzeniu';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$pl extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$pl._(TranslationsPl root) : this._root = root, super.internal(root);
@@ -3267,6 +3303,32 @@ extension on TranslationsPl {
'downloads.syncRuleNotAvailableForProfile' => 'Niedostępne dla bieżącego profilu',
'downloads.syncRuleUnknownServer' => 'Nieznany serwer',
'downloads.syncRuleListCreated' => 'Utworzono regułę synchronizacji',
'downloads.backgroundWarning.bannerBlocked' => 'Po opuszczeniu aplikacji pobieranie zostanie zatrzymane',
'downloads.backgroundWarning.bannerDegraded' => 'Pobieranie w tle może być ograniczone',
'downloads.backgroundWarning.bannerAction' => 'Szczegóły',
'downloads.backgroundWarning.sheetTitle' => 'Pobieranie w tle jest zablokowane',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Pobieranie w tle może być ograniczone',
'downloads.backgroundWarning.sheetIntro' => 'Android uniemożliwia Plezy niezawodne pobieranie plików w tle.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Twoje urządzenie ogranicza możliwość pobierania w tle przez Plezy.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Działanie Plezy w tle jest ograniczone. Ustaw użycie baterii lub działanie w tle na „Bez ograniczeń”.',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android umieścił Plezy w ograniczonym trybie gotowości. Ustaw użycie baterii na „Bez ograniczeń”.',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Powiadomienia o pobieraniu są wyłączone, więc postęp i opcje sterowania mogą być niedostępne.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Powiadomienia są wyłączone. W Android 13 lub nowszym są wymagane przy długim pobieraniu w tle.',
'downloads.backgroundWarning.reasonDataSaver' => 'Oszczędzanie danych jest włączone, co blokuje pobieranie w tle przez mobilną transmisję danych. Pobieranie powinno nadal działać przez Wi-Fi.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Pobieranie wielokrotnie przerywało się, gdy Plezy działało w tle. Sprawdź ustawienia baterii lub działania w tle dla Plezy.',
'downloads.backgroundWarning.openSettings' => 'Otwórz ustawienia',
'downloads.backgroundWarning.stillNotWorking' => 'Pomoc dla Twojego urządzenia',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Zobacz instrukcje dla swojego urządzenia lub, jeśli problem nadal występuje, wyślij log przez Ustawienia Pokaż logi.',
'downloads.backgroundWarning.dialogTitle' => 'Pobieranie może się nie zakończyć',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Pobierz mimo to',
'downloads.backgroundWarning.dialogFixFirst' => 'Najpierw rozwiąż problem',
'downloads.backgroundWarning.statusTile' => 'Pobieranie w tle',
'downloads.backgroundWarning.statusOk' => 'Działanie w tle jest dozwolone',
'downloads.backgroundWarning.statusBlocked' => 'Zablokowane przez ustawienia systemu',
'downloads.backgroundWarning.statusDegraded' => 'Ograniczone przez ustawienia systemu',
'downloads.backgroundWarning.statusUnknown' => 'Jeszcze nie sprawdzono',
'downloads.backgroundWarning.settingsUnavailable' => 'Nie udało się otworzyć ustawień systemowych na tym urządzeniu',
'downloads.backgroundWarning.linkUnavailable' => 'Nie udało się otworzyć dontkillmyapp.com na tym urządzeniu',
'shaders.title' => 'Shadery',
'shaders.noShaderDescription' => 'Bez ulepszenia wideo',
'shaders.nvscalerDescription' => 'Skalowanie obrazu NVIDIA dla ostrzejszego wideo',
+62
View File
@@ -1453,6 +1453,7 @@ class _Translations$downloads$pt extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Indisponível para o perfil atual';
@override String get syncRuleUnknownServer => 'Servidor desconhecido';
@override String get syncRuleListCreated => 'Regra de sincronização criada';
@override late final _Translations$downloads$backgroundWarning$pt backgroundWarning = _Translations$downloads$backgroundWarning$pt._(_root);
}
// Path: shaders
@@ -1983,6 +1984,41 @@ class _Translations$explore$status$pt extends Translations$explore$status$en {
@override String get upcoming => 'Em breve';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$pt extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$pt._(TranslationsPt root) : this._root = root, super.internal(root);
final TranslationsPt _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'Os downloads serão interrompidos ao sair do app';
@override String get bannerDegraded => 'Os downloads em segundo plano podem ser limitados';
@override String get bannerAction => 'Detalhes';
@override String get sheetTitle => 'Os downloads em segundo plano estão bloqueados';
@override String get sheetTitleDegraded => 'Os downloads em segundo plano podem ser limitados';
@override String get sheetIntro => 'O Android está impedindo que o Plezy faça downloads de forma confiável em segundo plano.';
@override String get sheetIntroDegraded => 'Seu dispositivo está limitando quando o Plezy pode fazer downloads em segundo plano.';
@override String get reasonBackgroundRestricted => 'O uso em segundo plano do Plezy está restrito. Defina o uso da bateria ou o uso em segundo plano como "Sem restrições".';
@override String get reasonStandbyRestricted => 'O Android colocou o Plezy em um modo de espera restrito. Defina o uso da bateria como "Sem restrições".';
@override String get reasonDownloadChannelBlocked => 'As notificações de download estão desativadas; por isso, o progresso e os controles podem ficar indisponíveis.';
@override String get reasonNotificationsDisabled => 'As notificações estão desativadas. No Android 13 ou mais recente, elas são necessárias para downloads longos em segundo plano.';
@override String get reasonDataSaver => 'A Economia de dados está ativada e bloqueia downloads em segundo plano usando dados móveis. Os downloads ainda devem funcionar no Wi-Fi.';
@override String get reasonOemUnknown => 'Os downloads foram interrompidos várias vezes enquanto o Plezy estava em segundo plano. Verifique as configurações de bateria ou uso em segundo plano do Plezy.';
@override String get openSettings => 'Abrir configurações';
@override String get stillNotWorking => 'Ajuda específica para o dispositivo';
@override String get stillNotWorkingDescription => 'Veja as instruções para seu dispositivo ou, se o problema persistir, envie um log em Configurações Ver Logs.';
@override String get dialogTitle => 'Os downloads podem não ser concluídos';
@override String get dialogDownloadAnyway => 'Baixar mesmo assim';
@override String get dialogFixFirst => 'Corrigir primeiro';
@override String get statusTile => 'Downloads em segundo plano';
@override String get statusOk => 'Execução em segundo plano permitida';
@override String get statusBlocked => 'Bloqueado pelas configurações do sistema';
@override String get statusDegraded => 'Limitado pelas configurações do sistema';
@override String get statusUnknown => 'Ainda não verificado';
@override String get settingsUnavailable => 'Não foi possível abrir as configurações do sistema neste dispositivo';
@override String get linkUnavailable => 'Não foi possível abrir dontkillmyapp.com neste dispositivo';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$pt extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$pt._(TranslationsPt root) : this._root = root, super.internal(root);
@@ -3261,6 +3297,32 @@ extension on TranslationsPt {
'downloads.syncRuleNotAvailableForProfile' => 'Indisponível para o perfil atual',
'downloads.syncRuleUnknownServer' => 'Servidor desconhecido',
'downloads.syncRuleListCreated' => 'Regra de sincronização criada',
'downloads.backgroundWarning.bannerBlocked' => 'Os downloads serão interrompidos ao sair do app',
'downloads.backgroundWarning.bannerDegraded' => 'Os downloads em segundo plano podem ser limitados',
'downloads.backgroundWarning.bannerAction' => 'Detalhes',
'downloads.backgroundWarning.sheetTitle' => 'Os downloads em segundo plano estão bloqueados',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Os downloads em segundo plano podem ser limitados',
'downloads.backgroundWarning.sheetIntro' => 'O Android está impedindo que o Plezy faça downloads de forma confiável em segundo plano.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Seu dispositivo está limitando quando o Plezy pode fazer downloads em segundo plano.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'O uso em segundo plano do Plezy está restrito. Defina o uso da bateria ou o uso em segundo plano como "Sem restrições".',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'O Android colocou o Plezy em um modo de espera restrito. Defina o uso da bateria como "Sem restrições".',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'As notificações de download estão desativadas; por isso, o progresso e os controles podem ficar indisponíveis.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'As notificações estão desativadas. No Android 13 ou mais recente, elas são necessárias para downloads longos em segundo plano.',
'downloads.backgroundWarning.reasonDataSaver' => 'A Economia de dados está ativada e bloqueia downloads em segundo plano usando dados móveis. Os downloads ainda devem funcionar no Wi-Fi.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Os downloads foram interrompidos várias vezes enquanto o Plezy estava em segundo plano. Verifique as configurações de bateria ou uso em segundo plano do Plezy.',
'downloads.backgroundWarning.openSettings' => 'Abrir configurações',
'downloads.backgroundWarning.stillNotWorking' => 'Ajuda específica para o dispositivo',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Veja as instruções para seu dispositivo ou, se o problema persistir, envie um log em Configurações Ver Logs.',
'downloads.backgroundWarning.dialogTitle' => 'Os downloads podem não ser concluídos',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Baixar mesmo assim',
'downloads.backgroundWarning.dialogFixFirst' => 'Corrigir primeiro',
'downloads.backgroundWarning.statusTile' => 'Downloads em segundo plano',
'downloads.backgroundWarning.statusOk' => 'Execução em segundo plano permitida',
'downloads.backgroundWarning.statusBlocked' => 'Bloqueado pelas configurações do sistema',
'downloads.backgroundWarning.statusDegraded' => 'Limitado pelas configurações do sistema',
'downloads.backgroundWarning.statusUnknown' => 'Ainda não verificado',
'downloads.backgroundWarning.settingsUnavailable' => 'Não foi possível abrir as configurações do sistema neste dispositivo',
'downloads.backgroundWarning.linkUnavailable' => 'Não foi possível abrir dontkillmyapp.com neste dispositivo',
'shaders.title' => 'Shaders',
'shaders.noShaderDescription' => 'Sem aprimoramento de vídeo',
'shaders.nvscalerDescription' => 'Dimensionamento de imagem da NVIDIA para vídeos mais nítidos',
+62
View File
@@ -1459,6 +1459,7 @@ class _Translations$downloads$ru extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Недоступно для текущего профиля';
@override String get syncRuleUnknownServer => 'Неизвестный сервер';
@override String get syncRuleListCreated => 'Правило синхронизации создано';
@override late final _Translations$downloads$backgroundWarning$ru backgroundWarning = _Translations$downloads$backgroundWarning$ru._(_root);
}
// Path: shaders
@@ -1989,6 +1990,41 @@ class _Translations$explore$status$ru extends Translations$explore$status$en {
@override String get upcoming => 'Скоро';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$ru extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$ru._(TranslationsRu root) : this._root = root, super.internal(root);
final TranslationsRu _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'Загрузки остановятся, когда вы выйдете из приложения';
@override String get bannerDegraded => 'Фоновые загрузки могут быть ограничены';
@override String get bannerAction => 'Подробнее';
@override String get sheetTitle => 'Фоновые загрузки заблокированы';
@override String get sheetTitleDegraded => 'Фоновые загрузки могут быть ограничены';
@override String get sheetIntro => 'Android не позволяет Plezy стабильно выполнять загрузки в фоновом режиме.';
@override String get sheetIntroDegraded => 'Ваше устройство ограничивает время, когда Plezy может выполнять загрузки в фоновом режиме.';
@override String get reasonBackgroundRestricted => 'Фоновая работа Plezy ограничена. В настройках расхода заряда батареи или фоновой работы выберите «Без ограничений».';
@override String get reasonStandbyRestricted => 'Android перевёл Plezy в режим ожидания с ограничениями. Для расхода заряда батареи выберите «Без ограничений».';
@override String get reasonDownloadChannelBlocked => 'Уведомления о загрузках отключены, поэтому ход загрузок и элементы управления могут быть недоступны.';
@override String get reasonNotificationsDisabled => 'Уведомления отключены. В Android 13 и более новых версиях они необходимы для длительных фоновых загрузок.';
@override String get reasonDataSaver => 'Экономия трафика включена, поэтому фоновые загрузки через мобильную сеть заблокированы. Загрузки по Wi-Fi должны работать.';
@override String get reasonOemUnknown => 'Загрузки неоднократно останавливались, пока Plezy работал в фоне. Проверьте настройки расхода заряда батареи или фоновой работы для Plezy.';
@override String get openSettings => 'Открыть настройки';
@override String get stillNotWorking => 'Помощь для вашего устройства';
@override String get stillNotWorkingDescription => 'Посмотрите инструкции для своего устройства. Если проблема не исчезнет, отправьте лог через Настройки › Просмотр логов.';
@override String get dialogTitle => 'Загрузки могут не завершиться';
@override String get dialogDownloadAnyway => 'Всё равно скачать';
@override String get dialogFixFirst => 'Сначала исправить';
@override String get statusTile => 'Фоновые загрузки';
@override String get statusOk => 'Работа в фоне разрешена';
@override String get statusBlocked => 'Заблокировано настройками системы';
@override String get statusDegraded => 'Ограничено настройками системы';
@override String get statusUnknown => 'Ещё не проверено';
@override String get settingsUnavailable => 'Не удалось открыть системные настройки на этом устройстве';
@override String get linkUnavailable => 'Не удалось открыть dontkillmyapp.com на этом устройстве';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$ru extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$ru._(TranslationsRu root) : this._root = root, super.internal(root);
@@ -3267,6 +3303,32 @@ extension on TranslationsRu {
'downloads.syncRuleNotAvailableForProfile' => 'Недоступно для текущего профиля',
'downloads.syncRuleUnknownServer' => 'Неизвестный сервер',
'downloads.syncRuleListCreated' => 'Правило синхронизации создано',
'downloads.backgroundWarning.bannerBlocked' => 'Загрузки остановятся, когда вы выйдете из приложения',
'downloads.backgroundWarning.bannerDegraded' => 'Фоновые загрузки могут быть ограничены',
'downloads.backgroundWarning.bannerAction' => 'Подробнее',
'downloads.backgroundWarning.sheetTitle' => 'Фоновые загрузки заблокированы',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Фоновые загрузки могут быть ограничены',
'downloads.backgroundWarning.sheetIntro' => 'Android не позволяет Plezy стабильно выполнять загрузки в фоновом режиме.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Ваше устройство ограничивает время, когда Plezy может выполнять загрузки в фоновом режиме.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Фоновая работа Plezy ограничена. В настройках расхода заряда батареи или фоновой работы выберите «Без ограничений».',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android перевёл Plezy в режим ожидания с ограничениями. Для расхода заряда батареи выберите «Без ограничений».',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Уведомления о загрузках отключены, поэтому ход загрузок и элементы управления могут быть недоступны.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Уведомления отключены. В Android 13 и более новых версиях они необходимы для длительных фоновых загрузок.',
'downloads.backgroundWarning.reasonDataSaver' => 'Экономия трафика включена, поэтому фоновые загрузки через мобильную сеть заблокированы. Загрузки по Wi-Fi должны работать.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Загрузки неоднократно останавливались, пока Plezy работал в фоне. Проверьте настройки расхода заряда батареи или фоновой работы для Plezy.',
'downloads.backgroundWarning.openSettings' => 'Открыть настройки',
'downloads.backgroundWarning.stillNotWorking' => 'Помощь для вашего устройства',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Посмотрите инструкции для своего устройства. Если проблема не исчезнет, отправьте лог через Настройки › Просмотр логов.',
'downloads.backgroundWarning.dialogTitle' => 'Загрузки могут не завершиться',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Всё равно скачать',
'downloads.backgroundWarning.dialogFixFirst' => 'Сначала исправить',
'downloads.backgroundWarning.statusTile' => 'Фоновые загрузки',
'downloads.backgroundWarning.statusOk' => 'Работа в фоне разрешена',
'downloads.backgroundWarning.statusBlocked' => 'Заблокировано настройками системы',
'downloads.backgroundWarning.statusDegraded' => 'Ограничено настройками системы',
'downloads.backgroundWarning.statusUnknown' => 'Ещё не проверено',
'downloads.backgroundWarning.settingsUnavailable' => 'Не удалось открыть системные настройки на этом устройстве',
'downloads.backgroundWarning.linkUnavailable' => 'Не удалось открыть dontkillmyapp.com на этом устройстве',
'shaders.title' => 'Шейдеры',
'shaders.noShaderDescription' => 'Без улучшения видео',
'shaders.nvscalerDescription' => 'Масштабирование NVIDIA для более чёткого видео',
+62
View File
@@ -1453,6 +1453,7 @@ class _Translations$downloads$sv extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => 'Inte tillgänglig för aktuell profil';
@override String get syncRuleUnknownServer => 'Okänd server';
@override String get syncRuleListCreated => 'Synkroniseringsregel skapad';
@override late final _Translations$downloads$backgroundWarning$sv backgroundWarning = _Translations$downloads$backgroundWarning$sv._(_root);
}
// Path: shaders
@@ -1983,6 +1984,41 @@ class _Translations$explore$status$sv extends Translations$explore$status$en {
@override String get upcoming => 'Kommande';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$sv extends Translations$downloads$backgroundWarning$en {
_Translations$downloads$backgroundWarning$sv._(TranslationsSv root) : this._root = root, super.internal(root);
final TranslationsSv _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => 'Nedladdningar stoppas när du lämnar appen';
@override String get bannerDegraded => 'Bakgrundsnedladdningar kan begränsas';
@override String get bannerAction => 'Detaljer';
@override String get sheetTitle => 'Bakgrundsnedladdningar är blockerade';
@override String get sheetTitleDegraded => 'Bakgrundsnedladdningar kan begränsas';
@override String get sheetIntro => 'Android hindrar Plezy från att ladda ned tillförlitligt i bakgrunden.';
@override String get sheetIntroDegraded => 'Din enhet begränsar när Plezy kan ladda ned i bakgrunden.';
@override String get reasonBackgroundRestricted => 'Plezys bakgrundsanvändning är begränsad. Ställ in batteri- eller bakgrundsanvändningen på "Obegränsad".';
@override String get reasonStandbyRestricted => 'Android har satt Plezy i ett begränsat vänteläge. Ställ in batterianvändningen på "Obegränsad".';
@override String get reasonDownloadChannelBlocked => 'Aviseringar om nedladdningar är avstängda, så förlopp och kontroller kanske inte är tillgängliga.';
@override String get reasonNotificationsDisabled => 'Aviseringar är avstängda. På Android 13 eller senare krävs de för långa bakgrundsnedladdningar.';
@override String get reasonDataSaver => 'Databesparing är aktiverad, vilket blockerar bakgrundsnedladdningar via mobildata. Nedladdningar bör fortfarande fungera via Wi-Fi.';
@override String get reasonOemUnknown => 'Nedladdningar stoppades upprepade gånger när Plezy kördes i bakgrunden. Kontrollera Plezys inställningar för batteri- eller bakgrundsanvändning.';
@override String get openSettings => 'Öppna inställningar';
@override String get stillNotWorking => 'Enhetsspecifik hjälp';
@override String get stillNotWorkingDescription => 'Se anvisningar för din enhet eller skicka en logg från Inställningar Visa loggar om problemet kvarstår.';
@override String get dialogTitle => 'Nedladdningar kanske inte slutförs';
@override String get dialogDownloadAnyway => 'Ladda ned ändå';
@override String get dialogFixFirst => 'Åtgärda först';
@override String get statusTile => 'Bakgrundsnedladdningar';
@override String get statusOk => 'Får köras i bakgrunden';
@override String get statusBlocked => 'Blockeras av systeminställningar';
@override String get statusDegraded => 'Begränsas av systeminställningar';
@override String get statusUnknown => 'Inte kontrollerat än';
@override String get settingsUnavailable => 'Det gick inte att öppna systeminställningarna på den här enheten';
@override String get linkUnavailable => 'Det gick inte att öppna dontkillmyapp.com på den här enheten';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$sv extends Translations$companionRemote$session$en {
_Translations$companionRemote$session$sv._(TranslationsSv root) : this._root = root, super.internal(root);
@@ -3261,6 +3297,32 @@ extension on TranslationsSv {
'downloads.syncRuleNotAvailableForProfile' => 'Inte tillgänglig för aktuell profil',
'downloads.syncRuleUnknownServer' => 'Okänd server',
'downloads.syncRuleListCreated' => 'Synkroniseringsregel skapad',
'downloads.backgroundWarning.bannerBlocked' => 'Nedladdningar stoppas när du lämnar appen',
'downloads.backgroundWarning.bannerDegraded' => 'Bakgrundsnedladdningar kan begränsas',
'downloads.backgroundWarning.bannerAction' => 'Detaljer',
'downloads.backgroundWarning.sheetTitle' => 'Bakgrundsnedladdningar är blockerade',
'downloads.backgroundWarning.sheetTitleDegraded' => 'Bakgrundsnedladdningar kan begränsas',
'downloads.backgroundWarning.sheetIntro' => 'Android hindrar Plezy från att ladda ned tillförlitligt i bakgrunden.',
'downloads.backgroundWarning.sheetIntroDegraded' => 'Din enhet begränsar när Plezy kan ladda ned i bakgrunden.',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Plezys bakgrundsanvändning är begränsad. Ställ in batteri- eller bakgrundsanvändningen på "Obegränsad".',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android har satt Plezy i ett begränsat vänteläge. Ställ in batterianvändningen på "Obegränsad".',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Aviseringar om nedladdningar är avstängda, så förlopp och kontroller kanske inte är tillgängliga.',
'downloads.backgroundWarning.reasonNotificationsDisabled' => 'Aviseringar är avstängda. På Android 13 eller senare krävs de för långa bakgrundsnedladdningar.',
'downloads.backgroundWarning.reasonDataSaver' => 'Databesparing är aktiverad, vilket blockerar bakgrundsnedladdningar via mobildata. Nedladdningar bör fortfarande fungera via Wi-Fi.',
'downloads.backgroundWarning.reasonOemUnknown' => 'Nedladdningar stoppades upprepade gånger när Plezy kördes i bakgrunden. Kontrollera Plezys inställningar för batteri- eller bakgrundsanvändning.',
'downloads.backgroundWarning.openSettings' => 'Öppna inställningar',
'downloads.backgroundWarning.stillNotWorking' => 'Enhetsspecifik hjälp',
'downloads.backgroundWarning.stillNotWorkingDescription' => 'Se anvisningar för din enhet eller skicka en logg från Inställningar Visa loggar om problemet kvarstår.',
'downloads.backgroundWarning.dialogTitle' => 'Nedladdningar kanske inte slutförs',
'downloads.backgroundWarning.dialogDownloadAnyway' => 'Ladda ned ändå',
'downloads.backgroundWarning.dialogFixFirst' => 'Åtgärda först',
'downloads.backgroundWarning.statusTile' => 'Bakgrundsnedladdningar',
'downloads.backgroundWarning.statusOk' => 'Får köras i bakgrunden',
'downloads.backgroundWarning.statusBlocked' => 'Blockeras av systeminställningar',
'downloads.backgroundWarning.statusDegraded' => 'Begränsas av systeminställningar',
'downloads.backgroundWarning.statusUnknown' => 'Inte kontrollerat än',
'downloads.backgroundWarning.settingsUnavailable' => 'Det gick inte att öppna systeminställningarna på den här enheten',
'downloads.backgroundWarning.linkUnavailable' => 'Det gick inte att öppna dontkillmyapp.com på den här enheten',
'shaders.title' => 'Shaders',
'shaders.noShaderDescription' => 'Ingen videoförbättring',
'shaders.nvscalerDescription' => 'NVIDIA-bildskalning för skarpare video',
+62
View File
@@ -1450,6 +1450,7 @@ class Translations$downloads$zh extends Translations$downloads$en {
@override String get syncRuleNotAvailableForProfile => '当前用户资料不可用';
@override String get syncRuleUnknownServer => '未知服务器';
@override String get syncRuleListCreated => '同步规则已创建';
@override late final Translations$downloads$backgroundWarning$zh backgroundWarning = Translations$downloads$backgroundWarning$zh.internal(_root);
}
// Path: shaders
@@ -1980,6 +1981,41 @@ class Translations$explore$status$zh extends Translations$explore$status$en {
@override String get upcoming => '即将上线';
}
// Path: downloads.backgroundWarning
class Translations$downloads$backgroundWarning$zh extends Translations$downloads$backgroundWarning$en {
Translations$downloads$backgroundWarning$zh.internal(TranslationsZh root) : this._root = root, super.internal(root);
final TranslationsZh _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => '离开应用后,下载将停止';
@override String get bannerDegraded => '后台下载可能受限';
@override String get bannerAction => '详情';
@override String get sheetTitle => '后台下载已被阻止';
@override String get sheetTitleDegraded => '后台下载可能受限';
@override String get sheetIntro => 'Android 正在阻止 Plezy 在后台稳定下载。';
@override String get sheetIntroDegraded => '你的设备限制了 Plezy 在后台下载的时机。';
@override String get reasonBackgroundRestricted => 'Plezy 的后台使用受到限制。请将其电池用量或后台使用设置为“不受限制”。';
@override String get reasonStandbyRestricted => 'Android 已将 Plezy 置于受限待机状态。请将其电池用量设为“不受限制”。';
@override String get reasonDownloadChannelBlocked => '下载通知已关闭,因此可能无法查看进度或进行控制。';
@override String get reasonNotificationsDisabled => '通知已关闭。在 Android 13 或更高版本中,长时间后台下载需要开启通知。';
@override String get reasonDataSaver => '流量节省程序已开启,会阻止使用移动数据进行后台下载。使用 Wi-Fi 时下载应仍可进行。';
@override String get reasonOemUnknown => 'Plezy 在后台时,下载曾多次停止。请检查 Plezy 的电池用量或后台使用设置。';
@override String get openSettings => '打开设置';
@override String get stillNotWorking => '设备专属帮助';
@override String get stillNotWorkingDescription => '查看适用于你设备的操作步骤;如果问题仍然存在,请通过设置 › 查看日志发送日志。';
@override String get dialogTitle => '下载可能无法完成';
@override String get dialogDownloadAnyway => '仍要下载';
@override String get dialogFixFirst => '先解决此问题';
@override String get statusTile => '后台下载';
@override String get statusOk => '允许在后台运行';
@override String get statusBlocked => '已被系统设置阻止';
@override String get statusDegraded => '受系统设置限制';
@override String get statusUnknown => '尚未检查';
@override String get settingsUnavailable => '无法在此设备上打开系统设置';
@override String get linkUnavailable => '无法在此设备上打开 dontkillmyapp.com';
}
// Path: companionRemote.session
class Translations$companionRemote$session$zh extends Translations$companionRemote$session$en {
Translations$companionRemote$session$zh.internal(TranslationsZh root) : this._root = root, super.internal(root);
@@ -3258,6 +3294,32 @@ extension on TranslationsZh {
'downloads.syncRuleNotAvailableForProfile' => '当前用户资料不可用',
'downloads.syncRuleUnknownServer' => '未知服务器',
'downloads.syncRuleListCreated' => '同步规则已创建',
'downloads.backgroundWarning.bannerBlocked' => '离开应用后,下载将停止',
'downloads.backgroundWarning.bannerDegraded' => '后台下载可能受限',
'downloads.backgroundWarning.bannerAction' => '详情',
'downloads.backgroundWarning.sheetTitle' => '后台下载已被阻止',
'downloads.backgroundWarning.sheetTitleDegraded' => '后台下载可能受限',
'downloads.backgroundWarning.sheetIntro' => 'Android 正在阻止 Plezy 在后台稳定下载。',
'downloads.backgroundWarning.sheetIntroDegraded' => '你的设备限制了 Plezy 在后台下载的时机。',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Plezy 的后台使用受到限制。请将其电池用量或后台使用设置为“不受限制”。',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android 已将 Plezy 置于受限待机状态。请将其电池用量设为“不受限制”。',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => '下载通知已关闭,因此可能无法查看进度或进行控制。',
'downloads.backgroundWarning.reasonNotificationsDisabled' => '通知已关闭。在 Android 13 或更高版本中,长时间后台下载需要开启通知。',
'downloads.backgroundWarning.reasonDataSaver' => '流量节省程序已开启,会阻止使用移动数据进行后台下载。使用 Wi-Fi 时下载应仍可进行。',
'downloads.backgroundWarning.reasonOemUnknown' => 'Plezy 在后台时,下载曾多次停止。请检查 Plezy 的电池用量或后台使用设置。',
'downloads.backgroundWarning.openSettings' => '打开设置',
'downloads.backgroundWarning.stillNotWorking' => '设备专属帮助',
'downloads.backgroundWarning.stillNotWorkingDescription' => '查看适用于你设备的操作步骤;如果问题仍然存在,请通过设置 › 查看日志发送日志。',
'downloads.backgroundWarning.dialogTitle' => '下载可能无法完成',
'downloads.backgroundWarning.dialogDownloadAnyway' => '仍要下载',
'downloads.backgroundWarning.dialogFixFirst' => '先解决此问题',
'downloads.backgroundWarning.statusTile' => '后台下载',
'downloads.backgroundWarning.statusOk' => '允许在后台运行',
'downloads.backgroundWarning.statusBlocked' => '已被系统设置阻止',
'downloads.backgroundWarning.statusDegraded' => '受系统设置限制',
'downloads.backgroundWarning.statusUnknown' => '尚未检查',
'downloads.backgroundWarning.settingsUnavailable' => '无法在此设备上打开系统设置',
'downloads.backgroundWarning.linkUnavailable' => '无法在此设备上打开 dontkillmyapp.com',
'shaders.title' => '着色器',
'shaders.noShaderDescription' => '无视频增强',
'shaders.nvscalerDescription' => 'NVIDIA 图像缩放,使视频更清晰',
+62
View File
@@ -1451,6 +1451,7 @@ class _Translations$downloads$zh_Hant extends Translations$downloads$zh {
@override String get syncRuleNotAvailableForProfile => '目前使用者設定檔無法使用';
@override String get syncRuleUnknownServer => '未知伺服器';
@override String get syncRuleListCreated => '同步規則已建立';
@override late final _Translations$downloads$backgroundWarning$zh_Hant backgroundWarning = _Translations$downloads$backgroundWarning$zh_Hant._(_root);
}
// Path: shaders
@@ -1981,6 +1982,41 @@ class _Translations$explore$status$zh_Hant extends Translations$explore$status$z
@override String get upcoming => '即將上線';
}
// Path: downloads.backgroundWarning
class _Translations$downloads$backgroundWarning$zh_Hant extends Translations$downloads$backgroundWarning$zh {
_Translations$downloads$backgroundWarning$zh_Hant._(TranslationsZhHant root) : this._root = root, super.internal(root);
final TranslationsZhHant _root; // ignore: unused_field
// Translations
@override String get bannerBlocked => '離開應用程式後,下載將會停止';
@override String get bannerDegraded => '背景下載可能受限';
@override String get bannerAction => '詳細資料';
@override String get sheetTitle => '背景下載遭到封鎖';
@override String get sheetTitleDegraded => '背景下載可能受限';
@override String get sheetIntro => 'Android 正在阻止 Plezy 在背景穩定下載。';
@override String get sheetIntroDegraded => '你的裝置限制了 Plezy 可在背景下載的時機。';
@override String get reasonBackgroundRestricted => 'Plezy 的背景使用受限。請將其電池用量或背景使用設定為「無限制」。';
@override String get reasonStandbyRestricted => 'Android 已將 Plezy 設為受限待命狀態。請將電池用量設為「無限制」。';
@override String get reasonDownloadChannelBlocked => '下載通知已關閉,因此可能無法查看進度或使用控制項。';
@override String get reasonNotificationsDisabled => '通知已關閉。在 Android 13 或更新版本中,長時間背景下載需要啟用通知。';
@override String get reasonDataSaver => '已開啟數據節省模式,因此系統會封鎖使用行動數據的背景下載。透過 Wi-Fi 下載應仍可正常執行。';
@override String get reasonOemUnknown => 'Plezy 在背景執行時,下載屢次停止。請檢查 Plezy 的電池用量或背景使用設定。';
@override String get openSettings => '開啟設定';
@override String get stillNotWorking => '裝置專屬說明';
@override String get stillNotWorkingDescription => '查看適用於你裝置的步驟;若問題持續發生,也可從設定 › 查看日誌傳送日誌。';
@override String get dialogTitle => '下載可能無法完成';
@override String get dialogDownloadAnyway => '仍要下載';
@override String get dialogFixFirst => '先修正設定';
@override String get statusTile => '背景下載';
@override String get statusOk => '可在背景執行';
@override String get statusBlocked => '遭系統設定封鎖';
@override String get statusDegraded => '受系統設定限制';
@override String get statusUnknown => '尚未檢查';
@override String get settingsUnavailable => '無法在這部裝置上開啟系統設定';
@override String get linkUnavailable => '無法在這部裝置上開啟 dontkillmyapp.com';
}
// Path: companionRemote.session
class _Translations$companionRemote$session$zh_Hant extends Translations$companionRemote$session$zh {
_Translations$companionRemote$session$zh_Hant._(TranslationsZhHant root) : this._root = root, super.internal(root);
@@ -3259,6 +3295,32 @@ extension on TranslationsZhHant {
'downloads.syncRuleNotAvailableForProfile' => '目前使用者設定檔無法使用',
'downloads.syncRuleUnknownServer' => '未知伺服器',
'downloads.syncRuleListCreated' => '同步規則已建立',
'downloads.backgroundWarning.bannerBlocked' => '離開應用程式後,下載將會停止',
'downloads.backgroundWarning.bannerDegraded' => '背景下載可能受限',
'downloads.backgroundWarning.bannerAction' => '詳細資料',
'downloads.backgroundWarning.sheetTitle' => '背景下載遭到封鎖',
'downloads.backgroundWarning.sheetTitleDegraded' => '背景下載可能受限',
'downloads.backgroundWarning.sheetIntro' => 'Android 正在阻止 Plezy 在背景穩定下載。',
'downloads.backgroundWarning.sheetIntroDegraded' => '你的裝置限制了 Plezy 可在背景下載的時機。',
'downloads.backgroundWarning.reasonBackgroundRestricted' => 'Plezy 的背景使用受限。請將其電池用量或背景使用設定為「無限制」。',
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android 已將 Plezy 設為受限待命狀態。請將電池用量設為「無限制」。',
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => '下載通知已關閉,因此可能無法查看進度或使用控制項。',
'downloads.backgroundWarning.reasonNotificationsDisabled' => '通知已關閉。在 Android 13 或更新版本中,長時間背景下載需要啟用通知。',
'downloads.backgroundWarning.reasonDataSaver' => '已開啟數據節省模式,因此系統會封鎖使用行動數據的背景下載。透過 Wi-Fi 下載應仍可正常執行。',
'downloads.backgroundWarning.reasonOemUnknown' => 'Plezy 在背景執行時,下載屢次停止。請檢查 Plezy 的電池用量或背景使用設定。',
'downloads.backgroundWarning.openSettings' => '開啟設定',
'downloads.backgroundWarning.stillNotWorking' => '裝置專屬說明',
'downloads.backgroundWarning.stillNotWorkingDescription' => '查看適用於你裝置的步驟;若問題持續發生,也可從設定 › 查看日誌傳送日誌。',
'downloads.backgroundWarning.dialogTitle' => '下載可能無法完成',
'downloads.backgroundWarning.dialogDownloadAnyway' => '仍要下載',
'downloads.backgroundWarning.dialogFixFirst' => '先修正設定',
'downloads.backgroundWarning.statusTile' => '背景下載',
'downloads.backgroundWarning.statusOk' => '可在背景執行',
'downloads.backgroundWarning.statusBlocked' => '遭系統設定封鎖',
'downloads.backgroundWarning.statusDegraded' => '受系統設定限制',
'downloads.backgroundWarning.statusUnknown' => '尚未檢查',
'downloads.backgroundWarning.settingsUnavailable' => '無法在這部裝置上開啟系統設定',
'downloads.backgroundWarning.linkUnavailable' => '無法在這部裝置上開啟 dontkillmyapp.com',
'shaders.title' => '著色器',
'shaders.noShaderDescription' => '無影片增強效果',
'shaders.nvscalerDescription' => 'NVIDIA 圖像縮放技術,使影片邊緣更清晰',
+29 -1
View File
@@ -1206,7 +1206,35 @@
"syncRuleSignInRequired": "Inloggning krävs",
"syncRuleNotAvailableForProfile": "Inte tillgänglig för aktuell profil",
"syncRuleUnknownServer": "Okänd server",
"syncRuleListCreated": "Synkroniseringsregel skapad"
"syncRuleListCreated": "Synkroniseringsregel skapad",
"backgroundWarning": {
"bannerBlocked": "Nedladdningar stoppas när du lämnar appen",
"bannerDegraded": "Bakgrundsnedladdningar kan begränsas",
"bannerAction": "Detaljer",
"sheetTitle": "Bakgrundsnedladdningar är blockerade",
"sheetTitleDegraded": "Bakgrundsnedladdningar kan begränsas",
"sheetIntro": "Android hindrar Plezy från att ladda ned tillförlitligt i bakgrunden.",
"sheetIntroDegraded": "Din enhet begränsar när Plezy kan ladda ned i bakgrunden.",
"reasonBackgroundRestricted": "Plezys bakgrundsanvändning är begränsad. Ställ in batteri- eller bakgrundsanvändningen på \"Obegränsad\".",
"reasonStandbyRestricted": "Android har satt Plezy i ett begränsat vänteläge. Ställ in batterianvändningen på \"Obegränsad\".",
"reasonDownloadChannelBlocked": "Aviseringar om nedladdningar är avstängda, så förlopp och kontroller kanske inte är tillgängliga.",
"reasonNotificationsDisabled": "Aviseringar är avstängda. På Android 13 eller senare krävs de för långa bakgrundsnedladdningar.",
"reasonDataSaver": "Databesparing är aktiverad, vilket blockerar bakgrundsnedladdningar via mobildata. Nedladdningar bör fortfarande fungera via Wi-Fi.",
"reasonOemUnknown": "Nedladdningar stoppades upprepade gånger när Plezy kördes i bakgrunden. Kontrollera Plezys inställningar för batteri- eller bakgrundsanvändning.",
"openSettings": "Öppna inställningar",
"stillNotWorking": "Enhetsspecifik hjälp",
"stillNotWorkingDescription": "Se anvisningar för din enhet eller skicka en logg från Inställningar Visa loggar om problemet kvarstår.",
"dialogTitle": "Nedladdningar kanske inte slutförs",
"dialogDownloadAnyway": "Ladda ned ändå",
"dialogFixFirst": "Åtgärda först",
"statusTile": "Bakgrundsnedladdningar",
"statusOk": "Får köras i bakgrunden",
"statusBlocked": "Blockeras av systeminställningar",
"statusDegraded": "Begränsas av systeminställningar",
"statusUnknown": "Inte kontrollerat än",
"settingsUnavailable": "Det gick inte att öppna systeminställningarna på den här enheten",
"linkUnavailable": "Det gick inte att öppna dontkillmyapp.com på den här enheten"
}
},
"shaders": {
"title": "Shaders",
+29 -1
View File
@@ -1203,7 +1203,35 @@
"syncRuleSignInRequired": "需要登入",
"syncRuleNotAvailableForProfile": "目前使用者設定檔無法使用",
"syncRuleUnknownServer": "未知伺服器",
"syncRuleListCreated": "同步規則已建立"
"syncRuleListCreated": "同步規則已建立",
"backgroundWarning": {
"bannerBlocked": "離開應用程式後,下載將會停止",
"bannerDegraded": "背景下載可能受限",
"bannerAction": "詳細資料",
"sheetTitle": "背景下載遭到封鎖",
"sheetTitleDegraded": "背景下載可能受限",
"sheetIntro": "Android 正在阻止 Plezy 在背景穩定下載。",
"sheetIntroDegraded": "你的裝置限制了 Plezy 可在背景下載的時機。",
"reasonBackgroundRestricted": "Plezy 的背景使用受限。請將其電池用量或背景使用設定為「無限制」。",
"reasonStandbyRestricted": "Android 已將 Plezy 設為受限待命狀態。請將電池用量設為「無限制」。",
"reasonDownloadChannelBlocked": "下載通知已關閉,因此可能無法查看進度或使用控制項。",
"reasonNotificationsDisabled": "通知已關閉。在 Android 13 或更新版本中,長時間背景下載需要啟用通知。",
"reasonDataSaver": "已開啟數據節省模式,因此系統會封鎖使用行動數據的背景下載。透過 Wi-Fi 下載應仍可正常執行。",
"reasonOemUnknown": "Plezy 在背景執行時,下載屢次停止。請檢查 Plezy 的電池用量或背景使用設定。",
"openSettings": "開啟設定",
"stillNotWorking": "裝置專屬說明",
"stillNotWorkingDescription": "查看適用於你裝置的步驟;若問題持續發生,也可從設定 › 查看日誌傳送日誌。",
"dialogTitle": "下載可能無法完成",
"dialogDownloadAnyway": "仍要下載",
"dialogFixFirst": "先修正設定",
"statusTile": "背景下載",
"statusOk": "可在背景執行",
"statusBlocked": "遭系統設定封鎖",
"statusDegraded": "受系統設定限制",
"statusUnknown": "尚未檢查",
"settingsUnavailable": "無法在這部裝置上開啟系統設定",
"linkUnavailable": "無法在這部裝置上開啟 dontkillmyapp.com"
}
},
"shaders": {
"title": "著色器",
+29 -1
View File
@@ -1203,7 +1203,35 @@
"syncRuleSignInRequired": "需要登录",
"syncRuleNotAvailableForProfile": "当前用户资料不可用",
"syncRuleUnknownServer": "未知服务器",
"syncRuleListCreated": "同步规则已创建"
"syncRuleListCreated": "同步规则已创建",
"backgroundWarning": {
"bannerBlocked": "离开应用后,下载将停止",
"bannerDegraded": "后台下载可能受限",
"bannerAction": "详情",
"sheetTitle": "后台下载已被阻止",
"sheetTitleDegraded": "后台下载可能受限",
"sheetIntro": "Android 正在阻止 Plezy 在后台稳定下载。",
"sheetIntroDegraded": "你的设备限制了 Plezy 在后台下载的时机。",
"reasonBackgroundRestricted": "Plezy 的后台使用受到限制。请将其电池用量或后台使用设置为“不受限制”。",
"reasonStandbyRestricted": "Android 已将 Plezy 置于受限待机状态。请将其电池用量设为“不受限制”。",
"reasonDownloadChannelBlocked": "下载通知已关闭,因此可能无法查看进度或进行控制。",
"reasonNotificationsDisabled": "通知已关闭。在 Android 13 或更高版本中,长时间后台下载需要开启通知。",
"reasonDataSaver": "流量节省程序已开启,会阻止使用移动数据进行后台下载。使用 Wi-Fi 时下载应仍可进行。",
"reasonOemUnknown": "Plezy 在后台时,下载曾多次停止。请检查 Plezy 的电池用量或后台使用设置。",
"openSettings": "打开设置",
"stillNotWorking": "设备专属帮助",
"stillNotWorkingDescription": "查看适用于你设备的操作步骤;如果问题仍然存在,请通过设置 › 查看日志发送日志。",
"dialogTitle": "下载可能无法完成",
"dialogDownloadAnyway": "仍要下载",
"dialogFixFirst": "先解决此问题",
"statusTile": "后台下载",
"statusOk": "允许在后台运行",
"statusBlocked": "已被系统设置阻止",
"statusDegraded": "受系统设置限制",
"statusUnknown": "尚未检查",
"settingsUnavailable": "无法在此设备上打开系统设置",
"linkUnavailable": "无法在此设备上打开 dontkillmyapp.com"
}
},
"shaders": {
"title": "着色器",
+87 -4
View File
@@ -13,6 +13,7 @@ import '../models/download_models.dart';
import '../utils/download_version_utils.dart';
import '../database/app_database.dart';
import '../database/download_operations.dart';
import '../services/background_work_diagnostics_service.dart';
import '../services/download_manager_service.dart';
import '../services/api_cache.dart';
import '../services/download_artwork_service.dart';
@@ -28,6 +29,7 @@ import '../utils/deletion_notifier.dart';
import '../utils/downloaded_version_match.dart';
import '../media/episode_collection.dart';
import '../utils/global_key_utils.dart';
import '../utils/notification_permission.dart';
import '../utils/watch_state_notifier.dart';
import '../mixins/disposable_change_notifier_mixin.dart';
@@ -105,6 +107,7 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
_activeProfileId == ownership.profileId && _profileGeneration == ownership.generation;
OfflineModeSource? _offlineSource;
int _networkStateGeneration = 0;
DownloadProvider({required this._downloadManager, required this._database})
: _syncRuleExecutor = SyncRuleExecutor(database: _database) {
@@ -117,6 +120,10 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
// Load persisted downloads from database
_initFuture = _loadPersistedDownloads();
// Lets the diagnostics service score whether downloads actually advance
// while the app is backgrounded, without it reaching into providers.
BackgroundWorkDiagnosticsService.instance.bindActivitySource(downloadActivitySnapshot);
}
/// Test-only constructor that skips the heavy initial load (artwork dir,
@@ -141,7 +148,12 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
/// the device has no Plex connectivity. Sync-rule execution receives a
/// snapshot of this state when invoked, keeping this provider as the owner.
void setOfflineSource(OfflineModeSource? source) {
_offlineSource = source;
if (!identical(_offlineSource, source)) {
_offlineSource?.removeListener(_onOfflineSourceChanged);
_offlineSource = source;
_offlineSource?.addListener(_onOfflineSourceChanged);
_networkStateGeneration++;
}
_downloadManager.setOfflineSource(source);
}
@@ -498,11 +510,32 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
void _onProgressUpdate(DownloadProgress progress) {
appLogger.d('Progress update received: ${progress.globalKey} - ${progress.status} - ${progress.progress}%');
final ownedByActiveProfile = _ownsDownloadKey(progress.globalKey);
_downloads[progress.globalKey] = progress;
final previous = _downloads[progress.globalKey];
final terminalUpdateOmittedBytes =
previous != null &&
progress.downloadedBytes == 0 &&
previous.downloadedBytes > 0 &&
switch (progress.status) {
DownloadStatus.completed ||
DownloadStatus.failed ||
DownloadStatus.cancelled ||
DownloadStatus.partial => true,
DownloadStatus.queued || DownloadStatus.downloading || DownloadStatus.paused => false,
};
// Terminal status-only events omit byte fields. Preserve only those omitted
// counters; live progress and explicit retry resets must remain authoritative.
final merged = terminalUpdateOmittedBytes
? progress.copyWith(
progress: progress.progress == 0 ? previous.progress : progress.progress,
downloadedBytes: previous.downloadedBytes,
totalBytes: progress.totalBytes == 0 ? previous.totalBytes : progress.totalBytes,
)
: progress;
_downloads[progress.globalKey] = merged;
// Sync artwork paths when they are available.
if (progress.hasArtworkPaths) {
_artworkPaths[progress.globalKey] = DownloadedArtwork(thumbPath: progress.thumbPath);
if (merged.hasArtworkPaths) {
_artworkPaths[merged.globalKey] = DownloadedArtwork(thumbPath: merged.thumbPath);
}
if (ownedByActiveProfile) safeNotifyListeners();
@@ -510,6 +543,8 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
@override
void dispose() {
BackgroundWorkDiagnosticsService.instance.unbindActivitySource(downloadActivitySnapshot);
_offlineSource?.removeListener(_onOfflineSourceChanged);
_progressSubscription?.cancel();
_deletionProgressSubscription?.cancel();
_metadataStore
@@ -519,6 +554,7 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
}
void _onMetadataStoreChanged() => safeNotifyListeners();
void _onOfflineSourceChanged() => _networkStateGeneration++;
/// Ensure metadata has a serverId, falling back to a parent's serverId.
MediaItem _ensureServerId(MediaItem metadata, String? fallbackServerId) =>
@@ -528,6 +564,49 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
Map<String, DownloadProgress> get downloads =>
Map.unmodifiable(Map.fromEntries(_downloads.entries.where(_ownsProgressEntry)));
/// Aggregate transfer activity for [BackgroundWorkDiagnosticsService].
///
/// Deliberately unfiltered by profile: the OS restricts the process, not a
/// profile. Byte and percentage totals are merged monotonically per row;
/// status counters distinguish a drained queue from a killed task.
///
/// A method rather than a getter so the tear-off handed to
/// [BackgroundWorkDiagnosticsService.bindActivitySource] compares equal at
/// unbind time.
DownloadActivitySnapshot downloadActivitySnapshot() {
var activeTasks = 0;
var completedTasks = 0;
var failedTasks = 0;
var downloadedBytes = 0;
var progressUnits = 0;
for (final progress in _downloads.values) {
switch (progress.status) {
case DownloadStatus.downloading:
activeTasks++;
case DownloadStatus.completed:
completedTasks++;
case DownloadStatus.failed:
failedTasks++;
case DownloadStatus.queued:
case DownloadStatus.paused:
case DownloadStatus.cancelled:
case DownloadStatus.partial:
break;
}
downloadedBytes += progress.downloadedBytes;
progressUnits += progress.progress;
}
return (
activeTasks: activeTasks,
completedTasks: completedTasks,
failedTasks: failedTasks,
downloadedBytes: downloadedBytes,
progressUnits: progressUnits,
networkAvailable: _offlineSource == null ? null : !_offlineSource!.isOffline,
networkStateGeneration: _networkStateGeneration,
);
}
/// All metadata for downloads
Map<String, MediaItem> get metadata => _metadataStore.resolvedItems;
@@ -996,6 +1075,10 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
throw CellularDownloadBlockedException();
}
if (!_isQueueOwnershipCurrent(ownership)) return 0;
// The queueing claim above remains held while Android's permission
// dialog is open, so a second tap cannot launch duplicate expansion.
await NotificationPermission.ensure();
if (!_isQueueOwnershipCurrent(ownership)) return 0;
if (metadata.isMovie || metadata.isEpisode || metadata.kind == MediaKind.track) {
final queued = await _queueSingleDownload(
@@ -11,7 +11,9 @@ import '../../providers/multi_server_provider.dart';
import '../../services/music/music_playback_service.dart';
import '../../theme/mono_tokens.dart';
import '../../utils/music_navigation.dart';
import '../../models/download_models.dart';
import '../../widgets/app_icon.dart';
import '../../widgets/background_download_warning_banner.dart';
import '../../widgets/focusable_tab_chip.dart';
import '../../widgets/music/mini_player.dart';
import '../../widgets/music/track_row.dart';
@@ -165,6 +167,16 @@ class DownloadsScreenState extends State<DownloadsScreen>
SliverFillRemaining(
child: Column(
children: [
// Only while something is actually queued or transferring —
// an idle Downloads screen has nothing to warn about.
Selector<DownloadProvider, bool>(
selector: (_, provider) => provider.downloads.values.any(
(progress) =>
progress.status == DownloadStatus.downloading || progress.status == DownloadStatus.queued,
),
builder: (context, hasPendingDownloads, _) =>
BackgroundDownloadWarningBanner(hasPendingDownloads: hasPendingDownloads),
),
// Tab selector chips (only on mobile - desktop has them in app bar)
if (!PlatformDetector.shouldUseSideNavigation(context))
Container(
@@ -473,6 +473,10 @@ extension _MediaDetailActionButtons on _MediaDetailScreenState {
}
if (progress?.status == DownloadStatus.failed) {
// A failed download is the likeliest moment for the restriction to be
// the actual cause, so check before spending another attempt on it.
if (!await confirmBackgroundDownloadRestrictions(context) || !mounted) return;
final client = _getMediaClientForMetadata(context);
if (client == null) return;
@@ -502,6 +506,7 @@ extension _MediaDetailActionButtons on _MediaDetailScreenState {
await downloadProvider.deleteDownload(globalKey);
if (mounted) showSuccessSnackBar(context, t.downloads.downloadDeleted);
} else if (retry && mounted) {
if (!await confirmBackgroundDownloadRestrictions(context) || !mounted) return;
final client = _getMediaClientForMetadata(context);
if (client == null) return;
@@ -524,6 +529,7 @@ extension _MediaDetailActionButtons on _MediaDetailScreenState {
await _showSyncRuleActions(context, downloadProvider, metadata, ruleKey: ruleKey, downloadGlobalKey: globalKey);
return;
}
if (!await confirmBackgroundDownloadRestrictions(context) || !mounted) return;
final client = _getMediaClientForMetadata(context);
if (client == null) return;
+1
View File
@@ -12,6 +12,7 @@ import '../services/fullscreen_state_manager.dart';
import 'package:flutter/services.dart';
import 'package:plezy/utils/platform_detector.dart';
import 'package:plezy/widgets/app_icon.dart';
import 'package:plezy/widgets/background_download_warning_banner.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import '../widgets/collapsible_text.dart';
@@ -25,6 +25,7 @@ import '../../utils/platform_detector.dart';
import '../../utils/provider_extensions.dart';
import '../../utils/snackbar_helper.dart';
import '../../widgets/app_icon.dart';
import '../../widgets/background_download_warning_banner.dart';
import '../../widgets/desktop_app_bar.dart';
import '../../widgets/download_status_icon.dart';
import '../../widgets/ios_status_bar_tap_scroll_to_top.dart';
@@ -178,6 +179,8 @@ class _AlbumDetailScreenState extends BaseMediaListDetailScreen<AlbumDetailScree
return;
}
if (!await confirmBackgroundDownloadRestrictions(context) || !mounted) return;
// Not downloaded (or partial/failed): queue the album — already-active
// tracks are skipped inside the provider, so this also fills gaps.
try {
+7
View File
@@ -17,6 +17,7 @@ import '../../i18n/strings.g.dart';
import '../../mixins/mounted_set_state_mixin.dart';
import '../../utils/dialogs.dart';
import '../../main.dart' show gitCommit;
import '../../services/background_work_diagnostics_service.dart';
import '../../services/device_performance.dart';
import '../../utils/app_logger.dart';
import '../../utils/formatters.dart';
@@ -106,6 +107,12 @@ class _LogsScreenState extends State<LogsScreen> with MountedSetStateMixin {
renderer = 'unknown';
}
buffer.writeln('Renderer: $renderer');
// "Are downloads allowed to run in the background" is the single most
// asked support question; answering it from the uploaded log turns a
// four-message thread into one reply.
final backgroundWork = BackgroundWorkDiagnosticsService.instance;
await backgroundWork.refresh();
buffer.writeln('Background: ${backgroundWork.describeSync()}');
} else if (Platform.isIOS) {
final info = await deviceInfo.iosInfo;
buffer.writeln('iOS ${info.systemVersion}');
+54
View File
@@ -29,7 +29,9 @@ import '../../providers/seerr_account_provider.dart';
import '../../providers/trackers_provider.dart';
import '../../providers/trakt_account_provider.dart';
import '../../services/keyboard_shortcuts_service.dart';
import '../../services/background_work_diagnostics_service.dart';
import '../../services/settings_service.dart' as settings;
import '../../widgets/background_download_warning_banner.dart';
import '../../services/update_service.dart';
import '../../utils/app_logger.dart';
import '../../utils/dialogs.dart';
@@ -63,6 +65,7 @@ class SettingsScreen extends StatefulWidget {
this.downloadDirectoryWritableChecker,
this.settingsExporter,
this.settingsImporter,
this.backgroundWorkDiagnosticsService,
});
@visibleForTesting
@@ -73,12 +76,16 @@ class SettingsScreen extends StatefulWidget {
@visibleForTesting
final Future<ImportResult?> Function()? settingsImporter;
@visibleForTesting
final BackgroundWorkDiagnosticsService? backgroundWorkDiagnosticsService;
@override
State<SettingsScreen> createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> with FocusableTab, MountedSetStateMixin {
BackgroundWorkDiagnosticsService get _backgroundWorkDiagnostics =>
widget.backgroundWorkDiagnosticsService ?? BackgroundWorkDiagnosticsService.instance;
late final FocusMemoryTracker _focusTracker;
// Focus tracking keys
@@ -90,6 +97,7 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab, Moun
static const _kDownloadLocation = 'download_location';
static const _kDownloadOnWifiOnly = 'download_on_wifi_only';
static const _kAutoRemoveWatchedDownloads = 'auto_remove_watched_downloads';
static const _kBackgroundDownloads = 'background_downloads';
static const _kVideoPlayerControls = 'video_player_controls';
static const _kVideoPlayerNavigation = 'video_player_navigation';
static const _kCrashReporting = 'crash_reporting';
@@ -376,10 +384,56 @@ class _SettingsScreenState extends State<SettingsScreen> with FocusableTab, Moun
title: t.settings.autoRemoveWatchedDownloads,
subtitle: t.settings.autoRemoveWatchedDownloadsDescription,
),
if (_backgroundWorkDiagnostics.isSupported) _buildBackgroundDownloadsTile(),
],
);
}
/// Standing answer to "why do my downloads stop when I leave the app" — also
/// what support can ask a user to read out without needing a log upload.
Widget _buildBackgroundDownloadsTile() {
final diagnostics = _backgroundWorkDiagnostics;
return ListenableBuilder(
listenable: diagnostics,
builder: (context, _) {
final status = diagnostics.status;
final scheme = Theme.of(context).colorScheme;
final (icon, color, summary) = switch (status) {
_ when !status.probed => (Symbols.help_rounded, null, t.downloads.backgroundWarning.statusUnknown),
_ when status.isBlocked => (
Symbols.battery_alert_rounded,
scheme.error,
t.downloads.backgroundWarning.statusBlocked,
),
_ when !status.isHealthy => (
Symbols.info_rounded,
scheme.tertiary,
t.downloads.backgroundWarning.statusDegraded,
),
_ => (Symbols.check_circle_rounded, null, t.downloads.backgroundWarning.statusOk),
};
return FocusableListTile(
focusNode: _focusTracker.get(_kBackgroundDownloads),
leading: AppIcon(icon, fill: 1, color: color),
title: Text(t.downloads.backgroundWarning.statusTile, style: settingsOptionTitleStyle(context)),
subtitle: Text(summary),
trailing: const AppIcon(Symbols.chevron_right_rounded, fill: 1),
onTap: () async {
await diagnostics.refresh();
if (!context.mounted) return;
if (diagnostics.status.isHealthy) {
showAppSnackBar(context, t.downloads.backgroundWarning.statusOk);
return;
}
await showBackgroundDownloadWarningDialog(context, service: diagnostics);
},
dense: false,
visualDensity: VisualDensity.standard,
);
},
);
}
Widget _buildKeyboardShortcutsSection() {
if (_keyboardService == null) return const SizedBox.shrink();
@@ -0,0 +1,517 @@
import 'dart:async';
import 'dart:io';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import '../mixins/disposable_change_notifier_mixin.dart';
import 'download_manager_service.dart';
import '../utils/app_logger.dart';
import '../utils/platform_detector.dart';
/// Why background downloads are expected to fail on this device.
///
/// Ordered by how actionable the remedy is; [BackgroundWorkStatus.reasons]
/// preserves the order the native classifier emits.
enum BackgroundWorkReason {
/// AOSP per-app "Restricted" background usage. Samsung's "Background usage
/// limits" (Sleeping apps / Deep sleeping apps) lands here too.
backgroundRestricted('background_restricted'),
/// App standby bucket is RESTRICTED or NEVER — deferred indefinitely.
standbyRestricted('standby_restricted'),
/// The downloader's notification channel is muted, which removes the
/// foreground-service anchor keeping downloads alive across screen-off.
downloadChannelBlocked('download_channel_blocked'),
/// Notifications are off app-wide.
notificationsDisabled('notifications_disabled'),
/// Data Saver blocks metered background traffic.
dataSaver('data_saver'),
/// No OS-visible restriction, but downloads empirically stop making progress
/// whenever the app is backgrounded. Covers OEMs that block background work
/// while reporting a clean bucket.
oemUnknown('oem_unknown');
const BackgroundWorkReason(this.id);
final String id;
static BackgroundWorkReason? fromId(String id) {
for (final reason in values) {
if (reason.id == id) return reason;
}
return null;
}
}
/// Severity of the detected restrictions.
enum BackgroundWorkVerdict {
/// Nothing detected, or the platform cannot tell us.
ok,
/// Downloads will run, but not under every network condition.
degraded,
/// Downloads are expected to stall the moment the app leaves the foreground.
blocked,
}
/// Settings screen a remedy button routes to. Mirrors `BackgroundSettingsTarget`.
enum BackgroundSettingsTarget {
appDetails('app_details'),
appNotifications('app_notifications'),
notificationChannel('notification_channel');
const BackgroundSettingsTarget(this.id);
final String id;
}
/// Immutable result of one probe.
@immutable
class BackgroundWorkStatus {
const BackgroundWorkStatus({
this.verdict = BackgroundWorkVerdict.ok,
this.reasons = const [],
this.standbyBucket,
this.ignoringBatteryOptimizations,
this.probed = false,
});
final BackgroundWorkVerdict verdict;
final List<BackgroundWorkReason> reasons;
final int? standbyBucket;
final bool? ignoringBatteryOptimizations;
/// False before the first successful native probe (and on every non-Android
/// platform), so callers can distinguish "healthy" from "never asked".
final bool probed;
bool get isBlocked => verdict == BackgroundWorkVerdict.blocked;
bool get isHealthy => verdict == BackgroundWorkVerdict.ok;
/// The reason a single-line summary should lead with, if any.
BackgroundWorkReason? get primaryReason => reasons.isEmpty ? null : reasons.first;
/// Which Settings screen best addresses [primaryReason].
BackgroundSettingsTarget get remedyTarget => switch (primaryReason) {
BackgroundWorkReason.notificationsDisabled => BackgroundSettingsTarget.appNotifications,
BackgroundWorkReason.downloadChannelBlocked => BackgroundSettingsTarget.notificationChannel,
_ => BackgroundSettingsTarget.appDetails,
};
BackgroundWorkStatus withReason(BackgroundWorkReason reason) {
if (reasons.contains(reason)) return this;
return BackgroundWorkStatus(
verdict: BackgroundWorkVerdict.blocked,
reasons: [...reasons, reason],
standbyBucket: standbyBucket,
ignoringBatteryOptimizations: ignoringBatteryOptimizations,
probed: probed,
);
}
/// Compact form for the log upload header — the whole point of Phase 2 is
/// that a support thread can be answered from one uploaded log.
String describe() {
if (!probed) return 'unknown';
final parts = <String>[
verdict.name,
if (reasons.isNotEmpty) reasons.map((r) => r.id).join('+'),
if (standbyBucket != null) 'bucket:$standbyBucket',
if (ignoringBatteryOptimizations != null) 'batteryWhitelist:$ignoringBatteryOptimizations',
];
return parts.join(' ');
}
@override
bool operator ==(Object other) =>
other is BackgroundWorkStatus &&
other.verdict == verdict &&
listEquals(other.reasons, reasons) &&
other.standbyBucket == standbyBucket &&
other.ignoringBatteryOptimizations == ignoringBatteryOptimizations &&
other.probed == probed;
@override
int get hashCode =>
Object.hash(verdict, Object.hashAll(reasons), standbyBucket, ignoringBatteryOptimizations, probed);
}
/// Aggregate process-wide download activity at a single instant.
typedef DownloadActivitySnapshot = ({
int activeTasks,
int completedTasks,
int failedTasks,
int downloadedBytes,
int progressUnits,
bool? networkAvailable,
int networkStateGeneration,
});
typedef DownloadStallObservation = ({
DownloadActivitySnapshot paused,
DownloadActivitySnapshot resumed,
Duration backgroundGap,
});
/// Detects OEMs that silently kill background work while reporting a clean
/// standby bucket (Xiaomi, Oppo, Vivo, Huawei), by watching whether downloads
/// actually advance while the app sits in the background.
///
/// Pure and clock-injected so the whole state machine is unit-testable.
class DownloadStallDetector {
DownloadStallDetector({
this.minimumBackgroundGap = const Duration(seconds: 60),
this.minimumProgressBytes = 64 * 1024,
this.observationsToConfirm = 2,
});
/// Shorter background trips prove nothing — the OS grants a grace period
/// before it starts deferring work.
final Duration minimumBackgroundGap;
/// Below this, "progress" is indistinguishable from a buffer flush.
final int minimumProgressBytes;
/// Two independent observations before accusing the OEM, so one flaky
/// network blip cannot produce a false warning.
final int observationsToConfirm;
DownloadActivitySnapshot? _pausedSnapshot;
DateTime? _pausedAt;
int _consecutiveStalls = 0;
int get consecutiveStalls => _consecutiveStalls;
bool get isStalled => _consecutiveStalls >= observationsToConfirm;
/// Arms the detector. Only meaningful while something is actually downloading.
void onPaused(DownloadActivitySnapshot snapshot, DateTime at) {
if (snapshot.activeTasks <= 0) {
_clearArmedSnapshot();
return;
}
_pausedSnapshot = snapshot;
_pausedAt = at;
}
/// Captures the resume state synchronously, before any awaited platform or
/// settings work can allow later progress to leak into this observation.
DownloadStallObservation? captureResumed(DownloadActivitySnapshot snapshot, DateTime at) {
final pausedSnapshot = _pausedSnapshot;
final pausedAt = _pausedAt;
_clearArmedSnapshot();
if (pausedSnapshot == null || pausedAt == null) return null;
return (paused: pausedSnapshot, resumed: snapshot, backgroundGap: at.difference(pausedAt));
}
/// Scores a captured background trip. Returns true only when this trip has
/// just confirmed a repeated OEM stall.
bool evaluate(DownloadStallObservation? observation, {required bool networkAvailable}) {
if (observation == null) return false;
final pausedSnapshot = observation.paused;
final snapshot = observation.resumed;
final completedInBackground = snapshot.completedTasks > pausedSnapshot.completedTasks;
final byteDelta = snapshot.downloadedBytes - pausedSnapshot.downloadedBytes;
final progressDelta = snapshot.progressUnits - pausedSnapshot.progressUnits;
// Real transfer progress clears prior evidence even when the network or
// policy check below is inconclusive. A terminal event may omit byte data,
// so completion and percentage progress are independent evidence.
if (completedInBackground || byteDelta >= minimumProgressBytes || progressDelta > 0) {
_consecutiveStalls = 0;
return false;
}
// Removed rows and other backwards counters are not evidence of a stall.
if (byteDelta < 0 || progressDelta < 0) return false;
if (observation.backgroundGap < minimumBackgroundGap) return false;
// Require known-good endpoints, no network/server transition anywhere in
// the background window, and an eligible download policy at resume.
if (!networkAvailable ||
pausedSnapshot.networkAvailable != true ||
snapshot.networkAvailable != true ||
snapshot.networkStateGeneration != pausedSnapshot.networkStateGeneration) {
return false;
}
final activeTasksDropped = snapshot.activeTasks < pausedSnapshot.activeTasks;
final taskFailed = snapshot.failedTasks > pausedSnapshot.failedTasks;
if (activeTasksDropped && !taskFailed) {
// A paused/cancelled/removed task is not evidence of an OEM restriction.
return false;
}
final wasConfirmed = isStalled;
_consecutiveStalls++;
return !wasConfirmed && isStalled;
}
/// Convenience wrapper for direct callers and unit tests.
bool onResumed(DownloadActivitySnapshot snapshot, DateTime at, {required bool networkAvailable}) {
return evaluate(captureResumed(snapshot, at), networkAvailable: networkAvailable);
}
void reset() {
_clearArmedSnapshot();
_consecutiveStalls = 0;
}
void _clearArmedSnapshot() {
_pausedSnapshot = null;
_pausedAt = null;
}
}
/// Probes the OS for reasons background downloads will not run, and watches for
/// the symptom on OEMs that will not admit to it.
///
/// Every failure path resolves to [BackgroundWorkVerdict.ok]: a warning we
/// cannot substantiate trains users to ignore the ones we can.
class BackgroundWorkDiagnosticsService extends ChangeNotifier
with WidgetsBindingObserver, DisposableChangeNotifierMixin {
BackgroundWorkDiagnosticsService._()
: _channelOverride = null,
_clockOverride = null,
_networkProbeOverride = null,
_supportedOverride = null;
@visibleForTesting
BackgroundWorkDiagnosticsService.forTesting({
MethodChannel? channel,
DownloadStallDetector? stallDetector,
DateTime Function()? clock,
Future<bool> Function()? networkProbe,
bool supported = true,
}) : _channelOverride = channel,
_stallDetector = stallDetector ?? DownloadStallDetector(),
_clockOverride = clock,
_networkProbeOverride = networkProbe,
_supportedOverride = supported;
static final BackgroundWorkDiagnosticsService instance = BackgroundWorkDiagnosticsService._();
static const MethodChannel _deviceChannel = MethodChannel('com.plezy/device');
final MethodChannel? _channelOverride;
final DateTime Function()? _clockOverride;
final Future<bool> Function()? _networkProbeOverride;
final bool? _supportedOverride;
DownloadStallDetector _stallDetector = DownloadStallDetector();
DownloadActivitySnapshot Function()? _activitySource;
BackgroundWorkStatus _status = const BackgroundWorkStatus();
BackgroundWorkStatus _lastProbedStatus = const BackgroundWorkStatus();
Future<void>? _inFlightRefresh;
bool _observing = false;
bool _channelMissing = false;
MethodChannel get _channel => _channelOverride ?? _deviceChannel;
DateTime _now() => (_clockOverride ?? DateTime.now)();
/// Android phone/tablet only. TV boxes are mains-powered, and surfacing
/// mobile battery/data restrictions there is noise.
bool get isSupported => _supportedOverride ?? (Platform.isAndroid && !PlatformDetector.isTV());
BackgroundWorkStatus get status => _status;
/// One-line summary for the log upload header.
String describeSync() => isSupported ? _status.describe() : 'n/a';
/// Lets the download layer publish aggregate progress without this service
/// reaching into providers.
void bindActivitySource(DownloadActivitySnapshot Function() source) {
_activitySource = source;
_startObserving();
// Populate the banner, Settings tile, and log header on a cold start.
// Concurrent profile binds coalesce through [refresh].
unawaited(refresh());
}
void unbindActivitySource(DownloadActivitySnapshot Function() source) {
// Compared by `==` rather than `identical` so an instance-method tear-off
// from the same provider matches on every SDK.
if (_activitySource != source) return;
_activitySource = null;
}
void _startObserving() {
if (_observing || !isSupported) return;
final binding = WidgetsBinding.instance;
_observing = true;
binding.addObserver(this);
}
/// Re-probes the OS. Coalesces concurrent calls — the bucket is cheap to read
/// but there is no reason to read it three times on a single resume.
Future<BackgroundWorkStatus> refresh() async {
if (!isSupported || _channelMissing || isDisposed) return _status;
final inFlight = _inFlightRefresh;
if (inFlight != null) {
await inFlight;
return _status;
}
final refresh = _refresh();
_inFlightRefresh = refresh;
try {
await refresh;
} finally {
if (identical(_inFlightRefresh, refresh)) _inFlightRefresh = null;
}
return _status;
}
Future<void> _refresh() async {
try {
final raw = await _channel.invokeMapMethod<String, Object?>('getBackgroundWorkSignals');
if (isDisposed) return;
if (raw == null) return;
_applyStatus(_parse(raw));
} on MissingPluginException {
// Stale native build — never warn on a signal we could not read.
_channelMissing = true;
} catch (e) {
appLogger.d('Background work probe failed: $e');
}
}
BackgroundWorkStatus _parse(Map<String, Object?> raw) {
final verdict = switch (raw['verdict']) {
'blocked' => BackgroundWorkVerdict.blocked,
'degraded' => BackgroundWorkVerdict.degraded,
_ => BackgroundWorkVerdict.ok,
};
final reasons = <BackgroundWorkReason>[];
final rawReasons = raw['reasons'];
if (rawReasons is List) {
for (final entry in rawReasons) {
if (entry is! String) continue;
final reason = BackgroundWorkReason.fromId(entry);
// oem_unknown is ours, never the platform's — refuse it over the wire.
if (reason != null && reason != BackgroundWorkReason.oemUnknown) reasons.add(reason);
}
}
final bucket = raw['standbyBucket'];
final whitelisted = raw['ignoringBatteryOptimizations'];
return BackgroundWorkStatus(
// Trust our own reason parse over the reported verdict: an unknown reason
// id from a newer native build must not silently downgrade to blocked.
verdict: reasons.isEmpty ? BackgroundWorkVerdict.ok : verdict,
reasons: reasons,
standbyBucket: bucket is int ? bucket : null,
ignoringBatteryOptimizations: whitelisted is bool ? whitelisted : null,
probed: true,
);
}
void _applyStatus(BackgroundWorkStatus probed) {
if (isDisposed) return;
_lastProbedStatus = probed;
_applyCurrentStatus();
}
void _applyCurrentStatus() {
// A confirmed empirical stall outlives any single probe: the OS keeps
// reporting healthy on exactly the devices this catches.
final next = _stallDetector.isStalled
? _lastProbedStatus.withReason(BackgroundWorkReason.oemUnknown)
: _lastProbedStatus;
if (next == _status) return;
final previous = _status;
_status = next;
if (previous.verdict != next.verdict || !listEquals(previous.reasons, next.reasons)) {
appLogger.i('Background work status: ${next.describe()}');
}
safeNotifyListeners();
}
/// Opens the Settings screen that addresses [target]. Returns false when the
/// device has no such screen, so the caller can fall back to written steps.
Future<bool> openSettings(BackgroundSettingsTarget target) async {
if (!isSupported) return false;
try {
return await _channel.invokeMethod<bool>('openBackgroundSettings', target.id) ?? false;
} catch (e) {
appLogger.w('Opening background settings failed', error: e);
return false;
}
}
/// Clears an empirical stall verdict after the user reports having fixed it.
Future<void> clearStallEvidence() async {
if (isDisposed) return;
_stallDetector.reset();
_applyCurrentStatus();
await refresh();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
final source = _activitySource;
switch (state) {
case AppLifecycleState.paused:
case AppLifecycleState.detached:
if (source != null) _stallDetector.onPaused(source(), _now());
case AppLifecycleState.resumed:
final snapshot = source?.call();
final observation = snapshot == null ? null : _stallDetector.captureResumed(snapshot, _now());
unawaited(_onResumed(observation));
case AppLifecycleState.inactive:
case AppLifecycleState.hidden:
break;
}
}
Future<void> _onResumed(DownloadStallObservation? observation) async {
try {
if (observation != null) {
final networkAvailable = await _networkAllowsStallScoring();
if (isDisposed) return;
final wasStalled = _stallDetector.isStalled;
final confirmed = _stallDetector.evaluate(observation, networkAvailable: networkAvailable);
if (wasStalled && !_stallDetector.isStalled) _applyCurrentStatus();
if (confirmed) {
appLogger.w(
'Background downloads made no progress across ${_stallDetector.observationsToConfirm} '
'backgrounded periods — treating as an OEM background restriction',
);
}
}
await refresh();
} catch (e) {
appLogger.d('Background work resume check failed: $e');
}
}
Future<bool> _networkAllowsStallScoring() async {
final probe = _networkProbeOverride;
if (probe != null) return probe();
try {
final connectivity = await Connectivity().checkConnectivity();
if (connectivity.isEmpty || connectivity.contains(ConnectivityResult.none)) return false;
return !await DownloadManagerService.shouldBlockDownloadOnCellularWith(connectivity);
} catch (_) {
// Unknown connectivity or policy is inconclusive, not proof of a stall.
return false;
}
}
@override
void dispose() {
if (_observing) {
WidgetsBinding.instance.removeObserver(this);
_observing = false;
}
super.dispose();
}
}
@@ -107,6 +107,11 @@ class DownloadManagerService {
final _progressController = StreamController<DownloadProgress>.broadcast();
Stream<DownloadProgress> get progressStream => _progressController.stream;
@visibleForTesting
void debugEmitProgress(DownloadProgress progress) {
if (!_disposed) _progressController.add(progress);
}
final _deletionProgressController = StreamController<DeletionProgress>.broadcast();
Stream<DeletionProgress> get deletionProgressStream => _deletionProgressController.stream;
@@ -289,7 +289,7 @@ class MusicPlaybackServiceImpl extends MusicPlaybackService with WidgetsBindingO
// Android 13+: the background playback notification needs
// POST_NOTIFICATIONS. Fire-and-forget — playback and the foreground
// service run regardless; a denial only hides the notification.
unawaited(ensureNotificationPermission());
unawaited(NotificationPermission.ensure());
final generation = ++_generation;
_invalidateArmRequests();
_finalizeCurrentTrack();
+6
View File
@@ -383,6 +383,11 @@ class SettingsService extends BaseSharedPreferencesService {
static const downloadOnWifiOnly = BoolPref('download_on_wifi_only');
static const autoRemoveWatchedDownloads = BoolPref('auto_remove_watched_downloads');
/// Set once the user has seen the pre-flight "background downloads are
/// blocked" dialog. The persistent Downloads-screen banner covers repeat
/// offenders, so the interrupting dialog is shown exactly once.
static const backgroundDownloadWarningAcknowledged = BoolPref('background_download_warning_ack');
/// Remembered state of the "Include Specials" toggle on the show download
/// dialog. Defaults to true (include) so existing behavior is unchanged;
/// turning it off persists so the next download keeps the choice.
@@ -858,6 +863,7 @@ class SettingsService extends BaseSharedPreferencesService {
rememberTrackSelections,
customDownloadPathType,
downloadOnWifiOnly,
backgroundDownloadWarningAcknowledged,
downloadIncludeSpecials,
autoCheckUpdatesOnStartup,
showPerformanceOverlay,
+5
View File
@@ -10,6 +10,7 @@ import '../database/app_database.dart';
import '../providers/download_provider.dart';
import '../services/settings_service.dart';
import '../services/sync_rule_executor.dart';
import '../widgets/background_download_warning_banner.dart';
import 'content_utils.dart';
import 'dialogs.dart';
import 'download_version_utils.dart';
@@ -77,6 +78,8 @@ Future<DownloadResult?> showDownloadOptionsAndQueue(
required DownloadProvider downloadProvider,
Future<void> Function()? onDelete,
}) async {
if (!await confirmBackgroundDownloadRestrictions(context) || !context.mounted) return null;
final kind = metadata.kind;
var filter = DownloadFilter.all;
@@ -220,6 +223,8 @@ Future<DownloadResult?> showListDownloadOptionsAndQueue(
}) async {
assert(targetType == ContentTypes.collection || targetType == ContentTypes.playlist);
if (!await confirmBackgroundDownloadRestrictions(context) || !context.mounted) return null;
final selectedFilter = await showOptionPickerDialog<DownloadFilter>(
context,
title: t.downloads.downloadNow,
+49 -22
View File
@@ -1,31 +1,58 @@
import 'dart:io';
import 'package:background_downloader/background_downloader.dart';
import 'package:flutter/foundation.dart';
import 'app_logger.dart';
bool _requested = false;
/// Android 13+ `POST_NOTIFICATIONS` runtime permission, routed through
/// background_downloader's permissions API (which the app already ships for
/// download notifications).
abstract final class NotificationPermission {
static Future<void>? _requestFuture;
/// Best-effort request for the Android 13+ POST_NOTIFICATIONS runtime
/// permission (routed through background_downloader's permissions API, which
/// the app already ships for download notifications).
///
/// Needed so the background music playback notification is visible; playback
/// and its foreground service run regardless of the outcome, so a denial only
/// costs notification visibility. Asked at most once per app run — Android
/// remembers a real denial, so re-prompting is a no-op anyway.
///
/// No-op off Android: iOS/macOS media controls don't use notifications.
Future<void> ensureNotificationPermission() async {
if (_requested || !Platform.isAndroid) return;
_requested = true;
try {
final permissions = FileDownloader().permissions;
final status = await permissions.status(PermissionType.notifications);
if (status == PermissionStatus.granted) return;
final result = await permissions.request(PermissionType.notifications);
appLogger.d('Notification permission request result: $result');
} catch (e) {
appLogger.w('Notification permission request failed', error: e);
/// Replaces the platform request in tests. Reset to null in `tearDown`.
@visibleForTesting
static Future<void> Function()? debugRequestOverride;
/// Clears the once-per-run future so each test starts from a clean slate.
@visibleForTesting
static void debugReset() => _requestFuture = null;
/// Best-effort permission request.
///
/// Both consumers need it for the same reason: the notification is what
/// anchors the foreground service. For music that costs only visibility, but
/// for downloads a denial lets the OS kill the transfer as soon as the screen
/// goes off — which is exactly the failure users report as "downloads only
/// work with the screen on".
///
/// Concurrent callers await the same request. The completed future stays
/// cached for the app run because Android remembers a real denial.
///
/// No-op off Android: iOS/macOS media controls don't use notifications.
static Future<void> ensure() => _requestFuture ??= _request();
static Future<void> _request() async {
try {
// Checked before the platform gate so tests exercise the once-per-run
// contract on a host that is not Android.
final override = debugRequestOverride;
if (override != null) {
await override();
return;
}
if (!Platform.isAndroid) return;
final permissions = FileDownloader().permissions;
final status = await permissions.status(PermissionType.notifications);
if (status == PermissionStatus.granted) return;
final result = await permissions.request(PermissionType.notifications);
appLogger.d('Notification permission request result: $result');
} catch (e, stackTrace) {
// Permission prompts are best-effort prerequisites. A platform failure
// must not poison the shared future or fail playback/download queueing.
appLogger.w('Notification permission request failed', error: e, stackTrace: stackTrace);
}
}
}
@@ -0,0 +1,246 @@
import 'package:flutter/material.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:url_launcher/url_launcher.dart';
import '../focus/focusable_button.dart';
import '../i18n/strings.g.dart';
import '../services/background_work_diagnostics_service.dart';
import '../services/settings_service.dart';
import '../utils/notification_permission.dart';
import '../utils/dialogs.dart';
import '../utils/snackbar_helper.dart';
import 'app_icon.dart';
import 'dialog_action_button.dart';
/// Community-maintained per-vendor instructions for OEM background-work
/// settings. The site root asks the user to pick their manufacturer, which is
/// more reliable than us guessing a slug from `Build.MANUFACTURER`.
const _dontKillMyAppUrl = 'https://dontkillmyapp.com';
/// Banner shown at the top of the Downloads screen when the OS is expected to
/// stop downloads the moment the app is backgrounded.
///
/// Deliberately conditional on there being something to download: an idle
/// Downloads screen should not nag, and a warning users learn to dismiss is
/// worse than no warning. Collapses to nothing when the verdict is healthy or
/// the platform cannot answer.
class BackgroundDownloadWarningBanner extends StatelessWidget {
const BackgroundDownloadWarningBanner({super.key, required this.hasPendingDownloads, this.service});
/// Whether anything is queued or in flight. The restriction only matters
/// while there is a transfer for it to kill.
final bool hasPendingDownloads;
final BackgroundWorkDiagnosticsService? service;
BackgroundWorkDiagnosticsService get _service => service ?? BackgroundWorkDiagnosticsService.instance;
@override
Widget build(BuildContext context) {
if (!hasPendingDownloads || !_service.isSupported) return const SizedBox.shrink();
return ListenableBuilder(
listenable: _service,
builder: (context, _) {
final status = _service.status;
if (status.isHealthy) return const SizedBox.shrink();
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final blocked = status.isBlocked;
final background = blocked ? scheme.errorContainer : scheme.tertiaryContainer;
final foreground = blocked ? scheme.onErrorContainer : scheme.onTertiaryContainer;
return Material(
color: background,
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 8, 8),
child: Row(
children: [
AppIcon(blocked ? Symbols.battery_alert_rounded : Symbols.info_rounded, fill: 1, color: foreground),
const SizedBox(width: 12),
Expanded(
child: Text(
blocked
? t.downloads.backgroundWarning.bannerBlocked
: t.downloads.backgroundWarning.bannerDegraded,
style: theme.textTheme.bodyMedium?.copyWith(color: foreground, fontWeight: .w500),
),
),
const SizedBox(width: 8),
FocusableButton(
onPressed: () => showBackgroundDownloadWarningDialog(context, service: _service),
child: FilledButton.tonal(
style: FilledButton.styleFrom(backgroundColor: foreground, foregroundColor: background),
onPressed: () => showBackgroundDownloadWarningDialog(context, service: _service),
child: Text(t.downloads.backgroundWarning.bannerAction),
),
),
],
),
),
);
},
);
}
}
/// Pre-flight gate before a user-initiated download.
///
/// Returns whether to proceed with queueing. Interrupts at most once per
/// install — the persistent Downloads-screen banner covers repeat offenders,
/// and a dialog on every download would only teach users to dismiss it.
/// Only [BackgroundWorkVerdict.blocked] is worth interrupting for; a Data Saver
/// warning can wait for the banner.
Future<bool> confirmBackgroundDownloadRestrictions(
BuildContext context, {
BackgroundWorkDiagnosticsService? service,
Future<void> Function()? notificationPermissionRequester,
}) async {
final diagnostics = service ?? BackgroundWorkDiagnosticsService.instance;
if (!diagnostics.isSupported) return true;
await (notificationPermissionRequester ?? NotificationPermission.ensure)();
if (!context.mounted) return false;
final settings = SettingsService.instanceOrNull;
if (settings?.read(SettingsService.backgroundDownloadWarningAcknowledged) ?? false) return true;
await diagnostics.refresh();
if (!diagnostics.status.isBlocked) return true;
if (!context.mounted) return false;
// Recorded before the dialog resolves: whichever button the user picks, they
// have now been told, and the banner takes over from here.
await settings?.write(SettingsService.backgroundDownloadWarningAcknowledged, true);
if (!context.mounted) return false;
return showBackgroundDownloadWarningDialog(context, preFlight: true, service: diagnostics);
}
/// Explains the detected restrictions and routes to the Settings screen that
/// fixes them.
///
/// When [preFlight] is set the dialog is interrupting a download the user just
/// asked for, so it offers "Download anyway" and returns whether to proceed.
/// Otherwise it is informational and always resolves to true.
Future<bool> showBackgroundDownloadWarningDialog(
BuildContext context, {
bool preFlight = false,
BackgroundWorkDiagnosticsService? service,
Future<bool> Function(Uri uri)? externalUrlLauncher,
}) async {
final diagnostics = service ?? BackgroundWorkDiagnosticsService.instance;
final status = diagnostics.status;
if (status.isHealthy) return true;
final proceed = await showScopedDialog<bool>(
context: context,
builder: (dialogContext) {
final theme = Theme.of(dialogContext);
return AlertDialog(
title: Text(
preFlight
? t.downloads.backgroundWarning.dialogTitle
: status.isBlocked
? t.downloads.backgroundWarning.sheetTitle
: t.downloads.backgroundWarning.sheetTitleDegraded,
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
Text(
status.isBlocked
? t.downloads.backgroundWarning.sheetIntro
: t.downloads.backgroundWarning.sheetIntroDegraded,
),
for (final reason in status.reasons) ...[
const SizedBox(height: 16),
Row(
crossAxisAlignment: .start,
children: [
Padding(
padding: const EdgeInsets.only(top: 2),
child: const AppIcon(Symbols.chevron_right_rounded, fill: 1, size: 18),
),
const SizedBox(width: 8),
Expanded(child: Text(_describeReason(reason))),
],
),
],
const SizedBox(height: 20),
Text(t.downloads.backgroundWarning.stillNotWorking, style: theme.textTheme.titleSmall),
const SizedBox(height: 4),
Text(t.downloads.backgroundWarning.stillNotWorkingDescription, style: theme.textTheme.bodySmall),
const SizedBox(height: 8),
Align(
alignment: .centerLeft,
child: FocusableButton(
onPressed: () => _openDontKillMyApp(dialogContext, launcher: externalUrlLauncher),
child: TextButton.icon(
onPressed: () => _openDontKillMyApp(dialogContext, launcher: externalUrlLauncher),
icon: const AppIcon(Symbols.open_in_new_rounded, fill: 1, size: 18),
label: const Text('dontkillmyapp.com'),
),
),
),
],
),
),
actions: [
if (preFlight)
DialogActionButton(
onPressed: () => Navigator.pop(dialogContext, true),
label: t.downloads.backgroundWarning.dialogDownloadAnyway,
)
else
DialogActionButton(onPressed: () => Navigator.pop(dialogContext, true), label: t.common.close),
DialogActionButton(
autofocus: true,
isPrimary: true,
onPressed: () async {
final navigator = Navigator.of(dialogContext);
final messengerContext = dialogContext;
if (status.reasons.contains(BackgroundWorkReason.oemUnknown)) {
await diagnostics.clearStallEvidence();
}
final opened = await diagnostics.openSettings(status.remedyTarget);
if (!opened && messengerContext.mounted) {
showErrorSnackBar(messengerContext, t.downloads.backgroundWarning.settingsUnavailable);
}
if (navigator.mounted) navigator.pop(preFlight ? false : true);
},
label: preFlight
? t.downloads.backgroundWarning.dialogFixFirst
: t.downloads.backgroundWarning.openSettings,
),
],
);
},
);
return proceed ?? !preFlight;
}
String _describeReason(BackgroundWorkReason reason) => switch (reason) {
BackgroundWorkReason.backgroundRestricted => t.downloads.backgroundWarning.reasonBackgroundRestricted,
BackgroundWorkReason.standbyRestricted => t.downloads.backgroundWarning.reasonStandbyRestricted,
BackgroundWorkReason.downloadChannelBlocked => t.downloads.backgroundWarning.reasonDownloadChannelBlocked,
BackgroundWorkReason.notificationsDisabled => t.downloads.backgroundWarning.reasonNotificationsDisabled,
BackgroundWorkReason.dataSaver => t.downloads.backgroundWarning.reasonDataSaver,
BackgroundWorkReason.oemUnknown => t.downloads.backgroundWarning.reasonOemUnknown,
};
Future<void> _openDontKillMyApp(BuildContext context, {Future<bool> Function(Uri uri)? launcher}) async {
var opened = false;
try {
final uri = Uri.parse(_dontKillMyAppUrl);
opened = await (launcher?.call(uri) ?? launchUrl(uri, mode: LaunchMode.externalApplication));
} catch (_) {
// Reported below using the same user-facing fallback as a false result.
}
if (!opened && context.mounted) {
showErrorSnackBar(context, t.downloads.backgroundWarning.linkUnavailable);
}
}
@@ -33,10 +33,36 @@ void main() {
}
}
});
test('background warning requires complete translations in every locale', () {
final sourceDirectory = Directory('lib/i18n');
final english = _decodeLocale(File('${sourceDirectory.path}/en.i18n.json'));
final englishWarning = _backgroundWarning(english);
final localeFiles = sourceDirectory.listSync().whereType<File>().where(
(file) => file.path.endsWith('.i18n.json') && !file.path.endsWith('en.i18n.json'),
);
expect(englishWarning.values, everyElement(isNotEmpty), reason: 'English warning copy must be usable');
for (final localeFile in localeFiles) {
final localizedWarning = _backgroundWarning(_decodeLocale(localeFile));
expect(
localizedWarning.keys,
unorderedEquals(englishWarning.keys),
reason: '${localeFile.path} must contain every background warning key',
);
expect(
localizedWarning.values,
everyElement(isNotEmpty),
reason: '${localeFile.path} must translate every background warning',
);
}
});
}
Map<String, dynamic> _decodeLocale(File file) => jsonDecode(file.readAsStringSync()) as Map<String, dynamic>;
Map<String, dynamic> _backgroundWarning(Map<String, dynamic> locale) =>
(locale['downloads'] as Map<String, dynamic>)['backgroundWarning'] as Map<String, dynamic>;
String? _valueAt(Map<String, dynamic> locale, List<String> path) {
Object? value = locale;
for (final segment in path) {
+190
View File
@@ -4,6 +4,7 @@ import 'package:drift/drift.dart' show ApplyInterceptor, QueryExecutor, QueryInt
import 'package:plezy/media/ids.dart';
import 'package:drift/native.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/database/app_database.dart';
import 'package:plezy/database/download_operations.dart';
@@ -18,7 +19,9 @@ import 'package:plezy/services/api_cache.dart';
import 'package:plezy/services/download_storage_service.dart';
import 'package:plezy/services/jellyfin_api_cache.dart';
import 'package:plezy/services/plex_api_cache.dart';
import 'package:plezy/services/offline_mode_source.dart';
import 'package:plezy/utils/deletion_notifier.dart';
import 'package:plezy/utils/notification_permission.dart';
import 'package:plezy/utils/watch_state_notifier.dart';
import 'package:plezy/utils/active_client_scope.dart';
import '../test_helpers/media_items.dart';
@@ -191,6 +194,21 @@ Future<void> _putPinnedPlexMetadata(
await PlexApiCache.instance.pinForOffline(scope.cacheServerId, id);
}
class _FakeOfflineModeSource extends ChangeNotifier implements OfflineModeSource {
_FakeOfflineModeSource(this._isOffline);
bool _isOffline;
@override
bool get isOffline => _isOffline;
void setOffline(bool value) {
if (_isOffline == value) return;
_isOffline = value;
notifyListeners();
}
}
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@@ -2713,6 +2731,178 @@ void main() {
});
});
group('DownloadProvider — background download prerequisites', () {
final movie = testMediaItem(
id: '1',
backend: MediaBackend.plex,
kind: MediaKind.movie,
title: 'Queued Movie',
serverId: ServerId('srv'),
);
setUp(NotificationPermission.debugReset);
tearDown(() {
NotificationPermission.debugRequestOverride = null;
NotificationPermission.debugReset();
});
test('queueDownload asks for the notification permission that anchors the transfer', () async {
// Regression guard: music playback used to be the only caller, so a user
// whose first action was a download was never asked, and Android killed
// the transfer the moment the screen went off.
var requests = 0;
NotificationPermission.debugRequestOverride = () async => requests++;
final p = DownloadProvider.forTesting(downloadManager: downloadManager, database: db);
await p.ensureInitialized();
// Already downloaded, so the queue short-circuits before touching the
// manager — the permission request is the only thing under test.
p.debugSeedState(
downloads: {'srv:1': const DownloadProgress(globalKey: 'srv:1', status: DownloadStatus.completed)},
metadata: {'srv:1': movie},
ownedDownloadKeys: const {},
);
await p.queueDownload(movie, _ThrowingClient());
expect(requests, 1);
p.dispose();
});
test('the permission is requested once, not on every queued item', () async {
var requests = 0;
NotificationPermission.debugRequestOverride = () async => requests++;
final second = movie.copyWith(id: '2');
final p = DownloadProvider.forTesting(downloadManager: downloadManager, database: db);
await p.ensureInitialized();
p.debugSeedState(
downloads: {
'srv:1': const DownloadProgress(globalKey: 'srv:1', status: DownloadStatus.completed),
'srv:2': const DownloadProgress(globalKey: 'srv:2', status: DownloadStatus.completed),
},
metadata: {'srv:1': movie, 'srv:2': second},
ownedDownloadKeys: const {},
);
await p.queueDownload(movie, _ThrowingClient());
await p.queueDownload(second, _ThrowingClient());
expect(requests, 1);
p.dispose();
});
test('holds the duplicate queue claim while permission is pending', () async {
final requestStarted = Completer<void>();
final releaseRequest = Completer<void>();
var requests = 0;
NotificationPermission.debugRequestOverride = () async {
requests++;
requestStarted.complete();
await releaseRequest.future;
};
final p = DownloadProvider.forTesting(downloadManager: downloadManager, database: db);
await p.ensureInitialized();
p.debugSeedState(
downloads: {'srv:1': const DownloadProgress(globalKey: 'srv:1', status: DownloadStatus.completed)},
metadata: {'srv:1': movie},
ownedDownloadKeys: const {},
);
final firstQueue = p.queueDownload(movie, _ThrowingClient());
await requestStarted.future;
expect(p.isQueueing(movie.globalKey), isTrue);
expect(await p.queueDownload(movie, _ThrowingClient()), 0);
releaseRequest.complete();
await firstQueue;
expect(requests, 1);
p.dispose();
});
test('the activity snapshot counts in-flight work and every downloaded byte', () async {
final p = DownloadProvider.forTesting(downloadManager: downloadManager, database: db);
await p.ensureInitialized();
p.debugSeedState(
downloads: {
'srv:1': const DownloadProgress(globalKey: 'srv:1', status: DownloadStatus.downloading, downloadedBytes: 100),
'srv:2': const DownloadProgress(globalKey: 'srv:2', status: DownloadStatus.queued, downloadedBytes: 0),
'srv:3': const DownloadProgress(globalKey: 'srv:3', status: DownloadStatus.completed, downloadedBytes: 900),
'srv:4': const DownloadProgress(globalKey: 'srv:4', status: DownloadStatus.failed, downloadedBytes: 5),
},
metadata: const {},
ownedDownloadKeys: const {},
);
final snapshot = p.downloadActivitySnapshot();
expect(snapshot.activeTasks, 1, reason: 'only actively downloading tasks arm the stall detector');
expect(snapshot.completedTasks, 1);
expect(snapshot.failedTasks, 1);
expect(snapshot.downloadedBytes, 1005, reason: 'every download, regardless of profile ownership');
p.dispose();
});
test('terminal status-only events preserve bytes but a retry resets progress', () async {
final p = DownloadProvider.forTesting(downloadManager: downloadManager, database: db);
await p.ensureInitialized();
p.debugSeedState(ownedDownloadKeys: const {'srv:1'});
downloadManager.debugEmitProgress(
const DownloadProgress(
globalKey: 'srv:1',
status: DownloadStatus.downloading,
progress: 47,
downloadedBytes: 500,
totalBytes: 1000,
),
);
await Future<void>.delayed(Duration.zero);
downloadManager.debugEmitProgress(
const DownloadProgress(globalKey: 'srv:1', status: DownloadStatus.completed, progress: 100),
);
await Future<void>.delayed(Duration.zero);
expect(p.downloads['srv:1']?.progress, 100);
expect(p.downloads['srv:1']?.downloadedBytes, 500);
expect(p.downloads['srv:1']?.totalBytes, 1000);
downloadManager.debugEmitProgress(const DownloadProgress(globalKey: 'srv:1', status: DownloadStatus.queued));
await Future<void>.delayed(Duration.zero);
expect(p.downloads['srv:1']?.progress, 0);
expect(p.downloads['srv:1']?.downloadedBytes, 0);
expect(p.downloads['srv:1']?.totalBytes, 0);
p.dispose();
});
test('activity snapshots record offline-source transitions', () async {
final source = _FakeOfflineModeSource(false);
final p = DownloadProvider.forTesting(downloadManager: downloadManager, database: db);
await p.ensureInitialized();
p.setOfflineSource(source);
final online = p.downloadActivitySnapshot();
source.setOffline(true);
final offline = p.downloadActivitySnapshot();
expect(online.networkAvailable, isTrue);
expect(offline.networkAvailable, isFalse);
expect(offline.networkStateGeneration, greaterThan(online.networkStateGeneration));
p.dispose();
source.dispose();
});
test('the activity snapshot tear-off is stable enough to unbind', () async {
final p = DownloadProvider.forTesting(downloadManager: downloadManager, database: db);
await p.ensureInitialized();
expect(p.downloadActivitySnapshot, equals(p.downloadActivitySnapshot));
p.dispose();
});
});
group('DownloadProvider — dispose hygiene', () {
test('isDisposed flips from false to true on dispose', () async {
final p = DownloadProvider.forTesting(downloadManager: downloadManager, database: db);
@@ -21,6 +21,7 @@ import 'package:plezy/providers/theme_provider.dart';
import 'package:plezy/providers/trackers_provider.dart';
import 'package:plezy/providers/trakt_account_provider.dart';
import 'package:plezy/screens/settings/settings_screen.dart';
import 'package:plezy/services/background_work_diagnostics_service.dart';
import 'package:plezy/services/donation_service.dart';
import 'package:plezy/services/download_storage_service.dart';
import 'package:plezy/services/download_manager_service.dart';
@@ -230,6 +231,59 @@ void main() {
expect(materialUpdateTile.focusNode, isNotNull);
});
testWidgets('background downloads tile renders and opens the injected blocked status', (tester) async {
const channel = MethodChannel('com.plezy/device.settings-background-test');
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, (call) async {
return switch (call.method) {
'getBackgroundWorkSignals' => {
'verdict': 'blocked',
'reasons': ['background_restricted'],
},
'openBackgroundSettings' => true,
_ => null,
};
});
final diagnostics = BackgroundWorkDiagnosticsService.forTesting(channel: channel);
await diagnostics.refresh();
final harness = await _pumpSettingsScreen(tester, backgroundWorkDiagnosticsService: diagnostics);
addTearDown(() async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
await harness.dispose(tester);
diagnostics.dispose();
});
expect(find.text(t.downloads.backgroundWarning.statusTile), findsOneWidget);
expect(find.text(t.downloads.backgroundWarning.statusBlocked), findsOneWidget);
await tester.tap(find.text(t.downloads.backgroundWarning.statusTile));
await _pumpUi(tester);
expect(find.text(t.downloads.backgroundWarning.sheetTitle), findsOneWidget);
});
testWidgets('unprobed background diagnostics stay unknown instead of claiming success', (tester) async {
final diagnostics = BackgroundWorkDiagnosticsService.forTesting();
final harness = await _pumpSettingsScreen(tester, backgroundWorkDiagnosticsService: diagnostics);
addTearDown(() async {
await harness.dispose(tester);
diagnostics.dispose();
});
expect(find.text(t.downloads.backgroundWarning.statusUnknown), findsOneWidget);
expect(find.text(t.downloads.backgroundWarning.statusOk), findsNothing);
});
testWidgets('background downloads tile is absent when diagnostics are unsupported', (tester) async {
final diagnostics = BackgroundWorkDiagnosticsService.forTesting(supported: false);
final harness = await _pumpSettingsScreen(tester, backgroundWorkDiagnosticsService: diagnostics);
addTearDown(() async {
await harness.dispose(tester);
diagnostics.dispose();
});
expect(find.text(t.downloads.backgroundWarning.statusTile), findsNothing);
});
testWidgets('relay dialog rejects invalid bases without persisting them', (tester) async {
final harness = await _pumpSettingsScreen(tester);
addTearDown(() => harness.dispose(tester));
@@ -534,6 +588,7 @@ Future<_SettingsHarness> _pumpSettingsScreen(
Future<bool> Function(Directory directory)? writableChecker,
Future<String?> Function()? settingsExporter,
Future<ImportResult?> Function()? settingsImporter,
BackgroundWorkDiagnosticsService? backgroundWorkDiagnosticsService,
}) async {
tester.view.physicalSize = const Size(1800, 3200);
tester.view.devicePixelRatio = 1;
@@ -621,6 +676,7 @@ Future<_SettingsHarness> _pumpSettingsScreen(
downloadDirectoryWritableChecker: writableChecker ?? (_) async => true,
settingsExporter: settingsExporter,
settingsImporter: settingsImporter,
backgroundWorkDiagnosticsService: backgroundWorkDiagnosticsService,
),
),
),
@@ -0,0 +1,505 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/services/background_work_diagnostics_service.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
group('DownloadStallDetector', () {
late DownloadStallDetector detector;
final start = DateTime(2026, 7, 25, 12);
setUp(() {
detector = DownloadStallDetector(
minimumBackgroundGap: const Duration(seconds: 60),
minimumProgressBytes: 64 * 1024,
observationsToConfirm: 2,
);
});
test('needs two consecutive stalls before accusing the OEM', () {
detector.onPaused(_activity(), start);
expect(detector.onResumed(_activity(), start.add(const Duration(minutes: 5)), networkAvailable: true), isFalse);
expect(detector.isStalled, isFalse);
detector.onPaused(_activity(), start.add(const Duration(minutes: 6)));
expect(detector.onResumed(_activity(), start.add(const Duration(minutes: 11)), networkAvailable: true), isTrue);
expect(detector.isStalled, isTrue);
});
test('confirms only once, so the warning is not re-raised every resume', () {
for (var i = 0; i < 2; i++) {
detector.onPaused(_activity(), start);
detector.onResumed(_activity(), start.add(const Duration(minutes: 5)), networkAvailable: true);
}
expect(detector.isStalled, isTrue);
detector.onPaused(_activity(), start);
expect(
detector.onResumed(_activity(), start.add(const Duration(minutes: 5)), networkAvailable: true),
isFalse,
reason: 'already confirmed',
);
});
test('real background progress clears the counter', () {
detector.onPaused(_activity(), start);
detector.onResumed(_activity(), start.add(const Duration(minutes: 5)), networkAvailable: true);
expect(detector.consecutiveStalls, 1);
detector.onPaused(_activity(), start.add(const Duration(minutes: 6)));
detector.onResumed(
_activity(downloadedBytes: 1000 + 5 * 1024 * 1024),
start.add(const Duration(minutes: 11)),
networkAvailable: true,
);
expect(detector.consecutiveStalls, 0);
});
test('a completed download below the byte floor clears the counter', () {
detector.onPaused(_activity(), start);
detector.onResumed(_activity(), start.add(const Duration(minutes: 5)), networkAvailable: true);
expect(detector.consecutiveStalls, 1);
detector.onPaused(_activity(), start.add(const Duration(minutes: 6)));
detector.onResumed(
_activity(activeTasks: 0, completedTasks: 1, downloadedBytes: 21 * 1024),
start.add(const Duration(minutes: 11)),
networkAvailable: true,
);
expect(detector.consecutiveStalls, 0);
});
test('a completion with omitted byte counters still clears the counter', () {
detector.onPaused(_activity(), start);
detector.onResumed(_activity(), start.add(const Duration(minutes: 5)), networkAvailable: true);
expect(detector.consecutiveStalls, 1);
detector.onPaused(_activity(downloadedBytes: 500 * 1024 * 1024), start.add(const Duration(minutes: 6)));
detector.onResumed(
_activity(activeTasks: 0, completedTasks: 1, downloadedBytes: 0),
start.add(const Duration(minutes: 11)),
networkAvailable: true,
);
expect(detector.consecutiveStalls, 0);
});
test('percentage progress clears the counter when byte length is unknown', () {
detector.onPaused(_activity(), start);
detector.onResumed(_activity(), start.add(const Duration(minutes: 5)), networkAvailable: true);
expect(detector.consecutiveStalls, 1);
detector.onPaused(_activity(progressUnits: 20), start.add(const Duration(minutes: 6)));
detector.onResumed(_activity(progressUnits: 21), start.add(const Duration(minutes: 11)), networkAvailable: true);
expect(detector.consecutiveStalls, 0);
});
test('an idle app is never scored', () {
detector.onPaused(_activity(activeTasks: 0), start);
expect(
detector.onResumed(_activity(activeTasks: 0), start.add(const Duration(hours: 8)), networkAvailable: true),
isFalse,
);
expect(detector.consecutiveStalls, 0);
});
test('a task removed without failing is inconclusive', () {
detector.onPaused(_activity(), start);
detector.onResumed(_activity(), start.add(const Duration(minutes: 5)), networkAvailable: true);
expect(detector.consecutiveStalls, 1);
detector.onPaused(_activity(), start.add(const Duration(minutes: 6)));
detector.onResumed(_activity(activeTasks: 0), start.add(const Duration(minutes: 11)), networkAvailable: true);
expect(detector.consecutiveStalls, 1);
});
test('a newly failed active task is stall evidence', () {
detector.onPaused(_activity(), start);
detector.onResumed(
_activity(activeTasks: 0, failedTasks: 1),
start.add(const Duration(minutes: 5)),
networkAvailable: true,
);
expect(detector.consecutiveStalls, 1);
});
test('a short background trip proves nothing', () {
detector.onPaused(_activity(), start);
detector.onResumed(_activity(), start.add(const Duration(seconds: 30)), networkAvailable: true);
expect(detector.consecutiveStalls, 0);
});
test('being offline is not evidence of a restriction', () {
detector.onPaused(_activity(), start);
detector.onResumed(_activity(), start.add(const Duration(minutes: 5)), networkAvailable: false);
expect(detector.consecutiveStalls, 0);
});
test('an offline endpoint is inconclusive even when policy allows downloads', () {
detector.onPaused(_activity(networkAvailable: false), start);
detector.onResumed(
_activity(networkAvailable: false),
start.add(const Duration(minutes: 5)),
networkAvailable: true,
);
expect(detector.consecutiveStalls, 0);
});
test('a network transition during the background window is inconclusive', () {
detector.onPaused(_activity(networkStateGeneration: 4), start);
detector.onResumed(
_activity(networkStateGeneration: 5),
start.add(const Duration(minutes: 5)),
networkAvailable: true,
);
expect(detector.consecutiveStalls, 0);
});
test('downloads deleted while backgrounded are inconclusive', () {
detector.onPaused(_activity(activeTasks: 2, downloadedBytes: 900 * 1024 * 1024), start);
detector.onResumed(
_activity(downloadedBytes: 10 * 1024 * 1024),
start.add(const Duration(minutes: 5)),
networkAvailable: true,
);
expect(detector.consecutiveStalls, 0);
});
test('reset clears both the armed snapshot and the count', () {
detector.onPaused(_activity(), start);
detector.onResumed(_activity(), start.add(const Duration(minutes: 5)), networkAvailable: true);
detector.reset();
expect(detector.consecutiveStalls, 0);
expect(detector.onResumed(_activity(), start.add(const Duration(minutes: 10)), networkAvailable: true), isFalse);
});
});
group('BackgroundWorkStatus', () {
test('describe reports unknown until a probe lands', () {
expect(const BackgroundWorkStatus().describe(), 'unknown');
});
test('describe carries verdict, reasons and raw signals for log uploads', () {
const status = BackgroundWorkStatus(
verdict: BackgroundWorkVerdict.blocked,
reasons: [BackgroundWorkReason.backgroundRestricted, BackgroundWorkReason.standbyRestricted],
standbyBucket: 45,
ignoringBatteryOptimizations: false,
probed: true,
);
expect(status.describe(), 'blocked background_restricted+standby_restricted bucket:45 batteryWhitelist:false');
});
test('routes notification faults to their valid settings screens', () {
const channel = BackgroundWorkStatus(
verdict: BackgroundWorkVerdict.degraded,
reasons: [BackgroundWorkReason.downloadChannelBlocked],
probed: true,
);
const appWide = BackgroundWorkStatus(
verdict: BackgroundWorkVerdict.blocked,
reasons: [BackgroundWorkReason.notificationsDisabled],
probed: true,
);
const battery = BackgroundWorkStatus(
verdict: BackgroundWorkVerdict.blocked,
reasons: [BackgroundWorkReason.backgroundRestricted],
probed: true,
);
expect(channel.remedyTarget, BackgroundSettingsTarget.notificationChannel);
expect(appWide.remedyTarget, BackgroundSettingsTarget.appNotifications);
expect(battery.remedyTarget, BackgroundSettingsTarget.appDetails);
});
});
group('BackgroundWorkDiagnosticsService', () {
late List<MethodCall> calls;
late Map<String, Object?> signals;
const channel = MethodChannel('com.plezy/device.test');
BackgroundWorkDiagnosticsService buildService({
DownloadStallDetector? stallDetector,
Future<bool> Function()? networkProbe,
DateTime Function()? clock,
bool supported = true,
}) => BackgroundWorkDiagnosticsService.forTesting(
channel: channel,
stallDetector: stallDetector,
networkProbe: networkProbe,
clock: clock,
supported: supported,
);
setUp(() {
calls = [];
signals = {'verdict': 'ok', 'reasons': <String>[]};
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, (call) async {
calls.add(call);
return switch (call.method) {
'getBackgroundWorkSignals' => signals,
'openBackgroundSettings' => true,
_ => null,
};
});
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
});
test('a healthy probe stays healthy but records that it ran', () async {
final service = buildService();
final status = await service.refresh();
expect(status.verdict, BackgroundWorkVerdict.ok);
expect(status.reasons, isEmpty);
expect(status.probed, isTrue);
expect(service.describeSync(), 'ok');
});
test('binding download activity performs the cold-start probe', () async {
signals = {
'verdict': 'blocked',
'reasons': ['background_restricted'],
};
final service = buildService();
service.bindActivitySource(_activity);
await Future<void>.delayed(Duration.zero);
expect(calls.where((call) => call.method == 'getBackgroundWorkSignals'), hasLength(1));
expect(service.status.isBlocked, isTrue);
service.unbindActivitySource(_activity);
service.dispose();
});
test('unsupported devices neither observe nor probe', () async {
final service = buildService(supported: false);
service.bindActivitySource(_activity);
await Future<void>.delayed(Duration.zero);
expect(service.isSupported, isFalse);
expect(calls, isEmpty);
expect(service.status.probed, isFalse);
service.unbindActivitySource(_activity);
service.dispose();
});
test('parses a blocked verdict with its reasons in order', () async {
signals = {
'verdict': 'blocked',
'reasons': ['background_restricted', 'standby_restricted'],
'standbyBucket': 45,
'ignoringBatteryOptimizations': false,
};
final service = buildService();
final status = await service.refresh();
expect(status.verdict, BackgroundWorkVerdict.blocked);
expect(status.reasons, [BackgroundWorkReason.backgroundRestricted, BackgroundWorkReason.standbyRestricted]);
expect(status.standbyBucket, 45);
expect(status.primaryReason, BackgroundWorkReason.backgroundRestricted);
});
test('a blocked verdict with no recognizable reason is not trusted', () async {
// A newer native build emitting an unknown reason id must not produce a
// warning the UI has nothing to say about.
signals = {
'verdict': 'blocked',
'reasons': ['some_future_reason'],
};
final service = buildService();
final status = await service.refresh();
expect(status.verdict, BackgroundWorkVerdict.ok);
expect(status.reasons, isEmpty);
});
test('refuses an oem_unknown reason arriving over the wire', () async {
signals = {
'verdict': 'blocked',
'reasons': ['oem_unknown'],
};
final service = buildService();
final status = await service.refresh();
expect(status.reasons, isEmpty);
expect(status.verdict, BackgroundWorkVerdict.ok);
});
test('data saver alone is degraded, not blocked', () async {
signals = {
'verdict': 'degraded',
'reasons': ['data_saver'],
};
final service = buildService();
final status = await service.refresh();
expect(status.verdict, BackgroundWorkVerdict.degraded);
expect(status.isBlocked, isFalse);
expect(status.isHealthy, isFalse);
});
test('concurrent refreshes coalesce into a single platform call', () async {
final service = buildService();
await Future.wait([service.refresh(), service.refresh(), service.refresh()]);
expect(calls.where((c) => c.method == 'getBackgroundWorkSignals'), hasLength(1));
});
test('a missing native handler never produces a warning and stops asking', () async {
var attempts = 0;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, (call) async {
attempts++;
throw MissingPluginException();
});
final service = buildService();
final first = await service.refresh();
final second = await service.refresh();
expect(first.verdict, BackgroundWorkVerdict.ok);
expect(first.probed, isFalse);
expect(second.probed, isFalse);
expect(attempts, 1);
});
test('an in-flight probe completing after disposal is ignored', () async {
final gate = Completer<Map<String, Object?>>();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, (call) {
calls.add(call);
return gate.future;
});
final service = buildService();
final refresh = service.refresh();
await Future<void>.delayed(Duration.zero);
service.dispose();
gate.complete({
'verdict': 'blocked',
'reasons': ['background_restricted'],
});
await expectLater(refresh, completes);
expect(service.status.probed, isFalse);
});
test('a confirmed stall survives a probe that reports everything is fine', () async {
final detector = DownloadStallDetector(observationsToConfirm: 1, minimumBackgroundGap: Duration.zero);
final service = buildService(stallDetector: detector, networkProbe: () async => true);
detector.onPaused(_activity(downloadedBytes: 0), DateTime(2026));
detector.onResumed(_activity(downloadedBytes: 0), DateTime(2026, 1, 1, 1), networkAvailable: true);
expect(detector.isStalled, isTrue);
final status = await service.refresh();
expect(status.verdict, BackgroundWorkVerdict.blocked);
expect(status.reasons, [BackgroundWorkReason.oemUnknown]);
});
test('clearing stall evidence drops the reason and re-probes', () async {
final detector = DownloadStallDetector(observationsToConfirm: 1, minimumBackgroundGap: Duration.zero);
final service = buildService(stallDetector: detector, networkProbe: () async => true);
detector.onPaused(_activity(downloadedBytes: 0), DateTime(2026));
detector.onResumed(_activity(downloadedBytes: 0), DateTime(2026, 1, 1, 1), networkAvailable: true);
await service.refresh();
expect(service.status.isBlocked, isTrue);
await service.clearStallEvidence();
expect(service.status.reasons, isEmpty);
expect(service.status.verdict, BackgroundWorkVerdict.ok);
expect(detector.isStalled, isFalse);
});
test('opening settings forwards the target id the native side expects', () async {
final service = buildService();
final opened = await service.openSettings(BackgroundSettingsTarget.notificationChannel);
expect(opened, isTrue);
final call = calls.firstWhere((c) => c.method == 'openBackgroundSettings');
expect(call.arguments, 'notification_channel');
});
test('lifecycle resume scores the synchronously captured snapshot', () async {
final detector = DownloadStallDetector(observationsToConfirm: 1, minimumBackgroundGap: Duration.zero);
final networkGate = Completer<bool>();
var now = DateTime(2026);
var activity = _activity(downloadedBytes: 1000);
final service = buildService(stallDetector: detector, networkProbe: () => networkGate.future, clock: () => now);
DownloadActivitySnapshot source() => activity;
service.bindActivitySource(source);
await Future<void>.delayed(Duration.zero);
service.didChangeAppLifecycleState(AppLifecycleState.paused);
now = now.add(const Duration(minutes: 5));
service.didChangeAppLifecycleState(AppLifecycleState.resumed);
activity = _activity(downloadedBytes: 5 * 1024 * 1024);
networkGate.complete(true);
await Future<void>.delayed(Duration.zero);
await Future<void>.delayed(Duration.zero);
expect(detector.isStalled, isTrue, reason: 'progress after resume must not be credited to the background window');
expect(service.status.reasons, contains(BackgroundWorkReason.oemUnknown));
service.unbindActivitySource(source);
service.dispose();
});
test('notifies listeners only when the status actually changes', () async {
signals = {
'verdict': 'blocked',
'reasons': ['background_restricted'],
};
final service = buildService();
var notifications = 0;
service.addListener(() => notifications++);
await service.refresh();
await service.refresh();
expect(notifications, 1);
});
});
}
DownloadActivitySnapshot _activity({
int activeTasks = 1,
int completedTasks = 0,
int failedTasks = 0,
int downloadedBytes = 1000,
int progressUnits = 0,
bool? networkAvailable = true,
int networkStateGeneration = 0,
}) => (
activeTasks: activeTasks,
completedTasks: completedTasks,
failedTasks: failedTasks,
downloadedBytes: downloadedBytes,
progressUnits: progressUnits,
networkAvailable: networkAvailable,
networkStateGeneration: networkStateGeneration,
);
@@ -0,0 +1,53 @@
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/utils/notification_permission.dart';
void main() {
setUp(NotificationPermission.debugReset);
tearDown(() {
NotificationPermission.debugRequestOverride = null;
NotificationPermission.debugReset();
});
test('concurrent callers await one shared permission request', () async {
final requestStarted = Completer<void>();
final releaseRequest = Completer<void>();
var requests = 0;
NotificationPermission.debugRequestOverride = () async {
requests++;
requestStarted.complete();
await releaseRequest.future;
};
var firstCompleted = false;
var secondCompleted = false;
final first = NotificationPermission.ensure().then((_) => firstCompleted = true);
final second = NotificationPermission.ensure().then((_) => secondCompleted = true);
await requestStarted.future;
expect(requests, 1);
expect(firstCompleted, isFalse);
expect(secondCompleted, isFalse);
releaseRequest.complete();
await Future.wait([first, second]);
expect(firstCompleted, isTrue);
expect(secondCompleted, isTrue);
});
test('a failed permission request is swallowed and remains cached', () async {
var requests = 0;
NotificationPermission.debugRequestOverride = () async {
requests++;
throw StateError('permission channel unavailable');
};
await NotificationPermission.ensure();
await NotificationPermission.ensure();
expect(requests, 1);
});
}
@@ -0,0 +1,295 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:plezy/focus/input_mode_tracker.dart';
import 'package:plezy/i18n/strings.g.dart';
import 'package:plezy/services/background_work_diagnostics_service.dart';
import 'package:plezy/services/settings_service.dart';
import 'package:plezy/widgets/background_download_warning_banner.dart';
import '../test_helpers/prefs.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const channel = MethodChannel('com.plezy/device.background-warning-test');
late Map<String, Object?> signals;
late List<MethodCall> calls;
setUpAll(() => LocaleSettings.setLocaleSync(AppLocale.en));
setUp(() async {
resetSharedPreferencesForTest();
SettingsService.resetForTesting();
await SettingsService.getInstance();
signals = {'verdict': 'ok', 'reasons': <String>[]};
calls = [];
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, (call) async {
calls.add(call);
return switch (call.method) {
'getBackgroundWorkSignals' => signals,
'openBackgroundSettings' => true,
_ => null,
};
});
});
tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, null);
SettingsService.resetForTesting();
});
testWidgets('requests notification permission before probing restrictions', (tester) async {
final service = BackgroundWorkDiagnosticsService.forTesting(channel: channel);
addTearDown(service.dispose);
final context = await _pumpShell(tester);
final events = <String>[];
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(channel, (call) async {
if (call.method == 'getBackgroundWorkSignals') {
events.add('probe');
return signals;
}
return true;
});
final proceed = await confirmBackgroundDownloadRestrictions(
context,
service: service,
notificationPermissionRequester: () async => events.add('permission'),
);
expect(proceed, isTrue);
expect(events, ['permission', 'probe']);
});
testWidgets('download anyway acknowledges the warning and proceeds', (tester) async {
signals = {
'verdict': 'blocked',
'reasons': ['notifications_disabled'],
};
final service = BackgroundWorkDiagnosticsService.forTesting(channel: channel);
addTearDown(service.dispose);
final context = await _pumpShell(tester);
final result = confirmBackgroundDownloadRestrictions(
context,
service: service,
notificationPermissionRequester: () async {},
);
await tester.pumpAndSettle();
expect(find.text(t.downloads.backgroundWarning.dialogTitle), findsOneWidget);
await tester.tap(find.text(t.downloads.backgroundWarning.dialogDownloadAnyway));
await tester.pumpAndSettle();
expect(await result, isTrue);
expect(SettingsService.instance.read(SettingsService.backgroundDownloadWarningAcknowledged), isTrue);
});
testWidgets('the focused fix action opens settings and cancels enqueue', (tester) async {
signals = {
'verdict': 'blocked',
'reasons': ['background_restricted'],
};
final service = BackgroundWorkDiagnosticsService.forTesting(channel: channel);
addTearDown(service.dispose);
final context = await _pumpShell(tester);
final result = confirmBackgroundDownloadRestrictions(
context,
service: service,
notificationPermissionRequester: () async {},
);
await tester.pumpAndSettle();
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
await tester.pumpAndSettle();
expect(await result, isFalse);
expect(
calls.where((call) => call.method == 'openBackgroundSettings').single.arguments,
BackgroundSettingsTarget.appDetails.id,
);
});
testWidgets('notification denial opens app settings without requiring a channel', (tester) async {
signals = {
'verdict': 'blocked',
'reasons': ['notifications_disabled'],
};
final service = BackgroundWorkDiagnosticsService.forTesting(channel: channel);
addTearDown(service.dispose);
final context = await _pumpShell(tester);
final result = confirmBackgroundDownloadRestrictions(
context,
service: service,
notificationPermissionRequester: () async {},
);
await tester.pumpAndSettle();
await tester.tap(find.text(t.downloads.backgroundWarning.dialogFixFirst));
await tester.pumpAndSettle();
expect(await result, isFalse);
expect(
calls.where((call) => call.method == 'openBackgroundSettings').single.arguments,
BackgroundSettingsTarget.appNotifications.id,
);
});
testWidgets('opening the OEM remedy clears stale empirical evidence', (tester) async {
final detector = DownloadStallDetector(observationsToConfirm: 1, minimumBackgroundGap: Duration.zero);
detector.onPaused(_activity(), DateTime(2026));
detector.onResumed(_activity(), DateTime(2026, 1, 1, 1), networkAvailable: true);
final service = BackgroundWorkDiagnosticsService.forTesting(channel: channel, stallDetector: detector);
addTearDown(service.dispose);
await service.refresh();
final context = await _pumpShell(tester);
final result = showBackgroundDownloadWarningDialog(context, service: service);
await tester.pumpAndSettle();
await tester.tap(find.text(t.downloads.backgroundWarning.openSettings));
await tester.pumpAndSettle();
expect(await result, isTrue);
expect(detector.isStalled, isFalse);
expect(service.status.reasons, isEmpty);
});
testWidgets('acknowledged warnings still request permission but skip the probe', (tester) async {
await SettingsService.instance.write(SettingsService.backgroundDownloadWarningAcknowledged, true);
final service = BackgroundWorkDiagnosticsService.forTesting(channel: channel);
addTearDown(service.dispose);
final context = await _pumpShell(tester);
var permissionRequests = 0;
final proceed = await confirmBackgroundDownloadRestrictions(
context,
service: service,
notificationPermissionRequester: () async => permissionRequests++,
);
expect(proceed, isTrue);
expect(permissionRequests, 1);
expect(calls, isEmpty);
});
testWidgets('blocked status renders only while downloads are pending', (tester) async {
signals = {
'verdict': 'blocked',
'reasons': ['background_restricted'],
};
final service = BackgroundWorkDiagnosticsService.forTesting(channel: channel);
addTearDown(service.dispose);
await service.refresh();
await _pumpShell(tester, child: BackgroundDownloadWarningBanner(hasPendingDownloads: true, service: service));
expect(find.text(t.downloads.backgroundWarning.bannerBlocked), findsOneWidget);
await _pumpShell(tester, child: BackgroundDownloadWarningBanner(hasPendingDownloads: false, service: service));
expect(find.text(t.downloads.backgroundWarning.bannerBlocked), findsNothing);
});
testWidgets('Data Saver uses degraded banner and dialog copy', (tester) async {
signals = {
'verdict': 'degraded',
'reasons': ['data_saver'],
};
final service = BackgroundWorkDiagnosticsService.forTesting(channel: channel);
addTearDown(service.dispose);
await service.refresh();
await _pumpShell(tester, child: BackgroundDownloadWarningBanner(hasPendingDownloads: true, service: service));
expect(find.text(t.downloads.backgroundWarning.bannerDegraded), findsOneWidget);
await tester.tap(find.text(t.downloads.backgroundWarning.bannerAction));
await tester.pumpAndSettle();
expect(
find.descendant(
of: find.byType(AlertDialog),
matching: find.text(t.downloads.backgroundWarning.sheetTitleDegraded),
),
findsOneWidget,
);
expect(find.text(t.downloads.backgroundWarning.sheetTitle), findsNothing);
});
testWidgets('an unavailable help link gives user feedback', (tester) async {
signals = {
'verdict': 'blocked',
'reasons': ['background_restricted'],
};
final service = BackgroundWorkDiagnosticsService.forTesting(channel: channel);
addTearDown(service.dispose);
await service.refresh();
final context = await _pumpShell(tester);
final result = showBackgroundDownloadWarningDialog(
context,
service: service,
externalUrlLauncher: (_) async => false,
);
await tester.pumpAndSettle();
await tester.tap(find.text('dontkillmyapp.com'));
await tester.pump();
expect(find.text(t.downloads.backgroundWarning.linkUnavailable), findsOneWidget);
await tester.tap(find.text(t.common.close));
await tester.pumpAndSettle();
expect(await result, isTrue);
});
testWidgets('unsupported devices skip both warning surfaces', (tester) async {
final service = BackgroundWorkDiagnosticsService.forTesting(channel: channel, supported: false);
addTearDown(service.dispose);
final context = await _pumpShell(
tester,
child: BackgroundDownloadWarningBanner(hasPendingDownloads: true, service: service),
);
var permissionRequests = 0;
final proceed = await confirmBackgroundDownloadRestrictions(
context,
service: service,
notificationPermissionRequester: () async => permissionRequests++,
);
expect(proceed, isTrue);
expect(permissionRequests, 0);
expect(calls, isEmpty);
expect(find.byType(BackgroundDownloadWarningBanner), findsOneWidget);
expect(find.text(t.downloads.backgroundWarning.bannerBlocked), findsNothing);
});
}
DownloadActivitySnapshot _activity() => (
activeTasks: 1,
completedTasks: 0,
failedTasks: 0,
downloadedBytes: 1000,
progressUnits: 0,
networkAvailable: true,
networkStateGeneration: 0,
);
Future<BuildContext> _pumpShell(WidgetTester tester, {Widget child = const SizedBox.shrink()}) async {
late BuildContext context;
await tester.pumpWidget(
TranslationProvider(
child: InputModeTracker(
child: MaterialApp(
home: Builder(
builder: (builderContext) {
context = builderContext;
return Scaffold(body: child);
},
),
),
),
),
);
await tester.pump();
return context;
}