|
@@ -1,6 +1,5 @@
|
|
|
package io.legado.app.ui.book.audio
|
|
package io.legado.app.ui.book.audio
|
|
|
|
|
|
|
|
-import androidx.compose.animation.AnimatedVisibility
|
|
|
|
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
|
|
import androidx.compose.animation.core.tween
|
|
import androidx.compose.animation.core.tween
|
|
|
import androidx.compose.foundation.gestures.Orientation
|
|
import androidx.compose.foundation.gestures.Orientation
|
|
@@ -12,6 +11,7 @@ import androidx.compose.foundation.layout.PaddingValues
|
|
|
import androidx.compose.foundation.layout.Row
|
|
import androidx.compose.foundation.layout.Row
|
|
|
import androidx.compose.foundation.layout.WindowInsets
|
|
import androidx.compose.foundation.layout.WindowInsets
|
|
|
import androidx.compose.foundation.layout.asPaddingValues
|
|
import androidx.compose.foundation.layout.asPaddingValues
|
|
|
|
|
+import androidx.compose.foundation.layout.aspectRatio
|
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
|
import androidx.compose.foundation.layout.navigationBars
|
|
import androidx.compose.foundation.layout.navigationBars
|
|
@@ -20,18 +20,25 @@ import androidx.compose.foundation.layout.statusBars
|
|
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
|
|
import androidx.compose.foundation.pager.VerticalPager
|
|
import androidx.compose.foundation.pager.VerticalPager
|
|
|
import androidx.compose.foundation.pager.rememberPagerState
|
|
import androidx.compose.foundation.pager.rememberPagerState
|
|
|
|
|
+import androidx.compose.foundation.shape.CircleShape
|
|
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
|
import androidx.compose.material.icons.Icons
|
|
import androidx.compose.material.icons.Icons
|
|
|
import androidx.compose.material.icons.automirrored.filled.FormatListBulleted
|
|
import androidx.compose.material.icons.automirrored.filled.FormatListBulleted
|
|
|
|
|
+import androidx.compose.material.icons.automirrored.filled.Login
|
|
|
import androidx.compose.material.icons.automirrored.filled.PlaylistPlay
|
|
import androidx.compose.material.icons.automirrored.filled.PlaylistPlay
|
|
|
|
|
+import androidx.compose.material.icons.automirrored.filled.VolumeUp
|
|
|
|
|
+import androidx.compose.material.icons.filled.Book
|
|
|
import androidx.compose.material.icons.filled.ContentCopy
|
|
import androidx.compose.material.icons.filled.ContentCopy
|
|
|
|
|
+import androidx.compose.material.icons.filled.Crop
|
|
|
|
|
+import androidx.compose.material.icons.filled.CropFree
|
|
|
|
|
+import androidx.compose.material.icons.filled.CropSquare
|
|
|
import androidx.compose.material.icons.filled.Edit
|
|
import androidx.compose.material.icons.filled.Edit
|
|
|
import androidx.compose.material.icons.filled.FastForward
|
|
import androidx.compose.material.icons.filled.FastForward
|
|
|
import androidx.compose.material.icons.filled.Headphones
|
|
import androidx.compose.material.icons.filled.Headphones
|
|
|
import androidx.compose.material.icons.filled.Info
|
|
import androidx.compose.material.icons.filled.Info
|
|
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
|
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
|
|
import androidx.compose.material.icons.filled.Lock
|
|
import androidx.compose.material.icons.filled.Lock
|
|
|
-import androidx.compose.material.icons.filled.Login
|
|
|
|
|
|
|
+import androidx.compose.material.icons.filled.RadioButtonUnchecked
|
|
|
import androidx.compose.material.icons.filled.Repeat
|
|
import androidx.compose.material.icons.filled.Repeat
|
|
|
import androidx.compose.material.icons.filled.RepeatOne
|
|
import androidx.compose.material.icons.filled.RepeatOne
|
|
|
import androidx.compose.material.icons.filled.Shuffle
|
|
import androidx.compose.material.icons.filled.Shuffle
|
|
@@ -40,7 +47,6 @@ import androidx.compose.material.icons.filled.SkipPrevious
|
|
|
import androidx.compose.material.icons.filled.Speed
|
|
import androidx.compose.material.icons.filled.Speed
|
|
|
import androidx.compose.material.icons.filled.SwapHoriz
|
|
import androidx.compose.material.icons.filled.SwapHoriz
|
|
|
import androidx.compose.material.icons.filled.Timer
|
|
import androidx.compose.material.icons.filled.Timer
|
|
|
-import androidx.compose.material.icons.filled.VolumeUp
|
|
|
|
|
import androidx.compose.material.icons.filled.WbTwilight
|
|
import androidx.compose.material.icons.filled.WbTwilight
|
|
|
import androidx.compose.runtime.Composable
|
|
import androidx.compose.runtime.Composable
|
|
|
import androidx.compose.runtime.getValue
|
|
import androidx.compose.runtime.getValue
|
|
@@ -52,7 +58,6 @@ 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.draw.shadow
|
|
|
|
|
import androidx.compose.ui.graphics.Color
|
|
import androidx.compose.ui.graphics.Color
|
|
|
import androidx.compose.ui.graphics.RectangleShape
|
|
import androidx.compose.ui.graphics.RectangleShape
|
|
|
import androidx.compose.ui.graphics.vector.ImageVector
|
|
import androidx.compose.ui.graphics.vector.ImageVector
|
|
@@ -67,6 +72,7 @@ import dev.chrisbanes.haze.hazeEffect
|
|
|
import dev.chrisbanes.haze.hazeSource
|
|
import dev.chrisbanes.haze.hazeSource
|
|
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
|
|
import io.legado.app.R
|
|
import io.legado.app.R
|
|
|
|
|
+import io.legado.app.constant.CoverRatio
|
|
|
import io.legado.app.constant.ReadAloudBgMode
|
|
import io.legado.app.constant.ReadAloudBgMode
|
|
|
import io.legado.app.constant.Status
|
|
import io.legado.app.constant.Status
|
|
|
import io.legado.app.domain.model.PlaybackTimer
|
|
import io.legado.app.domain.model.PlaybackTimer
|
|
@@ -84,6 +90,8 @@ import io.legado.app.ui.widget.components.menuItem.MenuItemIcon
|
|
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
|
|
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
|
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
|
|
|
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
|
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
|
|
|
|
|
+import io.legado.app.ui.widget.components.modalBottomSheet.OptionCard
|
|
|
|
|
+import io.legado.app.ui.widget.components.modalBottomSheet.OptionSheet
|
|
|
import io.legado.app.ui.widget.components.pager.rememberPagerFlingPassThroughConnection
|
|
import io.legado.app.ui.widget.components.pager.rememberPagerFlingPassThroughConnection
|
|
|
import io.legado.app.ui.widget.components.player.AnimatedPlayPauseButton
|
|
import io.legado.app.ui.widget.components.player.AnimatedPlayPauseButton
|
|
|
import io.legado.app.ui.widget.components.player.PlayerAdjustmentSlider
|
|
import io.legado.app.ui.widget.components.player.PlayerAdjustmentSlider
|
|
@@ -110,11 +118,6 @@ fun AudioPlayScreenContent(
|
|
|
orientation = Orientation.Vertical,
|
|
orientation = Orientation.Vertical,
|
|
|
)
|
|
)
|
|
|
val coroutineScope = rememberCoroutineScope()
|
|
val coroutineScope = rememberCoroutineScope()
|
|
|
- var activeAdjustment by remember { mutableStateOf<AudioAdjustment?>(null) }
|
|
|
|
|
- var speedPreview by remember(state.speed) { mutableFloatStateOf(state.speed) }
|
|
|
|
|
- var timerPreview by remember(state.timerMinutes) {
|
|
|
|
|
- mutableFloatStateOf(state.timerMinutes.toFloat())
|
|
|
|
|
- }
|
|
|
|
|
var menuExpanded by remember { mutableStateOf(false) }
|
|
var menuExpanded by remember { mutableStateOf(false) }
|
|
|
val pagerHazeState = remember { HazeState() }
|
|
val pagerHazeState = remember { HazeState() }
|
|
|
val hazeEnabled =
|
|
val hazeEnabled =
|
|
@@ -126,13 +129,7 @@ fun AudioPlayScreenContent(
|
|
|
)
|
|
)
|
|
|
val pageContentPadding = PaddingValues(
|
|
val pageContentPadding = PaddingValues(
|
|
|
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + 88.dp,
|
|
top = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + 88.dp,
|
|
|
- bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + when (
|
|
|
|
|
- activeAdjustment
|
|
|
|
|
- ) {
|
|
|
|
|
- null -> 216.dp
|
|
|
|
|
- AudioAdjustment.Speed -> 264.dp
|
|
|
|
|
- AudioAdjustment.Timer -> 344.dp
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ bottom = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 216.dp,
|
|
|
)
|
|
)
|
|
|
AppScaffold(
|
|
AppScaffold(
|
|
|
modifier = Modifier.fillMaxSize(),
|
|
modifier = Modifier.fillMaxSize(),
|
|
@@ -195,7 +192,7 @@ fun AudioPlayScreenContent(
|
|
|
RoundDropdownMenuItem(
|
|
RoundDropdownMenuItem(
|
|
|
text = stringResource(R.string.login),
|
|
text = stringResource(R.string.login),
|
|
|
leadingIcon = {
|
|
leadingIcon = {
|
|
|
- MenuItemIcon(Icons.Default.Login)
|
|
|
|
|
|
|
+ MenuItemIcon(Icons.AutoMirrored.Filled.Login)
|
|
|
},
|
|
},
|
|
|
onClick = {
|
|
onClick = {
|
|
|
dismiss()
|
|
dismiss()
|
|
@@ -236,13 +233,23 @@ fun AudioPlayScreenContent(
|
|
|
RoundDropdownMenuItem(
|
|
RoundDropdownMenuItem(
|
|
|
text = stringResource(R.string.audio_play_gain),
|
|
text = stringResource(R.string.audio_play_gain),
|
|
|
leadingIcon = {
|
|
leadingIcon = {
|
|
|
- MenuItemIcon(Icons.Default.VolumeUp)
|
|
|
|
|
|
|
+ MenuItemIcon(Icons.AutoMirrored.Filled.VolumeUp)
|
|
|
},
|
|
},
|
|
|
onClick = {
|
|
onClick = {
|
|
|
dismiss()
|
|
dismiss()
|
|
|
onIntent(AudioPlayIntent.OpenSheet(AudioPlaySheet.Gain))
|
|
onIntent(AudioPlayIntent.OpenSheet(AudioPlaySheet.Gain))
|
|
|
},
|
|
},
|
|
|
)
|
|
)
|
|
|
|
|
+ RoundDropdownMenuItem(
|
|
|
|
|
+ text = stringResource(R.string.audio_play_cover_ratio),
|
|
|
|
|
+ leadingIcon = {
|
|
|
|
|
+ MenuItemIcon(Icons.Default.Crop)
|
|
|
|
|
+ },
|
|
|
|
|
+ onClick = {
|
|
|
|
|
+ dismiss()
|
|
|
|
|
+ onIntent(AudioPlayIntent.OpenSheet(AudioPlaySheet.CoverRatioOptions))
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
RoundDropdownMenuItem(
|
|
RoundDropdownMenuItem(
|
|
|
text = stringResource(R.string.audio_play_wake_lock),
|
|
text = stringResource(R.string.audio_play_wake_lock),
|
|
|
leadingIcon = {
|
|
leadingIcon = {
|
|
@@ -416,7 +423,7 @@ fun AudioPlayScreenContent(
|
|
|
text = stringResource(R.string.audio_play_speed),
|
|
text = stringResource(R.string.audio_play_speed),
|
|
|
contentDescription = stringResource(R.string.audio_play_speed),
|
|
contentDescription = stringResource(R.string.audio_play_speed),
|
|
|
onCheckedChange = {
|
|
onCheckedChange = {
|
|
|
- activeAdjustment = activeAdjustment.toggle(AudioAdjustment.Speed)
|
|
|
|
|
|
|
+ onIntent(AudioPlayIntent.OpenSheet(AudioPlaySheet.Speed))
|
|
|
},
|
|
},
|
|
|
)
|
|
)
|
|
|
SmallAnimatedButton(
|
|
SmallAnimatedButton(
|
|
@@ -426,48 +433,10 @@ fun AudioPlayScreenContent(
|
|
|
text = stringResource(R.string.set_timer),
|
|
text = stringResource(R.string.set_timer),
|
|
|
contentDescription = stringResource(R.string.set_timer),
|
|
contentDescription = stringResource(R.string.set_timer),
|
|
|
onCheckedChange = {
|
|
onCheckedChange = {
|
|
|
- activeAdjustment = activeAdjustment.toggle(AudioAdjustment.Timer)
|
|
|
|
|
|
|
+ onIntent(AudioPlayIntent.OpenSheet(AudioPlaySheet.Timer))
|
|
|
},
|
|
},
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
- AnimatedVisibility(activeAdjustment == AudioAdjustment.Speed) {
|
|
|
|
|
- PlayerAdjustmentSlider(
|
|
|
|
|
- title = stringResource(R.string.audio_play_speed),
|
|
|
|
|
- value = speedPreview.coerceIn(AUDIO_SPEED_MIN, AUDIO_SPEED_MAX),
|
|
|
|
|
- valueLabel = String.format(Locale.ROOT, "%.1fX", speedPreview),
|
|
|
|
|
- startLabel = "0.5X",
|
|
|
|
|
- endLabel = "5.0X",
|
|
|
|
|
- enabled = state.status != Status.STOP,
|
|
|
|
|
- onValueChange = { speedPreview = it },
|
|
|
|
|
- onValueChangeFinished = {
|
|
|
|
|
- onIntent(AudioPlayIntent.SetSpeed(speedPreview))
|
|
|
|
|
- },
|
|
|
|
|
- valueRange = AUDIO_SPEED_MIN..AUDIO_SPEED_MAX,
|
|
|
|
|
- steps = 44,
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
- AnimatedVisibility(activeAdjustment == AudioAdjustment.Timer) {
|
|
|
|
|
- PlayerAdjustmentSlider(
|
|
|
|
|
- title = stringResource(R.string.set_timer),
|
|
|
|
|
- value = timerPreview.coerceIn(
|
|
|
|
|
- PlaybackTimer.MIN_MINUTES.toFloat(),
|
|
|
|
|
- PlaybackTimer.MAX_MINUTES.toFloat(),
|
|
|
|
|
- ),
|
|
|
|
|
- valueLabel = if (timerPreview == 0f) {
|
|
|
|
|
- stringResource(R.string.close)
|
|
|
|
|
- } else {
|
|
|
|
|
- stringResource(R.string.timer_m, timerPreview.roundToInt())
|
|
|
|
|
- },
|
|
|
|
|
- startLabel = stringResource(R.string.close),
|
|
|
|
|
- endLabel = stringResource(R.string.timer_m, PlaybackTimer.MAX_MINUTES),
|
|
|
|
|
- onValueChange = { timerPreview = it.roundToInt().toFloat() },
|
|
|
|
|
- onValueChangeFinished = {
|
|
|
|
|
- onIntent(AudioPlayIntent.SetTimer(timerPreview.roundToInt()))
|
|
|
|
|
- },
|
|
|
|
|
- valueRange = PlaybackTimer.MIN_MINUTES.toFloat()..PlaybackTimer.MAX_MINUTES.toFloat(),
|
|
|
|
|
- steps = PlaybackTimer.MAX_MINUTES - PlaybackTimer.MIN_MINUTES - 1,
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
) {
|
|
) {
|
|
@@ -521,6 +490,23 @@ fun AudioPlayScreenContent(
|
|
|
onDismissRequest = { onIntent(AudioPlayIntent.DismissSheet) },
|
|
onDismissRequest = { onIntent(AudioPlayIntent.DismissSheet) },
|
|
|
onIntent = onIntent,
|
|
onIntent = onIntent,
|
|
|
)
|
|
)
|
|
|
|
|
+ AudioCoverRatioSheet(
|
|
|
|
|
+ show = state.activeSheet == AudioPlaySheet.CoverRatioOptions,
|
|
|
|
|
+ onDismissRequest = { onIntent(AudioPlayIntent.DismissSheet) },
|
|
|
|
|
+ onIntent = onIntent,
|
|
|
|
|
+ )
|
|
|
|
|
+ AudioSpeedSheet(
|
|
|
|
|
+ show = state.activeSheet == AudioPlaySheet.Speed,
|
|
|
|
|
+ state = state,
|
|
|
|
|
+ onDismissRequest = { onIntent(AudioPlayIntent.DismissSheet) },
|
|
|
|
|
+ onIntent = onIntent,
|
|
|
|
|
+ )
|
|
|
|
|
+ AudioTimerSheet(
|
|
|
|
|
+ show = state.activeSheet == AudioPlaySheet.Timer,
|
|
|
|
|
+ state = state,
|
|
|
|
|
+ onDismissRequest = { onIntent(AudioPlayIntent.DismissSheet) },
|
|
|
|
|
+ onIntent = onIntent,
|
|
|
|
|
+ )
|
|
|
AppLogSheet(
|
|
AppLogSheet(
|
|
|
show = state.activeSheet == AudioPlaySheet.Log,
|
|
show = state.activeSheet == AudioPlaySheet.Log,
|
|
|
onDismissRequest = { onIntent(AudioPlayIntent.DismissSheet) },
|
|
onDismissRequest = { onIntent(AudioPlayIntent.DismissSheet) },
|
|
@@ -609,6 +595,120 @@ private fun AudioGainSheet(
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun AudioCoverRatioSheet(
|
|
|
|
|
+ show: Boolean,
|
|
|
|
|
+ onDismissRequest: () -> Unit,
|
|
|
|
|
+ onIntent: (AudioPlayIntent) -> Unit,
|
|
|
|
|
+) {
|
|
|
|
|
+ OptionSheet(
|
|
|
|
|
+ show = show,
|
|
|
|
|
+ onDismissRequest = onDismissRequest,
|
|
|
|
|
+ title = stringResource(R.string.audio_play_cover_ratio),
|
|
|
|
|
+ ) {
|
|
|
|
|
+ OptionCard(
|
|
|
|
|
+ icon = Icons.Default.CropSquare,
|
|
|
|
|
+ text = stringResource(R.string.cover_ratio_square),
|
|
|
|
|
+ onClick = {
|
|
|
|
|
+ onDismissRequest()
|
|
|
|
|
+ onIntent(AudioPlayIntent.SetCoverRatio(CoverRatio.Square))
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ OptionCard(
|
|
|
|
|
+ icon = Icons.Default.RadioButtonUnchecked,
|
|
|
|
|
+ text = stringResource(R.string.cover_ratio_circle),
|
|
|
|
|
+ onClick = {
|
|
|
|
|
+ onDismissRequest()
|
|
|
|
|
+ onIntent(AudioPlayIntent.SetCoverRatio(CoverRatio.Circle))
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ OptionCard(
|
|
|
|
|
+ icon = Icons.Default.Book,
|
|
|
|
|
+ text = stringResource(R.string.cover_ratio_book),
|
|
|
|
|
+ onClick = {
|
|
|
|
|
+ onDismissRequest()
|
|
|
|
|
+ onIntent(AudioPlayIntent.SetCoverRatio(CoverRatio.Book))
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ OptionCard(
|
|
|
|
|
+ icon = Icons.Default.CropFree,
|
|
|
|
|
+ text = stringResource(R.string.cover_ratio_unrestricted),
|
|
|
|
|
+ onClick = {
|
|
|
|
|
+ onDismissRequest()
|
|
|
|
|
+ onIntent(AudioPlayIntent.SetCoverRatio(CoverRatio.Unrestricted))
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun AudioSpeedSheet(
|
|
|
|
|
+ show: Boolean,
|
|
|
|
|
+ state: AudioPlayUiState,
|
|
|
|
|
+ onDismissRequest: () -> Unit,
|
|
|
|
|
+ onIntent: (AudioPlayIntent) -> Unit,
|
|
|
|
|
+) {
|
|
|
|
|
+ var speedPreview by remember(state.speed) { mutableFloatStateOf(state.speed) }
|
|
|
|
|
+ AppModalBottomSheet(
|
|
|
|
|
+ show = show,
|
|
|
|
|
+ onDismissRequest = onDismissRequest,
|
|
|
|
|
+ title = stringResource(R.string.audio_play_speed),
|
|
|
|
|
+ ) {
|
|
|
|
|
+ PlayerAdjustmentSlider(
|
|
|
|
|
+ title = stringResource(R.string.audio_play_speed),
|
|
|
|
|
+ value = speedPreview.coerceIn(AUDIO_SPEED_MIN, AUDIO_SPEED_MAX),
|
|
|
|
|
+ valueLabel = String.format(Locale.ROOT, "%.1fX", speedPreview),
|
|
|
|
|
+ startLabel = "0.5X",
|
|
|
|
|
+ endLabel = "5.0X",
|
|
|
|
|
+ enabled = state.status != Status.STOP,
|
|
|
|
|
+ onValueChange = { speedPreview = it },
|
|
|
|
|
+ onValueChangeFinished = {
|
|
|
|
|
+ onIntent(AudioPlayIntent.SetSpeed(speedPreview))
|
|
|
|
|
+ },
|
|
|
|
|
+ valueRange = AUDIO_SPEED_MIN..AUDIO_SPEED_MAX,
|
|
|
|
|
+ steps = 44,
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@Composable
|
|
|
|
|
+private fun AudioTimerSheet(
|
|
|
|
|
+ show: Boolean,
|
|
|
|
|
+ state: AudioPlayUiState,
|
|
|
|
|
+ onDismissRequest: () -> Unit,
|
|
|
|
|
+ onIntent: (AudioPlayIntent) -> Unit,
|
|
|
|
|
+) {
|
|
|
|
|
+ var timerPreview by remember(state.timerMinutes) {
|
|
|
|
|
+ mutableFloatStateOf(state.timerMinutes.toFloat())
|
|
|
|
|
+ }
|
|
|
|
|
+ AppModalBottomSheet(
|
|
|
|
|
+ show = show,
|
|
|
|
|
+ onDismissRequest = onDismissRequest,
|
|
|
|
|
+ title = stringResource(R.string.set_timer),
|
|
|
|
|
+ ) {
|
|
|
|
|
+ PlayerAdjustmentSlider(
|
|
|
|
|
+ title = stringResource(R.string.set_timer),
|
|
|
|
|
+ value = timerPreview.coerceIn(
|
|
|
|
|
+ PlaybackTimer.MIN_MINUTES.toFloat(),
|
|
|
|
|
+ PlaybackTimer.MAX_MINUTES.toFloat(),
|
|
|
|
|
+ ),
|
|
|
|
|
+ valueLabel = if (timerPreview == 0f) {
|
|
|
|
|
+ stringResource(R.string.close)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ stringResource(R.string.timer_m, timerPreview.roundToInt())
|
|
|
|
|
+ },
|
|
|
|
|
+ startLabel = stringResource(R.string.close),
|
|
|
|
|
+ endLabel = stringResource(R.string.timer_m, PlaybackTimer.MAX_MINUTES),
|
|
|
|
|
+ onValueChange = { timerPreview = it.roundToInt().toFloat() },
|
|
|
|
|
+ onValueChangeFinished = {
|
|
|
|
|
+ onIntent(AudioPlayIntent.SetTimer(timerPreview.roundToInt()))
|
|
|
|
|
+ },
|
|
|
|
|
+ valueRange = PlaybackTimer.MIN_MINUTES.toFloat()..PlaybackTimer.MAX_MINUTES.toFloat(),
|
|
|
|
|
+ steps = PlaybackTimer.MAX_MINUTES - PlaybackTimer.MIN_MINUTES - 1,
|
|
|
|
|
+ )
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
private fun formatGain(gainMb: Int): String {
|
|
private fun formatGain(gainMb: Int): String {
|
|
|
val dB = gainMb / 1000f
|
|
val dB = gainMb / 1000f
|
|
|
return if (dB == 0f) {
|
|
return if (dB == 0f) {
|
|
@@ -633,21 +733,35 @@ private fun AudioCoverPage(
|
|
|
.padding(horizontal = 24.dp),
|
|
.padding(horizontal = 24.dp),
|
|
|
horizontalAlignment = Alignment.CenterHorizontally,
|
|
horizontalAlignment = Alignment.CenterHorizontally,
|
|
|
) {
|
|
) {
|
|
|
|
|
+ val coverShape = when (state.coverRatio) {
|
|
|
|
|
+ CoverRatio.Circle -> CircleShape
|
|
|
|
|
+ else -> RoundedCornerShape(8.dp)
|
|
|
|
|
+ }
|
|
|
Box(
|
|
Box(
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
contentAlignment = Alignment.TopCenter,
|
|
contentAlignment = Alignment.TopCenter,
|
|
|
) {
|
|
) {
|
|
|
|
|
+ val coverModifier = when (state.coverRatio) {
|
|
|
|
|
+ // 边长取 0.8×宽 与 0.55×高 的较小值:既放大封面,又保证下方文字始终有空间
|
|
|
|
|
+ CoverRatio.Square, CoverRatio.Circle ->
|
|
|
|
|
+ Modifier.aspectRatio(1f)
|
|
|
|
|
+
|
|
|
|
|
+ CoverRatio.Book ->
|
|
|
|
|
+ Modifier
|
|
|
|
|
+ .fillMaxWidth(0.64f)
|
|
|
|
|
+ .aspectRatio(5f / 7f)
|
|
|
|
|
+
|
|
|
|
|
+ else ->
|
|
|
|
|
+ Modifier.fillMaxSize(0.64f)
|
|
|
|
|
+ }
|
|
|
BookCoverImage(
|
|
BookCoverImage(
|
|
|
name = state.bookName,
|
|
name = state.bookName,
|
|
|
author = state.author,
|
|
author = state.author,
|
|
|
path = state.coverPath,
|
|
path = state.coverPath,
|
|
|
sourceOrigin = state.sourceOrigin,
|
|
sourceOrigin = state.sourceOrigin,
|
|
|
modifier = Modifier
|
|
modifier = Modifier
|
|
|
- .fillMaxSize(0.64f)
|
|
|
|
|
- .shadow(
|
|
|
|
|
- elevation = 16.dp
|
|
|
|
|
- )
|
|
|
|
|
- .clip(RoundedCornerShape(8.dp))
|
|
|
|
|
|
|
+ .then(coverModifier)
|
|
|
|
|
+ .clip(coverShape)
|
|
|
)
|
|
)
|
|
|
}
|
|
}
|
|
|
Column(
|
|
Column(
|
|
@@ -689,11 +803,6 @@ private fun formatAudioTime(valueMs: Int): String {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-private enum class AudioAdjustment { Speed, Timer }
|
|
|
|
|
-
|
|
|
|
|
-private fun AudioAdjustment?.toggle(value: AudioAdjustment): AudioAdjustment? =
|
|
|
|
|
- if (this == value) null else value
|
|
|
|
|
-
|
|
|
|
|
@Composable
|
|
@Composable
|
|
|
private fun playModeContentDescription(mode: AudioPlay.PlayMode): String = when (mode) {
|
|
private fun playModeContentDescription(mode: AudioPlay.PlayMode): String = when (mode) {
|
|
|
AudioPlay.PlayMode.LIST_END_STOP -> stringResource(R.string.audio_play_mode_sequence)
|
|
AudioPlay.PlayMode.LIST_END_STOP -> stringResource(R.string.audio_play_mode_sequence)
|