Compare commits

..

1 Commits

Author SHA1 Message Date
111c74329f feat: Enable tooltip for unobvious elements 2025-07-06 00:47:40 +07:00
22 changed files with 501 additions and 236 deletions

View File

@ -40,8 +40,6 @@ class DownloadedAppRepository(
data: Parcelable, data: Parcelable,
expectedPackageName: String, expectedPackageName: String,
expectedVersion: String?, expectedVersion: String?,
appCompatibilityCheck: Boolean,
patchesCompatibilityCheck: Boolean,
onDownload: suspend (downloadProgress: Pair<Long, Long?>) -> Unit, onDownload: suspend (downloadProgress: Pair<Long, Long?>) -> Unit,
): File { ): File {
// Converted integers cannot contain / or .. unlike the package name or version, so they are safer to use here. // Converted integers cannot contain / or .. unlike the package name or version, so they are safer to use here.
@ -98,12 +96,7 @@ class DownloadedAppRepository(
val pkgInfo = val pkgInfo =
pm.getPackageInfo(targetFile.toFile()) ?: error("Downloaded APK file is invalid") 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 (pkgInfo.packageName != expectedPackageName) error("Downloaded APK has the wrong package name. Expected: $expectedPackageName, Actual: ${pkgInfo.packageName}")
expectedVersion?.let { if (expectedVersion != null && pkgInfo.versionName != expectedVersion) error("Downloaded APK has the wrong version. Expected: $expectedVersion, Actual: ${pkgInfo.versionName}")
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). // Delete the previous copy (if present).
dao.get(pkgInfo.packageName, pkgInfo.versionName!!)?.directory?.let { dao.get(pkgInfo.packageName, pkgInfo.versionName!!)?.directory?.let {

View File

@ -14,9 +14,9 @@ import android.os.Parcelable
import android.os.PowerManager import android.os.PowerManager
import android.util.Log import android.util.Log
import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResult
import androidx.core.content.ContextCompat
import androidx.work.ForegroundInfo import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import app.revanced.manager.MainActivity
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.data.platform.Filesystem import app.revanced.manager.data.platform.Filesystem
import app.revanced.manager.data.room.apps.installed.InstallType import app.revanced.manager.data.room.apps.installed.InstallType
@ -88,25 +88,22 @@ class PatcherWorker(
) )
private fun createNotification(): Notification { private fun createNotification(): Notification {
val notificationIntent = Intent(applicationContext, MainActivity::class.java).apply { val notificationIntent = Intent(applicationContext, PatcherWorker::class.java)
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP val pendingIntent: PendingIntent = PendingIntent.getActivity(
}
val pendingIntent = PendingIntent.getActivity(
applicationContext, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE applicationContext, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
) )
val channel = NotificationChannel( val channel = NotificationChannel(
"revanced-patcher-patching", "Patching", NotificationManager.IMPORTANCE_LOW "revanced-patcher-patching", "Patching", NotificationManager.IMPORTANCE_HIGH
) )
val notificationManager = val notificationManager =
applicationContext.getSystemService(NotificationManager::class.java) ContextCompat.getSystemService(applicationContext, NotificationManager::class.java)
notificationManager.createNotificationChannel(channel) notificationManager!!.createNotificationChannel(channel)
return Notification.Builder(applicationContext, channel.id) return Notification.Builder(applicationContext, channel.id)
.setContentTitle(applicationContext.getText(R.string.patcher_notification_title)) .setContentTitle(applicationContext.getText(R.string.app_name))
.setContentText(applicationContext.getText(R.string.patcher_notification_text)) .setContentText(applicationContext.getText(R.string.patcher_notification_message))
.setLargeIcon(Icon.createWithResource(applicationContext, R.drawable.ic_notification))
.setSmallIcon(Icon.createWithResource(applicationContext, R.drawable.ic_notification)) .setSmallIcon(Icon.createWithResource(applicationContext, R.drawable.ic_notification))
.setContentIntent(pendingIntent) .setContentIntent(pendingIntent).build()
.setCategory(Notification.CATEGORY_SERVICE)
.build()
} }
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
@ -161,8 +158,6 @@ class PatcherWorker(
data, data,
args.packageName, args.packageName,
args.input.version, args.input.version,
prefs.suggestedVersionSafeguard.get(),
!prefs.disablePatchVersionCompatCheck.get(),
onDownload = args.onDownloadProgress onDownload = args.onDownloadProgress
).also { ).also {
args.setInputFile(it) args.setInputFile(it)

View File

@ -69,8 +69,13 @@ fun AppTopBar(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
if (onBackClick != null) { if (onBackClick != null) {
IconButton(onClick = onBackClick) { TooltipWrap(
backIcon() modifier = Modifier,
tooltip = stringResource(R.string.back),
) {
IconButton(onClick = onBackClick) {
backIcon()
}
} }
} }
}, },
@ -108,8 +113,13 @@ fun AppTopBar(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
if (onBackClick != null) { if (onBackClick != null) {
IconButton(onClick = onBackClick) { TooltipWrap(
backIcon() modifier = Modifier,
tooltip = stringResource(R.string.back),
) {
IconButton(onClick = onBackClick) {
backIcon()
}
} }
} }
}, },

View File

@ -27,14 +27,19 @@ fun ArrowButton(
) )
onClick?.let { onClick?.let {
IconButton(onClick = it) { TooltipWrap(
Icon( modifier = Modifier,
imageVector = Icons.Filled.KeyboardArrowUp, tooltip = stringResource(description),
contentDescription = stringResource(description), ) {
modifier = Modifier IconButton(onClick = it) {
.rotate(rotation) Icon(
.then(modifier) imageVector = Icons.Filled.KeyboardArrowUp,
) contentDescription = stringResource(description),
modifier = Modifier
.rotate(rotation)
.then(modifier)
)
}
} }
} ?: Icon( } ?: Icon(
imageVector = Icons.Filled.KeyboardArrowUp, imageVector = Icons.Filled.KeyboardArrowUp,

View File

@ -39,25 +39,30 @@ fun ExceptionViewerDialog(text: String, onDismiss: () -> Unit) {
) )
}, },
actions = { actions = {
IconButton( TooltipWrap(
onClick = { modifier = Modifier,
val sendIntent: Intent = Intent().apply { tooltip = stringResource(R.string.share),
action = Intent.ACTION_SEND
putExtra(
Intent.EXTRA_TEXT,
text
)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
context.startActivity(shareIntent)
}
) { ) {
Icon( IconButton(
Icons.Outlined.Share, onClick = {
contentDescription = stringResource(R.string.share) val sendIntent: Intent = Intent().apply {
) action = Intent.ACTION_SEND
putExtra(
Intent.EXTRA_TEXT,
text
)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
context.startActivity(shareIntent)
}
) {
Icon(
Icons.Outlined.Share,
contentDescription = stringResource(R.string.share)
)
}
} }
} }
) )

