mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-01-10 21:26:18 +00:00
feat: Multiple downloader per APK
This commit is contained in:
@@ -7,8 +7,6 @@ import app.revanced.manager.downloader.GetScope
|
|||||||
import app.revanced.manager.downloader.Scope
|
import app.revanced.manager.downloader.Scope
|
||||||
import app.revanced.manager.downloader.Downloader
|
import app.revanced.manager.downloader.Downloader
|
||||||
import app.revanced.manager.downloader.DownloaderHostApi
|
import app.revanced.manager.downloader.DownloaderHostApi
|
||||||
import app.revanced.manager.downloader.webview.IWebView
|
|
||||||
import app.revanced.manager.downloader.webview.IWebViewEvents
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import android.util.Log
|
|||||||
import app.revanced.manager.data.room.AppDatabase
|
import app.revanced.manager.data.room.AppDatabase
|
||||||
import app.revanced.manager.data.room.downloader.TrustedDownloader
|
import app.revanced.manager.data.room.downloader.TrustedDownloader
|
||||||
import app.revanced.manager.domain.manager.PreferencesManager
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.network.downloader.DownloaderState
|
import app.revanced.manager.network.downloader.DownloaderPackageState
|
||||||
import app.revanced.manager.network.downloader.LoadedDownloader
|
import app.revanced.manager.network.downloader.LoadedDownloader
|
||||||
import app.revanced.manager.network.downloader.ParceledDownloaderData
|
import app.revanced.manager.network.downloader.ParceledDownloaderData
|
||||||
import app.revanced.manager.downloader.DownloaderBuilder
|
import app.revanced.manager.downloader.DownloaderBuilder
|
||||||
@@ -33,93 +33,98 @@ class DownloaderRepository(
|
|||||||
db: AppDatabase
|
db: AppDatabase
|
||||||
) {
|
) {
|
||||||
private val trustDao = db.trustedDownloaderDao()
|
private val trustDao = db.trustedDownloaderDao()
|
||||||
private val _downloaderStates = MutableStateFlow(emptyMap<String, DownloaderState>())
|
private val _downloaderPackageStates = MutableStateFlow(emptyMap<String, DownloaderPackageState>())
|
||||||
val downloaderStates = _downloaderStates.asStateFlow()
|
val downloaderPackageStates = _downloaderPackageStates.asStateFlow()
|
||||||
val loadedDownloaderFlow = downloaderStates.map { states ->
|
val loadedDownloaderPackageFlow = downloaderPackageStates.map { states ->
|
||||||
states.values.filterIsInstance<DownloaderState.Loaded>().map { it.downloader }
|
states.values.filterIsInstance<DownloaderPackageState.Loaded>().flatMap { it.downloader }
|
||||||
}
|
}
|
||||||
|
|
||||||
private val acknowledgedDownloader = prefs.acknowledgedDownloader
|
private val acknowledgedPackageDownloader = prefs.acknowledgedDownloader
|
||||||
private val installedDownloaderPackageNames = MutableStateFlow(emptySet<String>())
|
private val installedDownloaderPackageNames = MutableStateFlow(emptySet<String>())
|
||||||
val newDownloaderPackageNames = combine(
|
val newDownloaderPackageNames = combine(
|
||||||
installedDownloaderPackageNames,
|
installedDownloaderPackageNames,
|
||||||
acknowledgedDownloader.flow
|
acknowledgedPackageDownloader.flow
|
||||||
) { installed, acknowledged ->
|
) { installed, acknowledged ->
|
||||||
installed subtract acknowledged
|
installed subtract acknowledged
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun reload() {
|
suspend fun reload() {
|
||||||
val downloader =
|
val downloaderPackages =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
pm.getPackagesWithFeature(DOWNLOADER_FEATURE)
|
pm.getPackagesWithFeature(DOWNLOADER_FEATURE)
|
||||||
.associate { it.packageName to loadDownloader(it.packageName) }
|
.associate { it.packageName to loadDownloader(it.packageName) }
|
||||||
}
|
}
|
||||||
|
|
||||||
_downloaderStates.value = downloader
|
_downloaderPackageStates.value = downloaderPackages
|
||||||
installedDownloaderPackageNames.value = downloader.keys
|
installedDownloaderPackageNames.value = downloaderPackages.keys
|
||||||
|
|
||||||
val acknowledgedDownloader = this@DownloaderRepository.acknowledgedDownloader.get()
|
val acknowledgedDownloader = this@DownloaderRepository.acknowledgedPackageDownloader.get()
|
||||||
val uninstalledDownloader = acknowledgedDownloader subtract installedDownloaderPackageNames.value
|
val uninstalledDownloader = acknowledgedDownloader subtract installedDownloaderPackageNames.value
|
||||||
if (uninstalledDownloader.isNotEmpty()) {
|
if (uninstalledDownloader.isNotEmpty()) {
|
||||||
Log.d(tag, "Uninstalled downloader: ${uninstalledDownloader.joinToString(", ")}")
|
Log.d(tag, "Uninstalled downloader: ${uninstalledDownloader.joinToString(", ")}")
|
||||||
this@DownloaderRepository.acknowledgedDownloader.update(acknowledgedDownloader subtract uninstalledDownloader)
|
this@DownloaderRepository.acknowledgedPackageDownloader.update(acknowledgedDownloader subtract uninstalledDownloader)
|
||||||
trustDao.removeAll(uninstalledDownloader)
|
trustDao.removeAll(uninstalledDownloader)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unwrapParceledData(data: ParceledDownloaderData): Pair<LoadedDownloader, Parcelable> {
|
fun unwrapParceledData(data: ParceledDownloaderData): Pair<LoadedDownloader, Parcelable> {
|
||||||
val downloader =
|
val downloader =
|
||||||
(_downloaderStates.value[data.downloaderPackageName] as? DownloaderState.Loaded)?.downloader
|
(_downloaderPackageStates.value[data.downloaderPackageName] as? DownloaderPackageState.Loaded)
|
||||||
?: throw Exception("Downloader with name ${data.downloaderPackageName} is not available")
|
?.downloader?.first { it.name == data.downloaderName }
|
||||||
|
?: throw Exception("Downloader package name ${data.downloaderPackageName} is not available")
|
||||||
|
|
||||||
return downloader to data.unwrapWith(downloader)
|
return downloader to data.unwrapWith(downloader)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadDownloader(packageName: String): DownloaderState {
|
private suspend fun loadDownloader(packageName: String): DownloaderPackageState {
|
||||||
try {
|
try {
|
||||||
if (!verify(packageName)) return DownloaderState.Untrusted
|
if (!verify(packageName)) return DownloaderPackageState.Untrusted
|
||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(tag, "Got exception while verifying downloader $packageName", e)
|
Log.e(tag, "Got exception while verifying downloader $packageName", e)
|
||||||
return DownloaderState.Failed(e)
|
return DownloaderPackageState.Failed(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
return try {
|
return try {
|
||||||
val packageInfo = pm.getPackageInfo(packageName, flags = PackageManager.GET_META_DATA)!!
|
val packageInfo = pm.getPackageInfo(packageName, flags = PackageManager.GET_META_DATA)!!
|
||||||
val className = packageInfo.applicationInfo!!.metaData.getString(METADATA_DOWNLOADER_CLASS)
|
val classNames = packageInfo.applicationInfo!!.metaData.getStringArray(METADATA_DOWNLOADER_CLASSES)
|
||||||
?: throw Exception("Missing metadata attribute $METADATA_DOWNLOADER_CLASS")
|
?: throw Exception("Missing metadata attribute $METADATA_DOWNLOADER_CLASSES")
|
||||||
|
|
||||||
val classLoader =
|
val classLoader =
|
||||||
PathClassLoader(packageInfo.applicationInfo!!.sourceDir, app.classLoader)
|
PathClassLoader(packageInfo.applicationInfo!!.sourceDir, app.classLoader)
|
||||||
val downloaderContext = app.createPackageContext(packageName, 0)
|
val downloaderContext = app.createPackageContext(packageName, 0)
|
||||||
|
|
||||||
val downloader = classLoader
|
val scopeImpl = object : Scope {
|
||||||
.loadClass(className)
|
override val hostPackageName = app.packageName
|
||||||
.getDownloaderBuilder()
|
override val downloaderPackageName = downloaderContext.packageName
|
||||||
.build(
|
}
|
||||||
scopeImpl = object : Scope {
|
|
||||||
override val hostPackageName = app.packageName
|
|
||||||
override val downloaderPackageName = downloaderContext.packageName
|
|
||||||
},
|
|
||||||
context = downloaderContext
|
|
||||||
)
|
|
||||||
|
|
||||||
DownloaderState.Loaded(
|
DownloaderPackageState.Loaded(
|
||||||
LoadedDownloader(
|
classNames.map { className ->
|
||||||
packageName,
|
val downloader = classLoader
|
||||||
with(pm) { packageInfo.label() },
|
.loadClass(className)
|
||||||
packageInfo.versionName!!,
|
.getDownloaderBuilder()
|
||||||
downloader.get,
|
.build(
|
||||||
downloader.download,
|
scopeImpl = scopeImpl,
|
||||||
classLoader
|
context = downloaderContext
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LoadedDownloader(
|
||||||
|
packageName,
|
||||||
|
with(pm) { packageInfo.label() },
|
||||||
|
packageInfo.versionName!!,
|
||||||
|
downloader.get,
|
||||||
|
downloader.download,
|
||||||
|
classLoader
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
throw e
|
throw e
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Log.e(tag, "Failed to load downloader $packageName", t)
|
Log.e(tag, "Failed to load downloader $packageName", t)
|
||||||
DownloaderState.Failed(t)
|
DownloaderPackageState.Failed(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +138,7 @@ class DownloaderRepository(
|
|||||||
|
|
||||||
reload()
|
reload()
|
||||||
prefs.edit {
|
prefs.edit {
|
||||||
acknowledgedDownloader += packageName
|
acknowledgedPackageDownloader += packageName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +146,7 @@ class DownloaderRepository(
|
|||||||
trustDao.remove(packageName).also { reload() }
|
trustDao.remove(packageName).also { reload() }
|
||||||
|
|
||||||
suspend fun acknowledgeAllNewDownloader() =
|
suspend fun acknowledgeAllNewDownloader() =
|
||||||
acknowledgedDownloader.update(installedDownloaderPackageNames.value)
|
acknowledgedPackageDownloader.update(installedDownloaderPackageNames.value)
|
||||||
|
|
||||||
private suspend fun verify(packageName: String): Boolean {
|
private suspend fun verify(packageName: String): Boolean {
|
||||||
val expectedSignature =
|
val expectedSignature =
|
||||||
@@ -152,7 +157,7 @@ class DownloaderRepository(
|
|||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
const val DOWNLOADER_FEATURE = "app.revanced.manager.downloader"
|
const val DOWNLOADER_FEATURE = "app.revanced.manager.downloader"
|
||||||
const val METADATA_DOWNLOADER_CLASS = "app.revanced.manager.downloader.class"
|
const val METADATA_DOWNLOADER_CLASSES = "app.revanced.manager.downloader.classes"
|
||||||
|
|
||||||
const val PUBLIC_STATIC = Modifier.PUBLIC or Modifier.STATIC
|
const val PUBLIC_STATIC = Modifier.PUBLIC or Modifier.STATIC
|
||||||
val Int.isPublicStatic get() = (this and PUBLIC_STATIC) == PUBLIC_STATIC
|
val Int.isPublicStatic get() = (this and PUBLIC_STATIC) == PUBLIC_STATIC
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package app.revanced.manager.network.downloader
|
||||||
|
|
||||||
|
sealed interface DownloaderPackageState {
|
||||||
|
data object Untrusted : DownloaderPackageState
|
||||||
|
|
||||||
|
data class Loaded(val downloader: List<LoadedDownloader>) : DownloaderPackageState
|
||||||
|
|
||||||
|
data class Failed(val throwable: Throwable) : DownloaderPackageState
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package app.revanced.manager.network.downloader
|
|
||||||
|
|
||||||
sealed interface DownloaderState {
|
|
||||||
data object Untrusted : DownloaderState
|
|
||||||
|
|
||||||
data class Loaded(val downloader: LoadedDownloader) : DownloaderState
|
|
||||||
|
|
||||||
data class Failed(val throwable: Throwable) : DownloaderState
|
|
||||||
}
|
|
||||||
@@ -11,10 +11,12 @@ import kotlinx.parcelize.Parcelize
|
|||||||
*/
|
*/
|
||||||
class ParceledDownloaderData private constructor(
|
class ParceledDownloaderData private constructor(
|
||||||
val downloaderPackageName: String,
|
val downloaderPackageName: String,
|
||||||
|
val downloaderName: String,
|
||||||
private val bundle: Bundle
|
private val bundle: Bundle
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
constructor(downloader: LoadedDownloader, data: Parcelable) : this(
|
constructor(downloader: LoadedDownloader, data: Parcelable) : this(
|
||||||
downloader.packageName,
|
downloader.packageName,
|
||||||
|
downloader.name,
|
||||||
createBundle(data)
|
createBundle(data)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -182,7 +182,7 @@ class PatcherWorker(
|
|||||||
|
|
||||||
is SelectedApp.Search -> {
|
is SelectedApp.Search -> {
|
||||||
runStep(StepId.DownloadAPK, args.onEvent) {
|
runStep(StepId.DownloadAPK, args.onEvent) {
|
||||||
downloaderRepository.loadedDownloaderFlow.first()
|
downloaderRepository.loadedDownloaderPackageFlow.first()
|
||||||
.firstNotNullOfOrNull { downloader ->
|
.firstNotNullOfOrNull { downloader ->
|
||||||
try {
|
try {
|
||||||
val getScope = object : GetScope {
|
val getScope = object : GetScope {
|
||||||
@@ -250,11 +250,21 @@ class PatcherWorker(
|
|||||||
tag,
|
tag,
|
||||||
"An exception occurred in the remote process while patching. ${e.originalStackTrace}".logFmt()
|
"An exception occurred in the remote process while patching. ${e.originalStackTrace}".logFmt()
|
||||||
)
|
)
|
||||||
args.onEvent(ProgressEvent.Failed(null, e.toRemoteError())) // Fallback if exception doesn't occur within step
|
args.onEvent(
|
||||||
|
ProgressEvent.Failed(
|
||||||
|
null,
|
||||||
|
e.toRemoteError()
|
||||||
|
)
|
||||||
|
) // Fallback if exception doesn't occur within step
|
||||||
Result.failure()
|
Result.failure()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(tag, "An exception occurred while patching".logFmt(), e)
|
Log.e(tag, "An exception occurred while patching".logFmt(), e)
|
||||||
args.onEvent(ProgressEvent.Failed(null, e.toRemoteError())) // Fallback if exception doesn't occur within step
|
args.onEvent(
|
||||||
|
ProgressEvent.Failed(
|
||||||
|
null,
|
||||||
|
e.toRemoteError()
|
||||||
|
)
|
||||||
|
) // Fallback if exception doesn't occur within step
|
||||||
Result.failure()
|
Result.failure()
|
||||||
} finally {
|
} finally {
|
||||||
patchedApk.delete()
|
patchedApk.delete()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package app.revanced.manager.ui.screen
|
package app.revanced.manager.ui.screen
|
||||||
|
|
||||||
|
import android.R.attr.name
|
||||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
@@ -201,7 +202,7 @@ fun SelectedAppInfoScreen(
|
|||||||
is SelectedApp.Installed -> stringResource(R.string.apk_source_installed)
|
is SelectedApp.Installed -> stringResource(R.string.apk_source_installed)
|
||||||
is SelectedApp.Download -> stringResource(
|
is SelectedApp.Download -> stringResource(
|
||||||
R.string.apk_source_downloader,
|
R.string.apk_source_downloader,
|
||||||
downloader.find { it.packageName == app.data.downloaderPackageName }?.name
|
downloader.find { it.packageName == app.data.downloaderPackageName && it.name == app.data.downloaderName }?.let { "${it.packageName} ${it.name}" }
|
||||||
?: app.data.downloaderPackageName
|
?: app.data.downloaderPackageName
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ import androidx.compose.ui.text.style.TextAlign
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.network.downloader.DownloaderState
|
import app.revanced.manager.network.downloader.DownloaderPackageState
|
||||||
import app.revanced.manager.ui.component.AppLabel
|
import app.revanced.manager.ui.component.AppLabel
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
import app.revanced.manager.ui.component.ConfirmDialog
|
import app.revanced.manager.ui.component.ConfirmDialog
|
||||||
@@ -102,98 +102,105 @@ fun DownloadsSettingsScreen(
|
|||||||
item {
|
item {
|
||||||
GroupHeader(stringResource(R.string.downloader))
|
GroupHeader(stringResource(R.string.downloader))
|
||||||
}
|
}
|
||||||
downloaderStates.forEach { (packageName, state) ->
|
|
||||||
item(key = packageName) {
|
|
||||||
var showDialog by rememberSaveable {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun dismiss() {
|
if (downloaderStates.isNotEmpty()) {
|
||||||
showDialog = false
|
downloaderStates.forEach { (packageName, state) ->
|
||||||
}
|
item(key = packageName) {
|
||||||
|
var showDialog by rememberSaveable {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
val packageInfo =
|
fun dismiss() {
|
||||||
remember(packageName) {
|
showDialog = false
|
||||||
viewModel.pm.getPackageInfo(
|
}
|
||||||
packageName
|
|
||||||
)
|
|
||||||
} ?: return@item
|
|
||||||
|
|
||||||
if (showDialog) {
|
val packageInfo =
|
||||||
val signature =
|
|
||||||
remember(packageName) {
|
remember(packageName) {
|
||||||
val androidSignature =
|
viewModel.pm.getPackageInfo(
|
||||||
viewModel.pm.getSignature(packageName)
|
packageName
|
||||||
val hash = MessageDigest.getInstance("SHA-256")
|
)
|
||||||
.digest(androidSignature.toByteArray())
|
} ?: return@item
|
||||||
hash.toHexString(format = HexFormat.UpperCase)
|
|
||||||
|
if (showDialog) {
|
||||||
|
val signature =
|
||||||
|
remember(packageName) {
|
||||||
|
val androidSignature =
|
||||||
|
viewModel.pm.getSignature(packageName)
|
||||||
|
val hash = MessageDigest.getInstance("SHA-256")
|
||||||
|
.digest(androidSignature.toByteArray())
|
||||||
|
hash.toHexString(format = HexFormat.UpperCase)
|
||||||
|
}
|
||||||
|
val appName = remember {
|
||||||
|
packageInfo.applicationInfo?.loadLabel(context.packageManager)
|
||||||
|
?.toString()
|
||||||
|
?: packageName
|
||||||
}
|
}
|
||||||
val appName = remember {
|
|
||||||
packageInfo.applicationInfo?.loadLabel(context.packageManager)
|
|
||||||
?.toString()
|
|
||||||
?: packageName
|
|
||||||
}
|
|
||||||
|
|
||||||
when (state) {
|
|
||||||
is DownloaderState.Loaded -> TrustDialog(
|
|
||||||
title = R.string.downloader_revoke_trust_dialog_title,
|
|
||||||
body = stringResource(
|
|
||||||
R.string.downloader_trust_dialog_body,
|
|
||||||
packageName,
|
|
||||||
signature
|
|
||||||
),
|
|
||||||
downloaderName = appName,
|
|
||||||
signature = signature,
|
|
||||||
onDismiss = ::dismiss,
|
|
||||||
onConfirm = {
|
|
||||||
viewModel.revokeDownloaderTrust(packageName)
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
is DownloaderState.Failed -> ExceptionViewerDialog(
|
|
||||||
text = remember(state.throwable) {
|
|
||||||
state.throwable.stackTraceToString()
|
|
||||||
},
|
|
||||||
onDismiss = ::dismiss
|
|
||||||
)
|
|
||||||
|
|
||||||
is DownloaderState.Untrusted -> TrustDialog(
|
|
||||||
title = R.string.downloader_trust_dialog_title,
|
|
||||||
body = stringResource(
|
|
||||||
R.string.downloader_trust_dialog_body
|
|
||||||
),
|
|
||||||
downloaderName = appName,
|
|
||||||
signature = signature,
|
|
||||||
onDismiss = ::dismiss,
|
|
||||||
onConfirm = {
|
|
||||||
viewModel.trustDownloader(packageName)
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SettingsListItem(
|
|
||||||
modifier = Modifier.clickable { showDialog = true },
|
|
||||||
headlineContent = {
|
|
||||||
AppLabel(
|
|
||||||
packageInfo = packageInfo,
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
},
|
|
||||||
supportingContent = stringResource(
|
|
||||||
when (state) {
|
when (state) {
|
||||||
is DownloaderState.Loaded -> R.string.downloader_state_trusted
|
is DownloaderPackageState.Loaded -> TrustDialog(
|
||||||
is DownloaderState.Failed -> R.string.downloader_state_failed
|
title = R.string.downloader_revoke_trust_dialog_title,
|
||||||
is DownloaderState.Untrusted -> R.string.downloader_state_untrusted
|
body = stringResource(
|
||||||
|
R.string.downloader_trust_dialog_body,
|
||||||
|
packageName,
|
||||||
|
signature
|
||||||
|
),
|
||||||
|
downloaderName = appName,
|
||||||
|
signature = signature,
|
||||||
|
onDismiss = ::dismiss,
|
||||||
|
onConfirm = {
|
||||||
|
viewModel.revokeDownloaderTrust(packageName)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
is DownloaderPackageState.Failed -> ExceptionViewerDialog(
|
||||||
|
text = remember(state.throwable) {
|
||||||
|
state.throwable.stackTraceToString()
|
||||||
|
},
|
||||||
|
onDismiss = ::dismiss
|
||||||
|
)
|
||||||
|
|
||||||
|
is DownloaderPackageState.Untrusted -> TrustDialog(
|
||||||
|
title = R.string.downloader_trust_dialog_title,
|
||||||
|
body = stringResource(
|
||||||
|
R.string.downloader_trust_dialog_body
|
||||||
|
),
|
||||||
|
downloaderName = appName,
|
||||||
|
signature = signature,
|
||||||
|
onDismiss = ::dismiss,
|
||||||
|
onConfirm = {
|
||||||
|
viewModel.trustDownloader(packageName)
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
),
|
}
|
||||||
trailingContent = { Text(packageInfo.versionName!!) }
|
|
||||||
)
|
SettingsListItem(
|
||||||
|
modifier = Modifier.clickable { showDialog = true },
|
||||||
|
headlineContent = {
|
||||||
|
AppLabel(
|
||||||
|
packageInfo = packageInfo,
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
},
|
||||||
|
supportingContent = when (state) {
|
||||||
|
is DownloaderPackageState.Loaded -> {
|
||||||
|
val names = state.downloader.joinToString("\n") { it.name }
|
||||||
|
if (names.isNotEmpty())
|
||||||
|
stringResource(R.string.downloader_state_trusted, "\n\n$names")
|
||||||
|
else
|
||||||
|
stringResource(R.string.downloader_state_trusted)
|
||||||
|
}
|
||||||
|
|
||||||
|
is DownloaderPackageState.Failed -> stringResource(R.string.downloader_state_failed)
|
||||||
|
is DownloaderPackageState.Untrusted -> stringResource(R.string.downloader_state_untrusted)
|
||||||
|
},
|
||||||
|
trailingContent = { Text(packageInfo.versionName!!) }
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
if (downloaderStates.isEmpty()) {
|
|
||||||
item {
|
item {
|
||||||
Text(
|
Text(
|
||||||
stringResource(R.string.no_downloader_installed),
|
stringResource(R.string.no_downloader_installed),
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class DownloadsViewModel(
|
|||||||
private val downloaderRepository: DownloaderRepository,
|
private val downloaderRepository: DownloaderRepository,
|
||||||
val pm: PM
|
val pm: PM
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val downloaderStates = downloaderRepository.downloaderStates
|
val downloaderStates = downloaderRepository.downloaderPackageStates
|
||||||
val downloadedApps = downloadedAppRepository.getAll().map { downloadedApps ->
|
val downloadedApps = downloadedAppRepository.getAll().map { downloadedApps ->
|
||||||
downloadedApps.sortedWith(
|
downloadedApps.sortedWith(
|
||||||
compareBy<DownloadedApp> {
|
compareBy<DownloadedApp> {
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class SelectedAppInfoViewModel(
|
|||||||
private val pm: PM = get()
|
private val pm: PM = get()
|
||||||
private val savedStateHandle: SavedStateHandle = get()
|
private val savedStateHandle: SavedStateHandle = get()
|
||||||
val prefs: PreferencesManager = get()
|
val prefs: PreferencesManager = get()
|
||||||
val downloader = downloaderRepository.loadedDownloaderFlow
|
val downloader = downloaderRepository.loadedDownloaderPackageFlow
|
||||||
val desiredVersion = input.app.version
|
val desiredVersion = input.app.version
|
||||||
val packageName = input.app.packageName
|
val packageName = input.app.packageName
|
||||||
|
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ You will not be able to update the previously installed apps from this source."<
|
|||||||
<string name="patch_options_reset_all_dialog_description">You are about to reset all patch options. You will have to reapply each option again.</string>
|
<string name="patch_options_reset_all_dialog_description">You are about to reset all patch options. You will have to reapply each option again.</string>
|
||||||
<string name="patch_options_reset_all_description">Resets all patch options</string>
|
<string name="patch_options_reset_all_description">Resets all patch options</string>
|
||||||
<string name="downloader">Downloader</string>
|
<string name="downloader">Downloader</string>
|
||||||
<string name="downloader_state_trusted">Trusted</string>
|
<string name="downloader_state_trusted">Trusted%s</string>
|
||||||
<string name="downloader_state_failed">Failed to load. Click for more details</string>
|
<string name="downloader_state_failed">Failed to load. Click for more details</string>
|
||||||
<string name="downloader_state_untrusted">Untrusted</string>
|
<string name="downloader_state_untrusted">Untrusted</string>
|
||||||
<string name="downloader_trust_dialog_title">Trust downloader?</string>
|
<string name="downloader_trust_dialog_title">Trust downloader?</string>
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ kotlin.code.style=official
|
|||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
android.nonFinalResIds=false
|
android.nonFinalResIds=false
|
||||||
org.gradle.caching=true
|
org.gradle.caching=true
|
||||||
|
version=1.0.0
|
||||||
Reference in New Issue
Block a user