start implementing new UI and API changes (WIP)

This commit is contained in:
Ax333l 2024-07-26 23:38:23 +02:00
parent 29c849b6a1
commit 770e35bfe4
No known key found for this signature in database
GPG Key ID: D2B4D85271127D23
21 changed files with 114 additions and 71 deletions

View File

@ -112,8 +112,6 @@ dependencies {
implementation(libs.runtime.compose)
implementation(libs.splash.screen)
implementation(libs.compose.activity)
implementation(libs.paging.common.ktx)
implementation(libs.paging.compose)
implementation(libs.work.runtime.ktx)
implementation(libs.preferences.datastore)

View File

@ -98,10 +98,11 @@ class MainActivity : ComponentActivity() {
is Destination.AppSelector -> AppSelectorScreen(
// onAppClick = { navController.navigate(Destination.VersionSelector(it)) },
// TODO: complete this feature
onAppClick = { packageName, version ->
navController.navigate(
Destination.SelectedApplicationInfo(
SelectedApp.Downloadable(packageName, version.orEmpty())
SelectedApp.Search(packageName, version)
)
)
},

View File

@ -23,7 +23,7 @@ class DownloadedAppRepository(app: Application, db: AppDatabase) {
suspend fun download(
plugin: LoadedDownloaderPlugin,
app: App,
onDownload: suspend (downloadProgress: Pair<Float, Float?>) -> Unit,
onDownload: suspend (downloadProgress: Pair<Double, Double?>) -> Unit,
): File {
this.get(app.packageName, app.version)?.let { downloaded ->
return getApkFileForApp(downloaded)
@ -37,10 +37,10 @@ class DownloadedAppRepository(app: Application, db: AppDatabase) {
try {
val scope = object : DownloadScope {
override val targetFile = targetFile
override suspend fun reportProgress(bytesReceived: Int, bytesTotal: Int?) {
override suspend fun reportProgress(bytesReceived: Long, bytesTotal: Long?) {
require(bytesReceived >= 0) { "bytesReceived must not be negative" }
require(bytesTotal == null || bytesTotal >= bytesReceived) { "bytesTotal must be greater than or equal to bytesReceived" }
require(bytesTotal != 0) { "bytesTotal must not be zero" }
require(bytesTotal != 0L) { "bytesTotal must not be zero" }
onDownload(bytesReceived.megaBytes to bytesTotal?.megaBytes)
}
@ -77,6 +77,6 @@ class DownloadedAppRepository(app: Application, db: AppDatabase) {
}
private companion object {
val Int.megaBytes get() = div(100000).toFloat() / 10
val Long.megaBytes get() = toDouble() / 1_000_000
}
}

View File

@ -5,7 +5,6 @@ import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.Signature
import android.util.Log
import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.data.room.AppDatabase
import app.revanced.manager.data.room.plugins.TrustedDownloaderPlugin
import app.revanced.manager.domain.manager.PreferencesManager
@ -26,12 +25,10 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext
import java.io.File
import java.lang.reflect.Modifier
class DownloaderPluginRepository(
private val pm: PM,
private val fs: Filesystem,
private val prefs: PreferencesManager,
private val context: Context,
db: AppDatabase
@ -92,9 +89,11 @@ class DownloaderPluginRepository(
}
val downloaderContext = DownloaderContext(
androidContext = context,
tempDirectory = fs.tempDir.resolve("dl_plugin_${packageInfo.packageName}")
.also(File::mkdir)
androidContext = context.createPackageContext(
packageInfo.packageName,
0
),
pluginHostPackageName = context.packageName
)
return try {

View File

@ -1,5 +1,6 @@
package app.revanced.manager.network.downloader
import android.content.Context
import app.revanced.manager.plugin.downloader.App
import app.revanced.manager.plugin.downloader.DownloadScope
import app.revanced.manager.plugin.downloader.GetScope

View File

@ -28,15 +28,14 @@ data class PatchInfo(
fun compatibleWith(packageName: String) =
compatiblePackages?.any { it.packageName == packageName } ?: true
fun supportsVersion(packageName: String, versionName: String): Boolean {
fun supports(packageName: String, versionName: String?): Boolean {
val packages = compatiblePackages ?: return true // Universal patch
return packages.any { pkg ->
if (pkg.packageName != packageName) {
return@any false
}
if (pkg.packageName != packageName) return@any false
if (pkg.versions == null) return@any true
pkg.versions == null || pkg.versions.contains(versionName)
versionName != null && versionName in pkg.versions
}
}

View File

@ -69,7 +69,7 @@ class PatcherWorker(
val selectedPatches: PatchSelection,
val options: Options,
val logger: Logger,
val downloadProgress: MutableStateFlow<Pair<Float, Float?>?>,
val downloadProgress: MutableStateFlow<Pair<Double, Double?>?>,
val patchesProgress: MutableStateFlow<Pair<Int, Int>>,
val handleUserInteractionRequest: suspend () -> ActivityLaunchPermit?,
val setInputFile: (File) -> Unit,
@ -167,7 +167,7 @@ class PatcherWorker(
download(plugin, app)
}
is SelectedApp.Downloadable -> {
is SelectedApp.Search -> {
val getScope = object : GetScope {
override suspend fun requestUserInteraction() =
args.handleUserInteractionRequest()
@ -180,9 +180,11 @@ class PatcherWorker(
plugin.get(
getScope,
selectedApp.packageName,
selectedApp.suggestedVersion
selectedApp.version
)
?.takeIf { selectedApp.suggestedVersion == null || it.version == selectedApp.suggestedVersion }
?.takeIf { selectedApp.version == null || it.version == selectedApp.version }
} catch (e: UserInteractionException.Activity.NotCompleted) {
throw e
} catch (_: UserInteractionException) {
null
}?.let { app -> download(plugin, app) }

View File

@ -43,6 +43,7 @@ import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.model.State
import app.revanced.manager.ui.model.Step
import app.revanced.manager.ui.model.StepCategory
import java.util.Locale
import kotlin.math.floor
// Credits: https://github.com/Aliucord/AliucordManager/blob/main/app/src/main/kotlin/com/aliucord/manager/ui/component/installer/InstallGroup.kt
@ -134,7 +135,7 @@ fun SubStep(
name: String,
state: State,
message: String? = null,
downloadProgress: Pair<Float, Float?>? = null
downloadProgress: Pair<Double, Double?>? = null
) {
var messageExpanded by rememberSaveable { mutableStateOf(true) }
@ -180,7 +181,7 @@ fun SubStep(
} else {
downloadProgress?.let { (current, total) ->
Text(
if (total != null) "$current/$total MB" else "$current MB",
if (total != null) "${current.formatted}/${total.formatted} MB" else "${current.formatted} MB",
style = MaterialTheme.typography.labelSmall
)
}
@ -199,7 +200,7 @@ fun SubStep(
}
@Composable
fun StepIcon(state: State, progress: Pair<Float, Float?>? = null, size: Dp) {
fun StepIcon(state: State, progress: Pair<Double, Double?>? = null, size: Dp) {
val strokeWidth = Dp(floor(size.value / 10) + 1)
when (state) {
@ -237,9 +238,11 @@ fun StepIcon(state: State, progress: Pair<Float, Float?>? = null, size: Dp) {
progress?.let { (current, total) ->
if (total == null) return@let null
current / total
}
}?.toFloat()
},
strokeWidth = strokeWidth
)
}
}
private val Double.formatted get() = "%.1f".format(locale = Locale.ROOT, this)

View File

@ -49,7 +49,7 @@ data class BundleInfo(
bundle.uid to patches
}
fun PatchBundleRepository.bundleInfoFlow(packageName: String, version: String) =
fun PatchBundleRepository.bundleInfoFlow(packageName: String, version: String?) =
sources.flatMapLatestAndCombine(
combiner = { it.filterNotNull() }
) { source ->
@ -64,7 +64,7 @@ data class BundleInfo(
bundle.patches.filter { it.compatibleWith(packageName) }.forEach {
val targetList = when {
it.compatiblePackages == null -> universal
it.supportsVersion(
it.supports(
packageName,
version
) -> supported

View File

@ -19,5 +19,5 @@ data class Step(
val category: StepCategory,
val state: State = State.WAITING,
val message: String? = null,
val downloadProgress: StateFlow<Pair<Float, Float?>?>? = null
val downloadProgress: StateFlow<Pair<Double, Double?>?>? = null
)

View File

@ -2,13 +2,12 @@ package app.revanced.manager.ui.model
import android.os.Parcelable
import app.revanced.manager.network.downloader.ParceledDownloaderApp
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import java.io.File
sealed interface SelectedApp : Parcelable {
val packageName: String
val version: String // TODO: make this nullable
val version: String?
@Parcelize
data class Download(
@ -18,10 +17,7 @@ sealed interface SelectedApp : Parcelable {
) : SelectedApp
@Parcelize
data class Downloadable(override val packageName: String, val suggestedVersion: String?) : SelectedApp {
@IgnoredOnParcel
override val version = suggestedVersion.orEmpty()
}
data class Search(override val packageName: String, override val version: String?) : SelectedApp
@Parcelize
data class Local(

View File

@ -142,7 +142,7 @@ fun PatchesSelectorScreen(
}
}
if (vm.compatibleVersions.isNotEmpty())
if (vm.compatibleVersions.isNotEmpty() && vm.appVersion != null)
UnsupportedDialog(
appVersion = vm.appVersion,
supportedVersions = vm.compatibleVersions,

View File

@ -148,7 +148,7 @@ private fun SelectedAppInfoScreen(
availablePatchCount: Int,
selectedPatchCount: Int,
packageName: String,
version: String,
version: String?,
packageInfo: PackageInfo?,
) {
Scaffold(
@ -173,7 +173,13 @@ private fun SelectedAppInfoScreen(
) {
AppInfo(packageInfo, placeholderLabel = packageName) {
Text(
stringResource(R.string.selected_app_meta, version, availablePatchCount),
version?.let {
stringResource(
R.string.selected_app_meta_version,
it,
availablePatchCount
)
} ?: stringResource(R.string.selected_app_meta_no_version, availablePatchCount),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
)
@ -186,7 +192,8 @@ private fun SelectedAppInfoScreen(
)
PageItem(
R.string.version_selector_item,
stringResource(R.string.version_selector_item_description, version),
version?.let { stringResource(R.string.version_selector_item_description, it) }
?: stringResource(R.string.version_selector_item_description_auto),
onVersionSelectorClick
)
}

View File

@ -7,7 +7,6 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.revanced.manager.data.room.apps.downloaded.DownloadedApp
import app.revanced.manager.domain.manager.PreferencesManager
import app.revanced.manager.domain.repository.DownloadedAppRepository
import app.revanced.manager.domain.repository.DownloaderPluginRepository
import app.revanced.manager.util.PM

View File

@ -106,7 +106,7 @@ class PatcherViewModel(
}
val patchesProgress = MutableStateFlow(Pair(0, input.selectedPatches.values.sumOf { it.size }))
private val downloadProgress = MutableStateFlow<Pair<Float, Float?>?>(null)
private val downloadProgress = MutableStateFlow<Pair<Double, Double?>?>(null)
val steps = generateSteps(
app,
input.selectedApp,
@ -184,13 +184,15 @@ class PatcherViewModel(
installedAppRepository.addOrUpdate(
installedPackageName!!,
packageName,
input.selectedApp.version,
input.selectedApp.version
?: pm.getPackageInfo(outputFile)!!.versionName,
InstallType.DEFAULT,
input.selectedPatches
)
}
} else {
app.toast(app.getString(R.string.install_app_fail, extra))
Log.e(tag, "Installation failed: $extra")
}
}
}
@ -240,7 +242,6 @@ class PatcherViewModel(
fun rejectInteraction() {
currentInteractionRequest?.complete(null)
currentInteractionRequest = null
}
fun allowInteraction() {
@ -253,14 +254,19 @@ class PatcherViewModel(
launchedActivity = job
val result = job.await()
if (result.code != Activity.RESULT_OK) throw UserInteractionException.ActivityCancelled()
when (result.code) {
Activity.RESULT_OK -> result.intent
Activity.RESULT_CANCELED -> throw UserInteractionException.Activity.Cancelled()
else -> throw UserInteractionException.Activity.NotCompleted(
result.code,
result.intent
)
}
} finally {
launchedActivity = null
}
}
})
currentInteractionRequest = null
}
fun handleActivityResult(result: IntentContract.Result) {
@ -303,23 +309,24 @@ class PatcherViewModel(
InstallType.ROOT -> {
try {
val label = with(pm) {
getPackageInfo(outputFile)?.label()
val packageInfo = pm.getPackageInfo(outputFile)
?: throw Exception("Failed to load application info")
val label = with(pm) {
packageInfo.label()
}
rootInstaller.install(
outputFile,
inputFile,
packageName,
input.selectedApp.version,
packageInfo.versionName,
label
)
installedAppRepository.addOrUpdate(
packageName,
packageName,
input.selectedApp.version,
packageInfo.versionName,
InstallType.ROOT,
input.selectedPatches
)
@ -357,10 +364,10 @@ class PatcherViewModel(
fun generateSteps(
context: Context,
selectedApp: SelectedApp,
downloadProgress: StateFlow<Pair<Float, Float?>?>? = null
downloadProgress: StateFlow<Pair<Double, Double?>?>? = null
): List<Step> {
val needsDownload =
selectedApp is SelectedApp.Download || selectedApp is SelectedApp.Downloadable
selectedApp is SelectedApp.Download || selectedApp is SelectedApp.Search
return listOfNotNull(
Step(

View File

@ -34,7 +34,8 @@
<string name="bundle_name_default">Default</string>
<string name="bundle_name_fallback">Unnamed</string>
<string name="selected_app_meta">%1$s • %2$d available patches</string>
<string name="selected_app_meta_version">%1$s • %2$d available patches</string>
<string name="selected_app_meta_no_version">%d available patches</string>
<string name="patch_item_description">Start patching the application</string>
<string name="patch_selector_item">Patch selection and options</string>
@ -43,6 +44,7 @@
<string name="version_selector_item">Change version</string>
<string name="version_selector_item_description">%s selected</string>
<string name="version_selector_item_description_auto">Automatically selected</string>
<string name="legacy_import_failed">Could not import legacy settings</string>

View File

@ -21,9 +21,13 @@ public final class app/revanced/manager/plugin/downloader/App$Creator : android/
public synthetic fun newArray (I)[Ljava/lang/Object;
}
public final class app/revanced/manager/plugin/downloader/ConstantsKt {
public static final field PLUGIN_HOST_PERMISSION Ljava/lang/String;
}
public abstract interface class app/revanced/manager/plugin/downloader/DownloadScope {
public abstract fun getTargetFile ()Ljava/io/File;
public abstract fun reportProgress (ILjava/lang/Integer;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun reportProgress (JLjava/lang/Long;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class app/revanced/manager/plugin/downloader/Downloader {
@ -39,9 +43,9 @@ public final class app/revanced/manager/plugin/downloader/DownloaderBuilder {
}
public final class app/revanced/manager/plugin/downloader/DownloaderContext {
public fun <init> (Landroid/content/Context;Ljava/io/File;)V
public fun <init> (Landroid/content/Context;Ljava/lang/String;)V
public final fun getAndroidContext ()Landroid/content/Context;
public final fun getTempDirectory ()Ljava/io/File;
public final fun getPluginHostPackageName ()Ljava/lang/String;
}
public abstract interface annotation class app/revanced/manager/plugin/downloader/DownloaderDsl : java/lang/annotation/Annotation {
@ -59,10 +63,20 @@ public abstract class app/revanced/manager/plugin/downloader/UserInteractionExce
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/manager/plugin/downloader/UserInteractionException$ActivityCancelled : app/revanced/manager/plugin/downloader/UserInteractionException {
public abstract class app/revanced/manager/plugin/downloader/UserInteractionException$Activity : app/revanced/manager/plugin/downloader/UserInteractionException {
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
}
public final class app/revanced/manager/plugin/downloader/UserInteractionException$Activity$Cancelled : app/revanced/manager/plugin/downloader/UserInteractionException$Activity {
public fun <init> ()V
}
public final class app/revanced/manager/plugin/downloader/UserInteractionException$Activity$NotCompleted : app/revanced/manager/plugin/downloader/UserInteractionException$Activity {
public fun <init> (ILandroid/content/Intent;)V
public final fun getIntent ()Landroid/content/Intent;
public final fun getResultCode ()I
}
public final class app/revanced/manager/plugin/downloader/UserInteractionException$RequestDenied : app/revanced/manager/plugin/downloader/UserInteractionException {
public fun <init> ()V
}

View File

@ -2,10 +2,11 @@ plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
id("kotlin-parcelize")
`maven-publish`
}
android {
namespace = "app.revanced.manager.downloader_plugin"
namespace = "app.revanced.manager.plugin.downloader"
compileSdk = 34
defaultConfig {
@ -32,6 +33,20 @@ android {
}
}
dependencies {
api(libs.paging.common.ktx)
publishing {
repositories {
mavenLocal()
}
publications {
create<MavenPublication>("release") {
groupId = "app.revanced"
artifactId = "manager-downloader-plugin"
version = "1.0"
afterEvaluate {
from(components["release"])
}
}
}
}

View File

@ -26,7 +26,7 @@ interface DownloadScope {
/**
* A callback for reporting download progress
*/
suspend fun reportProgress(bytesReceived: Int, bytesTotal: Int?)
suspend fun reportProgress(bytesReceived: Long, bytesTotal: Long?)
}
@DownloaderDsl
@ -58,6 +58,10 @@ fun <A : App> downloader(block: DownloaderBuilder<A>.() -> Unit) =
sealed class UserInteractionException(message: String) : Exception(message) {
class RequestDenied : UserInteractionException("Request was denied")
// TODO: can cancelled activities return an intent?
class ActivityCancelled : UserInteractionException("Interaction was cancelled")
sealed class Activity(message: String) : UserInteractionException(message) {
class Cancelled : Activity("Interaction was cancelled")
class NotCompleted(val resultCode: Int, val intent: Intent?) :
Activity("Unexpected activity result code: $resultCode")
}
}

View File

@ -1,13 +1,12 @@
package app.revanced.manager.plugin.downloader
import android.content.Context
import java.io.File
@Suppress("Unused", "MemberVisibilityCanBePrivate")
/**
* The downloader plugin context.
*
* @param androidContext An Android [Context].
* @param tempDirectory The temporary directory belonging to this plugin.
* @param androidContext An Android [Context] for this plugin.
* @param pluginHostPackageName The package name of the plugin host.
*/
class DownloaderContext(val androidContext: Context, val tempDirectory: File)
class DownloaderContext(val androidContext: Context, val pluginHostPackageName: String)

View File

@ -5,7 +5,6 @@ ui-tooling = "1.6.8"
viewmodel-lifecycle = "2.8.3"
splash-screen = "1.0.1"
compose-activity = "1.9.0"
paging = "3.3.0"
preferences-datastore = "1.1.1"
work-runtime = "2.9.0"
compose-bom = "2024.06.00"
@ -44,8 +43,6 @@ runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", ve
runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "viewmodel-lifecycle" }
splash-screen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splash-screen" }
compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "compose-activity" }
paging-common-ktx = { group = "androidx.paging", name = "paging-common-ktx", version.ref = "paging" }
paging-compose = { group = "androidx.paging", name = "paging-compose", version.ref = "paging" }
work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work-runtime" }
preferences-datastore = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "preferences-datastore" }