View File

@ -138,12 +138,17 @@ fun NotificationCard(
) )
} }
if (onDismiss != null) { if (onDismiss != null) {
IconButton(onClick = onDismiss) { TooltipWrap(
Icon( modifier = modifier,
imageVector = Icons.Outlined.Close, tooltip = stringResource(R.string.close),
contentDescription = stringResource(R.string.close), ) {
tint = color, IconButton(onClick = onDismiss) {
) Icon(
imageVector = Icons.Outlined.Close,
contentDescription = stringResource(R.string.close),
tint = color,
)
}
} }
} }
} }

View File

@ -33,13 +33,18 @@ fun PasswordField(modifier: Modifier = Modifier, value: String, onValueChange: (
label = label, label = label,
modifier = modifier, modifier = modifier,
trailingIcon = { trailingIcon = {
IconButton(onClick = { TooltipWrap(
visible = !visible modifier = modifier,
}) { tooltip = if (visible) stringResource(R.string.show_password_field) else stringResource(R.string.hide_password_field),
val (icon, description) = remember(visible) { ) {
if (visible) Icons.Outlined.VisibilityOff to R.string.hide_password_field else Icons.Outlined.Visibility to R.string.show_password_field IconButton(onClick = {
visible = !visible
}) {
val (icon, description) = remember(visible) {
if (visible) Icons.Outlined.VisibilityOff to R.string.hide_password_field else Icons.Outlined.Visibility to R.string.show_password_field
}
Icon(icon, stringResource(description))
} }
Icon(icon, stringResource(description))
} }
}, },
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(

View File

@ -48,11 +48,16 @@ fun SearchView(
onExpandedChange = onActiveChange, onExpandedChange = onActiveChange,
placeholder = placeholder, placeholder = placeholder,
leadingIcon = { leadingIcon = {
IconButton(onClick = { onActiveChange(false) }) { TooltipWrap(
Icon( modifier = Modifier,
Icons.AutoMirrored.Filled.ArrowBack, tooltip = stringResource(R.string.back),
stringResource(R.string.back) ) {
) IconButton(onClick = { onActiveChange(false) }) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
stringResource(R.string.back)
)
}
} }
} }
) )

View File

