mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2024-11-10 01:01:56 +01:00
Merge branch 'compose-dev' into fix/minor-issues
This commit is contained in:
commit
d39804f7ed
40 changed files with 456 additions and 452 deletions
6
.github/workflows/pr-build.yml
vendored
6
.github/workflows/pr-build.yml
vendored
|
@ -23,8 +23,8 @@ jobs:
|
|||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
|
@ -38,7 +38,7 @@ jobs:
|
|||
run: mv app/build/outputs/apk/release/app-release.apk revanced-manager-${{ env.COMMIT_HASH }}.apk
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: revanced-manager
|
||||
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'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Setup Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
cache-disabled: true
|
||||
- name: Set up Gradle
|
||||
uses: gradle/actions/setup-gradle@v3
|
||||
|
||||
- name: Build with Gradle
|
||||
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
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: peter-evans/repository-dispatch@v2
|
||||
- uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
||||
repository: revanced/revanced-documentation
|
||||
|
|
|
@ -20,9 +20,6 @@ android {
|
|||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "0.0.1"
|
||||
resourceConfigurations.addAll(listOf(
|
||||
"en",
|
||||
))
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
}
|
||||
|
||||
|
@ -88,6 +85,12 @@ android {
|
|||
buildFeatures.aidl = true
|
||||
buildFeatures.buildConfig=true
|
||||
|
||||
android {
|
||||
androidResources {
|
||||
generateLocaleConfig = true
|
||||
}
|
||||
}
|
||||
|
||||
composeOptions.kotlinCompilerExtensionVersion = "1.5.10"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
|
|
|
@ -1,23 +1,18 @@
|
|||
package app.revanced.manager
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Intent
|
||||
import app.revanced.manager.di.*
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||
import app.revanced.manager.service.ManagerRootService
|
||||
import app.revanced.manager.service.RootConnection
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import coil.Coil
|
||||
import coil.ImageLoader
|
||||
import com.topjohnwu.superuser.Shell
|
||||
import com.topjohnwu.superuser.internal.BuilderImpl
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.launch
|
||||
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
|
||||
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.koin.androidContext
|
||||
import org.koin.android.ext.koin.androidLogger
|
||||
|
@ -61,9 +56,6 @@ class ManagerApplication : Application() {
|
|||
val shellBuilder = BuilderImpl.create().setFlags(Shell.FLAG_MOUNT_MASTER)
|
||||
Shell.setDefaultBuilder(shellBuilder)
|
||||
|
||||
val intent = Intent(this, ManagerRootService::class.java)
|
||||
RootService.bind(intent, get<RootConnection>())
|
||||
|
||||
scope.launch {
|
||||
prefs.preload()
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package app.revanced.manager.di
|
||||
|
||||
import app.revanced.manager.domain.installer.RootInstaller
|
||||
import app.revanced.manager.service.RootConnection
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
|
||||
val rootModule = module {
|
||||
singleOf(::RootConnection)
|
||||
singleOf(::RootInstaller)
|
||||
}
|
|
@ -18,6 +18,7 @@ val viewModelModule = module {
|
|||
viewModelOf(::ChangelogsViewModel)
|
||||
viewModelOf(::ImportExportViewModel)
|
||||
viewModelOf(::AboutViewModel)
|
||||
viewModelOf(::DeveloperOptionsViewModel)
|
||||
viewModelOf(::ContributorViewModel)
|
||||
viewModelOf(::DownloadsViewModel)
|
||||
viewModelOf(::InstalledAppsViewModel)
|
||||
|
|
|
@ -9,9 +9,11 @@ import app.revanced.manager.R
|
|||
import app.revanced.manager.domain.repository.PatchBundlePersistenceRepository
|
||||
import app.revanced.manager.patcher.patch.PatchBundle
|
||||
import app.revanced.manager.util.tag
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koin.core.component.KoinComponent
|
||||
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].
|
||||
* The flow will emit null if the associated [PatchBundleSource] is deleted.
|
||||
*/
|
||||
fun propsFlow() = configRepository.getProps(uid)
|
||||
suspend fun getProps() = configRepository.getProps(uid).first()!!
|
||||
fun propsFlow() = configRepository.getProps(uid).flowOn(Dispatchers.Default)
|
||||
suspend fun getProps() = propsFlow().first()!!
|
||||
|
||||
suspend fun currentVersion() = getProps().versionInfo
|
||||
protected suspend fun saveVersion(patches: String?, integrations: String?) =
|
||||
|
|
|
@ -1,49 +1,93 @@
|
|||
package app.revanced.manager.domain.installer
|
||||
|
||||
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 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.time.withTimeoutOrNull
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.time.Duration
|
||||
|
||||
class RootInstaller(
|
||||
private val app: Application,
|
||||
private val rootConnection: RootConnection,
|
||||
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 isAppInstalled(packageName: String) =
|
||||
rootConnection.remoteFS?.getFile("$modulesPath/$packageName-revanced")
|
||||
?.exists() ?: throw RootServiceException()
|
||||
suspend fun isAppInstalled(packageName: String) =
|
||||
awaitRemoteFS().getFile("$modulesPath/$packageName-revanced").exists()
|
||||
|
||||
fun isAppMounted(packageName: String): Boolean {
|
||||
return pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir?.let {
|
||||
Shell.cmd("mount | grep \"$it\"").exec().isSuccess
|
||||
suspend fun isAppMounted(packageName: String) = withContext(Dispatchers.IO) {
|
||||
pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir?.let {
|
||||
execute("mount | grep \"$it\"").isSuccess
|
||||
} ?: false
|
||||
}
|
||||
|
||||
fun mount(packageName: String) {
|
||||
suspend fun mount(packageName: String) {
|
||||
if (isAppMounted(packageName)) return
|
||||
|
||||
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
|
||||
?: throw Exception("Failed to load application info")
|
||||
val patchedAPK = "$modulesPath/$packageName-revanced/$packageName.apk"
|
||||
withContext(Dispatchers.IO) {
|
||||
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
|
||||
?: throw Exception("Failed to load application info")
|
||||
val patchedAPK = "$modulesPath/$packageName-revanced/$packageName.apk"
|
||||
|
||||
Shell.cmd("mount -o bind \"$patchedAPK\" \"$stockAPK\"").exec()
|
||||
.also { if (!it.isSuccess) throw Exception("Failed to mount APK") }
|
||||
execute("mount -o bind \"$patchedAPK\" \"$stockAPK\"").assertSuccess("Failed to mount APK")
|
||||
}
|
||||
}
|
||||
|
||||
fun unmount(packageName: String) {
|
||||
suspend fun unmount(packageName: String) {
|
||||
if (!isAppMounted(packageName)) return
|
||||
|
||||
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
|
||||
?: throw Exception("Failed to load application info")
|
||||
withContext(Dispatchers.IO) {
|
||||
val stockAPK = pm.getPackageInfo(packageName)?.applicationInfo?.sourceDir
|
||||
?: throw Exception("Failed to load application info")
|
||||
|
||||
Shell.cmd("umount -l \"$stockAPK\"").exec()
|
||||
.also { if (!it.isSuccess) throw Exception("Failed to unmount APK") }
|
||||
execute("umount -l \"$stockAPK\"").assertSuccess("Failed to unmount APK")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun install(
|
||||
|
@ -52,80 +96,77 @@ class RootInstaller(
|
|||
packageName: String,
|
||||
version: String,
|
||||
label: String
|
||||
) {
|
||||
withContext(Dispatchers.IO) {
|
||||
rootConnection.remoteFS?.let { remoteFS ->
|
||||
val assets = app.assets
|
||||
val modulePath = "$modulesPath/$packageName-revanced"
|
||||
) = withContext(Dispatchers.IO) {
|
||||
val remoteFS = awaitRemoteFS()
|
||||
val assets = app.assets
|
||||
val modulePath = "$modulesPath/$packageName-revanced"
|
||||
|
||||
unmount(packageName)
|
||||
unmount(packageName)
|
||||
|
||||
stockAPK?.let { stockApp ->
|
||||
pm.getPackageInfo(packageName)?.let { packageInfo ->
|
||||
if (packageInfo.versionName <= version)
|
||||
Shell.cmd("pm uninstall -k --user 0 $packageName").exec()
|
||||
.also { if (!it.isSuccess) throw Exception("Failed to uninstall stock app") }
|
||||
stockAPK?.let { stockApp ->
|
||||
pm.getPackageInfo(packageName)?.let { packageInfo ->
|
||||
if (packageInfo.versionName <= version)
|
||||
execute("pm uninstall -k --user 0 $packageName").assertSuccess("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()
|
||||
.also { if (!it.isSuccess) throw Exception("Failed to install stock app") }
|
||||
}
|
||||
"$modulePath/$packageName.apk".let { apkPath ->
|
||||
|
||||
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)
|
||||
}
|
||||
remoteFS.getFile(patchedAPK.absolutePath)
|
||||
.also { if (!it.exists()) throw Exception("File doesn't exist") }
|
||||
.newInputStream().use { inputStream ->
|
||||
remoteFS.getFile(apkPath).newOutputStream().use { outputStream ->
|
||||
inputStream.copyTo(outputStream)
|
||||
}
|
||||
}
|
||||
|
||||
"$modulePath/$packageName.apk".let { apkPath ->
|
||||
|
||||
remoteFS.getFile(patchedAPK.absolutePath)
|
||||
.also { if (!it.exists()) throw Exception("File doesn't exist") }
|
||||
.newInputStream().use { inputStream ->
|
||||
remoteFS.getFile(apkPath).newOutputStream().use { outputStream ->
|
||||
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()
|
||||
execute(
|
||||
"chmod 644 $apkPath",
|
||||
"chown system:system $apkPath",
|
||||
"chcon u:object_r:apk_data_file:s0 $apkPath",
|
||||
"chmod +x $modulePath/service.sh"
|
||||
).assertSuccess("Failed to set file permissions")
|
||||
}
|
||||
}
|
||||
|
||||
fun uninstall(packageName: String) {
|
||||
rootConnection.remoteFS?.let { remoteFS ->
|
||||
if (isAppMounted(packageName))
|
||||
unmount(packageName)
|
||||
suspend fun uninstall(packageName: String) {
|
||||
val remoteFS = awaitRemoteFS()
|
||||
if (isAppMounted(packageName))
|
||||
unmount(packageName)
|
||||
|
||||
remoteFS.getFile("$modulesPath/$packageName-revanced").deleteRecursively()
|
||||
.also { if (!it) throw Exception("Failed to delete files") }
|
||||
} ?: throw RootServiceException()
|
||||
remoteFS.getFile("$modulesPath/$packageName-revanced").deleteRecursively()
|
||||
.also { if (!it) throw Exception("Failed to delete files") }
|
||||
}
|
||||
|
||||
companion object {
|
||||
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 useProcessRuntime = booleanPreference("use_process_runtime", false)
|
||||
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 keystorePass = stringPreference("keystore_pass", KeystoreManager.DEFAULT)
|
||||
|
@ -25,8 +24,8 @@ class PreferencesManager(
|
|||
val firstLaunch = booleanPreference("first_launch", true)
|
||||
val managerAutoUpdates = booleanPreference("manager_auto_updates", false)
|
||||
|
||||
val disablePatchVersionCompatCheck = booleanPreference("disable_patch_version_compatibility_check", 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)
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package app.revanced.manager.service
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.os.IBinder
|
||||
import app.revanced.manager.IRootSystemService
|
||||
import com.topjohnwu.superuser.ipc.RootService
|
||||
|
@ -14,23 +12,5 @@ class ManagerRootService : RootService() {
|
|||
FileSystemManager.getService()
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
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
|
||||
}
|
||||
override fun onBind(intent: Intent): IBinder = RootSystemService()
|
||||
}
|
|
@ -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.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
|
@ -115,8 +116,8 @@ fun BaseBundleDialog(
|
|||
|
||||
if (remoteUrl != null) {
|
||||
BundleListItem(
|
||||
headlineText = stringResource(R.string.automatically_update),
|
||||
supportingText = stringResource(R.string.automatically_update_description),
|
||||
headlineText = stringResource(R.string.bundle_auto_update),
|
||||
supportingText = stringResource(R.string.bundle_auto_update_description),
|
||||
trailingContent = {
|
||||
Switch(
|
||||
checked = autoUpdate,
|
||||
|
@ -163,8 +164,7 @@ fun BaseBundleDialog(
|
|||
val patchesClickable = LocalContext.current.isDebuggable && patchCount > 0
|
||||
BundleListItem(
|
||||
headlineText = stringResource(R.string.patches),
|
||||
supportingText = if (patchCount == 0) stringResource(R.string.no_patches)
|
||||
else stringResource(R.string.patches_available, patchCount),
|
||||
supportingText = pluralStringResource(R.plurals.bundle_patches_available, patchCount, patchCount),
|
||||
modifier = Modifier.clickable(enabled = patchesClickable, onClick = onPatchesClick)
|
||||
) {
|
||||
if (patchesClickable)
|
||||
|
|
|
@ -6,35 +6,38 @@ import kotlinx.parcelize.Parcelize
|
|||
sealed interface SettingsDestination : Parcelable {
|
||||
|
||||
@Parcelize
|
||||
object Settings : SettingsDestination
|
||||
data object Settings : SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
object General : SettingsDestination
|
||||
data object General : SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
object Advanced : SettingsDestination
|
||||
data object Advanced : SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
object Updates : SettingsDestination
|
||||
data object Updates : SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
object Downloads : SettingsDestination
|
||||
data object Downloads : SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
object ImportExport : SettingsDestination
|
||||
data object ImportExport : SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
object About : SettingsDestination
|
||||
data object About : SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
data class Update(val downloadOnScreenEntry: Boolean = false) : SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
object Changelogs : SettingsDestination
|
||||
data object Changelogs : SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
object Contributors: SettingsDestination
|
||||
data object Contributors: SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
object Licenses: SettingsDestination
|
||||
data object Licenses: SettingsDestination
|
||||
|
||||
@Parcelize
|
||||
data object DeveloperOptions: SettingsDestination
|
||||
}
|
|
@ -70,8 +70,7 @@ fun AppSelectorScreen(
|
|||
vm.nonSuggestedVersionDialogSubject?.let {
|
||||
NonSuggestedVersionDialog(
|
||||
suggestedVersion = suggestedVersions[it.packageName].orEmpty(),
|
||||
onCancel = vm::dismissNonSuggestedVersionDialog,
|
||||
onContinue = vm::continueWithNonSuggestedVersion,
|
||||
onDismiss = vm::dismissNonSuggestedVersionDialog
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
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.compose.foundation.ExperimentalFoundationApi
|
||||
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.material.icons.Icons
|
||||
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.outlined.Apps
|
||||
import androidx.compose.material.icons.outlined.DeleteOutline
|
||||
|
@ -67,6 +72,7 @@ enum class DashboardPage(
|
|||
BUNDLES(R.string.tab_bundles, Icons.Outlined.Source),
|
||||
}
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DashboardScreen(
|
||||
|
@ -211,6 +217,20 @@ fun DashboardScreen(
|
|||
)
|
||||
}
|
||||
} 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 {
|
||||
{
|
||||
NotificationCard(
|
||||
|
|
|
@ -83,7 +83,7 @@ fun InstalledAppInfoScreen(
|
|||
|
||||
if (viewModel.installedApp.installType == InstallType.ROOT) {
|
||||
Text(
|
||||
text = if (viewModel.rootInstaller.isAppMounted(viewModel.installedApp.currentPackageName)) {
|
||||
text = if (viewModel.isMounted) {
|
||||
stringResource(R.string.mounted)
|
||||
} else {
|
||||
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.Search
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material.icons.outlined.WarningAmber
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
|
@ -37,7 +38,6 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.surfaceColorAtElevation
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
@ -48,17 +48,16 @@ import androidx.compose.runtime.setValue
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
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.PatchInfo
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
import app.revanced.manager.ui.component.Countdown
|
||||
import app.revanced.manager.ui.component.DangerousActionDialogBase
|
||||
import app.revanced.manager.ui.component.SafeguardDialog
|
||||
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
|
||||
import app.revanced.manager.ui.component.SearchView
|
||||
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.isScrollingUp
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.compose.koinInject
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
|
@ -160,10 +158,16 @@ fun PatchesSelectorScreen(
|
|||
)
|
||||
}
|
||||
|
||||
vm.pendingSelectionAction?.let {
|
||||
SelectionWarningDialog(
|
||||
onCancel = vm::dismissSelectionWarning,
|
||||
onConfirm = vm::confirmSelectionWarning
|
||||
var showSelectionWarning by rememberSaveable {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
if (showSelectionWarning) {
|
||||
SelectionWarningDialog(onDismiss = { showSelectionWarning = false })
|
||||
}
|
||||
vm.pendingUniversalPatchAction?.let {
|
||||
UniversalPatchWarningDialog(
|
||||
onCancel = vm::dismissUniversalPatchWarning,
|
||||
onConfirm = vm::confirmUniversalPatchWarning
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -196,9 +200,9 @@ fun PatchesSelectorScreen(
|
|||
),
|
||||
onToggle = {
|
||||
if (vm.selectionWarningEnabled) {
|
||||
vm.pendingSelectionAction = {
|
||||
vm.togglePatch(uid, patch)
|
||||
}
|
||||
showSelectionWarning = true
|
||||
} else if (vm.universalPatchWarningEnabled && patch.compatiblePackages == null) {
|
||||
vm.pendingUniversalPatchAction = { vm.togglePatch(uid, patch) }
|
||||
} else {
|
||||
vm.togglePatch(uid, patch)
|
||||
}
|
||||
|
@ -369,36 +373,43 @@ fun PatchesSelectorScreen(
|
|||
}
|
||||
|
||||
@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,
|
||||
onConfirm: (Boolean) -> Unit
|
||||
onConfirm: () -> Unit
|
||||
) {
|
||||
val prefs: PreferencesManager = koinInject()
|
||||
|
||||
DangerousActionDialogBase(
|
||||
onCancel = onCancel,
|
||||
confirmButton = { dismissPermanently ->
|
||||
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)
|
||||
}
|
||||
AlertDialog(
|
||||
onDismissRequest = onCancel,
|
||||
confirmButton = {
|
||||
TextButton(onClick = onConfirm) {
|
||||
Text(stringResource(R.string.continue_))
|
||||
}
|
||||
},
|
||||
title = R.string.selection_warning_title,
|
||||
body = stringResource(R.string.selection_warning_description),
|
||||
dismissButton = {
|
||||
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
|
||||
|
||||
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.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.BatteryAlert
|
||||
import androidx.compose.material.icons.outlined.*
|
||||
import androidx.compose.material3.*
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.ui.component.AppTopBar
|
||||
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.destination.SettingsDestination
|
||||
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.core.parameter.parametersOf
|
||||
|
||||
@SuppressLint("BatteryLife")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
|
@ -54,10 +38,6 @@ fun SettingsScreen(
|
|||
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(
|
||||
Triple(
|
||||
R.string.general,
|
||||
|
@ -122,7 +102,8 @@ fun SettingsScreen(
|
|||
is SettingsDestination.About -> AboutSettingsScreen(
|
||||
onBackClick = backClick,
|
||||
onContributorsClick = { navController.navigate(SettingsDestination.Contributors) },
|
||||
onLicensesClick = { navController.navigate(SettingsDestination.Licenses) }
|
||||
onDeveloperOptionsClick = { navController.navigate(SettingsDestination.DeveloperOptions) },
|
||||
onLicensesClick = { navController.navigate(SettingsDestination.Licenses) },
|
||||
)
|
||||
|
||||
is SettingsDestination.Update -> UpdateScreen(
|
||||
|
@ -146,6 +127,8 @@ fun SettingsScreen(
|
|||
onBackClick = backClick,
|
||||
)
|
||||
|
||||
is SettingsDestination.DeveloperOptions -> DeveloperOptionsScreen(onBackClick = backClick)
|
||||
|
||||
is SettingsDestination.Settings -> {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
|
@ -160,21 +143,6 @@ fun SettingsScreen(
|
|||
.padding(paddingValues)
|
||||
.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) ->
|
||||
SettingsListItem(
|
||||
modifier = Modifier.clickable { navController.navigate(destination) },
|
||||
|
|
|
@ -69,8 +69,7 @@ fun VersionSelectorScreen(
|
|||
if (viewModel.showNonSuggestedVersionDialog)
|
||||
NonSuggestedVersionDialog(
|
||||
suggestedVersion = viewModel.requiredVersion.orEmpty(),
|
||||
onCancel = viewModel::dismissNonSuggestedVersionDialog,
|
||||
onContinue = viewModel::continueWithNonSuggestedVersion,
|
||||
onDismiss = viewModel::dismissNonSuggestedVersionDialog
|
||||
)
|
||||
|
||||
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.viewmodel.AboutViewModel
|
||||
import app.revanced.manager.ui.viewmodel.AboutViewModel.Companion.getSocialIcon
|
||||
import app.revanced.manager.util.isDebuggable
|
||||
import app.revanced.manager.util.openUrl
|
||||
import com.google.accompanist.drawablepainter.rememberDrawablePainter
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
|
@ -50,6 +49,7 @@ fun AboutSettingsScreen(
|
|||
onBackClick: () -> Unit,
|
||||
onContributorsClick: () -> Unit,
|
||||
onLicensesClick: () -> Unit,
|
||||
onDeveloperOptionsClick: () -> Unit,
|
||||
viewModel: AboutViewModel = koinViewModel()
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
@ -116,9 +116,11 @@ fun AboutSettingsScreen(
|
|||
stringResource(R.string.contributors_description),
|
||||
third = onContributorsClick
|
||||
),
|
||||
Triple(stringResource(R.string.developer_options),
|
||||
Triple(
|
||||
stringResource(R.string.developer_options),
|
||||
stringResource(R.string.developer_options_description),
|
||||
third = { /*TODO*/ }).takeIf { context.isDebuggable },
|
||||
third = onDeveloperOptionsClick
|
||||
),
|
||||
Triple(
|
||||
stringResource(R.string.opensource_licenses),
|
||||
stringResource(R.string.opensource_licenses_description),
|
||||
|
|
|
@ -72,6 +72,8 @@ fun AdvancedSettingsScreen(
|
|||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
GroupHeader(stringResource(R.string.manager))
|
||||
|
||||
val apiUrl by vm.prefs.api.getAsState()
|
||||
var showApiUrlDialog by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
|
@ -111,12 +113,26 @@ fun AdvancedSettingsScreen(
|
|||
headline = R.string.process_runtime_memory_limit,
|
||||
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(
|
||||
preference = vm.prefs.disablePatchVersionCompatCheck,
|
||||
coroutineScope = vm.viewModelScope,
|
||||
headline = R.string.patch_compat_check,
|
||||
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(
|
||||
preference = vm.prefs.suggestedVersionSafeguard,
|
||||
coroutineScope = vm.viewModelScope,
|
||||
|
@ -124,24 +140,10 @@ fun AdvancedSettingsScreen(
|
|||
description = R.string.suggested_version_safeguard_description
|
||||
)
|
||||
BooleanItem(
|
||||
preference = vm.prefs.multithreadingDexFileWriter,
|
||||
preference = vm.prefs.disableSelectionWarning,
|
||||
coroutineScope = vm.viewModelScope,
|
||||
headline = R.string.multithreaded_dex_file_writer,
|
||||
description = R.string.multithreaded_dex_file_writer_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()
|
||||
}
|
||||
headline = R.string.patch_selection_safeguard,
|
||||
description = R.string.patch_selection_safeguard_description
|
||||
)
|
||||
|
||||
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 = {
|
||||
Button(
|
||||
TextButton(
|
||||
onClick = {
|
||||
onConfirm(selectedTheme)
|
||||
onDismiss()
|
||||
|
|
|
@ -6,12 +6,10 @@ import android.util.Log
|
|||
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.tag
|
||||
import app.revanced.manager.util.toast
|
||||
import app.revanced.manager.util.uiSafe
|
||||
import com.github.pgreze.process.Redirect
|
||||
import com.github.pgreze.process.process
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
@ -43,16 +41,6 @@ class AdvancedSettingsViewModel(
|
|||
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 {
|
||||
val exitCode = try {
|
||||
withContext(Dispatchers.IO) {
|
||||
|
|
|
@ -3,14 +3,12 @@ package app.revanced.manager.ui.viewmodel
|
|||
import android.app.Application
|
||||
import android.content.pm.PackageInfo
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.R
|
||||
import app.revanced.manager.domain.manager.PreferencesManager
|
||||
import app.revanced.manager.domain.repository.PatchBundleRepository
|
||||
import app.revanced.manager.ui.model.SelectedApp
|
||||
import app.revanced.manager.util.PM
|
||||
|
@ -25,8 +23,7 @@ import java.nio.file.Files
|
|||
class AppSelectorViewModel(
|
||||
private val app: Application,
|
||||
private val pm: PM,
|
||||
private val patchBundleRepository: PatchBundleRepository,
|
||||
private val prefs: PreferencesManager,
|
||||
private val patchBundleRepository: PatchBundleRepository
|
||||
) : ViewModel() {
|
||||
private val inputFile = File(app.cacheDir, "input.apk").also {
|
||||
it.delete()
|
||||
|
@ -46,13 +43,6 @@ class AppSelectorViewModel(
|
|||
nonSuggestedVersionDialogSubject = null
|
||||
}
|
||||
|
||||
fun continueWithNonSuggestedVersion(dismissPermanently: Boolean) = viewModelScope.launch {
|
||||
if (dismissPermanently) prefs.suggestedVersionSafeguard.update(false)
|
||||
|
||||
nonSuggestedVersionDialogSubject?.let(onStorageClick)
|
||||
dismissNonSuggestedVersionDialog()
|
||||
}
|
||||
|
||||
fun handleStorageResult(uri: Uri) = viewModelScope.launch {
|
||||
val selectedApp = withContext(Dispatchers.IO) {
|
||||
loadSelectedFile(uri)
|
||||
|
|
|
@ -3,10 +3,12 @@ package app.revanced.manager.ui.viewmodel
|
|||
import android.app.Application
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.os.PowerManager
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import app.revanced.manager.R
|
||||
|
@ -33,15 +35,21 @@ class DashboardViewModel(
|
|||
val availablePatches =
|
||||
patchBundleRepository.bundles.map { it.values.sumOf { bundle -> bundle.patches.size } }
|
||||
private val contentResolver: ContentResolver = app.contentResolver
|
||||
private val powerManager = app.getSystemService<PowerManager>()!!
|
||||
val sources = patchBundleRepository.sources
|
||||
val selectedSources = mutableStateListOf<PatchBundleSource>()
|
||||
|
||||
|
||||
var updatedManagerVersion: String? by mutableStateOf(null)
|
||||
private set
|
||||
var showBatteryOptimizationsWarning by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
init {
|
||||
viewModelScope.launch { checkForManagerUpdates() }
|
||||
viewModelScope.launch {
|
||||
checkForManagerUpdates()
|
||||
showBatteryOptimizationsWarning =
|
||||
!powerManager.isIgnoringBatteryOptimizations(app.packageName)
|
||||
}
|
||||
}
|
||||
|
||||
fun dismissUpdateDialog() {
|
||||
|
@ -80,12 +88,14 @@ class DashboardViewModel(
|
|||
fun cancelSourceSelection() {
|
||||
selectedSources.clear()
|
||||
}
|
||||
|
||||
fun createLocalSource(patchBundle: Uri, integrations: Uri?) =
|
||||
viewModelScope.launch {
|
||||
contentResolver.openInputStream(patchBundle)!!.use { patchesStream ->
|
||||
integrations?.let { contentResolver.openInputStream(it) }.use { integrationsStream ->
|
||||
patchBundleRepository.createLocal(patchesStream, integrationsStream)
|
||||
}
|
||||
integrations?.let { contentResolver.openInputStream(it) }
|
||||
.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)
|
||||
private set
|
||||
var appliedPatches: PatchSelection? by mutableStateOf(null)
|
||||
var isMounted by mutableStateOf(rootInstaller.isAppMounted(installedApp.currentPackageName))
|
||||
var isMounted by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
isMounted = rootInstaller.isAppMounted(installedApp.currentPackageName)
|
||||
}
|
||||
}
|
||||
|
||||
fun launch() = pm.launch(installedApp.currentPackageName)
|
||||
|
||||
fun mountOrUnmount() {
|
||||
fun mountOrUnmount() = viewModelScope.launch {
|
||||
try {
|
||||
if (isMounted)
|
||||
rootInstaller.unmount(installedApp.currentPackageName)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package app.revanced.manager.ui.viewmodel
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Application
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
|
@ -39,7 +40,7 @@ class MainViewModel(
|
|||
val launcher = componentActivity.registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result: ActivityResult ->
|
||||
if (result.resultCode == ComponentActivity.RESULT_OK) {
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
result.data?.getStringExtra("data")?.let {
|
||||
applyLegacySettings(it)
|
||||
} ?: 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.tag
|
||||
import app.revanced.manager.util.toast
|
||||
import app.revanced.manager.util.uiSafe
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.time.withTimeout
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.time.Duration
|
||||
import java.util.UUID
|
||||
|
||||
@Stable
|
||||
|
@ -177,6 +183,7 @@ class PatcherViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
app.unregisterReceiver(installBroadcastReceiver)
|
||||
|
@ -188,15 +195,16 @@ class PatcherViewModel(
|
|||
}
|
||||
|
||||
is SelectedApp.Installed -> {
|
||||
try {
|
||||
installedApp?.let {
|
||||
if (it.installType == InstallType.ROOT) {
|
||||
rootInstaller.mount(packageName)
|
||||
GlobalScope.launch(Dispatchers.Main) {
|
||||
uiSafe(app, R.string.failed_to_mount, "Failed to mount") {
|
||||
installedApp?.let {
|
||||
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
|
||||
val appVersion = input.app.version
|
||||
|
||||
var pendingSelectionAction by mutableStateOf<(() -> Unit)?>(null)
|
||||
var pendingUniversalPatchAction by mutableStateOf<(() -> Unit)?>(null)
|
||||
|
||||
var selectionWarningEnabled by mutableStateOf(true)
|
||||
private set
|
||||
var universalPatchWarningEnabled by mutableStateOf(true)
|
||||
private set
|
||||
|
||||
val allowIncompatiblePatches =
|
||||
get<PreferencesManager>().disablePatchVersionCompatCheck.getBlocking()
|
||||
|
@ -59,6 +61,8 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
|
|||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
universalPatchWarningEnabled = !prefs.disableUniversalPatchWarning.get()
|
||||
|
||||
if (prefs.disableSelectionWarning.get()) {
|
||||
selectionWarningEnabled = false
|
||||
return@launch
|
||||
|
@ -131,21 +135,15 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
|
|||
customPatchSelection = selection.put(bundle, newPatches)
|
||||
}
|
||||
|
||||
fun confirmSelectionWarning(dismissPermanently: Boolean) {
|
||||
selectionWarningEnabled = false
|
||||
fun confirmUniversalPatchWarning() {
|
||||
universalPatchWarningEnabled = false
|
||||
|
||||
pendingSelectionAction?.invoke()
|
||||
pendingSelectionAction = null
|
||||
|
||||
if (!dismissPermanently) return
|
||||
|
||||
viewModelScope.launch {
|
||||
prefs.disableSelectionWarning.update(true)
|
||||
}
|
||||
pendingUniversalPatchAction?.invoke()
|
||||
pendingUniversalPatchAction = null
|
||||
}
|
||||
|
||||
fun dismissSelectionWarning() {
|
||||
pendingSelectionAction = null
|
||||
fun dismissUniversalPatchWarning() {
|
||||
pendingUniversalPatchAction = null
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
|
|
|
@ -162,12 +162,6 @@ class VersionSelectorViewModel(
|
|||
nonSuggestedVersionDialogSubject = null
|
||||
}
|
||||
|
||||
fun continueWithNonSuggestedVersion(dismissPermanently: Boolean) = viewModelScope.launch {
|
||||
if (dismissPermanently) prefs.suggestedVersionSafeguard.update(false)
|
||||
selectedVersion = nonSuggestedVersionDialogSubject
|
||||
dismissNonSuggestedVersionDialog()
|
||||
}
|
||||
|
||||
fun select(app: SelectedApp) {
|
||||
if (requiredVersion != null && app.version != requiredVersion) {
|
||||
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">
|
||||
<item quantity="other">%d selected</item>
|
||||
</plurals>
|
||||
<plurals name="bundle_patches_available">
|
||||
<item quantity="one">%d patch available</item>
|
||||
<item quantity="other">%d patches available</item>
|
||||
</plurals>
|
||||
</resources>
|
|
@ -71,10 +71,15 @@
|
|||
<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_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_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_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_description">Import a custom keystore</string>
|
||||
<string name="import_keystore_dialog_title">Enter keystore credentials</string>
|
||||
|
@ -133,6 +138,7 @@
|
|||
<string name="apply">Apply</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="back">Back</string>
|
||||
<string name="warning">Warning</string>
|
||||
<string name="add">Add</string>
|
||||
<string name="close">Close</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_integrations_fail">Failed to update integrations: %s</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="bundles_selected">%s selected</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_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_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_description">You may encounter issues when not using the default patch selection and options.</string>
|
||||
<string name="selection_warning_continue_countdown">Continue (%ds)</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="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="universal">Universal</string>
|
||||
<string name="unsupported">Unsupported</string>
|
||||
|
@ -299,8 +303,8 @@
|
|||
<string name="bundle_input_source_url">Source URL</string>
|
||||
<string name="bundle_update_success">Successfully updated %s</string>
|
||||
<string name="bundle_update_unavailable">No update available for %s</string>
|
||||
<string name="automatically_update">Automatically update</string>
|
||||
<string name="automatically_update_description">Automatically update this bundle when ReVanced starts</string>
|
||||
<string name="bundle_auto_update">Auto update</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_description">Choose the type of bundle you want</string>
|
||||
<string name="about_revanced_manager">About ReVanced Manager</string>
|
||||
|
@ -319,7 +323,7 @@
|
|||
<string name="changelog_loading">Loading changelog</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="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="downloading_manager_update">Downloading update…</string>
|
||||
<string name="download_manager_failed">Failed to download update: %s</string>
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
[versions]
|
||||
ktx = "1.12.0"
|
||||
ktx = "1.13.1"
|
||||
material3 = "1.2.1"
|
||||
ui-tooling = "1.6.4"
|
||||
viewmodel-lifecycle = "2.7.0"
|
||||
ui-tooling = "1.6.8"
|
||||
viewmodel-lifecycle = "2.8.3"
|
||||
splash-screen = "1.0.1"
|
||||
compose-activity = "1.8.2"
|
||||
paging = "3.2.1"
|
||||
preferences-datastore = "1.0.0"
|
||||
compose-activity = "1.9.0"
|
||||
paging = "3.3.0"
|
||||
preferences-datastore = "1.1.1"
|
||||
work-runtime = "2.9.0"
|
||||
compose-bom = "2024.03.00"
|
||||
compose-bom = "2024.06.00"
|
||||
accompanist = "0.34.0"
|
||||
placeholder = "1.1.2"
|
||||
reorderable = "1.5.2"
|
||||
|
@ -23,7 +23,7 @@ reimagined-navigation = "1.5.0"
|
|||
ktor = "2.3.9"
|
||||
markdown-renderer = "0.22.0"
|
||||
fading-edges = "1.0.4"
|
||||
android-gradle-plugin = "8.3.0"
|
||||
android-gradle-plugin = "8.3.2"
|
||||
kotlin-gradle-plugin = "1.9.22"
|
||||
dev-tools-gradle-plugin = "1.9.22-1.0.17"
|
||||
about-libraries-gradle-plugin = "11.1.1"
|
||||
|
|
Loading…
Reference in a new issue