feat: Improve source system and fix connectivity issues (#3137)

This commit is contained in:
Ax333l
2026-03-19 21:33:10 +01:00
committed by GitHub
parent d7d5530e7a
commit a4e3266e90
78 changed files with 343 additions and 412 deletions

View File

@@ -1,12 +1,7 @@
public abstract interface class app/revanced/manager/downloader/BaseDownloadScope : app/revanced/manager/downloader/Scope {
}
public final class app/revanced/manager/downloader/ConstantsKt {
public static final field DOWNLOADER_HOST_PERMISSION Ljava/lang/String;
}
public final class app/revanced/manager/downloader/DownloadUrl : android/os/Parcelable {
public static final field $stable I
public static final field CREATOR Landroid/os/Parcelable$Creator;
public fun <init> (Ljava/lang/String;Ljava/util/Map;)V
public synthetic fun <init> (Ljava/lang/String;Ljava/util/Map;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -25,11 +20,9 @@ public final class app/revanced/manager/downloader/DownloadUrl : android/os/Parc
}
public final class app/revanced/manager/downloader/Downloader {
public static final field $stable I
}
public final class app/revanced/manager/downloader/DownloaderBuilder {
public static final field $stable I
}
public abstract interface annotation class app/revanced/manager/downloader/DownloaderHostApi : java/lang/annotation/Annotation {
@@ -40,9 +33,9 @@ public final class app/revanced/manager/downloader/DownloaderKt {
}
public final class app/revanced/manager/downloader/DownloaderScope : app/revanced/manager/downloader/Scope {
public static final field $stable I
public final fun download (Lkotlin/jvm/functions/Function3;)V
public final fun get (Lkotlin/jvm/functions/Function4;)V
public fun getDataDir ()Ljava/io/File;
public fun getDownloaderPackageName ()Ljava/lang/String;
public fun getHostPackageName ()Ljava/lang/String;
public final fun useService (Landroid/content/Intent;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@@ -54,6 +47,11 @@ public final class app/revanced/manager/downloader/ExtensionsKt {
public abstract interface class app/revanced/manager/downloader/GetScope : app/revanced/manager/downloader/Scope {
public abstract fun requestStartActivity (Landroid/content/Intent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun requestStartFragment (Ljava/lang/Class;Landroid/os/Bundle;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/manager/downloader/GetScope$DefaultImpls {
public static fun requestStartFragment (Lapp/revanced/manager/downloader/GetScope;Ljava/lang/Class;Landroid/os/Bundle;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class app/revanced/manager/downloader/InputDownloadScope : app/revanced/manager/downloader/BaseDownloadScope {
@@ -64,7 +62,6 @@ public abstract interface class app/revanced/manager/downloader/OutputDownloadSc
}
public final class app/revanced/manager/downloader/Package : android/os/Parcelable {
public static final field $stable I
public static final field CREATOR Landroid/os/Parcelable$Creator;
public fun <init> (Ljava/lang/String;Ljava/lang/String;)V
public final fun component1 ()Ljava/lang/String;
@@ -81,32 +78,28 @@ public final class app/revanced/manager/downloader/Package : android/os/Parcelab
}
public abstract interface class app/revanced/manager/downloader/Scope {
public abstract fun getDataDir ()Ljava/io/File;
public abstract fun getDownloaderPackageName ()Ljava/lang/String;
public abstract fun getHostPackageName ()Ljava/lang/String;
}
public abstract class app/revanced/manager/downloader/UserInteractionException : java/lang/Exception {
public static final field $stable I
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public abstract class app/revanced/manager/downloader/UserInteractionException$Activity : app/revanced/manager/downloader/UserInteractionException {
public static final field $stable I
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/manager/downloader/UserInteractionException$Activity$Cancelled : app/revanced/manager/downloader/UserInteractionException$Activity {
public static final field $stable I
}
public final class app/revanced/manager/downloader/UserInteractionException$Activity$NotCompleted : app/revanced/manager/downloader/UserInteractionException$Activity {
public static final field $stable I
public final fun getIntent ()Landroid/content/Intent;
public final fun getResultCode ()I
}
public final class app/revanced/manager/downloader/UserInteractionException$RequestDenied : app/revanced/manager/downloader/UserInteractionException {
public static final field $stable I
}
public final class app/revanced/manager/downloader/webview/APIKt {
@@ -149,8 +142,8 @@ public abstract interface class app/revanced/manager/downloader/webview/WebViewC
}
public final class app/revanced/manager/downloader/webview/WebViewScope : app/revanced/manager/downloader/Scope {
public static final field $stable I
public final fun download (Lkotlin/jvm/functions/Function5;)V
public fun getDataDir ()Ljava/io/File;
public fun getDownloaderPackageName ()Ljava/lang/String;
public fun getHostPackageName ()Ljava/lang/String;
public final fun pageLoad (Lkotlin/jvm/functions/Function3;)V

View File

@@ -1,7 +0,0 @@
package app.revanced.manager.downloader
/**
* The permission ID of the special downloader host permission. Only ReVanced Manager will have this permission.
* Downloader UI activities and internal services can be protected using this permission.
*/
const val DOWNLOADER_HOST_PERMISSION = "app.revanced.manager.permission.DOWNLOADER_HOST"

View File

@@ -6,6 +6,7 @@ import android.content.Intent
import android.content.ServiceConnection
import android.os.IBinder
import android.app.Activity
import android.content.res.Resources
import android.os.Bundle
import android.os.Parcelable
import androidx.annotation.StringRes
@@ -94,7 +95,8 @@ typealias GetResult<T> = Pair<T, Version?>
class DownloaderScope<T : Parcelable> internal constructor(
private val scopeImpl: Scope,
internal val context: Context
internal val context: Context,
internal val resources: Resources
) : Scope by scopeImpl {
// Returning an InputStream is the primary way for a downloader to implement the download function, but we also want to offer an OutputStream API since using InputStream might not be convenient in all cases.
// It is much easier to implement the main InputStream API on top of OutputStreams compared to doing it the other way around, which is why we are using OutputStream here.
@@ -156,8 +158,8 @@ class DownloaderBuilder<T : Parcelable> internal constructor(
private val block: DownloaderScope<T>.() -> Unit
) {
@DownloaderHostApi
fun build(scopeImpl: Scope, context: Context) =
with(DownloaderScope<T>(scopeImpl, context)) {
fun build(scopeImpl: Scope, context: Context, resources: Resources) =
with(DownloaderScope<T>(scopeImpl, context, resources)) {
block()
Downloader(

View File

@@ -144,7 +144,7 @@ suspend fun <T> GetScope.runWebView(
*/
fun WebViewDownloader(@StringRes name: Int, block: suspend WebViewScope<DownloadUrl>.(packageName: String, version: String?) -> InitialUrl?) =
Downloader(name) {
val label = context.getString(name)
val label = resources.getString(name)
get { packageName, version ->
class ReturnNull : Exception()

View File

@@ -2,14 +2,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<permission
android:name="app.revanced.manager.permission.DOWNLOADER_HOST"
android:protectionLevel="signature"
android:label="@string/downloader_host_permission_label"
android:description="@string/downloader_host_permission_description"
/>
<uses-permission android:name="app.revanced.manager.permission.DOWNLOADER_HOST" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

View File

@@ -36,9 +36,8 @@ class DownloaderActivity : FragmentActivity() {
val fragmentClassName = intent.getStringExtra("FRAGMENT_CLASS_NAME")!!
val args = intent.getBundleExtra("FRAGMENT_ARGS")
res =
downloaderPkgState?.context?.createConfigurationContext(super.resources.configuration)?.resources
// See DownloaderRepository.ResourceImpl.
res = downloaderPkgState?.resourceImpl?.apply(super.resources)
if (savedInstanceState == null) {
@Suppress("UNCHECKED_CAST")

View File

@@ -78,7 +78,7 @@ class ManagerApplication : Application() {
arrayOf(patchBundleRepository, downloaderRepository).forEach {
with(it) {
reload()
updateCheck()
updateCheck(force = false)
}
}
}

View File

@@ -9,11 +9,5 @@ class NetworkInfo(app: Application) {
private val connectivityManager = app.getSystemService<ConnectivityManager>()!!
private fun getCapabilities() = connectivityManager.activeNetwork?.let { connectivityManager.getNetworkCapabilities(it) }
fun isConnected() = connectivityManager.activeNetwork != null
fun isUnmetered() = getCapabilities()?.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) ?: true
/**
* Returns true if it is safe to download large files.
*/
fun isSafe(ignoreMetered: Boolean) = isConnected() && (ignoreMetered || isUnmetered())
}

View File

@@ -2,12 +2,13 @@ package app.revanced.manager.data.room.bundles
import androidx.room.*
import app.revanced.manager.data.room.sources.Source
import app.revanced.manager.domain.manager.SourceManager
@Entity(tableName = "patch_bundles")
data class PatchBundleEntity(
@PrimaryKey val uid: Int,
@PrimaryKey override val uid: Int,
@ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "version") val versionHash: String? = null,
@ColumnInfo(name = "source") val source: Source,
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
)
) : SourceManager.DatabaseEntity

View File

@@ -2,12 +2,13 @@ package app.revanced.manager.data.room.downloader
import androidx.room.*
import app.revanced.manager.data.room.sources.Source
import app.revanced.manager.domain.manager.SourceManager
@Entity(tableName = "downloaders")
data class DownloaderEntity(
@PrimaryKey val uid: Int,
@PrimaryKey override val uid: Int,
@ColumnInfo(name = "name") val name: String,
@ColumnInfo(name = "version") val versionHash: String? = null,
@ColumnInfo(name = "source") val source: Source,
@ColumnInfo(name = "auto_update") val autoUpdate: Boolean
)
) : SourceManager.DatabaseEntity

View File

@@ -11,23 +11,20 @@ import app.revanced.manager.data.room.AppDatabase.Companion.generateUid
import app.revanced.manager.data.room.sources.Source as SourceInfo
import app.revanced.manager.data.room.sources.SourceProperties
import app.revanced.manager.domain.sources.APISource
import app.revanced.manager.domain.sources.Extensions.asRemoteOrNull
import app.revanced.manager.domain.sources.LocalSource
import app.revanced.manager.domain.sources.RemoteSource
import app.revanced.manager.domain.sources.Source
import app.revanced.manager.util.simpleMessage
import app.revanced.manager.util.tag
import app.revanced.manager.util.toast
import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toPersistentMap
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@@ -40,7 +37,7 @@ import kotlin.collections.set
/**
* Abstraction for managing a source system. Used by [app.revanced.manager.domain.repository.PatchBundleRepository] and [app.revanced.manager.domain.repository.DownloaderRepository].
*/
abstract class SourceManager<DB, LOADED, OUTPUT>(
abstract class SourceManager<DB : SourceManager.DatabaseEntity, LOADED, OUTPUT>(
initial: OUTPUT,
protected val sourceDir: File
) : KoinComponent {
@@ -48,8 +45,6 @@ abstract class SourceManager<DB, LOADED, OUTPUT>(
protected val prefs: PreferencesManager by inject()
protected val networkInfo: NetworkInfo by inject()
protected abstract val defaultSource: DB
protected abstract suspend fun dbGetAll(): List<DB>
protected abstract suspend fun dbGetProps(uid: Int): SourceProperties?
protected abstract suspend fun dbUpsert(entity: DB)
@@ -59,28 +54,39 @@ abstract class SourceManager<DB, LOADED, OUTPUT>(
protected abstract fun loadEntity(entity: DB): Source<LOADED>
protected abstract fun entityFromProps(uid: Int, props: SourceProperties): DB
protected abstract fun uidOf(entity: DB): Int
protected abstract fun realNameOf(loaded: LOADED): String?
@get:StringRes
protected abstract val updateUnavailable: Int
@get:StringRes
protected abstract val updateSuccess: Int
@get:StringRes
protected abstract val updateFailed: Int
@get:StringRes
protected abstract val replaceFail: Int
protected abstract suspend fun loadDataFromSources(sources: MutableMap<Int, Source<LOADED>>): OUTPUT
private val _updateError = MutableStateFlow<Throwable?>(null)
val updateError = _updateError.asStateFlow()
protected val defaultSource = entityFromProps(
0, SourceProperties(
name = "",
versionHash = null,
source = SourceInfo.API,
autoUpdate = false
)
)
private val _apiOutageError = MutableStateFlow<Throwable?>(null)
val apiOutageError = _apiOutageError.asStateFlow()
protected val store = Store(
CoroutineScope(Dispatchers.Default),
State<LOADED, OUTPUT>(data = initial)
)
protected val store =
Store(CoroutineScope(Dispatchers.Default), State<LOADED, OUTPUT>(data = initial))
val updateErrors = store.state.map { it.updateErrors }
val apiOutageError = updateErrors.map { it[0] }
val hasOutdated = store.state.map { it.outdatedSources.isNotEmpty() }
protected suspend inline fun dispatchAction(
name: String,
@@ -97,13 +103,13 @@ abstract class SourceManager<DB, LOADED, OUTPUT>(
/**
* Performs a reload. Do not call this outside of a store action.
*/
protected suspend fun doReload(): State<LOADED, OUTPUT> {
protected suspend fun doReload(oldState: State<LOADED, OUTPUT>): State<LOADED, OUTPUT> {
val entities = loadFromDb().onEach {
Log.d(tag, "Source: $it")
}
val sources = entities
.associateTo(mutableMapOf()) { uidOf(it) to loadEntity(it) }
.associateTo(mutableMapOf()) { it.uid to loadEntity(it) }
sources.forEach syncName@{ (uid, src) ->
val newName = src.loaded?.let(::realNameOf).takeIf { it != src.name }
?: return@syncName
@@ -113,18 +119,20 @@ abstract class SourceManager<DB, LOADED, OUTPUT>(
}
val data = loadDataFromSources(sources)
return State(sources.toPersistentMap(), data)
return oldState.copy(data = data, sources = sources)
}
suspend fun reload() = dispatchAction("Full reload") {
doReload()
doReload(it)
}
private suspend fun loadFromDb(): List<DB> {
val all = dbGetAll()
if (all.isEmpty()) {
dbUpsert(defaultSource)
return listOf(defaultSource)
val all = dbGetAll().toMutableList()
val default = defaultSource
if (all.none { it.uid == default.uid }) {
dbUpsert(default)
all += default
}
return all
@@ -174,7 +182,7 @@ abstract class SourceManager<DB, LOADED, OUTPUT>(
suspend fun reset() = dispatchAction("Reset") { state ->
dbReset()
state.sources.keys.forEach { directoryOf(it).deleteRecursively() }
doReload()
doReload(state.copy(updateErrors = emptyMap(), outdatedSources = emptySet()))
}
suspend fun remove(vararg sources: Source<LOADED>) =
@@ -189,11 +197,17 @@ abstract class SourceManager<DB, LOADED, OUTPUT>(
}
val data = loadDataFromSources(currentSources)
State(currentSources.toPersistentMap(), data)
State(
data = data,
sources = currentSources,
updateErrors = state.updateErrors
.filter { (it, _) -> it in currentSources.keys },
outdatedSources = state.outdatedSources.filterTo(mutableSetOf()) { it in currentSources.keys }
)
}
suspend fun createLocal(createStream: suspend () -> InputStream) =
dispatchAction("Add local") {
dispatchAction("Add local") { state ->
val entity = createEntity("", SourceInfo.Local)
with(loadEntity(entity) as LocalSource<LOADED>) {
try {
@@ -209,40 +223,40 @@ abstract class SourceManager<DB, LOADED, OUTPUT>(
}
}
doReload()
doReload(state)
}
suspend fun createRemote(url: String, autoUpdate: Boolean) =
dispatchAction("Add remote ($url)") { state ->
val entity = createEntity("", SourceInfo.from(url), autoUpdate)
val src = loadEntity(entity) as RemoteSource<LOADED>
update(src, force = true)
state.copy(sources = state.sources.put(src.uid, src))
update(src)
state.copy(sources = state.sources.toMutableMap().also { it[src.uid] = src })
}
suspend fun reloadApiSources() = dispatchAction("Reload API sources") {
suspend fun reloadApiSources() = dispatchAction("Reload API sources") { state ->
this@SourceManager.store.state.value.sources.values.filterIsInstance<APISource<LOADED>>()
.forEach { src ->
with(src) { deleteLocalFile() }
updateDb(src.uid) { it.copy(versionHash = null) }
}
doReload()
doReload(state)
}
suspend fun RemoteSource<LOADED>.setAutoUpdate(value: Boolean) =
dispatchAction("Set auto update ($name, $value)") { state ->
updateDb(uid) { it.copy(autoUpdate = value) }
val newSrc = (state.sources[uid] as? RemoteSource<LOADED>)?.copy(autoUpdate = value)
val newSrc = state.sources[uid]?.asRemoteOrNull?.copy(autoUpdate = value)
?: return@dispatchAction state
state.copy(sources = state.sources.put(uid, newSrc))
state.copy(sources = state.sources.toMutableMap().also { it[uid] = newSrc })
}
suspend fun update(
vararg sources: RemoteSource<LOADED>,
showToast: Boolean = false,
force: Boolean = false
force: Boolean = true
) {
val uids = sources.map { it.uid }.toSet()
store.dispatch(Update(showToast = showToast, force = force) { it.uid in uids })
@@ -254,8 +268,12 @@ abstract class SourceManager<DB, LOADED, OUTPUT>(
/**
* Updates all sources that should be automatically updated.
*/
suspend fun updateCheck() =
store.dispatch(Update(force = prefs.allowMeteredNetworks.get()) { it.autoUpdate })
suspend fun updateCheck(showToast: Boolean = false, force: Boolean = true) = store.dispatch(
Update(
showToast = showToast,
force = force || prefs.allowMeteredNetworks.get()
) { it.autoUpdate }
)
private inner class Update(
private val force: Boolean = false,
@@ -263,8 +281,6 @@ abstract class SourceManager<DB, LOADED, OUTPUT>(
private val showToast: Boolean = false,
private val predicate: (source: RemoteSource<LOADED>) -> Boolean = { true },
) : Action<State<LOADED, OUTPUT>> {
private var attemptedMainApiUpdate = false
private suspend fun toast(@StringRes id: Int, vararg args: Any?) =
withContext(Dispatchers.Main) { app.toast(app.getString(id, *args)) }
@@ -272,25 +288,33 @@ abstract class SourceManager<DB, LOADED, OUTPUT>(
override suspend fun ActionContext.execute(
current: State<LOADED, OUTPUT>
) = coroutineScope {
// if (!networkInfo.isSafe(force)) {
// Log.d(tag, "Skipping update check because the network is down or metered.")
// return@coroutineScope current
// }
) = supervisorScope {
val checkOnly = !force && !networkInfo.isUnmetered()
val updated = current.sources.values
val errors = current.updateErrors.toMutableMap()
val outdated = current.outdatedSources.toMutableSet()
val results = current.sources.values
.filterIsInstance<RemoteSource<LOADED>>()
.filter { predicate(it) }
.also { targets ->
attemptedMainApiUpdate = targets.any { it.uid == 0 && it is APISource<*> }
// Clear errors for sources we are updating.
targets.forEach { src ->
errors.remove(src.uid)
outdated.remove(src.uid)
}
}
.map {
async {
async update@{
Log.d(tag, "Updating: ${it.name}")
val newVersion = with(it) {
if (redownload) downloadLatest() else update()
} ?: return@async null
val newVersion = it.runCatching {
when {
redownload -> downloadLatest()
checkOnly -> getUpdateInfo()?.version
else -> update()
} ?: return@update null
}
it to newVersion
}
@@ -298,35 +322,60 @@ abstract class SourceManager<DB, LOADED, OUTPUT>(
.awaitAll()
.filterNotNull()
.toMap()
if (updated.isEmpty()) {
if (results.isEmpty()) {
if (showToast) toast(updateUnavailable)
return@coroutineScope current
return@supervisorScope current.copy(
updateErrors = errors,
outdatedSources = outdated
)
}
updated.forEach { (src, newVersionHash) ->
val name = src.loaded?.let(::realNameOf) ?: src.name
var hasErrors = false
results.forEach { (src, result) ->
result.getOrNull()?.let { newVersionHash ->
if (checkOnly) {
outdated.add(src.uid)
return@let
}
updateDb(src.uid) {
it.copy(versionHash = newVersionHash, name = name)
val name = src.loaded?.let(::realNameOf) ?: src.name
updateDb(src.uid) {
it.copy(versionHash = newVersionHash, name = name)
}
}
result.exceptionOrNull()?.let {
errors[src.uid] = it
Log.e(tag, "Failed to update source (${src.uid})", it)
hasErrors = true
}
}
if (showToast) toast(updateSuccess)
_updateError.value = null
if (attemptedMainApiUpdate) _apiOutageError.value = null
doReload()
}
when {
!showToast -> {}
hasErrors -> {
val error = errors.values.first()
toast(updateFailed, error)
}
override suspend fun catch(exception: Exception) {
Log.e(tag, "Failed to update", exception)
_updateError.value = exception
if (attemptedMainApiUpdate) _apiOutageError.value = exception
toast(updateFailed, exception.simpleMessage())
else -> toast(updateSuccess)
}
doReload(
current.copy(
updateErrors = errors,
outdatedSources = outdated
)
)
}
}
data class State<LOADED, OUTPUT>(
val sources: PersistentMap<Int, Source<LOADED>> = persistentMapOf(),
val data: OUTPUT
val data: OUTPUT,
val sources: Map<Int, Source<LOADED>> = emptyMap(),
val updateErrors: Map<Int, Throwable> = emptyMap(),
val outdatedSources: Set<Int> = emptySet(),
)
interface DatabaseEntity {
val uid: Int
}
}

View File

@@ -3,9 +3,14 @@ package app.revanced.manager.domain.repository
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.res.Resources
import android.content.res.loader.ResourcesLoader
import android.content.res.loader.ResourcesProvider
import android.os.Build
import android.os.ParcelFileDescriptor
import android.os.Parcelable
import androidx.annotation.RequiresApi
import app.revanced.manager.R
import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.downloader.DownloaderEntity
@@ -26,6 +31,7 @@ import app.revanced.manager.util.PM
import dalvik.system.PathClassLoader
import kotlinx.coroutines.flow.map
import java.io.File
import java.lang.ref.WeakReference
import java.lang.reflect.Modifier
import app.revanced.manager.data.room.sources.Source as SourceInfo
@@ -40,14 +46,6 @@ class DownloaderRepository(
) {
private val dao = db.downloaderDao()
override val defaultSource = DownloaderEntity(
uid = 0,
name = "",
versionHash = null,
source = SourceInfo.API,
autoUpdate = false
)
override suspend fun dbGetAll() = dao.all()
override suspend fun dbGetProps(uid: Int) = dao.getProps(uid)
override suspend fun dbUpsert(entity: DownloaderEntity) = dao.upsert(entity)
@@ -55,17 +53,20 @@ class DownloaderRepository(
override suspend fun dbReset() = dao.reset()
private val loader = Loader { file ->
val dataDir = file.parentFile!!.resolve("data").also(File::mkdirs)
val pkgInfo = pm.getPackageInfo(file) ?: error("Failed to get package info for $file")
loadPackage(pkgInfo)
loadPackage(pkgInfo, dataDir)
}
override fun loadEntity(entity: DownloaderEntity): Source<DownloaderPackage> = with(entity) {
val file = directoryOf(uid).resolve("downloader.jar")
val actualName =
entity.name.ifEmpty { app.getString(if (uid == 0) R.string.auto_updates_dialog_downloaders else R.string.source_name_fallback) }
return when (source) {
is SourceInfo.Local -> LocalSource(name, uid, null, file, loader)
is SourceInfo.Local -> LocalSource(actualName, uid, null, file, loader)
is SourceInfo.API -> APISource(
name,
actualName,
uid,
versionHash,
null,
@@ -76,7 +77,7 @@ class DownloaderRepository(
) { getDownloaderUpdate() }
is SourceInfo.Remote -> JsonSource(
name,
actualName,
uid,
versionHash,
null,
@@ -99,7 +100,6 @@ class DownloaderRepository(
autoUpdate = props.autoUpdate
)
override fun uidOf(entity: DownloaderEntity) = entity.uid
override fun realNameOf(loaded: DownloaderPackage) = loaded.name
override val updateFailed = R.string.downloader_update_failed
@@ -113,55 +113,46 @@ class DownloaderRepository(
val downloaderSources = store.state.map { it.sources }
val loadedDownloadersFlow = store.state.map { it.data }
// TODO: clear data for removed downloaders.
private val dataDir = app.getDir("downloaders_data", Context.MODE_PRIVATE)
fun findPackageByName(packageName: String) =
store.state.value.sources.values.asSequence().mapNotNull { it.loaded }
.find { it.context.packageName == packageName }
.find { it.packageName == packageName }
fun unwrapParceledData(data: ParceledDownloaderData): Pair<LoadedDownloader, Parcelable> {
val pkg = findPackageByName(data.downloaderPackageName) ?: throw Exception("Downloader package ${data.downloaderPackageName} is not available")
val pkg = findPackageByName(data.downloaderPackageName)
?: throw Exception("Downloader package ${data.downloaderPackageName} is not available")
val downloader = pkg.downloaders.firstOrNull { it.className == data.downloaderClassName }
?: throw Exception("No downloader with name ${data.downloaderClassName} found in ${data.downloaderPackageName}")
return downloader to data.unwrapWith(pkg.classLoader)
}
private val createApplicationContext by lazy {
val clazz = Context::class.java
clazz.getMethod("createApplicationContext", ApplicationInfo::class.java, Int::class.java)
}
private fun loadPackage(packageInfo: PackageInfo): DownloaderPackage {
private fun loadPackage(packageInfo: PackageInfo, dataDir: File): DownloaderPackage {
val packageName = packageInfo.packageName
// The context is technically only necessary for resources. On API levels 30 and above, it would be better to use the proper APIs for dynamic resource loading.
val downloaderContext = createApplicationContext(
app,
packageInfo.applicationInfo,
0
) as Context
val resources = pm.getResources(packageInfo)
val classNamesResId =
@SuppressLint("DiscouragedApi") downloaderContext.resources.getIdentifier(
@SuppressLint("DiscouragedApi") resources.getIdentifier(
CLASSES_RESOURCE_NAME,
"array",
downloaderContext.packageName
packageName
)
if (classNamesResId == 0) throw Exception("Class names resource not found (array/$CLASSES_RESOURCE_NAME)")
val classNames = downloaderContext.resources.getStringArray(classNamesResId)
val classNames = resources.getStringArray(classNamesResId)
val apkPath = packageInfo.applicationInfo!!.sourceDir
val classLoader =
PathClassLoader(packageInfo.applicationInfo!!.sourceDir, app.classLoader)
PathClassLoader(apkPath, app.classLoader)
val scopeImpl = object : Scope {
override val hostPackageName = app.packageName
override val downloaderPackageName = downloaderContext.packageName
override val dataDir =
this@DownloaderRepository.dataDir.resolve(downloaderPackageName).also(File::mkdirs)
override val downloaderPackageName = packageName
override val dataDir = dataDir
}
val resourceImpl =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) Api30ResourceImpl(File(apkPath))
else OldResourceImpl(resources)
return DownloaderPackage(
classNames.map { className ->
val downloader = classLoader
@@ -169,24 +160,63 @@ class DownloaderRepository(
.getDownloaderBuilder()
.build(
scopeImpl = scopeImpl,
context = downloaderContext
context = app,
resources = resources
)
LoadedDownloader(
packageName,
className,
downloaderContext.getString(downloader.name),
resources.getString(downloader.name),
scopeImpl,
downloader
)
},
classLoader,
downloaderContext,
resourceImpl,
packageInfo.packageName,
with(pm) { packageInfo.label() },
packageInfo.versionName.orEmpty()
)
}
/**
* Provides resources for [app.revanced.manager.DownloaderActivity]. Has a better implementation on Android 11 and above.
*/
fun interface ResourceImpl {
fun apply(res: Resources): Resources?
}
private class OldResourceImpl(val resources: Resources) : ResourceImpl {
@Suppress("DEPRECATION")
override fun apply(res: Resources) =
resources.also { it.updateConfiguration(res.configuration, res.displayMetrics) }
}
@RequiresApi(Build.VERSION_CODES.R)
private class Api30ResourceImpl(private val file: File) : ResourceImpl {
private var weakRef: WeakReference<ResourcesLoader>? = null
private fun getLoader(): ResourcesLoader {
weakRef?.get()?.let { return it }
val provider =
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).use { pfd ->
ResourcesProvider.loadFromApk(pfd)
}
val loader = ResourcesLoader().apply { addProvider(provider) }
weakRef = WeakReference(loader)
return loader
}
override fun apply(res: Resources): Resources? {
val loader = getLoader()
res.addLoaders(loader)
return null
}
}
private companion object {
const val CLASSES_RESOURCE_NAME = "app.revanced.manager.downloader.classes"

View File

@@ -42,14 +42,6 @@ class PatchBundleRepository(
) {
private val dao = db.patchBundleDao()
override val defaultSource = PatchBundleEntity(
uid = 0,
name = "",
versionHash = null,
source = SourceInfo.API,
autoUpdate = false
)
override val updateFailed = R.string.patches_download_fail
override val updateSuccess = R.string.patches_update_success
override val updateUnavailable = R.string.patches_update_unavailable
@@ -64,7 +56,7 @@ class PatchBundleRepository(
override fun loadEntity(entity: PatchBundleEntity): PatchBundleSource = with(entity) {
val file = directoryOf(uid).resolve("patches.jar")
val actualName =
entity.name.ifEmpty { app.getString(if (uid == 0) R.string.patches_name_default else R.string.patches_name_fallback) }
entity.name.ifEmpty { app.getString(if (uid == 0) R.string.patches_name_default else R.string.source_name_fallback) }
return when (source) {
is SourceInfo.Local -> LocalPatchBundle(actualName, uid, null, file, PatchBundleLoader)
@@ -103,7 +95,6 @@ class PatchBundleRepository(
autoUpdate = props.autoUpdate
)
override fun uidOf(entity: PatchBundleEntity) = entity.uid
override fun realNameOf(loaded: PatchBundle) = loaded.manifestAttributes?.name
override suspend fun loadDataFromSources(sources: MutableMap<Int, Source<PatchBundle>>) = loadMetadata(sources).toPersistentMap()

View File

@@ -48,13 +48,9 @@ sealed class RemoteSource<T>(
* Downloads the latest version regardless if there is a new update available.
*/
suspend fun ActionContext.downloadLatest() = download(getLatestInfo())
suspend fun ActionContext.getUpdateInfo() = getLatestInfo().takeUnless { hasInstalled() && it.version == versionHash }
suspend fun ActionContext.update(): String? = withContext(Dispatchers.IO) {
val info = getLatestInfo()
if (hasInstalled() && info.version == versionHash)
return@withContext null
download(info)
getUpdateInfo()?.let { download(it) }
}
companion object {

View File

@@ -1,11 +1,12 @@
package app.revanced.manager.network.downloader
import android.content.Context
import app.revanced.manager.domain.repository.DownloaderRepository
data class DownloaderPackage(
val downloaders: List<LoadedDownloader>,
val classLoader: ClassLoader,
val context: Context,
val resourceImpl: DownloaderRepository.ResourceImpl,
val packageName: String,
val name: String,
val version: String
)

View File

@@ -140,28 +140,23 @@ fun BundleInformationScreen(
}
},
actions = {
if (!src.isDefault) {
TooltipIconButton(
onClick = { showDeleteConfirmationDialog = true },
tooltip = stringResource(R.string.delete),
) { contentDescription ->
Icon(
Icons.Filled.Delete,
contentDescription
)
}
if (!src.isDefault) TooltipIconButton(
onClick = { showDeleteConfirmationDialog = true },
tooltip = stringResource(R.string.delete),
) { contentDescription ->
Icon(
Icons.Filled.Delete,
contentDescription
)
}
val hasNetwork = remember { viewModel.networkInfo.isConnected() }
if (!isLocal && hasNetwork) {
TooltipIconButton(
onClick = viewModel::refresh,
tooltip = stringResource(R.string.refresh),
) { contentDescription ->
Icon(
Icons.Filled.Refresh,
contentDescription
)
}
if (!isLocal) TooltipIconButton(
onClick = viewModel::refresh,
tooltip = stringResource(R.string.refresh),
) { contentDescription ->
Icon(
Icons.Filled.Refresh,
contentDescription
)
}
},
scrollBehavior = scrollBehavior

View File

@@ -125,6 +125,7 @@ fun DashboardScreen(
val availablePatches by vm.availablePatches.collectAsStateWithLifecycle(0)
val bundleDownloadError by vm.bundleDownloadError.collectAsStateWithLifecycle(null)
val sourcesNotDownloaded by vm.sourcesNotDownloaded.collectAsStateWithLifecycle(false)
val sourceUpdatesAvailable by vm.sourceUpdatesAvailable.collectAsStateWithLifecycle(false)
val managerAutoUpdates by vm.prefs.managerAutoUpdates.getAsState()
val showManagerUpdateDialogOnLaunch by vm.prefs.showManagerUpdateDialogOnLaunch.getAsState()
val disablePatchVersionCompatCheck by vm.prefs.disablePatchVersionCompatCheck.getAsState()
@@ -405,7 +406,6 @@ fun DashboardScreen(
patchesSourceEditMode = patchesSourceEditMode,
onEnablePatchesSourceEditMode = { patchesSourceEditMode = true },
onAddBundleClick = {
vm.cancelSourceSelection()
showAddBundleDialog = true
}
)
@@ -440,15 +440,22 @@ fun DashboardScreen(
)
}
} else null,
if (sourcesNotDownloaded && bundleDownloadError == null) {
if (sourceUpdatesAvailable) {
{
NotificationCard(
type = NotificationCardType.WARNING,
icon = Icons.Outlined.Refresh,
text = stringResource(R.string.banner_sources_not_updated_description),
onClick = vm::downloadSources
)
}
} else if (sourcesNotDownloaded && bundleDownloadError == null) {
{
NotificationCard(
type = NotificationCardType.WARNING,
icon = Icons.Outlined.Refresh,
text = stringResource(R.string.banner_sources_not_downloaded_description),
onClick = {
vm.downloadSources()
}
onClick = vm::downloadSources
)
}
} else null,

View File

@@ -76,7 +76,6 @@ fun SelectedAppInfoScreen(
) {
val resources = LocalResources.current
val networkInfo = koinInject<NetworkInfo>()
val networkConnected = remember { networkInfo.isConnected() }
val networkMetered = remember { !networkInfo.isUnmetered() }
val packageName = vm.selectedApp.packageName
@@ -131,8 +130,8 @@ fun SelectedAppInfoScreen(
derivedStateOf {
val selectedVersion = vm.selectedApp.version ?: return@derivedStateOf false
allowIncompatiblePatches &&
strictVersionOptions.versions.isNotEmpty() &&
selectedVersion !in strictVersionOptions.versions
strictVersionOptions.versions.isNotEmpty() &&
selectedVersion !in strictVersionOptions.versions
}
}
@@ -359,26 +358,12 @@ fun SelectedAppInfoScreen(
val needsInternet =
vm.selectedApp.let { it is SelectedApp.Search || it is SelectedApp.Download }
when {
!needsInternet -> {}
!networkConnected -> {
NotificationCard(
type = NotificationCardType.WARNING,
icon = Icons.Outlined.WarningAmber,
text = stringResource(R.string.network_unavailable_warning),
onDismiss = null
)
}
networkMetered -> {
NotificationCard(
type = NotificationCardType.WARNING,
icon = Icons.Outlined.WarningAmber,
text = stringResource(R.string.network_metered_warning),
onDismiss = null
)
}
}
if (needsInternet && networkMetered) NotificationCard(
type = NotificationCardType.WARNING,
icon = Icons.Outlined.WarningAmber,
text = stringResource(R.string.network_metered_warning),
onDismiss = null
)
}
}
}
@@ -562,8 +547,10 @@ private fun AppSourceSelectorDialog(
LazyColumn {
item(key = "auto") {
val hasDownloader = downloaders.isNotEmpty()
val hasDownloaded = downloadedApps.any { app -> requiredVersion == null || app.version == requiredVersion }
val hasAutoSource = hasDownloader || hasDownloaded || autoSelection is SelectedApp.Installed
val hasDownloaded =
downloadedApps.any { app -> requiredVersion == null || app.version == requiredVersion }
val hasAutoSource =
hasDownloader || hasDownloaded || autoSelection is SelectedApp.Installed
ListItem(
modifier = Modifier
.clickable(enabled = canSelect && hasAutoSource) { onSelectAuto() }

View File

@@ -137,11 +137,6 @@ fun AboutSettingsScreen(
stringResource(R.string.contributors),
stringResource(R.string.contributors_description),
third = nav@{
if (!viewModel.isConnected) {
context.toast(resources.getString(R.string.no_network_toast))
return@nav
}
navigate(Settings.Contributors)
}
),

View File

@@ -10,6 +10,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.outlined.Api
import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.PostAdd
import androidx.compose.material.icons.outlined.Restore
import androidx.compose.material.icons.outlined.WorkOutline
@@ -138,7 +139,17 @@ fun DeveloperSettingsScreen(
)
SettingsListItem(
headlineContent = stringResource(R.string.patches_reset),
onClick = vm::redownloadBundles
onClick = vm::resetBundles
)
}
ListSection(
title = stringResource(R.string.downloaders),
leadingContent = { Icon(Icons.Outlined.Download, contentDescription = null, modifier = Modifier.size(18.dp)) }
) {
SettingsListItem(
headlineContent = stringResource(R.string.downloaders_reset),
onClick = vm::resetDownloaders
)
}
}

View File

@@ -128,9 +128,6 @@ fun DownloaderInfoScreen(
}
remote?.let {
val hasNetwork = remember { viewModel.networkInfo.isConnected() }
if (!hasNetwork) return@let
TooltipIconButton(
onClick = { viewModel.updateDownloader(it) },
enabled = !isDeleting,

View File

@@ -117,10 +117,6 @@ fun UpdatesSettingsScreen(
return@launch
}
if (!vm.isConnected) {
context.toast(resources.getString(R.string.no_network_toast))
return@launch
}
checkingForUpdate = true
try {
val version = vm.checkForUpdates()
@@ -202,13 +198,7 @@ fun UpdatesSettingsScreen(
}
Spacer(modifier = Modifier.height(4.dp))
Button(
onClick = {
if (!vm.isConnected) {
context.toast(resources.getString(R.string.no_network_toast))
} else {
onChangelogClick()
}
},
onClick = onChangelogClick,
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
contentColor = MaterialTheme.colorScheme.onSurface

View File

@@ -7,7 +7,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.network.api.ReVancedAPI
import app.revanced.manager.network.dto.ReVancedDonationLink
@@ -27,7 +26,6 @@ import kotlinx.coroutines.withContext
class AboutViewModel(
private val reVancedAPI: ReVancedAPI,
private val network: NetworkInfo,
prefs: PreferencesManager,
) : ViewModel() {
var socials by mutableStateOf(emptyList<ReVancedSocial>())
@@ -36,16 +34,11 @@ class AboutViewModel(
private set
var donate by mutableStateOf<String?>(null)
private set
val isConnected: Boolean
get() = network.isConnected()
val showDeveloperSettings = prefs.showDeveloperSettings
init {
viewModelScope.launch {
if (!isConnected) {
return@launch
}
withContext(Dispatchers.IO) {
reVancedAPI.getInfo().getOrNull()
}?.let {

View File

@@ -2,7 +2,6 @@ package app.revanced.manager.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.AnnouncementRepository
import app.revanced.manager.network.dto.ReVancedAnnouncement
@@ -12,8 +11,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import kotlin.time.Clock
data class AnnouncementSections(
@@ -26,7 +23,6 @@ data class AnnouncementSections(
class AnnouncementsViewModel(
private val announcementRepository: AnnouncementRepository,
private val network: NetworkInfo,
private val preferences: PreferencesManager
) : ViewModel() {
private val allAnnouncements = MutableStateFlow<List<ReVancedAnnouncement>?>(null)
@@ -92,11 +88,6 @@ class AnnouncementsViewModel(
private fun loadData() {
viewModelScope.launch {
if (!network.isConnected()) {
allAnnouncements.value = emptyList()
return@launch
}
withContext(Dispatchers.IO) {
announcementRepository.getAnnouncements()?.let {
allAnnouncements.value = it

View File

@@ -2,7 +2,6 @@ package app.revanced.manager.ui.viewmodel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.domain.sources.Extensions.asRemoteOrNull
@@ -14,7 +13,6 @@ import org.koin.core.component.get
class BundleInformationViewModel(uid: Int) : ViewModel(), KoinComponent {
private val patchBundleRepository: PatchBundleRepository = get()
val networkInfo: NetworkInfo = get()
val prefs: PreferencesManager = get()
var bundle = patchBundleRepository.sources.map { sources -> sources.find { it.uid == uid } }
@@ -26,7 +24,7 @@ class BundleInformationViewModel(uid: Int) : ViewModel(), KoinComponent {
fun refresh() = viewModelScope.launch {
bundle.first()?.asRemoteOrNull?.let {
patchBundleRepository.update(it, showToast = true, force = true)
patchBundleRepository.update(it, showToast = true)
}
}

View File

@@ -53,8 +53,7 @@ class BundleListViewModel : ViewModel(), KoinComponent {
Event.UPDATE_SELECTED -> viewModelScope.launch {
patchBundleRepository.update(
*getSelectedSources().filterIsInstance<RemotePatchBundle>().toTypedArray(),
showToast = true,
force = true
showToast = true
)
}
}
@@ -66,7 +65,7 @@ class BundleListViewModel : ViewModel(), KoinComponent {
fun update(src: PatchBundleSource) = viewModelScope.launch {
if (src !is RemotePatchBundle) return@launch
patchBundleRepository.update(src, showToast = true, force = true)
patchBundleRepository.update(src, showToast = true)
}
enum class Event {

View File

@@ -5,30 +5,24 @@ import android.app.Application
import android.content.ContentResolver
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.content.getSystemService
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.R
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.domain.sources.Extensions.asRemoteOrNull
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.AnnouncementRepository
import app.revanced.manager.domain.repository.DownloaderRepository
import app.revanced.manager.domain.repository.ManagerUpdateRepository
import app.revanced.manager.domain.repository.PatchBundleRepository
import app.revanced.manager.network.dto.ReVancedAnnouncement
import app.revanced.manager.network.dto.ReVancedAsset
import app.revanced.manager.util.PM
import app.revanced.manager.util.uiSafe
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -38,7 +32,6 @@ class DashboardViewModel(
private val downloaderRepository: DownloaderRepository,
private val announcementRepository: AnnouncementRepository,
private val managerUpdateRepository: ManagerUpdateRepository,
private val networkInfo: NetworkInfo,
val prefs: PreferencesManager,
private val pm: PM,
) : ViewModel() {
@@ -46,15 +39,19 @@ class DashboardViewModel(
patchBundleRepository.bundleInfoFlow.map { it.values.sumOf { bundle -> bundle.patches.size } }
val bundleDownloadError = patchBundleRepository.apiOutageError
private val contentResolver: ContentResolver = app.contentResolver
private val powerManager = app.getSystemService<PowerManager>()!!
val availableManagerUpdate = managerUpdateRepository.availableVersion
val sourcesNotDownloaded = patchBundleRepository.bundleInfoFlow.map { it.isEmpty() }
val sourceUpdatesAvailable = combine(
patchBundleRepository.hasOutdated,
downloaderRepository.hasOutdated
) { patches, downloaders -> patches || downloaders }
fun downloadSources() = viewModelScope.launch(Dispatchers.Default) {
patchBundleRepository.updateCheck()
downloaderRepository.updateCheck()
arrayOf(patchBundleRepository, downloaderRepository).forEach {
it.updateCheck(showToast = true)
}
}
/**
@@ -68,9 +65,6 @@ class DashboardViewModel(
var unreadAnnouncement by mutableStateOf<ReVancedAnnouncement?>(null)
private set
private val bundleListEventsChannel = Channel<BundleListViewModel.Event>()
val bundleListEventsFlow = bundleListEventsChannel.receiveAsFlow()
init {
viewModelScope.launch {
checkForManagerUpdates()
@@ -79,7 +73,7 @@ class DashboardViewModel(
}
private suspend fun checkForManagerUpdates() {
if (!prefs.managerAutoUpdates.get() || !networkInfo.isConnected()) return
if (!prefs.managerAutoUpdates.get()) return
uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {
managerUpdateRepository.refreshAvailableVersion()
@@ -123,16 +117,9 @@ class DashboardViewModel(
}
}
private fun sendEvent(event: BundleListViewModel.Event) {
viewModelScope.launch { bundleListEventsChannel.send(event) }
}
fun cancelSourceSelection() = sendEvent(BundleListViewModel.Event.CANCEL)
fun updateSources() = sendEvent(BundleListViewModel.Event.UPDATE_SELECTED)
fun deleteSources() = sendEvent(BundleListViewModel.Event.DELETE_SELECTED)
fun deleteSource(uid: Int) = viewModelScope.launch {
val source = patchBundleRepository.sources.first().firstOrNull { it.uid == uid } ?: return@launch
val source =
patchBundleRepository.sources.first().firstOrNull { it.uid == uid } ?: return@launch
patchBundleRepository.remove(source)
}

View File

@@ -40,6 +40,10 @@ class DeveloperOptionsViewModel(
patchBundleRepository.reset()
}
fun resetDownloaders() = viewModelScope.launch {
downloaderRepository.reset()
}
fun resetOnboarding() = viewModelScope.launch {
prefs.completedOnboarding.update(false)
app.toast(app.getString(R.string.sideeffect_restart))

View File

@@ -105,7 +105,7 @@ class DownloadsViewModel(
fun updateDownloader(src: RemoteSource<DownloaderPackage>) = viewModelScope.launch {
try {
isUpdatingDownloader = true
downloaderRepository.update(src, showToast = true, force = true)
downloaderRepository.update(src, showToast = true)
} finally {
isUpdatingDownloader = false
}

View File

@@ -9,7 +9,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.R
import app.revanced.manager.data.room.apps.installed.InstallType
import app.revanced.manager.domain.sources.Extensions.asRemoteOrNull
import app.revanced.manager.domain.manager.KeystoreManager
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloadedAppRepository
@@ -155,17 +154,6 @@ class MainViewModel(
settings.experimentalPatchesEnabled?.let { allowExperimental ->
prefs.disablePatchVersionCompatCheck.update(allowExperimental)
}
settings.patchesAutoUpdate?.let { autoUpdate ->
with(patchBundleRepository) {
sources
.first()
.find { it.uid == 0 }
?.asRemoteOrNull
?.setAutoUpdate(autoUpdate)
updateCheck()
}
}
settings.patchesChangeEnabled?.let { disableSelectionWarning ->
prefs.disableSelectionWarning.update(disableSelectionWarning)
}

View File

@@ -9,7 +9,6 @@ import androidx.compose.runtime.setValue
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.ViewModel
import app.revanced.manager.data.platform.NetworkInfo
import app.revanced.manager.domain.sources.Extensions.asRemoteOrNull
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloaderRepository
@@ -32,7 +31,6 @@ class OnboardingViewModel(
private val pm: PM,
private val downloaderRepository: DownloaderRepository,
private val patchBundleRepository: PatchBundleRepository,
private val networkInfo: NetworkInfo,
) : ViewModel() {
private val powerManager = app.getSystemService<PowerManager>()!!
@@ -41,8 +39,8 @@ class OnboardingViewModel(
}
val apiUrl = prefs.api.default
val hasNetworkError = combine(apps, patchBundleRepository.updateError) { apps, updateError ->
apps == null && (!networkInfo.isConnected() || updateError != null)
val hasNetworkError = combine(apps, patchBundleRepository.updateErrors) { apps, updateErrors ->
apps == null && updateErrors.isNotEmpty()
}
val suggestedVersions = patchBundleRepository.suggestedVersions
@@ -94,7 +92,7 @@ class OnboardingViewModel(
?.asRemoteOrNull ?: return@with
src.setAutoUpdate(patchesEnabled)
if (networkInfo.isConnected()) update(src)
update(src)
}
with(downloaderRepository) {
@@ -103,7 +101,7 @@ class OnboardingViewModel(
?.asRemoteOrNull ?: return@with
src.setAutoUpdate(downloadersEnabled)
if (networkInfo.isConnected()) update(src)
update(src)
}
}
@@ -122,5 +120,4 @@ class OnboardingViewModel(
OnboardingStep.Updates -> OnboardingStep.Permissions
OnboardingStep.Apps -> OnboardingStep.Updates
}
}

View File

@@ -82,7 +82,7 @@ class UpdateViewModel(
uiSafe(app, R.string.failed_to_download_update, "Failed to download update") {
val release = releaseInfo!!
withContext(Dispatchers.IO) {
if (!networkInfo.isSafe(false) && !ignoreInternetCheck) {
if (!ignoreInternetCheck && !networkInfo.isUnmetered()) {
showInternetCheckDialog = true
} else {
state = State.DOWNLOADING

View File

@@ -20,9 +20,6 @@ class UpdatesSettingsViewModel(
val useManagerPrereleases = prefs.useManagerPrereleases
val availableManagerUpdate = managerUpdateRepository.availableVersion
val isConnected: Boolean
get() = network.isConnected()
suspend fun checkForUpdates(): String? {
var availableVersion: String? = null

View File

@@ -4,11 +4,9 @@ import android.annotation.SuppressLint
import android.app.Application
import android.content.Intent
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.PackageInfoFlags
import android.content.pm.PackageManager.NameNotFoundException
import androidx.core.content.pm.PackageInfoCompat
import android.content.pm.Signature
import android.os.Build
import android.os.Parcelable
import androidx.compose.runtime.Immutable
@@ -99,12 +97,6 @@ class PM(
else
app.packageManager.getInstalledPackages(flags)
fun getPackagesWithFeature(feature: String) =
getInstalledPackages(PackageManager.GET_CONFIGURATIONS)
.filter { pkg ->
pkg.reqFeatures?.any { it.name == feature } ?: false
}
fun getPackageInfo(packageName: String, flags: Int = 0): PackageInfo? =
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
@@ -128,49 +120,11 @@ class PM(
return pkgInfo
}
@SuppressLint("InlinedApi")
fun getApkSignature(file: File): Signature? {
val path = file.absolutePath
val pkgInfo = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
app.packageManager.getPackageArchiveInfo(
path,
PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES.toLong())
)
else
app.packageManager.getPackageArchiveInfo(
path,
PackageManager.GET_SIGNING_CERTIFICATES
)
return pkgInfo?.signingInfo?.let { signingInfo ->
if (signingInfo.hasMultipleSigners()) {
val managerSignature = getManagerSignature()
signingInfo.apkContentsSigners.firstOrNull { it == managerSignature }
?: signingInfo.apkContentsSigners.lastOrNull()
} else {
signingInfo.signingCertificateHistory.lastOrNull()
}
}
}
fun getManagerSignature(): Signature = getSignature(app.packageName)
fun PackageInfo.label() = this.applicationInfo!!.loadLabel(app.packageManager).toString()
fun getResources(packageInfo: PackageInfo) = app.packageManager.getResourcesForApplication(packageInfo.applicationInfo!!)
fun getVersionCode(packageInfo: PackageInfo) = PackageInfoCompat.getLongVersionCode(packageInfo)
fun getSignature(packageName: String): Signature =
// Get the last signature from the list because we want the newest one if SigningInfo.getSigningCertificateHistory() was used.
PackageInfoCompat.getSignatures(app.packageManager, packageName).last()
@SuppressLint("InlinedApi")
fun hasSignature(packageName: String, signature: ByteArray) = PackageInfoCompat.hasSignatures(
app.packageManager,
packageName,
mapOf(signature to PackageManager.CERT_INPUT_RAW_X509),
false
)
suspend fun uninstallPackage(pkg: String, config: UninstallParametersDsl.() -> Unit = {}) = withContext(Dispatchers.IO) {
uninstaller.createSession(pkg) {
confirmation = Confirmation.IMMEDIATE

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">تعذر تحميل التعديلات. انقر لعرض الخطأ</string>
<string name="patches_not_downloaded">لم يتم تنزيل التصحيحات.</string>
<string name="patches_name_default">التعديلات</string>
<string name="patches_name_fallback">غير مُسمى</string>
<string name="source_name_fallback">غير مُسمى</string>
<string name="android_11_bug_dialog_title">خطأ Android 11</string>
<string name="android_11_bug_dialog_description">يجب منح إذن تثبيت التطبيق مسبقًا لتجنب خطأ في نظام Android 11 سيؤثر سلبًا على تجربة المستخدم.</string>
<string name="no_network_toast">لا يوجد اتصال بالإنترنت متاح</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Yamaqlar yüklənə bilmədi. Xətanı görmək üçün klikləyin</string>
<string name="patches_not_downloaded">Yamaqlar yüklənməyib.</string>
<string name="patches_name_default">Yamaqlar</string>
<string name="patches_name_fallback">Adsız</string>
<string name="source_name_fallback">Adsız</string>
<string name="android_11_bug_dialog_title">Android 11 səhvi</string>
<string name="android_11_bug_dialog_description">Tətbiqin quraşdırılması icazəsi, Android 11 sistemində istifadəçi təcrübəsinə mənfi təsir göstərəcək bir səhvin qarşısını almaq üçün əvvəlcədən verilməlidir.</string>
<string name="no_network_toast">İnternet bağlantısı yoxdur</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Патчы не ўдалося загрузіць. Націсніце, каб праглядзець памылку</string>
<string name="patches_not_downloaded">Выпраўленні не спампаваны.</string>
<string name="patches_name_default">Патчы</string>
<string name="patches_name_fallback">Без назвы</string>
<string name="source_name_fallback">Без назвы</string>
<string name="android_11_bug_dialog_title">Памылка Android 11</string>
<string name="android_11_bug_dialog_description">Дазвол на ўстаноўку праграм павінен быць выдадзены загадзя, каб пазбегнуць памылкі ў сістэме Android 11, якая негатыўна паўплывае на зручнасць карыстання.</string>
<string name="no_network_toast">Няма падключэння да інтэрнэту</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Пачовете не можаха да бъдат заредени. Кликнете, за да видите грешката</string>
<string name="patches_not_downloaded">Пачовете не са изтеглени.</string>
<string name="patches_name_default">Пачове</string>
<string name="patches_name_fallback">Без име</string>
<string name="source_name_fallback">Без име</string>
<string name="android_11_bug_dialog_title">Бъг в Android 11</string>
<string name="android_11_bug_dialog_description">Разрешението за инсталиране на приложението трябва да бъде предоставено предварително, за да се избегне бъг в системата на Android 11, който ще повлияе негативно на потребителското изживяване.</string>
<string name="no_network_toast">Няма налична интернет връзка</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">প্যাচ লোড করা যায়নি। ত্রুটি দেখতে ক্লিক করুন</string>
<string name="patches_not_downloaded">প্যাচগুলি ডাউনলোড করা হয়নি।</string>
<string name="patches_name_default">প্যাচগুলি</string>
<string name="patches_name_fallback">নামহীন</string>
<string name="source_name_fallback">নামহীন</string>
<string name="android_11_bug_dialog_title">Android 11 বাগ</string>
<string name="android_11_bug_dialog_description">Android 11 সিস্টেমে একটি বাগ এড়াতে অ্যাপ ইনস্টলেশনের অনুমতি আগে থেকেই দিতে হবে, যা ব্যবহারকারীর অভিজ্ঞতার উপর নেতিবাচক প্রভাব ফেলবে।</string>
<string name="no_network_toast">কোনো ইন্টারনেট সংযোগ উপলব্ধ নেই</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Záplaty nebylo možné načíst. Kliknutím zobrazíte chybu.</string>
<string name="patches_not_downloaded">Záplaty nebyly staženy.</string>
<string name="patches_name_default">Záplaty</string>
<string name="patches_name_fallback">Nepojmenováno</string>
<string name="source_name_fallback">Nepojmenováno</string>
<string name="android_11_bug_dialog_title">Chyba Androidu 11</string>
<string name="android_11_bug_dialog_description">Povolení k instalaci aplikací musí být uděleno předem, aby se předešlo chybě v systému Android 11, která by negativně ovlivnila uživatelský zážitek.</string>
<string name="no_network_toast">Připojení k internetu není dostupné</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Patches kunne ikke indlæses. Klik for at se fejlen</string>
<string name="patches_not_downloaded">Patches er ikke blevet downloadet.</string>
<string name="patches_name_default">Patches</string>
<string name="patches_name_fallback">Unavngivet</string>
<string name="source_name_fallback">Unavngivet</string>
<string name="android_11_bug_dialog_title">Android 11-fejl</string>
<string name="android_11_bug_dialog_description">App-installationstilladelsen skal gives på forhånd for at undgå en fejl i Android 11-systemet, som vil påvirke brugeroplevelsen negativt.</string>
<string name="no_network_toast">Ingen internetforbindelse tilgængelig</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Patches konnten nicht geladen werden. Klicken Sie hier, um den Fehler anzuzeigen.</string>
<string name="patches_not_downloaded">Patches wurden nicht heruntergeladen.</string>
<string name="patches_name_default">Patches</string>
<string name="patches_name_fallback">Unbenannt</string>
<string name="source_name_fallback">Unbenannt</string>
<string name="android_11_bug_dialog_title">Android 11 Fehler</string>
<string name="android_11_bug_dialog_description">Die Berechtigung zur App-Installation muss im Voraus erteilt werden, um einen Fehler im Android 11-System zu vermeiden, der die Benutzererfahrung negativ beeinflusst.</string>
<string name="no_network_toast">Keine Internetverbindung verfügbar</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Δεν ήταν δυνατή η φόρτωση των τροποποιήσεων. Πατήστε για να δείτε το σφάλμα</string>
<string name="patches_not_downloaded">Οι ενημερώσεις δεν έχουν ληφθεί.</string>
<string name="patches_name_default">Τροποποιήσεις</string>
<string name="patches_name_fallback">Χωρίς Όνομα</string>
<string name="source_name_fallback">Χωρίς Όνομα</string>
<string name="android_11_bug_dialog_title">Σφάλμα Android 11</string>
<string name="android_11_bug_dialog_description">Η άδεια εγκατάστασης εφαρμογών πρέπει να χορηγηθεί εκ των προτέρων για να αποφευχθεί ένα σφάλμα του συστήματος Android 11 που θα επηρεάσει αρνητικά την εμπειρία του χρήστη.</string>
<string name="no_network_toast">Δεν υπάρχει διαθέσιμη σύνδεση στο διαδίκτυο</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">No se pudieron cargar los parches. Haz clic para ver el error</string>
<string name="patches_not_downloaded">Los parches no se han descargado.</string>
<string name="patches_name_default">Parches</string>
<string name="patches_name_fallback">Sin nombre</string>
<string name="source_name_fallback">Sin nombre</string>
<string name="android_11_bug_dialog_title">Error de Android 11</string>
<string name="android_11_bug_dialog_description">El permiso de instalación de la aplicación debe concederse con antelación para evitar un error en el sistema Android 11 que afectará negativamente a la experiencia del usuario.</string>
<string name="no_network_toast">No hay conexión a internet disponible</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Paikasid ei saanud laadida. Vea vaatamiseks klõpsake.</string>
<string name="patches_not_downloaded">Paigad pole alla laaditud.</string>
<string name="patches_name_default">Paigad</string>
<string name="patches_name_fallback">Nimetu</string>
<string name="source_name_fallback">Nimetu</string>
<string name="android_11_bug_dialog_title">Android 11 viga</string>
<string name="android_11_bug_dialog_description">Rakenduse installimise luba tuleb eelnevalt anda, et vältida Android 11 süsteemi viga, mis mõjutab negatiivselt kasutajakogemust.</string>
<string name="no_network_toast">Internetiühendus puudub</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Paikkauksia ei voitu ladata. Napauta nähdäksesi virheen</string>
<string name="patches_not_downloaded">Päivityksiä ei ole ladattu.</string>
<string name="patches_name_default">Paikkaukset</string>
<string name="patches_name_fallback">Nimetön</string>
<string name="source_name_fallback">Nimetön</string>
<string name="android_11_bug_dialog_title">Android 11 -virhe</string>
<string name="android_11_bug_dialog_description">Sovelluksen asennusoikeus on myönnettävä etukäteen, jotta vältetään Android 11 -järjestelmän virhe, joka vaikuttaa negatiivisesti käyttökokemukseen.</string>
<string name="no_network_toast">Internet-yhteyttä ei ole käytettävissä</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Hindi ma-load ang mga patch. Mag-click upang tingnan ang error</string>
<string name="patches_not_downloaded">Hindi pa na-download ang mga patch.</string>
<string name="patches_name_default">Mga patch</string>
<string name="patches_name_fallback">Walang pangalan</string>
<string name="source_name_fallback">Walang pangalan</string>
<string name="android_11_bug_dialog_title">Bug sa Android 11</string>
<string name="android_11_bug_dialog_description">Ang pahintulot sa pag-install ng app ay dapat ibigay nang maaga upang maiwasan ang isang bug sa sistema ng Android 11 na magdulot ng negatibong epekto sa karanasan ng gumagamit.</string>
<string name="no_network_toast">Walang available na koneksyon sa internet</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Les patchs n\'ont pas pu être chargés, appuyez pour voir l\'erreur</string>
<string name="patches_not_downloaded">Les patchs n\'ont pas été téléchargés.</string>
<string name="patches_name_default">Patchs</string>
<string name="patches_name_fallback">Sans nom</string>
<string name="source_name_fallback">Sans nom</string>
<string name="android_11_bug_dialog_title">Bug Android 11</string>
<string name="android_11_bug_dialog_description">L\'autorisation d\'installation d\'applications doit être accordée à l\'avance pour contourner un bug système dans Android 11 qui affecterait négativement l\'expérience utilisateur.</string>
<string name="no_network_toast">Aucune connexion Internet disponible</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Níorbh fhéidir paistí a luchtú. Cliceáil chun an earráid a fheiceáil</string>
<string name="patches_not_downloaded">Níor íoslódáladh na paistí.</string>
<string name="patches_name_default">Paistí</string>
<string name="patches_name_fallback">Gan ainm</string>
<string name="source_name_fallback">Gan ainm</string>
<string name="android_11_bug_dialog_title">Fabht Android 11</string>
<string name="android_11_bug_dialog_description">Ní mór an cead suiteála aip a dheonú roimh ré chun fabht sa chóras Android 11 a sheachaint a dhéanfaidh difear diúltach d\'eispéireas an úsáideora.</string>
<string name="no_network_toast">Níl aon nasc idirlín ar fáil</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Patchevi se nisu mogli učitati. Kliknite za prikaz pogreške</string>
<string name="patches_not_downloaded">Patchevi nisu preuzeti.</string>
<string name="patches_name_default">Patchevi</string>
<string name="patches_name_fallback">Neimenovano</string>
<string name="source_name_fallback">Neimenovano</string>
<string name="android_11_bug_dialog_title">Greška Androida 11</string>
<string name="app_source_dialog_option_auto">Automatski</string>
<string name="app_source_dialog_option_auto_description">Koristite instaliranu aplikaciju, zatim preuzeti APK, a zatim dostupne programe za preuzimanje</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">A javítások betöltése sikertelen. Kattintson a hibák megtekintéséhez.</string>
<string name="patches_not_downloaded">A javítások nincsenek letöltve.</string>
<string name="patches_name_default">Javítások</string>
<string name="patches_name_fallback">Névtelen</string>
<string name="source_name_fallback">Névtelen</string>
<string name="android_11_bug_dialog_title">Android 11 hiba</string>
<string name="android_11_bug_dialog_description">Az alkalmazástelepítési engedélyt előre meg kell adni, hogy elkerülhető legyen egy Android 11 rendszerhiba, amely negatívan befolyásolná a felhasználói élményt.</string>
<string name="no_network_toast">Nincs elérhető internetkapcsolat</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Կարկատանները չհաջողվեց բեռնել։ Կտտացրեք՝ սխալը դիտելու համար</string>
<string name="patches_not_downloaded">Կարկատանները չեն ներբեռնվել։</string>
<string name="patches_name_default">Կարկատաններ</string>
<string name="patches_name_fallback">Անանուն</string>
<string name="source_name_fallback">Անանուն</string>
<string name="android_11_bug_dialog_title">Android 11-ի սխալ</string>
<string name="android_11_bug_dialog_description">Հավելվածի տեղադրման թույլտվությունը պետք է տրվի նախապես՝ խուսափելու համար Android 11 համակարգի սխալից, որը բացասաբար կազդի օգտագործողի փորձի վրա։</string>
<string name="no_network_toast">Ինտերնետ կապ չկա</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Tambalan tidak dapat dimuat. Klik untuk melihat kesalahan</string>
<string name="patches_not_downloaded">Tambalan belum diunduh.</string>
<string name="patches_name_default">Tambalan</string>
<string name="patches_name_fallback">Tanpa nama</string>
<string name="source_name_fallback">Tanpa nama</string>
<string name="android_11_bug_dialog_title">Bug Android 11</string>
<string name="android_11_bug_dialog_description">Izin pemasangan aplikasi harus diberikan sebelumnya untuk menghindari bug di sistem Android 11 yang akan berdampak negatif pada pengalaman pengguna.</string>
<string name="no_network_toast">Tidak ada koneksi internet yang tersedia</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Le patch non sono state caricate. Fai clic per visualizzare l\'errore</string>
<string name="patches_not_downloaded">Le patch non sono state scaricate.</string>
<string name="patches_name_default">Patch</string>
<string name="patches_name_fallback">Senza nome</string>
<string name="source_name_fallback">Senza nome</string>
<string name="android_11_bug_dialog_title">Bug di Android 11</string>
<string name="android_11_bug_dialog_description">L\'autorizzazione all\'installazione dell\'app deve essere concessa in anticipo per evitare un bug nel sistema Android 11 che influenzerà negativamente l\'esperienza utente.</string>
<string name="no_network_toast">Nessuna connessione internet disponibile</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">לא ניתן לטעון טלאים. לחץ כדי להציג את השגיאה</string>
<string name="patches_not_downloaded">הטלאים לא הורדו.</string>
<string name="patches_name_default">טלאים</string>
<string name="patches_name_fallback">ללא שם</string>
<string name="source_name_fallback">ללא שם</string>
<string name="android_11_bug_dialog_title">באג אנדרואיד 11</string>
<string name="android_11_bug_dialog_description">יש להעניק את הרשאת התקנת היישומים מראש כדי למנוע באג במערכת אנדרואיד 11 שישפיע לרעה על חווית המשתמש.</string>
<string name="no_network_toast">אין חיבור אינטרנט זמין</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">パッチを読み込めませんでした。クリックしてエラーを表示</string>
<string name="patches_not_downloaded">パッチがダウンロードされていません。</string>
<string name="patches_name_default">パッチ</string>
<string name="patches_name_fallback">名無し</string>
<string name="source_name_fallback">名無し</string>
<string name="android_11_bug_dialog_title">Android 11 のバグ</string>
<string name="android_11_bug_dialog_description">アプリのインストール権限は、Android 11 システムのバグがユーザーエクスペリエンスに悪影響を与えるのを避けるため、事前に付与しておく必要があります。</string>
<string name="no_network_toast">インターネット接続がありません</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">패치를 불러올 수 없습니다. 오류를 보려면 여기를 탭하세요</string>
<string name="patches_not_downloaded">패치가 다운로드되지 않았습니다.</string>
<string name="patches_name_default">패치</string>
<string name="patches_name_fallback">이름 없음</string>
<string name="source_name_fallback">이름 없음</string>
<string name="android_11_bug_dialog_title">Android 11 버그</string>
<string name="android_11_bug_dialog_description">사용자 환경에 부정적인 영향을 미치는 Android 11 시스템 버그를 방지하려면 앱 설치 권한을 미리 부여해야 합니다.</string>
<string name="no_network_toast">인터넷에 연결할 수 없습니다</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Taisymų nepavyko įkelti. Spustelėkite, kad peržiūrėtumėte klaidą.</string>
<string name="patches_not_downloaded">Lopai nebuvo atsisiųsti.</string>
<string name="patches_name_default">Taisymai</string>
<string name="patches_name_fallback">Be pavadinimo</string>
<string name="source_name_fallback">Be pavadinimo</string>
<string name="android_11_bug_dialog_title">„Android 11“ klaida</string>
<string name="android_11_bug_dialog_description">Programos diegimo leidimas turi būti suteiktas iš anksto, kad būtų išvengta „Android 11“ sistemos klaidos, kuri neigiamai paveiks naudotojo patirtį.</string>
<string name="no_network_toast">Nėra interneto ryšio</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Ielāpus nevarēja ielādēt. Noklikšķiniet, lai apskatītu kļūdu</string>
<string name="patches_not_downloaded">Plāksteri nav lejupielādēti.</string>
<string name="patches_name_default">Ielāpi</string>
<string name="patches_name_fallback">Bez nosaukuma</string>
<string name="source_name_fallback">Bez nosaukuma</string>
<string name="android_11_bug_dialog_title">Android 11 kļūda</string>
<string name="android_11_bug_dialog_description">Lietotņu instalēšanas atļauja jāpiešķir iepriekš, lai izvairītos no kļūdas Android 11 sistēmā, kas negatīvi ietekmēs lietotāja pieredzi.</string>
<string name="no_network_toast">Nav pieejams interneta savienojums</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Patches konden niet worden geladen. Klik om de fout te bekijken</string>
<string name="patches_not_downloaded">Patches zijn niet gedownload.</string>
<string name="patches_name_default">Patches</string>
<string name="patches_name_fallback">Naamloos</string>
<string name="source_name_fallback">Naamloos</string>
<string name="android_11_bug_dialog_title">Android 11-bug</string>
<string name="android_11_bug_dialog_description">De app-installatietoestemming moet vooraf worden verleend om een bug in het Android 11-systeem te voorkomen die de gebruikerservaring negatief zal beïnvloeden.</string>
<string name="no_network_toast">Geen internetverbinding beschikbaar</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Nie można było załadować łatek. Kliknij, aby wyświetlić błąd</string>
<string name="patches_not_downloaded">Poprawki nie zostały pobrane.</string>
<string name="patches_name_default">Łatki</string>
<string name="patches_name_fallback">Bez nazwy</string>
<string name="source_name_fallback">Bez nazwy</string>
<string name="android_11_bug_dialog_title">Błąd Androida 11</string>
<string name="android_11_bug_dialog_description">Zezwolenie na instalację aplikacji musi zostać udzielone z wyprzedzeniem, aby uniknąć błędu w systemie Android 11, który negatywnie wpłynie na doświadczenia użytkownika.</string>
<string name="no_network_toast">Brak połączenia z internetem</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Os patches não puderam ser carregados. Clique para ver o erro</string>
<string name="patches_not_downloaded">Os patches não foram baixados.</string>
<string name="patches_name_default">Patches</string>
<string name="patches_name_fallback">Sem nome</string>
<string name="source_name_fallback">Sem nome</string>
<string name="android_11_bug_dialog_title">Bug do Android 11</string>
<string name="android_11_bug_dialog_description">A permissão de instalação do aplicativo deve ser concedida antecipadamente para evitar um bug no sistema Android 11 que afetará negativamente a experiência do usuário.</string>
<string name="no_network_toast">Nenhuma conexão de internet disponível</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Não foi possível carregar os patches. Clique para ver o erro</string>
<string name="patches_not_downloaded">Os patches não foram baixados.</string>
<string name="patches_name_default">Patches</string>
<string name="patches_name_fallback">Sem nome</string>
<string name="source_name_fallback">Sem nome</string>
<string name="android_11_bug_dialog_title">Bug do Android 11</string>
<string name="android_11_bug_dialog_description">A permissão de instalação da aplicação deve ser concedida antecipadamente para evitar um bug no sistema Android 11 que afetará negativamente a experiência do utilizador.</string>
<string name="no_network_toast">Nenhuma conexão de internet disponível</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Patch-urile nu au putut fi încărcate. Apasă pentru a vedea eroarea.</string>
<string name="patches_not_downloaded">Pachetele nu au fost descărcate.</string>
<string name="patches_name_default">Patch-uri</string>
<string name="patches_name_fallback">Fără nume</string>
<string name="source_name_fallback">Fără nume</string>
<string name="android_11_bug_dialog_title">Eroare Android 11</string>
<string name="android_11_bug_dialog_description">Permisiunea de instalare a aplicației trebuie acordată în avans pentru a evita o eroare în sistemul Android 11 care va afecta negativ experiența utilizatorului.</string>
<string name="no_network_toast">Nicio conexiune la internet disponibilă</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Патчи не могут быть загружены. Нажмите, чтобы просмотреть ошибку</string>
<string name="patches_not_downloaded">Патчи не были загружены.</string>
<string name="patches_name_default">Патчи</string>
<string name="patches_name_fallback">Без названия</string>
<string name="source_name_fallback">Без названия</string>
<string name="android_11_bug_dialog_title">Ошибка Android 11</string>
<string name="android_11_bug_dialog_description">Разрешение на установку приложения должно быть предоставлено заранее, чтобы избежать ошибки в системе Android 11, которая негативно скажется на пользовательском опыте.</string>
<string name="no_network_toast">Нет доступного интернет-соединения</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Patche sa nepodarilo načítať. Kliknutím zobrazíte chybu.</string>
<string name="patches_not_downloaded">Záplaty neboli stiahnuté.</string>
<string name="patches_name_default">Patche</string>
<string name="patches_name_fallback">Bez názvu</string>
<string name="source_name_fallback">Bez názvu</string>
<string name="android_11_bug_dialog_title">Chyba systému Android 11</string>
<string name="android_11_bug_dialog_description">Povolenie na inštaláciu aplikácií musí byť udelené vopred, aby sa predišlo chybe v systéme Android 11, ktorá negatívne ovplyvní používateľský zážitok.</string>
<string name="no_network_toast">Nie je k dispozícii žiadne internetové pripojenie</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Popravkov ni bilo mogoče naložiti. Kliknite za ogled napake.</string>
<string name="patches_not_downloaded">Popravki niso bili preneseni.</string>
<string name="patches_name_default">Popravki</string>
<string name="patches_name_fallback">Nepoimenovano</string>
<string name="source_name_fallback">Nepoimenovano</string>
<string name="android_11_bug_dialog_title">Napaka v Androidu 11</string>
<string name="android_11_bug_dialog_description">Dovoljenje za namestitev aplikacije mora biti podeljeno vnaprej, da se izognete napaki v sistemu Android 11, ki bi negativno vplivala na uporabniško izkušnjo.</string>
<string name="no_network_toast">Ni na voljo internetne povezave</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Patch-et nuk mund të ngarkoheshin. Kliko për të parë gabimin.</string>
<string name="patches_not_downloaded">Arnat nuk janë shkarkuar.</string>
<string name="patches_name_default">Patch-et</string>
<string name="patches_name_fallback">Pa emër</string>
<string name="source_name_fallback">Pa emër</string>
<string name="android_11_bug_dialog_title">Gabim në Android 11</string>
<string name="android_11_bug_dialog_description">Leja për instalimin e aplikacionit duhet të jepet paraprakisht për të shmangur një gabim në sistemin Android 11 që do të ndikojë negativisht në përvojën e përdoruesit.</string>
<string name="no_network_toast">Nuk ka lidhje interneti në dispozicion</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Zakrpe se nisu mogle učitati. Kliknite da vidite grešku</string>
<string name="patches_not_downloaded">Zakrpe nisu preuzete.</string>
<string name="patches_name_default">Zakrpe</string>
<string name="patches_name_fallback">Neimenovano</string>
<string name="source_name_fallback">Neimenovano</string>
<string name="android_11_bug_dialog_title">Greška Androida 11</string>
<string name="android_11_bug_dialog_description">Dozvola za instalaciju aplikacije mora biti data unapred kako bi se izbegla greška u sistemu Android 11 koja će negativno uticati na korisničko iskustvo.</string>
<string name="no_network_toast">Nema dostupne internet veze</string>

View File

@@ -62,7 +62,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Закрпе нису могле да буду учитане. Кликните да бисте видели грешку.</string>
<string name="patches_not_downloaded">Закрпе нису преузете.</string>
<string name="patches_name_default">Закрпе</string>
<string name="patches_name_fallback">Неименовано</string>
<string name="source_name_fallback">Неименовано</string>
<string name="android_11_bug_dialog_title">Грешка Android-а 11</string>
<string name="android_11_bug_dialog_description">Дозвола за инсталацију апликације мора бити додељена унапред да би се избегла грешка у систему Android 11 која ће негативно утицати на корисничко искуство.</string>
<string name="no_network_toast">Нема доступне интернет везе</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Korrigeringar kunde inte läsas in. Klicka för att visa felet</string>
<string name="patches_not_downloaded">Patchar har inte laddats ned.</string>
<string name="patches_name_default">Korrigeringar</string>
<string name="patches_name_fallback">Namnlös</string>
<string name="source_name_fallback">Namnlös</string>
<string name="android_11_bug_dialog_title">Android 11-fel</string>
<string name="android_11_bug_dialog_description">Behörigheten för appinstallation måste beviljas i förväg för att undvika en fel i Android 11-systemet som negativt påverkar användarupplevelsen.</string>
<string name="no_network_toast">Ingen internetanslutning är tillgänglig</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">ไม่สามารถโหลดแพตช์ได้ คลิกเพื่อดูข้อผิดพลาด</string>
<string name="patches_not_downloaded">ยังไม่ได้ดาวน์โหลดแพตช์</string>
<string name="patches_name_default">แพตช์</string>
<string name="patches_name_fallback">ไม่มีชื่อ</string>
<string name="source_name_fallback">ไม่มีชื่อ</string>
<string name="android_11_bug_dialog_title">ข้อผิดพลาด Android 11</string>
<string name="android_11_bug_dialog_description">ต้องได้รับอนุญาตการติดตั้งแอปก่อนเวลาเพื่อหลีกเลี่ยงข้อผิดพลาดในระบบ Android 11 ซึ่งจะส่งผลเสียต่อประสบการณ์ผู้ใช้</string>
<string name="no_network_toast">ไม่มีการเชื่อมต่ออินเทอร์เน็ต</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Yamalar yüklenemedi. Hatayı görmek için tıklayın</string>
<string name="patches_not_downloaded">Yamalar indirilmedi.</string>
<string name="patches_name_default">Yamalar</string>
<string name="patches_name_fallback">Adsız</string>
<string name="source_name_fallback">Adsız</string>
<string name="android_11_bug_dialog_title">Android 11 hatası</string>
<string name="android_11_bug_dialog_description">Uygulama yükleme izni, Android 11 sistemindeki kullanıcı deneyimini olumsuz etkileyecek bir hatayı önlemek için önceden verilmelidir.</string>
<string name="no_network_toast">İnternet bağlantısı yok</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Не вдалося завантажити патчі. Натисніть, щоб переглянути помилку</string>
<string name="patches_not_downloaded">Патчі не завантажено.</string>
<string name="patches_name_default">Патчі</string>
<string name="patches_name_fallback">Без назви</string>
<string name="source_name_fallback">Без назви</string>
<string name="android_11_bug_dialog_title">Помилка Android 11</string>
<string name="android_11_bug_dialog_description">Дозвіл на встановлення програми має бути наданий заздалегідь, щоб уникнути помилки в системі Android 11, яка негативно вплине на взаємодію з користувачем.</string>
<string name="no_network_toast">Немає підключення до Інтернету</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">Không thể tải các bản vá. Nhấn để xem lỗi</string>
<string name="patches_not_downloaded">Các bản vá chưa được tải xuống.</string>
<string name="patches_name_default">Các bản vá</string>
<string name="patches_name_fallback">Chưa đặt tên</string>
<string name="source_name_fallback">Chưa đặt tên</string>
<string name="android_11_bug_dialog_title">Lỗi Android 11</string>
<string name="android_11_bug_dialog_description">Phải cấp quyền cài đặt ứng dụng trước để tránh lỗi trong hệ thống Android 11, vốn sẽ ảnh hưởng tiêu cực đến trải nghiệm người dùng.</string>
<string name="no_network_toast">Không có kết nối internet khả dụng</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">无法加载补丁。点击查看错误。</string>
<string name="patches_not_downloaded">补丁尚未下载。</string>
<string name="patches_name_default">补丁</string>
<string name="patches_name_fallback">未命名</string>
<string name="source_name_fallback">未命名</string>
<string name="android_11_bug_dialog_title">Android 11 错误</string>
<string name="android_11_bug_dialog_description">必须提前授予应用安装权限,以避免 Android 11 系统中的一个错误,该错误将对用户体验产生负面影响。</string>
<string name="no_network_toast">无可用互联网连接</string>

View File

@@ -59,7 +59,7 @@ Second \"item\" text"</string>
<string name="patches_error_description">無法載入修補程式。按一下以檢視錯誤</string>
<string name="patches_not_downloaded">修補程式尚未下載。</string>
<string name="patches_name_default">修補程式</string>
<string name="patches_name_fallback">未命名</string>
<string name="source_name_fallback">未命名</string>
<string name="android_11_bug_dialog_title">Android 11 錯誤</string>
<string name="android_11_bug_dialog_description">必須提前授予應用程式安裝權限,以避免 Android 11 系統中的錯誤對使用者體驗產生負面影響。</string>
<string name="no_network_toast">無可用的網際網路連線</string>

View File

@@ -26,6 +26,7 @@ Second \"item\" text"</string>
<string name="onboarding_updates_subtitle">Configure automatic updates to keep ReVanced Manager and patches up to date</string>
<string name="banner_sources_not_downloaded_description">Patches and downloaders could not be downloaded during setup. Tap update to download them.</string>
<string name="banner_sources_not_updated_description">Patches or downloaders have updates, but were not downloaded because the network is metered. Tap update to download them.</string>
<string name="onboarding_updates_note">ReVanced Manager will connect to %s in order to download initial versions if your device is connected to the internet.</string>
<string name="retry">Retry</string>
@@ -76,13 +77,11 @@ Second \"item\" text"</string>
<string name="patches_error_description">Patches could not be loaded. Click to view the error</string>
<string name="patches_not_downloaded">Patches has not been downloaded.</string>
<string name="patches_name_default">Patches</string>
<string name="patches_name_fallback">Unnamed</string>
<string name="source_name_fallback">Unnamed</string>
<string name="android_11_bug_dialog_title">Android 11 bug</string>
<string name="android_11_bug_dialog_description">The app installation permission must be granted ahead of time to avoid a bug in the Android 11 system that will negatively affect the user experience.</string>
<string name="no_network_toast">No internet connection available</string>
<string name="selected_app_meta_any_version">Any available version</string>
<string name="app_source_dialog_title">Select source</string>
<string name="app_source_dialog_option_auto">Auto</string>
@@ -97,7 +96,6 @@ Second \"item\" text"</string>
<string name="patch_selection_changed_warning">Selection of patches has been changed</string>
<string name="no_patches_selected">No patches selected</string>
<string name="network_unavailable_warning">Your device is not connected to the internet. Downloading will fail later.</string>
<string name="network_metered_warning">You are currently on a metered connection. Data charges from your service provider may apply.</string>
<string name="apk_source_selector_item">Select APK source</string>
@@ -118,7 +116,7 @@ Second \"item\" text"</string>
<string name="auto_updates_dialog_description">Do you want ReVanced Manager to periodically check for updates for the following components?</string>
<string name="auto_updates_dialog_manager">ReVanced Manager</string>
<string name="auto_updates_dialog_patches">ReVanced Patches</string>
<string name="auto_updates_dialog_downloaders">ReVanced Manager: Downloaders</string>
<string name="auto_updates_dialog_downloaders">APK Downloaders</string>
<string name="auto_updates_dialog_note">These settings can be changed later.
ReVanced Manager will connect to %s in order to download initial versions if your device is connected to the internet.</string>
@@ -323,6 +321,7 @@ You will not be able to update the previously installed apps from this source.</
<string name="device_memory_limit_format">%1$dMB (Normal) - %2$dMB (Large)</string>
<string name="patches_force_download">Force download all patches</string>
<string name="patches_reset">Reset patches</string>
<string name="downloaders_reset">Reset downloaders</string>
<string name="reset_onboarding">Reset onboarding</string>
<string name="reset_onboarding_description">Show the onboarding screen on next app launch</string>
<string name="reset_announcement">Reset announcement read</string>