diff --git a/app/src/main/java/live/mehiz/mpvkt/ui/player/PlayerViewModel.kt b/app/src/main/java/live/mehiz/mpvkt/ui/player/PlayerViewModel.kt index 104ca02..bd2256e 100644 --- a/app/src/main/java/live/mehiz/mpvkt/ui/player/PlayerViewModel.kt +++ b/app/src/main/java/live/mehiz/mpvkt/ui/player/PlayerViewModel.kt @@ -79,14 +79,18 @@ class PlayerViewModel( val sheetShown = MutableStateFlow(Sheets.None) val panelShown = MutableStateFlow(Panels.None) - val gestureSeekAmount = MutableStateFlow(0) + + // Pair(startingPosition, seekAmount) + val gestureSeekAmount = MutableStateFlow?>(null) private var timerJob: Job? = null - private val _remainingTime = MutableStateFlow(null) + private val _remainingTime = MutableStateFlow(0) val remainingTime = _remainingTime.asStateFlow() fun startTimer(seconds: Int) { timerJob?.cancel() + _remainingTime.value = seconds + if (seconds < 1) return timerJob = viewModelScope.launch { for (time in seconds downTo 0) { _remainingTime.value = time @@ -210,7 +214,7 @@ class PlayerViewModel( chapters.add( Segment( name = title, - start = time.toFloat(), + start = time.toFloat().coerceAtLeast(0f), ), ) } diff --git a/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/GestureHandler.kt b/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/GestureHandler.kt index e117b82..8b25a15 100644 --- a/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/GestureHandler.kt +++ b/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/GestureHandler.kt @@ -210,7 +210,7 @@ fun GestureHandler(modifier: Modifier = Modifier) { viewModel.pause() }, onDragEnd = { - viewModel.gestureSeekAmount.update { 0 } + viewModel.gestureSeekAmount.update { null } viewModel.hideSeekBar() if (!wasPlayerAlreadyPause) viewModel.unpause() }, @@ -219,7 +219,9 @@ fun GestureHandler(modifier: Modifier = Modifier) { if (position <= 0f && dragAmount < 0) return@detectHorizontalDragGestures val seekBy = ((dragAmount * 150f / size.width).coerceIn(0f - position, duration - position)).toInt() viewModel.seekBy(seekBy) - viewModel.gestureSeekAmount.update { (position - startingPosition).toInt() } + viewModel.gestureSeekAmount.update { + Pair(startingPosition.toInt(), (position - startingPosition).toInt()) + } if (showSeekbarWhenSeeking) viewModel.showSeekBar() } } diff --git a/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/PlayerControls.kt b/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/PlayerControls.kt index 5085254..39a93e1 100644 --- a/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/PlayerControls.kt +++ b/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/PlayerControls.kt @@ -30,6 +30,7 @@ import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect @@ -43,9 +44,12 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension +import `is`.xyz.mpv.Utils import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.delay import kotlinx.coroutines.flow.update @@ -63,6 +67,7 @@ import live.mehiz.mpvkt.ui.player.controls.components.VolumeSlider import live.mehiz.mpvkt.ui.theme.PlayerRippleTheme import live.mehiz.mpvkt.ui.theme.spacing import org.koin.compose.koinInject +import kotlin.math.abs @Suppress("CompositionLocalAllowlist") val LocalPlayerButtonsClickEvent = staticCompositionLocalOf { {} } @@ -83,6 +88,7 @@ fun PlayerControls( val duration by viewModel.duration.collectAsState() val position by viewModel.pos.collectAsState() val paused by viewModel.paused.collectAsState() + val gestureSeekAmount by viewModel.gestureSeekAmount.collectAsState() var isSeeking by remember { mutableStateOf(false) } var resetControls by remember { mutableStateOf(true) } LaunchedEffect( @@ -171,7 +177,7 @@ fun PlayerControls( viewModel.playerUpdate.update { PlayerUpdates.None } } AnimatedVisibility( - currentPlayerUpdate != PlayerUpdates.None, + gestureSeekAmount != null || currentPlayerUpdate != PlayerUpdates.None, enter = fadeIn(playControlsAnimationSpec()), exit = fadeOut(playControlsAnimationSpec()), modifier = Modifier.constrainAs(playerUpdates) { @@ -179,10 +185,24 @@ fun PlayerControls( linkTo(parent.top, parent.bottom, bias = 0.2f) }, ) { - when (currentPlayerUpdate) { - PlayerUpdates.DoubleSpeed -> DoubleSpeedPlayerUpdate() - PlayerUpdates.AspectRatio -> TextPlayerUpdate(stringResource(aspectRatio.titleRes)) - else -> {} + if (gestureSeekAmount != null) { + Text( + stringResource( + R.string.player_gesture_seek_indicator, + if (gestureSeekAmount!!.second >= 0) '+' else '-', + Utils.prettyTime(abs(gestureSeekAmount!!.second)), + Utils.prettyTime(gestureSeekAmount!!.first + gestureSeekAmount!!.second), + ), + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + ) + } else { + when (currentPlayerUpdate) { + PlayerUpdates.DoubleSpeed -> DoubleSpeedPlayerUpdate() + PlayerUpdates.AspectRatio -> TextPlayerUpdate(stringResource(aspectRatio.titleRes)) + else -> {} + } } } @@ -225,7 +245,7 @@ fun PlayerControls( interaction, rememberRipple(), ) { viewModel.pauseUnpause() } - .padding(16.dp), + .padding(MaterialTheme.spacing.medium), contentDescription = null, ) } @@ -307,5 +327,5 @@ fun PlayerControls( fun playControlsAnimationSpec(): FiniteAnimationSpec = tween( durationMillis = 200, - easing = LinearOutSlowInEasing + easing = LinearOutSlowInEasing, ) diff --git a/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/components/sheets/MoreSheet.kt b/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/components/sheets/MoreSheet.kt index 4d8fbae..3e389f0 100644 --- a/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/components/sheets/MoreSheet.kt +++ b/app/src/main/java/live/mehiz/mpvkt/ui/player/controls/components/sheets/MoreSheet.kt @@ -23,6 +23,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.IconToggleButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -89,18 +90,18 @@ fun MoreSheet( horizontalArrangement = Arrangement.spacedBy(MaterialTheme.spacing.extraSmall), ) { var isSleepTimerDialogShown by remember { mutableStateOf(false) } - IconButton(onClick = { isSleepTimerDialogShown = true }) { + val remainingTime by viewModel.remainingTime.collectAsState() + IconToggleButton( + checked = remainingTime > 0, + onCheckedChange = { isSleepTimerDialogShown = true }, + ) { Icon(imageVector = Icons.Outlined.Timer, contentDescription = null) } if (isSleepTimerDialogShown) { - val remainingTime by viewModel.remainingTime.collectAsState() TimePickerDialog( - remainingTime = remainingTime ?: 0, + remainingTime = remainingTime, onDismissRequest = { isSleepTimerDialogShown = false }, - onTimeSelect = { - if (it < 1) return@TimePickerDialog - viewModel.startTimer(it) - } + onTimeSelect = viewModel::startTimer ) } TextButton(onClick = onEnterFiltersPanel) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 27089e5..5839fe1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -161,6 +161,7 @@ Page %d %d seconds x%.2f + %c%s\n[%s] Fit Screen Crop