mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-04-30 05:54:26 +02:00
more api changes
This commit is contained in:
parent
45b1d18685
commit
e14497a1ce
@ -3,10 +3,11 @@ import kotlin.random.Random
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
|
alias(libs.plugins.kotlin.serialization)
|
||||||
|
alias(libs.plugins.kotlin.parcelize)
|
||||||
alias(libs.plugins.devtools)
|
alias(libs.plugins.devtools)
|
||||||
alias(libs.plugins.about.libraries)
|
alias(libs.plugins.about.libraries)
|
||||||
id("kotlin-parcelize")
|
alias(libs.plugins.compose.compiler)
|
||||||
kotlin("plugin.serialization") version "1.9.23"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -81,9 +82,11 @@ android {
|
|||||||
jvmTarget = "17"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildFeatures.compose = true
|
buildFeatures {
|
||||||
buildFeatures.aidl = true
|
compose = true
|
||||||
buildFeatures.buildConfig = true
|
aidl = true
|
||||||
|
buildConfig = true
|
||||||
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
androidResources {
|
androidResources {
|
||||||
@ -91,7 +94,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
composeOptions.kotlinCompilerExtensionVersion = "1.5.10"
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
path = file("src/main/cpp/CMakeLists.txt")
|
path = file("src/main/cpp/CMakeLists.txt")
|
||||||
|
@ -14,10 +14,10 @@ import kotlinx.coroutines.flow.conflate
|
|||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FilterInputStream
|
import java.io.FilterOutputStream
|
||||||
import java.nio.file.Files
|
import java.nio.file.StandardOpenOption
|
||||||
import java.nio.file.StandardCopyOption
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
import kotlin.io.path.exists
|
import kotlin.io.path.outputStream
|
||||||
|
|
||||||
class DownloadedAppRepository(app: Application, db: AppDatabase) {
|
class DownloadedAppRepository(app: Application, db: AppDatabase) {
|
||||||
private val dir = app.getDir("downloaded-apps", Context.MODE_PRIVATE)
|
private val dir = app.getDir("downloaded-apps", Context.MODE_PRIVATE)
|
||||||
@ -45,50 +45,49 @@ class DownloadedAppRepository(app: Application, db: AppDatabase) {
|
|||||||
val targetFile = saveDir.resolve("base.apk").toPath()
|
val targetFile = saveDir.resolve("base.apk").toPath()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
channelFlow {
|
val downloadSize = AtomicLong(0)
|
||||||
var fileSize: Long? = null
|
val downloadedBytes = AtomicLong(0)
|
||||||
var downloadedBytes = 0L
|
|
||||||
|
|
||||||
|
channelFlow {
|
||||||
val scope = object : DownloadScope {
|
val scope = object : DownloadScope {
|
||||||
override suspend fun reportSize(size: Long) {
|
override suspend fun reportSize(size: Long) {
|
||||||
fileSize = size
|
require(size > 0) { "Size must be greater than zero" }
|
||||||
send(downloadedBytes to size)
|
require(
|
||||||
|
downloadSize.compareAndSet(
|
||||||
|
0,
|
||||||
|
size
|
||||||
|
)
|
||||||
|
) { "Download size has already been set" }
|
||||||
|
send(downloadedBytes.get() to size)
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
override val targetFile = targetFile
|
|
||||||
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 != 0L) { "bytesTotal must not be zero" }
|
|
||||||
|
|
||||||
onDownload(bytesReceived.megaBytes to bytesTotal?.megaBytes)
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.download(scope, app).use { inputStream ->
|
fun emitProgress(bytes: Long) {
|
||||||
Files.copy(object : FilterInputStream(inputStream) {
|
val newValue = downloadedBytes.addAndGet(bytes)
|
||||||
override fun read(): Int {
|
val totalSize = downloadSize.get()
|
||||||
val array = ByteArray(1)
|
if (totalSize < 1) return
|
||||||
if (read(array, 0, 1) != 1) return -1
|
trySend(newValue to totalSize).getOrThrow()
|
||||||
return array[0].toInt()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun read(b: ByteArray?, off: Int, len: Int) =
|
targetFile.outputStream(StandardOpenOption.CREATE_NEW).buffered().use {
|
||||||
super.read(b, off, len).also { result ->
|
val stream = object : FilterOutputStream(it) {
|
||||||
// Report download progress
|
override fun write(b: Int) = out.write(b).also { emitProgress(1) }
|
||||||
if (result > 0) {
|
|
||||||
downloadedBytes += result
|
override fun write(b: ByteArray?, off: Int, len: Int) =
|
||||||
fileSize?.let { trySend(downloadedBytes to it).getOrThrow() }
|
out.write(b, off, len).also {
|
||||||
|
emitProgress(
|
||||||
|
(len - off).toLong()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, targetFile, StandardCopyOption.REPLACE_EXISTING)
|
plugin.download(scope, app, stream)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.conflate()
|
.conflate()
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.collect { (downloaded, size) -> onDownload(downloaded.megaBytes to size.megaBytes) }
|
.collect { (downloaded, size) -> onDownload(downloaded.megaBytes to size.megaBytes) }
|
||||||
|
|
||||||
if (!targetFile.exists()) throw Exception("Downloader did not download any files")
|
if (downloadedBytes.get() < 1) throw Exception("Downloader did not download any files")
|
||||||
|
|
||||||
dao.insert(
|
dao.insert(
|
||||||
DownloadedApp(
|
DownloadedApp(
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package app.revanced.manager.domain.repository
|
package app.revanced.manager.domain.repository
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.pm.PackageInfo
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import app.revanced.manager.data.room.AppDatabase
|
import app.revanced.manager.data.room.AppDatabase
|
||||||
@ -11,9 +10,9 @@ import app.revanced.manager.network.downloader.DownloaderPluginState
|
|||||||
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
|
import app.revanced.manager.network.downloader.LoadedDownloaderPlugin
|
||||||
import app.revanced.manager.network.downloader.ParceledDownloaderApp
|
import app.revanced.manager.network.downloader.ParceledDownloaderApp
|
||||||
import app.revanced.manager.plugin.downloader.App
|
import app.revanced.manager.plugin.downloader.App
|
||||||
import app.revanced.manager.plugin.downloader.DownloadScope
|
|
||||||
import app.revanced.manager.plugin.downloader.Downloader
|
import app.revanced.manager.plugin.downloader.Downloader
|
||||||
import app.revanced.manager.plugin.downloader.DownloaderContext
|
import app.revanced.manager.plugin.downloader.DownloaderBuilder
|
||||||
|
import app.revanced.manager.plugin.downloader.PluginHostApi
|
||||||
import app.revanced.manager.util.PM
|
import app.revanced.manager.util.PM
|
||||||
import app.revanced.manager.util.tag
|
import app.revanced.manager.util.tag
|
||||||
import dalvik.system.PathClassLoader
|
import dalvik.system.PathClassLoader
|
||||||
@ -24,9 +23,9 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.InputStream
|
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
|
|
||||||
|
@OptIn(PluginHostApi::class)
|
||||||
class DownloaderPluginRepository(
|
class DownloaderPluginRepository(
|
||||||
private val pm: PM,
|
private val pm: PM,
|
||||||
private val prefs: PreferencesManager,
|
private val prefs: PreferencesManager,
|
||||||
@ -88,32 +87,28 @@ class DownloaderPluginRepository(
|
|||||||
|
|
||||||
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 pluginContext = app.createPackageContext(packageName, 0)
|
|
||||||
|
|
||||||
val className = packageInfo.applicationInfo.metaData.getString(METADATA_PLUGIN_CLASS)
|
val className = packageInfo.applicationInfo.metaData.getString(METADATA_PLUGIN_CLASS)
|
||||||
?: throw Exception("Missing metadata attribute $METADATA_PLUGIN_CLASS")
|
?: throw Exception("Missing metadata attribute $METADATA_PLUGIN_CLASS")
|
||||||
val classLoader = PathClassLoader(
|
|
||||||
packageInfo.applicationInfo.sourceDir,
|
val classLoader =
|
||||||
Downloader::class.java.classLoader
|
PathClassLoader(packageInfo.applicationInfo.sourceDir, app.classLoader)
|
||||||
)
|
val pluginContext = app.createPackageContext(packageName, 0)
|
||||||
|
|
||||||
val downloader = classLoader
|
val downloader = classLoader
|
||||||
.loadClass(className)
|
.loadClass(className)
|
||||||
.getDownloaderImplementation(
|
.getDownloaderBuilder()
|
||||||
DownloaderContext(
|
.build(
|
||||||
androidContext = pluginContext,
|
hostPackageName = app.packageName,
|
||||||
pluginHostPackageName = app.packageName
|
context = pluginContext
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
DownloaderPluginState.Loaded(
|
DownloaderPluginState.Loaded(
|
||||||
LoadedDownloaderPlugin(
|
LoadedDownloaderPlugin(
|
||||||
packageName,
|
packageName,
|
||||||
with(pm) { packageInfo.label() },
|
with(pm) { packageInfo.label() },
|
||||||
packageInfo.versionName,
|
packageInfo.versionName,
|
||||||
downloader.get,
|
downloader.get,
|
||||||
downloader.download as suspend DownloadScope.(App) -> InputStream,
|
downloader.download,
|
||||||
classLoader
|
classLoader
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -156,22 +151,15 @@ class DownloaderPluginRepository(
|
|||||||
const val PLUGIN_FEATURE = "app.revanced.manager.plugin.downloader"
|
const val PLUGIN_FEATURE = "app.revanced.manager.plugin.downloader"
|
||||||
const val METADATA_PLUGIN_CLASS = "app.revanced.manager.plugin.downloader.class"
|
const val METADATA_PLUGIN_CLASS = "app.revanced.manager.plugin.downloader.class"
|
||||||
|
|
||||||
val Class<*>.isDownloader get() = Downloader::class.java.isAssignableFrom(this)
|
|
||||||
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
|
||||||
|
val Class<*>.isDownloaderBuilder get() = DownloaderBuilder::class.java.isAssignableFrom(this)
|
||||||
|
|
||||||
fun Class<*>.getDownloaderImplementation(context: DownloaderContext) =
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
fun Class<*>.getDownloaderBuilder() =
|
||||||
declaredMethods
|
declaredMethods
|
||||||
.filter { it.modifiers.isPublicStatic && it.returnType.isDownloader }
|
.firstOrNull { it.modifiers.isPublicStatic && it.returnType.isDownloaderBuilder && it.parameterTypes.isEmpty() }
|
||||||
.firstNotNullOfOrNull callMethod@{
|
?.let { it(null) as DownloaderBuilder<App> }
|
||||||
if (it.parameterTypes contentEquals arrayOf(DownloaderContext::class.java)) return@callMethod it(
|
|
||||||
null,
|
|
||||||
context
|
|
||||||
) as Downloader<*>
|
|
||||||
if (it.parameterTypes.isEmpty()) return@callMethod it(null) as Downloader<*>
|
|
||||||
|
|
||||||
return@callMethod null
|
|
||||||
}
|
|
||||||
?: throw Exception("Could not find a valid downloader implementation in class $canonicalName")
|
?: throw Exception("Could not find a valid downloader implementation in class $canonicalName")
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,16 +1,15 @@
|
|||||||
package app.revanced.manager.network.downloader
|
package app.revanced.manager.network.downloader
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import app.revanced.manager.plugin.downloader.App
|
import app.revanced.manager.plugin.downloader.App
|
||||||
import app.revanced.manager.plugin.downloader.DownloadScope
|
import app.revanced.manager.plugin.downloader.DownloadScope
|
||||||
import app.revanced.manager.plugin.downloader.GetScope
|
import app.revanced.manager.plugin.downloader.GetScope
|
||||||
import java.io.InputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
class LoadedDownloaderPlugin(
|
class LoadedDownloaderPlugin(
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val version: String,
|
val version: String,
|
||||||
val get: suspend GetScope.(packageName: String, version: String?) -> App?,
|
val get: suspend GetScope.(packageName: String, version: String?) -> App?,
|
||||||
val download: suspend DownloadScope.(app: App) -> InputStream,
|
val download: suspend DownloadScope.(app: App, outputStream: OutputStream) -> Unit,
|
||||||
val classLoader: ClassLoader
|
val classLoader: ClassLoader
|
||||||
)
|
)
|
@ -3,8 +3,11 @@ package app.revanced.manager.ui.screen
|
|||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.ArrowRight
|
import androidx.compose.material.icons.automirrored.outlined.ArrowRight
|
||||||
import androidx.compose.material.icons.filled.AutoFixHigh
|
import androidx.compose.material.icons.filled.AutoFixHigh
|
||||||
@ -13,18 +16,24 @@ import androidx.compose.material3.ExtendedFloatingActionButton
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.ListItem
|
import androidx.compose.material3.ListItem
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
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.ui.component.AlertDialogExtended
|
||||||
import app.revanced.manager.ui.component.AppInfo
|
import app.revanced.manager.ui.component.AppInfo
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
import app.revanced.manager.ui.component.ColumnWithScrollbar
|
import app.revanced.manager.ui.component.ColumnWithScrollbar
|
||||||
@ -36,6 +45,7 @@ import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel
|
|||||||
import app.revanced.manager.util.Options
|
import app.revanced.manager.util.Options
|
||||||
import app.revanced.manager.util.PatchSelection
|
import app.revanced.manager.util.PatchSelection
|
||||||
import app.revanced.manager.util.toast
|
import app.revanced.manager.util.toast
|
||||||
|
import app.revanced.manager.util.transparentListItemColors
|
||||||
import dev.olshevski.navigation.reimagined.AnimatedNavHost
|
import dev.olshevski.navigation.reimagined.AnimatedNavHost
|
||||||
import dev.olshevski.navigation.reimagined.NavBackHandler
|
import dev.olshevski.navigation.reimagined.NavBackHandler
|
||||||
import dev.olshevski.navigation.reimagined.navigate
|
import dev.olshevski.navigation.reimagined.navigate
|
||||||
@ -70,6 +80,10 @@ fun SelectedAppInfoScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var showSourceSelectorDialog by rememberSaveable {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
val navController =
|
val navController =
|
||||||
rememberNavController<SelectedAppInfoDestination>(startDestination = SelectedAppInfoDestination.Main)
|
rememberNavController<SelectedAppInfoDestination>(startDestination = SelectedAppInfoDestination.Main)
|
||||||
|
|
||||||
@ -102,7 +116,8 @@ fun SelectedAppInfoScreen(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onVersionSelectorClick = {
|
onSourceSelectorClick = {
|
||||||
|
showSourceSelectorDialog = true
|
||||||
// navController.navigate(SelectedAppInfoDestination.VersionSelector)
|
// navController.navigate(SelectedAppInfoDestination.VersionSelector)
|
||||||
},
|
},
|
||||||
onBackClick = onBackClick,
|
onBackClick = onBackClick,
|
||||||
@ -137,7 +152,7 @@ fun SelectedAppInfoScreen(
|
|||||||
private fun SelectedAppInfoScreen(
|
private fun SelectedAppInfoScreen(
|
||||||
onPatchClick: () -> Unit,
|
onPatchClick: () -> Unit,
|
||||||
onPatchSelectorClick: () -> Unit,
|
onPatchSelectorClick: () -> Unit,
|
||||||
onVersionSelectorClick: () -> Unit,
|
onSourceSelectorClick: () -> Unit,
|
||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
selectedPatchCount: Int,
|
selectedPatchCount: Int,
|
||||||
packageName: String,
|
packageName: String,
|
||||||
@ -186,7 +201,7 @@ private fun SelectedAppInfoScreen(
|
|||||||
R.string.version_selector_item,
|
R.string.version_selector_item,
|
||||||
version?.let { stringResource(R.string.version_selector_item_description, it) }
|
version?.let { stringResource(R.string.version_selector_item_description, it) }
|
||||||
?: stringResource(R.string.version_selector_item_description_auto),
|
?: stringResource(R.string.version_selector_item_description_auto),
|
||||||
onVersionSelectorClick
|
onSourceSelectorClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,3 +232,59 @@ private fun PageItem(@StringRes title: Int, description: String, onClick: () ->
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AppSourceSelectorDialog(onDismissRequest: () -> Unit) {
|
||||||
|
AlertDialogExtended(
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Select")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = { Text("Select source") },
|
||||||
|
textHorizontalPadding = PaddingValues(horizontal = 0.dp),
|
||||||
|
text = {
|
||||||
|
/*
|
||||||
|
val presets = remember(scope.option.presets) {
|
||||||
|
scope.option.presets?.entries?.toList().orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyColumn {
|
||||||
|
@Composable
|
||||||
|
fun Item(title: String, value: Any?, presetKey: String?) {
|
||||||
|
ListItem(
|
||||||
|
modifier = Modifier.clickable { selectedPreset = presetKey },
|
||||||
|
headlineContent = { Text(title) },
|
||||||
|
supportingContent = value?.toString()?.let { { Text(it) } },
|
||||||
|
leadingContent = {
|
||||||
|
RadioButton(
|
||||||
|
selected = selectedPreset == presetKey,
|
||||||
|
onClick = { selectedPreset = presetKey }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors = transparentListItemColors
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
items(presets, key = { it.key }) {
|
||||||
|
Item(it.key, it.value, it.key)
|
||||||
|
}
|
||||||
|
|
||||||
|
item(key = null) {
|
||||||
|
Item(stringResource(R.string.option_preset_custom_value), null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
@ -45,6 +45,9 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
|
|||||||
mutableStateOf(input.app)
|
mutableStateOf(input.app)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var selectedAppInfo: PackageInfo? by mutableStateOf(null)
|
||||||
|
private set
|
||||||
|
|
||||||
var selectedApp
|
var selectedApp
|
||||||
get() = _selectedApp
|
get() = _selectedApp
|
||||||
set(value) {
|
set(value) {
|
||||||
@ -52,8 +55,6 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
|
|||||||
invalidateSelectedAppInfo()
|
invalidateSelectedAppInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
var selectedAppInfo: PackageInfo? by mutableStateOf(null)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
invalidateSelectedAppInfo()
|
invalidateSelectedAppInfo()
|
||||||
}
|
}
|
||||||
@ -64,8 +65,8 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if (!persistConfiguration) return@launch // TODO: save options for patched apps.
|
if (!persistConfiguration) return@launch // TODO: save options for patched apps.
|
||||||
|
|
||||||
val packageName =
|
// Accessing this from another thread may cause crashes.
|
||||||
selectedApp.packageName // Accessing this from another thread may cause crashes.
|
val packageName = selectedApp.packageName
|
||||||
|
|
||||||
state.value = withContext(Dispatchers.Default) {
|
state.value = withContext(Dispatchers.Default) {
|
||||||
val bundlePatches = bundleRepository.bundles.first()
|
val bundlePatches = bundleRepository.bundles.first()
|
||||||
|
@ -2,11 +2,15 @@ plugins {
|
|||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.devtools) apply false
|
alias(libs.plugins.devtools) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
|
alias(libs.plugins.kotlin.serialization) apply false
|
||||||
|
alias(libs.plugins.kotlin.parcelize) apply false
|
||||||
alias(libs.plugins.about.libraries) apply false
|
alias(libs.plugins.about.libraries) apply false
|
||||||
alias(libs.plugins.android.library) apply false
|
alias(libs.plugins.android.library) apply false
|
||||||
|
alias(libs.plugins.compose.compiler) apply false
|
||||||
alias(libs.plugins.binary.compatibility.validator)
|
alias(libs.plugins.binary.compatibility.validator)
|
||||||
}
|
}
|
||||||
|
|
||||||
apiValidation {
|
apiValidation {
|
||||||
ignoredProjects.addAll(listOf("app", "example-downloader-plugin"))
|
ignoredProjects.addAll(listOf("app", "example-downloader-plugin"))
|
||||||
|
nonPublicMarkers += "app.revanced.manager.plugin.downloader.PluginHostApi"
|
||||||
}
|
}
|
@ -26,20 +26,13 @@ public final class app/revanced/manager/plugin/downloader/ConstantsKt {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public abstract interface class app/revanced/manager/plugin/downloader/DownloadScope {
|
public abstract interface class app/revanced/manager/plugin/downloader/DownloadScope {
|
||||||
public abstract fun getTargetFile ()Ljava/io/File;
|
public abstract fun reportSize (JLkotlin/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 {
|
public final class app/revanced/manager/plugin/downloader/Downloader {
|
||||||
public final fun getDownload ()Lkotlin/jvm/functions/Function3;
|
|
||||||
public final fun getGet ()Lkotlin/jvm/functions/Function4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/manager/plugin/downloader/DownloaderBuilder {
|
public final class app/revanced/manager/plugin/downloader/DownloaderBuilder {
|
||||||
public fun <init> ()V
|
|
||||||
public final fun build ()Lapp/revanced/manager/plugin/downloader/Downloader;
|
|
||||||
public final fun download (Lkotlin/jvm/functions/Function3;)V
|
|
||||||
public final fun get (Lkotlin/jvm/functions/Function4;)V
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/manager/plugin/downloader/DownloaderContext {
|
public final class app/revanced/manager/plugin/downloader/DownloaderContext {
|
||||||
@ -48,17 +41,28 @@ public final class app/revanced/manager/plugin/downloader/DownloaderContext {
|
|||||||
public final fun getPluginHostPackageName ()Ljava/lang/String;
|
public final fun getPluginHostPackageName ()Ljava/lang/String;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract interface annotation class app/revanced/manager/plugin/downloader/DownloaderDsl : java/lang/annotation/Annotation {
|
public final class app/revanced/manager/plugin/downloader/DownloaderKt {
|
||||||
|
public static final fun downloader (Lkotlin/jvm/functions/Function1;)Lapp/revanced/manager/plugin/downloader/DownloaderBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class app/revanced/manager/plugin/downloader/DownloaderKt {
|
public final class app/revanced/manager/plugin/downloader/DownloaderScope {
|
||||||
public static final fun downloader (Lkotlin/jvm/functions/Function1;)Lapp/revanced/manager/plugin/downloader/Downloader;
|
public final fun download (Lkotlin/jvm/functions/Function2;)V
|
||||||
|
public final fun get (Lkotlin/jvm/functions/Function4;)V
|
||||||
|
public final fun getHostPackageName ()Ljava/lang/String;
|
||||||
|
public final fun getPluginPackageName ()Ljava/lang/String;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final class app/revanced/manager/plugin/downloader/ExtensionsKt {
|
||||||
|
public static final fun download (Lapp/revanced/manager/plugin/downloader/DownloaderScope;Lkotlin/jvm/functions/Function4;)V
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract interface class app/revanced/manager/plugin/downloader/GetScope {
|
public abstract interface class app/revanced/manager/plugin/downloader/GetScope {
|
||||||
public abstract fun requestUserInteraction (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
public abstract fun requestUserInteraction (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract interface annotation class app/revanced/manager/plugin/downloader/PluginHostApi : java/lang/annotation/Annotation {
|
||||||
|
}
|
||||||
|
|
||||||
public abstract class app/revanced/manager/plugin/downloader/UserInteractionException : java/lang/Exception {
|
public abstract class app/revanced/manager/plugin/downloader/UserInteractionException : java/lang/Exception {
|
||||||
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.library)
|
alias(libs.plugins.android.library)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
id("kotlin-parcelize")
|
alias(libs.plugins.kotlin.parcelize)
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,10 +33,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(libs.kotlinx.coroutines)
|
|
||||||
}
|
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
package app.revanced.manager.plugin.downloader
|
package app.revanced.manager.plugin.downloader
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import java.io.File
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE)
|
@RequiresOptIn(
|
||||||
@DslMarker
|
level = RequiresOptIn.Level.ERROR,
|
||||||
annotation class DownloaderDsl
|
message = "This API is only intended for plugin hosts, don't use it in a plugin.",
|
||||||
|
)
|
||||||
|
annotation class PluginHostApi
|
||||||
|
|
||||||
@DownloaderDsl
|
|
||||||
interface GetScope {
|
interface GetScope {
|
||||||
suspend fun requestUserInteraction(): ActivityLaunchPermit
|
suspend fun requestUserInteraction(): ActivityLaunchPermit
|
||||||
}
|
}
|
||||||
@ -17,37 +19,66 @@ fun interface ActivityLaunchPermit {
|
|||||||
suspend fun launch(intent: Intent): Intent?
|
suspend fun launch(intent: Intent): Intent?
|
||||||
}
|
}
|
||||||
|
|
||||||
@DownloaderDsl
|
|
||||||
interface DownloadScope {
|
interface DownloadScope {
|
||||||
suspend fun reportSize(size: Long)
|
suspend fun reportSize(size: Long)
|
||||||
}
|
}
|
||||||
|
|
||||||
@DownloaderDsl
|
typealias Size = Long
|
||||||
class DownloaderBuilder<A : App> {
|
typealias DownloadResult = Pair<InputStream, Size?>
|
||||||
private var download: (suspend DownloadScope.(A) -> InputStream)? = null
|
|
||||||
private var get: (suspend GetScope.(String, String?) -> A?)? = null
|
class DownloaderScope<A : App> internal constructor(
|
||||||
|
/**
|
||||||
|
* The package name of ReVanced Manager.
|
||||||
|
*/
|
||||||
|
val hostPackageName: String,
|
||||||
|
internal val context: Context
|
||||||
|
) {
|
||||||
|
internal var download: (suspend DownloadScope.(A, OutputStream) -> Unit)? = null
|
||||||
|
internal var get: (suspend GetScope.(String, String?) -> A?)? = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The package name of the plugin.
|
||||||
|
*/
|
||||||
|
val pluginPackageName: String get() = context.packageName
|
||||||
|
|
||||||
fun get(block: suspend GetScope.(packageName: String, version: String?) -> A?) {
|
fun get(block: suspend GetScope.(packageName: String, version: String?) -> A?) {
|
||||||
get = block
|
get = block
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(block: suspend DownloadScope.(app: A) -> InputStream) {
|
/**
|
||||||
download = block
|
* Define the download function for this plugin.
|
||||||
|
*/
|
||||||
|
fun download(block: suspend (app: A) -> DownloadResult) {
|
||||||
|
download = { app, outputStream ->
|
||||||
|
val (inputStream, size) = block(app)
|
||||||
|
|
||||||
|
inputStream.use {
|
||||||
|
if (size != null) reportSize(size)
|
||||||
|
it.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build() = Downloader(
|
class DownloaderBuilder<A : App> internal constructor(private val block: DownloaderScope<A>.() -> Unit) {
|
||||||
|
@PluginHostApi
|
||||||
|
fun build(hostPackageName: String, context: Context) =
|
||||||
|
with(DownloaderScope<A>(hostPackageName, context)) {
|
||||||
|
block()
|
||||||
|
|
||||||
|
Downloader(
|
||||||
download = download ?: error("download was not declared"),
|
download = download ?: error("download was not declared"),
|
||||||
get = get ?: error("get was not declared")
|
get = get ?: error("get was not declared")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Downloader<A : App> internal constructor(
|
class Downloader<A : App> internal constructor(
|
||||||
val get: suspend GetScope.(packageName: String, version: String?) -> A?,
|
@property:PluginHostApi val get: suspend GetScope.(packageName: String, version: String?) -> A?,
|
||||||
val download: suspend DownloadScope.(app: A) -> InputStream
|
@property:PluginHostApi val download: suspend DownloadScope.(app: A, outputStream: OutputStream) -> Unit
|
||||||
)
|
)
|
||||||
|
|
||||||
fun <A : App> downloader(block: DownloaderBuilder<A>.() -> Unit) =
|
fun <A : App> downloader(block: DownloaderScope<A>.() -> Unit) = DownloaderBuilder(block)
|
||||||
DownloaderBuilder<A>().apply(block).build()
|
|
||||||
|
|
||||||
sealed class UserInteractionException(message: String) : Exception(message) {
|
sealed class UserInteractionException(message: String) : Exception(message) {
|
||||||
class RequestDenied : UserInteractionException("Request was denied")
|
class RequestDenied : UserInteractionException("Request was denied")
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
package app.revanced.manager.plugin.downloader
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
|
|
||||||
@Suppress("Unused", "MemberVisibilityCanBePrivate")
|
|
||||||
/**
|
|
||||||
* The downloader plugin context.
|
|
||||||
*
|
|
||||||
* @param androidContext An Android [Context] for this plugin.
|
|
||||||
* @param pluginHostPackageName The package name of the plugin host.
|
|
||||||
*/
|
|
||||||
class DownloaderContext(val androidContext: Context, val pluginHostPackageName: String)
|
|
@ -1,96 +1,8 @@
|
|||||||
package app.revanced.manager.plugin.downloader
|
package app.revanced.manager.plugin.downloader
|
||||||
|
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.Job
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.io.FilterInputStream
|
|
||||||
import java.io.FilterOutputStream
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.io.PipedInputStream
|
|
||||||
import java.io.PipedOutputStream
|
|
||||||
|
|
||||||
// OutputStream-based version of download
|
// OutputStream-based version of download
|
||||||
fun <A : App> DownloaderBuilder<A>.download(block: suspend DownloadScope.(A, OutputStream) -> Unit) =
|
fun <A : App> DownloaderScope<A>.download(block: suspend DownloadScope.(A, OutputStream) -> Unit) {
|
||||||
download { app ->
|
download = block
|
||||||
val input = PipedInputStream(1024 * 1024)
|
|
||||||
var currentThrowable: Throwable? = null
|
|
||||||
|
|
||||||
val coroutineScope =
|
|
||||||
CoroutineScope(Dispatchers.IO + Job() + CoroutineExceptionHandler { _, throwable ->
|
|
||||||
currentThrowable?.let {
|
|
||||||
it.addSuppressed(throwable)
|
|
||||||
return@CoroutineExceptionHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
currentThrowable = throwable
|
|
||||||
})
|
|
||||||
var started = false
|
|
||||||
|
|
||||||
fun rethrowException() {
|
|
||||||
currentThrowable?.let {
|
|
||||||
currentThrowable = null
|
|
||||||
throw it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
started = true
|
|
||||||
coroutineScope.launch {
|
|
||||||
PipedOutputStream(input).use {
|
|
||||||
block(app, object : FilterOutputStream(it) {
|
|
||||||
var closed = false
|
|
||||||
|
|
||||||
private fun assertIsOpen() {
|
|
||||||
if (closed) throw IOException("Stream is closed.")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(b: ByteArray?, off: Int, len: Int) {
|
|
||||||
assertIsOpen()
|
|
||||||
super.write(b, off, len)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun write(b: Int) {
|
|
||||||
assertIsOpen()
|
|
||||||
super.write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object : FilterInputStream(input) {
|
|
||||||
override fun read(): Int {
|
|
||||||
val array = ByteArray(1)
|
|
||||||
if (read(array, 0, 1) != 1) return -1
|
|
||||||
return array[0].toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun read(b: ByteArray?, off: Int, len: Int): Int {
|
|
||||||
if (!started) start()
|
|
||||||
rethrowException()
|
|
||||||
return super.read(b, off, len)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun close() {
|
|
||||||
super.close()
|
|
||||||
coroutineScope.cancel()
|
|
||||||
rethrowException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <A : App> DownloaderBuilder<A>.download(block: suspend DownloadScope.(A, (InputStream) -> Unit) -> Unit) =
|
|
||||||
download { app, outputStream: OutputStream ->
|
|
||||||
block(app) { inputStream ->
|
|
||||||
inputStream.use { it.copyTo(outputStream) }
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,7 +1,8 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
id("kotlin-parcelize")
|
alias(libs.plugins.kotlin.parcelize)
|
||||||
|
alias(libs.plugins.compose.compiler)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
@ -16,7 +17,6 @@ android {
|
|||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.0"
|
versionName = "1.0"
|
||||||
buildConfigField("String", "PLUGIN_PACKAGE_NAME", "\"$packageName\"")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@ -39,11 +39,7 @@ android {
|
|||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "17"
|
jvmTarget = "17"
|
||||||
}
|
}
|
||||||
composeOptions.kotlinCompilerExtensionVersion = "1.5.10"
|
buildFeatures.compose = true
|
||||||
buildFeatures {
|
|
||||||
compose = true
|
|
||||||
buildConfig = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -2,22 +2,19 @@
|
|||||||
|
|
||||||
package app.revanced.manager.plugin.downloader.example
|
package app.revanced.manager.plugin.downloader.example
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import app.revanced.manager.plugin.downloader.App
|
import app.revanced.manager.plugin.downloader.App
|
||||||
import app.revanced.manager.plugin.downloader.DownloaderContext
|
|
||||||
import app.revanced.manager.plugin.downloader.download
|
import app.revanced.manager.plugin.downloader.download
|
||||||
import app.revanced.manager.plugin.downloader.downloader
|
import app.revanced.manager.plugin.downloader.downloader
|
||||||
import app.revanced.manager.plugin.downloader.example.BuildConfig.PLUGIN_PACKAGE_NAME
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.StandardCopyOption
|
|
||||||
import kotlin.io.path.Path
|
import kotlin.io.path.Path
|
||||||
import kotlin.io.path.fileSize
|
import kotlin.io.path.fileSize
|
||||||
import kotlin.io.path.inputStream
|
import kotlin.io.path.inputStream
|
||||||
|
|
||||||
// TODO: document API, change dispatcher.
|
// TODO: document and update API, change dispatcher, finish UI
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
class InstalledApp(
|
class InstalledApp(
|
||||||
@ -26,8 +23,15 @@ class InstalledApp(
|
|||||||
internal val apkPath: String
|
internal val apkPath: String
|
||||||
) : App(packageName, version)
|
) : App(packageName, version)
|
||||||
|
|
||||||
fun installedAppDownloader(context: DownloaderContext) = downloader<InstalledApp> {
|
private val application by lazy {
|
||||||
val pm = context.androidContext.packageManager
|
// Don't do this in a real plugin.
|
||||||
|
val clazz = Class.forName("android.app.ActivityThread")
|
||||||
|
val activityThread = clazz.getMethod("currentActivityThread")(null)
|
||||||
|
clazz.getMethod("getApplication")(activityThread) as Application
|
||||||
|
}
|
||||||
|
|
||||||
|
val installedAppDownloader = downloader<InstalledApp> {
|
||||||
|
val pm = application.packageManager
|
||||||
|
|
||||||
get { packageName, version ->
|
get { packageName, version ->
|
||||||
val packageInfo = try {
|
val packageInfo = try {
|
||||||
@ -38,7 +42,7 @@ fun installedAppDownloader(context: DownloaderContext) = downloader<InstalledApp
|
|||||||
|
|
||||||
requestUserInteraction().launch(Intent().apply {
|
requestUserInteraction().launch(Intent().apply {
|
||||||
setClassName(
|
setClassName(
|
||||||
PLUGIN_PACKAGE_NAME,
|
pluginPackageName,
|
||||||
InteractionActivity::class.java.canonicalName!!
|
InteractionActivity::class.java.canonicalName!!
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -50,12 +54,9 @@ fun installedAppDownloader(context: DownloaderContext) = downloader<InstalledApp
|
|||||||
).takeIf { version == null || it.version == version }
|
).takeIf { version == null || it.version == version }
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
download { app ->
|
download { app ->
|
||||||
Path(app.apkPath).also {
|
with(Path(app.apkPath)) { inputStream() to fileSize() }
|
||||||
reportSize(it.fileSize())
|
}
|
||||||
}.inputStream()
|
|
||||||
}*/
|
|
||||||
|
|
||||||
download { app, outputStream ->
|
download { app, outputStream ->
|
||||||
val path = Path(app.apkPath)
|
val path = Path(app.apkPath)
|
@ -1,18 +1,18 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
kotlin = "2.0.10"
|
||||||
ktx = "1.13.1"
|
ktx = "1.13.1"
|
||||||
material3 = "1.3.0-beta04"
|
material3 = "1.3.0-beta05"
|
||||||
ui-tooling = "1.6.8"
|
ui-tooling = "1.6.8"
|
||||||
viewmodel-lifecycle = "2.8.3"
|
viewmodel-lifecycle = "2.8.4"
|
||||||
splash-screen = "1.0.1"
|
splash-screen = "1.0.1"
|
||||||
compose-activity = "1.9.0"
|
compose-activity = "1.9.1"
|
||||||
preferences-datastore = "1.1.1"
|
preferences-datastore = "1.1.1"
|
||||||
work-runtime = "2.9.0"
|
work-runtime = "2.9.1"
|
||||||
compose-bom = "2024.06.00"
|
compose-bom = "2024.06.00"
|
||||||
accompanist = "0.34.0"
|
accompanist = "0.34.0"
|
||||||
placeholder = "1.1.2"
|
placeholder = "1.1.2"
|
||||||
reorderable = "1.5.2"
|
reorderable = "1.5.2"
|
||||||
serialization = "1.6.3"
|
serialization = "1.7.1"
|
||||||
coroutines = "1.8.1"
|
|
||||||
collection = "0.3.7"
|
collection = "0.3.7"
|
||||||
room-version = "2.6.1"
|
room-version = "2.6.1"
|
||||||
revanced-patcher = "19.3.1"
|
revanced-patcher = "19.3.1"
|
||||||
@ -24,8 +24,7 @@ ktor = "2.3.9"
|
|||||||
markdown-renderer = "0.22.0"
|
markdown-renderer = "0.22.0"
|
||||||
fading-edges = "1.0.4"
|
fading-edges = "1.0.4"
|
||||||
android-gradle-plugin = "8.3.2"
|
android-gradle-plugin = "8.3.2"
|
||||||
kotlin-gradle-plugin = "1.9.22"
|
dev-tools-ksp-gradle-plugin = "2.0.10-1.0.24"
|
||||||
dev-tools-gradle-plugin = "1.9.22-1.0.17"
|
|
||||||
about-libraries-gradle-plugin = "11.1.1"
|
about-libraries-gradle-plugin = "11.1.1"
|
||||||
binary-compatibility-validator = "0.15.1"
|
binary-compatibility-validator = "0.15.1"
|
||||||
coil = "2.6.0"
|
coil = "2.6.0"
|
||||||
@ -69,7 +68,6 @@ placeholder-material3 = { group = "io.github.fornewid", name = "placeholder-mate
|
|||||||
# Kotlinx
|
# Kotlinx
|
||||||
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
|
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "serialization" }
|
||||||
kotlinx-collection-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "collection" }
|
kotlinx-collection-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "collection" }
|
||||||
kotlinx-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines" }
|
|
||||||
|
|
||||||
# Room
|
# Room
|
||||||
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room-version" }
|
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room-version" }
|
||||||
@ -132,7 +130,10 @@ compose-icons-fontawesome = { group = "com.github.BenjaminHalko.compose-icons",
|
|||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
|
android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" }
|
||||||
android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" }
|
android-library = { id = "com.android.library", version.ref = "android-gradle-plugin" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-gradle-plugin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
devtools = { id = "com.google.devtools.ksp", version.ref = "dev-tools-gradle-plugin" }
|
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
||||||
|
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
|
||||||
|
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
devtools = { id = "com.google.devtools.ksp", version.ref = "dev-tools-ksp-gradle-plugin" }
|
||||||
about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "about-libraries-gradle-plugin" }
|
about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "about-libraries-gradle-plugin" }
|
||||||
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
binary-compatibility-validator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "binary-compatibility-validator" }
|
Loading…
x
Reference in New Issue
Block a user