Skip to content

Commit 3fc4bc9

Browse files
committed
Add tooltips for long press menu icons
1 parent 3aa5edc commit 3fc4bc9

7 files changed

Lines changed: 174 additions & 82 deletions

File tree

app/src/androidTest/java/org/schabi/newpipe/ui/components/menu/LongPressMenuTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,18 +127,18 @@ class LongPressMenuTest {
127127
private fun assertEditorIsEnteredAndExitedProperly() {
128128
composeRule.onNodeWithContentDescription(R.string.long_press_menu_enabled_actions_description)
129129
.assertDoesNotExist()
130-
composeRule.onNodeWithContentDescription(R.string.edit)
130+
composeRule.onNodeWithContentDescription(R.string.long_press_menu_actions_editor)
131131
.performClick()
132132
composeRule.waitUntil {
133133
composeRule.onNodeWithText(R.string.long_press_menu_enabled_actions)
134134
.isDisplayed()
135135
}
136136

137-
composeRule.onNodeWithContentDescription(R.string.edit)
137+
composeRule.onNodeWithContentDescription(R.string.long_press_menu_actions_editor)
138138
.assertDoesNotExist()
139139
Espresso.pressBack()
140140
composeRule.waitUntil {
141-
composeRule.onNodeWithContentDescription(R.string.edit)
141+
composeRule.onNodeWithContentDescription(R.string.long_press_menu_actions_editor)
142142
.isDisplayed()
143143
}
144144

app/src/main/java/org/schabi/newpipe/ui/Toolbar.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import androidx.compose.foundation.layout.fillMaxWidth
88
import androidx.compose.foundation.layout.padding
99
import androidx.compose.material.icons.Icons
1010
import androidx.compose.material.icons.automirrored.filled.ArrowBack
11+
import androidx.compose.material.icons.filled.Search
1112
import androidx.compose.material3.ExperimentalMaterial3Api
1213
import androidx.compose.material3.Icon
13-
import androidx.compose.material3.IconButton
1414
import androidx.compose.material3.MaterialTheme
1515
import androidx.compose.material3.SearchBar
1616
import androidx.compose.material3.Text
@@ -26,6 +26,7 @@ import androidx.compose.ui.res.painterResource
2626
import androidx.compose.ui.res.stringResource
2727
import androidx.compose.ui.tooling.preview.Preview
2828
import org.schabi.newpipe.R
29+
import org.schabi.newpipe.ui.components.common.TooltipIconButton
2930
import org.schabi.newpipe.ui.theme.AppTheme
3031
import org.schabi.newpipe.ui.theme.SizeTokens
3132

@@ -70,13 +71,12 @@ fun Toolbar(
7071
actions = {
7172
actions()
7273
if (hasSearch) {
73-
IconButton(onClick = { isSearchActive = true }) {
74-
Icon(
75-
painterResource(id = R.drawable.ic_search),
76-
contentDescription = stringResource(id = R.string.search),
77-
tint = MaterialTheme.colorScheme.onSurface
78-
)
79-
}
74+
TooltipIconButton(
75+
onClick = { isSearchActive = true },
76+
icon = Icons.Default.Search,
77+
contentDescription = stringResource(id = R.string.search),
78+
tint = MaterialTheme.colorScheme.onSurface
79+
)
8080
}
8181
}
8282
)

app/src/main/java/org/schabi/newpipe/ui/components/common/ScaffoldWithToolbar.kt

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import androidx.compose.foundation.layout.RowScope
66
import androidx.compose.material.icons.Icons
77
import androidx.compose.material.icons.automirrored.filled.ArrowBack
88
import androidx.compose.material3.ExperimentalMaterial3Api
9-
import androidx.compose.material3.Icon
10-
import androidx.compose.material3.IconButton
119
import androidx.compose.material3.MaterialTheme
1210
import androidx.compose.material3.Scaffold
1311
import androidx.compose.material3.Text
@@ -39,12 +37,11 @@ fun ScaffoldWithToolbar(
3937
actionIconContentColor = MaterialTheme.colorScheme.onPrimaryContainer
4038
),
4139
navigationIcon = {
42-
IconButton(onClick = onBackClick) {
43-
Icon(
44-
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
45-
contentDescription = stringResource(R.string.back)
46-
)
47-
}
40+
TooltipIconButton(
41+
onClick = onBackClick,
42+
icon = Icons.AutoMirrored.Filled.ArrowBack,
43+
contentDescription = stringResource(R.string.back)
44+
)
4845
},
4946
actions = actions
5047
)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
@file:OptIn(ExperimentalMaterial3Api::class)
2+
3+
package org.schabi.newpipe.ui.components.common
4+
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.material3.ExperimentalMaterial3Api
7+
import androidx.compose.material3.Icon
8+
import androidx.compose.material3.IconButton
9+
import androidx.compose.material3.LocalContentColor
10+
import androidx.compose.material3.PlainTooltip
11+
import androidx.compose.material3.Text
12+
import androidx.compose.material3.TooltipBox
13+
import androidx.compose.material3.TooltipDefaults
14+
import androidx.compose.material3.rememberTooltipState
15+
import androidx.compose.runtime.Composable
16+
import androidx.compose.ui.Modifier
17+
import androidx.compose.ui.graphics.Color
18+
import androidx.compose.ui.graphics.vector.ImageVector
19+
20+
/**
21+
* Useful to show a descriptive popup tooltip when something (e.g. a button) is long pressed. This
22+
* happens by default on XML Views buttons, but needs to be done manually in Compose.
23+
*
24+
* @param text the text to show in the tooltip
25+
* @param modifier The [TooltipBox] implementation does not handle modifiers well, since it wraps
26+
* [content] in a [Box], rendering some [content] modifiers useless. Therefore we have to wrap the
27+
* [TooltipBox] in yet another [Box] with its own modifier, passed as a parameter here.
28+
* @param content the content that will show a tooltip when long pressed (e.g. a button)
29+
*/
30+
@Composable
31+
fun SimpleTooltipBox(
32+
text: String,
33+
modifier: Modifier = Modifier,
34+
content: @Composable () -> Unit
35+
) {
36+
Box(modifier = modifier) {
37+
TooltipBox(
38+
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
39+
tooltip = { PlainTooltip { Text(text) } },
40+
state = rememberTooltipState(),
41+
content = content
42+
)
43+
}
44+
}
45+
46+
/**
47+
* An [IconButton] that shows a descriptive popup tooltip when it is long pressed.
48+
*
49+
* @param onClick handles clicks on the button
50+
* @param icon the icon to show inside the button
51+
* @param contentDescription the text to use as content description for the button,
52+
* and also to show in the tooltip
53+
* @param modifier as described in [SimpleTooltipBox]
54+
* @param buttonModifier a modifier for the internal [IconButton]
55+
* @param iconModifier a modifier for the internal [Icon]
56+
* @param tint the color of the icon
57+
*/
58+
@Composable
59+
fun TooltipIconButton(
60+
onClick: () -> Unit,
61+
icon: ImageVector,
62+
contentDescription: String,
63+
modifier: Modifier = Modifier,
64+
buttonModifier: Modifier = Modifier,
65+
iconModifier: Modifier = Modifier,
66+
tint: Color = LocalContentColor.current
67+
) {
68+
SimpleTooltipBox(
69+
text = contentDescription,
70+
modifier = modifier
71+
) {
72+
IconButton(
73+
onClick = onClick,
74+
modifier = buttonModifier
75+
) {
76+
Icon(
77+
icon,
78+
contentDescription = contentDescription,
79+
tint = tint,
80+
modifier = iconModifier
81+
)
82+
}
83+
}
84+
}

app/src/main/java/org/schabi/newpipe/ui/components/menu/LongPressMenu.kt

Lines changed: 68 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.FlowRow
2222
import androidx.compose.foundation.layout.PaddingValues
2323
import androidx.compose.foundation.layout.Row
2424
import androidx.compose.foundation.layout.Spacer
25+
import androidx.compose.foundation.layout.fillMaxSize
2526
import androidx.compose.foundation.layout.fillMaxWidth
2627
import androidx.compose.foundation.layout.height
2728
import androidx.compose.foundation.layout.heightIn
@@ -40,7 +41,6 @@ import androidx.compose.material3.BottomSheetDefaults
4041
import androidx.compose.material3.CircularProgressIndicator
4142
import androidx.compose.material3.ExperimentalMaterial3Api
4243
import androidx.compose.material3.Icon
43-
import androidx.compose.material3.IconButton
4444
import androidx.compose.material3.MaterialTheme
4545
import androidx.compose.material3.ModalBottomSheet
4646
import androidx.compose.material3.OutlinedButton
@@ -70,6 +70,7 @@ import androidx.compose.ui.res.painterResource
7070
import androidx.compose.ui.res.stringResource
7171
import androidx.compose.ui.semantics.contentDescription
7272
import androidx.compose.ui.semantics.semantics
73+
import androidx.compose.ui.text.AnnotatedString
7374
import androidx.compose.ui.text.Placeholder
7475
import androidx.compose.ui.text.PlaceholderVerticalAlign
7576
import androidx.compose.ui.text.SpanStyle
@@ -93,6 +94,8 @@ import org.schabi.newpipe.error.ErrorInfo
9394
import org.schabi.newpipe.error.ErrorUtil
9495
import org.schabi.newpipe.error.UserAction.LONG_PRESS_MENU_ACTION
9596
import org.schabi.newpipe.extractor.stream.StreamType
97+
import org.schabi.newpipe.ui.components.common.SimpleTooltipBox
98+
import org.schabi.newpipe.ui.components.common.TooltipIconButton
9699
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.EnqueueNext
97100
import org.schabi.newpipe.ui.components.menu.LongPressAction.Type.ShowChannelDetails
98101
import org.schabi.newpipe.ui.discardAllTouchesIf
@@ -284,7 +287,6 @@ private fun LongPressMenuContent(
284287
enabled = action.enabled(),
285288
modifier = Modifier
286289
.height(buttonHeight)
287-
.fillMaxWidth()
288290
.weight(1F)
289291
)
290292
rowIndex += 1
@@ -355,22 +357,19 @@ fun LongPressMenuDragHandle(onEditActions: () -> Unit) {
355357
BottomSheetDefaults.DragHandle(
356358
modifier = Modifier.align(Alignment.Center)
357359
)
358-
IconButton(
360+
361+
// show a small button here, it's not an important button and it shouldn't
362+
// capture the user attention
363+
TooltipIconButton(
359364
onClick = onEditActions,
360-
modifier = Modifier.align(Alignment.CenterEnd)
361-
) {
362-
// show a small button here, it's not an important button and it shouldn't
363-
// capture the user attention
364-
Icon(
365-
imageVector = Icons.Default.Tune,
366-
contentDescription = stringResource(R.string.edit),
367-
// same color and height as the DragHandle
368-
tint = MaterialTheme.colorScheme.onSurfaceVariant,
369-
modifier = Modifier
370-
.padding(2.dp)
371-
.size(16.dp)
372-
)
373-
}
365+
icon = Icons.Default.Tune,
366+
contentDescription = stringResource(R.string.long_press_menu_actions_editor),
367+
tint = MaterialTheme.colorScheme.onSurfaceVariant,
368+
modifier = Modifier.align(Alignment.CenterEnd),
369+
iconModifier = Modifier
370+
.padding(2.dp)
371+
.size(16.dp)
372+
)
374373
}
375374
}
376375

