From 747017a5f9ca13110271bbd65a9fd4e14257fc87 Mon Sep 17 00:00:00 2001 From: Pun Butrach Date: Thu, 15 Aug 2024 19:59:06 +0700 Subject: [PATCH 01/10] feat: Improve Settings order (#2060) Co-authored-by: oSumAtrIX Co-authored-by: Ax333l --- .../manager/ui/screen/SettingsScreen.kt | 2 +- .../screen/settings/AdvancedSettingsScreen.kt | 57 +++++++++++++------ .../ui/screen/settings/ContributorScreen.kt | 15 ++++- .../screen/settings/DeveloperOptionsScreen.kt | 2 +- .../screen/settings/GeneralSettingsScreen.kt | 16 +++++- .../settings/ImportExportSettingsScreen.kt | 9 ++- .../ui/screen/settings/LicensesScreen.kt | 7 ++- app/src/main/res/values/strings.xml | 24 ++++---- 8 files changed, 93 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt index f43ecb4b..0d0bd46e 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/SettingsScreen.kt @@ -66,7 +66,7 @@ fun SettingsScreen( ) to SettingsDestination.Advanced, Triple( R.string.about, - R.string.about_description, + R.string.app_name, Icons.Outlined.Info ) to SettingsDestination.About, ) 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 04a104ff..fa8cae28 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 @@ -1,10 +1,14 @@ package app.revanced.manager.ui.screen.settings import android.app.ActivityManager +import android.content.ClipData +import android.content.ClipboardManager import android.os.Build import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.clickable +import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Api @@ -13,7 +17,9 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier +import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -28,9 +34,10 @@ import app.revanced.manager.ui.component.settings.BooleanItem import app.revanced.manager.ui.component.settings.IntegerItem import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.viewmodel.AdvancedSettingsViewModel +import app.revanced.manager.util.toast import org.koin.androidx.compose.koinViewModel -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @Composable fun AdvancedSettingsScreen( onBackClick: () -> Unit, @@ -45,6 +52,7 @@ fun AdvancedSettingsScreen( activityManager.largeMemoryClass ) } + val haptics = LocalHapticFeedback.current Scaffold( topBar = { @@ -82,15 +90,6 @@ fun AdvancedSettingsScreen( } ) - val exportDebugLogsLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { - it?.let(vm::exportDebugLogs) - } - SettingsListItem( - headlineContent = stringResource(R.string.debug_logs_export), - modifier = Modifier.clickable { exportDebugLogsLauncher.launch(vm.debugLogFileName) } - ) - GroupHeader(stringResource(R.string.patcher)) BooleanItem( preference = vm.prefs.useProcessRuntime, @@ -138,16 +137,38 @@ fun AdvancedSettingsScreen( ) GroupHeader(stringResource(R.string.debugging)) + val exportDebugLogsLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/plain")) { + it?.let(vm::exportDebugLogs) + } SettingsListItem( - headlineContent = stringResource(R.string.about_device), - supportingContent = """ - **Version**: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) - **Build type**: ${BuildConfig.BUILD_TYPE} - **Model**: ${Build.MODEL} - **Android version**: ${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT}) - **Supported Archs**: ${Build.SUPPORTED_ABIS.joinToString(", ")} - **Memory limit**: $memoryLimit + headlineContent = stringResource(R.string.debug_logs_export), + modifier = Modifier.clickable { exportDebugLogsLauncher.launch(vm.debugLogFileName) } + ) + val clipboard = remember { context.getSystemService()!! } + val deviceContent = """ + Version: ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE}) + Build type: ${BuildConfig.BUILD_TYPE} + Model: ${Build.MODEL} + Android version: ${Build.VERSION.RELEASE} (${Build.VERSION.SDK_INT}) + Supported Archs: ${Build.SUPPORTED_ABIS.joinToString(", ")} + Memory limit: $memoryLimit """.trimIndent() + SettingsListItem( + modifier = Modifier.combinedClickable( + onClick = { }, + onLongClickLabel = stringResource(R.string.copy_to_clipboard), + onLongClick = { + haptics.performHapticFeedback(HapticFeedbackType.LongPress) + clipboard.setPrimaryClip( + ClipData.newPlainText("Device Information", deviceContent) + ) + + context.toast(context.getString(R.string.toast_copied_to_clipboard)) + } + ), + headlineContent = stringResource(R.string.about_device), + supportingContent = deviceContent ) } } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/ContributorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/ContributorScreen.kt index d4995aad..0b3a7888 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/ContributorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/ContributorScreen.kt @@ -2,7 +2,20 @@ package app.revanced.manager.ui.screen.settings import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.border -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.items import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/DeveloperOptionsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/DeveloperOptionsScreen.kt index f8f1e0cf..1789329e 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/DeveloperOptionsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/DeveloperOptionsScreen.kt @@ -32,7 +32,7 @@ fun DeveloperOptionsScreen( Column(modifier = Modifier.padding(paddingValues)) { GroupHeader(stringResource(R.string.patch_bundles_section)) SettingsListItem( - headlineContent = stringResource(R.string.patch_bundles_redownload), + headlineContent = stringResource(R.string.patch_bundles_force_download), modifier = Modifier.clickable(onClick = vm::redownloadBundles) ) SettingsListItem( diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt index f41e6a66..03eae09f 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt @@ -2,8 +2,18 @@ package app.revanced.manager.ui.screen.settings import android.os.Build import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.material3.* +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.material3.AlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.FilledTonalButton +import androidx.compose.material3.RadioButton +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 @@ -96,7 +106,7 @@ private fun ThemePicker( title = { Text(stringResource(R.string.theme)) }, text = { Column { - Theme.values().forEach { + Theme.entries.forEach { Row( modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/ImportExportSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/ImportExportSettingsScreen.kt index 1f15f469..84a96694 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/ImportExportSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/ImportExportSettingsScreen.kt @@ -250,12 +250,17 @@ private fun PackageSelector(packages: Set, onFinish: (String?) -> Unit) } @Composable -private fun GroupItem(onClick: () -> Unit, @StringRes headline: Int, @StringRes description: Int) = +private fun GroupItem( + onClick: () -> Unit, + @StringRes headline: Int, + @StringRes description: Int? = null +) { SettingsListItem( modifier = Modifier.clickable { onClick() }, headlineContent = stringResource(headline), - supportingContent = stringResource(description) + supportingContent = description?.let { stringResource(it) } ) +} @Composable fun KeystoreCredentialsDialog( diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/LicensesScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/LicensesScreen.kt index 3a9bb824..76e2e964 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/LicensesScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/LicensesScreen.kt @@ -1,8 +1,11 @@ package app.revanced.manager.ui.screen.settings -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.* +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1270e091..828c726d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,9 @@ CLI Manager + Copied! + Copy to clipboard + Dashboard Settings Select an app @@ -49,19 +52,18 @@ These settings can be changed later. General - General settings - Advanced - Advanced settings + Theme, dynamic color Updates - Updates for ReVanced Manager + Check for updates and view changelogs + Downloads + Downloader plugins and downloaded apps + Import & export + Keystore, patch options and selection + Advanced + API URL, memory limit, debugging + About Open source licenses View all the libraries used to make this application - Downloads - Manage downloaded content - Import & export - Import and export settings - About - About ReVanced Contributors View the contributors of ReVanced @@ -169,7 +171,7 @@ Memory limits %1$dMB (Normal) - %2$dMB (Large) Patch bundles - Redownload all patch bundles + Force download all patch bundles Reset patch bundles Patching Signing From edb4e421e26bdaadc9795066185a0edf45a5d224 Mon Sep 17 00:00:00 2001 From: Ushie Date: Sat, 17 Aug 2024 01:58:43 +0300 Subject: [PATCH 02/10] feat: Improve patch bundle screen (#2070) --- .../ui/component/bundle/BaseBundleDialog.kt | 197 +++++++++--------- .../bundle/BundleInformationDialog.kt | 16 +- app/src/main/res/values/plurals.xml | 4 - app/src/main/res/values/strings.xml | 7 +- 4 files changed, 103 insertions(+), 121 deletions(-) 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 2b678603..4450ef5c 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 @@ -2,37 +2,33 @@ package app.revanced.manager.ui.component.bundle import android.webkit.URLUtil import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowRight -import androidx.compose.material3.FilledTonalButton -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Switch -import androidx.compose.material3.Text +import androidx.compose.material.icons.outlined.Extension +import androidx.compose.material.icons.outlined.Inventory2 +import androidx.compose.material.icons.outlined.Sell +import androidx.compose.material3.* 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.platform.LocalContext -import androidx.compose.ui.res.pluralStringResource +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import app.revanced.manager.R import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.TextInputDialog -import app.revanced.manager.util.isDebuggable @Composable fun BaseBundleDialog( modifier: Modifier = Modifier, isDefault: Boolean, name: String?, - onNameChange: ((String) -> Unit)? = null, remoteUrl: String?, onRemoteUrlChange: ((String) -> Unit)? = null, patchCount: Int, @@ -40,39 +36,66 @@ fun BaseBundleDialog( autoUpdate: Boolean, onAutoUpdateChange: (Boolean) -> Unit, onPatchesClick: () -> Unit, - onBundleTypeClick: () -> Unit = {}, extraFields: @Composable ColumnScope.() -> Unit = {} ) { ColumnWithScrollbar( modifier = Modifier .fillMaxWidth() - .then(modifier) + .then(modifier), ) { - if (name != null) { - var showNameInputDialog by rememberSaveable { - mutableStateOf(false) - } - if (showNameInputDialog) { - TextInputDialog( - initial = name, - title = stringResource(R.string.bundle_input_name), - onDismissRequest = { - showNameInputDialog = false - }, - onConfirm = { - showNameInputDialog = false - onNameChange?.invoke(it) - }, - validator = { - it.length in 1..19 - } + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.Start), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = Icons.Outlined.Inventory2, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(32.dp) ) + name?.let { + Text( + text = it, + style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight(800)), + color = MaterialTheme.colorScheme.primary, + ) + } } + Row( + horizontalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(start = 2.dp) + ) { + version?.let { + Tag(Icons.Outlined.Sell, it) + } + Tag(Icons.Outlined.Extension, patchCount.toString()) + } + } + + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + color = MaterialTheme.colorScheme.outlineVariant + ) + + if (remoteUrl != null) { BundleListItem( - headlineText = stringResource(R.string.bundle_input_name), - supportingText = name.ifEmpty { stringResource(R.string.field_not_set) }, - modifier = Modifier.clickable(enabled = onNameChange != null) { - showNameInputDialog = true + headlineText = stringResource(R.string.bundle_auto_update), + supportingText = stringResource(R.string.bundle_auto_update_description), + trailingContent = { + Switch( + checked = autoUpdate, + onCheckedChange = onAutoUpdateChange + ) + }, + modifier = Modifier.clickable { + onAutoUpdateChange(!autoUpdate) } ) } @@ -99,81 +122,59 @@ fun BaseBundleDialog( } BundleListItem( - modifier = Modifier.clickable(enabled = onRemoteUrlChange != null) { - showUrlInputDialog = true - }, - headlineText = stringResource(R.string.bundle_input_source_url), - supportingText = url.ifEmpty { stringResource(R.string.field_not_set) } - ) - } - - extraFields() - - if (remoteUrl != null) { - BundleListItem( - headlineText = stringResource(R.string.bundle_auto_update), - supportingText = stringResource(R.string.bundle_auto_update_description), - trailingContent = { - Switch( - checked = autoUpdate, - onCheckedChange = onAutoUpdateChange - ) - }, - modifier = Modifier.clickable { - onAutoUpdateChange(!autoUpdate) - } - ) - } - - BundleListItem( - headlineText = stringResource(R.string.bundle_type), - supportingText = stringResource(R.string.bundle_type_description), - modifier = Modifier.clickable { - onBundleTypeClick() - } - ) { - FilledTonalButton( - onClick = onBundleTypeClick, - content = { - if (remoteUrl == null) { - Text(stringResource(R.string.local)) - } else { - Text(stringResource(R.string.remote)) + modifier = Modifier.clickable( + enabled = onRemoteUrlChange != null, + onClick = { + showUrlInputDialog = true } + ), + headlineText = stringResource(R.string.bundle_input_source_url), + supportingText = url.ifEmpty { + stringResource(R.string.field_not_set) } ) } - if (version != null || patchCount > 0) { - Text( - text = stringResource(R.string.information), - modifier = Modifier.padding( - horizontal = 16.dp, - vertical = 12.dp - ), - style = MaterialTheme.typography.labelLarge, - color = MaterialTheme.colorScheme.primary, - ) - } - - val patchesClickable = LocalContext.current.isDebuggable && patchCount > 0 + val patchesClickable = patchCount > 0 BundleListItem( headlineText = stringResource(R.string.patches), - supportingText = pluralStringResource(R.plurals.bundle_patches_available, patchCount, patchCount), - modifier = Modifier.clickable(enabled = patchesClickable, onClick = onPatchesClick) + supportingText = stringResource(R.string.bundle_view_patches), + modifier = Modifier.clickable( + enabled = patchesClickable, + onClick = onPatchesClick + ) ) { - if (patchesClickable) + if (patchesClickable) { Icon( Icons.AutoMirrored.Outlined.ArrowRight, stringResource(R.string.patches) ) + } } - version?.let { - BundleListItem( - headlineText = stringResource(R.string.version), - supportingText = it, - ) - } + extraFields() + } +} + +@Composable +private fun Tag( + icon: ImageVector, + text: String +) { + Row( + horizontalArrangement = Arrangement.spacedBy(6.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + imageVector = icon, + contentDescription = null, + modifier = Modifier.size(16.dp), + tint = MaterialTheme.colorScheme.outline, + ) + Text( + text, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.outline, + ) } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt index 9a9573a5..f5919ced 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt @@ -11,18 +11,9 @@ import androidx.compose.material.icons.automirrored.outlined.ArrowRight import androidx.compose.material.icons.outlined.DeleteOutline import androidx.compose.material.icons.outlined.Share import androidx.compose.material.icons.outlined.Update -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope +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,7 +69,7 @@ fun BundleInformationDialog( Scaffold( topBar = { BundleTopBar( - title = bundleName, + title = stringResource(R.string.patch_bundle_field), onBackClick = onDismissRequest, backIcon = { Icon( @@ -111,7 +102,6 @@ fun BundleInformationDialog( modifier = Modifier.padding(paddingValues), isDefault = bundle.isDefault, name = bundleName, - onNameChange = { composableScope.launch { bundle.setName(it) } }, remoteUrl = bundle.asRemoteOrNull?.endpoint, patchCount = patchCount, version = props?.versionInfo?.patches, diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml index 9bcfcc08..d0178073 100644 --- a/app/src/main/res/values/plurals.xml +++ b/app/src/main/res/values/plurals.xml @@ -11,8 +11,4 @@ %d selected - - %d patch available - %d patches available - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 828c726d..36e2e43e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -145,7 +145,6 @@ Close System Light - Information Dark Appearance Downloaded apps @@ -181,8 +180,6 @@ Patch bundles Delete Refresh - Remote - Local Continue anyways Download another version Download app @@ -302,14 +299,12 @@ Help us improve this application Developer options Options for debugging issues - Name Source URL Successfully updated %s No update available for %s Auto update Automatically update this bundle when ReVanced starts - Bundle type - Choose the type of bundle you want + View patches About ReVanced Manager ReVanced Manager is an application designed to work with ReVanced Patcher, which allows for long-lasting patches to be created for Android apps. The patching system is designed to automatically work with new versions of apps with minimal maintenance. An update is available From 10e7e4b39f0e05f2977f3f6ca446242670ff1c6a Mon Sep 17 00:00:00 2001 From: kitadai31 <90122968+kitadai31@users.noreply.github.com> Date: Sun, 18 Aug 2024 19:54:07 +0900 Subject: [PATCH 03/10] feat: Open the app-specific manage all files permission dialog (#2148) --- .../app/revanced/manager/util/RequestManageStorageContract.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/revanced/manager/util/RequestManageStorageContract.kt b/app/src/main/java/app/revanced/manager/util/RequestManageStorageContract.kt index 8d7b7ec3..67dce3d4 100644 --- a/app/src/main/java/app/revanced/manager/util/RequestManageStorageContract.kt +++ b/app/src/main/java/app/revanced/manager/util/RequestManageStorageContract.kt @@ -2,6 +2,7 @@ package app.revanced.manager.util import android.content.Context import android.content.Intent +import android.net.Uri import android.os.Build import android.os.Environment import android.provider.Settings @@ -10,7 +11,7 @@ import androidx.annotation.RequiresApi @RequiresApi(Build.VERSION_CODES.R) class RequestManageStorageContract(private val forceLaunch: Boolean = false) : ActivityResultContract() { - override fun createIntent(context: Context, input: String) = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) + override fun createIntent(context: Context, input: String) = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.fromParts("package", context.packageName, null)) override fun getSynchronousResult(context: Context, input: String): SynchronousResult? = if (!forceLaunch && Environment.isExternalStorageManager()) SynchronousResult(true) else null From 20554005652f050835940a6e7ff5b74848f57245 Mon Sep 17 00:00:00 2001 From: Ushie Date: Sun, 25 Aug 2024 01:23:13 +0300 Subject: [PATCH 04/10] feat: View bundle patches (#2065) --- .../component/bundle/BundlePatchesDialog.kt | 261 +++++++++++++++--- app/src/main/res/values/strings.xml | 4 + 2 files changed, 220 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundlePatchesDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundlePatchesDialog.kt index a4fbce81..99201949 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundlePatchesDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundlePatchesDialog.kt @@ -1,33 +1,35 @@ package app.revanced.manager.ui.component.bundle import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack -import androidx.compose.material.icons.outlined.Lightbulb -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.ListItem -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text +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.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow 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.bundles.PatchBundleSource +import app.revanced.manager.patcher.patch.PatchInfo +import app.revanced.manager.ui.component.ArrowButton import app.revanced.manager.ui.component.LazyColumnWithScrollbar -import app.revanced.manager.ui.component.NotificationCard @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -35,7 +37,8 @@ fun BundlePatchesDialog( onDismissRequest: () -> Unit, bundle: PatchBundleSource, ) { - var informationCardVisible by remember { mutableStateOf(true) } + var showAllVersions by rememberSaveable { mutableStateOf(false) } + var showOptions by rememberSaveable { mutableStateOf(false) } val state by bundle.state.collectAsStateWithLifecycle() Dialog( @@ -62,44 +65,212 @@ fun BundlePatchesDialog( LazyColumnWithScrollbar( modifier = Modifier .fillMaxWidth() - .padding(paddingValues) - .padding(16.dp) + .padding(paddingValues), + verticalArrangement = Arrangement.spacedBy(12.dp), + contentPadding = PaddingValues(16.dp) ) { - item { - AnimatedVisibility(visible = informationCardVisible) { - NotificationCard( - icon = Icons.Outlined.Lightbulb, - text = stringResource(R.string.tap_on_patches), - onDismiss = { informationCardVisible = false } - ) - } - } - state.patchBundleOrNull()?.let { bundle -> - items(bundle.patches.size) { bundleIndex -> - val patch = bundle.patches[bundleIndex] - ListItem( - headlineContent = { - Text( - text = patch.name, - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurface - ) - }, - supportingContent = { - patch.description?.let { - Text( - text = it, - style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant - ) - } - } + items(bundle.patches) { patch -> + PatchItem( + patch, + showAllVersions, + onExpandVersions = { showAllVersions = !showAllVersions }, + showOptions, + onExpandOptions = { showOptions = !showOptions } ) - HorizontalDivider() } } } } } } + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun PatchItem( + patch: PatchInfo, + expandVersions: Boolean, + onExpandVersions: () -> Unit, + expandOptions: Boolean, + onExpandOptions: () -> Unit +) { + ElevatedCard( + modifier = Modifier + .fillMaxWidth() + .then( + if (patch.options.isNullOrEmpty()) Modifier else Modifier + .clip(RoundedCornerShape(8.dp)) + .clickable(onClick = onExpandOptions), + ) + ) { + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(6.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Absolute.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = patch.name, + color = MaterialTheme.colorScheme.primary, + style = MaterialTheme.typography.titleLarge, + fontWeight = FontWeight.Bold + ) + + if (!patch.options.isNullOrEmpty()) { + ArrowButton(expanded = expandOptions, onClick = null) + } + } + patch.description?.let { + Text( + text = it, + style = MaterialTheme.typography.bodyMedium + ) + } + Column( + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + if (patch.compatiblePackages.isNullOrEmpty()) { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + PatchInfoChip( + text = "$PACKAGE_ICON ${stringResource(R.string.bundle_view_patches_any_package)}" + ) + PatchInfoChip( + text = "$VERSION_ICON ${stringResource(R.string.bundle_view_patches_any_version)}" + ) + } + } else { + patch.compatiblePackages.forEach { compatiblePackage -> + val packageName = compatiblePackage.packageName + val versions = compatiblePackage.versions.orEmpty().reversed() + + FlowRow( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + PatchInfoChip( + modifier = Modifier.align(Alignment.CenterVertically), + text = "$PACKAGE_ICON $packageName" + ) + + if (versions.isNotEmpty()) { + if (expandVersions) { + versions.forEach { version -> + PatchInfoChip( + modifier = Modifier.align(Alignment.CenterVertically), + text = "$VERSION_ICON $version" + ) + } + } else { + PatchInfoChip( + modifier = Modifier.align(Alignment.CenterVertically), + text = "$VERSION_ICON ${versions.first()}" + ) + } + if (versions.size > 1) { + PatchInfoChip( + onClick = onExpandVersions, + text = if (expandVersions) stringResource(R.string.less) else "+${versions.size - 1}" + ) + } + } + } + } + } + } + if (!patch.options.isNullOrEmpty()) { + AnimatedVisibility(visible = expandOptions) { + val options = patch.options + + Column { + options.forEachIndexed { i, option -> + OutlinedCard( + modifier = Modifier.fillMaxWidth(), + colors = CardColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onSurface, + disabledContainerColor = Color.Transparent, + disabledContentColor = MaterialTheme.colorScheme.onSurface + ), shape = when { + options.size == 1 -> RoundedCornerShape(8.dp) + i == 0 -> RoundedCornerShape(topStart = 8.dp, topEnd = 8.dp) + i == options.lastIndex -> RoundedCornerShape( + bottomStart = 8.dp, + bottomEnd = 8.dp + ) + + else -> RoundedCornerShape(0.dp) + } + ) { + Column( + modifier = Modifier.padding(12.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + text = option.title, + style = MaterialTheme.typography.titleMedium, + color = MaterialTheme.colorScheme.primary + ) + Text( + text = option.description, + style = MaterialTheme.typography.bodyMedium + ) + } + } + } + } + } + } + } + } +} + +@Composable +fun PatchInfoChip( + modifier: Modifier = Modifier, + onClick: (() -> Unit)? = null, + text: String +) { + val shape = RoundedCornerShape(8.0.dp) + val cardModifier = if (onClick != null) { + Modifier + .clip(shape) + .clickable(onClick = onClick) + } else { + Modifier + } + + OutlinedCard( + modifier = modifier.then(cardModifier), + colors = CardColors( + containerColor = Color.Transparent, + contentColor = MaterialTheme.colorScheme.onSurface, + disabledContainerColor = Color.Transparent, + disabledContentColor = MaterialTheme.colorScheme.onSurface + ), + shape = shape, + border = BorderStroke(1.dp, MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.20f)) + ) { + Row( + modifier = Modifier.padding(8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Text( + text, + overflow = TextOverflow.Ellipsis, + softWrap = false, + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.onSurfaceVariant + ) + } + } +} + +const val PACKAGE_ICON = "\uD83D\uDCE6" +const val VERSION_ICON = "\uD83C\uDFAF" \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 36e2e43e..81522032 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -287,6 +287,7 @@ reorder More + Less Continue Dismiss Do not show this again @@ -305,6 +306,9 @@ Auto update Automatically update this bundle when ReVanced starts View patches + Any version + Any package + About ReVanced Manager ReVanced Manager is an application designed to work with ReVanced Patcher, which allows for long-lasting patches to be created for Android apps. The patching system is designed to automatically work with new versions of apps with minimal maintenance. An update is available From d201bdc422fcceb09040398c87edbaf2e97c7296 Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Thu, 29 Aug 2024 20:28:13 +0400 Subject: [PATCH 05/10] feat: Add installer status dialog (#1473) Co-authored-by: Benjamin Halko Co-authored-by: Benjamin <73490201+BenjaminHalko@users.noreply.github.com> Co-authored-by: Ushie Co-authored-by: Ax333l --- app/build.gradle.kts | 4 + .../ui/component/InstallerStatusDialog.kt | 159 ++++++++++++++++++ .../manager/ui/screen/PatcherScreen.kt | 8 +- .../manager/ui/viewmodel/PatcherViewModel.kt | 120 +++++++++++-- .../main/java/app/revanced/manager/util/PM.kt | 5 +- app/src/main/res/values/strings.xml | 19 +++ gradle/libs.versions.toml | 5 + 7 files changed, 303 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/app/revanced/manager/ui/component/InstallerStatusDialog.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ee855884..70281945 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -191,6 +191,10 @@ dependencies { // Scrollbars implementation(libs.scrollbars) + // EnumUtil + implementation(libs.enumutil) + ksp(libs.enumutil.ksp) + // Reorderable lists implementation(libs.reorderable) diff --git a/app/src/main/java/app/revanced/manager/ui/component/InstallerStatusDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/InstallerStatusDialog.kt new file mode 100644 index 00000000..a31a813e --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/component/InstallerStatusDialog.kt @@ -0,0 +1,159 @@ +package app.revanced.manager.ui.component + +import android.content.pm.PackageInstaller +import androidx.annotation.RequiresApi +import androidx.annotation.StringRes +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material.icons.outlined.ErrorOutline +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.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import app.revanced.manager.R +import com.github.materiiapps.enumutil.FromValue + +private typealias InstallerStatusDialogButtonHandler = ((model: InstallerModel) -> Unit) +private typealias InstallerStatusDialogButton = @Composable (model: InstallerStatusDialogModel) -> Unit + +interface InstallerModel { + fun reinstall() + fun install() +} + +interface InstallerStatusDialogModel : InstallerModel { + var packageInstallerStatus: Int? +} + +@Composable +fun InstallerStatusDialog(model: InstallerStatusDialogModel) { + val dialogKind = remember { + DialogKind.fromValue(model.packageInstallerStatus!!) ?: DialogKind.FAILURE + } + + AlertDialog( + onDismissRequest = { + model.packageInstallerStatus = null + }, + confirmButton = { + dialogKind.confirmButton(model) + }, + dismissButton = { + dialogKind.dismissButton?.invoke(model) + }, + icon = { + Icon(dialogKind.icon, null) + }, + title = { + Text( + text = stringResource(dialogKind.title), + style = MaterialTheme.typography.headlineSmall.copy(textAlign = TextAlign.Center), + color = MaterialTheme.colorScheme.onSurface, + ) + }, + text = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + Text(stringResource(dialogKind.contentStringResId)) + } + } + ) +} + +private fun installerStatusDialogButton( + @StringRes buttonStringResId: Int, + buttonHandler: InstallerStatusDialogButtonHandler = { }, +): InstallerStatusDialogButton = { model -> + TextButton( + onClick = { + model.packageInstallerStatus = null + buttonHandler(model) + } + ) { + Text(stringResource(buttonStringResId)) + } +} + +@FromValue("flag") +enum class DialogKind( + val flag: Int, + val title: Int, + @StringRes val contentStringResId: Int, + val icon: ImageVector = Icons.Outlined.ErrorOutline, + val confirmButton: InstallerStatusDialogButton = installerStatusDialogButton(R.string.ok), + val dismissButton: InstallerStatusDialogButton? = null, +) { + FAILURE( + flag = PackageInstaller.STATUS_FAILURE, + title = R.string.installation_failed_dialog_title, + contentStringResId = R.string.installation_failed_description, + confirmButton = installerStatusDialogButton(R.string.install_app) { model -> + model.install() + } + ), + FAILURE_ABORTED( + flag = PackageInstaller.STATUS_FAILURE_ABORTED, + title = R.string.installation_cancelled_dialog_title, + contentStringResId = R.string.installation_aborted_description, + confirmButton = installerStatusDialogButton(R.string.install_app) { model -> + model.install() + } + ), + FAILURE_BLOCKED( + flag = PackageInstaller.STATUS_FAILURE_BLOCKED, + title = R.string.installation_blocked_dialog_title, + contentStringResId = R.string.installation_blocked_description, + ), + FAILURE_CONFLICT( + flag = PackageInstaller.STATUS_FAILURE_CONFLICT, + title = R.string.installation_conflict_dialog_title, + contentStringResId = R.string.installation_conflict_description, + confirmButton = installerStatusDialogButton(R.string.reinstall) { model -> + model.reinstall() + }, + dismissButton = installerStatusDialogButton(R.string.cancel), + ), + FAILURE_INCOMPATIBLE( + flag = PackageInstaller.STATUS_FAILURE_INCOMPATIBLE, + title = R.string.installation_incompatible_dialog_title, + contentStringResId = R.string.installation_incompatible_description, + ), + FAILURE_INVALID( + flag = PackageInstaller.STATUS_FAILURE_INVALID, + title = R.string.installation_invalid_dialog_title, + contentStringResId = R.string.installation_invalid_description, + confirmButton = installerStatusDialogButton(R.string.reinstall) { model -> + model.reinstall() + }, + dismissButton = installerStatusDialogButton(R.string.cancel), + ), + FAILURE_STORAGE( + flag = PackageInstaller.STATUS_FAILURE_STORAGE, + title = R.string.installation_storage_issue_dialog_title, + contentStringResId = R.string.installation_storage_issue_description, + ), + + @RequiresApi(34) + FAILURE_TIMEOUT( + flag = PackageInstaller.STATUS_FAILURE_TIMEOUT, + title = R.string.installation_timeout_dialog_title, + contentStringResId = R.string.installation_timeout_description, + confirmButton = installerStatusDialogButton(R.string.install_app) { model -> + model.install() + }, + ); + // Needed due to the @FromValue annotation. + companion object +} diff --git a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt index 163dfbc6..096bbf03 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt @@ -40,6 +40,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import app.revanced.manager.R import app.revanced.manager.ui.component.AppScaffold import app.revanced.manager.ui.component.AppTopBar +import app.revanced.manager.ui.component.InstallerStatusDialog import app.revanced.manager.ui.component.patcher.InstallPickerDialog import app.revanced.manager.ui.component.patcher.Steps import app.revanced.manager.ui.model.State @@ -91,6 +92,9 @@ fun PatcherScreen( onConfirm = vm::install ) + if (vm.installerStatusDialogModel.packageInstallerStatus != null) + InstallerStatusDialog(vm.installerStatusDialogModel) + AppScaffold( topBar = { AppTopBar( @@ -103,7 +107,7 @@ fun PatcherScreen( actions = { IconButton( onClick = { exportApkLauncher.launch("${vm.packageName}.apk") }, - enabled = canInstall + enabled = patcherSucceeded == true ) { Icon(Icons.Outlined.Save, stringResource(id = R.string.save_apk)) } @@ -172,4 +176,4 @@ fun PatcherScreen( } } } -} \ No newline at end of file +} 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 e0995911..ae7f95b9 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 @@ -30,6 +30,8 @@ import app.revanced.manager.patcher.logger.LogLevel import app.revanced.manager.patcher.logger.Logger import app.revanced.manager.patcher.worker.PatcherWorker import app.revanced.manager.service.InstallService +import app.revanced.manager.service.UninstallService +import app.revanced.manager.ui.component.InstallerStatusDialogModel import app.revanced.manager.ui.destination.Destination import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.model.State @@ -66,6 +68,20 @@ class PatcherViewModel( private val installedAppRepository: InstalledAppRepository by inject() private val rootInstaller: RootInstaller by inject() + val installerStatusDialogModel : InstallerStatusDialogModel = object : InstallerStatusDialogModel { + override var packageInstallerStatus: Int? by mutableStateOf(null) + + override fun reinstall() { + this@PatcherViewModel.reinstall() + } + + override fun install() { + // Since this is a package installer status dialog, + // InstallType.ROOT is never used here. + install(InstallType.DEFAULT) + } + } + private var installedApp: InstalledApp? = null val packageName: String = input.selectedApp.packageName var installedPackageName by mutableStateOf(null) @@ -144,15 +160,19 @@ class PatcherViewModel( } } - private val installBroadcastReceiver = object : BroadcastReceiver() { + private val installerBroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { when (intent?.action) { InstallService.APP_INSTALL_ACTION -> { - val pmStatus = intent.getIntExtra(InstallService.EXTRA_INSTALL_STATUS, -999) - val extra = intent.getStringExtra(InstallService.EXTRA_INSTALL_STATUS_MESSAGE)!! + val pmStatus = intent.getIntExtra( + InstallService.EXTRA_INSTALL_STATUS, + PackageInstaller.STATUS_FAILURE + ) + + intent.getStringExtra(UninstallService.EXTRA_UNINSTALL_STATUS_MESSAGE) + ?.let(logger::trace) if (pmStatus == PackageInstaller.STATUS_SUCCESS) { - app.toast(app.getString(R.string.install_app_success)) installedPackageName = intent.getStringExtra(InstallService.EXTRA_PACKAGE_NAME) viewModelScope.launch { @@ -164,8 +184,24 @@ class PatcherViewModel( input.selectedPatches ) } - } else { - app.toast(app.getString(R.string.install_app_fail, extra)) + } + + installerStatusDialogModel.packageInstallerStatus = pmStatus + + isInstalling = false + } + + UninstallService.APP_UNINSTALL_ACTION -> { + val pmStatus = intent.getIntExtra( + UninstallService.EXTRA_UNINSTALL_STATUS, + PackageInstaller.STATUS_FAILURE + ) + + intent.getStringExtra(UninstallService.EXTRA_UNINSTALL_STATUS_MESSAGE) + ?.let(logger::trace) + + if (pmStatus != PackageInstaller.STATUS_SUCCESS) { + installerStatusDialogModel.packageInstallerStatus = pmStatus } } } @@ -173,9 +209,15 @@ class PatcherViewModel( } init { // TODO: navigate away when system-initiated process death is detected because it is not possible to recover from it. - ContextCompat.registerReceiver(app, installBroadcastReceiver, IntentFilter().apply { - addAction(InstallService.APP_INSTALL_ACTION) - }, ContextCompat.RECEIVER_NOT_EXPORTED) + ContextCompat.registerReceiver( + app, + installerBroadcastReceiver, + IntentFilter().apply { + addAction(InstallService.APP_INSTALL_ACTION) + addAction(UninstallService.APP_UNINSTALL_ACTION) + }, + ContextCompat.RECEIVER_NOT_EXPORTED + ) viewModelScope.launch { installedApp = installedAppRepository.get(packageName) @@ -185,7 +227,7 @@ class PatcherViewModel( @OptIn(DelicateCoroutinesApi::class) override fun onCleared() { super.onCleared() - app.unregisterReceiver(installBroadcastReceiver) + app.unregisterReceiver(installerBroadcastReceiver) workManager.cancelWorkById(patcherWorkerId) if (input.selectedApp is SelectedApp.Installed && installedApp?.installType == InstallType.ROOT) { @@ -228,20 +270,56 @@ class PatcherViewModel( fun open() = installedPackageName?.let(pm::launch) fun install(installType: InstallType) = viewModelScope.launch { + var pmInstallStarted = false try { isInstalling = true + + val currentPackageInfo = pm.getPackageInfo(outputFile) + ?: throw Exception("Failed to load application info") + + // If the app is currently installed + val existingPackageInfo = pm.getPackageInfo(currentPackageInfo.packageName) + if (existingPackageInfo != null) { + // Check if the app version is less than the installed version + if (pm.getVersionCode(currentPackageInfo) < pm.getVersionCode(existingPackageInfo)) { + // Exit if the selected app version is less than the installed version + installerStatusDialogModel.packageInstallerStatus = PackageInstaller.STATUS_FAILURE_CONFLICT + return@launch + } + } + when (installType) { InstallType.DEFAULT -> { + // Check if the app is mounted as root + // If it is, unmount it first, silently + if (rootInstaller.hasRootAccess() && rootInstaller.isAppMounted(packageName)) { + rootInstaller.unmount(packageName) + } + + // Install regularly pm.installApp(listOf(outputFile)) + pmInstallStarted = true } InstallType.ROOT -> { try { - val label = with(pm) { - getPackageInfo(outputFile)?.label() - ?: throw Exception("Failed to load application info") + // Check for base APK, first check if the app is already installed + if (existingPackageInfo == null) { + // If the app is not installed, check if the output file is a base apk + if (currentPackageInfo.splitNames != null) { + // Exit if there is no base APK package + installerStatusDialogModel.packageInstallerStatus = + PackageInstaller.STATUS_FAILURE_INVALID + return@launch + } } + // Get label + val label = with(pm) { + currentPackageInfo.label() + } + + // Install as root rootInstaller.install( outputFile, inputFile, @@ -273,8 +351,22 @@ class PatcherViewModel( } } } + } catch(e: Exception) { + Log.e(tag, "Failed to install", e) + app.toast(app.getString(R.string.install_app_fail, e.simpleMessage())) } finally { - isInstalling = false + if (!pmInstallStarted) + isInstalling = false + } + } + + fun reinstall() = viewModelScope.launch { + uiSafe(app, R.string.reinstall_app_fail, "Failed to reinstall") { + pm.getPackageInfo(outputFile)?.packageName?.let { pm.uninstallPackage(it) } + ?: throw Exception("Failed to load application info") + + pm.installApp(listOf(outputFile)) + isInstalling = true } } diff --git a/app/src/main/java/app/revanced/manager/util/PM.kt b/app/src/main/java/app/revanced/manager/util/PM.kt index 21a60b97..0d7a822b 100644 --- a/app/src/main/java/app/revanced/manager/util/PM.kt +++ b/app/src/main/java/app/revanced/manager/util/PM.kt @@ -10,6 +10,7 @@ import android.content.pm.PackageInstaller import android.content.pm.PackageManager import android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES import android.content.pm.PackageManager.NameNotFoundException +import androidx.core.content.pm.PackageInfoCompat import android.os.Build import android.os.Parcelable import androidx.compose.runtime.Immutable @@ -115,6 +116,8 @@ class PM( fun PackageInfo.label() = this.applicationInfo.loadLabel(app.packageManager).toString() + fun getVersionCode(packageInfo: PackageInfo) = PackageInfoCompat.getLongVersionCode(packageInfo) + suspend fun installApp(apks: List) = withContext(Dispatchers.IO) { val packageInstaller = app.packageManager.packageInstaller packageInstaller.openSession(packageInstaller.createSession(sessionParams)).use { session -> @@ -170,4 +173,4 @@ class PM( Intent(this, UninstallService::class.java), intentFlags ).intentSender -} \ No newline at end of file +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 81522032..5b6c6d93 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -257,6 +257,7 @@ Install App installed Failed to install app: %s + Failed to reinstall app: %s Failed to uninstall app: %s Open Save APK @@ -360,6 +361,24 @@ Import local files from your storage, does not automatically update Import remote files from a URL, can automatically update Recommended + + Installation failed + Installation cancelled + Installation blocked + Installation conflict + Installation incompatible + Installation invalid + Not enough storage + Installation timed out + The installation failed due to an unknown reason. Try again? + The installation was cancelled manually. Try again? + The installation was blocked. Review your device security settings and try again. + The installation was prevented by an existing installation of the app. Uninstall the installed app and try again? + The app is incompatible with this device. Use the correct APK for your device and try again. + The app is invalid. Uninstall the app and try again? + The app could not be installed due to insufficient storage. Free up some space and try again. + The installation took too long. Try again? + Reinstall Show Debugging About device diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 05e854ed..1a3f425a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,7 @@ app-icon-loader-coil = "1.5.0" skrapeit = "1.2.2" libsu = "5.2.2" scrollbars = "1.0.4" +enumutil = "1.1.0" compose-icons = "1.2.4" kotlin-process = "1.4.1" hidden-api-stub = "4.3.3" @@ -121,6 +122,10 @@ libsu-nio = { group = "com.github.topjohnwu.libsu", name = "nio", version.ref = # Scrollbars scrollbars = { group = "com.github.GIGAMOLE", name = "ComposeScrollbars", version.ref = "scrollbars" } +# EnumUtil +enumutil = { group = "io.github.materiiapps", name = "enumutil", version.ref = "enumutil" } +enumutil-ksp = { group = "io.github.materiiapps", name = "enumutil-ksp", version.ref = "enumutil" } + # Reorderable lists reorderable = { module = "sh.calvin.reorderable:reorderable", version.ref = "reorderable" } From f6f72387b92f73b5e3b36f3096928fa83e7614a1 Mon Sep 17 00:00:00 2001 From: alieRN <45766489+aliernfrog@users.noreply.github.com> Date: Thu, 19 Sep 2024 22:17:38 +0300 Subject: [PATCH 06/10] feat(patcher): Improve installation (#2185) --- .../manager/data/room/apps/installed/InstalledApp.kt | 2 +- .../revanced/manager/domain/installer/RootInstaller.kt | 6 +++++- .../revanced/manager/patcher/worker/PatcherWorker.kt | 2 +- .../ui/component/patcher/InstallPickerDialog.kt | 3 ++- .../manager/ui/screen/InstalledAppInfoScreen.kt | 6 +++--- .../app/revanced/manager/ui/screen/PatcherScreen.kt | 4 +++- .../manager/ui/screen/VersionSelectorScreen.kt | 4 ++-- .../manager/ui/viewmodel/InstalledAppInfoViewModel.kt | 2 +- .../manager/ui/viewmodel/InstalledAppsViewModel.kt | 4 ++-- .../revanced/manager/ui/viewmodel/PatcherViewModel.kt | 10 ++++++---- app/src/main/res/values/strings.xml | 2 +- 11 files changed, 27 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledApp.kt b/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledApp.kt index ad7033dd..290a226d 100644 --- a/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledApp.kt +++ b/app/src/main/java/app/revanced/manager/data/room/apps/installed/InstalledApp.kt @@ -9,7 +9,7 @@ import kotlinx.parcelize.Parcelize enum class InstallType(val stringResource: Int) { DEFAULT(R.string.default_install), - ROOT(R.string.root_install) + MOUNT(R.string.mount_install) } @Parcelize diff --git a/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt b/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt index 9ca6cd9b..885f8ad1 100644 --- a/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt +++ b/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt @@ -43,7 +43,7 @@ class RootInstaller( } } - return withTimeoutOrNull(Duration.ofSeconds(120L)) { + return withTimeoutOrNull(Duration.ofSeconds(20L)) { remoteFS.await() } ?: throw RootServiceException() } @@ -58,6 +58,10 @@ class RootInstaller( fun hasRootAccess() = Shell.isAppGrantedRoot() ?: false + fun isDeviceRooted() = System.getenv("PATH")?.split(":")?.any { path -> + File(path, "su").canExecute() + } ?: false + suspend fun isAppInstalled(packageName: String) = awaitRemoteFS().getFile("$modulesPath/$packageName-revanced").exists() 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 0e779df7..a5c551a4 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 @@ -135,7 +135,7 @@ class PatcherWorker( return try { if (args.input is SelectedApp.Installed) { installedAppRepository.get(args.packageName)?.let { - if (it.installType == InstallType.ROOT) { + if (it.installType == InstallType.MOUNT) { rootInstaller.unmount(args.packageName) } } diff --git a/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt index ec3cf979..e331db2e 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt @@ -7,6 +7,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.ListItem import androidx.compose.material3.RadioButton 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 @@ -27,7 +28,7 @@ fun InstallPickerDialog( AlertDialog( onDismissRequest = onDismiss, dismissButton = { - Button(onClick = onDismiss) { + TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } }, diff --git a/app/src/main/java/app/revanced/manager/ui/screen/InstalledAppInfoScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/InstalledAppInfoScreen.kt index 2727e290..239aebbf 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/InstalledAppInfoScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/InstalledAppInfoScreen.kt @@ -81,7 +81,7 @@ fun InstalledAppInfoScreen( AppInfo(viewModel.appInfo) { Text(viewModel.installedApp.version, color = MaterialTheme.colorScheme.onSurfaceVariant, style = MaterialTheme.typography.bodyMedium) - if (viewModel.installedApp.installType == InstallType.ROOT) { + if (viewModel.installedApp.installType == InstallType.MOUNT) { Text( text = if (viewModel.isMounted) { stringResource(R.string.mounted) @@ -112,7 +112,7 @@ fun InstalledAppInfoScreen( onClick = viewModel::uninstall ) - InstallType.ROOT -> { + InstallType.MOUNT -> { SegmentedButton( icon = Icons.Outlined.SettingsBackupRestore, text = stringResource(R.string.unpatch), @@ -138,7 +138,7 @@ fun InstalledAppInfoScreen( onPatchClick(viewModel.installedApp.originalPackageName, it) } }, - enabled = viewModel.installedApp.installType != InstallType.ROOT || viewModel.rootInstaller.hasRootAccess() + enabled = viewModel.installedApp.installType != InstallType.MOUNT || viewModel.rootInstaller.hasRootAccess() ) } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt index 096bbf03..2c7792d0 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import app.revanced.manager.R +import app.revanced.manager.data.room.apps.installed.InstallType import app.revanced.manager.ui.component.AppScaffold import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.InstallerStatusDialog @@ -139,7 +140,8 @@ fun PatcherScreen( }, onClick = { if (vm.installedPackageName == null) - showInstallPicker = true + if (vm.isDeviceRooted()) showInstallPicker = true + else vm.install(InstallType.DEFAULT) else vm.open() } ) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt index 3a5fc3d7..2ca8baa6 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt @@ -113,8 +113,8 @@ fun VersionSelectorScreen( onClick = { viewModel.select(it) }, patchCount = supportedVersions[it.version], enabled = - !(installedApp?.installType == InstallType.ROOT && !viewModel.rootInstaller.hasRootAccess()), - alreadyPatched = installedApp != null && installedApp.installType != InstallType.ROOT + !(installedApp?.installType == InstallType.MOUNT && !viewModel.rootInstaller.hasRootAccess()), + alreadyPatched = installedApp != null && installedApp.installType != InstallType.MOUNT ) } } diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppInfoViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppInfoViewModel.kt index 7e610f55..93e2cb74 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppInfoViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppInfoViewModel.kt @@ -78,7 +78,7 @@ class InstalledAppInfoViewModel( when (installedApp.installType) { InstallType.DEFAULT -> pm.uninstallPackage(installedApp.currentPackageName) - InstallType.ROOT -> viewModelScope.launch { + InstallType.MOUNT -> viewModelScope.launch { rootInstaller.uninstall(installedApp.currentPackageName) installedAppRepository.delete(installedApp) onBackClick() diff --git a/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppsViewModel.kt b/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppsViewModel.kt index 27bec4c4..42ad08c7 100644 --- a/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppsViewModel.kt +++ b/app/src/main/java/app/revanced/manager/ui/viewmodel/InstalledAppsViewModel.kt @@ -30,7 +30,7 @@ class InstalledAppsViewModel( packageInfoMap[installedApp.currentPackageName] = withContext(Dispatchers.IO) { try { if ( - installedApp.installType == InstallType.ROOT && !rootInstaller.isAppInstalled(installedApp.currentPackageName) + installedApp.installType == InstallType.MOUNT && !rootInstaller.isAppInstalled(installedApp.currentPackageName) ) { installedAppsRepository.delete(installedApp) return@withContext null @@ -39,7 +39,7 @@ class InstalledAppsViewModel( val packageInfo = pm.getPackageInfo(installedApp.currentPackageName) - if (packageInfo == null && installedApp.installType != InstallType.ROOT) { + if (packageInfo == null && installedApp.installType != InstallType.MOUNT) { installedAppsRepository.delete(installedApp) return@withContext null } 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 ae7f95b9..a63feed7 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 @@ -77,7 +77,7 @@ class PatcherViewModel( override fun install() { // Since this is a package installer status dialog, - // InstallType.ROOT is never used here. + // InstallType.MOUNT is never used here. install(InstallType.DEFAULT) } } @@ -230,7 +230,7 @@ class PatcherViewModel( app.unregisterReceiver(installerBroadcastReceiver) workManager.cancelWorkById(patcherWorkerId) - if (input.selectedApp is SelectedApp.Installed && installedApp?.installType == InstallType.ROOT) { + if (input.selectedApp is SelectedApp.Installed && installedApp?.installType == InstallType.MOUNT) { GlobalScope.launch(Dispatchers.Main) { uiSafe(app, R.string.failed_to_mount, "Failed to mount") { withTimeout(Duration.ofMinutes(1L)) { @@ -243,6 +243,8 @@ class PatcherViewModel( tempDir.deleteRecursively() } + fun isDeviceRooted() = rootInstaller.isDeviceRooted() + fun export(uri: Uri?) = viewModelScope.launch { uri?.let { withContext(Dispatchers.IO) { @@ -301,7 +303,7 @@ class PatcherViewModel( pmInstallStarted = true } - InstallType.ROOT -> { + InstallType.MOUNT -> { try { // Check for base APK, first check if the app is already installed if (existingPackageInfo == null) { @@ -332,7 +334,7 @@ class PatcherViewModel( packageName, packageName, input.selectedApp.version, - InstallType.ROOT, + InstallType.MOUNT, input.selectedPatches ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5b6c6d93..ffcfc9fd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -223,7 +223,7 @@ Applied patches View applied patches Default - Root + Mount Mounted Not mounted Mount From 697386c36c769c07db568aced4b8388e7242924d Mon Sep 17 00:00:00 2001 From: kitadai31 <90122968+kitadai31@users.noreply.github.com> Date: Tue, 1 Oct 2024 01:51:22 +0900 Subject: [PATCH 07/10] fix: Match "Installation incompatible" dialog message with Flutter Manager (#2231) --- app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ffcfc9fd..0b260bb8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -374,7 +374,7 @@ The installation was cancelled manually. Try again? The installation was blocked. Review your device security settings and try again. The installation was prevented by an existing installation of the app. Uninstall the installed app and try again? - The app is incompatible with this device. Use the correct APK for your device and try again. + The app is incompatible with this device. Use an APK that is supported by this device and try again. The app is invalid. Uninstall the app and try again? The app could not be installed due to insufficient storage. Free up some space and try again. The installation took too long. Try again? From b4c37e6ddc396dd15731d861448dba6beff65ced Mon Sep 17 00:00:00 2001 From: Benjamin <73490201+BenjaminHalko@users.noreply.github.com> Date: Wed, 6 Nov 2024 12:48:40 -0800 Subject: [PATCH 08/10] feat: Add haptic feedback (#1457) Co-authored-by: Ushie --- .../manager/ui/component/AutoUpdatesDialog.kt | 6 +-- .../ui/component/AvailableUpdateDialog.kt | 3 +- .../ui/component/bundle/BaseBundleDialog.kt | 3 +- .../manager/ui/component/bundle/BundleItem.kt | 4 +- .../ui/component/bundle/ImportBundleDialog.kt | 29 +++-------- .../ui/component/haptics/HapticCheckbox.kt | 40 ++++++++++++++++ .../HapticExtendedFloatingActionButton.kt | 48 +++++++++++++++++++ .../haptics/HapticFloatingActionButton.kt | 44 +++++++++++++++++ .../ui/component/haptics/HapticRadioButton.kt | 42 ++++++++++++++++ .../ui/component/haptics/HapticSwitch.kt | 46 ++++++++++++++++++ .../manager/ui/component/haptics/HapticTab.kt | 43 +++++++++++++++++ .../component/patcher/InstallPickerDialog.kt | 10 ++-- .../ui/component/patches/OptionFields.kt | 47 +++++------------- .../ui/component/settings/BooleanItem.kt | 4 +- .../manager/ui/screen/DashboardScreen.kt | 8 ++-- .../manager/ui/screen/PatcherScreen.kt | 18 ++----- .../ui/screen/PatchesSelectorScreen.kt | 13 +++-- .../ui/screen/SelectedAppInfoScreen.kt | 17 ++----- .../ui/screen/VersionSelectorScreen.kt | 8 ++-- .../settings/DownloadsSettingsScreen.kt | 4 +- .../screen/settings/GeneralSettingsScreen.kt | 3 +- 21 files changed, 326 insertions(+), 114 deletions(-) create mode 100644 app/src/main/java/app/revanced/manager/ui/component/haptics/HapticCheckbox.kt create mode 100644 app/src/main/java/app/revanced/manager/ui/component/haptics/HapticExtendedFloatingActionButton.kt create mode 100644 app/src/main/java/app/revanced/manager/ui/component/haptics/HapticFloatingActionButton.kt create mode 100644 app/src/main/java/app/revanced/manager/ui/component/haptics/HapticRadioButton.kt create mode 100644 app/src/main/java/app/revanced/manager/ui/component/haptics/HapticSwitch.kt create mode 100644 app/src/main/java/app/revanced/manager/ui/component/haptics/HapticTab.kt diff --git a/app/src/main/java/app/revanced/manager/ui/component/AutoUpdatesDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/AutoUpdatesDialog.kt index 9da4f27f..1e2234eb 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/AutoUpdatesDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/AutoUpdatesDialog.kt @@ -8,7 +8,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Source import androidx.compose.material.icons.outlined.Update import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Checkbox import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Icon import androidx.compose.material3.ListItem @@ -24,6 +23,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import app.revanced.manager.R +import app.revanced.manager.ui.component.haptics.HapticCheckbox @Composable fun AutoUpdatesDialog(onSubmit: (Boolean, Boolean) -> Unit) { @@ -76,6 +76,6 @@ private fun AutoUpdatesItem( ) = ListItem( leadingContent = { Icon(icon, null) }, headlineContent = { Text(stringResource(headline)) }, - trailingContent = { Checkbox(checked = checked, onCheckedChange = null) }, + trailingContent = { HapticCheckbox(checked = checked, onCheckedChange = null) }, modifier = Modifier.clickable { onCheckedChange(!checked) } -) \ No newline at end of file +) 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 index 7059ad0d..4a684c1e 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/AvailableUpdateDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/AvailableUpdateDialog.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import app.revanced.manager.R +import app.revanced.manager.ui.component.haptics.HapticCheckbox @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -70,7 +71,7 @@ fun AvailableUpdateDialog( }, leadingContent = { CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) { - Checkbox(checked = dontShowAgain, onCheckedChange = { dontShowAgain = it }) + HapticCheckbox(checked = dontShowAgain, onCheckedChange = { dontShowAgain = it }) } } ) 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 4450ef5c..dfc63735 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 @@ -23,6 +23,7 @@ import androidx.compose.ui.unit.dp import app.revanced.manager.R import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.TextInputDialog +import app.revanced.manager.ui.component.haptics.HapticSwitch @Composable fun BaseBundleDialog( @@ -89,7 +90,7 @@ fun BaseBundleDialog( headlineText = stringResource(R.string.bundle_auto_update), supportingText = stringResource(R.string.bundle_auto_update_description), trailingContent = { - Switch( + HapticSwitch( checked = autoUpdate, onCheckedChange = onAutoUpdateChange ) diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt index 617c384f..2fdd8f5d 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt @@ -9,7 +9,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ErrorOutline import androidx.compose.material.icons.outlined.Warning -import androidx.compose.material3.Checkbox import androidx.compose.material3.Icon import androidx.compose.material3.ListItem import androidx.compose.material3.MaterialTheme @@ -27,6 +26,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import app.revanced.manager.R import app.revanced.manager.domain.bundles.PatchBundleSource +import app.revanced.manager.ui.component.haptics.HapticCheckbox import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState import kotlinx.coroutines.flow.map @@ -71,7 +71,7 @@ fun BundleItem( ), leadingContent = if (selectable) { { - Checkbox( + HapticCheckbox( checked = isBundleSelected, onCheckedChange = toggleSelection, ) diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt index 2de48a56..2de10053 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt @@ -10,26 +10,9 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Topic -import androidx.compose.material3.Checkbox -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.ListItem -import androidx.compose.material3.LocalMinimumInteractiveComponentEnforcement -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember +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.res.stringResource import androidx.compose.ui.semantics.Role @@ -37,6 +20,8 @@ import androidx.compose.ui.unit.dp import app.revanced.manager.R import app.revanced.manager.ui.component.AlertDialogExtended import app.revanced.manager.ui.component.TextHorizontalPadding +import app.revanced.manager.ui.component.haptics.HapticCheckbox +import app.revanced.manager.ui.component.haptics.HapticRadioButton import app.revanced.manager.ui.model.BundleType import app.revanced.manager.util.APK_MIMETYPE import app.revanced.manager.util.JAR_MIMETYPE @@ -170,7 +155,7 @@ fun SelectBundleTypeStep( overlineContent = { Text(stringResource(R.string.recommended)) }, supportingContent = { Text(stringResource(R.string.remote_bundle_description)) }, leadingContent = { - RadioButton( + HapticRadioButton( selected = bundleType == BundleType.Remote, onClick = null ) @@ -186,7 +171,7 @@ fun SelectBundleTypeStep( supportingContent = { Text(stringResource(R.string.local_bundle_description)) }, overlineContent = { }, leadingContent = { - RadioButton( + HapticRadioButton( selected = bundleType == BundleType.Local, onClick = null ) @@ -263,7 +248,7 @@ fun ImportBundleStep( headlineContent = { Text(stringResource(R.string.auto_update)) }, leadingContent = { CompositionLocalProvider(LocalMinimumInteractiveComponentEnforcement provides false) { - Checkbox( + HapticCheckbox( checked = autoUpdate, onCheckedChange = { onAutoUpdateChange(!autoUpdate) diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticCheckbox.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticCheckbox.kt new file mode 100644 index 00000000..fb98e40f --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticCheckbox.kt @@ -0,0 +1,40 @@ +package app.revanced.manager.ui.component.haptics + +import android.view.HapticFeedbackConstants +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CheckboxColors +import androidx.compose.material3.CheckboxDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView + +@Composable +fun HapticCheckbox ( + checked: Boolean, + onCheckedChange: ((Boolean) -> Unit)?, + modifier: Modifier = Modifier, + enabled: Boolean = true, + colors: CheckboxColors = CheckboxDefaults.colors(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } +) { + val checkedState = remember { mutableStateOf(checked) } + + // Perform haptic feedback + if (checkedState.value != checked) { + val view = LocalView.current + view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) + checkedState.value = checked + } + + Checkbox( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + enabled = enabled, + colors = colors, + interactionSource = interactionSource + ) +} diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticExtendedFloatingActionButton.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticExtendedFloatingActionButton.kt new file mode 100644 index 00000000..f9d91caf --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticExtendedFloatingActionButton.kt @@ -0,0 +1,48 @@ +package app.revanced.manager.ui.component.haptics + +import android.view.HapticFeedbackConstants +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.ExtendedFloatingActionButton +import androidx.compose.material3.FloatingActionButtonDefaults +import androidx.compose.material3.FloatingActionButtonElevation +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.platform.LocalView + +@Composable +fun HapticExtendedFloatingActionButton ( + text: @Composable () -> Unit, + icon: @Composable () -> Unit, + onClick: () -> Unit, + modifier: Modifier = Modifier, + expanded: Boolean = true, + shape: Shape = FloatingActionButtonDefaults.extendedFabShape, + containerColor: Color = FloatingActionButtonDefaults.containerColor, + contentColor: Color = contentColorFor(containerColor), + elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val view = LocalView.current + + ExtendedFloatingActionButton( + text = text, + icon = icon, + onClick = { + // Perform haptic feedback + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) + + onClick() + }, + modifier = modifier, + expanded = expanded, + shape = shape, + containerColor = containerColor, + contentColor = contentColor, + elevation = elevation, + interactionSource = interactionSource + ) +} diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticFloatingActionButton.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticFloatingActionButton.kt new file mode 100644 index 00000000..0268accc --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticFloatingActionButton.kt @@ -0,0 +1,44 @@ +package app.revanced.manager.ui.component.haptics + +import android.view.HapticFeedbackConstants +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.FloatingActionButton +import androidx.compose.material3.FloatingActionButtonDefaults +import androidx.compose.material3.FloatingActionButtonElevation +import androidx.compose.material3.contentColorFor +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.platform.LocalView + +@Composable +fun HapticFloatingActionButton ( + onClick: () -> Unit, + modifier: Modifier = Modifier, + shape: Shape = FloatingActionButtonDefaults.shape, + containerColor: Color = FloatingActionButtonDefaults.containerColor, + contentColor: Color = contentColorFor(containerColor), + elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + content: @Composable () -> Unit, +) { + val view = LocalView.current + + FloatingActionButton( + onClick = { + // Perform haptic feedback + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) + + onClick() + }, + modifier = modifier, + shape = shape, + containerColor = containerColor, + contentColor = contentColor, + elevation = elevation, + interactionSource = interactionSource, + content = content + ) +} diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticRadioButton.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticRadioButton.kt new file mode 100644 index 00000000..7ac6a7a8 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticRadioButton.kt @@ -0,0 +1,42 @@ +package app.revanced.manager.ui.component.haptics + +import android.view.HapticFeedbackConstants +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.RadioButton +import androidx.compose.material3.RadioButtonColors +import androidx.compose.material3.RadioButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView + +@Composable +fun HapticRadioButton ( + selected: Boolean, + onClick: (() -> Unit)?, + modifier: Modifier = Modifier, + enabled: Boolean = true, + colors: RadioButtonColors = RadioButtonDefaults.colors(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } +) { + val selectedState = remember { mutableStateOf(selected) } + + // Perform haptic feedback + if (selectedState.value != selected) { + if (selected) { + val view = LocalView.current + view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) + } + selectedState.value = selected + } + + RadioButton( + selected = selected, + onClick = onClick, + modifier = modifier, + enabled = enabled, + colors = colors, + interactionSource = interactionSource + ) +} diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticSwitch.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticSwitch.kt new file mode 100644 index 00000000..fa3e894b --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticSwitch.kt @@ -0,0 +1,46 @@ +package app.revanced.manager.ui.component.haptics +import android.os.Build +import android.view.HapticFeedbackConstants +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.Switch +import androidx.compose.material3.SwitchColors +import androidx.compose.material3.SwitchDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView + +@Composable +fun HapticSwitch( + checked: Boolean, + onCheckedChange: ((Boolean) -> Unit), + modifier: Modifier = Modifier, + thumbContent: (@Composable () -> Unit)? = null, + enabled: Boolean = true, + colors: SwitchColors = SwitchDefaults.colors(), + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, +) { + val checkedState = remember { mutableStateOf(checked) } + + // Perform haptic feedback + if (checkedState.value != checked) { + val view = LocalView.current + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + view.performHapticFeedback(if (checked) HapticFeedbackConstants.TOGGLE_ON else HapticFeedbackConstants.TOGGLE_OFF) + } else { + view.performHapticFeedback(if (checked) HapticFeedbackConstants.VIRTUAL_KEY else HapticFeedbackConstants.CLOCK_TICK) + } + checkedState.value = checked + } + + Switch( + checked = checked, + onCheckedChange = onCheckedChange, + modifier = modifier, + thumbContent = thumbContent, + enabled = enabled, + colors = colors, + interactionSource = interactionSource, + ) +} diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticTab.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticTab.kt new file mode 100644 index 00000000..3b5a11e9 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticTab.kt @@ -0,0 +1,43 @@ +package app.revanced.manager.ui.component.haptics + +import android.view.HapticFeedbackConstants +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.material3.LocalContentColor +import androidx.compose.material3.Tab +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalView + +@Composable +fun HapticTab ( + selected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + text: @Composable (() -> Unit)? = null, + icon: @Composable (() -> Unit)? = null, + selectedContentColor: Color = LocalContentColor.current, + unselectedContentColor: Color = selectedContentColor, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } +) { + val view = LocalView.current + + Tab( + selected = selected, + onClick = { + // Perform haptic feedback + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) + + onClick() + }, + modifier = modifier, + enabled = enabled, + text = text, + icon = icon, + selectedContentColor = selectedContentColor, + unselectedContentColor = unselectedContentColor, + interactionSource = interactionSource + ) +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt index e331db2e..bb667bad 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt @@ -2,12 +2,7 @@ package app.revanced.manager.ui.component.patcher import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.Button -import androidx.compose.material3.ListItem -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -17,6 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import app.revanced.manager.R import app.revanced.manager.data.room.apps.installed.InstallType +import app.revanced.manager.ui.component.haptics.HapticRadioButton @Composable fun InstallPickerDialog( @@ -49,7 +45,7 @@ fun InstallPickerDialog( ListItem( modifier = Modifier.clickable { selectedInstallType = it }, leadingContent = { - RadioButton( + HapticRadioButton( selected = selectedInstallType == it, onClick = 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 433c711b..993270ea 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 @@ -20,53 +20,31 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.DragHandle -import androidx.compose.material.icons.outlined.Add -import androidx.compose.material.icons.outlined.Delete -import androidx.compose.material.icons.outlined.Edit -import androidx.compose.material.icons.outlined.Folder -import androidx.compose.material.icons.outlined.MoreVert -import androidx.compose.material.icons.outlined.Restore -import androidx.compose.material.icons.outlined.SelectAll -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.DropdownMenu -import androidx.compose.material3.DropdownMenuItem -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.ListItem -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Scaffold -import androidx.compose.material3.TextButton +import androidx.compose.material.icons.outlined.* +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.DisallowComposableCalls import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.setValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.Modifier import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.Dialog as ComposeDialog import androidx.compose.ui.window.DialogProperties import app.revanced.manager.R import app.revanced.manager.data.platform.Filesystem import app.revanced.manager.patcher.patch.Option -import app.revanced.manager.ui.component.AlertDialogExtended -import app.revanced.manager.ui.component.AppTopBar -import app.revanced.manager.ui.component.FloatInputDialog -import app.revanced.manager.ui.component.IntInputDialog -import app.revanced.manager.ui.component.LongInputDialog +import app.revanced.manager.ui.component.* +import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton +import app.revanced.manager.ui.component.haptics.HapticRadioButton +import app.revanced.manager.ui.component.haptics.HapticSwitch import app.revanced.manager.util.isScrollingUp import app.revanced.manager.util.mutableStateSetOf import app.revanced.manager.util.saver.snapshotStateListSaver @@ -80,6 +58,7 @@ import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyColumnState import java.io.Serializable import kotlin.random.Random +import androidx.compose.ui.window.Dialog as ComposeDialog private class OptionEditorScope( private val editor: OptionEditor, @@ -335,7 +314,7 @@ private object BooleanOptionEditor : OptionEditor { @Composable override fun ListItemTrailingContent(scope: OptionEditorScope) { - Switch(checked = scope.current, onCheckedChange = scope.setValue) + HapticSwitch(checked = scope.current, onCheckedChange = scope.setValue) } @Composable @@ -422,7 +401,7 @@ private class PresetOptionEditor(private val innerEditor: OptionEditor< headlineContent = { Text(title) }, supportingContent = value?.toString()?.let { { Text(it) } }, leadingContent = { - RadioButton( + HapticRadioButton( selected = selectedPreset == presetKey, onClick = { selectedPreset = presetKey } ) @@ -568,7 +547,7 @@ private class ListOptionEditor(private val elementEditor: Opti floatingActionButton = { if (deleteMode) return@Scaffold - ExtendedFloatingActionButton( + HapticExtendedFloatingActionButton( text = { Text(stringResource(R.string.add)) }, icon = { Icon( diff --git a/app/src/main/java/app/revanced/manager/ui/component/settings/BooleanItem.kt b/app/src/main/java/app/revanced/manager/ui/component/settings/BooleanItem.kt index 42e9a83e..0be1be91 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/settings/BooleanItem.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/settings/BooleanItem.kt @@ -2,13 +2,13 @@ package app.revanced.manager.ui.component.settings import androidx.annotation.StringRes import androidx.compose.foundation.clickable -import androidx.compose.material3.Switch import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import app.revanced.manager.domain.manager.base.Preference +import app.revanced.manager.ui.component.haptics.HapticSwitch import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -45,7 +45,7 @@ fun BooleanItem( headlineContent = stringResource(headline), supportingContent = stringResource(description), trailingContent = { - Switch( + HapticSwitch( checked = value, onCheckedChange = onValueChange, ) 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 bf310fc7..157986ec 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 @@ -33,6 +33,8 @@ 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 +import app.revanced.manager.ui.component.haptics.HapticFloatingActionButton +import app.revanced.manager.ui.component.haptics.HapticTab import app.revanced.manager.ui.component.bundle.ImportPatchBundleDialog import app.revanced.manager.ui.viewmodel.DashboardViewModel import app.revanced.manager.util.toast @@ -168,7 +170,7 @@ fun DashboardScreen( } }, floatingActionButton = { - FloatingActionButton( + HapticFloatingActionButton( onClick = { vm.cancelSourceSelection() @@ -181,7 +183,7 @@ fun DashboardScreen( DashboardPage.BUNDLES.ordinal ) } - return@FloatingActionButton + return@HapticFloatingActionButton } onAppSelectorClick() @@ -201,7 +203,7 @@ fun DashboardScreen( containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp) ) { DashboardPage.entries.forEachIndexed { index, page -> - Tab( + HapticTab( selected = pagerState.currentPage == index, onClick = { composableScope.launch { pagerState.animateScrollToPage(index) } }, text = { Text(stringResource(page.titleResId)) }, diff --git a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt index 2c7792d0..6aedde2f 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/PatcherScreen.kt @@ -4,12 +4,7 @@ import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts.CreateDocument import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -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.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons @@ -17,13 +12,7 @@ import androidx.compose.material.icons.automirrored.outlined.OpenInNew import androidx.compose.material.icons.outlined.FileDownload import androidx.compose.material.icons.outlined.PostAdd import androidx.compose.material.icons.outlined.Save -import androidx.compose.material3.BottomAppBar -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.LinearProgressIndicator -import androidx.compose.material3.Text +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -42,6 +31,7 @@ import app.revanced.manager.data.room.apps.installed.InstallType import app.revanced.manager.ui.component.AppScaffold import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.InstallerStatusDialog +import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton import app.revanced.manager.ui.component.patcher.InstallPickerDialog import app.revanced.manager.ui.component.patcher.Steps import app.revanced.manager.ui.model.State @@ -121,7 +111,7 @@ fun PatcherScreen( }, floatingActionButton = { AnimatedVisibility(visible = canInstall) { - ExtendedFloatingActionButton( + HapticExtendedFloatingActionButton( text = { Text( stringResource(if (vm.installedPackageName == null) R.string.install_app else R.string.open_app) 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 f8109fab..511ede21 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 @@ -35,6 +35,9 @@ import app.revanced.manager.ui.component.AppTopBar 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.haptics.HapticCheckbox +import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton +import app.revanced.manager.ui.component.haptics.HapticTab import app.revanced.manager.ui.component.patches.OptionItem import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW_SUPPORTED @@ -293,7 +296,7 @@ fun PatchesSelectorScreen( floatingActionButton = { if (!showPatchButton) return@Scaffold - ExtendedFloatingActionButton( + HapticExtendedFloatingActionButton( text = { Text(stringResource(R.string.save)) }, icon = { Icon( @@ -321,7 +324,7 @@ fun PatchesSelectorScreen( containerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(3.0.dp) ) { bundles.forEachIndexed { index, bundle -> - Tab( + HapticTab( selected = pagerState.currentPage == index, onClick = { composableScope.launch { @@ -432,13 +435,13 @@ private fun PatchItem( selected: Boolean, onToggle: () -> Unit, supported: Boolean = true -) = ListItem( +) = ListItem ( modifier = Modifier .let { if (!supported) it.alpha(0.5f) else it } .clickable(onClick = onToggle) .fillMaxSize(), leadingContent = { - Checkbox( + HapticCheckbox( checked = selected, onCheckedChange = { onToggle() }, enabled = supported @@ -452,7 +455,7 @@ private fun PatchItem( Icon(Icons.Outlined.Settings, null) } } - } + }, ) @Composable 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 c4bfc841..9c3f59d4 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 @@ -8,13 +8,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowRight import androidx.compose.material.icons.filled.AutoFixHigh -import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExtendedFloatingActionButton -import androidx.compose.material3.Icon -import androidx.compose.material3.ListItem -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text +import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -28,6 +22,7 @@ import app.revanced.manager.R import app.revanced.manager.ui.component.AppInfo import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.ColumnWithScrollbar +import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton import app.revanced.manager.ui.destination.SelectedAppInfoDestination import app.revanced.manager.ui.model.BundleInfo.Extensions.bundleInfoFlow import app.revanced.manager.ui.model.SelectedApp @@ -36,11 +31,7 @@ import app.revanced.manager.ui.viewmodel.SelectedAppInfoViewModel import app.revanced.manager.util.Options import app.revanced.manager.util.PatchSelection import app.revanced.manager.util.toast -import dev.olshevski.navigation.reimagined.AnimatedNavHost -import dev.olshevski.navigation.reimagined.NavBackHandler -import dev.olshevski.navigation.reimagined.navigate -import dev.olshevski.navigation.reimagined.pop -import dev.olshevski.navigation.reimagined.rememberNavController +import dev.olshevski.navigation.reimagined.* import org.koin.androidx.compose.koinViewModel import org.koin.core.parameter.parametersOf @@ -161,7 +152,7 @@ private fun SelectedAppInfoScreen( ) }, floatingActionButton = { - ExtendedFloatingActionButton( + HapticExtendedFloatingActionButton( text = { Text(stringResource(R.string.patch)) }, icon = { Icon( diff --git a/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt index 2ca8baa6..cd18dcc7 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt @@ -11,10 +11,8 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material3.ExperimentalMaterial3Api -import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.ListItem -import androidx.compose.material3.RadioButton import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -34,6 +32,8 @@ import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.GroupHeader import app.revanced.manager.ui.component.LazyColumnWithScrollbar import app.revanced.manager.ui.component.LoadingIndicator +import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton +import app.revanced.manager.ui.component.haptics.HapticRadioButton import app.revanced.manager.ui.component.NonSuggestedVersionDialog import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.viewmodel.VersionSelectorViewModel @@ -81,7 +81,7 @@ fun VersionSelectorScreen( ) }, floatingActionButton = { - ExtendedFloatingActionButton( + HapticExtendedFloatingActionButton( text = { Text(stringResource(R.string.select_version)) }, icon = { Icon( @@ -170,7 +170,7 @@ fun SelectedAppItem( alreadyPatched: Boolean = false, ) { ListItem( - leadingContent = { RadioButton(selected, null) }, + leadingContent = { HapticRadioButton(selected, null) }, headlineContent = { Text(selectedApp.version) }, supportingContent = when (selectedApp) { is SelectedApp.Installed -> diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt index 432c4808..41e80b40 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/DownloadsSettingsScreen.kt @@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete -import androidx.compose.material3.Checkbox import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -20,6 +19,7 @@ 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.GroupHeader +import app.revanced.manager.ui.component.haptics.HapticCheckbox import app.revanced.manager.ui.component.settings.BooleanItem import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.viewmodel.DownloadsViewModel @@ -70,7 +70,7 @@ fun DownloadsSettingsScreen( modifier = Modifier.clickable { viewModel.toggleItem(app) }, headlineContent = app.packageName, leadingContent = (@Composable { - Checkbox( + HapticCheckbox( checked = selected, onCheckedChange = { viewModel.toggleItem(app) } ) diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt index 03eae09f..56242679 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/GeneralSettingsScreen.kt @@ -28,6 +28,7 @@ import app.revanced.manager.domain.manager.PreferencesManager import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.GroupHeader +import app.revanced.manager.ui.component.haptics.HapticRadioButton import app.revanced.manager.ui.component.settings.BooleanItem import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.theme.Theme @@ -113,7 +114,7 @@ private fun ThemePicker( .clickable { selectedTheme = it }, verticalAlignment = Alignment.CenterVertically ) { - RadioButton( + HapticRadioButton( selected = selectedTheme == it, onClick = { selectedTheme = it }) Text(stringResource(it.displayName)) From cf322147d5e16d816508c9b015eaed73a318275d Mon Sep 17 00:00:00 2001 From: Ax333l Date: Tue, 12 Nov 2024 21:17:02 +0100 Subject: [PATCH 09/10] fix: only perform haptics on events --- .../ui/component/haptics/HapticCheckbox.kt | 16 ++-------- .../HapticExtendedFloatingActionButton.kt | 11 ++----- .../haptics/HapticFloatingActionButton.kt | 11 ++----- .../ui/component/haptics/HapticRadioButton.kt | 22 ++++++-------- .../ui/component/haptics/HapticSwitch.kt | 29 ++++++++----------- .../manager/ui/component/haptics/HapticTab.kt | 11 ++----- .../screen/settings/AdvancedSettingsScreen.kt | 8 ++--- .../java/app/revanced/manager/util/Util.kt | 24 ++++++++++++++- 8 files changed, 56 insertions(+), 76 deletions(-) diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticCheckbox.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticCheckbox.kt index fb98e40f..fb5453f9 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticCheckbox.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticCheckbox.kt @@ -6,13 +6,12 @@ import androidx.compose.material3.Checkbox import androidx.compose.material3.CheckboxColors import androidx.compose.material3.CheckboxDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalView +import app.revanced.manager.util.withHapticFeedback @Composable -fun HapticCheckbox ( +fun HapticCheckbox( checked: Boolean, onCheckedChange: ((Boolean) -> Unit)?, modifier: Modifier = Modifier, @@ -20,18 +19,9 @@ fun HapticCheckbox ( colors: CheckboxColors = CheckboxDefaults.colors(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } ) { - val checkedState = remember { mutableStateOf(checked) } - - // Perform haptic feedback - if (checkedState.value != checked) { - val view = LocalView.current - view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) - checkedState.value = checked - } - Checkbox( checked = checked, - onCheckedChange = onCheckedChange, + onCheckedChange = onCheckedChange?.withHapticFeedback(HapticFeedbackConstants.CLOCK_TICK), modifier = modifier, enabled = enabled, colors = colors, diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticExtendedFloatingActionButton.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticExtendedFloatingActionButton.kt index f9d91caf..4fc6ad30 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticExtendedFloatingActionButton.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticExtendedFloatingActionButton.kt @@ -11,7 +11,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.platform.LocalView +import app.revanced.manager.util.withHapticFeedback @Composable fun HapticExtendedFloatingActionButton ( @@ -26,17 +26,10 @@ fun HapticExtendedFloatingActionButton ( elevation: FloatingActionButtonElevation = FloatingActionButtonDefaults.elevation(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { - val view = LocalView.current - ExtendedFloatingActionButton( text = text, icon = icon, - onClick = { - // Perform haptic feedback - view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) - - onClick() - }, + onClick = onClick.withHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY), modifier = modifier, expanded = expanded, shape = shape, diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticFloatingActionButton.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticFloatingActionButton.kt index 0268accc..f4a2e153 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticFloatingActionButton.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticFloatingActionButton.kt @@ -11,7 +11,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Shape -import androidx.compose.ui.platform.LocalView +import app.revanced.manager.util.withHapticFeedback @Composable fun HapticFloatingActionButton ( @@ -24,15 +24,8 @@ fun HapticFloatingActionButton ( interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, content: @Composable () -> Unit, ) { - val view = LocalView.current - FloatingActionButton( - onClick = { - // Perform haptic feedback - view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) - - onClick() - }, + onClick = onClick.withHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY), modifier = modifier, shape = shape, containerColor = containerColor, diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticRadioButton.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticRadioButton.kt index 7ac6a7a8..63a9e582 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticRadioButton.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticRadioButton.kt @@ -6,13 +6,12 @@ import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButtonColors import androidx.compose.material3.RadioButtonDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalView @Composable -fun HapticRadioButton ( +fun HapticRadioButton( selected: Boolean, onClick: (() -> Unit)?, modifier: Modifier = Modifier, @@ -20,20 +19,17 @@ fun HapticRadioButton ( colors: RadioButtonColors = RadioButtonDefaults.colors(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } ) { - val selectedState = remember { mutableStateOf(selected) } - - // Perform haptic feedback - if (selectedState.value != selected) { - if (selected) { - val view = LocalView.current - view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) - } - selectedState.value = selected - } + val view = LocalView.current RadioButton( selected = selected, - onClick = onClick, + onClick = onClick?.let { + { + // Perform haptic feedback + if (!selected) view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK) + it() + } + }, modifier = modifier, enabled = enabled, colors = colors, diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticSwitch.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticSwitch.kt index fa3e894b..c2491397 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticSwitch.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticSwitch.kt @@ -1,4 +1,5 @@ package app.revanced.manager.ui.component.haptics + import android.os.Build import android.view.HapticFeedbackConstants import androidx.compose.foundation.interaction.MutableInteractionSource @@ -6,37 +7,31 @@ import androidx.compose.material3.Switch import androidx.compose.material3.SwitchColors import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalView @Composable fun HapticSwitch( checked: Boolean, - onCheckedChange: ((Boolean) -> Unit), + onCheckedChange: (Boolean) -> Unit, modifier: Modifier = Modifier, thumbContent: (@Composable () -> Unit)? = null, enabled: Boolean = true, colors: SwitchColors = SwitchDefaults.colors(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { - val checkedState = remember { mutableStateOf(checked) } - - // Perform haptic feedback - if (checkedState.value != checked) { - val view = LocalView.current - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - view.performHapticFeedback(if (checked) HapticFeedbackConstants.TOGGLE_ON else HapticFeedbackConstants.TOGGLE_OFF) - } else { - view.performHapticFeedback(if (checked) HapticFeedbackConstants.VIRTUAL_KEY else HapticFeedbackConstants.CLOCK_TICK) - } - checkedState.value = checked - } - Switch( checked = checked, - onCheckedChange = onCheckedChange, + onCheckedChange = { newChecked -> + val useNewConstants = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE + when { + newChecked && useNewConstants -> HapticFeedbackConstants.TOGGLE_ON + newChecked -> HapticFeedbackConstants.VIRTUAL_KEY + !newChecked && useNewConstants -> HapticFeedbackConstants.TOGGLE_OFF + !newChecked -> HapticFeedbackConstants.CLOCK_TICK + } + onCheckedChange(newChecked) + }, modifier = modifier, thumbContent = thumbContent, enabled = enabled, diff --git a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticTab.kt b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticTab.kt index 3b5a11e9..d0676951 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticTab.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/haptics/HapticTab.kt @@ -8,7 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalView +import app.revanced.manager.util.withHapticFeedback @Composable fun HapticTab ( @@ -22,16 +22,9 @@ fun HapticTab ( unselectedContentColor: Color = selectedContentColor, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } ) { - val view = LocalView.current - Tab( selected = selected, - onClick = { - // Perform haptic feedback - view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) - - onClick() - }, + onClick = onClick.withHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY), modifier = modifier, enabled = enabled, text = text, 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 fa8cae28..9082f4bb 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 @@ -4,6 +4,7 @@ import android.app.ActivityManager import android.content.ClipData import android.content.ClipboardManager import android.os.Build +import android.view.HapticFeedbackConstants import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.ExperimentalFoundationApi @@ -17,9 +18,7 @@ import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier -import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -35,6 +34,7 @@ import app.revanced.manager.ui.component.settings.IntegerItem import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.viewmodel.AdvancedSettingsViewModel import app.revanced.manager.util.toast +import app.revanced.manager.util.withHapticFeedback import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @@ -52,7 +52,6 @@ fun AdvancedSettingsScreen( activityManager.largeMemoryClass ) } - val haptics = LocalHapticFeedback.current Scaffold( topBar = { @@ -159,13 +158,12 @@ fun AdvancedSettingsScreen( onClick = { }, onLongClickLabel = stringResource(R.string.copy_to_clipboard), onLongClick = { - haptics.performHapticFeedback(HapticFeedbackType.LongPress) clipboard.setPrimaryClip( ClipData.newPlainText("Device Information", deviceContent) ) context.toast(context.getString(R.string.toast_copied_to_clipboard)) - } + }.withHapticFeedback(HapticFeedbackConstants.LONG_PRESS) ), headlineContent = stringResource(R.string.about_device), supportingContent = deviceContent diff --git a/app/src/main/java/app/revanced/manager/util/Util.kt b/app/src/main/java/app/revanced/manager/util/Util.kt index f1de38fd..bc48c54a 100644 --- a/app/src/main/java/app/revanced/manager/util/Util.kt +++ b/app/src/main/java/app/revanced/manager/util/Util.kt @@ -14,6 +14,7 @@ import androidx.annotation.StringRes import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable +import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -21,6 +22,7 @@ import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalView import androidx.core.net.toUri import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner @@ -218,4 +220,24 @@ fun ScrollState.isScrollingUp(): State { } val LazyListState.isScrollingUp: Boolean @Composable get() = this.isScrollingUp().value -val ScrollState.isScrollingUp: Boolean @Composable get() = this.isScrollingUp().value \ No newline at end of file +val ScrollState.isScrollingUp: Boolean @Composable get() = this.isScrollingUp().value + +@Composable +@ReadOnlyComposable +fun (() -> R).withHapticFeedback(constant: Int): () -> R { + val view = LocalView.current + return { + view.performHapticFeedback(constant) + this() + } +} + +@Composable +@ReadOnlyComposable +fun ((T) -> R).withHapticFeedback(constant: Int): (T) -> R { + val view = LocalView.current + return { + view.performHapticFeedback(constant) + this(it) + } +} \ No newline at end of file From 20c13ee71cb3ed868a0a9375e72d8e9660c10c75 Mon Sep 17 00:00:00 2001 From: Ax333l Date: Wed, 13 Nov 2024 22:11:36 +0100 Subject: [PATCH 10/10] chore: update dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🦀 integrations are gone! 🦀 --- app/build.gradle.kts | 21 +++--- .../1.json | 24 +++---- .../data/room/bundles/PatchBundleDao.kt | 8 +-- .../data/room/bundles/PatchBundleEntity.kt | 9 +-- .../manager/data/room/options/Option.kt | 23 ++++--- .../domain/bundles/LocalPatchBundle.kt | 19 ++---- .../domain/bundles/PatchBundleSource.kt | 9 ++- .../domain/bundles/RemotePatchBundle.kt | 67 +++++-------------- .../manager/domain/installer/RootInstaller.kt | 7 +- .../manager/domain/manager/KeystoreManager.kt | 16 +++-- .../domain/manager/PreferencesManager.kt | 1 - .../PatchBundlePersistenceRepository.kt | 9 ++- .../repository/PatchBundleRepository.kt | 8 +-- .../manager/network/dto/BundleInfo.kt | 9 --- .../manager/network/dto/PatchBundleInfo.kt | 7 ++ .../app/revanced/manager/patcher/Session.kt | 11 ++- .../manager/patcher/patch/PatchBundle.kt | 10 +-- .../manager/patcher/patch/PatchInfo.kt | 49 +++++--------- .../patcher/runtime/CoroutineRuntime.kt | 8 +-- .../manager/patcher/runtime/ProcessRuntime.kt | 4 +- .../manager/patcher/runtime/Runtime.kt | 1 - .../patcher/runtime/process/Parameters.kt | 2 - .../patcher/runtime/process/PatcherProcess.kt | 9 +-- .../manager/patcher/worker/PatcherWorker.kt | 2 +- .../manager/ui/component/AutoUpdatesDialog.kt | 4 +- .../bundle/BundleInformationDialog.kt | 2 +- .../manager/ui/component/bundle/BundleItem.kt | 2 +- .../ui/component/bundle/ImportBundleDialog.kt | 52 ++++---------- .../component/patcher/InstallPickerDialog.kt | 6 +- .../ui/component/patches/OptionFields.kt | 29 ++++---- .../manager/ui/screen/AppSelectorScreen.kt | 5 +- .../manager/ui/screen/DashboardScreen.kt | 4 +- .../ui/screen/PatchesSelectorScreen.kt | 4 +- .../ui/screen/VersionSelectorScreen.kt | 2 +- .../screen/settings/AdvancedSettingsScreen.kt | 6 -- .../ui/viewmodel/AppSelectorViewModel.kt | 2 +- .../ui/viewmodel/DashboardViewModel.kt | 7 +- .../manager/ui/viewmodel/PatcherViewModel.kt | 6 +- .../app/revanced/manager/util/Constants.kt | 10 +-- .../main/java/app/revanced/manager/util/PM.kt | 4 +- .../java/app/revanced/manager/util/Util.kt | 14 +++- app/src/main/res/values/strings.xml | 6 -- build.gradle.kts | 3 + gradle/libs.versions.toml | 35 +++++----- gradle/wrapper/gradle-wrapper.properties | 3 +- 45 files changed, 223 insertions(+), 316 deletions(-) delete mode 100644 app/src/main/java/app/revanced/manager/network/dto/BundleInfo.kt create mode 100644 app/src/main/java/app/revanced/manager/network/dto/PatchBundleInfo.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 70281945..9f04a1b9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,21 +3,22 @@ import kotlin.random.Random plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) + alias(libs.plugins.kotlin.serialization) + alias(libs.plugins.kotlin.parcelize) + alias(libs.plugins.compose.compiler) alias(libs.plugins.devtools) alias(libs.plugins.about.libraries) - id("kotlin-parcelize") - kotlin("plugin.serialization") version "1.9.23" } android { namespace = "app.revanced.manager" - compileSdk = 34 - buildToolsVersion = "34.0.0" + compileSdk = 35 + buildToolsVersion = "35.0.0" defaultConfig { applicationId = "app.revanced.manager" minSdk = 26 - targetSdk = 34 + targetSdk = 35 versionCode = 1 versionName = "0.0.1" vectorDrawables.useSupportLibrary = true @@ -81,9 +82,11 @@ android { jvmTarget = "17" } - buildFeatures.compose = true - buildFeatures.aidl = true - buildFeatures.buildConfig=true + buildFeatures { + compose = true + aidl = true + buildConfig = true + } android { androidResources { @@ -91,7 +94,6 @@ android { } } - composeOptions.kotlinCompilerExtensionVersion = "1.5.10" externalNativeBuild { cmake { path = file("src/main/cpp/CMakeLists.txt") @@ -112,7 +114,6 @@ dependencies { implementation(libs.runtime.compose) implementation(libs.splash.screen) implementation(libs.compose.activity) - implementation(libs.paging.common.ktx) implementation(libs.work.runtime.ktx) implementation(libs.preferences.datastore) 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 e9c0fd3a..eff10786 100644 --- a/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json +++ b/app/schemas/app.revanced.manager.data.room.AppDatabase/1.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "1dd9d5c0201fdf3cfef3ae669fd65e46", + "identityHash": "c385297c07ea54804dc8526c388f706d", "entities": [ { "tableName": "patch_bundles", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `name` TEXT NOT NULL, `source` TEXT NOT NULL, `auto_update` INTEGER NOT NULL, `version` TEXT, `integrations_version` TEXT, PRIMARY KEY(`uid`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`uid` INTEGER NOT NULL, `name` TEXT NOT NULL, `version` TEXT, `source` TEXT NOT NULL, `auto_update` INTEGER NOT NULL, PRIMARY KEY(`uid`))", "fields": [ { "fieldPath": "uid", @@ -20,6 +20,12 @@ "affinity": "TEXT", "notNull": true }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT", + "notNull": false + }, { "fieldPath": "source", "columnName": "source", @@ -31,18 +37,6 @@ "columnName": "auto_update", "affinity": "INTEGER", "notNull": true - }, - { - "fieldPath": "versionInfo.patches", - "columnName": "version", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "versionInfo.integrations", - "columnName": "integrations_version", - "affinity": "TEXT", - "notNull": false } ], "primaryKey": { @@ -397,7 +391,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, '1dd9d5c0201fdf3cfef3ae669fd65e46')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c385297c07ea54804dc8526c388f706d')" ] } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt index 77de9b03..d9955a70 100644 --- a/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt +++ b/app/src/main/java/app/revanced/manager/data/room/bundles/PatchBundleDao.kt @@ -8,11 +8,11 @@ interface PatchBundleDao { @Query("SELECT * FROM patch_bundles") suspend fun all(): List - @Query("SELECT version, integrations_version, auto_update FROM patch_bundles WHERE uid = :uid") + @Query("SELECT version, auto_update FROM patch_bundles WHERE uid = :uid") fun getPropsById(uid: Int): Flow - @Query("UPDATE patch_bundles SET version = :patches, integrations_version = :integrations WHERE uid = :uid") - suspend fun updateVersion(uid: Int, patches: String?, integrations: String?) + @Query("UPDATE patch_bundles SET version = :patches WHERE uid = :uid") + suspend fun updateVersion(uid: Int, patches: String?) @Query("UPDATE patch_bundles SET auto_update = :value WHERE uid = :uid") suspend fun setAutoUpdate(uid: Int, value: Boolean) @@ -26,7 +26,7 @@ interface PatchBundleDao { @Transaction suspend fun reset() { purgeCustomBundles() - updateVersion(0, null, null) // Reset the main source + updateVersion(0, null) // Reset the main source } @Query("DELETE FROM patch_bundles WHERE uid = :uid") 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 d120abf5..8ba5f64a 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 @@ -29,21 +29,16 @@ sealed class Source { } } -data class VersionInfo( - @ColumnInfo(name = "version") val patches: String? = null, - @ColumnInfo(name = "integrations_version") val integrations: String? = null, -) - @Entity(tableName = "patch_bundles") data class PatchBundleEntity( @PrimaryKey val uid: Int, @ColumnInfo(name = "name") val name: String, - @Embedded val versionInfo: VersionInfo, + @ColumnInfo(name = "version") val version: String? = null, @ColumnInfo(name = "source") val source: Source, @ColumnInfo(name = "auto_update") val autoUpdate: Boolean ) data class BundleProperties( - @Embedded val versionInfo: VersionInfo, + @ColumnInfo(name = "version") val version: String? = null, @ColumnInfo(name = "auto_update") val autoUpdate: Boolean ) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/data/room/options/Option.kt b/app/src/main/java/app/revanced/manager/data/room/options/Option.kt index b59dbd16..44bc3d40 100644 --- a/app/src/main/java/app/revanced/manager/data/room/options/Option.kt +++ b/app/src/main/java/app/revanced/manager/data/room/options/Option.kt @@ -20,6 +20,8 @@ import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.long import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.typeOf @Entity( tableName = "options", @@ -46,8 +48,8 @@ data class Option( val errorMessage = "Cannot deserialize value as ${option.type}" try { - if (option.type.endsWith("Array")) { - val elementType = option.type.removeSuffix("Array") + if (option.type.classifier == List::class) { + val elementType = option.type.arguments.first().type!! return raw.jsonArray.map { deserializeBasicType(elementType, it.jsonPrimitive) } } @@ -67,12 +69,17 @@ data class Option( allowSpecialFloatingPointValues = true } - private fun deserializeBasicType(type: String, value: JsonPrimitive) = when (type) { - "Boolean" -> value.boolean - "Int" -> value.int - "Long" -> value.long - "Float" -> value.float - "String" -> value.content.also { if (!value.isString) throw SerializationException("Expected value to be a string: $value") } + private fun deserializeBasicType(type: KType, value: JsonPrimitive) = when (type) { + typeOf() -> value.boolean + typeOf() -> value.int + typeOf() -> value.long + typeOf() -> value.float + typeOf() -> value.content.also { + if (!value.isString) throw SerializationException( + "Expected value to be a string: $value" + ) + } + else -> throw SerializationException("Unknown type: $type") } diff --git a/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt b/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt index 1d8b41f3..bcbc59cf 100644 --- a/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt +++ b/app/src/main/java/app/revanced/manager/domain/bundles/LocalPatchBundle.kt @@ -4,29 +4,18 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.io.File import java.io.InputStream -import java.nio.file.Files -import java.nio.file.StandardCopyOption class LocalPatchBundle(name: String, id: Int, directory: File) : PatchBundleSource(name, id, directory) { - suspend fun replace(patches: InputStream? = null, integrations: InputStream? = null) { + suspend fun replace(patches: InputStream) { withContext(Dispatchers.IO) { - patches?.let { inputStream -> - patchBundleOutputStream().use { outputStream -> - inputStream.copyTo(outputStream) - } - } - integrations?.let { - Files.copy( - it, - this@LocalPatchBundle.integrationsFile.toPath(), - StandardCopyOption.REPLACE_EXISTING - ) + patchBundleOutputStream().use { outputStream -> + patches.copyTo(outputStream) } } reload()?.also { - saveVersion(it.readManifestAttribute("Version"), null) + saveVersion(it.readManifestAttribute("Version")) } } } diff --git a/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt b/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt index 1ded6d43..308e2a56 100644 --- a/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt +++ b/app/src/main/java/app/revanced/manager/domain/bundles/PatchBundleSource.kt @@ -28,7 +28,6 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil protected val configRepository: PatchBundlePersistenceRepository by inject() private val app: Application by inject() protected val patchesFile = directory.resolve("patches.jar") - protected val integrationsFile = directory.resolve("integrations.apk") private val _state = MutableStateFlow(load()) val state = _state.asStateFlow() @@ -58,7 +57,7 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil if (!hasInstalled()) return State.Missing return try { - State.Loaded(PatchBundle(patchesFile, integrationsFile.takeIf(File::exists))) + State.Loaded(PatchBundle(patchesFile)) } catch (t: Throwable) { Log.e(tag, "Failed to load patch bundle with UID $uid", t) State.Failed(t) @@ -85,9 +84,9 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil 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?) = - configRepository.updateVersion(uid, patches, integrations) + suspend fun currentVersion() = getProps().version + protected suspend fun saveVersion(version: String?) = + configRepository.updateVersion(uid, version) suspend fun setName(name: String) { configRepository.setName(uid, name) diff --git a/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt b/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt index 8bbc230d..e3214db9 100644 --- a/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt +++ b/app/src/main/java/app/revanced/manager/domain/bundles/RemotePatchBundle.kt @@ -1,20 +1,12 @@ package app.revanced.manager.domain.bundles import androidx.compose.runtime.Stable -import app.revanced.manager.data.room.bundles.VersionInfo import app.revanced.manager.network.api.ReVancedAPI -import app.revanced.manager.network.api.ReVancedAPI.Extensions.findAssetByType -import app.revanced.manager.network.dto.BundleAsset -import app.revanced.manager.network.dto.BundleInfo +import app.revanced.manager.network.dto.PatchBundleInfo import app.revanced.manager.network.service.HttpService import app.revanced.manager.network.utils.getOrThrow -import app.revanced.manager.util.APK_MIMETYPE -import app.revanced.manager.util.JAR_MIMETYPE import io.ktor.client.request.url import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.core.component.inject import java.io.File @@ -24,27 +16,17 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo PatchBundleSource(name, id, directory) { protected val http: HttpService by inject() - protected abstract suspend fun getLatestInfo(): BundleInfo + protected abstract suspend fun getLatestInfo(): PatchBundleInfo - private suspend fun download(info: BundleInfo) = withContext(Dispatchers.IO) { - val (patches, integrations) = info - coroutineScope { - launch { - patchBundleOutputStream().use { - http.streamTo(it) { - url(patches.url) - } - } - } - - launch { - http.download(integrationsFile) { - url(integrations.url) - } + private suspend fun download(info: PatchBundleInfo) = withContext(Dispatchers.IO) { + val (version, url) = info + patchBundleOutputStream().use { + http.streamTo(it) { + url(url) } } - saveVersion(patches.version, integrations.version) + saveVersion(version) reload() } @@ -54,20 +36,15 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo suspend fun update(): Boolean = withContext(Dispatchers.IO) { val info = getLatestInfo() - if (hasInstalled() && VersionInfo( - info.patches.version, - info.integrations.version - ) == currentVersion() - ) { + if (hasInstalled() && info.version == currentVersion()) return@withContext false - } download(info) true } suspend fun deleteLocalFiles() = withContext(Dispatchers.Default) { - arrayOf(patchesFile, integrationsFile).forEach(File::delete) + patchesFile.delete() reload() } @@ -81,7 +58,7 @@ sealed class RemotePatchBundle(name: String, id: Int, directory: File, val endpo class JsonPatchBundle(name: String, id: Int, directory: File, endpoint: String) : RemotePatchBundle(name, id, directory, endpoint) { override suspend fun getLatestInfo() = withContext(Dispatchers.IO) { - http.request { + http.request { url(endpoint) }.getOrThrow() } @@ -91,22 +68,10 @@ class APIPatchBundle(name: String, id: Int, directory: File, endpoint: String) : RemotePatchBundle(name, id, directory, endpoint) { private val api: ReVancedAPI by inject() - override suspend fun getLatestInfo() = coroutineScope { - fun getAssetAsync(repo: String, mime: String) = async(Dispatchers.IO) { - api - .getLatestRelease(repo) - .getOrThrow() - .let { - BundleAsset(it.version, it.findAssetByType(mime).downloadUrl) - } + override suspend fun getLatestInfo() = api + .getLatestRelease("revanced-patches") + .getOrThrow() + .let { + PatchBundleInfo(it.version, it.assets.first { it.name.endsWith(".rvp") }.downloadUrl) } - - val patches = getAssetAsync("revanced-patches", JAR_MIMETYPE) - val integrations = getAssetAsync("revanced-integrations", APK_MIMETYPE) - - BundleInfo( - patches.await(), - integrations.await() - ) - } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt b/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt index 885f8ad1..293484ca 100644 --- a/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt +++ b/app/src/main/java/app/revanced/manager/domain/installer/RootInstaller.kt @@ -109,7 +109,12 @@ class RootInstaller( stockAPK?.let { stockApp -> pm.getPackageInfo(packageName)?.let { packageInfo -> - if (packageInfo.versionName <= version) + // TODO: get user id programmatically + if (pm.getVersionCode(packageInfo) <= pm.getVersionCode( + pm.getPackageInfo(patchedAPK) + ?: error("Failed to get package info for patched app") + ) + ) execute("pm uninstall -k --user 0 $packageName").assertSuccess("Failed to uninstall stock app") } diff --git a/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt b/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt index c1c4700d..4f9dc5a3 100644 --- a/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt +++ b/app/src/main/java/app/revanced/manager/domain/manager/KeystoreManager.kt @@ -12,6 +12,8 @@ import java.io.InputStream import java.io.OutputStream import java.nio.file.Files import java.security.UnrecoverableKeyException +import java.util.Date +import kotlin.time.Duration.Companion.days class KeystoreManager(app: Application, private val prefs: PreferencesManager) { companion object Constants { @@ -19,6 +21,7 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) { * Default alias and password for the keystore. */ const val DEFAULT = "ReVanced" + private val eightYearsFromNow get() = Date(System.currentTimeMillis() + (365.days * 8).inWholeMilliseconds * 24) } private val keystorePath = @@ -29,23 +32,26 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) { prefs.keystorePass.value = pass } - private suspend fun signingOptions(path: File = keystorePath) = ApkUtils.SigningOptions( + private suspend fun signingDetails(path: File = keystorePath) = ApkUtils.KeyStoreDetails( keyStore = path, keyStorePassword = null, alias = prefs.keystoreCommonName.get(), - signer = prefs.keystoreCommonName.get(), password = prefs.keystorePass.get() ) suspend fun sign(input: File, output: File) = withContext(Dispatchers.Default) { - ApkUtils.sign(input, output, signingOptions()) + ApkUtils.signApk(input, output, prefs.keystoreCommonName.get(), signingDetails()) } suspend fun regenerate() = withContext(Dispatchers.Default) { + val keyCertPair = ApkSigner.newPrivateKeyCertificatePair( + prefs.keystoreCommonName.get(), + eightYearsFromNow + ) val ks = ApkSigner.newKeyStore( setOf( ApkSigner.KeyStoreEntry( - DEFAULT, DEFAULT + DEFAULT, DEFAULT, keyCertPair ) ) ) @@ -64,7 +70,7 @@ class KeystoreManager(app: Application, private val prefs: PreferencesManager) { try { val ks = ApkSigner.readKeyStore(ByteArrayInputStream(keystoreData), null) - ApkSigner.readKeyCertificatePair(ks, cn, pass) + ApkSigner.readPrivateKeyCertificatePair(ks, cn, pass) } catch (_: UnrecoverableKeyException) { return false } catch (_: IllegalArgumentException) { 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 548df41a..8cdc1f19 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 @@ -12,7 +12,6 @@ class PreferencesManager( val api = stringPreference("api_url", "https://api.revanced.app") - val multithreadingDexFileWriter = booleanPreference("multithreading_dex_file_writer", true) val useProcessRuntime = booleanPreference("use_process_runtime", false) val patcherProcessMemoryLimit = intPreference("process_runtime_memory_limit", 700) diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt index 4b853ecf..5711d997 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundlePersistenceRepository.kt @@ -4,7 +4,6 @@ import app.revanced.manager.data.room.AppDatabase import app.revanced.manager.data.room.AppDatabase.Companion.generateUid import app.revanced.manager.data.room.bundles.PatchBundleEntity import app.revanced.manager.data.room.bundles.Source -import app.revanced.manager.data.room.bundles.VersionInfo import kotlinx.coroutines.flow.distinctUntilChanged class PatchBundlePersistenceRepository(db: AppDatabase) { @@ -26,7 +25,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) { PatchBundleEntity( uid = generateUid(), name = name, - versionInfo = VersionInfo(), + version = null, source = source, autoUpdate = autoUpdate ).also { @@ -35,8 +34,8 @@ class PatchBundlePersistenceRepository(db: AppDatabase) { suspend fun delete(uid: Int) = dao.remove(uid) - suspend fun updateVersion(uid: Int, patches: String?, integrations: String?) = - dao.updateVersion(uid, patches, integrations) + suspend fun updateVersion(uid: Int, version: String?) = + dao.updateVersion(uid, version) suspend fun setAutoUpdate(uid: Int, value: Boolean) = dao.setAutoUpdate(uid, value) @@ -48,7 +47,7 @@ class PatchBundlePersistenceRepository(db: AppDatabase) { val defaultSource = PatchBundleEntity( uid = 0, name = "", - versionInfo = VersionInfo(), + version = null, source = Source.API, autoUpdate = false ) diff --git a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt index f40d6c0b..79bb5cea 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/PatchBundleRepository.kt @@ -3,7 +3,7 @@ package app.revanced.manager.domain.repository import android.app.Application import android.content.Context import android.util.Log -import app.revanced.library.PatchUtils +import app.revanced.library.mostCommonCompatibleVersions import app.revanced.manager.R import app.revanced.manager.data.platform.NetworkInfo import app.revanced.manager.data.room.bundles.PatchBundleEntity @@ -55,7 +55,7 @@ class PatchBundleRepository( val allPatches = it.values.flatMap { bundle -> bundle.patches.map(PatchInfo::toPatcherPatch) }.toSet() - PatchUtils.getMostCommonCompatibleVersions(allPatches, countUnusedPatches = true) + allPatches.mostCommonCompatibleVersions(countUnusedPatches = true) .mapValues { (_, versions) -> if (versions.keys.size < 2) return@mapValues versions.keys.firstOrNull() @@ -137,11 +137,11 @@ class PatchBundleRepository( private fun addBundle(patchBundle: PatchBundleSource) = _sources.update { it.toMutableMap().apply { put(patchBundle.uid, patchBundle) } } - suspend fun createLocal(patches: InputStream, integrations: InputStream?) = withContext(Dispatchers.Default) { + suspend fun createLocal(patches: InputStream) = withContext(Dispatchers.Default) { val uid = persistenceRepo.create("", SourceInfo.Local).uid val bundle = LocalPatchBundle("", uid, directoryOf(uid)) - bundle.replace(patches, integrations) + bundle.replace(patches) addBundle(bundle) } diff --git a/app/src/main/java/app/revanced/manager/network/dto/BundleInfo.kt b/app/src/main/java/app/revanced/manager/network/dto/BundleInfo.kt deleted file mode 100644 index e2b56a87..00000000 --- a/app/src/main/java/app/revanced/manager/network/dto/BundleInfo.kt +++ /dev/null @@ -1,9 +0,0 @@ -package app.revanced.manager.network.dto - -import kotlinx.serialization.Serializable - -@Serializable -data class BundleInfo(val patches: BundleAsset, val integrations: BundleAsset) - -@Serializable -data class BundleAsset(val version: String, val url: String) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/network/dto/PatchBundleInfo.kt b/app/src/main/java/app/revanced/manager/network/dto/PatchBundleInfo.kt new file mode 100644 index 00000000..02d89919 --- /dev/null +++ b/app/src/main/java/app/revanced/manager/network/dto/PatchBundleInfo.kt @@ -0,0 +1,7 @@ +package app.revanced.manager.network.dto + +import kotlinx.serialization.Serializable + +@Serializable +// TODO: replace this +data class PatchBundleInfo(val version: String, val url: String) \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/Session.kt b/app/src/main/java/app/revanced/manager/patcher/Session.kt index 4393794d..d1368f24 100644 --- a/app/src/main/java/app/revanced/manager/patcher/Session.kt +++ b/app/src/main/java/app/revanced/manager/patcher/Session.kt @@ -22,7 +22,6 @@ class Session( cacheDir: String, frameworkDir: String, aaptPath: String, - multithreadingDexFileWriter: Boolean, private val androidContext: Context, private val logger: Logger, private val input: File, @@ -38,8 +37,7 @@ class Session( apkFile = input, temporaryFilesPath = tempDir, frameworkFileDirectory = frameworkDir, - aaptBinaryPath = aaptPath, - multithreadingDexFileWriter = multithreadingDexFileWriter, + aaptBinaryPath = aaptPath ) ) @@ -51,7 +49,7 @@ class Session( state = State.RUNNING ) - this.apply(true).collect { (patch, exception) -> + this().collect { (patch, exception) -> if (patch !in selectedPatches) return@collect if (exception != null) { @@ -89,7 +87,7 @@ class Session( ) } - suspend fun run(output: File, selectedPatches: PatchList, integrations: List) { + suspend fun run(output: File, selectedPatches: PatchList) { updateProgress(state = State.COMPLETED) // Unpacking java.util.logging.Logger.getLogger("").apply { @@ -103,8 +101,7 @@ class Session( with(patcher) { logger.info("Merging integrations") - acceptIntegrations(integrations.toSet()) - acceptPatches(selectedPatches.toSet()) + this += selectedPatches.toSet() logger.info("Applying patches...") applyPatchesVerbose(selectedPatches.sortedBy { it.name }) diff --git a/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt b/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt index 8dbcf153..2b93a829 100644 --- a/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt +++ b/app/src/main/java/app/revanced/manager/patcher/patch/PatchBundle.kt @@ -2,17 +2,17 @@ package app.revanced.manager.patcher.patch import android.util.Log import app.revanced.manager.util.tag -import app.revanced.patcher.PatchBundleLoader import app.revanced.patcher.patch.Patch +import app.revanced.patcher.patch.PatchLoader import java.io.File import java.io.IOException import java.util.jar.JarFile -class PatchBundle(val patchesJar: File, val integrations: File?) { +class PatchBundle(val patchesJar: File) { private val loader = object : Iterable> { private fun load(): Iterable> { patchesJar.setReadOnly() - return PatchBundleLoader.Dex(patchesJar, optimizedDexDirectory = null) + return PatchLoader.Dex(setOf(patchesJar)) } override fun iterator(): Iterator> = load().iterator() @@ -41,12 +41,12 @@ class PatchBundle(val patchesJar: File, val integrations: File?) { /** * Load all patches compatible with the specified package. */ - fun patchClasses(packageName: String) = loader.filter { patch -> + fun patches(packageName: String) = loader.filter { patch -> val compatiblePackages = patch.compatiblePackages ?: // The patch has no compatibility constraints, which means it is universal. return@filter true - if (!compatiblePackages.any { it.name == packageName }) { + if (!compatiblePackages.any { (name, _) -> name == packageName }) { // Patch is not compatible with this package. return@filter false } diff --git a/app/src/main/java/app/revanced/manager/patcher/patch/PatchInfo.kt b/app/src/main/java/app/revanced/manager/patcher/patch/PatchInfo.kt index 31e707ba..cd2a2f83 100644 --- a/app/src/main/java/app/revanced/manager/patcher/patch/PatchInfo.kt +++ b/app/src/main/java/app/revanced/manager/patcher/patch/PatchInfo.kt @@ -1,14 +1,14 @@ package app.revanced.manager.patcher.patch import androidx.compose.runtime.Immutable -import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.patch.Patch -import app.revanced.patcher.patch.ResourcePatch -import app.revanced.patcher.patch.options.PatchOption +import app.revanced.patcher.patch.Option as PatchOption +import app.revanced.patcher.patch.resourcePatch import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableSet import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableSet +import kotlin.reflect.KType data class PatchInfo( val name: String, @@ -21,7 +21,12 @@ data class PatchInfo( patch.name.orEmpty(), patch.description, patch.use, - patch.compatiblePackages?.map { CompatiblePackage(it) }?.toImmutableList(), + patch.compatiblePackages?.map { (pkgName, versions) -> + CompatiblePackage( + pkgName, + versions?.toImmutableSet() + ) + }?.toImmutableList(), patch.options.map { (_, option) -> Option(option) }.ifEmpty { null }?.toImmutableList() ) @@ -45,37 +50,19 @@ data class PatchInfo( * The resulting patch cannot be executed. * This is necessary because some functions in ReVanced Library only accept full [Patch] objects. */ - fun toPatcherPatch(): Patch<*> = object : ResourcePatch( - name = name, - description = description, - compatiblePackages = compatiblePackages - ?.map(app.revanced.manager.patcher.patch.CompatiblePackage::toPatcherCompatiblePackage) - ?.toSet(), - use = include, - ) { - override fun execute(context: ResourceContext) = - throw Exception("Metadata patches cannot be executed") - } + fun toPatcherPatch(): Patch<*> = + resourcePatch(name = name, description = description, use = include) { + compatiblePackages?.let { pkgs -> + compatibleWith(*pkgs.map { it.packageName to it.versions }.toTypedArray()) + } + } } @Immutable data class CompatiblePackage( val packageName: String, val versions: ImmutableSet? -) { - constructor(pkg: Patch.CompatiblePackage) : this( - pkg.name, - pkg.versions?.toImmutableSet() - ) - - /** - * Converts this [CompatiblePackage] into a [Patch.CompatiblePackage] from patcher. - */ - fun toPatcherCompatiblePackage() = Patch.CompatiblePackage( - name = packageName, - versions = versions, - ) -} +) @Immutable data class Option( @@ -83,7 +70,7 @@ data class Option( val key: String, val description: String, val required: Boolean, - val type: String, + val type: KType, val default: T?, val presets: Map?, val validator: (T?) -> Boolean, @@ -93,7 +80,7 @@ data class Option( option.key, option.description.orEmpty(), option.required, - option.valueType, + option.type, option.default, option.values, { option.validator(option, it) }, diff --git a/app/src/main/java/app/revanced/manager/patcher/runtime/CoroutineRuntime.kt b/app/src/main/java/app/revanced/manager/patcher/runtime/CoroutineRuntime.kt index e2aed2ee..3780e899 100644 --- a/app/src/main/java/app/revanced/manager/patcher/runtime/CoroutineRuntime.kt +++ b/app/src/main/java/app/revanced/manager/patcher/runtime/CoroutineRuntime.kt @@ -27,15 +27,13 @@ class CoroutineRuntime(private val context: Context) : Runtime(context) { val selectedBundles = selectedPatches.keys val allPatches = bundles.filterKeys { selectedBundles.contains(it) } - .mapValues { (_, bundle) -> bundle.patchClasses(packageName) } + .mapValues { (_, bundle) -> bundle.patches(packageName) } val patchList = selectedPatches.flatMap { (bundle, selected) -> allPatches[bundle]?.filter { selected.contains(it.name) } ?: throw IllegalArgumentException("Patch bundle $bundle does not exist") } - val integrations = bundles.mapNotNull { (_, bundle) -> bundle.integrations } - // Set all patch options. options.forEach { (bundle, bundlePatchOptions) -> val patches = allPatches[bundle] ?: return@forEach @@ -53,7 +51,6 @@ class CoroutineRuntime(private val context: Context) : Runtime(context) { cacheDir, frameworkPath, aaptPath, - enableMultithreadedDexWriter(), context, logger, File(inputFile), @@ -62,8 +59,7 @@ class CoroutineRuntime(private val context: Context) : Runtime(context) { ).use { session -> session.run( File(outputFile), - patchList, - integrations + patchList ) } } diff --git a/app/src/main/java/app/revanced/manager/patcher/runtime/ProcessRuntime.kt b/app/src/main/java/app/revanced/manager/patcher/runtime/ProcessRuntime.kt index 389d5201..ada1d943 100644 --- a/app/src/main/java/app/revanced/manager/patcher/runtime/ProcessRuntime.kt +++ b/app/src/main/java/app/revanced/manager/patcher/runtime/ProcessRuntime.kt @@ -70,7 +70,7 @@ class ProcessRuntime(private val context: Context) : Runtime(context) { onProgress: ProgressEventHandler, ) = coroutineScope { // Get the location of our own Apk. - val managerBaseApk = pm.getPackageInfo(context.packageName)!!.applicationInfo.sourceDir + val managerBaseApk = pm.getPackageInfo(context.packageName)!!.applicationInfo!!.sourceDir val limit = "${prefs.patcherProcessMemoryLimit.get()}M" val propOverride = resolvePropOverride(context)?.absolutePath @@ -148,13 +148,11 @@ class ProcessRuntime(private val context: Context) : Runtime(context) { packageName = packageName, inputFile = inputFile, outputFile = outputFile, - enableMultithrededDexWriter = enableMultithreadedDexWriter(), configurations = selectedPatches.map { (id, patches) -> val bundle = bundles[id]!! PatchConfiguration( bundle.patchesJar.absolutePath, - bundle.integrations?.absolutePath, patches, options[id].orEmpty() ) diff --git a/app/src/main/java/app/revanced/manager/patcher/runtime/Runtime.kt b/app/src/main/java/app/revanced/manager/patcher/runtime/Runtime.kt index fd39c3f3..434c97c6 100644 --- a/app/src/main/java/app/revanced/manager/patcher/runtime/Runtime.kt +++ b/app/src/main/java/app/revanced/manager/patcher/runtime/Runtime.kt @@ -26,7 +26,6 @@ sealed class Runtime(context: Context) : KoinComponent { context.cacheDir.resolve("framework").also { it.mkdirs() }.absolutePath protected suspend fun bundles() = patchBundlesRepo.bundles.first() - protected suspend fun enableMultithreadedDexWriter() = prefs.multithreadingDexFileWriter.get() abstract suspend fun execute( inputFile: String, diff --git a/app/src/main/java/app/revanced/manager/patcher/runtime/process/Parameters.kt b/app/src/main/java/app/revanced/manager/patcher/runtime/process/Parameters.kt index c669c875..b00d558a 100644 --- a/app/src/main/java/app/revanced/manager/patcher/runtime/process/Parameters.kt +++ b/app/src/main/java/app/revanced/manager/patcher/runtime/process/Parameters.kt @@ -12,14 +12,12 @@ data class Parameters( val packageName: String, val inputFile: String, val outputFile: String, - val enableMultithrededDexWriter: Boolean, val configurations: List, ) : Parcelable @Parcelize data class PatchConfiguration( val bundlePath: String, - val integrationsPath: String?, val patches: Set, val options: @RawValue Map> ) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/patcher/runtime/process/PatcherProcess.kt b/app/src/main/java/app/revanced/manager/patcher/runtime/process/PatcherProcess.kt index 4467f3ae..b0f8e248 100644 --- a/app/src/main/java/app/revanced/manager/patcher/runtime/process/PatcherProcess.kt +++ b/app/src/main/java/app/revanced/manager/patcher/runtime/process/PatcherProcess.kt @@ -54,13 +54,11 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() { logger.info("Memory limit: ${Runtime.getRuntime().maxMemory() / (1024 * 1024)}MB") - val integrations = - parameters.configurations.mapNotNull { it.integrationsPath?.let(::File) } val patchList = parameters.configurations.flatMap { config -> - val bundle = PatchBundle(File(config.bundlePath), null) + val bundle = PatchBundle(File(config.bundlePath)) val patches = - bundle.patchClasses(parameters.packageName).filter { it.name in config.patches } + bundle.patches(parameters.packageName).filter { it.name in config.patches } .associateBy { it.name } config.options.forEach { (patchName, opts) -> @@ -81,7 +79,6 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() { cacheDir = parameters.cacheDir, aaptPath = parameters.aaptPath, frameworkDir = parameters.frameworkDir, - multithreadingDexFileWriter = parameters.enableMultithrededDexWriter, androidContext = context, logger = logger, input = File(parameters.inputFile), @@ -90,7 +87,7 @@ class PatcherProcess(private val context: Context) : IPatcherProcess.Stub() { events.progress(name, state?.name, message) } ).use { - it.run(File(parameters.outputFile), patchList, integrations) + it.run(File(parameters.outputFile), patchList) } events.finished(null) 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 a5c551a4..c295bde1 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 @@ -154,7 +154,7 @@ class PatcherWorker( } is SelectedApp.Local -> selectedApp.file.also { args.setInputFile(it) } - is SelectedApp.Installed -> File(pm.getPackageInfo(selectedApp.packageName)!!.applicationInfo.sourceDir) + is SelectedApp.Installed -> File(pm.getPackageInfo(selectedApp.packageName)!!.applicationInfo!!.sourceDir) } val runtime = if (prefs.useProcessRuntime.get()) { diff --git a/app/src/main/java/app/revanced/manager/ui/component/AutoUpdatesDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/AutoUpdatesDialog.kt index 1e2234eb..29f2f970 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/AutoUpdatesDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/AutoUpdatesDialog.kt @@ -24,6 +24,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import app.revanced.manager.R import app.revanced.manager.ui.component.haptics.HapticCheckbox +import app.revanced.manager.util.transparentListItemColors @Composable fun AutoUpdatesDialog(onSubmit: (Boolean, Boolean) -> Unit) { @@ -77,5 +78,6 @@ private fun AutoUpdatesItem( leadingContent = { Icon(icon, null) }, headlineContent = { Text(stringResource(headline)) }, trailingContent = { HapticCheckbox(checked = checked, onCheckedChange = null) }, - modifier = Modifier.clickable { onCheckedChange(!checked) } + modifier = Modifier.clickable { onCheckedChange(!checked) }, + colors = transparentListItemColors ) diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt index f5919ced..83c60e0f 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleInformationDialog.kt @@ -104,7 +104,7 @@ fun BundleInformationDialog( name = bundleName, remoteUrl = bundle.asRemoteOrNull?.endpoint, patchCount = patchCount, - version = props?.versionInfo?.patches, + version = props?.version, autoUpdate = props?.autoUpdate ?: false, onAutoUpdateChange = { composableScope.launch { diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt index 2fdd8f5d..6f3ae914 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/BundleItem.kt @@ -45,7 +45,7 @@ fun BundleItem( val state by bundle.state.collectAsStateWithLifecycle() val version by remember(bundle) { - bundle.propsFlow().map { props -> props?.versionInfo?.patches } + bundle.propsFlow().map { props -> props?.version } }.collectAsStateWithLifecycle(null) val name by bundle.nameState diff --git a/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt index 2de10053..cbc699ec 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/bundle/ImportBundleDialog.kt @@ -23,19 +23,18 @@ import app.revanced.manager.ui.component.TextHorizontalPadding import app.revanced.manager.ui.component.haptics.HapticCheckbox import app.revanced.manager.ui.component.haptics.HapticRadioButton import app.revanced.manager.ui.model.BundleType -import app.revanced.manager.util.APK_MIMETYPE -import app.revanced.manager.util.JAR_MIMETYPE +import app.revanced.manager.util.BIN_MIMETYPE +import app.revanced.manager.util.transparentListItemColors @Composable fun ImportPatchBundleDialog( onDismiss: () -> Unit, onRemoteSubmit: (String, Boolean) -> Unit, - onLocalSubmit: (Uri, Uri?) -> Unit + onLocalSubmit: (Uri) -> Unit ) { var currentStep by rememberSaveable { mutableIntStateOf(0) } var bundleType by rememberSaveable { mutableStateOf(BundleType.Remote) } var patchBundle by rememberSaveable { mutableStateOf(null) } - var integrations by rememberSaveable { mutableStateOf(null) } var remoteUrl by rememberSaveable { mutableStateOf("") } var autoUpdate by rememberSaveable { mutableStateOf(false) } @@ -45,16 +44,7 @@ fun ImportPatchBundleDialog( } fun launchPatchActivity() { - patchActivityLauncher.launch(JAR_MIMETYPE) - } - - val integrationsActivityLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> - uri?.let { integrations = it } - } - - fun launchIntegrationsActivity() { - integrationsActivityLauncher.launch(APK_MIMETYPE) + patchActivityLauncher.launch(BIN_MIMETYPE) } val steps = listOf<@Composable () -> Unit>( @@ -67,11 +57,9 @@ fun ImportPatchBundleDialog( ImportBundleStep( bundleType, patchBundle, - integrations, remoteUrl, autoUpdate, { launchPatchActivity() }, - { launchIntegrationsActivity() }, { remoteUrl = it }, { autoUpdate = it } ) @@ -99,13 +87,7 @@ fun ImportPatchBundleDialog( enabled = inputsAreValid, onClick = { when (bundleType) { - BundleType.Local -> patchBundle?.let { - onLocalSubmit( - it, - integrations - ) - } - + BundleType.Local -> patchBundle?.let(onLocalSubmit) BundleType.Remote -> onRemoteSubmit(remoteUrl, autoUpdate) } } @@ -159,7 +141,8 @@ fun SelectBundleTypeStep( selected = bundleType == BundleType.Remote, onClick = null ) - } + }, + colors = transparentListItemColors ) HorizontalDivider(modifier = Modifier.padding(horizontal = 16.dp)) ListItem( @@ -175,7 +158,8 @@ fun SelectBundleTypeStep( selected = bundleType == BundleType.Local, onClick = null ) - } + }, + colors = transparentListItemColors ) } } @@ -186,11 +170,9 @@ fun SelectBundleTypeStep( fun ImportBundleStep( bundleType: BundleType, patchBundle: Uri?, - integrations: Uri?, remoteUrl: String, autoUpdate: Boolean, launchPatchActivity: () -> Unit, - launchIntegrationsActivity: () -> Unit, onRemoteUrlChange: (String) -> Unit, onAutoUpdateChange: (Boolean) -> Unit ) { @@ -210,19 +192,8 @@ fun ImportBundleStep( Icon(imageVector = Icons.Default.Topic, contentDescription = null) } }, - modifier = Modifier.clickable { launchPatchActivity() } - ) - ListItem( - headlineContent = { - Text(stringResource(R.string.integrations_field)) - }, - supportingContent = { Text(stringResource(if (integrations != null) R.string.file_field_set else R.string.file_field_not_set)) }, - trailingContent = { - IconButton(onClick = launchIntegrationsActivity) { - Icon(imageVector = Icons.Default.Topic, contentDescription = null) - } - }, - modifier = Modifier.clickable { launchIntegrationsActivity() } + modifier = Modifier.clickable { launchPatchActivity() }, + colors = transparentListItemColors ) } } @@ -256,6 +227,7 @@ fun ImportBundleStep( ) } }, + colors = transparentListItemColors ) } } diff --git a/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt b/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt index bb667bad..b86124d9 100644 --- a/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt +++ b/app/src/main/java/app/revanced/manager/ui/component/patcher/InstallPickerDialog.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.res.stringResource import app.revanced.manager.R import app.revanced.manager.data.room.apps.installed.InstallType import app.revanced.manager.ui.component.haptics.HapticRadioButton +import app.revanced.manager.util.transparentListItemColors @Composable fun InstallPickerDialog( @@ -41,7 +42,7 @@ fun InstallPickerDialog( title = { Text(stringResource(R.string.select_install_type)) }, text = { Column { - InstallType.values().forEach { + InstallType.entries.forEach { ListItem( modifier = Modifier.clickable { selectedInstallType = it }, leadingContent = { @@ -50,7 +51,8 @@ fun InstallPickerDialog( onClick = null ) }, - headlineContent = { Text(stringResource(it.stringResource)) } + headlineContent = { Text(stringResource(it.stringResource)) }, + colors = transparentListItemColors ) } } 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 993270ea..3c0504bc 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 @@ -50,6 +50,7 @@ import app.revanced.manager.util.mutableStateSetOf import app.revanced.manager.util.saver.snapshotStateListSaver import app.revanced.manager.util.saver.snapshotStateSetSaver import app.revanced.manager.util.toast +import app.revanced.manager.util.transparentListItemColors import kotlinx.parcelize.Parcelize import org.koin.compose.koinInject import org.koin.core.component.KoinComponent @@ -58,6 +59,7 @@ import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.rememberReorderableLazyColumnState import java.io.Serializable import kotlin.random.Random +import kotlin.reflect.typeOf import androidx.compose.ui.window.Dialog as ComposeDialog private class OptionEditorScope( @@ -96,17 +98,17 @@ private interface OptionEditor { fun Dialog(scope: OptionEditorScope) } +private inline fun OptionEditor.toMapEditorElements() = arrayOf( + typeOf() to this, + typeOf>() to ListOptionEditor(this) +) + private val optionEditors = mapOf( - "Boolean" to BooleanOptionEditor, - "String" to StringOptionEditor, - "Int" to IntOptionEditor, - "Long" to LongOptionEditor, - "Float" to FloatOptionEditor, - "BooleanArray" to ListOptionEditor(BooleanOptionEditor), - "StringArray" to ListOptionEditor(StringOptionEditor), - "IntArray" to ListOptionEditor(IntOptionEditor), - "LongArray" to ListOptionEditor(LongOptionEditor), - "FloatArray" to ListOptionEditor(FloatOptionEditor), + *BooleanOptionEditor.toMapEditorElements(), + *StringOptionEditor.toMapEditorElements(), + *IntOptionEditor.toMapEditorElements(), + *LongOptionEditor.toMapEditorElements(), + *FloatOptionEditor.toMapEditorElements() ) @Composable @@ -145,7 +147,7 @@ fun OptionItem(option: Option, value: T?, setValue: (T?) -> Unit) { val baseOptionEditor = optionEditors.getOrDefault(option.type, UnknownTypeEditor) as OptionEditor - if (option.type != "Boolean" && option.presets != null) PresetOptionEditor(baseOptionEditor) + if (option.type != typeOf() && option.presets != null) PresetOptionEditor(baseOptionEditor) else baseOptionEditor } @@ -405,7 +407,8 @@ private class PresetOptionEditor(private val innerEditor: OptionEditor< selected = selectedPreset == presetKey, onClick = { selectedPreset = presetKey } ) - } + }, + colors = transparentListItemColors ) } @@ -430,7 +433,7 @@ private class ListOptionEditor(private val elementEditor: Opti option.key, option.description, option.required, - option.type.removeSuffix("Array"), + option.type.arguments.first().type!!, null, null ) { true } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/AppSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/AppSelectorScreen.kt index 6d2e1d5f..eb814c3b 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/AppSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/AppSelectorScreen.kt @@ -33,6 +33,7 @@ import app.revanced.manager.ui.component.SearchView import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.viewmodel.AppSelectorViewModel import app.revanced.manager.util.APK_MIMETYPE +import app.revanced.manager.util.transparentListItemColors import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @@ -110,9 +111,9 @@ fun AppSelectorScreen( ) ) } - } + }, + colors = transparentListItemColors ) - } } } else { 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 157986ec..51caeb25 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 @@ -79,9 +79,9 @@ fun DashboardScreen( if (showAddBundleDialog) { ImportPatchBundleDialog( onDismiss = { showAddBundleDialog = false }, - onLocalSubmit = { patches, integrations -> + onLocalSubmit = { patches -> showAddBundleDialog = false - vm.createLocalSource(patches, integrations) + vm.createLocalSource(patches) }, onRemoteSubmit = { url, autoUpdate -> showAddBundleDialog = false 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 511ede21..511a1c36 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 @@ -46,6 +46,7 @@ import app.revanced.manager.ui.viewmodel.PatchesSelectorViewModel.Companion.SHOW import app.revanced.manager.util.Options import app.revanced.manager.util.PatchSelection import app.revanced.manager.util.isScrollingUp +import app.revanced.manager.util.transparentListItemColors import kotlinx.coroutines.launch @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) @@ -480,7 +481,8 @@ private fun ListHeader( ) } } - } + }, + colors = transparentListItemColors ) } diff --git a/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt index cd18dcc7..a8d12d50 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/VersionSelectorScreen.kt @@ -104,7 +104,7 @@ fun VersionSelectorScreen( viewModel.installedApp?.let { (packageInfo, installedApp) -> SelectedApp.Installed( packageName = viewModel.packageName, - version = packageInfo.versionName + version = packageInfo.versionName!! ).let { item { SelectedAppItem( 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 9082f4bb..cb474f09 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 @@ -102,12 +102,6 @@ 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( 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 85cee8d1..2acfdcd5 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 @@ -69,7 +69,7 @@ class AppSelectorViewModel( pm.getPackageInfo(this)?.let { packageInfo -> SelectedApp.Local( packageName = packageInfo.packageName, - version = packageInfo.versionName, + version = packageInfo.versionName!!, file = this, temporary = true ) 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 ce68249d..5a019c51 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 @@ -95,13 +95,10 @@ class DashboardViewModel( selectedSources.clear() } - fun createLocalSource(patchBundle: Uri, integrations: Uri?) = + fun createLocalSource(patchBundle: Uri) = viewModelScope.launch { contentResolver.openInputStream(patchBundle)!!.use { patchesStream -> - integrations?.let { contentResolver.openInputStream(it) } - .use { integrationsStream -> - patchBundleRepository.createLocal(patchesStream, integrationsStream) - } + patchBundleRepository.createLocal(patchesStream) } } 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 a63feed7..00c1231f 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 @@ -152,8 +152,8 @@ class PatcherViewModel( ) val patcherSucceeded = - workManager.getWorkInfoByIdLiveData(patcherWorkerId).map { workInfo: WorkInfo -> - when (workInfo.state) { + workManager.getWorkInfoByIdLiveData(patcherWorkerId).map { workInfo: WorkInfo? -> + when (workInfo?.state) { WorkInfo.State.SUCCEEDED -> true WorkInfo.State.FAILED -> false else -> null @@ -308,7 +308,7 @@ class PatcherViewModel( // Check for base APK, first check if the app is already installed if (existingPackageInfo == null) { // If the app is not installed, check if the output file is a base apk - if (currentPackageInfo.splitNames != null) { + if (currentPackageInfo.splitNames.isNotEmpty()) { // Exit if there is no base APK package installerStatusDialogModel.packageInstallerStatus = PackageInstaller.STATUS_FAILURE_INVALID diff --git a/app/src/main/java/app/revanced/manager/util/Constants.kt b/app/src/main/java/app/revanced/manager/util/Constants.kt index 983a7c42..000da463 100644 --- a/app/src/main/java/app/revanced/manager/util/Constants.kt +++ b/app/src/main/java/app/revanced/manager/util/Constants.kt @@ -1,14 +1,8 @@ package app.revanced.manager.util -private const val team = "revanced" -const val ghOrganization = "https://github.com/$team" -const val ghCli = "$team/revanced-cli" -const val ghPatches = "$team/revanced-patches" -const val ghPatcher = "$team/revanced-patcher" -const val ghManager = "$team/revanced-manager" -const val ghIntegrations = "$team/revanced-integrations" const val tag = "ReVanced Manager" const val JAR_MIMETYPE = "application/java-archive" const val APK_MIMETYPE = "application/vnd.android.package-archive" -const val JSON_MIMETYPE = "application/json" \ No newline at end of file +const val JSON_MIMETYPE = "application/json" +const val BIN_MIMETYPE = "application/octet-stream" \ No newline at end of file diff --git a/app/src/main/java/app/revanced/manager/util/PM.kt b/app/src/main/java/app/revanced/manager/util/PM.kt index 0d7a822b..d0e6dbb4 100644 --- a/app/src/main/java/app/revanced/manager/util/PM.kt +++ b/app/src/main/java/app/revanced/manager/util/PM.kt @@ -106,7 +106,7 @@ class PM( val pkgInfo = app.packageManager.getPackageArchiveInfo(path, 0) ?: return null // This is needed in order to load label and icon. - pkgInfo.applicationInfo.apply { + pkgInfo.applicationInfo!!.apply { sourceDir = path publicSourceDir = path } @@ -114,7 +114,7 @@ class PM( return pkgInfo } - fun PackageInfo.label() = this.applicationInfo.loadLabel(app.packageManager).toString() + fun PackageInfo.label() = this.applicationInfo!!.loadLabel(app.packageManager).toString() fun getVersionCode(packageInfo: PackageInfo) = PackageInfoCompat.getLongVersionCode(packageInfo) diff --git a/app/src/main/java/app/revanced/manager/util/Util.kt b/app/src/main/java/app/revanced/manager/util/Util.kt index bc48c54a..8fb13c88 100644 --- a/app/src/main/java/app/revanced/manager/util/Util.kt +++ b/app/src/main/java/app/revanced/manager/util/Util.kt @@ -13,6 +13,8 @@ import android.widget.Toast import androidx.annotation.StringRes import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material3.ListItemColors +import androidx.compose.material3.ListItemDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.State @@ -240,4 +242,14 @@ fun ((T) -> R).withHapticFeedback(constant: Int): (T) -> R { view.performHapticFeedback(constant) this(it) } -} \ No newline at end of file +} + +private var transparentListItemColorsCached: ListItemColors? = null + +/** + * The default ListItem colors, but with [ListItemColors.containerColor] set to [Color.Transparent]. + */ +val transparentListItemColors + @Composable get() = transparentListItemColorsCached + ?: ListItemDefaults.colors(containerColor = Color.Transparent) + .also { transparentListItemColorsCached = it } \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0b260bb8..20722c02 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,7 +2,6 @@ ReVanced Manager Patcher Patches - Integrations CLI Manager @@ -20,7 +19,6 @@ Import patch bundle Bundle patches Patch bundle - Integrations Selected Not selected @@ -71,8 +69,6 @@ Adapt colors to the wallpaper Theme Choose between light or dark theme - Multi-threaded DEX file writer - Use multiple cores to write DEX files. This is faster, but uses more memory Safeguards Disable version compatibility check The check restricts patches to supported app versions @@ -123,7 +119,6 @@ Search apps… Loading… Downloading patch bundle… - Downloading Integrations… Options OK @@ -186,7 +181,6 @@ Download APK file Failed to download patch bundle: %s Failed to load updated patch bundle: %s - Failed to update integrations: %s No patched apps found Tap on the patches to get more information about them %s selected diff --git a/build.gradle.kts b/build.gradle.kts index 89d27215..ca1372dd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,5 +2,8 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.devtools) apply false alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.serialization) apply false + alias(libs.plugins.kotlin.parcelize) apply false + alias(libs.plugins.compose.compiler) apply false alias(libs.plugins.about.libraries) apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1a3f425a..7c4dafcc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,31 +1,30 @@ [versions] -ktx = "1.13.1" -material3 = "1.2.1" -ui-tooling = "1.6.8" -viewmodel-lifecycle = "2.8.3" +ktx = "1.15.0" +material3 = "1.3.1" +ui-tooling = "1.7.5" +viewmodel-lifecycle = "2.8.7" splash-screen = "1.0.1" -compose-activity = "1.9.0" -paging = "3.3.0" +compose-activity = "1.9.3" preferences-datastore = "1.1.1" -work-runtime = "2.9.0" -compose-bom = "2024.06.00" +work-runtime = "2.10.0" +compose-bom = "2024.10.01" accompanist = "0.34.0" placeholder = "1.1.2" reorderable = "1.5.2" -serialization = "1.6.3" -collection = "0.3.7" +serialization = "1.7.3" +collection = "0.3.8" room-version = "2.6.1" -revanced-patcher = "19.3.1" -revanced-library = "2.2.1" +revanced-patcher = "21.0.0" +revanced-library = "3.0.2" koin-version = "3.5.3" koin-version-compose = "3.5.3" reimagined-navigation = "1.5.0" ktor = "2.3.9" markdown-renderer = "0.22.0" fading-edges = "1.0.4" -android-gradle-plugin = "8.3.2" -kotlin-gradle-plugin = "1.9.22" -dev-tools-gradle-plugin = "1.9.22-1.0.17" +kotlin = "2.0.21" +android-gradle-plugin = "8.7.2" +dev-tools-gradle-plugin = "2.0.21-1.0.27" about-libraries-gradle-plugin = "11.1.1" coil = "2.6.0" app-icon-loader-coil = "1.5.0" @@ -44,7 +43,6 @@ runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", ve runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "viewmodel-lifecycle" } splash-screen = { group = "androidx.core", name = "core-splashscreen", version.ref = "splash-screen" } compose-activity = { group = "androidx.activity", name = "activity-compose", version.ref = "compose-activity" } -paging-common-ktx = { group = "androidx.paging", name = "paging-common-ktx", version.ref = "paging" } work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work-runtime" } preferences-datastore = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "preferences-datastore" } @@ -135,6 +133,9 @@ compose-icons-fontawesome = { group = "com.github.BenjaminHalko.compose-icons", [plugins] android-application = { id = "com.android.application", version.ref = "android-gradle-plugin" } -kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin-gradle-plugin" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } +compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } devtools = { id = "com.google.devtools.ksp", version.ref = "dev-tools-gradle-plugin" } about-libraries = { id = "com.mikepenz.aboutlibraries.plugin", version.ref = "about-libraries-gradle-plugin" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22ce..dfe2d1c1 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ +#Tue Nov 12 21:36:50 CET 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME