Merge branch 'compose-dev' into compose/downloader-system

This commit is contained in:
Ax333l 2024-08-12 23:04:30 +02:00
commit ce38745dd3
No known key found for this signature in database
GPG Key ID: D2B4D85271127D23
30 changed files with 336 additions and 227 deletions

View File

@ -24,7 +24,7 @@ jobs:
distribution: 'temurin'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
env:

View File

@ -21,7 +21,7 @@ jobs:
distribution: 'temurin'
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3
uses: gradle/actions/setup-gradle@v4
- name: Build with Gradle
env:

View File

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "ab113134d89f2c5e412e87775510b327",
"identityHash": "393045599eb516fc7ef99f142485e9a2",
"entities": [
{
"tableName": "patch_bundles",
@ -51,17 +51,7 @@
"uid"
]
},
"indices": [
{
"name": "index_patch_bundles_name",
"unique": true,
"columnNames": [
"name"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_patch_bundles_name` ON `${TABLE_NAME}` (`name`)"
}
],
"indices": [],
"foreignKeys": []
},
{
@ -433,7 +423,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ab113134d89f2c5e412e87775510b327')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '393045599eb516fc7ef99f142485e9a2')"
]
}
}

View File

@ -1,10 +1,11 @@
package app.revanced.manager.data.platform
import android.Manifest
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.Manifest
import android.content.pm.PackageManager
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import app.revanced.manager.util.RequestManageStorageContract
@ -16,7 +17,7 @@ class Filesystem(private val app: Application) {
* A directory that gets cleared when the app restarts.
* Do not store paths to this directory in a parcel.
*/
val tempDir = app.cacheDir.resolve("ephemeral").apply {
val tempDir = app.getDir("ephemeral", Context.MODE_PRIVATE).apply {
deleteRecursively()
mkdirs()
}

View File

@ -21,7 +21,7 @@ sealed class Source {
}
companion object {
fun from(value: String) = when(value) {
fun from(value: String) = when (value) {
Local.SENTINEL -> Local
API.SENTINEL -> API
else -> Remote(Url(value))
@ -34,7 +34,7 @@ data class VersionInfo(
@ColumnInfo(name = "integrations_version") val integrations: String? = null,
)
@Entity(tableName = "patch_bundles", indices = [Index(value = ["name"], unique = true)])
@Entity(tableName = "patch_bundles")
data class PatchBundleEntity(
@PrimaryKey val uid: Int,
@ColumnInfo(name = "name") val name: String,

View File

@ -21,6 +21,7 @@ class PreferencesManager(
val firstLaunch = booleanPreference("first_launch", true)
val managerAutoUpdates = booleanPreference("manager_auto_updates", false)
val showManagerUpdateDialogOnLaunch = booleanPreference("show_manager_update_dialog_on_launch", true)
val disablePatchVersionCompatCheck = booleanPreference("disable_patch_version_compatibility_check", false)
val disableSelectionWarning = booleanPreference("disable_selection_warning", false)

View File

@ -63,7 +63,7 @@ class EditorContext(private val prefs: MutablePreferences) {
abstract class Preference<T>(
private val dataStore: DataStore<Preferences>,
protected val default: T
val default: T
) {
internal abstract fun Preferences.read(): T
internal abstract fun MutablePreferences.write(value: T)

View File

@ -4,7 +4,7 @@ import android.content.Context
import app.revanced.manager.patcher.LibraryResolver
import android.os.Build.SUPPORTED_ABIS as DEVICE_ABIS
object Aapt : LibraryResolver() {
private val WORKING_ABIS = setOf("arm64-v8a", "x86", "x86_64")
private val WORKING_ABIS = setOf("arm64-v8a", "x86", "x86_64", "armeabi-v7a")
fun supportsDevice() = (DEVICE_ABIS intersect WORKING_ABIS).isNotEmpty()

View File

@ -234,6 +234,9 @@ class PatcherWorker(
Result.failure()
} finally {
patchedApk.delete()
if (args.input is SelectedApp.Local && args.input.temporary) {
args.input.file.delete()
}
}
}

View File

@ -55,7 +55,7 @@ fun AlertDialogExtended(
) {
Column(modifier = Modifier.padding(vertical = 24.dp)) {
Column(
modifier = Modifier.padding(horizontal = 24.dp)
modifier = Modifier.padding(horizontal = 24.dp).fillMaxWidth()
) {
icon?.let {
ContentStyle(color = iconContentColor) {

View File

@ -33,11 +33,9 @@ fun AppIcon(
Image(
image,
contentDescription,
Modifier.placeholder(visible = showPlaceHolder, color = MaterialTheme.colorScheme.inverseOnSurface, shape = RoundedCornerShape(100)).then(modifier),
modifier,
colorFilter = colorFilter
)
showPlaceHolder = false
} else {
AsyncImage(
packageInfo,

View File

@ -13,10 +13,16 @@ import androidx.compose.ui.res.stringResource
import app.revanced.manager.R
@Composable
fun ArrowButton(modifier: Modifier = Modifier, expanded: Boolean, onClick: (() -> Unit)?) {
fun ArrowButton(
modifier: Modifier = Modifier,
expanded: Boolean,
onClick: (() -> Unit)?,
rotationInitial: Float = 0f,
rotationFinal: Float = 180f
) {
val description = if (expanded) R.string.collapse_content else R.string.expand_content
val rotation by animateFloatAsState(
targetValue = if (expanded) 0f else 180f,
targetValue = if (expanded) rotationInitial else rotationFinal,
label = "rotation"
)

View File

@ -0,0 +1,81 @@
package app.revanced.manager.ui.component
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Update
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import app.revanced.manager.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AvailableUpdateDialog(
onDismiss: () -> Unit,
onConfirm: () -> Unit,
setShowManagerUpdateDialogOnLaunch: (Boolean) -> Unit,
newVersion: String
) {
var dontShowAgain by rememberSaveable { mutableStateOf(false) }
val dismissDialog = {
setShowManagerUpdateDialogOnLaunch(!dontShowAgain)
onDismiss()
}
AlertDialogExtended(
onDismissRequest = dismissDialog,
confirmButton = {
TextButton(
onClick = {
dismissDialog()
onConfirm()
}
) {
Text(stringResource(R.string.show))
}
},
dismissButton = {
TextButton(
onClick = dismissDialog
) {
Text(stringResource(R.string.dismiss))
}
},
icon = {
Icon(imageVector = Icons.Outlined.Update, contentDescription = null)
},
title = {
Text(stringResource(R.string.update_available))
},
text = {
Column(
modifier = Modifier.padding(horizontal = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
modifier = Modifier.padding(horizontal = 16.dp),
text = stringResource(R.string.update_available_dialog_description, newVersion)
)
ListItem(
modifier = Modifier.clickable { dontShowAgain = !dontShowAgain },
headlineContent = {
Text(stringResource(R.string.never_show_again))
},
leadingContent = {
CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) {
Checkbox(checked = dontShowAgain, onCheckedChange = { dontShowAgain = it })
}
}
)
}
},
textHorizontalPadding = PaddingValues(0.dp)
)
}

View File

@ -46,11 +46,6 @@ fun BaseBundleDialog(
ColumnWithScrollbar(
modifier = Modifier
.fillMaxWidth()
.padding(
start = 8.dp,
top = 8.dp,
end = 4.dp,
)
.then(modifier)
) {
if (name != null) {

View File

@ -572,7 +572,12 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.add)) },
icon = { Icon(Icons.Outlined.Add, null) },
icon = {
Icon(
Icons.Outlined.Add,
stringResource(R.string.add)
)
},
expanded = lazyListState.isScrollingUp,
onClick = { items.add(Item(null)) }
)

View File

@ -6,44 +6,17 @@ import android.net.Uri
import android.provider.Settings
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
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
import androidx.compose.material.icons.outlined.Download
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material.icons.outlined.Source
import androidx.compose.material.icons.outlined.Update
import androidx.compose.material.icons.outlined.WarningAmber
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
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
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
@ -56,6 +29,7 @@ import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefaul
import app.revanced.manager.patcher.aapt.Aapt
import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.AutoUpdatesDialog
import app.revanced.manager.ui.component.AvailableUpdateDialog
import app.revanced.manager.ui.component.NotificationCard
import app.revanced.manager.ui.component.bundle.BundleItem
import app.revanced.manager.ui.component.bundle.BundleTopBar
@ -118,6 +92,20 @@ fun DashboardScreen(
)
}
var showDialog by rememberSaveable { mutableStateOf(vm.prefs.showManagerUpdateDialogOnLaunch.getBlocking()) }
val availableUpdate by remember {
derivedStateOf { vm.updatedManagerVersion.takeIf { showDialog } }
}
availableUpdate?.let { version ->
AvailableUpdateDialog(
onDismiss = { showDialog = false },
setShowManagerUpdateDialogOnLaunch = vm::setShowManagerUpdateDialogOnLaunch,
onConfirm = onUpdateClick,
newVersion = version
)
}
Scaffold(
topBar = {
if (bundlesSelectable) {
@ -159,6 +147,23 @@ fun DashboardScreen(
AppTopBar(
title = stringResource(R.string.app_name),
actions = {
if (!vm.updatedManagerVersion.isNullOrEmpty()) {
IconButton(
onClick = onUpdateClick,
) {
BadgedBox(
badge = {
Badge(
// A size value above 6.dp forces the Badge icon to be closer to the center, fixing a clipping issue
modifier = Modifier.size(7.dp),
containerColor = MaterialTheme.colorScheme.primary,
)
}
) {
Icon(Icons.Outlined.Update, stringResource(R.string.update))
}
}
}
IconButton(onClick = onSettingsClick) {
Icon(Icons.Outlined.Settings, stringResource(R.string.settings))
}
@ -236,22 +241,6 @@ fun DashboardScreen(
)
}
} else null,
vm.updatedManagerVersion?.let {
{
NotificationCard(
text = stringResource(R.string.update_available_dialog_description, it),
icon = Icons.Outlined.Update,
actions = {
TextButton(onClick = vm::dismissUpdateDialog) {
Text(stringResource(R.string.dismiss))
}
TextButton(onClick = onUpdateClick) {
Text(stringResource(R.string.show))
}
}
)
}
},
if (showNewDownloaderPluginsNotification) {
{
NotificationCard(

View File

@ -2,12 +2,7 @@ package app.revanced.manager.ui.screen
import androidx.compose.foundation.ExperimentalFoundationApi
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
@ -15,28 +10,8 @@ import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material.icons.outlined.FilterList
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
import androidx.compose.material3.ExtendedFloatingActionButton
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.material.icons.outlined.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@ -57,8 +32,8 @@ import app.revanced.manager.R
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.SafeguardDialog
import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.SafeguardDialog
import app.revanced.manager.ui.component.SearchView
import app.revanced.manager.ui.component.patches.OptionItem
import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel
@ -94,6 +69,21 @@ fun PatchesSelectorScreen(
derivedStateOf { vm.selectionIsValid(bundles) }
}
val availablePatchCount by remember {
derivedStateOf {
bundles.sumOf { it.patchCount }
}
}
val defaultPatchSelectionCount by vm.defaultSelectionCount
.collectAsStateWithLifecycle(initialValue = 0)
val selectedPatchCount by remember {
derivedStateOf {
vm.customPatchSelection?.values?.sumOf { it.size } ?: defaultPatchSelectionCount
}
}
val patchLazyListStates = remember(bundles) { List(bundles.size) { LazyListState() } }
if (showBottomSheet) {
@ -142,12 +132,21 @@ fun PatchesSelectorScreen(
}
}
// TODO: properly handle appVersion == null
if (vm.compatibleVersions.isNotEmpty() && vm.appVersion != null)
UnsupportedDialog(
UnsupportedPatchDialog(
appVersion = vm.appVersion,
supportedVersions = vm.compatibleVersions,
onDismissRequest = vm::dismissDialogs
)
var showUnsupportedPatchesDialog by rememberSaveable {
mutableStateOf(false)
}
if (showUnsupportedPatchesDialog && vm.appVersion != null)
UnsupportedPatchesDialog(
appVersion = vm.appVersion,
onDismissRequest = { showUnsupportedPatchesDialog = false }
)
vm.optionsDialog?.let { (bundle, patch) ->
OptionsDialog(
@ -165,7 +164,7 @@ fun PatchesSelectorScreen(
if (showSelectionWarning) {
SelectionWarningDialog(onDismiss = { showSelectionWarning = false })
}
vm.pendingUniversalPatchAction?.let {
vm.pendingUniversalPatchAction?.let {
UniversalPatchWarningDialog(
onCancel = vm::dismissUniversalPatchWarning,
onConfirm = vm::confirmUniversalPatchWarning
@ -200,12 +199,20 @@ fun PatchesSelectorScreen(
patch
),
onToggle = {
if (vm.selectionWarningEnabled) {
showSelectionWarning = true
} else if (vm.universalPatchWarningEnabled && patch.compatiblePackages == null) {
vm.pendingUniversalPatchAction = { vm.togglePatch(uid, patch) }
} else {
vm.togglePatch(uid, patch)
when {
// Open unsupported dialog if the patch is not supported
!supported -> vm.openUnsupportedDialog(patch)
// Show selection warning if enabled
vm.selectionWarningEnabled -> showSelectionWarning = true
// Set pending universal patch action if the universal patch warning is enabled and there are no compatible packages
vm.universalPatchWarningEnabled && patch.compatiblePackages == null -> {
vm.pendingUniversalPatchAction = { vm.togglePatch(uid, patch) }
}
// Toggle the patch otherwise
else -> vm.togglePatch(uid, patch)
}
},
supported = supported
@ -256,7 +263,7 @@ fun PatchesSelectorScreen(
) {
ListHeader(
title = stringResource(R.string.unsupported_patches),
onHelpClick = { vm.openUnsupportedDialog(bundle.unsupported) }
onHelpClick = { showUnsupportedPatchesDialog = true }
)
}
}
@ -266,7 +273,11 @@ fun PatchesSelectorScreen(
Scaffold(
topBar = {
AppTopBar(
title = stringResource(R.string.select_patches),
title = stringResource(
R.string.patches_selected,
selectedPatchCount,
availablePatchCount
),
onBackClick = onBackClick,
actions = {
IconButton(onClick = vm::reset) {
@ -290,8 +301,14 @@ fun PatchesSelectorScreen(
ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.save)) },
icon = { Icon(Icons.Outlined.Save, null) },
expanded = patchLazyListStates.getOrNull(pagerState.currentPage)?.isScrollingUp ?: true,
icon = {
Icon(
Icons.Outlined.Save,
stringResource(R.string.save)
)
},
expanded = patchLazyListStates.getOrNull(pagerState.currentPage)?.isScrollingUp
?: true,
onClick = {
// TODO: only allow this if all required options have been set.
onSave(vm.getCustomSelection(), vm.getOptions())
@ -363,7 +380,7 @@ fun PatchesSelectorScreen(
) {
ListHeader(
title = stringResource(R.string.unsupported_patches),
onHelpClick = { vm.openUnsupportedDialog(bundle.unsupported) }
onHelpClick = { showUnsupportedPatchesDialog = true }
)
}
}
@ -374,7 +391,7 @@ fun PatchesSelectorScreen(
}
@Composable
fun SelectionWarningDialog(onDismiss: () -> Unit) {
private fun SelectionWarningDialog(onDismiss: () -> Unit) {
SafeguardDialog(
onDismiss = onDismiss,
title = R.string.warning,
@ -383,7 +400,7 @@ fun SelectionWarningDialog(onDismiss: () -> Unit) {
}
@Composable
fun UniversalPatchWarningDialog(
private fun UniversalPatchWarningDialog(
onCancel: () -> Unit,
onConfirm: () -> Unit
) {
@ -415,7 +432,7 @@ fun UniversalPatchWarningDialog(
}
@Composable
fun PatchItem(
private fun PatchItem(
patch: PatchInfo,
onOptionsDialog: () -> Unit,
selected: Boolean,
@ -424,7 +441,7 @@ fun PatchItem(
) = ListItem(
modifier = Modifier
.let { if (!supported) it.alpha(0.5f) else it }
.clickable(enabled = supported, onClick = onToggle)
.clickable(onClick = onToggle)
.fillMaxSize(),
leadingContent = {
Checkbox(
@ -446,7 +463,7 @@ fun PatchItem(
)
@Composable
fun ListHeader(
private fun ListHeader(
title: String,
onHelpClick: (() -> Unit)? = null
) {
@ -473,18 +490,46 @@ fun ListHeader(
}
@Composable
fun UnsupportedDialog(
private fun UnsupportedPatchesDialog(
appVersion: String,
supportedVersions: List<String>,
onDismissRequest: () -> Unit
) = AlertDialog(
icon = {
Icon(Icons.Outlined.WarningAmber, null)
},
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
Text(stringResource(R.string.ok))
}
},
title = { Text(stringResource(R.string.unsupported_app)) },
title = { Text(stringResource(R.string.unsupported_patches)) },
text = {
Text(
stringResource(
R.string.unsupported_patches_dialog,
appVersion
)
)
}
)
@Composable
private fun UnsupportedPatchDialog(
appVersion: String,
supportedVersions: List<String>,
onDismissRequest: () -> Unit
) = AlertDialog(
icon = {
Icon(Icons.Outlined.WarningAmber, null)
},
onDismissRequest = onDismissRequest,
confirmButton = {
TextButton(onClick = onDismissRequest) {
Text(stringResource(R.string.ok))
}
},
title = { Text(stringResource(R.string.unsupported_patch)) },
text = {
Text(
stringResource(
@ -498,7 +543,7 @@ fun UnsupportedDialog(
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun OptionsDialog(
private fun OptionsDialog(
patch: PatchInfo,
values: Map<String, Any?>?,
reset: () -> Unit,
@ -535,8 +580,14 @@ fun OptionsDialog(
if (values == null || !values.contains(key)) option.default else values[key]
@Suppress("UNCHECKED_CAST")
OptionItem(option = option as Option<Any>, value = value, setValue = { set(key, it) })
OptionItem(
option = option as Option<Any>,
value = value,
setValue = {
set(key, it)
}
)
}
}
}
}
}

View File

@ -69,11 +69,6 @@ fun SelectedAppInfoScreen(
patches.values.sumOf { it.size }
}
}
val availablePatchCount by remember {
derivedStateOf {
bundles.sumOf { it.patchCount }
}
}
val navController =
rememberNavController<SelectedAppInfoDestination>(startDestination = SelectedAppInfoDestination.Main)
@ -111,7 +106,6 @@ fun SelectedAppInfoScreen(
// navController.navigate(SelectedAppInfoDestination.VersionSelector)
},
onBackClick = onBackClick,
availablePatchCount = availablePatchCount,
selectedPatchCount = selectedPatchCount,
packageName = packageName,
version = version,
@ -145,7 +139,6 @@ private fun SelectedAppInfoScreen(
onPatchSelectorClick: () -> Unit,
onVersionSelectorClick: () -> Unit,
onBackClick: () -> Unit,
availablePatchCount: Int,
selectedPatchCount: Int,
packageName: String,
version: String?,
@ -161,7 +154,12 @@ private fun SelectedAppInfoScreen(
floatingActionButton = {
ExtendedFloatingActionButton(
text = { Text(stringResource(R.string.patch)) },
icon = { Icon(Icons.Default.AutoFixHigh, null) },
icon = {
Icon(
Icons.Default.AutoFixHigh,
stringResource(R.string.patch)
)
},
onClick = onPatchClick
)
}
@ -173,13 +171,7 @@ private fun SelectedAppInfoScreen(
) {
AppInfo(packageInfo, placeholderLabel = packageName) {
Text(
version?.let {
stringResource(
R.string.selected_app_meta_version,
it,
availablePatchCount
)
} ?: stringResource(R.string.selected_app_meta_no_version, availablePatchCount),
version ?: stringResource(R.string.selected_app_meta_any_version),
color = MaterialTheme.colorScheme.onSurfaceVariant,
style = MaterialTheme.typography.bodyMedium,
)

View File

@ -5,26 +5,13 @@ import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Api
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
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.remember
import androidx.compose.material.icons.outlined.Restore
import androidx.compose.material3.*
import androidx.compose.runtime.*
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.stringResource
@ -78,14 +65,18 @@ fun AdvancedSettingsScreen(
var showApiUrlDialog by rememberSaveable { mutableStateOf(false) }
if (showApiUrlDialog) {
APIUrlDialog(apiUrl) {
showApiUrlDialog = false
it?.let(vm::setApiUrl)
}
APIUrlDialog(
currentUrl = apiUrl,
defaultUrl = vm.prefs.api.default,
onSubmit = {
showApiUrlDialog = false
it?.let(vm::setApiUrl)
}
)
}
SettingsListItem(
headlineContent = stringResource(R.string.api_url),
supportingContent = apiUrl,
supportingContent = stringResource(R.string.api_url_description),
modifier = Modifier.clickable {
showApiUrlDialog = true
}
@ -163,7 +154,7 @@ fun AdvancedSettingsScreen(
}
@Composable
private fun APIUrlDialog(currentUrl: String, onSubmit: (String?) -> Unit) {
private fun APIUrlDialog(currentUrl: String, defaultUrl: String, onSubmit: (String?) -> Unit) {
var url by rememberSaveable(currentUrl) { mutableStateOf(currentUrl) }
AlertDialog(
@ -207,9 +198,15 @@ private fun APIUrlDialog(currentUrl: String, onSubmit: (String?) -> Unit) {
color = MaterialTheme.colorScheme.error
)
OutlinedTextField(
modifier = Modifier.fillMaxWidth(),
value = url,
onValueChange = { url = it },
label = { Text(stringResource(R.string.api_url)) }
label = { Text(stringResource(R.string.api_url)) },
trailingIcon = {
IconButton(onClick = { url = defaultUrl }) {
Icon(Icons.Outlined.Restore, stringResource(R.string.api_url_dialog_reset))
}
}
)
}
}

View File

@ -64,6 +64,12 @@ fun UpdatesSettingsScreen(
headline = R.string.update_checking_manager,
description = R.string.update_checking_manager_description
)
BooleanItem(
preference = vm.showManagerUpdateDialogOnLaunch,
headline = R.string.show_manager_update_dialog_on_launch,
description = R.string.update_checking_manager_description
)
}
}
}

View File

@ -29,7 +29,7 @@ class AppSelectorViewModel(
private val pm: PM,
private val patchBundleRepository: PatchBundleRepository
) : ViewModel() {
private val inputFile = File(app.cacheDir, "input.apk").also {
private val inputFile = File(app.filesDir, "input.apk").also {
it.delete()
}
val appList = pm.appList

View File

@ -72,6 +72,12 @@ class DashboardViewModel(
}
}
fun setShowManagerUpdateDialogOnLaunch(value: Boolean) {
viewModelScope.launch {
prefs.showManagerUpdateDialogOnLaunch.update(value)
}
}
fun applyAutoUpdatePrefs(manager: Boolean, patches: Boolean) = viewModelScope.launch {
prefs.firstLaunch.update(false)

View File

@ -218,26 +218,14 @@ class PatcherViewModel(
app.unregisterReceiver(installBroadcastReceiver)
workManager.cancelWorkById(patcherWorkerId)
when (val selectedApp = input.selectedApp) {
is SelectedApp.Local -> {
if (selectedApp.temporary) selectedApp.file.delete()
}
is SelectedApp.Installed -> {
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)
}
}
}
if (input.selectedApp is SelectedApp.Installed && installedApp?.installType == InstallType.ROOT) {
GlobalScope.launch(Dispatchers.Main) {
uiSafe(app, R.string.failed_to_mount, "Failed to mount") {
withTimeout(Duration.ofMinutes(1L)) {
rootInstaller.mount(packageName)
}
}
}
else -> Unit
}
tempDir.deleteRecursively()

View File

@ -36,6 +36,7 @@ import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import kotlinx.collections.immutable.*
import kotlinx.coroutines.flow.map
@Stable
@OptIn(SavedStateHandleSaveableApi::class)
@ -77,7 +78,7 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
}
private var hasModifiedSelection = false
private var customPatchSelection: PersistentPatchSelection? by savedStateHandle.saveable(
var customPatchSelection: PersistentPatchSelection? by savedStateHandle.saveable(
key = "selection",
stateSaver = selectionSaver,
) {
@ -100,15 +101,16 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
val compatibleVersions = mutableStateListOf<String>()
var filter by mutableIntStateOf(SHOW_SUPPORTED or SHOW_UNIVERSAL or SHOW_UNSUPPORTED)
var filter by mutableIntStateOf(0)
private set
private suspend fun generateDefaultSelection(): PersistentPatchSelection {
val bundles = bundlesFlow.first()
val generatedSelection =
bundles.toPatchSelection(allowIncompatiblePatches) { _, patch -> patch.include }
private val defaultPatchSelection = bundlesFlow.map { bundles ->
bundles.toPatchSelection(allowIncompatiblePatches) { _, patch -> patch.include }
.toPersistentPatchSelection()
}
return generatedSelection.toPersistentPatchSelection()
val defaultSelectionCount = defaultPatchSelection.map { selection ->
selection.values.sumOf { it.size }
}
fun selectionIsValid(bundles: List<BundleInfo>) = bundles.any { bundle ->
@ -124,7 +126,7 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
fun togglePatch(bundle: Int, patch: PatchInfo) = viewModelScope.launch {
hasModifiedSelection = true
val selection = customPatchSelection ?: generateDefaultSelection()
val selection = customPatchSelection ?: defaultPatchSelection.first()
val newPatches = selection[bundle]?.let { patches ->
if (patch.name in patches)
patches.remove(patch.name)
@ -188,10 +190,8 @@ class PatchesSelectorViewModel(input: Params) : ViewModel(), KoinComponent {
compatibleVersions.clear()
}
fun openUnsupportedDialog(unsupportedPatches: List<PatchInfo>) {
compatibleVersions.addAll(unsupportedPatches.flatMap { patch ->
patch.compatiblePackages?.find { it.packageName == packageName }?.versions.orEmpty()
})
fun openUnsupportedDialog(unsupportedPatch: PatchInfo) {
compatibleVersions.addAll(unsupportedPatch.compatiblePackages?.find { it.packageName == packageName }?.versions.orEmpty())
}
fun toggleFlag(flag: Int) {

View File

@ -1,6 +1,5 @@
package app.revanced.manager.ui.viewmodel
import android.app.Application
import android.content.pm.PackageInfo
import android.os.Parcelable
import androidx.compose.runtime.MutableState
@ -38,7 +37,6 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
private val optionsRepository: PatchOptionsRepository = get()
private val pm: PM = get()
private val savedStateHandle: SavedStateHandle = get()
private val app: Application = get()
val prefs: PreferencesManager = get()
private val persistConfiguration = input.patches == null
@ -82,20 +80,17 @@ class SelectedAppInfoViewModel(input: Params) : ViewModel(), KoinComponent {
private set
private var selectionState by savedStateHandle.saveable {
if (input.patches != null) {
if (input.patches != null)
return@saveable mutableStateOf(SelectionState.Customized(input.patches))
}
val selection: MutableState<SelectionState> = mutableStateOf(SelectionState.Default)
// Get previous selection (if present).
// Try to get the previous selection if customization is enabled.
viewModelScope.launch {
if (!prefs.disableSelectionWarning.get()) return@launch
val previous = selectionRepository.getSelection(selectedApp.packageName)
if (previous.values.sumOf { it.size } == 0) {
return@launch
}
if (previous.values.sumOf { it.size } == 0) return@launch
selection.value = SelectionState.Customized(previous)
}

View File

@ -14,6 +14,7 @@ class UpdatesSettingsViewModel(
private val reVancedAPI: ReVancedAPI,
) : ViewModel() {
val managerAutoUpdates = prefs.managerAutoUpdates
val showManagerUpdateDialogOnLaunch = prefs.showManagerUpdateDialogOnLaunch
suspend fun checkForUpdates(): Boolean {
uiSafe(app, R.string.failed_to_check_updates, "Failed to check for updates") {

Binary file not shown.

View File

@ -12,10 +12,10 @@
<string name="dashboard">Dashboard</string>
<string name="settings">Settings</string>
<string name="select_app">Select an app</string>
<string name="select_patches">Select patches</string>
<string name="patches_selected">%1$d/%2$d selected</string>
<string name="unsupported_architecture_warning">Patching on ARMv7 devices is not yet supported and will most likely fail.</string>
<string name="new_downloader_plugins_notification">New downloader plugins available. Click here to configure them.</string>
<string name="unsupported_architecture_warning">Patching on this device architecture is unsupported and will most likely fail.</string>
<string name="import_">Import</string>
<string name="import_bundle">Import patch bundle</string>
@ -34,8 +34,7 @@
<string name="bundle_name_default">Default</string>
<string name="bundle_name_fallback">Unnamed</string>
<string name="selected_app_meta_version">%1$s • %2$d available patches</string>
<string name="selected_app_meta_no_version">%d available patches</string>
<string name="selected_app_meta_any_version">Any available version</string>
<string name="patch_item_description">Start patching the application</string>
<string name="patch_selector_item">Patch selection and options</string>
@ -166,10 +165,12 @@
<string name="debug_logs_export_failed">Failed to export logs</string>
<string name="debug_logs_export_success">Exported logs</string>
<string name="api_url">API URL</string>
<string name="api_url_description">The API used to download necessary files.</string>
<string name="api_url_dialog_title">Set custom API URL</string>
<string name="api_url_dialog_description">You may have issues with features when using a custom API URL.</string>
<string name="api_url_dialog_warning">Only use API\'s you trust!</string>
<string name="api_url_dialog_description">Set the API URL of ReVanced Manager. ReVanced Manager uses the API to download patches and updates.</string>
<string name="api_url_dialog_warning">ReVanced Manager connects to the API to download patches and updates. Make sure that you trust it.</string>
<string name="api_url_dialog_save">Set</string>
<string name="api_url_dialog_reset">Reset API URL</string>
<string name="device">Device</string>
<string name="device_android_version">Android version</string>
<string name="device_model">Model</string>
@ -199,7 +200,6 @@
<string name="no_patched_apps_found">No patched apps found</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>
<string name="unsupported_patches">Unsupported patches</string>
<string name="universal_patches">Universal patches</string>
<string name="patch_selection_reset_toast">Patch selection and options has been reset to recommended defaults</string>
@ -213,7 +213,7 @@
<string name="universal">Universal</string>
<string name="unsupported">Unsupported</string>
<string name="search_patches">Patch name</string>
<string name="app_not_supported">Some of the patches do not support this app version (%1$s). The patches only support the following version(s): %2$s.</string>
<string name="app_not_supported">This patch is not compatible with the selected app version (%1$s).\n\nIt only supports the following version(s): %2$s.</string>
<string name="continue_with_version">Continue with this version?</string>
<string name="version_not_supported">Not all patches support this version (%s). Do you want to continue anyway?</string>
<string name="download_application">Download application?</string>
@ -333,9 +333,9 @@
<string name="install_update_manager_failed">Failed to install update</string>
<string name="manual_update_check">Check for updates</string>
<string name="manual_update_check_description">Manually check for updates</string>
<string name="update_checking_manager">Update checking</string>
<string name="update_checking_manager">Auto check for updates</string>
<string name="update_checking_manager_description">Check for new versions of ReVanced Manager when the application starts</string>
<string name="changelog">Changelog</string>
<string name="changelog">View changelogs</string>
<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>
@ -382,4 +382,9 @@
<string name="add_patch_bundle">Add patch bundle</string>
<string name="bundle_url">Bundle URL</string>
<string name="auto_update">Auto update</string>
<string name="unsupported_patches_dialog">These patches are not compatible with the selected app version (%1$s).\n\nClick on the patches to see more details.</string>
<string name="unsupported_patch">Unsupported patch</string>
<string name="never_show_again">Never show again</string>
<string name="show_manager_update_dialog_on_launch">Show update message on launch</string>
<string name="show_manager_update_dialog_on_launch_description">Shows a popup notification whenever there is a new update available on launch.</string>
</resources>

View File

@ -5,9 +5,6 @@ In order to use ReVanced Manager, certain requirements must be met.
## 🤝 Requirements
- An Android device running Android 8 or higher
- Any device architecture except ARMv7[^1]
[^1]: This constraint only applies to patches, that require patching APK resources which is why some patches may or may not work on ARMv7 architecture. You can find out, which architectures your device supports here: [⚙️ Configuring ReVanced Manager](2_4_settings.md#%E2%84%B9%EF%B8%8F-about).
## ⏭️ What's next

View File

@ -21,4 +21,6 @@ kotlin.code.style=official
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
android.nonFinalResIds=false
android.nonFinalResIds=false
org.gradle.configuration-cache=true
org.gradle.caching=true