Add tooltips for long press menu icons
This commit is contained in:
parent
3aa5edc453
commit
3fc4bc9cd3
@ -127,18 +127,18 @@ class LongPressMenuTest {
|
||||
private fun assertEditorIsEnteredAndExitedProperly() {
|
||||
composeRule.onNodeWithContentDescription(R.string.long_press_menu_enabled_actions_description)
|
||||
.assertDoesNotExist()
|
||||
composeRule.onNodeWithContentDescription(R.string.edit)
|
||||
composeRule.onNodeWithContentDescription(R.string.long_press_menu_actions_editor)
|
||||
.performClick()
|
||||
composeRule.waitUntil {
|
||||
composeRule.onNodeWithText(R.string.long_press_menu_enabled_actions)
|
||||
.isDisplayed()
|
||||
}
|
||||
|
||||
composeRule.onNodeWithContentDescription(R.string.edit)
|
||||
composeRule.onNodeWithContentDescription(R.string.long_press_menu_actions_editor)
|
||||
.assertDoesNotExist()
|
||||
Espresso.pressBack()
|
||||
composeRule.waitUntil {
|
||||
composeRule.onNodeWithContentDescription(R.string.edit)
|
||||
composeRule.onNodeWithContentDescription(R.string.long_press_menu_actions_editor)
|
||||
.isDisplayed()
|
||||
}
|
||||
|
||||
|
||||
@ -8,9 +8,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SearchBar
|
||||
import androidx.compose.material3.Text
|
||||
@ -26,6 +26,7 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.ui.components.common.TooltipIconButton
|
||||
import org.schabi.newpipe.ui.theme.AppTheme
|
||||
import org.schabi.newpipe.ui.theme.SizeTokens
|
||||
|
||||
@ -70,13 +71,12 @@ fun Toolbar(
|
||||
actions = {
|
||||
actions()
|
||||
if (hasSearch) {
|
||||
IconButton(onClick = { isSearchActive = true }) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_search),
|
||||
contentDescription = stringResource(id = R.string.search),
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
TooltipIconButton(
|
||||
onClick = { isSearchActive = true },
|
||||
icon = Icons.Default.Search,
|
||||
contentDescription = stringResource(id = R.string.search),
|
||||
tint = MaterialTheme.colorScheme.onSurface
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -6,8 +6,6 @@ import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
@ -39,12 +37,11 @@ fun ScaffoldWithToolbar(
|
||||
actionIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBackClick) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back)
|
||||
)
|
||||
}
|
||||
TooltipIconButton(
|
||||
onClick = onBackClick,
|
||||
icon = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back)
|
||||
)
|
||||
},
|
||||
actions = actions
|
||||
)
|
||||
|
||||
@ -0,0 +1,84 @@
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package org.schabi.newpipe.ui.components.common
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
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.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
/**
|
||||
* Useful to show a descriptive popup tooltip when something (e.g. a button) is long pressed. This
|
||||
* happens by default on XML Views buttons, but needs to be done manually in Compose.
|
||||
*
|
||||
* @param text the text to show in the tooltip
|
||||
* @param modifier The [TooltipBox] implementation does not handle modifiers well, since it wraps
|
||||
* [content] in a [Box], rendering some [content] modifiers useless. Therefore we have to wrap the
|
||||
* [TooltipBox] in yet another [Box] with its own modifier, passed as a parameter here.
|
||||
* @param content the content that will show a tooltip when long pressed (e.g. a button)
|
||||
*/
|
||||
@Composable
|
||||
fun SimpleTooltipBox(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Box(modifier = modifier) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = { PlainTooltip { Text(text) } },
|
||||
state = rememberTooltipState(),
|
||||
content = content
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An [IconButton] that shows a descriptive popup tooltip when it is long pressed.
|
||||
*
|
||||
* @param onClick handles clicks on the button
|
||||
* @param icon the icon to show inside the button
|
||||
* @param contentDescription the text to use as content description for the button,
|
||||
* and also to show in the tooltip
|
||||
* @param modifier as described in [SimpleTooltipBox]
|
||||
* @param buttonModifier a modifier for the internal [IconButton]
|
||||
* @param iconModifier a modifier for the internal [Icon]
|
||||
* @param tint the color of the icon
|
||||
*/
|
||||
@Composable
|
||||
fun TooltipIconButton(
|
||||
onClick: () -> Unit,
|
||||
icon: ImageVector,
|
||||
contentDescription: String,
|
||||
modifier: Modifier = Modifier,
|
||||
buttonModifier: Modifier = Modifier,
|
||||
iconModifier: Modifier = Modifier,
|
||||
tint: Color = LocalContentColor.current
|
||||
) {
|
||||
SimpleTooltipBox(
|
||||
text = contentDescription,
|
||||
modifier = modifier
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
modifier = buttonModifier
|
||||
) {
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = contentDescription,
|
||||
tint = tint,
|
||||
modifier = iconModifier
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
@ -40,7 +41,6 @@ import androidx.compose.material3.BottomSheetDefaults
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
@ -70,6 +70,7 @@ import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.Placeholder
|
||||
import androidx.compose.ui.text.PlaceholderVerticalAlign
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
@ -93,6 +94,8 @@ import org.schabi.newpipe.error.ErrorInfo
|
||||
import org.schabi.newpipe.error.ErrorUtil
|
||||
import org.schabi.newpipe.error.UserAction.LONG_PRESS_MENU_ACTION
|
||||
import org.schabi.newpipe.extractor.stream.StreamType
|
||||
import org.schabi.newpipe.ui.components.common.SimpleTooltipBox
|
||||
import org.schabi.newpipe.ui.components.common.TooltipIconButton
|
||||
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.EnqueueNext
|
||||
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.ShowChannelDetails
|
||||
import org.schabi.newpipe.ui.discardAllTouchesIf
|
||||
@ -284,7 +287,6 @@ private fun LongPressMenuContent(
|
||||
enabled = action.enabled(),
|
||||
modifier = Modifier
|
||||
.height(buttonHeight)
|
||||
.fillMaxWidth()
|
||||
.weight(1F)
|
||||
)
|
||||
rowIndex += 1
|
||||
@ -355,22 +357,19 @@ fun LongPressMenuDragHandle(onEditActions: () -> Unit) {
|
||||
BottomSheetDefaults.DragHandle(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
IconButton(
|
||||
|
||||
// show a small button here, it's not an important button and it shouldn't
|
||||
// capture the user attention
|
||||
TooltipIconButton(
|
||||
onClick = onEditActions,
|
||||
modifier = Modifier.align(Alignment.CenterEnd)
|
||||
) {
|
||||
// show a small button here, it's not an important button and it shouldn't
|
||||
// capture the user attention
|
||||
Icon(
|
||||
imageVector = Icons.Default.Tune,
|
||||
contentDescription = stringResource(R.string.edit),
|
||||
// same color and height as the DragHandle
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier
|
||||
.padding(2.dp)
|
||||
.size(16.dp)
|
||||
)
|
||||
}
|
||||
icon = Icons.Default.Tune,
|
||||
contentDescription = stringResource(R.string.long_press_menu_actions_editor),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.align(Alignment.CenterEnd),
|
||||
iconModifier = Modifier
|
||||
.padding(2.dp)
|
||||
.size(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -519,32 +518,42 @@ fun LongPressMenuHeader(
|
||||
if (subtitle.isNotBlank()) {
|
||||
Spacer(Modifier.height(1.dp))
|
||||
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
inlineContent = getSubtitleInlineContent(),
|
||||
modifier = if (onUploaderClick == null) {
|
||||
Modifier
|
||||
if (onUploaderClick == null) {
|
||||
LongPressMenuHeaderSubtitle(subtitle)
|
||||
} else {
|
||||
val label = if (item.uploader != null) {
|
||||
stringResource(R.string.show_channel_details_for, item.uploader)
|
||||
} else {
|
||||
Modifier.clickable(
|
||||
onClick = onUploaderClick,
|
||||
onClickLabel = if (item.uploader != null) {
|
||||
stringResource(R.string.show_channel_details_for, item.uploader)
|
||||
} else {
|
||||
stringResource(R.string.show_channel_details)
|
||||
}
|
||||
stringResource(R.string.show_channel_details)
|
||||
}
|
||||
SimpleTooltipBox(
|
||||
text = label
|
||||
) {
|
||||
LongPressMenuHeaderSubtitle(
|
||||
subtitle,
|
||||
Modifier.clickable(onClick = onUploaderClick, onClickLabel = label)
|
||||
)
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.fadedMarquee(edgeWidth = 12.dp)
|
||||
.testTag("ShowChannelDetails")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LongPressMenuHeaderSubtitle(subtitle: AnnotatedString, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = subtitle,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
inlineContent = getSubtitleInlineContent(),
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.fadedMarquee(edgeWidth = 12.dp)
|
||||
.testTag("ShowChannelDetails")
|
||||
)
|
||||
}
|
||||
|
||||
fun getSubtitleAnnotatedString(
|
||||
item: LongPressable,
|
||||
showLink: Boolean,
|
||||
@ -618,30 +627,33 @@ fun LongPressMenuButton(
|
||||
icon: ImageVector,
|
||||
text: String,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true
|
||||
enabled: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
// TODO possibly make it so that when you long-press on the button, the label appears on-screen
|
||||
// as a small popup, so in case the label text is cut off the users can still read it in full
|
||||
OutlinedButton(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
shape = MaterialTheme.shapes.large,
|
||||
contentPadding = PaddingValues(start = 3.dp, top = 8.dp, end = 3.dp, bottom = 2.dp),
|
||||
border = null,
|
||||
SimpleTooltipBox(
|
||||
text = text,
|
||||
modifier = modifier
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
FixedHeightCenteredText(
|
||||
text = text,
|
||||
lines = 2,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
OutlinedButton(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
shape = MaterialTheme.shapes.large,
|
||||
contentPadding = PaddingValues(start = 3.dp, top = 8.dp, end = 3.dp, bottom = 2.dp),
|
||||
border = null,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(32.dp)
|
||||
)
|
||||
FixedHeightCenteredText(
|
||||
text = text,
|
||||
lines = 2,
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -659,6 +671,7 @@ private fun LongPressMenuButtonPreviews() {
|
||||
icon = entry.icon,
|
||||
text = stringResource(entry.label),
|
||||
onClick = { },
|
||||
enabled = true,
|
||||
modifier = Modifier.size(86.dp)
|
||||
)
|
||||
}
|
||||
|
||||
@ -42,7 +42,6 @@ import androidx.compose.material.icons.filled.RestartAlt
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
@ -76,6 +75,7 @@ import kotlin.math.floor
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.ktx.letIf
|
||||
import org.schabi.newpipe.ui.components.common.ScaffoldWithToolbar
|
||||
import org.schabi.newpipe.ui.components.common.TooltipIconButton
|
||||
import org.schabi.newpipe.ui.detectDragGestures
|
||||
import org.schabi.newpipe.ui.theme.AppTheme
|
||||
import org.schabi.newpipe.util.text.FixedHeightCenteredText
|
||||
@ -199,12 +199,11 @@ private fun ResetToDefaultsButton(onClick: () -> Unit) {
|
||||
)
|
||||
}
|
||||
|
||||
IconButton(onClick = { showDialog = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.RestartAlt,
|
||||
contentDescription = stringResource(R.string.playback_reset)
|
||||
)
|
||||
}
|
||||
TooltipIconButton(
|
||||
onClick = { showDialog = true },
|
||||
icon = Icons.Default.RestartAlt,
|
||||
contentDescription = stringResource(R.string.playback_reset)
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@ -872,7 +872,6 @@
|
||||
<string name="migration_info_6_7_message">SoundCloud has discontinued the original Top 50 charts. The corresponding tab has been removed from your main page.</string>
|
||||
<string name="auto_queue_description">Next</string>
|
||||
<string name="newpipe_extractor_description">NewPipeExtractor is a library for extracting things from streaming sites. It is a core component of NewPipe, but could be used independently.</string>
|
||||
<string name="edit">Edit</string>
|
||||
<plurals name="comments">
|
||||
<item quantity="one">%d comment</item>
|
||||
<item quantity="other">%d comments</item>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user