mirror of
https://github.com/ReVanced/revanced-manager
synced 2026-04-25 17:15:36 +02:00
feat: Improve source system and fix connectivity issues (#3137)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -78,7 +78,7 @@ class ManagerApplication : Application() {
|
||||
arrayOf(patchBundleRepository, downloaderRepository).forEach {
|
||||
with(it) {
|
||||
reload()
|
||||
updateCheck()
|
||||
updateCheck(force = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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() }
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
),
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user