mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2026-01-18 16:53:56 +00:00
chore: Merge branch 'compose-dev' into feat/tooltip
This commit is contained in:
@@ -15,7 +15,7 @@ class LocalPatchBundle(name: String, id: Int, directory: File) :
|
||||
}
|
||||
|
||||
reload()?.also {
|
||||
saveVersionHash(it.readManifestAttribute("Version"))
|
||||
saveVersionHash(it.patchBundleManifestAttributes?.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,6 +40,8 @@ class DownloadedAppRepository(
|
||||
data: Parcelable,
|
||||
expectedPackageName: String,
|
||||
expectedVersion: String?,
|
||||
appCompatibilityCheck: Boolean,
|
||||
patchesCompatibilityCheck: Boolean,
|
||||
onDownload: suspend (downloadProgress: Pair<Long, Long?>) -> 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 {
|
||||
|
||||
@@ -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<Patch<*>> {
|
||||
private fun load(): Iterable<Patch<*>> {
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -410,6 +410,7 @@ fun PatchesSelectorScreen(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
.padding(top = 16.dp)
|
||||
) {
|
||||
if (bundles.size > 1) {
|
||||
ScrollableTabRow(
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -317,7 +317,8 @@
|
||||
<string name="patcher_step_group_saving">Saving</string>
|
||||
<string name="patcher_step_write_patched">Write patched APK file</string>
|
||||
<string name="patcher_step_sign_apk">Sign patched APK file</string>
|
||||
<string name="patcher_notification_message">Patching in progress…</string>
|
||||
<string name="patcher_notification_title">Patching in progress…</string>
|
||||
<string name="patcher_notification_text">Tap to return to the patcher</string>
|
||||
<string name="patcher_stop_confirm_title">Stop patcher</string>
|
||||
<string name="patcher_stop_confirm_description">Are you sure you want to stop the patching process?</string>
|
||||
<string name="execute_patches">Execute patches</string>
|
||||
@@ -353,6 +354,7 @@
|
||||
<string name="bundle_auto_update">Auto update</string>
|
||||
<string name="bundle_auto_update_description">Automatically update this bundle when ReVanced starts</string>
|
||||
<string name="bundle_view_patches">View patches</string>
|
||||
<string name="bundle_view_all_patches">View all %d patches</string>
|
||||
<string name="bundle_view_patches_any_version">Any version</string>
|
||||
<string name="bundle_view_patches_any_package">Any package</string>
|
||||
<string name="bundle_delete_single_dialog_title">Delete bundle</string>
|
||||
|
||||
Reference in New Issue
Block a user