feat(downloads): warn about Android background restrictions
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 s’arrêteront lorsque vous quitterez l’application",
|
||||
"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": "L’utilisation de Plezy en arrière-plan est restreinte. Dans les paramètres de batterie ou d’utilisation en arrière-plan, sélectionnez « Sans restriction ».",
|
||||
"reasonStandbyRestricted": "Android a placé Plezy en veille restreinte. Réglez l’utilisation 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 d’utilisation en arrière-plan de Plezy.",
|
||||
"openSettings": "Ouvrir les paramètres",
|
||||
"stillNotWorking": "Aide spécifique à l’appareil",
|
||||
"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 d’abord",
|
||||
"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 d’ouvrir les paramètres système sur cet appareil",
|
||||
"linkUnavailable": "Impossible d’ouvrir dontkillmyapp.com sur cet appareil"
|
||||
}
|
||||
},
|
||||
"shaders": {
|
||||
"title": "Shaders",
|
||||
|
||||
+29
-1
@@ -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
@@ -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 dall’app",
|
||||
"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": "L’uso in background di Plezy è limitato. Nelle impostazioni della batteria o dell’uso in background, seleziona «Senza restrizioni».",
|
||||
"reasonStandbyRestricted": "Android ha messo Plezy in uno stato di standby con restrizioni. Imposta l’uso della batteria su «Senza restrizioni».",
|
||||
"reasonDownloadChannelBlocked": "Le notifiche dei download sono disattivate, quindi l’avanzamento 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 dell’uso 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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": "Шейдеры",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 за по-рязко видео',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 s’arrêteront lorsque vous quitterez l’application';
|
||||
@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 => 'L’utilisation de Plezy en arrière-plan est restreinte. Dans les paramètres de batterie ou d’utilisation en arrière-plan, sélectionnez « Sans restriction ».';
|
||||
@override String get reasonStandbyRestricted => 'Android a placé Plezy en veille restreinte. Réglez l’utilisation 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 d’utilisation en arrière-plan de Plezy.';
|
||||
@override String get openSettings => 'Ouvrir les paramètres';
|
||||
@override String get stillNotWorking => 'Aide spécifique à l’appareil';
|
||||
@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 d’abord';
|
||||
@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 d’ouvrir les paramètres système sur cet appareil';
|
||||
@override String get linkUnavailable => 'Impossible d’ouvrir 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 s’arrêteront lorsque vous quitterez l’application',
|
||||
'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' => 'L’utilisation de Plezy en arrière-plan est restreinte. Dans les paramètres de batterie ou d’utilisation en arrière-plan, sélectionnez « Sans restriction ».',
|
||||
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android a placé Plezy en veille restreinte. Réglez l’utilisation 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 d’utilisation en arrière-plan de Plezy.',
|
||||
'downloads.backgroundWarning.openSettings' => 'Ouvrir les paramètres',
|
||||
'downloads.backgroundWarning.stillNotWorking' => 'Aide spécifique à l’appareil',
|
||||
'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 d’abord',
|
||||
'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 d’ouvrir les paramètres système sur cet appareil',
|
||||
'downloads.backgroundWarning.linkUnavailable' => 'Impossible d’ouvrir 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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 dall’app';
|
||||
@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 => 'L’uso in background di Plezy è limitato. Nelle impostazioni della batteria o dell’uso in background, seleziona «Senza restrizioni».';
|
||||
@override String get reasonStandbyRestricted => 'Android ha messo Plezy in uno stato di standby con restrizioni. Imposta l’uso della batteria su «Senza restrizioni».';
|
||||
@override String get reasonDownloadChannelBlocked => 'Le notifiche dei download sono disattivate, quindi l’avanzamento 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 dell’uso 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 dall’app',
|
||||
'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' => 'L’uso in background di Plezy è limitato. Nelle impostazioni della batteria o dell’uso in background, seleziona «Senza restrizioni».',
|
||||
'downloads.backgroundWarning.reasonStandbyRestricted' => 'Android ha messo Plezy in uno stato di standby con restrizioni. Imposta l’uso della batteria su «Senza restrizioni».',
|
||||
'downloads.backgroundWarning.reasonDownloadChannelBlocked' => 'Le notifiche dei download sono disattivate, quindi l’avanzamento 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 dell’uso 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',
|
||||
|
||||
@@ -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画像スケーリングで映像をより鮮明にします',
|
||||
|
||||
@@ -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 이미지 스케일링',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 для более чёткого видео',
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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 图像缩放,使视频更清晰',
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
@@ -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": "着色器",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}');
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user