@ -0,0 +1,94 @@
package app.revanced.manager.ui.component
import androidx.annotation.StringRes
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.window.PopupPositionProvider
/**
* Wraps a composable with a tooltip.
*
* @param modifier the [Modifier] to be applied to the [TooltipBox]
* @param tooltip the text to be displayed in the [TooltipBox]
* @param positionProvider Anchor point for the tooltip, defaults to [TooltipDefaults.rememberPlainTooltipPositionProvider]
* @param content The composable UI to be wrapped with [TooltipBox]
*
* @see [TooltipBox]
*/
@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun TooltipWrap(
modifier: Modifier,
tooltip: String,
positionProvider: PopupPositionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
content: @Composable () -> Unit
) {
val tooltipState = rememberTooltipState()
val haptic = LocalHapticFeedback.current
LaunchedEffect(tooltipState.isVisible) {
if (tooltipState.isVisible) {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}
}
TooltipBox(
modifier = modifier,
positionProvider = positionProvider,
tooltip = {
PlainTooltip { Text(tooltip) }
},
state = tooltipState
) {
content()
}
}
/**
* Wraps a composable with a tooltip.
*
* @param modifier the [Modifier] to be applied to the [TooltipBox]
* @param tooltip the R.string to be displayed in the [TooltipBox]
* @param positionProvider Anchor point for the tooltip, defaults to [TooltipDefaults.rememberPlainTooltipPositionProvider]
* @param content The composable UI to be wrapped with [TooltipBox]
*
* @see [TooltipBox]
*/
@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun TooltipWrap(
modifier: Modifier,
@StringRes tooltip: Int,
positionProvider: PopupPositionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
content: @Composable () -> Unit
) {
val tooltipState = rememberTooltipState()
val haptic = LocalHapticFeedback.current
LaunchedEffect(tooltipState.isVisible) {
if (tooltipState.isVisible) {
haptic.performHapticFeedback(HapticFeedbackType.LongPress)
}
}
TooltipBox(
modifier = modifier,
positionProvider = positionProvider,
tooltip = {
PlainTooltip { Text(stringResource(tooltip)) }
},
state = tooltipState
) {
content()
}
}

View File

@ -22,6 +22,7 @@ import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.isDefaul
import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState import app.revanced.manager.domain.bundles.PatchBundleSource.Extensions.nameState
import app.revanced.manager.ui.component.ExceptionViewerDialog import app.revanced.manager.ui.component.ExceptionViewerDialog
import app.revanced.manager.ui.component.FullscreenDialog import app.revanced.manager.ui.component.FullscreenDialog
import app.revanced.manager.ui.component.TooltipWrap
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.koin.compose.koinInject import org.koin.compose.koinInject
@ -72,19 +73,29 @@ fun BundleInformationDialog(
}, },
actions = { actions = {
if (!bundle.isDefault) { if (!bundle.isDefault) {
IconButton(onClick = onDeleteRequest) { TooltipWrap(
Icon( modifier = Modifier,
Icons.Outlined.DeleteOutline, tooltip = stringResource(R.string.delete),
stringResource(R.string.delete) ) {
) IconButton(onClick = onDeleteRequest) {
Icon(
Icons.Outlined.DeleteOutline,
stringResource(R.string.delete)
)
}
} }
} }
if (!isLocal && hasNetwork) { if (!isLocal && hasNetwork) {
IconButton(onClick = onUpdate) { TooltipWrap(
Icon( modifier = Modifier,
Icons.Outlined.Update, tooltip = stringResource(R.string.refresh),
stringResource(R.string.refresh) ) {
) IconButton(onClick = onUpdate) {
Icon(
Icons.Outlined.Update,
stringResource(R.string.refresh)
)
}
} }
} }
} }

View File

