mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-04-30 05:54:26 +02:00
Merge branch 'compose-dev' into fix/minor-issues
This commit is contained in:
commit
d39804f7ed
6
.github/workflows/pr-build.yml
vendored
6
.github/workflows/pr-build.yml
vendored
@ -23,8 +23,8 @@ jobs:
|
|||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Set up Gradle
|
||||||
uses: gradle/gradle-build-action@v2
|
uses: gradle/actions/setup-gradle@v3
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
env:
|
env:
|
||||||
@ -38,7 +38,7 @@ jobs:
|
|||||||
run: mv app/build/outputs/apk/release/app-release.apk revanced-manager-${{ env.COMMIT_HASH }}.apk
|
run: mv app/build/outputs/apk/release/app-release.apk revanced-manager-${{ env.COMMIT_HASH }}.apk
|
||||||
|
|
||||||
- name: Upload build
|
- name: Upload build
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: revanced-manager
|
name: revanced-manager
|
||||||
path: revanced-manager-${{ env.COMMIT_HASH }}.apk
|
path: revanced-manager-${{ env.COMMIT_HASH }}.apk
|
||||||
|
6
.github/workflows/release-build.yml
vendored
6
.github/workflows/release-build.yml
vendored
@ -20,10 +20,8 @@ jobs:
|
|||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Set up Gradle
|
||||||
uses: gradle/gradle-build-action@v2
|
uses: gradle/actions/setup-gradle@v3
|
||||||
with:
|
|
||||||
cache-disabled: true
|
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
env:
|
env:
|
||||||
|
2
.github/workflows/update-documentation.yml
vendored
2
.github/workflows/update-documentation.yml
vendored
@ -11,7 +11,7 @@ jobs:
|
|||||||
name: Dispatch event to documentation repository
|
name: Dispatch event to documentation repository
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
steps:
|
steps:
|
||||||
- uses: peter-evans/repository-dispatch@v2
|
- uses: peter-evans/repository-dispatch@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
||||||
repository: revanced/revanced-documentation
|
repository: revanced/revanced-documentation
|
||||||
|
@ -20,9 +20,6 @@ android {
|
|||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "0.0.1"
|
versionName = "0.0.1"
|
||||||
resourceConfigurations.addAll(listOf(
|
|
||||||
"en",
|
|
||||||
))
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +85,12 @@ android {
|
|||||||
buildFeatures.aidl = true
|
buildFeatures.aidl = true
|
||||||
buildFeatures.buildConfig=true
|
buildFeatures.buildConfig=true
|
||||||
|
|
||||||
|
android {
|
||||||
|
androidResources {
|
||||||
|
generateLocaleConfig = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
composeOptions.kotlinCompilerExtensionVersion = "1.5.10"
|
composeOptions.kotlinCompilerExtensionVersion = "1.5.10"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
|
@ -1,23 +1,18 @@
|
|||||||
package app.revanced.manager
|
package app.revanced.manager
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Intent
|
|
||||||
import app.revanced.manager.di.*
|
import app.revanced.manager.di.*
|
||||||
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
|
||||||
import app.revanced.manager.service.ManagerRootService
|
|
||||||
import app.revanced.manager.service.RootConnection
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import coil.ImageLoader
|
import coil.ImageLoader
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
import com.topjohnwu.superuser.internal.BuilderImpl
|
import com.topjohnwu.superuser.internal.BuilderImpl
|
||||||
import com.topjohnwu.superuser.ipc.RootService
|
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
||||||
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
|
||||||
import org.koin.android.ext.android.get
|
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.android.ext.koin.androidLogger
|
import org.koin.android.ext.koin.androidLogger
|
||||||
@ -61,9 +56,6 @@ class ManagerApplication : Application() {
|
|||||||
val shellBuilder = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER)
|
val shellBuilder = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER)
|
||||||
Shell.setDefaultBuilder(shellBuilder)
|
Shell.setDefaultBuilder(shellBuilder)
|
||||||
|
|
||||||
val intent = Intent(this, ManagerRootService::class.java)
|
|
||||||
RootService.bind(intent, get<RootConnection>())
|
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
prefs.preload()
|
prefs.preload()
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
package app.revanced.manager.di
|
package app.revanced.manager.di
|
||||||
|
|
||||||
import app.revanced.manager.domain.installer.RootInstaller
|
import app.revanced.manager.domain.installer.RootInstaller
|
||||||
import app.revanced.manager.service.RootConnection
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val rootModule = module {
|
val rootModule = module {
|
||||||
singleOf(::RootConnection)
|
|
||||||
singleOf(::RootInstaller)
|
singleOf(::RootInstaller)
|
||||||
}
|
}
|
@ -18,6 +18,7 @@ val viewModelModule = module {
|
|||||||
viewModelOf(::ChangelogsViewModel)
|
viewModelOf(::ChangelogsViewModel)
|
||||||
viewModelOf(::ImportExportViewModel)
|
viewModelOf(::ImportExportViewModel)
|
||||||
viewModelOf(::AboutViewModel)
|
viewModelOf(::AboutViewModel)
|
||||||
|
viewModelOf(::DeveloperOptionsViewModel)
|
||||||
viewModelOf(::ContributorViewModel)
|
viewModelOf(::ContributorViewModel)
|
||||||
viewModelOf(::DownloadsViewModel)
|
viewModelOf(::DownloadsViewModel)
|
||||||
viewModelOf(::InstalledAppsViewModel)
|
viewModelOf(::InstalledAppsViewModel)
|
||||||
|
@ -9,9 +9,11 @@ import app.revanced.manager.R
|
|||||||
import app.revanced.manager.domain.repository.PatchBundlePersistenceRepository
|
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.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
@ -80,8 +82,8 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil
|
|||||||
* Create a flow that emits the [app.revanced.manager.data.room.bundles.BundleProperties] of this [PatchBundleSource].
|
* 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.
|
* The flow will emit null if the associated [PatchBundleSource] is deleted.
|
||||||
*/
|
*/
|
||||||
fun propsFlow() = configRepository.getProps(uid)
|
fun propsFlow() = configRepository.getProps(uid).flowOn(Dispatchers.Default)
|
||||||
suspend fun getProps() = configRepository.getProps(uid).first()!!
|
suspend fun getProps() = propsFlow().first()!!
|
||||||
|
|
||||||
suspend fun currentVersion() = getProps().versionInfo
|
suspend fun currentVersion() = getProps().versionInfo
|
||||||
protected suspend fun saveVersion(patches: String?, integrations: String?) =
|
protected suspend fun saveVersion(patches: String?, integrations: String?) =
|
||||||
|
@ -1,49 +1,93 @@
|
|||||||
package app.revanced.manager.domain.installer
|
package app.revanced.manager.domain.installer
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import app.revanced.manager.service.RootConnection
|
import android.content.ComponentName
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.os.IBinder
|
||||||
|
import app.revanced.manager.IRootSystemService
|
||||||
|
import app.revanced.manager.service.ManagerRootService
|
||||||
import app.revanced.manager.util.PM
|
import app.revanced.manager.util.PM
|
||||||
import com.topjohnwu.superuser.Shell
|
import com.topjohnwu.superuser.Shell
|
||||||
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
|
import com.topjohnwu.superuser.nio.FileSystemManager
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.time.withTimeoutOrNull
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
class RootInstaller(
|
class RootInstaller(
|
||||||
private val app: Application,
|
private val app: Application,
|
||||||
private val rootConnection: RootConnection,
|
|
||||||
private val pm: PM
|
private val pm: PM
|
||||||
) {
|
) : ServiceConnection {
|
||||||
|
private var remoteFS = CompletableDeferred<FileSystemManager>()
|
||||||
|
|
||||||
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
|
val ipc = IRootSystemService.Stub.asInterface(service)
|
||||||
|
val binder = ipc.fileSystemService
|
||||||
|
|
||||||
|
remoteFS.complete(FileSystemManager.getRemote(binder))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
|
remoteFS = CompletableDeferred()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun awaitRemoteFS(): FileSystemManager {
|
||||||
|
if (remoteFS.isActive) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
val intent = Intent(app, ManagerRootService::class.java)
|
||||||
|
RootService.bind(intent, this@RootInstaller)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return withTimeoutOrNull(Duration.ofSeconds(120L)) {
|
||||||
|
remoteFS.await()
|
||||||
|
} ?: throw RootServiceException()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun getShell() = with(CompletableDeferred<Shell>()) {
|
||||||
|
Shell.getShell(::complete)
|
||||||
|
|
||||||
|
await()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun execute(vararg commands: String) = getShell().newJob().add(*commands).exec()
|
||||||
|
|
||||||
fun hasRootAccess() = Shell.isAppGrantedRoot() ?: false
|
fun hasRootAccess() = Shell.isAppGrantedRoot() ?: false
|
||||||
|
|
||||||
fun isAppInstalled(packageName: String) =
|
suspend fun isAppInstalled(packageName: String) =
|
||||||
rootConnection.remoteFS?.getFile("$modulesPath/$packageName-revanced")
|
awaitRemoteFS().getFile("$modulesPath/$packageName-revanced").exists()
|
||||||
?.exists() ?: throw RootServiceException()
|
|
||||||
|
|
||||||
fun isAppMounted(packageName: String): Boolean {
|
suspend fun isAppMounted(packageName: String) = withContext(Dispatchers.IO) {
|
||||||
return pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir?.let {
|
pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir?.let {
|
||||||
Shell.cmd("mount | grep \"$it\"").exec().isSuccess
|
execute("mount | grep \"$it\"").isSuccess
|
||||||
} ?: false
|
} ?: false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun mount(packageName: String) {
|
suspend fun mount(packageName: String) {
|
||||||
if (isAppMounted(packageName)) return
|
if (isAppMounted(packageName)) return
|
||||||
|
|
||||||
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
|
withContext(Dispatchers.IO) {
|
||||||
?: throw Exception("Failed to load application info")
|
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
|
||||||
val patchedAPK = "$modulesPath/$packageName-revanced/$packageName.apk"
|
?: throw Exception("Failed to load application info")
|
||||||
|
val patchedAPK = "$modulesPath/$packageName-revanced/$packageName.apk"
|
||||||
|
|
||||||
Shell.cmd("mount -o bind \"$patchedAPK\" \"$stockAPK\"").exec()
|
execute("mount -o bind \"$patchedAPK\" \"$stockAPK\"").assertSuccess("Failed to mount APK")
|
||||||
.also { if (!it.isSuccess) throw Exception("Failed to mount APK") }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unmount(packageName: String) {
|
suspend fun unmount(packageName: String) {
|
||||||
if (!isAppMounted(packageName)) return
|
if (!isAppMounted(packageName)) return
|
||||||
|
|
||||||
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
|
withContext(Dispatchers.IO) {
|
||||||
?: throw Exception("Failed to load application info")
|
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
|
||||||
|
?: throw Exception("Failed to load application info")
|
||||||
|
|
||||||
Shell.cmd("umount -l \"$stockAPK\"").exec()
|
execute("umount -l \"$stockAPK\"").assertSuccess("Failed to unmount APK")
|
||||||
.also { if (!it.isSuccess) throw Exception("Failed to unmount APK") }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun install(
|
suspend fun install(
|
||||||
@ -52,80 +96,77 @@ class RootInstaller(
|
|||||||
packageName: String,
|
packageName: String,
|
||||||
version: String,
|
version: String,
|
||||||
label: String
|
label: String
|
||||||
) {
|
) = withContext(Dispatchers.IO) {
|
||||||
withContext(Dispatchers.IO) {
|
val remoteFS = awaitRemoteFS()
|
||||||
rootConnection.remoteFS?.let { remoteFS ->
|
val assets = app.assets
|
||||||
val assets = app.assets
|
val modulePath = "$modulesPath/$packageName-revanced"
|
||||||
val modulePath = "$modulesPath/$packageName-revanced"
|
|
||||||
|
|
||||||
unmount(packageName)
|
unmount(packageName)
|
||||||
|
|
||||||
stockAPK?.let { stockApp ->
|
stockAPK?.let { stockApp ->
|
||||||
pm.getPackageInfo(packageName)?.let { packageInfo ->
|
pm.getPackageInfo(packageName)?.let { packageInfo ->
|
||||||
if (packageInfo.versionName <= version)
|
if (packageInfo.versionName <= version)
|
||||||
Shell.cmd("pm uninstall -k --user 0 $packageName").exec()
|
execute("pm uninstall -k --user 0 $packageName").assertSuccess("Failed to uninstall stock app")
|
||||||
.also { if (!it.isSuccess) throw Exception("Failed to uninstall stock app") }
|
}
|
||||||
|
|
||||||
|
execute("pm install \"${stockApp.absolutePath}\"").assertSuccess("Failed to install stock app")
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteFS.getFile(modulePath).mkdir()
|
||||||
|
|
||||||
|
listOf(
|
||||||
|
"service.sh",
|
||||||
|
"module.prop",
|
||||||
|
).forEach { file ->
|
||||||
|
assets.open("root/$file").use { inputStream ->
|
||||||
|
remoteFS.getFile("$modulePath/$file").newOutputStream()
|
||||||
|
.use { outputStream ->
|
||||||
|
val content = String(inputStream.readBytes())
|
||||||
|
.replace("__PKG_NAME__", packageName)
|
||||||
|
.replace("__VERSION__", version)
|
||||||
|
.replace("__LABEL__", label)
|
||||||
|
.toByteArray()
|
||||||
|
|
||||||
|
outputStream.write(content)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Shell.cmd("pm install \"${stockApp.absolutePath}\"").exec()
|
"$modulePath/$packageName.apk".let { apkPath ->
|
||||||
.also { if (!it.isSuccess) throw Exception("Failed to install stock app") }
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteFS.getFile(modulePath).mkdir()
|
remoteFS.getFile(patchedAPK.absolutePath)
|
||||||
|
.also { if (!it.exists()) throw Exception("File doesn't exist") }
|
||||||
listOf(
|
.newInputStream().use { inputStream ->
|
||||||
"service.sh",
|
remoteFS.getFile(apkPath).newOutputStream().use { outputStream ->
|
||||||
"module.prop",
|
inputStream.copyTo(outputStream)
|
||||||
).forEach { file ->
|
|
||||||
assets.open("root/$file").use { inputStream ->
|
|
||||||
remoteFS.getFile("$modulePath/$file").newOutputStream()
|
|
||||||
.use { outputStream ->
|
|
||||||
val content = String(inputStream.readBytes())
|
|
||||||
.replace("__PKG_NAME__", packageName)
|
|
||||||
.replace("__VERSION__", version)
|
|
||||||
.replace("__LABEL__", label)
|
|
||||||
.toByteArray()
|
|
||||||
|
|
||||||
outputStream.write(content)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"$modulePath/$packageName.apk".let { apkPath ->
|
execute(
|
||||||
|
"chmod 644 $apkPath",
|
||||||
remoteFS.getFile(patchedAPK.absolutePath)
|
"chown system:system $apkPath",
|
||||||
.also { if (!it.exists()) throw Exception("File doesn't exist") }
|
"chcon u:object_r:apk_data_file:s0 $apkPath",
|
||||||
.newInputStream().use { inputStream ->
|
"chmod +x $modulePath/service.sh"
|
||||||
remoteFS.getFile(apkPath).newOutputStream().use { outputStream ->
|
).assertSuccess("Failed to set file permissions")
|
||||||
inputStream.copyTo(outputStream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Shell.cmd(
|
|
||||||
"chmod 644 $apkPath",
|
|
||||||
"chown system:system $apkPath",
|
|
||||||
"chcon u:object_r:apk_data_file:s0 $apkPath",
|
|
||||||
"chmod +x $modulePath/service.sh"
|
|
||||||
).exec()
|
|
||||||
.let { if (!it.isSuccess) throw Exception("Failed to set file permissions") }
|
|
||||||
}
|
|
||||||
} ?: throw RootServiceException()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun uninstall(packageName: String) {
|
suspend fun uninstall(packageName: String) {
|
||||||
rootConnection.remoteFS?.let { remoteFS ->
|
val remoteFS = awaitRemoteFS()
|
||||||
if (isAppMounted(packageName))
|
if (isAppMounted(packageName))
|
||||||
unmount(packageName)
|
unmount(packageName)
|
||||||
|
|
||||||
remoteFS.getFile("$modulesPath/$packageName-revanced").deleteRecursively()
|
remoteFS.getFile("$modulesPath/$packageName-revanced").deleteRecursively()
|
||||||
.also { if (!it) throw Exception("Failed to delete files") }
|
.also { if (!it) throw Exception("Failed to delete files") }
|
||||||
} ?: throw RootServiceException()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val modulesPath = "/data/adb/modules"
|
const val modulesPath = "/data/adb/modules"
|
||||||
|
|
||||||
|
private fun Shell.Result.assertSuccess(errorMessage: String) {
|
||||||
|
if (!isSuccess) throw Exception(errorMessage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RootServiceException: Exception("Root not available")
|
class RootServiceException : Exception("Root not available")
|
@ -15,7 +15,6 @@ class PreferencesManager(
|
|||||||
val multithreadingDexFileWriter = booleanPreference("multithreading_dex_file_writer", true)
|
val multithreadingDexFileWriter = booleanPreference("multithreading_dex_file_writer", true)
|
||||||
val useProcessRuntime = booleanPreference("use_process_runtime", false)
|
val useProcessRuntime = booleanPreference("use_process_runtime", false)
|
||||||
val patcherProcessMemoryLimit = intPreference("process_runtime_memory_limit", 700)
|
val patcherProcessMemoryLimit = intPreference("process_runtime_memory_limit", 700)
|
||||||
val disablePatchVersionCompatCheck = booleanPreference("disable_patch_version_compatibility_check", false)
|
|
||||||
|
|
||||||
val keystoreCommonName = stringPreference("keystore_cn", KeystoreManager.DEFAULT)
|
val keystoreCommonName = stringPreference("keystore_cn", KeystoreManager.DEFAULT)
|
||||||
val keystorePass = stringPreference("keystore_pass", KeystoreManager.DEFAULT)
|
val keystorePass = stringPreference("keystore_pass", KeystoreManager.DEFAULT)
|
||||||
@ -25,8 +24,8 @@ class PreferencesManager(
|
|||||||
val firstLaunch = booleanPreference("first_launch", true)
|
val firstLaunch = booleanPreference("first_launch", true)
|
||||||
val managerAutoUpdates = booleanPreference("manager_auto_updates", false)
|
val managerAutoUpdates = booleanPreference("manager_auto_updates", false)
|
||||||
|
|
||||||
|
val disablePatchVersionCompatCheck = booleanPreference("disable_patch_version_compatibility_check", false)
|
||||||
val disableSelectionWarning = booleanPreference("disable_selection_warning", false)
|
val disableSelectionWarning = booleanPreference("disable_selection_warning", false)
|
||||||
val enableSelectionWarningCountdown = booleanPreference("enable_selection_warning_countdown", true)
|
val disableUniversalPatchWarning = booleanPreference("disable_universal_patch_warning", false)
|
||||||
|
|
||||||
val suggestedVersionSafeguard = booleanPreference("suggested_version_safeguard", true)
|
val suggestedVersionSafeguard = booleanPreference("suggested_version_safeguard", true)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package app.revanced.manager.service
|
package app.revanced.manager.service
|
||||||
|
|
||||||
import android.content.ComponentName
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.ServiceConnection
|
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import app.revanced.manager.IRootSystemService
|
import app.revanced.manager.IRootSystemService
|
||||||
import com.topjohnwu.superuser.ipc.RootService
|
import com.topjohnwu.superuser.ipc.RootService
|
||||||
@ -14,23 +12,5 @@ class ManagerRootService : RootService() {
|
|||||||
FileSystemManager.getService()
|
FileSystemManager.getService()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder {
|
override fun onBind(intent: Intent): IBinder = RootSystemService()
|
||||||
return RootSystemService()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RootConnection : ServiceConnection {
|
|
||||||
var remoteFS: FileSystemManager? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
|
||||||
val ipc = IRootSystemService.Stub.asInterface(service)
|
|
||||||
val binder = ipc.fileSystemService
|
|
||||||
|
|
||||||
remoteFS = FileSystemManager.getRemote(binder)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onServiceDisconnected(name: ComponentName?) {
|
|
||||||
remoteFS = null
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,26 +0,0 @@
|
|||||||
package app.revanced.manager.ui.component
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun Countdown(start: Int, content: @Composable (Int) -> Unit) {
|
|
||||||
var timer by rememberSaveable(start) {
|
|
||||||
mutableStateOf(start)
|
|
||||||
}
|
|
||||||
LaunchedEffect(timer) {
|
|
||||||
if (timer == 0) {
|
|
||||||
return@LaunchedEffect
|
|
||||||
}
|
|
||||||
|
|
||||||
delay(1000L)
|
|
||||||
timer -= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
content(timer)
|
|
||||||
}
|
|
@ -1,91 +0,0 @@
|
|||||||
package app.revanced.manager.ui.component
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.WarningAmber
|
|
||||||
import androidx.compose.material3.AlertDialog
|
|
||||||
import androidx.compose.material3.Checkbox
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import app.revanced.manager.R
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DangerousActionDialogBase(
|
|
||||||
onCancel: () -> Unit,
|
|
||||||
confirmButton: @Composable (Boolean) -> Unit,
|
|
||||||
@StringRes title: Int,
|
|
||||||
body: String,
|
|
||||||
) {
|
|
||||||
var dismissPermanently by rememberSaveable {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
AlertDialog(
|
|
||||||
onDismissRequest = onCancel,
|
|
||||||
confirmButton = {
|
|
||||||
confirmButton(dismissPermanently)
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onCancel) {
|
|
||||||
Text(stringResource(R.string.cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon = {
|
|
||||||
Icon(Icons.Outlined.WarningAmber, null)
|
|
||||||
},
|
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
text = stringResource(title),
|
|
||||||
style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
horizontalAlignment = Alignment.Start
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = body,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
|
||||||
)
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(0.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable {
|
|
||||||
dismissPermanently = !dismissPermanently
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Checkbox(
|
|
||||||
checked = dismissPermanently,
|
|
||||||
onCheckedChange = {
|
|
||||||
dismissPermanently = it
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Text(stringResource(R.string.permanent_dismiss))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package app.revanced.manager.ui.component
|
|
||||||
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TextButton
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import app.revanced.manager.R
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun NonSuggestedVersionDialog(suggestedVersion: String, onCancel: () -> Unit, onContinue: (Boolean) -> Unit) {
|
|
||||||
DangerousActionDialogBase(
|
|
||||||
onCancel = onCancel,
|
|
||||||
confirmButton = { dismissPermanently ->
|
|
||||||
TextButton(
|
|
||||||
onClick = { onContinue(dismissPermanently) }
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.continue_))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
title = R.string.non_suggested_version_warning_title,
|
|
||||||
body = stringResource(R.string.non_suggested_version_warning_description, suggestedVersion),
|
|
||||||
)
|
|
||||||
}
|
|
@ -0,0 +1,51 @@
|
|||||||
|
package app.revanced.manager.ui.component
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.WarningAmber
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import app.revanced.manager.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SafeguardDialog(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
@StringRes title: Int,
|
||||||
|
body: String,
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = onDismiss) {
|
||||||
|
Text(stringResource(R.string.ok))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(Icons.Outlined.WarningAmber, null)
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(title),
|
||||||
|
style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(body)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NonSuggestedVersionDialog(suggestedVersion: String, onDismiss: () -> Unit) {
|
||||||
|
SafeguardDialog(
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
title = R.string.non_suggested_version_warning_title,
|
||||||
|
body = stringResource(R.string.non_suggested_version_warning_description, suggestedVersion),
|
||||||
|
)
|
||||||
|
}
|
@ -19,6 +19,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.runtime.setValue
|
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.pluralStringResource
|
||||||
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 app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
@ -115,8 +116,8 @@ fun BaseBundleDialog(
|
|||||||
|
|
||||||
if (remoteUrl != null) {
|
if (remoteUrl != null) {
|
||||||
BundleListItem(
|
BundleListItem(
|
||||||
headlineText = stringResource(R.string.automatically_update),
|
headlineText = stringResource(R.string.bundle_auto_update),
|
||||||
supportingText = stringResource(R.string.automatically_update_description),
|
supportingText = stringResource(R.string.bundle_auto_update_description),
|
||||||
trailingContent = {
|
trailingContent = {
|
||||||
Switch(
|
Switch(
|
||||||
checked = autoUpdate,
|
checked = autoUpdate,
|
||||||
@ -163,8 +164,7 @@ fun BaseBundleDialog(
|
|||||||
val patchesClickable = LocalContext.current.isDebuggable && patchCount > 0
|
val patchesClickable = LocalContext.current.isDebuggable && patchCount > 0
|
||||||
BundleListItem(
|
BundleListItem(
|
||||||
headlineText = stringResource(R.string.patches),
|
headlineText = stringResource(R.string.patches),
|
||||||
supportingText = if (patchCount == 0) stringResource(R.string.no_patches)
|
supportingText = pluralStringResource(R.plurals.bundle_patches_available, patchCount, patchCount),
|
||||||
else stringResource(R.string.patches_available, patchCount),
|
|
||||||
modifier = Modifier.clickable(enabled = patchesClickable, onClick = onPatchesClick)
|
modifier = Modifier.clickable(enabled = patchesClickable, onClick = onPatchesClick)
|
||||||
) {
|
) {
|
||||||
if (patchesClickable)
|
if (patchesClickable)
|
||||||
|
@ -6,35 +6,38 @@ import kotlinx.parcelize.Parcelize
|
|||||||
sealed interface SettingsDestination : Parcelable {
|
sealed interface SettingsDestination : Parcelable {
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object Settings : SettingsDestination
|
data object Settings : SettingsDestination
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object General : SettingsDestination
|
data object General : SettingsDestination
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object Advanced : SettingsDestination
|
data object Advanced : SettingsDestination
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object Updates : SettingsDestination
|
data object Updates : SettingsDestination
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object Downloads : SettingsDestination
|
data object Downloads : SettingsDestination
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object ImportExport : SettingsDestination
|
data object ImportExport : SettingsDestination
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object About : SettingsDestination
|
data object About : SettingsDestination
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class Update(val downloadOnScreenEntry: Boolean = false) : SettingsDestination
|
data class Update(val downloadOnScreenEntry: Boolean = false) : SettingsDestination
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object Changelogs : SettingsDestination
|
data object Changelogs : SettingsDestination
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object Contributors: SettingsDestination
|
data object Contributors: SettingsDestination
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
object Licenses: SettingsDestination
|
data object Licenses: SettingsDestination
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data object DeveloperOptions: SettingsDestination
|
||||||
}
|
}
|
@ -70,8 +70,7 @@ fun AppSelectorScreen(
|
|||||||
vm.nonSuggestedVersionDialogSubject?.let {
|
vm.nonSuggestedVersionDialogSubject?.let {
|
||||||
NonSuggestedVersionDialog(
|
NonSuggestedVersionDialog(
|
||||||
suggestedVersion = suggestedVersions[it.packageName].orEmpty(),
|
suggestedVersion = suggestedVersions[it.packageName].orEmpty(),
|
||||||
onCancel = vm::dismissNonSuggestedVersionDialog,
|
onDismiss = vm::dismissNonSuggestedVersionDialog
|
||||||
onContinue = vm::continueWithNonSuggestedVersion,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
package app.revanced.manager.ui.screen
|
package app.revanced.manager.ui.screen
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.provider.Settings
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -10,6 +14,7 @@ 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
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.BatteryAlert
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.outlined.Apps
|
import androidx.compose.material.icons.outlined.Apps
|
||||||
import androidx.compose.material.icons.outlined.DeleteOutline
|
import androidx.compose.material.icons.outlined.DeleteOutline
|
||||||
@ -67,6 +72,7 @@ enum class DashboardPage(
|
|||||||
BUNDLES(R.string.tab_bundles, Icons.Outlined.Source),
|
BUNDLES(R.string.tab_bundles, Icons.Outlined.Source),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("BatteryLife")
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DashboardScreen(
|
fun DashboardScreen(
|
||||||
@ -211,6 +217,20 @@ fun DashboardScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else null,
|
} else null,
|
||||||
|
if (vm.showBatteryOptimizationsWarning) {
|
||||||
|
{
|
||||||
|
NotificationCard(
|
||||||
|
isWarning = true,
|
||||||
|
icon = Icons.Default.BatteryAlert,
|
||||||
|
text = stringResource(R.string.battery_optimization_notification),
|
||||||
|
onClick = {
|
||||||
|
androidContext.startActivity(Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
||||||
|
data = Uri.parse("package:${androidContext.packageName}")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else null,
|
||||||
vm.updatedManagerVersion?.let {
|
vm.updatedManagerVersion?.let {
|
||||||
{
|
{
|
||||||
NotificationCard(
|
NotificationCard(
|
||||||
|
@ -83,7 +83,7 @@ fun InstalledAppInfoScreen(
|
|||||||
|
|
||||||
if (viewModel.installedApp.installType == InstallType.ROOT) {
|
if (viewModel.installedApp.installType == InstallType.ROOT) {
|
||||||
Text(
|
Text(
|
||||||
text = if (viewModel.rootInstaller.isAppMounted(viewModel.installedApp.currentPackageName)) {
|
text = if (viewModel.isMounted) {
|
||||||
stringResource(R.string.mounted)
|
stringResource(R.string.mounted)
|
||||||
} else {
|
} else {
|
||||||
stringResource(R.string.not_mounted)
|
stringResource(R.string.not_mounted)
|
||||||
|
@ -20,6 +20,7 @@ import androidx.compose.material.icons.outlined.Restore
|
|||||||
import androidx.compose.material.icons.outlined.Save
|
import androidx.compose.material.icons.outlined.Save
|
||||||
import androidx.compose.material.icons.outlined.Search
|
import androidx.compose.material.icons.outlined.Search
|
||||||
import androidx.compose.material.icons.outlined.Settings
|
import androidx.compose.material.icons.outlined.Settings
|
||||||
|
import androidx.compose.material.icons.outlined.WarningAmber
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Checkbox
|
import androidx.compose.material3.Checkbox
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
@ -37,7 +38,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.surfaceColorAtElevation
|
import androidx.compose.material3.surfaceColorAtElevation
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
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.mutableStateOf
|
||||||
@ -48,17 +48,16 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
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.manager.PreferencesManager
|
|
||||||
import app.revanced.manager.patcher.patch.Option
|
import app.revanced.manager.patcher.patch.Option
|
||||||
import app.revanced.manager.patcher.patch.PatchInfo
|
import app.revanced.manager.patcher.patch.PatchInfo
|
||||||
import app.revanced.manager.ui.component.AppTopBar
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
import app.revanced.manager.ui.component.Countdown
|
import app.revanced.manager.ui.component.SafeguardDialog
|
||||||
import app.revanced.manager.ui.component.DangerousActionDialogBase
|
|
||||||
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
|
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
|
||||||
import app.revanced.manager.ui.component.SearchView
|
import app.revanced.manager.ui.component.SearchView
|
||||||
import app.revanced.manager.ui.component.patches.OptionItem
|
import app.revanced.manager.ui.component.patches.OptionItem
|
||||||
@ -70,7 +69,6 @@ import app.revanced.manager.util.Options
|
|||||||
import app.revanced.manager.util.PatchSelection
|
import app.revanced.manager.util.PatchSelection
|
||||||
import app.revanced.manager.util.isScrollingUp
|
import app.revanced.manager.util.isScrollingUp
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.compose.koinInject
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@ -160,10 +158,16 @@ fun PatchesSelectorScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
vm.pendingSelectionAction?.let {
|
var showSelectionWarning by rememberSaveable {
|
||||||
SelectionWarningDialog(
|
mutableStateOf(false)
|
||||||
onCancel = vm::dismissSelectionWarning,
|
}
|
||||||
onConfirm = vm::confirmSelectionWarning
|
if (showSelectionWarning) {
|
||||||
|
SelectionWarningDialog(onDismiss = { showSelectionWarning = false })
|
||||||
|
}
|
||||||
|
vm.pendingUniversalPatchAction?.let {
|
||||||
|
UniversalPatchWarningDialog(
|
||||||
|
onCancel = vm::dismissUniversalPatchWarning,
|
||||||
|
onConfirm = vm::confirmUniversalPatchWarning
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,9 +200,9 @@ fun PatchesSelectorScreen(
|
|||||||
),
|
),
|
||||||
onToggle = {
|
onToggle = {
|
||||||
if (vm.selectionWarningEnabled) {
|
if (vm.selectionWarningEnabled) {
|
||||||
vm.pendingSelectionAction = {
|
showSelectionWarning = true
|
||||||
vm.togglePatch(uid, patch)
|
} else if (vm.universalPatchWarningEnabled && patch.compatiblePackages == null) {
|
||||||
}
|
vm.pendingUniversalPatchAction = { vm.togglePatch(uid, patch) }
|
||||||
} else {
|
} else {
|
||||||
vm.togglePatch(uid, patch)
|
vm.togglePatch(uid, patch)
|
||||||
}
|
}
|
||||||
@ -369,36 +373,43 @@ fun PatchesSelectorScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SelectionWarningDialog(
|
fun SelectionWarningDialog(onDismiss: () -> Unit) {
|
||||||
|
SafeguardDialog(
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
title = R.string.warning,
|
||||||
|
body = stringResource(R.string.selection_warning_description),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UniversalPatchWarningDialog(
|
||||||
onCancel: () -> Unit,
|
onCancel: () -> Unit,
|
||||||
onConfirm: (Boolean) -> Unit
|
onConfirm: () -> Unit
|
||||||
) {
|
) {
|
||||||
val prefs: PreferencesManager = koinInject()
|
AlertDialog(
|
||||||
|
onDismissRequest = onCancel,
|
||||||
DangerousActionDialogBase(
|
confirmButton = {
|
||||||
onCancel = onCancel,
|
TextButton(onClick = onConfirm) {
|
||||||
confirmButton = { dismissPermanently ->
|
Text(stringResource(R.string.continue_))
|
||||||
val enableCountdown by prefs.enableSelectionWarningCountdown.getAsState()
|
|
||||||
|
|
||||||
Countdown(start = if (enableCountdown) 3 else 0) { timer ->
|
|
||||||
LaunchedEffect(timer) {
|
|
||||||
if (timer == 0) prefs.enableSelectionWarningCountdown.update(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
TextButton(
|
|
||||||
onClick = { onConfirm(dismissPermanently) },
|
|
||||||
enabled = timer == 0
|
|
||||||
) {
|
|
||||||
val text =
|
|
||||||
if (timer == 0) stringResource(R.string.continue_) else stringResource(
|
|
||||||
R.string.selection_warning_continue_countdown, timer
|
|
||||||
)
|
|
||||||
Text(text, color = MaterialTheme.colorScheme.error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = R.string.selection_warning_title,
|
dismissButton = {
|
||||||
body = stringResource(R.string.selection_warning_description),
|
TextButton(onClick = onCancel) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(Icons.Outlined.WarningAmber, null)
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.warning),
|
||||||
|
style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.universal_patch_warning_description))
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,32 +1,17 @@
|
|||||||
package app.revanced.manager.ui.screen
|
package app.revanced.manager.ui.screen
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.PowerManager
|
|
||||||
import android.provider.Settings
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
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.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.BatteryAlert
|
|
||||||
import androidx.compose.material.icons.outlined.*
|
import androidx.compose.material.icons.outlined.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
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.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import app.revanced.manager.R
|
import app.revanced.manager.R
|
||||||
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
|
||||||
import app.revanced.manager.ui.component.NotificationCard
|
|
||||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||||
import app.revanced.manager.ui.destination.SettingsDestination
|
import app.revanced.manager.ui.destination.SettingsDestination
|
||||||
import app.revanced.manager.ui.screen.settings.*
|
import app.revanced.manager.ui.screen.settings.*
|
||||||
@ -38,7 +23,6 @@ import dev.olshevski.navigation.reimagined.*
|
|||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
|
|
||||||
@SuppressLint("BatteryLife")
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsScreen(
|
fun SettingsScreen(
|
||||||
@ -54,10 +38,6 @@ fun SettingsScreen(
|
|||||||
else navController.pop()
|
else navController.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
val pm = context.getSystemService(Context.POWER_SERVICE) as PowerManager
|
|
||||||
var showBatteryButton by remember { mutableStateOf(!pm.isIgnoringBatteryOptimizations(context.packageName)) }
|
|
||||||
|
|
||||||
val settingsSections = listOf(
|
val settingsSections = listOf(
|
||||||
Triple(
|
Triple(
|
||||||
R.string.general,
|
R.string.general,
|
||||||
@ -122,7 +102,8 @@ fun SettingsScreen(
|
|||||||
is SettingsDestination.About -> AboutSettingsScreen(
|
is SettingsDestination.About -> AboutSettingsScreen(
|
||||||
onBackClick = backClick,
|
onBackClick = backClick,
|
||||||
onContributorsClick = { navController.navigate(SettingsDestination.Contributors) },
|
onContributorsClick = { navController.navigate(SettingsDestination.Contributors) },
|
||||||
onLicensesClick = { navController.navigate(SettingsDestination.Licenses) }
|
onDeveloperOptionsClick = { navController.navigate(SettingsDestination.DeveloperOptions) },
|
||||||
|
onLicensesClick = { navController.navigate(SettingsDestination.Licenses) },
|
||||||
)
|
)
|
||||||
|
|
||||||
is SettingsDestination.Update -> UpdateScreen(
|
is SettingsDestination.Update -> UpdateScreen(
|
||||||
@ -146,6 +127,8 @@ fun SettingsScreen(
|
|||||||
onBackClick = backClick,
|
onBackClick = backClick,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
is SettingsDestination.DeveloperOptions -> DeveloperOptionsScreen(onBackClick = backClick)
|
||||||
|
|
||||||
is SettingsDestination.Settings -> {
|
is SettingsDestination.Settings -> {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -160,21 +143,6 @@ fun SettingsScreen(
|
|||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(visible = showBatteryButton) {
|
|
||||||
NotificationCard(
|
|
||||||
modifier = Modifier.padding(16.dp),
|
|
||||||
isWarning = true,
|
|
||||||
icon = Icons.Default.BatteryAlert,
|
|
||||||
text = stringResource(R.string.battery_optimization_notification),
|
|
||||||
onClick = {
|
|
||||||
context.startActivity(Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply {
|
|
||||||
data = Uri.parse("package:${context.packageName}")
|
|
||||||
})
|
|
||||||
showBatteryButton =
|
|
||||||
!pm.isIgnoringBatteryOptimizations(context.packageName)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
settingsSections.forEach { (titleDescIcon, destination) ->
|
settingsSections.forEach { (titleDescIcon, destination) ->
|
||||||
SettingsListItem(
|
SettingsListItem(
|
||||||
modifier = Modifier.clickable { navController.navigate(destination) },
|
modifier = Modifier.clickable { navController.navigate(destination) },
|
||||||
|
@ -69,8 +69,7 @@ fun VersionSelectorScreen(
|
|||||||
if (viewModel.showNonSuggestedVersionDialog)
|
if (viewModel.showNonSuggestedVersionDialog)
|
||||||
NonSuggestedVersionDialog(
|
NonSuggestedVersionDialog(
|
||||||
suggestedVersion = viewModel.requiredVersion.orEmpty(),
|
suggestedVersion = viewModel.requiredVersion.orEmpty(),
|
||||||
onCancel = viewModel::dismissNonSuggestedVersionDialog,
|
onDismiss = viewModel::dismissNonSuggestedVersionDialog
|
||||||
onContinue = viewModel::continueWithNonSuggestedVersion,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
|
@ -39,7 +39,6 @@ import app.revanced.manager.ui.component.ColumnWithScrollbar
|
|||||||
import app.revanced.manager.ui.component.settings.SettingsListItem
|
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||||
import app.revanced.manager.ui.viewmodel.AboutViewModel
|
import app.revanced.manager.ui.viewmodel.AboutViewModel
|
||||||
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.getSocialIcon
|
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.getSocialIcon
|
||||||
import app.revanced.manager.util.isDebuggable
|
|
||||||
import app.revanced.manager.util.openUrl
|
import app.revanced.manager.util.openUrl
|
||||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
@ -50,6 +49,7 @@ fun AboutSettingsScreen(
|
|||||||
onBackClick: () -> Unit,
|
onBackClick: () -> Unit,
|
||||||
onContributorsClick: () -> Unit,
|
onContributorsClick: () -> Unit,
|
||||||
onLicensesClick: () -> Unit,
|
onLicensesClick: () -> Unit,
|
||||||
|
onDeveloperOptionsClick: () -> Unit,
|
||||||
viewModel: AboutViewModel = koinViewModel()
|
viewModel: AboutViewModel = koinViewModel()
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@ -116,9 +116,11 @@ fun AboutSettingsScreen(
|
|||||||
stringResource(R.string.contributors_description),
|
stringResource(R.string.contributors_description),
|
||||||
third = onContributorsClick
|
third = onContributorsClick
|
||||||
),
|
),
|
||||||
Triple(stringResource(R.string.developer_options),
|
Triple(
|
||||||
|
stringResource(R.string.developer_options),
|
||||||
stringResource(R.string.developer_options_description),
|
stringResource(R.string.developer_options_description),
|
||||||
third = { /*TODO*/ }).takeIf { context.isDebuggable },
|
third = onDeveloperOptionsClick
|
||||||
|
),
|
||||||
Triple(
|
Triple(
|
||||||
stringResource(R.string.opensource_licenses),
|
stringResource(R.string.opensource_licenses),
|
||||||
stringResource(R.string.opensource_licenses_description),
|
stringResource(R.string.opensource_licenses_description),
|
||||||
|
@ -72,6 +72,8 @@ fun AdvancedSettingsScreen(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
|
GroupHeader(stringResource(R.string.manager))
|
||||||
|
|
||||||
val apiUrl by vm.prefs.api.getAsState()
|
val apiUrl by vm.prefs.api.getAsState()
|
||||||
var showApiUrlDialog by rememberSaveable { mutableStateOf(false) }
|
var showApiUrlDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
@ -111,12 +113,26 @@ fun AdvancedSettingsScreen(
|
|||||||
headline = R.string.process_runtime_memory_limit,
|
headline = R.string.process_runtime_memory_limit,
|
||||||
description = R.string.process_runtime_memory_limit_description,
|
description = R.string.process_runtime_memory_limit_description,
|
||||||
)
|
)
|
||||||
|
BooleanItem(
|
||||||
|
preference = vm.prefs.multithreadingDexFileWriter,
|
||||||
|
coroutineScope = vm.viewModelScope,
|
||||||
|
headline = R.string.multithreaded_dex_file_writer,
|
||||||
|
description = R.string.multithreaded_dex_file_writer_description,
|
||||||
|
)
|
||||||
|
|
||||||
|
GroupHeader(stringResource(R.string.safeguards))
|
||||||
BooleanItem(
|
BooleanItem(
|
||||||
preference = vm.prefs.disablePatchVersionCompatCheck,
|
preference = vm.prefs.disablePatchVersionCompatCheck,
|
||||||
coroutineScope = vm.viewModelScope,
|
coroutineScope = vm.viewModelScope,
|
||||||
headline = R.string.patch_compat_check,
|
headline = R.string.patch_compat_check,
|
||||||
description = R.string.patch_compat_check_description
|
description = R.string.patch_compat_check_description
|
||||||
)
|
)
|
||||||
|
BooleanItem(
|
||||||
|
preference = vm.prefs.disableUniversalPatchWarning,
|
||||||
|
coroutineScope = vm.viewModelScope,
|
||||||
|
headline = R.string.universal_patches_safeguard,
|
||||||
|
description = R.string.universal_patches_safeguard_description
|
||||||
|
)
|
||||||
BooleanItem(
|
BooleanItem(
|
||||||
preference = vm.prefs.suggestedVersionSafeguard,
|
preference = vm.prefs.suggestedVersionSafeguard,
|
||||||
coroutineScope = vm.viewModelScope,
|
coroutineScope = vm.viewModelScope,
|
||||||
@ -124,24 +140,10 @@ fun AdvancedSettingsScreen(
|
|||||||
description = R.string.suggested_version_safeguard_description
|
description = R.string.suggested_version_safeguard_description
|
||||||
)
|
)
|
||||||
BooleanItem(
|
BooleanItem(
|
||||||
preference = vm.prefs.multithreadingDexFileWriter,
|
preference = vm.prefs.disableSelectionWarning,
|
||||||
coroutineScope = vm.viewModelScope,
|
coroutineScope = vm.viewModelScope,
|
||||||
headline = R.string.multithreaded_dex_file_writer,
|
headline = R.string.patch_selection_safeguard,
|
||||||
description = R.string.multithreaded_dex_file_writer_description,
|
description = R.string.patch_selection_safeguard_description
|
||||||
)
|
|
||||||
|
|
||||||
GroupHeader(stringResource(R.string.patch_bundles_section))
|
|
||||||
SettingsListItem(
|
|
||||||
headlineContent = stringResource(R.string.patch_bundles_redownload),
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
vm.redownloadBundles()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
SettingsListItem(
|
|
||||||
headlineContent = stringResource(R.string.patch_bundles_reset),
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
vm.resetBundles()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
GroupHeader(stringResource(R.string.debugging))
|
GroupHeader(stringResource(R.string.debugging))
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
package app.revanced.manager.ui.screen.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.ui.component.AppTopBar
|
||||||
|
import app.revanced.manager.ui.component.GroupHeader
|
||||||
|
import app.revanced.manager.ui.component.settings.SettingsListItem
|
||||||
|
import app.revanced.manager.ui.viewmodel.DeveloperOptionsViewModel
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun DeveloperOptionsScreen(
|
||||||
|
onBackClick: () -> Unit,
|
||||||
|
vm: DeveloperOptionsViewModel = koinViewModel()
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
AppTopBar(
|
||||||
|
title = stringResource(R.string.developer_options),
|
||||||
|
onBackClick = onBackClick
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(modifier = Modifier.padding(paddingValues)) {
|
||||||
|
GroupHeader(stringResource(R.string.patch_bundles_section))
|
||||||
|
SettingsListItem(
|
||||||
|
headlineContent = stringResource(R.string.patch_bundles_redownload),
|
||||||
|
modifier = Modifier.clickable(onClick = vm::redownloadBundles)
|
||||||
|
)
|
||||||
|
SettingsListItem(
|
||||||
|
headlineContent = stringResource(R.string.patch_bundles_reset),
|
||||||
|
modifier = Modifier.clickable(onClick = vm::redownloadBundles)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -112,7 +112,7 @@ private fun ThemePicker(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
Button(
|
TextButton(
|
||||||
onClick = {
|
onClick = {
|
||||||
onConfirm(selectedTheme)
|
onConfirm(selectedTheme)
|
||||||
onDismiss()
|
onDismiss()
|
||||||
|
@ -6,12 +6,10 @@ import android.util.Log
|
|||||||
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.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
|
||||||
import app.revanced.manager.util.tag
|
import app.revanced.manager.util.tag
|
||||||
import app.revanced.manager.util.toast
|
import app.revanced.manager.util.toast
|
||||||
import app.revanced.manager.util.uiSafe
|
|
||||||
import com.github.pgreze.process.Redirect
|
import com.github.pgreze.process.Redirect
|
||||||
import com.github.pgreze.process.process
|
import com.github.pgreze.process.process
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
@ -43,16 +41,6 @@ class AdvancedSettingsViewModel(
|
|||||||
patchBundleRepository.reloadApiBundles()
|
patchBundleRepository.reloadApiBundles()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun redownloadBundles() = viewModelScope.launch {
|
|
||||||
uiSafe(app, R.string.source_download_fail, RemotePatchBundle.updateFailMsg) {
|
|
||||||
patchBundleRepository.redownloadRemoteBundles()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun resetBundles() = viewModelScope.launch {
|
|
||||||
patchBundleRepository.reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exportDebugLogs(target: Uri) = viewModelScope.launch {
|
fun exportDebugLogs(target: Uri) = viewModelScope.launch {
|
||||||
val exitCode = try {
|
val exitCode = try {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
@ -3,14 +3,12 @@ package app.revanced.manager.ui.viewmodel
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.pm.PackageInfo
|
import android.content.pm.PackageInfo
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
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.manager.PreferencesManager
|
|
||||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||||
import app.revanced.manager.ui.model.SelectedApp
|
import app.revanced.manager.ui.model.SelectedApp
|
||||||
import app.revanced.manager.util.PM
|
import app.revanced.manager.util.PM
|
||||||
@ -25,8 +23,7 @@ import java.nio.file.Files
|
|||||||
class AppSelectorViewModel(
|
class AppSelectorViewModel(
|
||||||
private val app: Application,
|
private val app: Application,
|
||||||
private val pm: PM,
|
private val pm: PM,
|
||||||
private val patchBundleRepository: PatchBundleRepository,
|
private val patchBundleRepository: PatchBundleRepository
|
||||||
private val prefs: PreferencesManager,
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val inputFile = File(app.cacheDir, "input.apk").also {
|
private val inputFile = File(app.cacheDir, "input.apk").also {
|
||||||
it.delete()
|
it.delete()
|
||||||
@ -46,13 +43,6 @@ class AppSelectorViewModel(
|
|||||||
nonSuggestedVersionDialogSubject = null
|
nonSuggestedVersionDialogSubject = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun continueWithNonSuggestedVersion(dismissPermanently: Boolean) = viewModelScope.launch {
|
|
||||||
if (dismissPermanently) prefs.suggestedVersionSafeguard.update(false)
|
|
||||||
|
|
||||||
nonSuggestedVersionDialogSubject?.let(onStorageClick)
|
|
||||||
dismissNonSuggestedVersionDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun handleStorageResult(uri: Uri) = viewModelScope.launch {
|
fun handleStorageResult(uri: Uri) = viewModelScope.launch {
|
||||||
val selectedApp = withContext(Dispatchers.IO) {
|
val selectedApp = withContext(Dispatchers.IO) {
|
||||||
loadSelectedFile(uri)
|
loadSelectedFile(uri)
|
||||||
|
@ -3,10 +3,12 @@ package app.revanced.manager.ui.viewmodel
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.ContentResolver
|
import android.content.ContentResolver
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
|
import android.os.PowerManager
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
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
|
||||||
@ -33,15 +35,21 @@ class DashboardViewModel(
|
|||||||
val availablePatches =
|
val availablePatches =
|
||||||
patchBundleRepository.bundles.map { it.values.sumOf { bundle -> bundle.patches.size } }
|
patchBundleRepository.bundles.map { it.values.sumOf { bundle -> bundle.patches.size } }
|
||||||
private val contentResolver: ContentResolver = app.contentResolver
|
private val contentResolver: ContentResolver = app.contentResolver
|
||||||
|
private val powerManager = app.getSystemService<PowerManager>()!!
|
||||||
val sources = patchBundleRepository.sources
|
val sources = patchBundleRepository.sources
|
||||||
val selectedSources = mutableStateListOf<PatchBundleSource>()
|
val selectedSources = mutableStateListOf<PatchBundleSource>()
|
||||||
|
|
||||||
|
|
||||||
var updatedManagerVersion: String? by mutableStateOf(null)
|
var updatedManagerVersion: String? by mutableStateOf(null)
|
||||||
private set
|
private set
|
||||||
|
var showBatteryOptimizationsWarning by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch { checkForManagerUpdates() }
|
viewModelScope.launch {
|
||||||
|
checkForManagerUpdates()
|
||||||
|
showBatteryOptimizationsWarning =
|
||||||
|
!powerManager.isIgnoringBatteryOptimizations(app.packageName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dismissUpdateDialog() {
|
fun dismissUpdateDialog() {
|
||||||
@ -80,12 +88,14 @@ class DashboardViewModel(
|
|||||||
fun cancelSourceSelection() {
|
fun cancelSourceSelection() {
|
||||||
selectedSources.clear()
|
selectedSources.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createLocalSource(patchBundle: Uri, integrations: Uri?) =
|
fun createLocalSource(patchBundle: Uri, integrations: Uri?) =
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
contentResolver.openInputStream(patchBundle)!!.use { patchesStream ->
|
contentResolver.openInputStream(patchBundle)!!.use { patchesStream ->
|
||||||
integrations?.let { contentResolver.openInputStream(it) }.use { integrationsStream ->
|
integrations?.let { contentResolver.openInputStream(it) }
|
||||||
patchBundleRepository.createLocal(patchesStream, integrationsStream)
|
.use { integrationsStream ->
|
||||||
}
|
patchBundleRepository.createLocal(patchesStream, integrationsStream)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
package app.revanced.manager.ui.viewmodel
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import app.revanced.manager.R
|
||||||
|
import app.revanced.manager.domain.bundles.RemotePatchBundle
|
||||||
|
import app.revanced.manager.domain.manager.PreferencesManager
|
||||||
|
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||||
|
import app.revanced.manager.util.uiSafe
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class DeveloperOptionsViewModel(
|
||||||
|
val prefs: PreferencesManager,
|
||||||
|
private val app: Application,
|
||||||
|
private val patchBundleRepository: PatchBundleRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
fun redownloadBundles() = viewModelScope.launch {
|
||||||
|
uiSafe(app, R.string.source_download_fail, RemotePatchBundle.updateFailMsg) {
|
||||||
|
patchBundleRepository.redownloadRemoteBundles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetBundles() = viewModelScope.launch {
|
||||||
|
patchBundleRepository.reset()
|
||||||
|
}
|
||||||
|
}
|
@ -44,12 +44,18 @@ class InstalledAppInfoViewModel(
|
|||||||
var appInfo: PackageInfo? by mutableStateOf(null)
|
var appInfo: PackageInfo? by mutableStateOf(null)
|
||||||
private set
|
private set
|
||||||
var appliedPatches: PatchSelection? by mutableStateOf(null)
|
var appliedPatches: PatchSelection? by mutableStateOf(null)
|
||||||
var isMounted by mutableStateOf(rootInstaller.isAppMounted(installedApp.currentPackageName))
|
var isMounted by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
isMounted = rootInstaller.isAppMounted(installedApp.currentPackageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun launch() = pm.launch(installedApp.currentPackageName)
|
fun launch() = pm.launch(installedApp.currentPackageName)
|
||||||
|
|
||||||
fun mountOrUnmount() {
|
fun mountOrUnmount() = viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
if (isMounted)
|
if (isMounted)
|
||||||
rootInstaller.unmount(installedApp.currentPackageName)
|
rootInstaller.unmount(installedApp.currentPackageName)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package app.revanced.manager.ui.viewmodel
|
package app.revanced.manager.ui.viewmodel
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -39,7 +40,7 @@ class MainViewModel(
|
|||||||
val launcher = componentActivity.registerForActivityResult(
|
val launcher = componentActivity.registerForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
) { result: ActivityResult ->
|
) { result: ActivityResult ->
|
||||||
if (result.resultCode == ComponentActivity.RESULT_OK) {
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
result.data?.getStringExtra("data")?.let {
|
result.data?.getStringExtra("data")?.let {
|
||||||
applyLegacySettings(it)
|
applyLegacySettings(it)
|
||||||
} ?: app.toast(app.getString(R.string.legacy_import_failed))
|
} ?: app.toast(app.getString(R.string.legacy_import_failed))
|
||||||
|
@ -39,15 +39,21 @@ import app.revanced.manager.util.PM
|
|||||||
import app.revanced.manager.util.simpleMessage
|
import app.revanced.manager.util.simpleMessage
|
||||||
import app.revanced.manager.util.tag
|
import app.revanced.manager.util.tag
|
||||||
import app.revanced.manager.util.toast
|
import app.revanced.manager.util.toast
|
||||||
|
import app.revanced.manager.util.uiSafe
|
||||||
|
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.time.withTimeout
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import kotlinx.coroutines.withTimeout
|
||||||
import org.koin.core.component.KoinComponent
|
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
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
import java.time.Duration
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@ -177,6 +183,7 @@ class PatcherViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(DelicateCoroutinesApi::class)
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
app.unregisterReceiver(installBroadcastReceiver)
|
app.unregisterReceiver(installBroadcastReceiver)
|
||||||
@ -188,15 +195,16 @@ class PatcherViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is SelectedApp.Installed -> {
|
is SelectedApp.Installed -> {
|
||||||
try {
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
installedApp?.let {
|
uiSafe(app, R.string.failed_to_mount, "Failed to mount") {
|
||||||
if (it.installType == InstallType.ROOT) {
|
installedApp?.let {
|
||||||
rootInstaller.mount(packageName)
|
if (it.installType == InstallType.ROOT) {
|
||||||
|
withTimeout(Duration.ofMinutes(1L)) {
|
||||||
|
rootInstaller.mount(packageName)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(tag, "Failed to mount", e)
|
|
||||||
app.toast(app.getString(R.string.failed_to_mount, e.simpleMessage()))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,10 +47,12 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
|
|||||||
private val packageName = input.app.packageName
|
private val packageName = input.app.packageName
|
||||||
val appVersion = input.app.version
|
val appVersion = input.app.version
|
||||||
|
|
||||||
var pendingSelectionAction by mutableStateOf<(() -> Unit)?>(null)
|
var pendingUniversalPatchAction by mutableStateOf<(() -> Unit)?>(null)
|
||||||
|
|
||||||
var selectionWarningEnabled by mutableStateOf(true)
|
var selectionWarningEnabled by mutableStateOf(true)
|
||||||
private set
|
private set
|
||||||
|
var universalPatchWarningEnabled by mutableStateOf(true)
|
||||||
|
private set
|
||||||
|
|
||||||
val allowIncompatiblePatches =
|
val allowIncompatiblePatches =
|
||||||
get<PreferencesManager>().disablePatchVersionCompatCheck.getBlocking()
|
get<PreferencesManager>().disablePatchVersionCompatCheck.getBlocking()
|
||||||
@ -59,6 +61,8 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
universalPatchWarningEnabled = !prefs.disableUniversalPatchWarning.get()
|
||||||
|
|
||||||
if (prefs.disableSelectionWarning.get()) {
|
if (prefs.disableSelectionWarning.get()) {
|
||||||
selectionWarningEnabled = false
|
selectionWarningEnabled = false
|
||||||
return@launch
|
return@launch
|
||||||
@ -131,21 +135,15 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
|
|||||||
customPatchSelection = selection.put(bundle, newPatches)
|
customPatchSelection = selection.put(bundle, newPatches)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun confirmSelectionWarning(dismissPermanently: Boolean) {
|
fun confirmUniversalPatchWarning() {
|
||||||
selectionWarningEnabled = false
|
universalPatchWarningEnabled = false
|
||||||
|
|
||||||
pendingSelectionAction?.invoke()
|
pendingUniversalPatchAction?.invoke()
|
||||||
pendingSelectionAction = null
|
pendingUniversalPatchAction = null
|
||||||
|
|
||||||
if (!dismissPermanently) return
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
|
||||||
prefs.disableSelectionWarning.update(true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun dismissSelectionWarning() {
|
fun dismissUniversalPatchWarning() {
|
||||||
pendingSelectionAction = null
|
pendingUniversalPatchAction = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reset() {
|
fun reset() {
|
||||||
|
@ -162,12 +162,6 @@ class VersionSelectorViewModel(
|
|||||||
nonSuggestedVersionDialogSubject = null
|
nonSuggestedVersionDialogSubject = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun continueWithNonSuggestedVersion(dismissPermanently: Boolean) = viewModelScope.launch {
|
|
||||||
if (dismissPermanently) prefs.suggestedVersionSafeguard.update(false)
|
|
||||||
selectedVersion = nonSuggestedVersionDialogSubject
|
|
||||||
dismissNonSuggestedVersionDialog()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun select(app: SelectedApp) {
|
fun select(app: SelectedApp) {
|
||||||
if (requiredVersion != null && app.version != requiredVersion) {
|
if (requiredVersion != null && app.version != requiredVersion) {
|
||||||
nonSuggestedVersionDialogSubject = app
|
nonSuggestedVersionDialogSubject = app
|
||||||
|
1
app/src/main/res/resources.properties
Normal file
1
app/src/main/res/resources.properties
Normal file
@ -0,0 +1 @@
|
|||||||
|
unqualifiedResLocale=en-US
|
@ -11,4 +11,8 @@
|
|||||||
<plurals name="selected_count">
|
<plurals name="selected_count">
|
||||||
<item quantity="other">%d selected</item>
|
<item quantity="other">%d selected</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="bundle_patches_available">
|
||||||
|
<item quantity="one">%d patch available</item>
|
||||||
|
<item quantity="other">%d patches available</item>
|
||||||
|
</plurals>
|
||||||
</resources>
|
</resources>
|
@ -71,10 +71,15 @@
|
|||||||
<string name="theme_description">Choose between light or dark theme</string>
|
<string name="theme_description">Choose between light or dark theme</string>
|
||||||
<string name="multithreaded_dex_file_writer">Multi-threaded DEX file writer</string>
|
<string name="multithreaded_dex_file_writer">Multi-threaded DEX file writer</string>
|
||||||
<string name="multithreaded_dex_file_writer_description">Use multiple cores to write DEX files. This is faster, but uses more memory</string>
|
<string name="multithreaded_dex_file_writer_description">Use multiple cores to write DEX files. This is faster, but uses more memory</string>
|
||||||
|
<string name="safeguards">Safeguards</string>
|
||||||
<string name="patch_compat_check">Disable version compatibility check</string>
|
<string name="patch_compat_check">Disable version compatibility check</string>
|
||||||
<string name="patch_compat_check_description">The check restricts patches to supported app versions</string>
|
<string name="patch_compat_check_description">The check restricts patches to supported app versions</string>
|
||||||
<string name="suggested_version_safeguard">Require suggested app version</string>
|
<string name="suggested_version_safeguard">Require suggested app version</string>
|
||||||
<string name="suggested_version_safeguard_description">Enforce selection of the suggested app version</string>
|
<string name="suggested_version_safeguard_description">Enforce selection of the suggested app version</string>
|
||||||
|
<string name="patch_selection_safeguard">Allow changing patch selection</string>
|
||||||
|
<string name="patch_selection_safeguard_description">Do not prevent selecting or deselecting patches</string>
|
||||||
|
<string name="universal_patches_safeguard">Disable universal patch warning</string>
|
||||||
|
<string name="universal_patches_safeguard_description">Disables the warning that appears when you try to select universal patches</string>
|
||||||
<string name="import_keystore">Import keystore</string>
|
<string name="import_keystore">Import keystore</string>
|
||||||
<string name="import_keystore_description">Import a custom keystore</string>
|
<string name="import_keystore_description">Import a custom keystore</string>
|
||||||
<string name="import_keystore_dialog_title">Enter keystore credentials</string>
|
<string name="import_keystore_dialog_title">Enter keystore credentials</string>
|
||||||
@ -133,6 +138,7 @@
|
|||||||
<string name="apply">Apply</string>
|
<string name="apply">Apply</string>
|
||||||
<string name="help">Help</string>
|
<string name="help">Help</string>
|
||||||
<string name="back">Back</string>
|
<string name="back">Back</string>
|
||||||
|
<string name="warning">Warning</string>
|
||||||
<string name="add">Add</string>
|
<string name="add">Add</string>
|
||||||
<string name="close">Close</string>
|
<string name="close">Close</string>
|
||||||
<string name="system">System</string>
|
<string name="system">System</string>
|
||||||
@ -181,8 +187,6 @@
|
|||||||
<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>
|
||||||
<string name="no_patched_apps_found">No patched apps found</string>
|
<string name="no_patched_apps_found">No patched apps found</string>
|
||||||
<string name="no_patches">No patches available to view</string>
|
|
||||||
<string name="patches_available">%d Patches available, tap to view</string>
|
|
||||||
<string name="tap_on_patches">Tap on the patches to get more information about them</string>
|
<string name="tap_on_patches">Tap on the patches to get more information about them</string>
|
||||||
<string name="bundles_selected">%s selected</string>
|
<string name="bundles_selected">%s selected</string>
|
||||||
<string name="unsupported_app">Unsupported app</string>
|
<string name="unsupported_app">Unsupported app</string>
|
||||||
@ -191,10 +195,10 @@
|
|||||||
<string name="patch_selection_reset_toast">Patch selection and options has been reset to recommended defaults</string>
|
<string name="patch_selection_reset_toast">Patch selection and options has been reset to recommended defaults</string>
|
||||||
<string name="patch_options_reset_toast">Patch options have been reset</string>
|
<string name="patch_options_reset_toast">Patch options have been reset</string>
|
||||||
<string name="non_suggested_version_warning_title">Non suggested version</string>
|
<string name="non_suggested_version_warning_title">Non suggested version</string>
|
||||||
<string name="non_suggested_version_warning_description">The version of the app you have selected does not match the suggested version.\nPlease use the suggested version: %s</string>
|
<string name="non_suggested_version_warning_description">The version of the app you have selected does not match the suggested version.\nPlease use the suggested version: %s\n\nTo continue anyway, disable \"Require suggested app version\" in the advanced settings.</string>
|
||||||
<string name="selection_warning_title">Stop using defaults?</string>
|
<string name="selection_warning_title">Stop using defaults?</string>
|
||||||
<string name="selection_warning_description">You may encounter issues when not using the default patch selection and options.</string>
|
<string name="selection_warning_description">It is recommended to use the default patch selection and options. Changing them may result in unexpected issues.\n\nYou need to turn on \"Allow changing patch selection\" in the advanced settings before toggling patches.</string>
|
||||||
<string name="selection_warning_continue_countdown">Continue (%ds)</string>
|
<string name="universal_patch_warning_description">Universal patches have a more generalized use and do not work as reliably as patches that target specific apps. You may encounter issues while using them.\n\nThis warning can be disabled in the advanced settings.</string>
|
||||||
<string name="supported">Supported</string>
|
<string name="supported">Supported</string>
|
||||||
<string name="universal">Universal</string>
|
<string name="universal">Universal</string>
|
||||||
<string name="unsupported">Unsupported</string>
|
<string name="unsupported">Unsupported</string>
|
||||||
@ -299,8 +303,8 @@
|
|||||||
<string name="bundle_input_source_url">Source URL</string>
|
<string name="bundle_input_source_url">Source URL</string>
|
||||||
<string name="bundle_update_success">Successfully updated %s</string>
|
<string name="bundle_update_success">Successfully updated %s</string>
|
||||||
<string name="bundle_update_unavailable">No update available for %s</string>
|
<string name="bundle_update_unavailable">No update available for %s</string>
|
||||||
<string name="automatically_update">Automatically update</string>
|
<string name="bundle_auto_update">Auto update</string>
|
||||||
<string name="automatically_update_description">Automatically update this bundle when ReVanced starts</string>
|
<string name="bundle_auto_update_description">Automatically update this bundle when ReVanced starts</string>
|
||||||
<string name="bundle_type">Bundle type</string>
|
<string name="bundle_type">Bundle type</string>
|
||||||
<string name="bundle_type_description">Choose the type of bundle you want</string>
|
<string name="bundle_type_description">Choose the type of bundle you want</string>
|
||||||
<string name="about_revanced_manager">About ReVanced Manager</string>
|
<string name="about_revanced_manager">About ReVanced Manager</string>
|
||||||
@ -319,7 +323,7 @@
|
|||||||
<string name="changelog_loading">Loading changelog</string>
|
<string name="changelog_loading">Loading changelog</string>
|
||||||
<string name="changelog_download_fail">Failed to download changelog: %s</string>
|
<string name="changelog_download_fail">Failed to download changelog: %s</string>
|
||||||
<string name="changelog_description">Check out the latest changes in this update</string>
|
<string name="changelog_description">Check out the latest changes in this update</string>
|
||||||
<string name="battery_optimization_notification">Battery optimization must be turned off in order for ReVanced Manager to work correctly in the background. Click here to turn off.</string>
|
<string name="battery_optimization_notification">Battery optimizations must be turned off in order for ReVanced Manager to work correctly in the background. Click here to turn off optimizations.</string>
|
||||||
<string name="installing_manager_update">Installing update…</string>
|
<string name="installing_manager_update">Installing update…</string>
|
||||||
<string name="downloading_manager_update">Downloading update…</string>
|
<string name="downloading_manager_update">Downloading update…</string>
|
||||||
<string name="download_manager_failed">Failed to download update: %s</string>
|
<string name="download_manager_failed">Failed to download update: %s</string>
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
[versions]
|
[versions]
|
||||||
ktx = "1.12.0"
|
ktx = "1.13.1"
|
||||||
material3 = "1.2.1"
|
material3 = "1.2.1"
|
||||||
ui-tooling = "1.6.4"
|
ui-tooling = "1.6.8"
|
||||||
viewmodel-lifecycle = "2.7.0"
|
viewmodel-lifecycle = "2.8.3"
|
||||||
splash-screen = "1.0.1"
|
splash-screen = "1.0.1"
|
||||||
compose-activity = "1.8.2"
|
compose-activity = "1.9.0"
|
||||||
paging = "3.2.1"
|
paging = "3.3.0"
|
||||||
preferences-datastore = "1.0.0"
|
preferences-datastore = "1.1.1"
|
||||||
work-runtime = "2.9.0"
|
work-runtime = "2.9.0"
|
||||||
compose-bom = "2024.03.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"
|
||||||
@ -23,7 +23,7 @@ reimagined-navigation = "1.5.0"
|
|||||||
ktor = "2.3.9"
|
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.0"
|
android-gradle-plugin = "8.3.2"
|
||||||
kotlin-gradle-plugin = "1.9.22"
|
kotlin-gradle-plugin = "1.9.22"
|
||||||
dev-tools-gradle-plugin = "1.9.22-1.0.17"
|
dev-tools-gradle-plugin = "1.9.22-1.0.17"
|
||||||
about-libraries-gradle-plugin = "11.1.1"
|
about-libraries-gradle-plugin = "11.1.1"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user