mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-04-30 22:14:25 +02:00
Merge branch 'compose-dev' of https://github.com/ReVanced/revanced-manager into fix/minor-issues
This commit is contained in:
commit
397a1f8f9c
@ -29,7 +29,7 @@ android {
|
|||||||
buildTypes {
|
buildTypes {
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix = ".debug"
|
applicationIdSuffix = ".debug"
|
||||||
resValue("string", "app_name", "ReVanced Manager Debug")
|
resValue("string", "app_name", "ReVanced Manager (dev)")
|
||||||
|
|
||||||
buildConfigField("long", "BUILD_ID", "${Random.nextLong()}L")
|
buildConfigField("long", "BUILD_ID", "${Random.nextLong()}L")
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ interface PatchBundleDao {
|
|||||||
suspend fun all(): List<PatchBundleEntity>
|
suspend fun all(): List<PatchBundleEntity>
|
||||||
|
|
||||||
@Query("SELECT version, integrations_version, auto_update FROM patch_bundles WHERE uid = :uid")
|
@Query("SELECT version, integrations_version, auto_update FROM patch_bundles WHERE uid = :uid")
|
||||||
fun getPropsById(uid: Int): Flow<BundleProperties>
|
fun getPropsById(uid: Int): Flow<BundleProperties?>
|
||||||
|
|
||||||
@Query("UPDATE patch_bundles SET version = :patches, integrations_version = :integrations WHERE uid = :uid")
|
@Query("UPDATE patch_bundles SET version = :patches, integrations_version = :integrations WHERE uid = :uid")
|
||||||
suspend fun updateVersion(uid: Int, patches: String?, integrations: String?)
|
suspend fun updateVersion(uid: Int, patches: String?, integrations: String?)
|
||||||
@ -17,6 +17,9 @@ interface PatchBundleDao {
|
|||||||
@Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid")
|
@Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid")
|
||||||
suspend fun setAutoUpdate(uid: Int, value: Boolean)
|
suspend fun setAutoUpdate(uid: Int, value: Boolean)
|
||||||
|
|
||||||
|
@Query("UPDATE patch_bundles SET name = :value WHERE uid = :uid")
|
||||||
|
suspend fun setName(uid: Int, value: String)
|
||||||
|
|
||||||
@Query("DELETE FROM patch_bundles WHERE uid != 0")
|
@Query("DELETE FROM patch_bundles WHERE uid != 0")
|
||||||
suspend fun purgeCustomBundles()
|
suspend fun purgeCustomBundles()
|
||||||
|
|
||||||
|
@ -7,7 +7,8 @@ import java.io.InputStream
|
|||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
|
|
||||||
class LocalPatchBundle(name: String, id: Int, directory: File) : PatchBundleSource(name, id, directory) {
|
class LocalPatchBundle(name: String, id: Int, directory: File) :
|
||||||
|
PatchBundleSource(name, id, directory) {
|
||||||
suspend fun replace(patches: InputStream? = null, integrations: InputStream? = null) {
|
suspend fun replace(patches: InputStream? = null, integrations: InputStream? = null) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
patches?.let { inputStream ->
|
patches?.let { inputStream ->
|
||||||
@ -16,10 +17,16 @@ class LocalPatchBundle(name: String, id: Int, directory: File) : PatchBundleSour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
integrations?.let {
|
integrations?.let {
|
||||||
Files.copy(it, this@LocalPatchBundle.integrationsFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
Files.copy(
|
||||||
|
it,
|
||||||
|
this@LocalPatchBundle.integrationsFile.toPath(),
|
||||||
|
StandardCopyOption.REPLACE_EXISTING
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reload()
|
reload()?.also {
|
||||||
|
saveVersion(it.readManifestAttribute("Version"), null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,20 @@
|
|||||||
package app.revanced.manager.domain.bundles
|
package app.revanced.manager.domain.bundles
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.domain.repository.PatchBundlePersistenceRepository
|
||||||
import app.revanced.manager.patcher.patch.PatchBundle
|
import app.revanced.manager.patcher.patch.PatchBundle
|
||||||
import app.revanced.manager.util.tag
|
import app.revanced.manager.util.tag
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.flowOf
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
|
||||||
@ -14,13 +22,21 @@ import java.io.OutputStream
|
|||||||
* A [PatchBundle] source.
|
* A [PatchBundle] source.
|
||||||
*/
|
*/
|
||||||
@Stable
|
@Stable
|
||||||
sealed class PatchBundleSource(val name: String, val uid: Int, directory: File) {
|
sealed class PatchBundleSource(initialName: String, val uid: Int, directory: File) : KoinComponent {
|
||||||
|
protected val configRepository: PatchBundlePersistenceRepository by inject()
|
||||||
|
private val app: Application by inject()
|
||||||
protected val patchesFile = directory.resolve("patches.jar")
|
protected val patchesFile = directory.resolve("patches.jar")
|
||||||
protected val integrationsFile = directory.resolve("integrations.apk")
|
protected val integrationsFile = directory.resolve("integrations.apk")
|
||||||
|
|
||||||
private val _state = MutableStateFlow(load())
|
private val _state = MutableStateFlow(load())
|
||||||
val state = _state.asStateFlow()
|
val state = _state.asStateFlow()
|
||||||
|
|
||||||
|
private val _nameFlow = MutableStateFlow(initialName)
|
||||||
|
val nameFlow =
|
||||||
|
_nameFlow.map { it.ifEmpty { app.getString(if (isDefault) R.string.bundle_name_default else R.string.bundle_name_fallback) } }
|
||||||
|
|
||||||
|
suspend fun getName() = nameFlow.first()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the bundle has been downloaded to local storage.
|
* Returns true if the bundle has been downloaded to local storage.
|
||||||
*/
|
*/
|
||||||
@ -42,13 +58,38 @@ sealed class PatchBundleSource(val name: String, val uid: Int, directory: File)
|
|||||||
return try {
|
return try {
|
||||||
State.Loaded(PatchBundle(patchesFile, integrationsFile.takeIf(File::exists)))
|
State.Loaded(PatchBundle(patchesFile, integrationsFile.takeIf(File::exists)))
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
Log.e(tag, "Failed to load patch bundle $name", t)
|
Log.e(tag, "Failed to load patch bundle with UID $uid", t)
|
||||||
State.Failed(t)
|
State.Failed(t)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reload() {
|
suspend fun reload(): PatchBundle? {
|
||||||
_state.value = load()
|
val newState = load()
|
||||||
|
_state.value = newState
|
||||||
|
|
||||||
|
val bundle = newState.patchBundleOrNull()
|
||||||
|
// Try to read the name from the patch bundle manifest if the bundle does not have a name.
|
||||||
|
if (bundle != null && _nameFlow.value.isEmpty()) {
|
||||||
|
bundle.readManifestAttribute("Name")?.let { setName(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundle
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a flow that emits the [app.revanced.manager.data.room.bundles.BundleProperties] of this [PatchBundleSource].
|
||||||
|
* The flow will emit null if the associated [PatchBundleSource] is deleted.
|
||||||
|
*/
|
||||||
|
fun propsFlow() = configRepository.getProps(uid)
|
||||||
|
suspend fun getProps() = configRepository.getProps(uid).first()!!
|
||||||
|
|
||||||
|
suspend fun currentVersion() = getProps().versionInfo
|
||||||
|
protected suspend fun saveVersion(patches: String?, integrations: String?) =
|
||||||
|
configRepository.updateVersion(uid, patches, integrations)
|
||||||
|
|
||||||
|
suspend fun setName(name: String) {
|
||||||
|
configRepository.setName(uid, name)
|
||||||
|
_nameFlow.value = name
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed interface State {
|
sealed interface State {
|
||||||
@ -61,9 +102,12 @@ sealed class PatchBundleSource(val name: String, val uid: Int, directory: File)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object Extensions {
|
||||||
val PatchBundleSource.isDefault get() = uid == 0
|
val PatchBundleSource.isDefault inline get() = uid == 0
|
||||||
val PatchBundleSource.asRemoteOrNull get() = this as? RemotePatchBundle
|
val PatchBundleSource.asRemoteOrNull inline get() = this as? RemotePatchBundle
|
||||||
fun PatchBundleSource.propsOrNullFlow() = asRemoteOrNull?.propsFlow() ?: flowOf(null)
|
val PatchBundleSource.nameState
|
||||||
|
@Composable inline get() = nameFlow.collectAsStateWithLifecycle(
|
||||||
|
""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,6 @@ package app.revanced.manager.domain.bundles
|
|||||||
|
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import app.revanced.manager.data.room.bundles.VersionInfo
|
import app.revanced.manager.data.room.bundles.VersionInfo
|
||||||
import app.revanced.manager.domain.repository.PatchBundlePersistenceRepository
|
|
||||||
import app.revanced.manager.network.api.ReVancedAPI
|
import app.revanced.manager.network.api.ReVancedAPI
|
||||||
import app.revanced.manager.network.api.ReVancedAPI.Extensions.findAssetByType
|
import app.revanced.manager.network.api.ReVancedAPI.Extensions.findAssetByType
|
||||||
import app.revanced.manager.network.dto.BundleAsset
|
import app.revanced.manager.network.dto.BundleAsset
|
||||||
@ -15,17 +14,14 @@ import io.ktor.client.request.url
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.coroutineScope
|
import kotlinx.coroutines.coroutineScope
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.koin.core.component.KoinComponent
|
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpoint: String) :
|
sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpoint: String) :
|
||||||
PatchBundleSource(name, id, directory), KoinComponent {
|
PatchBundleSource(name, id, directory) {
|
||||||
private val configRepository: PatchBundlePersistenceRepository by inject()
|
|
||||||
protected val http: HttpService by inject()
|
protected val http: HttpService by inject()
|
||||||
|
|
||||||
protected abstract suspend fun getLatestInfo(): BundleInfo
|
protected abstract suspend fun getLatestInfo(): BundleInfo
|
||||||
@ -70,17 +66,11 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun currentVersion() = configRepository.getProps(uid).first().versionInfo
|
|
||||||
private suspend fun saveVersion(patches: String, integrations: String) =
|
|
||||||
configRepository.updateVersion(uid, patches, integrations)
|
|
||||||
|
|
||||||
suspend fun deleteLocalFiles() = withContext(Dispatchers.Default) {
|
suspend fun deleteLocalFiles() = withContext(Dispatchers.Default) {
|
||||||
arrayOf(patchesFile, integrationsFile).forEach(File::delete)
|
arrayOf(patchesFile, integrationsFile).forEach(File::delete)
|
||||||
reload()
|
reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun propsFlow() = configRepository.getProps(uid)
|
|
||||||
|
|
||||||
suspend fun setAutoUpdate(value: Boolean) = configRepository.setAutoUpdate(uid, value)
|
suspend fun setAutoUpdate(value: Boolean) = configRepository.setAutoUpdate(uid, value)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -5,7 +5,6 @@ import app.revanced.manager.data.room.AppDatabase.Companion.generateUid
|
|||||||
import app.revanced.manager.data.room.bundles.PatchBundleEntity
|
import app.revanced.manager.data.room.bundles.PatchBundleEntity
|
||||||
import app.revanced.manager.data.room.bundles.Source
|
import app.revanced.manager.data.room.bundles.Source
|
||||||
import app.revanced.manager.data.room.bundles.VersionInfo
|
import app.revanced.manager.data.room.bundles.VersionInfo
|
||||||
import io.ktor.http.*
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
|
||||||
class PatchBundlePersistenceRepository(db: AppDatabase) {
|
class PatchBundlePersistenceRepository(db: AppDatabase) {
|
||||||
@ -23,7 +22,6 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
|
|||||||
|
|
||||||
suspend fun reset() = dao.reset()
|
suspend fun reset() = dao.reset()
|
||||||
|
|
||||||
|
|
||||||
suspend fun create(name: String, source: Source, autoUpdate: Boolean = false) =
|
suspend fun create(name: String, source: Source, autoUpdate: Boolean = false) =
|
||||||
PatchBundleEntity(
|
PatchBundleEntity(
|
||||||
uid = generateUid(),
|
uid = generateUid(),
|
||||||
@ -37,17 +35,19 @@ class PatchBundlePersistenceRepository(db: AppDatabase) {
|
|||||||
|
|
||||||
suspend fun delete(uid: Int) = dao.remove(uid)
|
suspend fun delete(uid: Int) = dao.remove(uid)
|
||||||
|
|
||||||
suspend fun updateVersion(uid: Int, patches: String, integrations: String) =
|
suspend fun updateVersion(uid: Int, patches: String?, integrations: String?) =
|
||||||
dao.updateVersion(uid, patches, integrations)
|
dao.updateVersion(uid, patches, integrations)
|
||||||
|
|
||||||
suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value)
|
suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value)
|
||||||
|
|
||||||
|
suspend fun setName(uid: Int, name: String) = dao.setName(uid, name)
|
||||||
|
|
||||||
fun getProps(id: Int) = dao.getPropsById(id).distinctUntilChanged()
|
fun getProps(id: Int) = dao.getPropsById(id).distinctUntilChanged()
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
val defaultSource = PatchBundleEntity(
|
val defaultSource = PatchBundleEntity(
|
||||||
uid = 0,
|
uid = 0,
|
||||||
name = "Main",
|
name = "",
|
||||||
versionInfo = VersionInfo(),
|
versionInfo = VersionInfo(),
|
||||||
source = Source.API,
|
source = Source.API,
|
||||||
autoUpdate = false
|
autoUpdate = false
|
||||||
|
@ -137,16 +137,16 @@ class PatchBundleRepository(
|
|||||||
private fun addBundle(patchBundle: PatchBundleSource) =
|
private fun addBundle(patchBundle: PatchBundleSource) =
|
||||||
_sources.update { it.toMutableMap().apply { put(patchBundle.uid, patchBundle) } }
|
_sources.update { it.toMutableMap().apply { put(patchBundle.uid, patchBundle) } }
|
||||||
|
|
||||||
suspend fun createLocal(name: String, patches: InputStream, integrations: InputStream?) {
|
suspend fun createLocal(patches: InputStream, integrations: InputStream?) {
|
||||||
val id = persistenceRepo.create(name, SourceInfo.Local).uid
|
val uid = persistenceRepo.create("", SourceInfo.Local).uid
|
||||||
val bundle = LocalPatchBundle(name, id, directoryOf(id))
|
val bundle = LocalPatchBundle("", uid, directoryOf(uid))
|
||||||
|
|
||||||
bundle.replace(patches, integrations)
|
bundle.replace(patches, integrations)
|
||||||
addBundle(bundle)
|
addBundle(bundle)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun createRemote(name: String, url: String, autoUpdate: Boolean) {
|
suspend fun createRemote(url: String, autoUpdate: Boolean) {
|
||||||
val entity = persistenceRepo.create(name, SourceInfo.from(url), autoUpdate)
|
val entity = persistenceRepo.create("", SourceInfo.from(url), autoUpdate)
|
||||||
addBundle(entity.load())
|
addBundle(entity.load())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,8 +174,8 @@ class PatchBundleRepository(
|
|||||||
|
|
||||||
getBundlesByType<RemotePatchBundle>().forEach {
|
getBundlesByType<RemotePatchBundle>().forEach {
|
||||||
launch {
|
launch {
|
||||||
if (!it.propsFlow().first().autoUpdate) return@launch
|
if (!it.getProps().autoUpdate) return@launch
|
||||||
Log.d(tag, "Updating patch bundle: ${it.name}")
|
Log.d(tag, "Updating patch bundle: ${it.getName()}")
|
||||||
it.update()
|
it.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ class Session(
|
|||||||
var nextPatchIndex = 0
|
var nextPatchIndex = 0
|
||||||
|
|
||||||
updateProgress(
|
updateProgress(
|
||||||
name = androidContext.getString(R.string.applying_patch, selectedPatches[nextPatchIndex]),
|
name = androidContext.getString(R.string.executing_patch, selectedPatches[nextPatchIndex]),
|
||||||
state = State.RUNNING
|
state = State.RUNNING
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ class Session(
|
|||||||
|
|
||||||
if (exception != null) {
|
if (exception != null) {
|
||||||
updateProgress(
|
updateProgress(
|
||||||
name = androidContext.getString(R.string.failed_to_apply_patch, patch.name),
|
name = androidContext.getString(R.string.failed_to_execute_patch, patch.name),
|
||||||
state = State.FAILED,
|
state = State.FAILED,
|
||||||
message = exception.stackTraceToString()
|
message = exception.stackTraceToString()
|
||||||
)
|
)
|
||||||
@ -72,7 +72,7 @@ class Session(
|
|||||||
|
|
||||||
selectedPatches.getOrNull(nextPatchIndex)?.let { nextPatch ->
|
selectedPatches.getOrNull(nextPatchIndex)?.let { nextPatch ->
|
||||||
updateProgress(
|
updateProgress(
|
||||||
name = androidContext.getString(R.string.applying_patch, nextPatch.name)
|
name = androidContext.getString(R.string.executing_patch, nextPatch.name)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,7 +82,7 @@ class Session(
|
|||||||
updateProgress(
|
updateProgress(
|
||||||
state = State.COMPLETED,
|
state = State.COMPLETED,
|
||||||
name = androidContext.resources.getQuantityString(
|
name = androidContext.resources.getQuantityString(
|
||||||
R.plurals.patches_applied,
|
R.plurals.patches_executed,
|
||||||
selectedPatches.size,
|
selectedPatches.size,
|
||||||
selectedPatches.size
|
selectedPatches.size
|
||||||
)
|
)
|
||||||
@ -105,7 +105,6 @@ class Session(
|
|||||||
logger.info("Merging integrations")
|
logger.info("Merging integrations")
|
||||||
acceptIntegrations(integrations.toSet())
|
acceptIntegrations(integrations.toSet())
|
||||||
acceptPatches(selectedPatches.toSet())
|
acceptPatches(selectedPatches.toSet())
|
||||||
updateProgress(state = State.COMPLETED) // Merging
|
|
||||||
|
|
||||||
logger.info("Applying patches...")
|
logger.info("Applying patches...")
|
||||||
applyPatchesVerbose(selectedPatches.sortedBy { it.name })
|
applyPatchesVerbose(selectedPatches.sortedBy { it.name })
|
||||||
|
@ -5,6 +5,8 @@ import app.revanced.manager.util.tag
|
|||||||
import app.revanced.patcher.PatchBundleLoader
|
import app.revanced.patcher.PatchBundleLoader
|
||||||
import app.revanced.patcher.patch.Patch
|
import app.revanced.patcher.patch.Patch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.util.jar.JarFile
|
||||||
|
|
||||||
class PatchBundle(val patchesJar: File, val integrations: File?) {
|
class PatchBundle(val patchesJar: File, val integrations: File?) {
|
||||||
private val loader = object : Iterable<Patch<*>> {
|
private val loader = object : Iterable<Patch<*>> {
|
||||||
@ -25,6 +27,17 @@ class PatchBundle(val patchesJar: File, val integrations: File?) {
|
|||||||
*/
|
*/
|
||||||
val patches = loader.map(::PatchInfo)
|
val patches = loader.map(::PatchInfo)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [java.util.jar.Manifest] of [patchesJar].
|
||||||
|
*/
|
||||||
|
private val manifest = try {
|
||||||
|
JarFile(patchesJar).use { it.manifest }
|
||||||
|
} catch (_: IOException) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readManifestAttribute(name: String) = manifest?.mainAttributes?.getValue(name)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load all patches compatible with the specified package.
|
* Load all patches compatible with the specified package.
|
||||||
*/
|
*/
|
||||||
|
@ -184,11 +184,11 @@ class PatcherWorker(
|
|||||||
Log.i(tag, "Patching succeeded".logFmt())
|
Log.i(tag, "Patching succeeded".logFmt())
|
||||||
Result.success()
|
Result.success()
|
||||||
} catch (e: ProcessRuntime.RemoteFailureException) {
|
} catch (e: ProcessRuntime.RemoteFailureException) {
|
||||||
Log.e(tag, "An exception occured in the remote process while patching. ${e.originalStackTrace}".logFmt())
|
Log.e(tag, "An exception occurred in the remote process while patching. ${e.originalStackTrace}".logFmt())
|
||||||
updateProgress(state = State.FAILED, message = e.originalStackTrace)
|
updateProgress(state = State.FAILED, message = e.originalStackTrace)
|
||||||
Result.failure()
|
Result.failure()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(tag, "An exception occured while patching".logFmt(), e)
|
Log.e(tag, "An exception occurred while patching".logFmt(), e)
|
||||||
updateProgress(state = State.FAILED, message = e.stackTraceToString())
|
updateProgress(state = State.FAILED, message = e.stackTraceToString())
|
||||||
Result.failure()
|
Result.failure()
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -30,7 +30,7 @@ import app.revanced.manager.util.isDebuggable
|
|||||||
fun BaseBundleDialog(
|
fun BaseBundleDialog(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
isDefault: Boolean,
|
isDefault: Boolean,
|
||||||
name: String,
|
name: String?,
|
||||||
onNameChange: ((String) -> Unit)? = null,
|
onNameChange: ((String) -> Unit)? = null,
|
||||||
remoteUrl: String?,
|
remoteUrl: String?,
|
||||||
onRemoteUrlChange: ((String) -> Unit)? = null,
|
onRemoteUrlChange: ((String) -> Unit)? = null,
|
||||||
@ -52,32 +52,34 @@ fun BaseBundleDialog(
|
|||||||
)
|
)
|
||||||
.then(modifier)
|
.then(modifier)
|
||||||
) {
|
) {
|
||||||
var showNameInputDialog by rememberSaveable {
|
if (name != null) {
|
||||||
mutableStateOf(false)
|
var showNameInputDialog by rememberSaveable {
|
||||||
}
|
mutableStateOf(false)
|
||||||
if (showNameInputDialog) {
|
}
|
||||||
TextInputDialog(
|
if (showNameInputDialog) {
|
||||||
initial = name,
|
TextInputDialog(
|
||||||
title = stringResource(R.string.bundle_input_name),
|
initial = name,
|
||||||
onDismissRequest = {
|
title = stringResource(R.string.bundle_input_name),
|
||||||
showNameInputDialog = false
|
onDismissRequest = {
|
||||||
},
|
showNameInputDialog = false
|
||||||
onConfirm = {
|
},
|
||||||
showNameInputDialog = false
|
onConfirm = {
|
||||||
onNameChange?.invoke(it)
|
showNameInputDialog = false
|
||||||
},
|
onNameChange?.invoke(it)
|
||||||
validator = {
|
},
|
||||||
it.length in 1..19
|
validator = {
|
||||||
|
it.length in 1..19
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
BundleListItem(
|
||||||
|
headlineText = stringResource(R.string.bundle_input_name),
|
||||||
|
supportingText = name.ifEmpty { stringResource(R.string.field_not_set) },
|
||||||
|
modifier = Modifier.clickable(enabled = onNameChange != null) {
|
||||||
|
showNameInputDialog = true
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
BundleListItem(
|
|
||||||
headlineText = stringResource(R.string.bundle_input_name),
|
|
||||||
supportingText = name.ifEmpty { stringResource(R.string.field_not_set) },
|
|
||||||
modifier = Modifier.clickable(enabled = onNameChange != null) {
|
|
||||||
showNameInputDialog = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
remoteUrl?.takeUnless { isDefault }?.let { url ->
|
remoteUrl?.takeUnless { isDefault }?.let { url ->
|
||||||
var showUrlInputDialog by rememberSaveable {
|
var showUrlInputDialog by rememberSaveable {
|
||||||
|
@ -4,7 +4,7 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.outlined.DeleteOutline
|
import androidx.compose.material.icons.outlined.DeleteOutline
|
||||||
import androidx.compose.material.icons.outlined.Refresh
|
import androidx.compose.material.icons.outlined.Update
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
@ -23,9 +23,9 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.domain.bundles.LocalPatchBundle
|
import app.revanced.manager.domain.bundles.LocalPatchBundle
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource
|
import app.revanced.manager.domain.bundles.PatchBundleSource
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.asRemoteOrNull
|
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.asRemoteOrNull
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.isDefault
|
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefault
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.propsOrNullFlow
|
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ fun BundleInformationDialog(
|
|||||||
bundle.state.map { it.patchBundleOrNull()?.patches?.size ?: 0 }
|
bundle.state.map { it.patchBundleOrNull()?.patches?.size ?: 0 }
|
||||||
}.collectAsStateWithLifecycle(0)
|
}.collectAsStateWithLifecycle(0)
|
||||||
val props by remember(bundle) {
|
val props by remember(bundle) {
|
||||||
bundle.propsOrNullFlow()
|
bundle.propsFlow()
|
||||||
}.collectAsStateWithLifecycle(null)
|
}.collectAsStateWithLifecycle(null)
|
||||||
|
|
||||||
if (viewCurrentBundlePatches) {
|
if (viewCurrentBundlePatches) {
|
||||||
@ -63,10 +63,12 @@ fun BundleInformationDialog(
|
|||||||
dismissOnBackPress = true
|
dismissOnBackPress = true
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
val bundleName by bundle.nameState
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
BundleTopBar(
|
BundleTopBar(
|
||||||
title = bundle.name,
|
title = bundleName,
|
||||||
onBackClick = onDismissRequest,
|
onBackClick = onDismissRequest,
|
||||||
onBackIcon = {
|
onBackIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
@ -86,7 +88,7 @@ fun BundleInformationDialog(
|
|||||||
if (!isLocal) {
|
if (!isLocal) {
|
||||||
IconButton(onClick = onRefreshButton) {
|
IconButton(onClick = onRefreshButton) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.Outlined.Refresh,
|
Icons.Outlined.Update,
|
||||||
stringResource(R.string.refresh)
|
stringResource(R.string.refresh)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -98,7 +100,8 @@ fun BundleInformationDialog(
|
|||||||
BaseBundleDialog(
|
BaseBundleDialog(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
isDefault = bundle.isDefault,
|
isDefault = bundle.isDefault,
|
||||||
name = bundle.name,
|
name = bundleName,
|
||||||
|
onNameChange = { composableScope.launch { bundle.setName(it) } },
|
||||||
remoteUrl = bundle.asRemoteOrNull?.endpoint,
|
remoteUrl = bundle.asRemoteOrNull?.endpoint,
|
||||||
patchCount = patchCount,
|
patchCount = patchCount,
|
||||||
version = props?.versionInfo?.patches,
|
version = props?.versionInfo?.patches,
|
||||||
|
@ -27,7 +27,7 @@ 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.domain.bundles.PatchBundleSource
|
import app.revanced.manager.domain.bundles.PatchBundleSource
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.propsOrNullFlow
|
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@ -45,8 +45,9 @@ fun BundleItem(
|
|||||||
val state by bundle.state.collectAsStateWithLifecycle()
|
val state by bundle.state.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val version by remember(bundle) {
|
val version by remember(bundle) {
|
||||||
bundle.propsOrNullFlow().map { props -> props?.versionInfo?.patches }
|
bundle.propsFlow().map { props -> props?.versionInfo?.patches }
|
||||||
}.collectAsStateWithLifecycle(null)
|
}.collectAsStateWithLifecycle(null)
|
||||||
|
val name by bundle.nameState
|
||||||
|
|
||||||
if (viewBundleDialogPage) {
|
if (viewBundleDialogPage) {
|
||||||
BundleInformationDialog(
|
BundleInformationDialog(
|
||||||
@ -77,10 +78,10 @@ fun BundleItem(
|
|||||||
}
|
}
|
||||||
} else null,
|
} else null,
|
||||||
|
|
||||||
headlineContent = { Text(text = bundle.name) },
|
headlineContent = { Text(name) },
|
||||||
supportingContent = {
|
supportingContent = {
|
||||||
state.patchBundleOrNull()?.patches?.size?.let { patchCount ->
|
state.patchBundleOrNull()?.patches?.size?.let { patchCount ->
|
||||||
Text(text = pluralStringResource(R.plurals.patch_count, patchCount, patchCount))
|
Text(pluralStringResource(R.plurals.patch_count, patchCount, patchCount))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
@ -95,7 +96,7 @@ fun BundleItem(
|
|||||||
|
|
||||||
icon?.let { (vector, description) ->
|
icon?.let { (vector, description) ->
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = vector,
|
vector,
|
||||||
contentDescription = stringResource(description),
|
contentDescription = stringResource(description),
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
tint = MaterialTheme.colorScheme.error
|
tint = MaterialTheme.colorScheme.error
|
||||||
|
@ -12,10 +12,12 @@ import androidx.compose.material3.ModalBottomSheet
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource
|
import app.revanced.manager.domain.bundles.PatchBundleSource
|
||||||
|
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@ -51,6 +53,7 @@ fun BundleSelector(bundles: List<PatchBundleSource>, onFinish: (PatchBundleSourc
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
bundles.forEach {
|
bundles.forEach {
|
||||||
|
val name by it.nameState
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
@ -62,7 +65,7 @@ fun BundleSelector(bundles: List<PatchBundleSource>, onFinish: (PatchBundleSourc
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = it.name,
|
name,
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
)
|
)
|
||||||
|
@ -44,8 +44,8 @@ import app.revanced.manager.util.JAR_MIMETYPE
|
|||||||
@Composable
|
@Composable
|
||||||
fun ImportPatchBundleDialog(
|
fun ImportPatchBundleDialog(
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onRemoteSubmit: (String, String, Boolean) -> Unit,
|
onRemoteSubmit: (String, Boolean) -> Unit,
|
||||||
onLocalSubmit: (String, Uri, Uri?) -> Unit
|
onLocalSubmit: (Uri, Uri?) -> Unit
|
||||||
) {
|
) {
|
||||||
var currentStep by rememberSaveable { mutableIntStateOf(0) }
|
var currentStep by rememberSaveable { mutableIntStateOf(0) }
|
||||||
var bundleType by rememberSaveable { mutableStateOf(BundleType.Remote) }
|
var bundleType by rememberSaveable { mutableStateOf(BundleType.Remote) }
|
||||||
@ -59,11 +59,19 @@ fun ImportPatchBundleDialog(
|
|||||||
uri?.let { patchBundle = it }
|
uri?.let { patchBundle = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun launchPatchActivity() {
|
||||||
|
patchActivityLauncher.launch(JAR_MIMETYPE)
|
||||||
|
}
|
||||||
|
|
||||||
val integrationsActivityLauncher =
|
val integrationsActivityLauncher =
|
||||||
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||||
uri?.let { integrations = it }
|
uri?.let { integrations = it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun launchIntegrationsActivity() {
|
||||||
|
integrationsActivityLauncher.launch(APK_MIMETYPE)
|
||||||
|
}
|
||||||
|
|
||||||
val steps = listOf<@Composable () -> Unit>(
|
val steps = listOf<@Composable () -> Unit>(
|
||||||
{
|
{
|
||||||
SelectBundleTypeStep(bundleType) { selectedType ->
|
SelectBundleTypeStep(bundleType) { selectedType ->
|
||||||
@ -77,8 +85,8 @@ fun ImportPatchBundleDialog(
|
|||||||
integrations,
|
integrations,
|
||||||
remoteUrl,
|
remoteUrl,
|
||||||
autoUpdate,
|
autoUpdate,
|
||||||
{ patchActivityLauncher.launch(JAR_MIMETYPE) },
|
{ launchPatchActivity() },
|
||||||
{ integrationsActivityLauncher.launch(APK_MIMETYPE) },
|
{ launchIntegrationsActivity() },
|
||||||
{ remoteUrl = it },
|
{ remoteUrl = it },
|
||||||
{ autoUpdate = it }
|
{ autoUpdate = it }
|
||||||
)
|
)
|
||||||
@ -108,13 +116,12 @@ fun ImportPatchBundleDialog(
|
|||||||
when (bundleType) {
|
when (bundleType) {
|
||||||
BundleType.Local -> patchBundle?.let {
|
BundleType.Local -> patchBundle?.let {
|
||||||
onLocalSubmit(
|
onLocalSubmit(
|
||||||
"BundleName",
|
|
||||||
it,
|
it,
|
||||||
integrations
|
integrations
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
BundleType.Remote -> onRemoteSubmit("BundleName", remoteUrl, autoUpdate)
|
BundleType.Remote -> onRemoteSubmit(remoteUrl, autoUpdate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
@ -75,7 +75,7 @@ data class BundleInfo(
|
|||||||
targetList.add(it)
|
targetList.add(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
BundleInfo(source.name, source.uid, supported, unsupported, universal)
|
BundleInfo(source.getName(), source.uid, supported, unsupported, universal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ 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.data.room.apps.installed.InstalledApp
|
import app.revanced.manager.data.room.apps.installed.InstalledApp
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.isDefault
|
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefault
|
||||||
import app.revanced.manager.patcher.aapt.Aapt
|
import app.revanced.manager.patcher.aapt.Aapt
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
import app.revanced.manager.ui.component.AutoUpdatesDialog
|
import app.revanced.manager.ui.component.AutoUpdatesDialog
|
||||||
@ -96,13 +96,13 @@ fun DashboardScreen(
|
|||||||
if (showAddBundleDialog) {
|
if (showAddBundleDialog) {
|
||||||
ImportPatchBundleDialog(
|
ImportPatchBundleDialog(
|
||||||
onDismiss = { showAddBundleDialog = false },
|
onDismiss = { showAddBundleDialog = false },
|
||||||
onLocalSubmit = { name, patches, integrations ->
|
onLocalSubmit = { patches, integrations ->
|
||||||
showAddBundleDialog = false
|
showAddBundleDialog = false
|
||||||
vm.createLocalSource(name, patches, integrations)
|
vm.createLocalSource(patches, integrations)
|
||||||
},
|
},
|
||||||
onRemoteSubmit = { name, url, autoUpdate ->
|
onRemoteSubmit = { url, autoUpdate ->
|
||||||
showAddBundleDialog = false
|
showAddBundleDialog = false
|
||||||
vm.createRemoteSource(name, url, autoUpdate)
|
vm.createRemoteSource(url, autoUpdate)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
@ -94,6 +94,8 @@ fun PatchesSelectorScreen(
|
|||||||
derivedStateOf { vm.selectionIsValid(bundles) }
|
derivedStateOf { vm.selectionIsValid(bundles) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val patchLazyListStates = remember(bundles) { List(bundles.size) { LazyListState() } }
|
||||||
|
|
||||||
if (showBottomSheet) {
|
if (showBottomSheet) {
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
@ -255,7 +257,6 @@ fun PatchesSelectorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val patchLazyListState = rememberLazyListState()
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
AppTopBar(
|
AppTopBar(
|
||||||
@ -284,7 +285,7 @@ fun PatchesSelectorScreen(
|
|||||||
ExtendedFloatingActionButton(
|
ExtendedFloatingActionButton(
|
||||||
text = { Text(stringResource(R.string.save)) },
|
text = { Text(stringResource(R.string.save)) },
|
||||||
icon = { Icon(Icons.Outlined.Save, null) },
|
icon = { Icon(Icons.Outlined.Save, null) },
|
||||||
expanded = patchLazyListState.isScrollingUp,
|
expanded = patchLazyListStates.getOrNull(pagerState.currentPage)?.isScrollingUp ?: true,
|
||||||
onClick = {
|
onClick = {
|
||||||
// TODO: only allow this if all required options have been set.
|
// TODO: only allow this if all required options have been set.
|
||||||
onSave(vm.getCustomSelection(), vm.getOptions())
|
onSave(vm.getCustomSelection(), vm.getOptions())
|
||||||
@ -324,11 +325,13 @@ fun PatchesSelectorScreen(
|
|||||||
state = pagerState,
|
state = pagerState,
|
||||||
userScrollEnabled = true,
|
userScrollEnabled = true,
|
||||||
pageContent = { index ->
|
pageContent = { index ->
|
||||||
|
// Avoid crashing if the lists have not been fully initialized yet.
|
||||||
|
if (index > bundles.lastIndex || bundles.size != patchLazyListStates.size) return@HorizontalPager
|
||||||
val bundle = bundles[index]
|
val bundle = bundles[index]
|
||||||
|
|
||||||
LazyColumnWithScrollbar(
|
LazyColumnWithScrollbar(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
state = patchLazyListState
|
state = patchLazyListStates[index]
|
||||||
) {
|
) {
|
||||||
patchList(
|
patchList(
|
||||||
uid = bundle.uid,
|
uid = bundle.uid,
|
||||||
|
@ -12,7 +12,7 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.data.platform.NetworkInfo
|
import app.revanced.manager.data.platform.NetworkInfo
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource
|
import app.revanced.manager.domain.bundles.PatchBundleSource
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.asRemoteOrNull
|
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.asRemoteOrNull
|
||||||
import app.revanced.manager.domain.bundles.RemotePatchBundle
|
import app.revanced.manager.domain.bundles.RemotePatchBundle
|
||||||
import app.revanced.manager.domain.manager.PreferencesManager
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||||
@ -80,20 +80,17 @@ class DashboardViewModel(
|
|||||||
fun cancelSourceSelection() {
|
fun cancelSourceSelection() {
|
||||||
selectedSources.clear()
|
selectedSources.clear()
|
||||||
}
|
}
|
||||||
fun createLocalSource(name: String, patchBundle: Uri, integrations: Uri?) =
|
fun createLocalSource(patchBundle: Uri, integrations: Uri?) =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
contentResolver.openInputStream(patchBundle)!!.use { patchesStream ->
|
contentResolver.openInputStream(patchBundle)!!.use { patchesStream ->
|
||||||
val integrationsStream = integrations?.let { contentResolver.openInputStream(it) }
|
integrations?.let { contentResolver.openInputStream(it) }.use { integrationsStream ->
|
||||||
try {
|
patchBundleRepository.createLocal(patchesStream, integrationsStream)
|
||||||
patchBundleRepository.createLocal(name, patchesStream, integrationsStream)
|
|
||||||
} finally {
|
|
||||||
integrationsStream?.close()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createRemoteSource(name: String, apiUrl: String, autoUpdate: Boolean) =
|
fun createRemoteSource(apiUrl: String, autoUpdate: Boolean) =
|
||||||
viewModelScope.launch { patchBundleRepository.createRemote(name, apiUrl, autoUpdate) }
|
viewModelScope.launch { patchBundleRepository.createRemote(apiUrl, autoUpdate) }
|
||||||
|
|
||||||
fun delete(bundle: PatchBundleSource) =
|
fun delete(bundle: PatchBundleSource) =
|
||||||
viewModelScope.launch { patchBundleRepository.remove(bundle) }
|
viewModelScope.launch { patchBundleRepository.remove(bundle) }
|
||||||
@ -107,9 +104,9 @@ class DashboardViewModel(
|
|||||||
RemotePatchBundle.updateFailMsg
|
RemotePatchBundle.updateFailMsg
|
||||||
) {
|
) {
|
||||||
if (bundle.update())
|
if (bundle.update())
|
||||||
app.toast(app.getString(R.string.bundle_update_success, bundle.name))
|
app.toast(app.getString(R.string.bundle_update_success, bundle.getName()))
|
||||||
else
|
else
|
||||||
app.toast(app.getString(R.string.bundle_update_unavailable, bundle.name))
|
app.toast(app.getString(R.string.bundle_update_unavailable, bundle.getName()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,7 +11,7 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
import app.revanced.manager.domain.bundles.PatchBundleSource.Companion.asRemoteOrNull
|
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.asRemoteOrNull
|
||||||
import app.revanced.manager.domain.manager.KeystoreManager
|
import app.revanced.manager.domain.manager.KeystoreManager
|
||||||
import app.revanced.manager.domain.manager.PreferencesManager
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||||
|
@ -316,13 +316,9 @@ class PatcherViewModel(
|
|||||||
context.getString(R.string.patcher_step_unpack),
|
context.getString(R.string.patcher_step_unpack),
|
||||||
StepCategory.PREPARING
|
StepCategory.PREPARING
|
||||||
),
|
),
|
||||||
Step(
|
|
||||||
context.getString(R.string.patcher_step_integrations),
|
|
||||||
StepCategory.PREPARING
|
|
||||||
),
|
|
||||||
|
|
||||||
Step(
|
Step(
|
||||||
context.getString(R.string.apply_patches),
|
context.getString(R.string.execute_patches),
|
||||||
StepCategory.PATCHING
|
StepCategory.PATCHING
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -4,8 +4,8 @@
|
|||||||
<item quantity="one">%d patch</item>
|
<item quantity="one">%d patch</item>
|
||||||
<item quantity="other">%d patches</item>
|
<item quantity="other">%d patches</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<plurals name="patches_applied">
|
<plurals name="patches_executed">
|
||||||
<item quantity="one">Applied %d patch</item>
|
<item quantity="one">Executed %d patch</item>
|
||||||
<item quantity="other">Applied %d patches</item>
|
<item quantity="other">Executed %d patches</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
</resources>
|
</resources>
|
@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
<string name="bundle_missing">Missing</string>
|
<string name="bundle_missing">Missing</string>
|
||||||
<string name="bundle_error">Error</string>
|
<string name="bundle_error">Error</string>
|
||||||
|
<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">%1$s • %2$d available patches</string>
|
||||||
|
|
||||||
@ -162,7 +164,7 @@
|
|||||||
<string name="continue_anyways">Continue anyways</string>
|
<string name="continue_anyways">Continue anyways</string>
|
||||||
<string name="download_another_version">Download another version</string>
|
<string name="download_another_version">Download another version</string>
|
||||||
<string name="download_app">Download app</string>
|
<string name="download_app">Download app</string>
|
||||||
<string name="download_apk">Download APK</string>
|
<string name="download_apk">Download APK file</string>
|
||||||
<string name="source_download_fail">Failed to download patch bundle: %s</string>
|
<string name="source_download_fail">Failed to download patch bundle: %s</string>
|
||||||
<string name="source_replace_fail">Failed to load updated patch bundle: %s</string>
|
<string name="source_replace_fail">Failed to load updated patch bundle: %s</string>
|
||||||
<string name="source_replace_integrations_fail">Failed to update integrations: %s</string>
|
<string name="source_replace_integrations_fail">Failed to update integrations: %s</string>
|
||||||
@ -248,16 +250,15 @@
|
|||||||
|
|
||||||
<string name="patcher_step_group_preparing">Preparing</string>
|
<string name="patcher_step_group_preparing">Preparing</string>
|
||||||
<string name="patcher_step_load_patches">Load patches</string>
|
<string name="patcher_step_load_patches">Load patches</string>
|
||||||
<string name="patcher_step_unpack">Unpack APK</string>
|
<string name="patcher_step_unpack">Read APK file</string>
|
||||||
<string name="patcher_step_integrations">Merge Integrations</string>
|
|
||||||
<string name="patcher_step_group_patching">Patching</string>
|
<string name="patcher_step_group_patching">Patching</string>
|
||||||
<string name="patcher_step_group_saving">Saving</string>
|
<string name="patcher_step_group_saving">Saving</string>
|
||||||
<string name="patcher_step_write_patched">Write patched APK</string>
|
<string name="patcher_step_write_patched">Write patched APK file</string>
|
||||||
<string name="patcher_step_sign_apk">Sign APK</string>
|
<string name="patcher_step_sign_apk">Sign patched APK file</string>
|
||||||
<string name="patcher_notification_message">Patching in progress…</string>
|
<string name="patcher_notification_message">Patching in progress…</string>
|
||||||
<string name="apply_patches">Apply patches</string>
|
<string name="execute_patches">Execute patches</string>
|
||||||
<string name="applying_patch">Applying %s</string>
|
<string name="executing_patch">Execute %s</string>
|
||||||
<string name="failed_to_apply_patch">Failed to apply %s</string>
|
<string name="failed_to_execute_patch">Failed to execute %s</string>
|
||||||
|
|
||||||
<string name="step_completed">completed</string>
|
<string name="step_completed">completed</string>
|
||||||
<string name="step_failed">failed</string>
|
<string name="step_failed">failed</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user