@ -10,7 +10,11 @@ import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.material3.surfaceColorAtElevation
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import app.revanced.manager.R
import app.revanced.manager.ui.component.TooltipWrap
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -33,8 +37,13 @@ fun BundleTopBar(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { navigationIcon = {
if (onBackClick != null) { if (onBackClick != null) {
IconButton(onClick = onBackClick) { TooltipWrap(
backIcon() modifier = Modifier,
tooltip = stringResource(R.string.back),
) {
IconButton(onClick = onBackClick) {
backIcon()
}
} }
} }
}, },

View File

@ -189,6 +189,7 @@ fun ImportBundleStep(
}, },
supportingContent = { Text(stringResource(if (patchBundle != null) R.string.file_field_set else R.string.file_field_not_set)) }, supportingContent = { Text(stringResource(if (patchBundle != null) R.string.file_field_set else R.string.file_field_not_set)) },
trailingContent = { trailingContent = {
// TODO: Determine if this button should be [TooltipWrap]'ped
IconButton(onClick = launchPatchActivity) { IconButton(onClick = launchPatchActivity) {
Icon(imageVector = Icons.Default.Topic, contentDescription = null) Icon(imageVector = Icons.Default.Topic, contentDescription = null)
} }

View File

@ -65,6 +65,7 @@ import app.revanced.manager.ui.component.FloatInputDialog
import app.revanced.manager.ui.component.FullscreenDialog import app.revanced.manager.ui.component.FullscreenDialog
import app.revanced.manager.ui.component.IntInputDialog import app.revanced.manager.ui.component.IntInputDialog
import app.revanced.manager.ui.component.LongInputDialog import app.revanced.manager.ui.component.LongInputDialog
import app.revanced.manager.ui.component.TooltipWrap
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.component.haptics.HapticRadioButton import app.revanced.manager.ui.component.haptics.HapticRadioButton
import app.revanced.manager.ui.component.haptics.HapticSwitch import app.revanced.manager.ui.component.haptics.HapticSwitch
@ -74,13 +75,11 @@ import app.revanced.manager.util.saver.snapshotStateListSaver
import app.revanced.manager.util.saver.snapshotStateSetSaver import app.revanced.manager.util.saver.snapshotStateSetSaver
import app.revanced.manager.util.toast import app.revanced.manager.util.toast
import app.revanced.manager.util.transparentListItemColors import app.revanced.manager.util.transparentListItemColors
import kotlinx.coroutines.CoroutineScope
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.koin.compose.koinInject import org.koin.compose.koinInject
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get import org.koin.core.component.get
import sh.calvin.reorderable.ReorderableItem import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyColumnState
import sh.calvin.reorderable.rememberReorderableLazyListState import sh.calvin.reorderable.rememberReorderableLazyListState
import java.io.Serializable import java.io.Serializable
import kotlin.random.Random import kotlin.random.Random
@ -113,8 +112,13 @@ private interface OptionEditor<T : Any> {
@Composable @Composable
fun ListItemTrailingContent(scope: OptionEditorScope<T>) { fun ListItemTrailingContent(scope: OptionEditorScope<T>) {
IconButton(onClick = { clickAction(scope) }) { TooltipWrap(
Icon(Icons.Outlined.Edit, stringResource(R.string.edit)) modifier = Modifier,
tooltip = stringResource(R.string.edit),
) {
IconButton(onClick = { clickAction(scope) }) {
Icon(Icons.Outlined.Edit, stringResource(R.string.edit))
}
} }
} }
@ -249,13 +253,18 @@ private object StringOptionEditor : OptionEditor<String> {
}, },
trailingIcon = { trailingIcon = {
var showDropdownMenu by rememberSaveable { mutableStateOf(false) } var showDropdownMenu by rememberSaveable { mutableStateOf(false) }
IconButton( TooltipWrap(
onClick = { showDropdownMenu = true } modifier = Modifier,
tooltip = stringResource(R.string.string_option_menu_description),
) { ) {
Icon( IconButton(
Icons.Outlined.MoreVert, onClick = { showDropdownMenu = true }
stringResource(R.string.string_option_menu_description) ) {
) Icon(
Icons.Outlined.MoreVert,
stringResource(R.string.string_option_menu_description)
)
}
} }
DropdownMenu( DropdownMenu(
@ -551,32 +560,47 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
}, },
actions = { actions = {
if (deleteMode) { if (deleteMode) {
IconButton( TooltipWrap(
onClick = { modifier = Modifier,
if (items.size == deletionTargets.size) deletionTargets.clear() tooltip = stringResource(R.string.select_deselect_all),
else deletionTargets.addAll(items.map { it.key })
}
) { ) {
Icon( IconButton(
Icons.Outlined.SelectAll, onClick = {
stringResource(R.string.select_deselect_all) if (items.size == deletionTargets.size) deletionTargets.clear()
) else deletionTargets.addAll(items.map { it.key })
}
) {
Icon(
Icons.Outlined.SelectAll,
stringResource(R.string.select_deselect_all)
)
}
} }
IconButton( TooltipWrap(
onClick = { modifier = Modifier,
items.removeIf { it.key in deletionTargets } tooltip = stringResource(R.string.delete),
deletionTargets.clear()
deleteMode = false
}
) { ) {
Icon( IconButton(
Icons.Outlined.Delete, onClick = {
stringResource(R.string.delete) items.removeIf { it.key in deletionTargets }
) deletionTargets.clear()
deleteMode = false
}
) {
Icon(
Icons.Outlined.Delete,
stringResource(R.string.delete)
)
}
} }
} else { } else {
IconButton(onClick = items::clear) { TooltipWrap(
Icon(Icons.Outlined.Restore, stringResource(R.string.reset)) modifier = Modifier,
tooltip = stringResource(R.string.reset),
) {
IconButton(onClick = items::clear) {
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
}
} }
} }
} }
@ -643,14 +667,19 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
), ),
tonalElevation = if (deleteMode && item.key in deletionTargets) 8.dp else 0.dp, tonalElevation = if (deleteMode && item.key in deletionTargets) 8.dp else 0.dp,
leadingContent = { leadingContent = {
IconButton( TooltipWrap(
modifier = Modifier.draggableHandle(interactionSource = interactionSource), modifier = Modifier,
onClick = {}, tooltip = stringResource(R.string.drag_handle),
) { ) {
Icon( IconButton(
Icons.Filled.DragHandle, modifier = Modifier.draggableHandle(interactionSource = interactionSource),
stringResource(R.string.drag_handle) onClick = {},
) ) {
Icon(
Icons.Filled.DragHandle,
stringResource(R.string.drag_handle)
)
}
} }
}, },
headlineContent = { headlineContent = {

View File

@ -17,6 +17,7 @@ import androidx.compose.ui.res.stringResource
import app.revanced.manager.R import app.revanced.manager.R
import app.revanced.manager.domain.manager.base.Preference import app.revanced.manager.domain.manager.base.Preference
import app.revanced.manager.ui.component.IntInputDialog import app.revanced.manager.ui.component.IntInputDialog
import app.revanced.manager.ui.component.TooltipWrap
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -65,11 +66,16 @@ fun IntegerItem(
headlineContent = stringResource(headline), headlineContent = stringResource(headline),
supportingContent = stringResource(description), supportingContent = stringResource(description),
trailingContent = { trailingContent = {
IconButton(onClick = { dialogOpen = true }) { TooltipWrap(
Icon( modifier = Modifier,
Icons.Outlined.Edit, tooltip = stringResource(R.string.edit),
contentDescription = stringResource(R.string.edit) ) {
) IconButton(onClick = { dialogOpen = true }) {
Icon(
Icons.Outlined.Edit,
contentDescription = stringResource(R.string.edit)
)
}
} }
} }
) )

View File

@ -44,6 +44,7 @@ import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.LoadingIndicator import app.revanced.manager.ui.component.LoadingIndicator
import app.revanced.manager.ui.component.NonSuggestedVersionDialog import app.revanced.manager.ui.component.NonSuggestedVersionDialog
import app.revanced.manager.ui.component.SearchView import app.revanced.manager.ui.component.SearchView
import app.revanced.manager.ui.component.TooltipWrap
import app.revanced.manager.ui.model.SelectedApp import app.revanced.manager.ui.model.SelectedApp
import app.revanced.manager.ui.viewmodel.AppSelectorViewModel import app.revanced.manager.ui.viewmodel.AppSelectorViewModel
import app.revanced.manager.util.APK_MIMETYPE import app.revanced.manager.util.APK_MIMETYPE
@ -162,8 +163,13 @@ fun AppSelectorScreen(
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
onBackClick = onBackClick, onBackClick = onBackClick,
actions = { actions = {
IconButton(onClick = { search = true }) { TooltipWrap(
Icon(Icons.Outlined.Search, stringResource(R.string.search)) modifier = Modifier,
tooltip = stringResource(R.string.search_patches),
) {
IconButton(onClick = { search = true }) {
Icon(Icons.Outlined.Search, stringResource(R.string.search))
}
} }
} }
) )

View File

@ -62,8 +62,9 @@ import app.revanced.manager.ui.component.AlertDialogExtended
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.AutoUpdatesDialog import app.revanced.manager.ui.component.AutoUpdatesDialog
import app.revanced.manager.ui.component.AvailableUpdateDialog import app.revanced.manager.ui.component.AvailableUpdateDialog
import app.revanced.manager.ui.component.NotificationCard
import app.revanced.manager.ui.component.ConfirmDialog import app.revanced.manager.ui.component.ConfirmDialog
import app.revanced.manager.ui.component.NotificationCard
import app.revanced.manager.ui.component.TooltipWrap
import app.revanced.manager.ui.component.bundle.BundleTopBar import app.revanced.manager.ui.component.bundle.BundleTopBar
import app.revanced.manager.ui.component.bundle.ImportPatchBundleDialog import app.revanced.manager.ui.component.bundle.ImportPatchBundleDialog
import app.revanced.manager.ui.component.haptics.HapticFloatingActionButton import app.revanced.manager.ui.component.haptics.HapticFloatingActionButton
@ -183,26 +184,36 @@ fun DashboardScreen(
) )
}, },
actions = { actions = {
IconButton( TooltipWrap(
onClick = { modifier = Modifier,
showDeleteConfirmationDialog = true tooltip = stringResource(R.string.delete),
}
) { ) {
Icon( IconButton(
Icons.Outlined.DeleteOutline, onClick = {
stringResource(R.string.delete) showDeleteConfirmationDialog = true
) }
) {
Icon(
Icons.Outlined.DeleteOutline,
stringResource(R.string.delete)
)
}
} }
IconButton( TooltipWrap(
onClick = { modifier = Modifier,
vm.selectedSources.forEach { vm.update(it) } tooltip = stringResource(R.string.refresh),
vm.cancelSourceSelection()
}
) { ) {
Icon( IconButton(
Icons.Outlined.Refresh, onClick = {
stringResource(R.string.refresh) vm.selectedSources.forEach { vm.update(it) }
) vm.cancelSourceSelection()
}
) {
Icon(
Icons.Outlined.Refresh,
stringResource(R.string.refresh)
)
}
} }
} }
) )
@ -211,20 +222,30 @@ fun DashboardScreen(
title = stringResource(R.string.app_name), title = stringResource(R.string.app_name),
actions = { actions = {
if (!vm.updatedManagerVersion.isNullOrEmpty()) { if (!vm.updatedManagerVersion.isNullOrEmpty()) {
IconButton( TooltipWrap(
onClick = onUpdateClick, modifier = Modifier,
tooltip = stringResource(R.string.update),
) { ) {
BadgedBox( IconButton(
badge = { onClick = onUpdateClick,
Badge(modifier = Modifier.size(6.dp))
}
) { ) {
Icon(Icons.Outlined.Update, stringResource(R.string.update)) BadgedBox(
badge = {
Badge(modifier = Modifier.size(6.dp))
}
) {
Icon(Icons.Outlined.Update, stringResource(R.string.update))
}
} }
} }
} }
IconButton(onClick = onSettingsClick) { TooltipWrap(
Icon(Icons.Outlined.Settings, stringResource(R.string.settings)) modifier = Modifier,
tooltip = stringResource(R.string.settings),
) {
IconButton(onClick = onSettingsClick) {
Icon(Icons.Outlined.Settings, stringResource(R.string.settings))
}
} }
}, },
applyContainerColor = true applyContainerColor = true
@ -232,35 +253,40 @@ fun DashboardScreen(
} }
}, },
floatingActionButton = { floatingActionButton = {
HapticFloatingActionButton( TooltipWrap(
onClick = { modifier = Modifier,
vm.cancelSourceSelection() tooltip = stringResource(R.string.add),
) {
HapticFloatingActionButton(
onClick = {
vm.cancelSourceSelection()
when (pagerState.currentPage) { when (pagerState.currentPage) {
DashboardPage.DASHBOARD.ordinal -> { DashboardPage.DASHBOARD.ordinal -> {
if (availablePatches < 1) { if (availablePatches < 1) {
androidContext.toast(androidContext.getString(R.string.patches_unavailable)) androidContext.toast(androidContext.getString(R.string.patches_unavailable))
composableScope.launch { composableScope.launch {
pagerState.animateScrollToPage( pagerState.animateScrollToPage(
DashboardPage.BUNDLES.ordinal DashboardPage.BUNDLES.ordinal
) )
}
return@HapticFloatingActionButton
} }
return@HapticFloatingActionButton if (vm.android11BugActive) {
} showAndroid11Dialog = true
if (vm.android11BugActive) { return@HapticFloatingActionButton
showAndroid11Dialog = true }
return@HapticFloatingActionButton
onAppSelectorClick()
} }
onAppSelectorClick() DashboardPage.BUNDLES.ordinal -> {
} showAddBundleDialog = true
}
DashboardPage.BUNDLES.ordinal -> {
showAddBundleDialog = true
} }
} }
} ) { Icon(Icons.Default.Add, stringResource(R.string.add)) }
) { Icon(Icons.Default.Add, stringResource(R.string.add)) } }
} }
) { paddingValues -> ) { paddingValues ->
Column(Modifier.padding(paddingValues)) { Column(Modifier.padding(paddingValues)) {

View File

@ -48,6 +48,7 @@ import app.revanced.manager.ui.component.AppScaffold
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ConfirmDialog import app.revanced.manager.ui.component.ConfirmDialog
import app.revanced.manager.ui.component.InstallerStatusDialog import app.revanced.manager.ui.component.InstallerStatusDialog
import app.revanced.manager.ui.component.TooltipWrap
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.component.patcher.InstallPickerDialog import app.revanced.manager.ui.component.patcher.InstallPickerDialog
import app.revanced.manager.ui.component.patcher.Steps import app.revanced.manager.ui.component.patcher.Steps
@ -164,17 +165,27 @@ fun PatcherScreen(
bottomBar = { bottomBar = {
BottomAppBar( BottomAppBar(
actions = { actions = {
IconButton( TooltipWrap(
onClick = { exportApkLauncher.launch("${viewModel.packageName}_${viewModel.version}_revanced_patched.apk") }, modifier = Modifier,
enabled = patcherSucceeded == true tooltip = stringResource(R.string.save_apk),
) { ) {
Icon(Icons.Outlined.Save, stringResource(id = R.string.save_apk)) IconButton(
onClick = { exportApkLauncher.launch("${viewModel.packageName}_${viewModel.version}_revanced_patched.apk") },
enabled = patcherSucceeded == true
) {
Icon(Icons.Outlined.Save, stringResource(id = R.string.save_apk))
}
} }
IconButton( TooltipWrap(
onClick = { viewModel.exportLogs(context) }, modifier = Modifier,
enabled = patcherSucceeded != null tooltip = stringResource(R.string.save_logs),
) { ) {
Icon(Icons.Outlined.PostAdd, stringResource(id = R.string.save_logs)) IconButton(
onClick = { viewModel.exportLogs(context) },
enabled = patcherSucceeded != null
) {
Icon(Icons.Outlined.PostAdd, stringResource(id = R.string.save_logs))
}
} }
}, },
floatingActionButton = { floatingActionButton = {

View File

@ -69,6 +69,7 @@ import app.revanced.manager.ui.component.FullscreenDialog
import app.revanced.manager.ui.component.LazyColumnWithScrollbar import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.SafeguardDialog import app.revanced.manager.ui.component.SafeguardDialog
import app.revanced.manager.ui.component.SearchBar import app.revanced.manager.ui.component.SearchBar
import app.revanced.manager.ui.component.TooltipWrap
import app.revanced.manager.ui.component.haptics.HapticCheckbox import app.revanced.manager.ui.component.haptics.HapticCheckbox
import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton import app.revanced.manager.ui.component.haptics.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.component.haptics.HapticTab import app.revanced.manager.ui.component.haptics.HapticTab
@ -259,20 +260,25 @@ fun PatchesSelectorScreen(
animationSpec = tween(durationMillis = 400, easing = EaseInOut), animationSpec = tween(durationMillis = 400, easing = EaseInOut),
label = "SearchBar back button" label = "SearchBar back button"
) )
IconButton( TooltipWrap(
onClick = { modifier = Modifier,
if (searchExpanded) { tooltip = stringResource(R.string.back),
setSearchExpanded(false)
} else {
onBackClick()
}
}
) { ) {
Icon( IconButton(
modifier = Modifier.rotate(rotation), onClick = {
imageVector = Icons.AutoMirrored.Filled.ArrowBack, if (searchExpanded) {
contentDescription = stringResource(R.string.back) setSearchExpanded(false)
) } else {
onBackClick()
}
}
) {
Icon(
modifier = Modifier.rotate(rotation),
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back)
)
}
} }
}, },
trailingIcon = { trailingIcon = {
@ -282,21 +288,31 @@ fun PatchesSelectorScreen(
transitionSpec = { fadeIn() togetherWith fadeOut() } transitionSpec = { fadeIn() togetherWith fadeOut() }
) { searchExpanded -> ) { searchExpanded ->
if (searchExpanded) { if (searchExpanded) {
IconButton( TooltipWrap(
onClick = { setQuery("") }, modifier = Modifier,
enabled = query.isNotEmpty() tooltip = stringResource(R.string.clear),
) { ) {
Icon( IconButton(
imageVector = Icons.Filled.Close, onClick = { setQuery("") },
contentDescription = stringResource(R.string.clear) enabled = query.isNotEmpty()
) ) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = stringResource(R.string.clear)
)
}
} }
} else { } else {
IconButton(onClick = { showBottomSheet = true }) { TooltipWrap(
Icon( modifier = Modifier,
imageVector = Icons.Outlined.FilterList, tooltip = stringResource(R.string.more),
contentDescription = stringResource(R.string.more) ) {
) IconButton(onClick = { showBottomSheet = true }) {
Icon(
imageVector = Icons.Outlined.FilterList,
contentDescription = stringResource(R.string.more)
)
}
} }
} }
} }
@ -354,11 +370,16 @@ fun PatchesSelectorScreen(
horizontalAlignment = Alignment.End, horizontalAlignment = Alignment.End,
verticalArrangement = Arrangement.spacedBy(4.dp) verticalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
SmallFloatingActionButton( TooltipWrap(
onClick = viewModel::reset, modifier = Modifier,
containerColor = MaterialTheme.colorScheme.tertiaryContainer tooltip = stringResource(R.string.reset),
) { ) {
Icon(Icons.Outlined.Restore, stringResource(R.string.reset)) SmallFloatingActionButton(
onClick = viewModel::reset,
containerColor = MaterialTheme.colorScheme.tertiaryContainer
) {
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
}
} }
HapticExtendedFloatingActionButton( HapticExtendedFloatingActionButton(
text = { text = {
@ -516,6 +537,7 @@ private fun PatchItem(
supportingContent = patch.description?.let { { Text(it) } }, supportingContent = patch.description?.let { { Text(it) } },
trailingContent = { trailingContent = {
if (patch.options?.isNotEmpty() == true) { if (patch.options?.isNotEmpty() == true) {
// TODO: Determine if this button should be [TooltipWrap]
IconButton(onClick = onOptionsDialog, enabled = compatible) { IconButton(onClick = onOptionsDialog, enabled = compatible) {
Icon(Icons.Outlined.Settings, null) Icon(Icons.Outlined.Settings, null)
} }
@ -539,11 +561,16 @@ fun ListHeader(
}, },
trailingContent = onHelpClick?.let { trailingContent = onHelpClick?.let {
{ {
IconButton(onClick = it) { TooltipWrap(
Icon( modifier = Modifier,
Icons.AutoMirrored.Outlined.HelpOutline, tooltip = stringResource(R.string.help),
stringResource(R.string.help) ) {
) IconButton(onClick = it) {
Icon(
Icons.AutoMirrored.Outlined.HelpOutline,
stringResource(R.string.help)
)
}
} }
} }
}, },
@ -618,8 +645,13 @@ private fun OptionsDialog(
title = patch.name, title = patch.name,
onBackClick = onDismissRequest, onBackClick = onDismissRequest,
actions = { actions = {
IconButton(onClick = reset) { TooltipWrap(
Icon(Icons.Outlined.Restore, stringResource(R.string.reset)) modifier = Modifier,
tooltip = stringResource(R.string.reset),
) {
IconButton(onClick = reset) {
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
}
} }
} }
) )

View File

@ -49,6 +49,7 @@ import app.revanced.manager.R
import app.revanced.manager.network.dto.ReVancedSocial import app.revanced.manager.network.dto.ReVancedSocial
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.TooltipWrap
import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.model.navigation.Settings import app.revanced.manager.ui.model.navigation.Settings
import app.revanced.manager.ui.viewmodel.AboutViewModel import app.revanced.manager.ui.viewmodel.AboutViewModel
@ -252,16 +253,21 @@ fun AboutSettingsScreen(
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally) horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
) { ) {
socialButtons.forEach { (icon, text, onClick) -> socialButtons.forEach { (icon, text, onClick) ->
IconButton( TooltipWrap(
onClick = onClick, modifier = Modifier,
modifier = Modifier.padding(end = 8.dp), tooltip = text,
) { ) {
Icon( IconButton(
icon, onClick = onClick,
contentDescription = text, modifier = Modifier.padding(end = 8.dp),
modifier = Modifier.size(28.dp), ) {
tint = MaterialTheme.colorScheme.secondary Icon(
) icon,
contentDescription = text,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.secondary
)
}
} }
} }
} }

View File

@ -48,6 +48,7 @@ import app.revanced.manager.R
import app.revanced.manager.ui.component.AppTopBar import app.revanced.manager.ui.component.AppTopBar
import app.revanced.manager.ui.component.ColumnWithScrollbar import app.revanced.manager.ui.component.ColumnWithScrollbar
import app.revanced.manager.ui.component.GroupHeader import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.TooltipWrap
import app.revanced.manager.ui.component.settings.BooleanItem import app.revanced.manager.ui.component.settings.BooleanItem
import app.revanced.manager.ui.component.settings.IntegerItem import app.revanced.manager.ui.component.settings.IntegerItem
import app.revanced.manager.ui.component.settings.SafeguardBooleanItem import app.revanced.manager.ui.component.settings.SafeguardBooleanItem
@ -243,8 +244,13 @@ private fun APIUrlDialog(currentUrl: String, defaultUrl: String, onSubmit: (Stri
onValueChange = { url = it }, onValueChange = { url = it },
label = { Text(stringResource(R.string.api_url)) }, label = { Text(stringResource(R.string.api_url)) },
trailingIcon = { trailingIcon = {
IconButton(onClick = { url = defaultUrl }) { TooltipWrap(
Icon(Icons.Outlined.Restore, stringResource(R.string.api_url_dialog_reset)) modifier = Modifier,
tooltip = stringResource(R.string.api_url_dialog_reset),
) {
IconButton(onClick = { url = defaultUrl }) {
Icon(Icons.Outlined.Restore, stringResource(R.string.api_url_dialog_reset))
}
} }
} }
) )

View File

@ -45,6 +45,7 @@ import app.revanced.manager.ui.component.ExceptionViewerDialog
import app.revanced.manager.ui.component.GroupHeader import app.revanced.manager.ui.component.GroupHeader
import app.revanced.manager.ui.component.LazyColumnWithScrollbar import app.revanced.manager.ui.component.LazyColumnWithScrollbar
import app.revanced.manager.ui.component.ConfirmDialog import app.revanced.manager.ui.component.ConfirmDialog
import app.revanced.manager.ui.component.TooltipWrap
import app.revanced.manager.ui.component.haptics.HapticCheckbox import app.revanced.manager.ui.component.haptics.HapticCheckbox
import app.revanced.manager.ui.component.settings.SettingsListItem import app.revanced.manager.ui.component.settings.SettingsListItem
import app.revanced.manager.ui.viewmodel.DownloadsViewModel import app.revanced.manager.ui.viewmodel.DownloadsViewModel
@ -81,8 +82,13 @@ fun DownloadsSettingsScreen(
onBackClick = onBackClick, onBackClick = onBackClick,
actions = { actions = {
if (viewModel.appSelection.isNotEmpty()) { if (viewModel.appSelection.isNotEmpty()) {
IconButton(onClick = { showDeleteConfirmationDialog = true }) { TooltipWrap(
Icon(Icons.Default.Delete, stringResource(R.string.delete)) modifier = Modifier,
tooltip = stringResource(R.string.delete),
) {
IconButton(onClick = { showDeleteConfirmationDialog = true }) {
Icon(Icons.Default.Delete, stringResource(R.string.delete))
}
} }
} }
} }

View File

@ -317,8 +317,7 @@
<string name="patcher_step_group_saving">Saving</string> <string name="patcher_step_group_saving">Saving</string>
<string name="patcher_step_write_patched">Write patched APK file</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_step_sign_apk">Sign patched APK file</string>
<string name="patcher_notification_title">Patching in progress…</string> <string name="patcher_notification_message">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_title">Stop patcher</string>
<string name="patcher_stop_confirm_description">Are you sure you want to stop the patching process?</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> <string name="execute_patches">Execute patches</string>