diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index f912fedbb9..4ca4a56580 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -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: diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 9574e59b27..c057a644ca 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -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: diff --git a/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json b/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json index ce36924a5f..c339f1c4d3 100644 --- a/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json +++ b/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json @@ -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')" ] } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/data/platform/Filesystem.kt b/app/src/main/java/app/revanced/manager/data/platform/Filesystem.kt index ec01f09ba8..3afbe6e8e5 100644 --- a/app/src/main/java/app/revanced/manager/data/platform/Filesystem.kt +++ b/app/src/main/java/app/revanced/manager/data/platform/Filesystem.kt @@ -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() } diff --git a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt index e9869de9f4..d120abf5b9 100644 --- a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt +++ b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleEntity.kt @@ -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, diff --git a/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt b/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt index 87127e4208..5b8bed3baf 100644 --- a/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt +++ b/app/src/main/java/app/revanced/manager/domain/manager/PreferencesManager.kt @@ -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) diff --git a/app/src/main/java/app/revanced/manager/domain/manager/base/BasePreferencesManager.kt b/app/src/main/java/app/revanced/manager/domain/manager/base/BasePreferencesManager.kt index e78d26a97d..06f75465d3 100644 --- a/app/src/main/java/app/revanced/manager/domain/manager/base/BasePreferencesManager.kt +++ b/app/src/main/java/app/revanced/manager/domain/manager/base/BasePreferencesManager.kt @@ -63,7 +63,7 @@ class EditorContext(private val prefs: MutablePreferences) { abstract class Preference( private val dataStore: DataStore, - protected val default: T + val default: T ) { internal abstract fun Preferences.read(): T internal abstract fun MutablePreferences.write(value: T) diff --git a/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt b/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt index f81ba2f406..406c9e9d1f 100644 --- a/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt +++ b/app/src/main/java/app/revanced/manager/patcher/aapt/Aapt.kt @@ -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() diff --git a/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt b/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt index 2c836f6077..729e4381f1 100644 --- a/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt +++ b/app/src/main/java/app/revanced/manager/patcher/worker/PatcherWorker.kt @@ -234,6 +234,9 @@ class PatcherWorker( Result.failure() } finally { patchedApk.delete() + if (args.input is SelectedApp.Local && args.input.temporary) { + args.input.file.delete() + } } } diff --git a/app/src/main/java/app/revanced/manager/ui/component/AlertDialogExtended.kt b/app/src/main/java/app/revanced/manager/ui/component/AlertDialogExtended.kt index cceb189f32..c2089d5811 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/AlertDialogExtended.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/AlertDialogExtended.kt @@ -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) { diff --git a/app/src/main/java/app/revanced/manager/ui/component/AppIcon.kt b/app/src/main/java/app/revanced/manager/ui/component/AppIcon.kt index a308f17432..0d8cd822a0 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/AppIcon.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/AppIcon.kt @@ -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, diff --git a/app/src/main/java/app/revanced/manager/ui/component/ArrowButton.kt b/app/src/main/java/app/revanced/manager/ui/component/ArrowButton.kt index d97749c035..aed7e0c7db 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/ArrowButton.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/ArrowButton.kt @@ -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" ) diff --git a/app/src/main/java/app/revanced/manager/ui/component/AvailableUpdateDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/AvailableUpdateDialog.kt new file mode 100644 index 0000000000..7059ad0da9 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/component/AvailableUpdateDialog.kt @@ -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) + ) +} diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt index f2bb3de5c3..2b67860315 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BaseBundleDialog.kt @@ -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) { diff --git a/app/src/main/java/app/revanced/manager/ui/component/patches/OptionFields.kt b/app/src/main/java/app/revanced/manager/ui/component/patches/OptionFields.kt index 42a8119a94..25254d9544 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/patches/OptionFields.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/patches/OptionFields.kt @@ -572,7 +572,12 @@ private class ListOptionEditor(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)) } ) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt index 933f2f3563..3c7ac5220e 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/DashboardScreen.kt @@ -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( diff --git a/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt index 681344bc4b..07ada19cf0 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/PatchesSelectorScreen.kt @@ -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, + 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_patches)) }, + text = { + Text( + stringResource( + R.string.unsupported_patches_dialog, + appVersion + ) + ) + } +) + +@Composable +private fun UnsupportedPatchDialog( appVersion: String, supportedVersions: List, 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_patch)) }, text = { Text( stringResource( @@ -498,7 +543,7 @@ fun UnsupportedDialog( @OptIn(ExperimentalMaterial3Api::class) @Composable -fun OptionsDialog( +private fun OptionsDialog( patch: PatchInfo, values: Map?, 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, value = value, setValue = { set(key, it) }) + OptionItem( + option = option as Option, + value = value, + setValue = { + set(key, it) + } + ) } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt index ac86733502..da3e02eed1 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/SelectedAppInfoScreen.kt @@ -69,11 +69,6 @@ fun SelectedAppInfoScreen( patches.values.sumOf { it.size } } } - val availablePatchCount by remember { - derivedStateOf { - bundles.sumOf { it.patchCount } - } - } val navController = rememberNavController(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, ) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt index 997d5284e6..04a104fffc 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/AdvancedSettingsScreen.kt @@ -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)) + } + } ) } } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt index 96aa2cd2ef..3772f3f4ff 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt @@ -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 + ) } } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt index 3f810f6344..7b91c4d370 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/AppSelectorViewModel.kt @@ -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 diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt index 1d3e96a58e..994897c018 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/DashboardViewModel.kt @@ -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) diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt index 83adb9b409..e01bd640cd 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatcherViewModel.kt @@ -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() diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt index 331548d6b3..b2986fbba2 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/PatchesSelectorViewModel.kt @@ -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() - 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) = 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) { - 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) { diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt index 697e0ecbb0..ee6c5c8722 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/SelectedAppInfoViewModel.kt @@ -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 = mutableStateOf(SelectionState.Default) - // Get previous selection (if present). + // Try to get the previous selection if customization is enabled. viewModelScope.launch { - val previous = selectionRepository.getSelection(selectedApp.packageName) - - if (previous.values.sumOf { it.size } == 0) { - return@launch - } + if (!prefs.disableSelectionWarning.get()) return@launch + val previous = selectionRepository.getSelection(selectedApp.packageName) + if (previous.values.sumOf { it.size } == 0) return@launch selection.value = SelectionState.Customized(previous) } diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdatesSettingsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdatesSettingsViewModel.kt index cd96e091ce..385aeddf52 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdatesSettingsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/UpdatesSettingsViewModel.kt @@ -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") { diff --git a/app/src/main/jniLibs/armeabi-v7a/libaapt2.so b/app/src/main/jniLibs/armeabi-v7a/libaapt2.so new file mode 100644 index 0000000000..46bdce5ce7 Binary files /dev/null and b/app/src/main/jniLibs/armeabi-v7a/libaapt2.so differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e8af7a0338..774ecd504b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,10 +12,10 @@ Dashboard Settings Select an app - Select patches + %1$d/%2$d selected - Patching on ARMv7 devices is not yet supported and will most likely fail. New downloader plugins available. Click here to configure them. + Patching on this device architecture is unsupported and will most likely fail. Import Import patch bundle @@ -34,8 +34,7 @@ Default Unnamed - %1$s • %2$d available patches - %d available patches + Any available version Start patching the application Patch selection and options @@ -166,10 +165,12 @@ Failed to export logs Exported logs API URL + The API used to download necessary files. Set custom API URL - You may have issues with features when using a custom API URL. - Only use API\'s you trust! + Set the API URL of ReVanced Manager. ReVanced Manager uses the API to download patches and updates. + ReVanced Manager connects to the API to download patches and updates. Make sure that you trust it. Set + Reset API URL Device Android version Model @@ -199,7 +200,6 @@ No patched apps found Tap on the patches to get more information about them %s selected - Unsupported app Unsupported patches Universal patches Patch selection and options has been reset to recommended defaults @@ -213,7 +213,7 @@ Universal Unsupported Patch name - Some of the patches do not support this app version (%1$s). The patches only support the following version(s): %2$s. + This patch is not compatible with the selected app version (%1$s).\n\nIt only supports the following version(s): %2$s. Continue with this version? Not all patches support this version (%s). Do you want to continue anyway? Download application? @@ -333,9 +333,9 @@ Failed to install update Check for updates Manually check for updates - Update checking + Auto check for updates Check for new versions of ReVanced Manager when the application starts - Changelog + View changelogs Loading changelog Failed to download changelog: %s Check out the latest changes in this update @@ -382,4 +382,9 @@ Add patch bundle Bundle URL Auto update + These patches are not compatible with the selected app version (%1$s).\n\nClick on the patches to see more details. + Unsupported patch + Never show again + Show update message on launch + Shows a popup notification whenever there is a new update available on launch. diff --git a/docs/0_prerequisites.md b/docs/0_prerequisites.md index a53b46fb9c..9d86cd5438 100644 --- a/docs/0_prerequisites.md +++ b/docs/0_prerequisites.md @@ -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 diff --git a/gradle.properties b/gradle.properties index f19c7b9b29..36d069afc2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 \ No newline at end of file +android.nonFinalResIds=false +org.gradle.configuration-cache=true +org.gradle.caching=true