Compare commits

..

1 Commits

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

View File

@ -40,8 +40,6 @@ 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.
@ -98,12 +96,7 @@ 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}")
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\".")
}
if (expectedVersion != null && pkgInfo.versionName != expectedVersion) error("Downloaded APK has the wrong version. Expected: $expectedVersion, Actual: ${pkgInfo.versionName}")
// Delete the previous copy (if present).
dao.get(pkgInfo.packageName, pkgInfo.versionName!!)?.directory?.let {

View File

@ -158,8 +158,6 @@ class PatcherWorker(
data,
args.packageName,
args.input.version,
prefs.suggestedVersionSafeguard.get(),
!prefs.disablePatchVersionCompatCheck.get(),
onDownload = args.onDownloadProgress
).also {
args.setInputFile(it)

View File

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

View File

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

View File

@ -39,25 +39,30 @@ fun ExceptionViewerDialog(text: String, onDismiss: () -> Unit) {
)
},
actions = {
IconButton(
onClick = {
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)
}
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.share),
) {
Icon(
Icons.Outlined.Share,
contentDescription = stringResource(R.string.share)
)
IconButton(
onClick = {
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) {
IconButton(onClick = onDismiss) {
Icon(
imageVector = Icons.Outlined.Close,
contentDescription = stringResource(R.string.close),
tint = color,
)
TooltipWrap(
modifier = modifier,
tooltip = stringResource(R.string.close),
) {
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,
modifier = modifier,
trailingIcon = {
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
TooltipWrap(
modifier = modifier,
tooltip = if (visible) stringResource(R.string.show_password_field) else stringResource(R.string.hide_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(

View File

@ -48,11 +48,16 @@ fun SearchView(
onExpandedChange = onActiveChange,
placeholder = placeholder,
leadingIcon = {
IconButton(onClick = { onActiveChange(false) }) {
Icon(
Icons.AutoMirrored.Filled.ArrowBack,
stringResource(R.string.back)
)
TooltipWrap(
modifier = Modifier,
tooltip = 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.ui.component.ExceptionViewerDialog
import app.revanced.manager.ui.component.FullscreenDialog
import app.revanced.manager.ui.component.TooltipWrap
import kotlinx.coroutines.launch
import org.koin.compose.koinInject
@ -72,19 +73,29 @@ fun BundleInformationDialog(
},
actions = {
if (!bundle.isDefault) {
IconButton(onClick = onDeleteRequest) {
Icon(
Icons.Outlined.DeleteOutline,
stringResource(R.string.delete)
)
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.delete),
) {
IconButton(onClick = onDeleteRequest) {
Icon(
Icons.Outlined.DeleteOutline,
stringResource(R.string.delete)
)
}
}
}
if (!isLocal && hasNetwork) {
IconButton(onClick = onUpdate) {
Icon(
Icons.Outlined.Update,
stringResource(R.string.refresh)
)
TooltipWrap(
modifier = Modifier,
tooltip = 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.surfaceColorAtElevation
import androidx.compose.runtime.Composable
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.TooltipWrap
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -33,8 +37,13 @@ fun BundleTopBar(
scrollBehavior = scrollBehavior,
navigationIcon = {
if (onBackClick != null) {
IconButton(onClick = onBackClick) {
backIcon()
TooltipWrap(
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)) },
trailingContent = {
// TODO: Determine if this button should be [TooltipWrap]'ped
IconButton(onClick = launchPatchActivity) {
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.IntInputDialog
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.HapticRadioButton
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.toast
import app.revanced.manager.util.transparentListItemColors
import kotlinx.coroutines.CoroutineScope
import kotlinx.parcelize.Parcelize
import org.koin.compose.koinInject
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyColumnState
import sh.calvin.reorderable.rememberReorderableLazyListState
import java.io.Serializable
import kotlin.random.Random
@ -113,8 +112,13 @@ private interface OptionEditor<T : Any> {
@Composable
fun ListItemTrailingContent(scope: OptionEditorScope<T>) {
IconButton(onClick = { clickAction(scope) }) {
Icon(Icons.Outlined.Edit, stringResource(R.string.edit))
TooltipWrap(
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 = {
var showDropdownMenu by rememberSaveable { mutableStateOf(false) }
IconButton(
onClick = { showDropdownMenu = true }
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.string_option_menu_description),
) {
Icon(
Icons.Outlined.MoreVert,
stringResource(R.string.string_option_menu_description)
)
IconButton(
onClick = { showDropdownMenu = true }
) {
Icon(
Icons.Outlined.MoreVert,
stringResource(R.string.string_option_menu_description)
)
}
}
DropdownMenu(
@ -551,32 +560,47 @@ private class ListOptionEditor<T : Serializable>(private val elementEditor: Opti
},
actions = {
if (deleteMode) {
IconButton(
onClick = {
if (items.size == deletionTargets.size) deletionTargets.clear()
else deletionTargets.addAll(items.map { it.key })
}
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.select_deselect_all),
) {
Icon(
Icons.Outlined.SelectAll,
stringResource(R.string.select_deselect_all)
)
IconButton(
onClick = {
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(
onClick = {
items.removeIf { it.key in deletionTargets }
deletionTargets.clear()
deleteMode = false
}
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.delete),
) {
Icon(
Icons.Outlined.Delete,
stringResource(R.string.delete)
)
IconButton(
onClick = {
items.removeIf { it.key in deletionTargets }
deletionTargets.clear()
deleteMode = false
}
) {
Icon(
Icons.Outlined.Delete,
stringResource(R.string.delete)
)
}
}
} else {
IconButton(onClick = items::clear) {
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
TooltipWrap(
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,
leadingContent = {
IconButton(
modifier = Modifier.draggableHandle(interactionSource = interactionSource),
onClick = {},
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.drag_handle),
) {
Icon(
Icons.Filled.DragHandle,
stringResource(R.string.drag_handle)
)
IconButton(
modifier = Modifier.draggableHandle(interactionSource = interactionSource),
onClick = {},
) {
Icon(
Icons.Filled.DragHandle,
stringResource(R.string.drag_handle)
)
}
}
},
headlineContent = {

View File

@ -17,6 +17,7 @@ import androidx.compose.ui.res.stringResource
import app.revanced.manager.R
import app.revanced.manager.domain.manager.base.Preference
import app.revanced.manager.ui.component.IntInputDialog
import app.revanced.manager.ui.component.TooltipWrap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ -65,11 +66,16 @@ fun IntegerItem(
headlineContent = stringResource(headline),
supportingContent = stringResource(description),
trailingContent = {
IconButton(onClick = { dialogOpen = true }) {
Icon(
Icons.Outlined.Edit,
contentDescription = stringResource(R.string.edit)
)
TooltipWrap(
modifier = Modifier,
tooltip = 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.NonSuggestedVersionDialog
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.viewmodel.AppSelectorViewModel
import app.revanced.manager.util.APK_MIMETYPE
@ -162,8 +163,13 @@ fun AppSelectorScreen(
scrollBehavior = scrollBehavior,
onBackClick = onBackClick,
actions = {
IconButton(onClick = { search = true }) {
Icon(Icons.Outlined.Search, stringResource(R.string.search))
TooltipWrap(
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.AutoUpdatesDialog
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.NotificationCard
import app.revanced.manager.ui.component.TooltipWrap
import app.revanced.manager.ui.component.bundle.BundleTopBar
import app.revanced.manager.ui.component.bundle.ImportPatchBundleDialog
import app.revanced.manager.ui.component.haptics.HapticFloatingActionButton
@ -183,26 +184,36 @@ fun DashboardScreen(
)
},
actions = {
IconButton(
onClick = {
showDeleteConfirmationDialog = true
}
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.delete),
) {
Icon(
Icons.Outlined.DeleteOutline,
stringResource(R.string.delete)
)
IconButton(
onClick = {
showDeleteConfirmationDialog = true
}
) {
Icon(
Icons.Outlined.DeleteOutline,
stringResource(R.string.delete)
)
}
}
IconButton(
onClick = {
vm.selectedSources.forEach { vm.update(it) }
vm.cancelSourceSelection()
}
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.refresh),
) {
Icon(
Icons.Outlined.Refresh,
stringResource(R.string.refresh)
)
IconButton(
onClick = {
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),
actions = {
if (!vm.updatedManagerVersion.isNullOrEmpty()) {
IconButton(
onClick = onUpdateClick,
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.update),
) {
BadgedBox(
badge = {
Badge(modifier = Modifier.size(6.dp))
}
IconButton(
onClick = onUpdateClick,
) {
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) {
Icon(Icons.Outlined.Settings, stringResource(R.string.settings))
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.settings),
) {
IconButton(onClick = onSettingsClick) {
Icon(Icons.Outlined.Settings, stringResource(R.string.settings))
}
}
},
applyContainerColor = true
@ -232,35 +253,40 @@ fun DashboardScreen(
}
},
floatingActionButton = {
HapticFloatingActionButton(
onClick = {
vm.cancelSourceSelection()
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.add),
) {
HapticFloatingActionButton(
onClick = {
vm.cancelSourceSelection()
when (pagerState.currentPage) {
DashboardPage.DASHBOARD.ordinal -> {
if (availablePatches < 1) {
androidContext.toast(androidContext.getString(R.string.patches_unavailable))
composableScope.launch {
pagerState.animateScrollToPage(
DashboardPage.BUNDLES.ordinal
)
when (pagerState.currentPage) {
DashboardPage.DASHBOARD.ordinal -> {
if (availablePatches < 1) {
androidContext.toast(androidContext.getString(R.string.patches_unavailable))
composableScope.launch {
pagerState.animateScrollToPage(
DashboardPage.BUNDLES.ordinal
)
}
return@HapticFloatingActionButton
}
return@HapticFloatingActionButton
}
if (vm.android11BugActive) {
showAndroid11Dialog = true
return@HapticFloatingActionButton
if (vm.android11BugActive) {
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 ->
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.ConfirmDialog
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.patcher.InstallPickerDialog
import app.revanced.manager.ui.component.patcher.Steps
@ -164,17 +165,27 @@ fun PatcherScreen(
bottomBar = {
BottomAppBar(
actions = {
IconButton(
onClick = { exportApkLauncher.launch("${viewModel.packageName}_${viewModel.version}_revanced_patched.apk") },
enabled = patcherSucceeded == true
TooltipWrap(
modifier = Modifier,
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(
onClick = { viewModel.exportLogs(context) },
enabled = patcherSucceeded != null
TooltipWrap(
modifier = Modifier,
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 = {

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.SafeguardDialog
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.HapticExtendedFloatingActionButton
import app.revanced.manager.ui.component.haptics.HapticTab
@ -259,20 +260,25 @@ fun PatchesSelectorScreen(
animationSpec = tween(durationMillis = 400, easing = EaseInOut),
label = "SearchBar back button"
)
IconButton(
onClick = {
if (searchExpanded) {
setSearchExpanded(false)
} else {
onBackClick()
}
}
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.back),
) {
Icon(
modifier = Modifier.rotate(rotation),
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back)
)
IconButton(
onClick = {
if (searchExpanded) {
setSearchExpanded(false)
} else {
onBackClick()
}
}
) {
Icon(
modifier = Modifier.rotate(rotation),
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(R.string.back)
)
}
}
},
trailingIcon = {
@ -282,21 +288,31 @@ fun PatchesSelectorScreen(
transitionSpec = { fadeIn() togetherWith fadeOut() }
) { searchExpanded ->
if (searchExpanded) {
IconButton(
onClick = { setQuery("") },
enabled = query.isNotEmpty()
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.clear),
) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = stringResource(R.string.clear)
)
IconButton(
onClick = { setQuery("") },
enabled = query.isNotEmpty()
) {
Icon(
imageVector = Icons.Filled.Close,
contentDescription = stringResource(R.string.clear)
)
}
}
} else {
IconButton(onClick = { showBottomSheet = true }) {
Icon(
imageVector = Icons.Outlined.FilterList,
contentDescription = stringResource(R.string.more)
)
TooltipWrap(
modifier = Modifier,
tooltip = 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,
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
SmallFloatingActionButton(
onClick = viewModel::reset,
containerColor = MaterialTheme.colorScheme.tertiaryContainer
TooltipWrap(
modifier = Modifier,
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(
text = {
@ -516,6 +537,7 @@ private fun PatchItem(
supportingContent = patch.description?.let { { Text(it) } },
trailingContent = {
if (patch.options?.isNotEmpty() == true) {
// TODO: Determine if this button should be [TooltipWrap]
IconButton(onClick = onOptionsDialog, enabled = compatible) {
Icon(Icons.Outlined.Settings, null)
}
@ -539,11 +561,16 @@ fun ListHeader(
},
trailingContent = onHelpClick?.let {
{
IconButton(onClick = it) {
Icon(
Icons.AutoMirrored.Outlined.HelpOutline,
stringResource(R.string.help)
)
TooltipWrap(
modifier = Modifier,
tooltip = 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,
onBackClick = onDismissRequest,
actions = {
IconButton(onClick = reset) {
Icon(Icons.Outlined.Restore, stringResource(R.string.reset))
TooltipWrap(
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.ui.component.AppTopBar
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.model.navigation.Settings
import app.revanced.manager.ui.viewmodel.AboutViewModel
@ -252,16 +253,21 @@ fun AboutSettingsScreen(
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
) {
socialButtons.forEach { (icon, text, onClick) ->
IconButton(
onClick = onClick,
modifier = Modifier.padding(end = 8.dp),
TooltipWrap(
modifier = Modifier,
tooltip = text,
) {
Icon(
icon,
contentDescription = text,
modifier = Modifier.size(28.dp),
tint = MaterialTheme.colorScheme.secondary
)
IconButton(
onClick = onClick,
modifier = Modifier.padding(end = 8.dp),
) {
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.ColumnWithScrollbar
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.IntegerItem
import app.revanced.manager.ui.component.settings.SafeguardBooleanItem
@ -243,8 +244,13 @@ private fun APIUrlDialog(currentUrl: String, defaultUrl: String, onSubmit: (Stri
onValueChange = { url = it },
label = { Text(stringResource(R.string.api_url)) },
trailingIcon = {
IconButton(onClick = { url = defaultUrl }) {
Icon(Icons.Outlined.Restore, stringResource(R.string.api_url_dialog_reset))
TooltipWrap(
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.LazyColumnWithScrollbar
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.settings.SettingsListItem
import app.revanced.manager.ui.viewmodel.DownloadsViewModel
@ -81,8 +82,13 @@ fun DownloadsSettingsScreen(
onBackClick = onBackClick,
actions = {
if (viewModel.appSelection.isNotEmpty()) {
IconButton(onClick = { showDeleteConfirmationDialog = true }) {
Icon(Icons.Default.Delete, stringResource(R.string.delete))
TooltipWrap(
modifier = Modifier,
tooltip = stringResource(R.string.delete),
) {
IconButton(onClick = { showDeleteConfirmationDialog = true }) {
Icon(Icons.Default.Delete, stringResource(R.string.delete))
}
}
}
}