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 2fcac8d3..a940e05d 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 @@ -15,7 +15,7 @@ class LocalPatchBundle(name: String, id: Int, directory: File) : } reload()?.also { - saveVersionHash(it.readManifestAttribute("Version")) + saveVersionHash(it.patchBundleManifestAttributes?.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 ee46cc71..1bc8a7f8 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 @@ -38,7 +38,7 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil suspend fun getName() = nameFlow.first() - val versionFlow = state.map { it.patchBundleOrNull()?.readManifestAttribute("Version") } + val versionFlow = state.map { it.patchBundleOrNull()?.patchBundleManifestAttributes?.version } val patchCountFlow = state.map { it.patchBundleOrNull()?.patches?.size ?: 0 } /** @@ -74,7 +74,7 @@ sealed class PatchBundleSource(initialName: String, val uid: Int, directory: Fil val bundle = newState.patchBundleOrNull() // Try to read the name from the patch bundle manifest if the bundle does not have a name. if (bundle != null && _nameFlow.value.isEmpty()) { - bundle.readManifestAttribute("Name")?.let { setName(it) } + bundle.patchBundleManifestAttributes?.name?.let { setName(it) } } return bundle diff --git a/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt b/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt index b4598fb9..e8536de1 100644 --- a/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt +++ b/app/src/main/java/app/revanced/manager/domain/repository/DownloadedAppRepository.kt @@ -40,6 +40,8 @@ class DownloadedAppRepository( data: Parcelable, expectedPackageName: String, expectedVersion: String?, + appCompatibilityCheck: Boolean, + patchesCompatibilityCheck: Boolean, onDownload: suspend (downloadProgress: Pair) -> Unit, ): File { // Converted integers cannot contain / or .. unlike the package name or version, so they are safer to use here. @@ -96,7 +98,12 @@ class DownloadedAppRepository( val pkgInfo = pm.getPackageInfo(targetFile.toFile()) ?: error("Downloaded APK file is invalid") if (pkgInfo.packageName != expectedPackageName) error("Downloaded APK has the wrong package name. Expected: $expectedPackageName, Actual: ${pkgInfo.packageName}") - if (expectedVersion != null && pkgInfo.versionName != expectedVersion) error("Downloaded APK has the wrong version. Expected: $expectedVersion, Actual: ${pkgInfo.versionName}") + expectedVersion?.let { + if ( + pkgInfo.versionName != expectedVersion && + (appCompatibilityCheck || patchesCompatibilityCheck) + ) error("The selected app version ($pkgInfo.versionName) doesn't match the suggested version. Please use the suggested version ($expectedVersion), or adjust your settings by disabling \"Require suggested app version\" and enabling \"Disable version compatibility check\".") + } // Delete the previous copy (if present). dao.get(pkgInfo.packageName, pkgInfo.versionName!!)?.directory?.let { 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 2b93a829..e894f748 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 @@ -8,6 +8,17 @@ import java.io.File import java.io.IOException import java.util.jar.JarFile +class PatchBundleManifestAttributes( + val name: String?, + val version: String?, + val description: String?, + val source: String?, + val author: String?, + val contact: String?, + val website: String?, + val license: String? +) + class PatchBundle(val patchesJar: File) { private val loader = object : Iterable> { private fun load(): Iterable> { @@ -36,7 +47,20 @@ class PatchBundle(val patchesJar: File) { null } - fun readManifestAttribute(name: String) = manifest?.mainAttributes?.getValue(name) + val patchBundleManifestAttributes = if(manifest != null) + PatchBundleManifestAttributes( + name = readManifestAttribute("name"), + version = readManifestAttribute("version"), + description = readManifestAttribute("description"), + source = readManifestAttribute("source"), + author = readManifestAttribute("author"), + contact = readManifestAttribute("contact"), + website = readManifestAttribute("website"), + license = readManifestAttribute("license") + ) else + null + + private fun readManifestAttribute(name: String) = manifest?.mainAttributes?.getValue(name)?.takeIf { it.isNotBlank() } // If empty, set it to null instead. /** * Load all patches compatible with the specified package. @@ -53,4 +77,4 @@ class PatchBundle(val patchesJar: File) { true } -} \ No newline at end of file +} 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 5096170c..0708817d 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 @@ -14,9 +14,9 @@ import android.os.Parcelable import android.os.PowerManager import android.util.Log import androidx.activity.result.ActivityResult -import androidx.core.content.ContextCompat import androidx.work.ForegroundInfo import androidx.work.WorkerParameters +import app.revanced.manager.MainActivity import app.revanced.manager.R import app.revanced.manager.data.platform.Filesystem import app.revanced.manager.data.room.apps.installed.InstallType @@ -88,22 +88,25 @@ class PatcherWorker( ) private fun createNotification(): Notification { - val notificationIntent = Intent(applicationContext, PatcherWorker::class.java) - val pendingIntent: PendingIntent = PendingIntent.getActivity( + val notificationIntent = Intent(applicationContext, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP + } + val pendingIntent = PendingIntent.getActivity( applicationContext, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE ) val channel = NotificationChannel( - "revanced-patcher-patching", "Patching", NotificationManager.IMPORTANCE_HIGH + "revanced-patcher-patching", "Patching", NotificationManager.IMPORTANCE_LOW ) val notificationManager = - ContextCompat.getSystemService(applicationContext, NotificationManager::class.java) - notificationManager!!.createNotificationChannel(channel) + applicationContext.getSystemService(NotificationManager::class.java) + notificationManager.createNotificationChannel(channel) return Notification.Builder(applicationContext, channel.id) - .setContentTitle(applicationContext.getText(R.string.app_name)) - .setContentText(applicationContext.getText(R.string.patcher_notification_message)) - .setLargeIcon(Icon.createWithResource(applicationContext, R.drawable.ic_notification)) + .setContentTitle(applicationContext.getText(R.string.patcher_notification_title)) + .setContentText(applicationContext.getText(R.string.patcher_notification_text)) .setSmallIcon(Icon.createWithResource(applicationContext, R.drawable.ic_notification)) - .setContentIntent(pendingIntent).build() + .setContentIntent(pendingIntent) + .setCategory(Notification.CATEGORY_SERVICE) + .build() } override suspend fun doWork(): Result { @@ -158,6 +161,8 @@ class PatcherWorker( data, args.packageName, args.input.version, + prefs.suggestedVersionSafeguard.get(), + !prefs.disablePatchVersionCompatCheck.get(), onDownload = args.onDownloadProgress ).also { args.setInputFile(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 dfc63735..0ec27d8d 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,13 +2,26 @@ package app.revanced.manager.ui.component.bundle import android.webkit.URLUtil import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowRight -import androidx.compose.material.icons.outlined.Extension -import androidx.compose.material.icons.outlined.Inventory2 +import androidx.compose.material.icons.automirrored.outlined.Send +import androidx.compose.material.icons.outlined.Commit +import androidx.compose.material.icons.outlined.Description +import androidx.compose.material.icons.outlined.Gavel +import androidx.compose.material.icons.outlined.Language +import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.Sell -import androidx.compose.material3.* +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -17,10 +30,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.LocalUriHandler 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.patcher.patch.PatchBundleManifestAttributes import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.TextInputDialog import app.revanced.manager.ui.component.haptics.HapticSwitch @@ -29,12 +43,12 @@ import app.revanced.manager.ui.component.haptics.HapticSwitch fun BaseBundleDialog( modifier: Modifier = Modifier, isDefault: Boolean, - name: String?, remoteUrl: String?, onRemoteUrlChange: ((String) -> Unit)? = null, patchCount: Int, version: String?, autoUpdate: Boolean, + bundleManifestAttributes: PatchBundleManifestAttributes?, onAutoUpdateChange: (Boolean) -> Unit, onPatchesClick: () -> Unit, extraFields: @Composable ColumnScope.() -> Unit = {} @@ -48,35 +62,26 @@ fun BaseBundleDialog( 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, - ) - } + version?.let { + Tag(Icons.Outlined.Sell, it) } - 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()) + bundleManifestAttributes?.description?.let { + Tag(Icons.Outlined.Description, it) + } + bundleManifestAttributes?.source?.let { + Tag(Icons.Outlined.Commit, it) + } + bundleManifestAttributes?.author?.let { + Tag(Icons.Outlined.Person, it) + } + bundleManifestAttributes?.contact?.let { + Tag(Icons.AutoMirrored.Outlined.Send, it) + } + bundleManifestAttributes?.website?.let { + Tag(Icons.Outlined.Language, it, isUrl = true) + } + bundleManifestAttributes?.license?.let { + Tag(Icons.Outlined.Gavel, it) } } @@ -138,8 +143,8 @@ fun BaseBundleDialog( val patchesClickable = patchCount > 0 BundleListItem( - headlineText = stringResource(R.string.patches), - supportingText = stringResource(R.string.bundle_view_patches), + headlineText = stringResource(R.string.bundle_view_patches), + supportingText = stringResource(R.string.bundle_view_all_patches, patchCount), modifier = Modifier.clickable( enabled = patchesClickable, onClick = onPatchesClick @@ -160,22 +165,34 @@ fun BaseBundleDialog( @Composable private fun Tag( icon: ImageVector, - text: String + text: String, + isUrl: Boolean = false ) { + val uriHandler = LocalUriHandler.current + Row( horizontalArrangement = Arrangement.spacedBy(6.dp), - verticalAlignment = Alignment.CenterVertically + verticalAlignment = Alignment.CenterVertically, + modifier = if (isUrl) { + Modifier + .clickable { + try { + uriHandler.openUri(text) + } catch (_: Exception) {} + } + } + else + Modifier, ) { Icon( imageVector = icon, contentDescription = null, - modifier = Modifier.size(16.dp), - tint = MaterialTheme.colorScheme.outline, + modifier = Modifier.size(16.dp) ) Text( text, style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.outline, + color = if(isUrl) MaterialTheme.colorScheme.primary else 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 79816443..8a6768f6 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 @@ -45,13 +45,14 @@ fun BundleInformationDialog( }.collectAsStateWithLifecycle(null) val patchCount by bundle.patchCountFlow.collectAsStateWithLifecycle(0) val version by bundle.versionFlow.collectAsStateWithLifecycle(null) + val bundleManifestAttributes = state.patchBundleOrNull()?.patchBundleManifestAttributes if (viewCurrentBundlePatches) { BundlePatchesDialog( onDismissRequest = { viewCurrentBundlePatches = false }, - bundle = bundle, + bundle = bundle ) } @@ -63,7 +64,7 @@ fun BundleInformationDialog( Scaffold( topBar = { BundleTopBar( - title = stringResource(R.string.patch_bundle_field), + title = bundleName, onBackClick = onDismissRequest, backIcon = { Icon( @@ -105,11 +106,11 @@ fun BundleInformationDialog( BaseBundleDialog( modifier = Modifier.padding(paddingValues), isDefault = bundle.isDefault, - name = bundleName, remoteUrl = bundle.asRemoteOrNull?.endpoint, patchCount = patchCount, version = version, autoUpdate = props?.autoUpdate == true, + bundleManifestAttributes = bundleManifestAttributes, onAutoUpdateChange = { composableScope.launch { bundle.asRemoteOrNull?.setAutoUpdate(it) 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 e71ae480..bf1a9e98 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 @@ -211,7 +211,7 @@ fun ImportBundleStep( ) } Column( - modifier = Modifier.padding(horizontal = 8.dp) + modifier = Modifier.padding(horizontal = 8.dp, vertical = 5.dp) ) { ListItem( modifier = Modifier.clickable( 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 c2491397..f1e26e2c 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 @@ -9,6 +9,7 @@ import androidx.compose.material3.SwitchDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView @Composable fun HapticSwitch( @@ -20,16 +21,19 @@ fun HapticSwitch( colors: SwitchColors = SwitchDefaults.colors(), interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, ) { + val view = LocalView.current Switch( checked = checked, onCheckedChange = { newChecked -> val useNewConstants = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE - when { + val hapticFeedbackType = when { newChecked && useNewConstants -> HapticFeedbackConstants.TOGGLE_ON newChecked -> HapticFeedbackConstants.VIRTUAL_KEY !newChecked && useNewConstants -> HapticFeedbackConstants.TOGGLE_OFF !newChecked -> HapticFeedbackConstants.CLOCK_TICK + else -> {HapticFeedbackConstants.VIRTUAL_KEY} } + view.performHapticFeedback(hapticFeedbackType) onCheckedChange(newChecked) }, modifier = modifier, diff --git a/app/src/main/java/app/revanced/manager/ui/model/BundleInfo.kt b/app/src/main/java/app/revanced/manager/ui/model/BundleInfo.kt index cfef366f..0fb01a76 100644 --- a/app/src/main/java/app/revanced/manager/ui/model/BundleInfo.kt +++ b/app/src/main/java/app/revanced/manager/ui/model/BundleInfo.kt @@ -23,8 +23,6 @@ data class BundleInfo( yieldAll(universal) } - val patchCount get() = compatible.size + incompatible.size + universal.size - fun patchSequence(allowIncompatible: Boolean) = if (allowIncompatible) { all } else { @@ -79,7 +77,7 @@ data class BundleInfo( targetList.add(it) } - BundleInfo(source.getName(), bundle.readManifestAttribute("Version"), source.uid, compatible, incompatible, universal) + BundleInfo(source.getName(), bundle.patchBundleManifestAttributes?.version, source.uid, compatible, incompatible, universal) } } 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 22860800..c7f07264 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 @@ -410,6 +410,7 @@ fun PatchesSelectorScreen( modifier = Modifier .fillMaxSize() .padding(paddingValues) + .padding(top = 16.dp) ) { if (bundles.size > 1) { ScrollableTabRow( diff --git a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt index c3762772..c613a179 100644 --- a/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt +++ b/app/src/main/java/app/revanced/manager/ui/screen/settings/update/UpdatesSettingsScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.res.stringResource 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.settings.BooleanItem import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.viewmodel.UpdatesSettingsViewModel @@ -50,6 +51,8 @@ fun UpdatesSettingsScreen( .fillMaxSize() .padding(paddingValues) ) { + GroupHeader(stringResource(R.string.manager)) + SettingsListItem( modifier = Modifier.clickable { coroutineScope.launch { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2148b082..31730c8c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -317,7 +317,8 @@ Saving Write patched APK file Sign patched APK file - Patching in progress… + Patching in progress… + Tap to return to the patcher Stop patcher Are you sure you want to stop the patching process? Execute patches @@ -353,6 +354,7 @@ Auto update Automatically update this bundle when ReVanced starts View patches + View all %d patches Any version Any package Delete bundle