Add tooltips to widgets (#1229)

* add tooltips

* update toolbar tooltip

* prettify ifelse statements
This commit is contained in:
leekleak 2025-01-18 17:17:23 +02:00 committed by GitHub
parent 11ae8386fd
commit 8d7084e23f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 208 additions and 272 deletions

View File

@ -1,7 +1,6 @@
package de.mm20.launcher2.ui.component package de.mm20.launcher2.ui.component
import androidx.compose.animation.animateContentSize import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -16,16 +15,11 @@ import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text 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.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
@ -35,7 +29,6 @@ import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.ktx.toDp import de.mm20.launcher2.ui.ktx.toDp
import de.mm20.launcher2.ui.locals.LocalWindowPosition import de.mm20.launcher2.ui.locals.LocalWindowPosition
import kotlinx.coroutines.launch
import kotlin.math.min import kotlin.math.min
@Composable @Composable
@ -57,7 +50,6 @@ fun Toolbar(
@Composable @Composable
fun Icons(actions: List<ToolbarAction>, slots: Int) { fun Icons(actions: List<ToolbarAction>, slots: Int) {
val scope = rememberCoroutineScope()
for (i in 0 until min(slots, actions.size)) { for (i in 0 until min(slots, actions.size)) {
if (i == slots - 1 && slots != actions.size) { if (i == slots - 1 && slots != actions.size) {
var showMenu by remember { mutableStateOf(false) } var showMenu by remember { mutableStateOf(false) }
@ -82,28 +74,11 @@ fun Icons(actions: List<ToolbarAction>, slots: Int) {
} }
} else { } else {
val action = actions[i] val action = actions[i]
val tooltipState = rememberTooltipState() Tooltip(action.label) {
TooltipBox(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
PlainTooltip {
Text(action.label)
}
},
state = tooltipState
) {
when (action) { when (action) {
is DefaultToolbarAction -> { is DefaultToolbarAction -> {
IconButton( IconButton(
onClick = action.action, onClick = action.action,
modifier = Modifier.combinedClickable(
onClick = {},
onLongClick = {
scope.launch {
tooltipState.show()
}
}
)
) { ) {
Icon(action.icon, contentDescription = action.label) Icon(action.icon, contentDescription = action.label)
} }
@ -116,14 +91,6 @@ fun Icons(actions: List<ToolbarAction>, slots: Int) {
onClick = { onClick = {
showMenu = true showMenu = true
}, },
modifier = Modifier.combinedClickable(
onClick = {},
onLongClick = {
scope.launch {
tooltipState.show()
}
}
)
) { ) {
Icon(action.icon, contentDescription = action.label) Icon(action.icon, contentDescription = action.label)
} }

View File

@ -0,0 +1,28 @@
package de.mm20.launcher2.ui.component
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
@Composable
fun Tooltip (
tooltipText: String,
modifier: Modifier = Modifier,
content: @Composable () -> Unit
) {
val tooltipState = rememberTooltipState()
TooltipBox(
state = tooltipState,
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = { PlainTooltip { Text(tooltipText) } },
modifier = modifier
) {
content()
}
}

View File

@ -49,23 +49,17 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Slider import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Text 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.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
@ -88,13 +82,13 @@ import de.mm20.launcher2.music.PlaybackState
import de.mm20.launcher2.music.SupportedActions import de.mm20.launcher2.music.SupportedActions
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.MissingPermissionBanner import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.Tooltip
import de.mm20.launcher2.ui.ktx.conditional import de.mm20.launcher2.ui.ktx.conditional
import de.mm20.launcher2.ui.launcher.transitions.EnterHomeTransitionParams import de.mm20.launcher2.ui.launcher.transitions.EnterHomeTransitionParams
import de.mm20.launcher2.ui.launcher.transitions.HandleEnterHomeTransition import de.mm20.launcher2.ui.launcher.transitions.HandleEnterHomeTransition
import de.mm20.launcher2.ui.locals.LocalCardStyle import de.mm20.launcher2.ui.locals.LocalCardStyle
import de.mm20.launcher2.ui.locals.LocalWindowSize import de.mm20.launcher2.ui.locals.LocalWindowSize
import de.mm20.launcher2.widgets.MusicWidget import de.mm20.launcher2.widgets.MusicWidget
import kotlinx.coroutines.launch
import kotlin.math.min import kotlin.math.min
@Composable @Composable
@ -334,44 +328,58 @@ fun MusicWidget(widget: MusicWidget) {
) { ) {
if (supportedActions.skipToPrevious) { if (supportedActions.skipToPrevious) {
IconButton( Tooltip(
onClick = { tooltipText = stringResource(R.string.music_widget_previous_track)
viewModel.skipPrevious() ) {
}) { IconButton(
Icon( onClick = {
imageVector = Icons.Rounded.SkipPrevious, viewModel.skipPrevious()
stringResource(R.string.music_widget_previous_track) }) {
) Icon(
imageVector = Icons.Rounded.SkipPrevious,
stringResource(R.string.music_widget_previous_track)
)
}
} }
} }
val playPauseIcon = val playPauseIcon =
AnimatedImageVector.animatedVectorResource(R.drawable.anim_ic_play_pause) AnimatedImageVector.animatedVectorResource(R.drawable.anim_ic_play_pause)
FilledTonalIconButton( Tooltip(
colors = IconButtonDefaults.filledTonalIconButtonColors( tooltipText = stringResource(
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = LocalCardStyle.current.opacity), if (playbackState == PlaybackState.Playing) R.string.music_widget_pause
), else R.string.music_widget_play
onClick = { viewModel.togglePause() },
) {
Icon(
painter = rememberAnimatedVectorPainter(
playPauseIcon,
atEnd = playbackState == PlaybackState.Playing
),
contentDescription = if (playbackState == PlaybackState.Playing) {
stringResource(R.string.music_widget_pause)
} else {
stringResource(R.string.music_widget_play)
}
) )
) {
FilledTonalIconButton(
colors = IconButtonDefaults.filledTonalIconButtonColors(
containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = LocalCardStyle.current.opacity),
),
onClick = { viewModel.togglePause() },
) {
Icon(
painter = rememberAnimatedVectorPainter(
playPauseIcon,
atEnd = playbackState == PlaybackState.Playing
),
contentDescription = stringResource(
if (playbackState == PlaybackState.Playing) R.string.music_widget_pause
else R.string.music_widget_play
)
)
}
} }
if (supportedActions.skipToNext) { if (supportedActions.skipToNext) {
IconButton(onClick = { Tooltip(
viewModel.skipNext() tooltipText = stringResource(R.string.music_widget_next_track)
}) { ) {
Icon( IconButton(onClick = {
imageVector = Icons.Rounded.SkipNext, viewModel.skipNext()
stringResource(R.string.music_widget_next_track) }) {
) Icon(
imageVector = Icons.Rounded.SkipNext,
stringResource(R.string.music_widget_next_track)
)
}
} }
} }
CustomActions( CustomActions(
@ -394,29 +402,12 @@ fun CustomActions(
val usedSlots = 1 + (if (actions.skipToPrevious) 1 else 0) + (if (actions.skipToNext) 1 else 0) val usedSlots = 1 + (if (actions.skipToPrevious) 1 else 0) + (if (actions.skipToNext) 1 else 0)
val slots = 5 - usedSlots val slots = 5 - usedSlots
val scope = rememberCoroutineScope()
for (i in 0 until min(actions.customActions.size, slots - 1)) { for (i in 0 until min(actions.customActions.size, slots - 1)) {
val action = actions.customActions[i] val action = actions.customActions[i]
val tooltipState = rememberTooltipState() Tooltip(
TooltipBox( tooltipText = action.name.toString()
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
state = tooltipState,
tooltip = {
PlainTooltip {
Text(action.name.toString())
}
},
) { ) {
IconButton( IconButton(
modifier = Modifier.combinedClickable(
onClick = {},
onLongClick = {
scope.launch {
tooltipState.show()
}
}
),
onClick = { onClick = {
onActionSelected(action) onActionSelected(action)
} }
@ -428,8 +419,12 @@ fun CustomActions(
if (slots < actions.customActions.size) { if (slots < actions.customActions.size) {
var showOverflowMenu by remember { mutableStateOf(false) } var showOverflowMenu by remember { mutableStateOf(false) }
Box { Box {
IconButton(onClick = { showOverflowMenu = true }) { Tooltip(
Icon(imageVector = Icons.Rounded.MoreVert, contentDescription = null) tooltipText = stringResource(R.string.action_more_actions)
) {
IconButton(onClick = { showOverflowMenu = true }) {
Icon(imageVector = Icons.Rounded.MoreVert, contentDescription = null)
}
} }
DropdownMenu( DropdownMenu(
expanded = showOverflowMenu, expanded = showOverflowMenu,
@ -456,26 +451,11 @@ fun CustomActions(
} }
} }
} else if (slots == actions.customActions.size) { } else if (slots == actions.customActions.size) {
val tooltipState = rememberTooltipState()
val action = actions.customActions.last() val action = actions.customActions.last()
TooltipBox( Tooltip (
state = tooltipState, tooltipText = action.name.toString()
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
PlainTooltip {
Text(action.name.toString())
}
}
) { ) {
IconButton( IconButton(
modifier = Modifier.combinedClickable(
onClick = {},
onLongClick = {
scope.launch {
tooltipState.show()
}
}
),
onClick = { onClick = {
onActionSelected(action) onActionSelected(action)
} }

View File

@ -43,22 +43,17 @@ import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -74,6 +69,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.Tooltip
import de.mm20.launcher2.ui.component.markdown.MarkdownEditor import de.mm20.launcher2.ui.component.markdown.MarkdownEditor
import de.mm20.launcher2.ui.component.markdown.MarkdownText import de.mm20.launcher2.ui.component.markdown.MarkdownText
import de.mm20.launcher2.ui.locals.LocalSnackbarHostState import de.mm20.launcher2.ui.locals.LocalSnackbarHostState
@ -94,8 +90,6 @@ fun NotesWidget(
val snackbarHostState = LocalSnackbarHostState.current val snackbarHostState = LocalSnackbarHostState.current
val lifecycleOwner = LocalLifecycleOwner.current val lifecycleOwner = LocalLifecycleOwner.current
val scope = rememberCoroutineScope()
var showConflictResolveSheet by remember { mutableStateOf(false) } var showConflictResolveSheet by remember { mutableStateOf(false) }
var readWriteErrorSheetText by remember { mutableStateOf<String?>(null) } var readWriteErrorSheetText by remember { mutableStateOf<String?>(null) }
@ -181,20 +175,12 @@ fun NotesWidget(
Row( Row(
modifier = Modifier.padding(8.dp), modifier = Modifier.padding(8.dp),
) { ) {
val tooltipState = rememberTooltipState() Tooltip(
TooltipBox( tooltipText = stringResource(
state = tooltipState, if (widget.config.linkedFile == null) R.string.note_widget_link_file
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(), else R.string.note_widget_action_unlink_file
tooltip = { ),
PlainTooltip { ) {
Text(
stringResource(
if (widget.config.linkedFile == null) R.string.note_widget_link_file
else R.string.note_widget_action_unlink_file
)
)
}
}) {
IconButton( IconButton(
onClick = { onClick = {
if (widget.config.linkedFile == null) { if (widget.config.linkedFile == null) {
@ -205,14 +191,6 @@ fun NotesWidget(
viewModel.unlinkFile(context) viewModel.unlinkFile(context)
} }
}, },
modifier = Modifier.combinedClickable(
onClick = {},
onLongClick = {
scope.launch {
tooltipState.show()
}
}
)
) { ) {
Icon( Icon(
if (widget.config.linkedFile == null) Icons.Rounded.Link if (widget.config.linkedFile == null) Icons.Rounded.Link
@ -225,26 +203,13 @@ fun NotesWidget(
} }
} }
if (isLastWidget == false) { if (isLastWidget == false) {
TooltipBox( Tooltip(
state = tooltipState, tooltipText = stringResource(R.string.notes_widget_action_dismiss)
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(), ) {
tooltip = {
PlainTooltip {
Text(stringResource(R.string.notes_widget_action_dismiss))
}
}) {
IconButton( IconButton(
onClick = { onClick = {
viewModel.dismissNote() viewModel.dismissNote()
}, },
modifier = Modifier.combinedClickable(
onClick = {},
onLongClick = {
scope.launch {
tooltipState.show()
}
}
)
) { ) {
Icon(Icons.Rounded.Delete, null) Icon(Icons.Rounded.Delete, null)
} }
@ -297,8 +262,12 @@ fun NotesWidget(
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Box { Box {
IconButton(onClick = { showMenu = true }) { Tooltip(
Icon(Icons.Rounded.MoreVert, stringResource(R.string.action_more_actions)) tooltipText = stringResource(R.string.action_more_actions)
) {
IconButton(onClick = { showMenu = true }) {
Icon(Icons.Rounded.MoreVert, stringResource(R.string.action_more_actions))
}
} }
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) { DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
DropdownMenuItem( DropdownMenuItem(

View File

@ -67,6 +67,7 @@ import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog import de.mm20.launcher2.ui.common.WeatherLocationSearchDialog
import de.mm20.launcher2.ui.component.Banner import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.MissingPermissionBanner import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.Tooltip
import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon import de.mm20.launcher2.ui.component.weather.AnimatedWeatherIcon
import de.mm20.launcher2.ui.component.weather.WeatherIcon import de.mm20.launcher2.ui.component.weather.WeatherIcon
import de.mm20.launcher2.ui.ktx.blendIntoViewScale import de.mm20.launcher2.ui.ktx.blendIntoViewScale
@ -231,32 +232,36 @@ fun CurrentWeather(forecast: Forecast, imperialUnits: Boolean) {
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
} }
Surface( Tooltip(
shape = MaterialTheme.shapes.extraSmall.copy( tooltipText = stringResource(R.string.preference_weather_provider)
topStart = CornerSize(0),
topEnd = CornerSize(0),
bottomEnd = CornerSize(0)
),
color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = LocalCardStyle.current.opacity),
) { ) {
Text( Surface(
text = "${forecast.provider} (${ shape = MaterialTheme.shapes.extraSmall.copy(
formatTime( topStart = CornerSize(0),
LocalContext.current, topEnd = CornerSize(0),
forecast.updateTime bottomEnd = CornerSize(0)
) ),
})", color = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = LocalCardStyle.current.opacity),
style = MaterialTheme.typography.bodySmall.copy(fontSize = 8.sp), ) {
modifier = Modifier Text(
.clickable(onClick = { text = "${forecast.provider} (${
val intent = Intent(Intent.ACTION_VIEW).apply { formatTime(
data = Uri.parse(forecast.providerUrl) LocalContext.current,
?: return@clickable forecast.updateTime
} )
context.tryStartActivity(intent) })",
}) style = MaterialTheme.typography.bodySmall.copy(fontSize = 8.sp),
.padding(start = 8.dp, top = 4.dp, bottom = 4.dp, end = 12.dp) modifier = Modifier
) .clickable(onClick = {
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(forecast.providerUrl)
?: return@clickable
}
context.tryStartActivity(intent)
})
.padding(start = 8.dp, top = 4.dp, bottom = 4.dp, end = 12.dp)
)
}
} }
} }
Row( Row(
@ -293,71 +298,84 @@ fun CurrentWeather(forecast: Forecast, imperialUnits: Boolean) {
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
if (forecast.humidity != null) { if (forecast.humidity != null) {
Row( Tooltip(
verticalAlignment = Alignment.CenterVertically tooltipText = stringResource(R.string.weather_forecast_humidity)
) { ) {
Icon( Row(
imageVector = Icons.Rounded.HumidityPercentage, verticalAlignment = Alignment.CenterVertically
modifier = Modifier.size(20.dp), ) {
tint = MaterialTheme.colorScheme.secondary,
contentDescription = null
)
Spacer(modifier = Modifier.padding(3.dp))
Text(
text = "${forecast.humidity!!.roundToInt()} %",
style = MaterialTheme.typography.bodySmall,
)
}
}
if (forecast.windDirection != null || forecast.windSpeed != null) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
if (forecast.windDirection != null) {
// windDirection is "fromDirection"; Wind (arrow) blows into opposite direction
val angle by animateFloatAsState(forecast.windDirection!!.toFloat() + 180f)
Icon( Icon(
imageVector = Icons.Rounded.North, imageVector = Icons.Rounded.HumidityPercentage,
modifier = Modifier
.rotate(angle)
.size(20.dp),
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
)
} else {
Icon(
imageVector = Icons.Rounded.Air,
contentDescription = null,
modifier = Modifier.size(20.dp), modifier = Modifier.size(20.dp),
tint = MaterialTheme.colorScheme.secondary, tint = MaterialTheme.colorScheme.secondary,
contentDescription = null
)
Spacer(modifier = Modifier.padding(3.dp))
Text(
text = "${forecast.humidity!!.roundToInt()} %",
style = MaterialTheme.typography.bodySmall,
) )
} }
Spacer(modifier = Modifier.padding(3.dp)) }
Text(
text = if (forecast.windSpeed != null) { }
formatWindSpeed(imperialUnits, forecast) if (forecast.windDirection != null || forecast.windSpeed != null) {
Tooltip(
tooltipText = stringResource(R.string.weather_forecast_wind)
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
if (forecast.windDirection != null) {
// windDirection is "fromDirection"; Wind (arrow) blows into opposite direction
val angle by animateFloatAsState(forecast.windDirection!!.toFloat() + 180f)
Icon(
imageVector = Icons.Rounded.North,
modifier = Modifier
.rotate(angle)
.size(20.dp),
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary,
)
} else { } else {
windDirectionAsWord(forecast.windDirection!!) Icon(
}, imageVector = Icons.Rounded.Air,
style = MaterialTheme.typography.bodySmall, contentDescription = null,
) modifier = Modifier.size(20.dp),
tint = MaterialTheme.colorScheme.secondary,
)
}
Spacer(modifier = Modifier.padding(3.dp))
Text(
text = if (forecast.windSpeed != null) {
formatWindSpeed(imperialUnits, forecast)
} else {
windDirectionAsWord(forecast.windDirection!!)
},
style = MaterialTheme.typography.bodySmall,
)
}
} }
} }
if (forecast.precipitation != null) { if (forecast.precipitation != null) {
Row( Tooltip(
verticalAlignment = Alignment.CenterVertically tooltipText = stringResource(id = R.string.weather_forecast_precipitation)
) { ) {
Icon( Row(
imageVector = Icons.Rounded.Rain, verticalAlignment = Alignment.CenterVertically
modifier = Modifier.size(20.dp), ) {
contentDescription = null, Icon(
tint = MaterialTheme.colorScheme.secondary, imageVector = Icons.Rounded.Rain,
) modifier = Modifier.size(20.dp),
Spacer(modifier = Modifier.padding(3.dp)) contentDescription = null,
Text( tint = MaterialTheme.colorScheme.secondary,
text = formatPrecipitation(imperialUnits, forecast), )
style = MaterialTheme.typography.bodySmall, Spacer(modifier = Modifier.padding(3.dp))
) Text(
text = formatPrecipitation(imperialUnits, forecast),
style = MaterialTheme.typography.bodySmall,
)
}
} }
} }
} }

View File

@ -16,17 +16,12 @@ import androidx.compose.material.icons.rounded.SettingsSuggest
import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -36,6 +31,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.Tooltip
import de.mm20.launcher2.ui.component.colorpicker.HctColorPicker import de.mm20.launcher2.ui.component.colorpicker.HctColorPicker
import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState
import de.mm20.launcher2.ui.component.preferences.SwitchPreference import de.mm20.launcher2.ui.component.preferences.SwitchPreference
@ -51,17 +47,8 @@ fun CorePaletteColorPreference(
) { ) {
var showDialog by remember { mutableStateOf(false) } var showDialog by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope() Tooltip(
val tooltipState = rememberTooltipState() tooltipText = title
TooltipBox(
state = tooltipState,
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = {
PlainTooltip {
Text(title)
}
},
) { ) {
ColorSwatch( ColorSwatch(
color = Color(value ?: defaultValue), color = Color(value ?: defaultValue),

View File

@ -4,7 +4,6 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
@ -25,21 +24,16 @@ import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Slider import androidx.compose.material3.Slider
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextButton import androidx.compose.material3.TextButton
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -61,11 +55,11 @@ import de.mm20.launcher2.themes.atTone
import de.mm20.launcher2.themes.get import de.mm20.launcher2.themes.get
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.BottomSheetDialog import de.mm20.launcher2.ui.component.BottomSheetDialog
import de.mm20.launcher2.ui.component.Tooltip
import de.mm20.launcher2.ui.component.colorpicker.HctColorPicker import de.mm20.launcher2.ui.component.colorpicker.HctColorPicker
import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState import de.mm20.launcher2.ui.component.colorpicker.rememberHctColorPickerState
import de.mm20.launcher2.ui.ktx.hct import de.mm20.launcher2.ui.ktx.hct
import hct.Hct import hct.Hct
import kotlinx.coroutines.launch
import kotlin.math.roundToInt import kotlin.math.roundToInt
import de.mm20.launcher2.themes.Color as ThemeColor import de.mm20.launcher2.themes.Color as ThemeColor
@ -80,25 +74,15 @@ fun ThemeColorPreference(
) { ) {
var showDialog by remember { mutableStateOf(false) } var showDialog by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope() Tooltip(
val tooltipState = rememberTooltipState() tooltipText = title
TooltipBox(
state = tooltipState,
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
tooltip = { PlainTooltip { Text(title) } }
) { ) {
ColorSwatch( ColorSwatch(
color = Color((value ?: defaultValue).get(corePalette)), color = Color((value ?: defaultValue).get(corePalette)),
modifier = modifier modifier = modifier
.size(48.dp) .size(48.dp)
.combinedClickable( .clickable(
onClick = { showDialog = true }, onClick = { showDialog = true },
onLongClick = {
scope.launch {
tooltipState.show()
}
}
), ),
) )
} }

View File

@ -1005,4 +1005,7 @@
<item quantity="other">%1$s lists selected</item> <item quantity="other">%1$s lists selected</item>
</plurals> </plurals>
<string name="reset_icon">Reset icon</string> <string name="reset_icon">Reset icon</string>
<string name="weather_forecast_humidity">Humidity</string>
<string name="weather_forecast_wind">Wind</string>
<string name="weather_forecast_precipitation">Precipitation</string>
</resources> </resources>