@@ -519,32 +518,42 @@ fun LongPressMenuHeader(
519518
if (subtitle.isNotBlank()) {
520519
Spacer(Modifier.height(1.dp))
521520

522-
Text(
523-
text = subtitle,
524-
style = MaterialTheme.typography.bodyMedium,
525-
inlineContent = getSubtitleInlineContent(),
526-
modifier = if (onUploaderClick == null) {
527-
Modifier
521+
if (onUploaderClick == null) {
522+
LongPressMenuHeaderSubtitle(subtitle)
523+
} else {
524+
val label = if (item.uploader != null) {
525+
stringResource(R.string.show_channel_details_for, item.uploader)
528526
} else {
529-
Modifier.clickable(
530-
onClick = onUploaderClick,
531-
onClickLabel = if (item.uploader != null) {
532-
stringResource(R.string.show_channel_details_for, item.uploader)
533-
} else {
534-
stringResource(R.string.show_channel_details)
535-
}
527+
stringResource(R.string.show_channel_details)
528+
}
529+
SimpleTooltipBox(
530+
text = label
531+
) {
532+
LongPressMenuHeaderSubtitle(
533+
subtitle,
534+
Modifier.clickable(onClick = onUploaderClick, onClickLabel = label)
536535
)
537536
}
538-
.fillMaxWidth()
539-
.fadedMarquee(edgeWidth = 12.dp)
540-
.testTag("ShowChannelDetails")
541-
)
537+
}
542538
}
543539
}
544540
}
545541
}
546542
}
547543

