From 1609c62489a421a0ad99e5b6ba002b29de1f95bc Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sun, 30 Nov 2025 13:51:48 +0100 Subject: [PATCH] change player control button arrangement in RTL layouts --- .../widgets/clock/parts/MusicPartProvider.kt | 215 +++++++++--------- .../ui/launcher/widgets/music/MusicWidget.kt | 209 +++++++++-------- 2 files changed, 221 insertions(+), 203 deletions(-) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/MusicPartProvider.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/MusicPartProvider.kt index c2d10ef13..366b2b63f 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/MusicPartProvider.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/clock/parts/MusicPartProvider.kt @@ -19,16 +19,19 @@ import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.ktx.sendWithBackgroundPermission @@ -63,77 +66,81 @@ class MusicPartProvider : PartProvider, KoinComponent { val supportedActions by musicService.supportedActions.collectAsState(SupportedActions()) if (compactLayout) { - Row( - verticalAlignment = Alignment.CenterVertically + CompositionLocalProvider( + LocalLayoutDirection provides LayoutDirection.Ltr ) { - Column( - modifier = Modifier - .weight(1f) - .padding(8.dp) - .combinedClickable( - onClick = { - try { - musicService - .openPlayer() - ?.sendWithBackgroundPermission(context) - } catch (e: PendingIntent.CanceledException) { - CrashReporter.logException(e) - } - }, - onLongClick = { - musicService.openPlayerChooser(context) - } - ) + Row( + verticalAlignment = Alignment.CenterVertically ) { - title?.let { - Text( - text = it, - style = MaterialTheme.typography.labelLarge, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - artist?.let { - Text( - text = it, - style = MaterialTheme.typography.bodyMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } - IconButton(onClick = { musicService.togglePause() }) { - AnimatedContent( - state == PlaybackState.Playing, - transitionSpec = { - fadeIn().togetherWith(fadeOut()) - }, - modifier = Modifier.rotate( - animateFloatAsState( - if (state == PlaybackState.Playing) 90f else 0f - ).value - ) - ) { - if (it) { - Icon( - painterResource(R.drawable.pause_24px), - stringResource(R.string.music_widget_pause), - modifier = Modifier.rotate(-90f) + Column( + modifier = Modifier + .weight(1f) + .padding(8.dp) + .combinedClickable( + onClick = { + try { + musicService + .openPlayer() + ?.sendWithBackgroundPermission(context) + } catch (e: PendingIntent.CanceledException) { + CrashReporter.logException(e) + } + }, + onLongClick = { + musicService.openPlayerChooser(context) + } ) - } else { - Icon( - painterResource(R.drawable.play_arrow_24px), - stringResource(R.string.music_widget_play), + ) { + title?.let { + Text( + text = it, + style = MaterialTheme.typography.labelLarge, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + } + artist?.let { + Text( + text = it, + style = MaterialTheme.typography.bodyMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis ) } } - } - if (supportedActions.skipToNext) { - IconButton(onClick = { musicService.next() }) { - Icon( - painterResource(R.drawable.skip_next_24px), - contentDescription = stringResource(R.string.music_widget_next_track) - ) + IconButton(onClick = { musicService.togglePause() }) { + AnimatedContent( + state == PlaybackState.Playing, + transitionSpec = { + fadeIn().togetherWith(fadeOut()) + }, + modifier = Modifier.rotate( + animateFloatAsState( + if (state == PlaybackState.Playing) 90f else 0f + ).value + ) + ) { + if (it) { + Icon( + painterResource(R.drawable.pause_24px), + stringResource(R.string.music_widget_pause), + modifier = Modifier.rotate(-90f) + ) + } else { + Icon( + painterResource(R.drawable.play_arrow_24px), + stringResource(R.string.music_widget_play), + ) + } + } + } + if (supportedActions.skipToNext) { + IconButton(onClick = { musicService.next() }) { + Icon( + painterResource(R.drawable.skip_next_24px), + contentDescription = stringResource(R.string.music_widget_next_track) + ) + } } } } @@ -177,47 +184,51 @@ class MusicPartProvider : PartProvider, KoinComponent { ) } } - Row { - if (supportedActions.skipToPrevious) { - IconButton(onClick = { musicService.previous() }) { - Icon( - painterResource(R.drawable.skip_previous_24px), - contentDescription = stringResource(R.string.music_widget_previous_track) - ) - } - } - IconButton(onClick = { musicService.togglePause() }) { - AnimatedContent( - state == PlaybackState.Playing, - transitionSpec = { - fadeIn().togetherWith(fadeOut()) - }, - modifier = Modifier.rotate( - animateFloatAsState( - if (state == PlaybackState.Playing) 90f else 0f - ).value - ) - ) { - if (it) { + CompositionLocalProvider( + LocalLayoutDirection provides LayoutDirection.Ltr + ) { + Row { + if (supportedActions.skipToPrevious) { + IconButton(onClick = { musicService.previous() }) { Icon( - painterResource(R.drawable.pause_24px), - stringResource(R.string.music_widget_pause), - modifier = Modifier.rotate(-90f) - ) - } else { - Icon( - painterResource(R.drawable.play_arrow_24px), - stringResource(R.string.music_widget_play), + painterResource(R.drawable.skip_previous_24px), + contentDescription = stringResource(R.string.music_widget_previous_track) ) } } - } - if (supportedActions.skipToPrevious) { - IconButton(onClick = { musicService.next() }) { - Icon( - painterResource(R.drawable.skip_next_24px), - contentDescription = stringResource(R.string.music_widget_next_track) - ) + IconButton(onClick = { musicService.togglePause() }) { + AnimatedContent( + state == PlaybackState.Playing, + transitionSpec = { + fadeIn().togetherWith(fadeOut()) + }, + modifier = Modifier.rotate( + animateFloatAsState( + if (state == PlaybackState.Playing) 90f else 0f + ).value + ) + ) { + if (it) { + Icon( + painterResource(R.drawable.pause_24px), + stringResource(R.string.music_widget_pause), + modifier = Modifier.rotate(-90f) + ) + } else { + Icon( + painterResource(R.drawable.play_arrow_24px), + stringResource(R.string.music_widget_play), + ) + } + } + } + if (supportedActions.skipToPrevious) { + IconButton(onClick = { musicService.next() }) { + Icon( + painterResource(R.drawable.skip_next_24px), + contentDescription = stringResource(R.string.music_widget_next_track) + ) + } } } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/music/MusicWidget.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/music/MusicWidget.kt index f1999cfc6..bcb7433be 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/music/MusicWidget.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/launcher/widgets/music/MusicWidget.kt @@ -7,13 +7,8 @@ import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut -import androidx.compose.animation.graphics.res.animatedVectorResource -import androidx.compose.animation.graphics.res.rememberAnimatedVectorPainter -import androidx.compose.animation.graphics.vector.AnimatedImageVector -import androidx.compose.animation.scaleIn import androidx.compose.animation.togetherWith import androidx.compose.foundation.Image import androidx.compose.foundation.background @@ -48,6 +43,7 @@ import androidx.compose.material3.Slider import androidx.compose.material3.SliderDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -62,12 +58,14 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalLayoutDirection 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.style.TextOverflow import androidx.compose.ui.unit.IntRect +import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.roundToIntRect import androidx.core.content.res.ResourcesCompat @@ -165,39 +163,44 @@ fun MusicWidget(widget: MusicWidget) { var seekPosition by remember { mutableStateOf(null) } if (pos != null && dur != null && dur > 0) { - if (playbackState != PlaybackState.Stopped && supportedActions.seekTo && widget.config.interactiveProgressBar) { - Slider( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 14.dp) - .requiredHeightIn(max = 20.dp), - value = (if (isDragged) seekPosition else pos?.toFloat()) ?: 0f, - valueRange = 0f..dur.toFloat(), - interactionSource = interactionSource, - onValueChange = { - seekPosition = it - }, - onValueChangeFinished = { - seekPosition?.let { - viewModel.seekTo(it.toLong()) - pos = it.toLong() + CompositionLocalProvider( + LocalLayoutDirection provides LayoutDirection.Ltr + ) { + if (playbackState != PlaybackState.Stopped && supportedActions.seekTo && widget.config.interactiveProgressBar) { + Slider( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp) + .requiredHeightIn(max = 20.dp), + value = (if (isDragged) seekPosition else pos?.toFloat()) + ?: 0f, + valueRange = 0f..dur.toFloat(), + interactionSource = interactionSource, + onValueChange = { + seekPosition = it + }, + onValueChangeFinished = { + seekPosition?.let { + viewModel.seekTo(it.toLong()) + pos = it.toLong() + } + }, + track = { + SliderDefaults.Track( + sliderState = it, + modifier = Modifier.requiredHeight(4.dp) + ) } - }, - track = { - SliderDefaults.Track( - sliderState = it, - modifier = Modifier.requiredHeight(4.dp) - ) - } - ) - } else { - LinearProgressIndicator( - progress = { (pos?.toFloat() ?: 0f) / dur.toFloat() }, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 8.dp, horizontal = 16.dp), - strokeCap = StrokeCap.Round, - ) + ) + } else { + LinearProgressIndicator( + progress = { (pos?.toFloat() ?: 0f) / dur.toFloat() }, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp, horizontal = 16.dp), + strokeCap = StrokeCap.Round, + ) + } } } else { Box( @@ -302,80 +305,84 @@ fun MusicWidget(widget: MusicWidget) { } } } - Row( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 16.dp), - horizontalArrangement = Arrangement.SpaceAround, - verticalAlignment = Alignment.CenterVertically, - ) { - if (supportedActions.skipToPrevious) { - Tooltip( - tooltipText = stringResource(R.string.music_widget_previous_track) + CompositionLocalProvider( + LocalLayoutDirection provides LayoutDirection.Ltr + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 16.dp), + horizontalArrangement = Arrangement.SpaceAround, + verticalAlignment = Alignment.CenterVertically, + ) { - IconButton( - onClick = { - viewModel.skipPrevious() - }) { - Icon( - painter = painterResource(R.drawable.skip_previous_24px), - stringResource(R.string.music_widget_previous_track) - ) - } - } - } - val playPauseIcon = - AnimatedImageVector.animatedVectorResource(R.drawable.anim_ic_play_pause) - Tooltip( - tooltipText = stringResource( - if (playbackState == PlaybackState.Playing) R.string.music_widget_pause - else R.string.music_widget_play - ) - ) { - FilledTonalIconButton( - colors = IconButtonDefaults.filledTonalIconButtonColors( - containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = MaterialTheme.transparency.surface), - ), - onClick = { viewModel.togglePause() }, - ) { - AnimatedContent( - playbackState == PlaybackState.Playing, - transitionSpec = { - fadeIn().togetherWith(fadeOut()) - }, - modifier = Modifier.rotate( - animateFloatAsState( - if (playbackState == PlaybackState.Playing) 90f else 0f - ).value - ) + if (supportedActions.skipToPrevious) { + Tooltip( + tooltipText = stringResource(R.string.music_widget_previous_track) ) { - if (it) { + IconButton( + onClick = { + viewModel.skipPrevious() + }) { Icon( - painterResource(R.drawable.pause_24px), - stringResource(R.string.music_widget_pause), - modifier = Modifier.rotate(-90f) - ) - } else { - Icon( - painterResource(R.drawable.play_arrow_24px), - stringResource(R.string.music_widget_play), + painter = painterResource(R.drawable.skip_previous_24px), + stringResource(R.string.music_widget_previous_track) ) } } } - } - if (supportedActions.skipToNext) { + Tooltip( - tooltipText = stringResource(R.string.music_widget_next_track) + tooltipText = stringResource( + if (playbackState == PlaybackState.Playing) R.string.music_widget_pause + else R.string.music_widget_play + ) ) { - IconButton(onClick = { - viewModel.skipNext() - }) { - Icon( - painter = painterResource(R.drawable.skip_next_24px), - stringResource(R.string.music_widget_next_track) - ) + FilledTonalIconButton( + colors = IconButtonDefaults.filledTonalIconButtonColors( + containerColor = MaterialTheme.colorScheme.secondaryContainer.copy(alpha = MaterialTheme.transparency.surface), + ), + onClick = { viewModel.togglePause() }, + ) { + AnimatedContent( + playbackState == PlaybackState.Playing, + transitionSpec = { + fadeIn().togetherWith(fadeOut()) + }, + modifier = Modifier.rotate( + animateFloatAsState( + if (playbackState == PlaybackState.Playing) 90f else 0f + ).value + ) + ) { + if (it) { + Icon( + painterResource(R.drawable.pause_24px), + stringResource(R.string.music_widget_pause), + modifier = Modifier.rotate(-90f) + ) + } else { + Icon( + painterResource(R.drawable.play_arrow_24px), + stringResource(R.string.music_widget_play), + ) + } + } + } + } + if (supportedActions.skipToNext) { + Tooltip( + tooltipText = stringResource(R.string.music_widget_next_track) + ) { + IconButton(onClick = { + viewModel.skipNext() + }) { + Icon( + painter = painterResource(R.drawable.skip_next_24px), + stringResource(R.string.music_widget_next_track) + ) + } } } }