544+
@Composable
545+
private fun LongPressMenuHeaderSubtitle(subtitle: AnnotatedString, modifier: Modifier = Modifier) {
546+
Text(
547+
text = subtitle,
548+
style = MaterialTheme.typography.bodyMedium,
549+
inlineContent = getSubtitleInlineContent(),
550+
modifier = modifier
551+
.fillMaxWidth()
552+
.fadedMarquee(edgeWidth = 12.dp)
553+
.testTag("ShowChannelDetails")
554+
)
555+
}
556+
548557
fun getSubtitleAnnotatedString(
549558
item: LongPressable,
550559
showLink: Boolean,
@@ -618,30 +627,33 @@ fun LongPressMenuButton(
618627
icon: ImageVector,
619628
text: String,
620629
onClick: () -> Unit,
621-
modifier: Modifier = Modifier,
622-
enabled: Boolean = true
630+
enabled: Boolean,
631+
modifier: Modifier = Modifier
623632
) {
624-
// TODO possibly make it so that when you long-press on the button, the label appears on-screen
625-
// as a small popup, so in case the label text is cut off the users can still read it in full
626-
OutlinedButton(
627-
onClick = onClick,
628-
enabled = enabled,
629-
shape = MaterialTheme.shapes.large,
630-
contentPadding = PaddingValues(start = 3.dp, top = 8.dp, end = 3.dp, bottom = 2.dp),
631-
border = null,
633+
SimpleTooltipBox(
634+
text = text,
632635
modifier = modifier
633636
) {
634-
Column(horizontalAlignment = Alignment.CenterHorizontally) {
635-
Icon(
636-
imageVector = icon,
637-
contentDescription = null,
638-
modifier = Modifier.size(32.dp)
639-
)
640-
FixedHeightCenteredText(
641-
text = text,
642-
lines = 2,
643-
style = MaterialTheme.typography.bodySmall
644-
)
637+
OutlinedButton(
638+
onClick = onClick,
639+
enabled = enabled,
640+
shape = MaterialTheme.shapes.large,
641+
contentPadding = PaddingValues(start = 3.dp, top = 8.dp, end = 3.dp, bottom = 2.dp),
642+
border = null,
643+
modifier = Modifier.fillMaxSize()
644+
) {
645+
Column(horizontalAlignment = Alignment.CenterHorizontally) {
646+
Icon(
647+
imageVector = icon,
648+
contentDescription = null,
649+
modifier = Modifier.size(32.dp)
650+
)
651+
FixedHeightCenteredText(
652+
text = text,
653+
lines = 2,
654+
style = MaterialTheme.typography.bodySmall
655+
)
656+
}
645657
}
646658
}
647659
}
@@ -659,6 +671,7 @@ private fun LongPressMenuButtonPreviews() {
659671
icon = entry.icon,
660672
text = stringResource(entry.label),
661673
onClick = { },
674+
enabled = true,
662675
modifier = Modifier.size(86.dp)
663676
)
664677
}

0 commit comments

Comments
 (0)