diff --git a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PlayerViewModel.kt b/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PlayerViewModel.kt index c2b00e32..6ef18fed 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PlayerViewModel.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/backend/viewmodel/PlayerViewModel.kt @@ -13,6 +13,7 @@ import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory import androidx.media3.common.MediaItem +import androidx.media3.common.PlaybackParameters import androidx.media3.session.MediaController import app.suhasdissa.vibeyou.MellowMusicApplication import app.suhasdissa.vibeyou.backend.data.Song @@ -102,6 +103,12 @@ class PlayerViewModel( controller!!.playGracefully(song.asMediaItem) } + fun setPlaybackParams(speed: Float, pitch: Float) { + controller!!.playbackParameters = PlaybackParameters(speed, pitch) + } + + fun getPlaybackParams() = controller!!.playbackParameters + fun toggleFavourite(id: String) { viewModelScope.launch { val song = songDatabaseRepository.getSongById(id) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/FullScreenPlayer.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/FullScreenPlayer.kt index 3104d01c..8cfe8b31 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/FullScreenPlayer.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/FullScreenPlayer.kt @@ -27,6 +27,7 @@ import androidx.compose.material.icons.filled.RepeatOneOn import androidx.compose.material.icons.filled.SkipNext import androidx.compose.material.icons.filled.SkipPrevious import androidx.compose.material.icons.rounded.ExpandMore +import androidx.compose.material.icons.rounded.MoreVert import androidx.compose.material3.CardDefaults import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CircularProgressIndicator @@ -77,6 +78,8 @@ fun FullScreenPlayer( playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) ) { var showQueueSheet by remember { mutableStateOf(false) } + var showSongOptions by remember { mutableStateOf(false) } + val view = LocalView.current CenterAlignedTopAppBar(navigationIcon = { IconButton({ @@ -89,9 +92,9 @@ fun FullScreenPlayer( ) } }, title = { Text(stringResource(R.string.now_playing)) }, actions = { -// IconButton(onClick = { }) { -// Icon(Icons.Rounded.MoreVert, contentDescription = stringResource(R.string.song_options)) -// } + IconButton(onClick = { showSongOptions = true }) { + Icon(Icons.Rounded.MoreVert, contentDescription = stringResource(R.string.song_options)) + } }) Divider(Modifier.fillMaxWidth()) Column( @@ -167,6 +170,7 @@ fun FullScreenPlayer( } } if (showQueueSheet) QueueSheet(onDismissRequest = { showQueueSheet = false }) + if (showSongOptions) SongOptionsSheet(onDismissRequest = { showSongOptions = false }) } @Composable diff --git a/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/SongOptionsSheet.kt b/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/SongOptionsSheet.kt new file mode 100644 index 00000000..cf3c7dde --- /dev/null +++ b/app/src/main/java/app/suhasdissa/vibeyou/ui/screens/player/SongOptionsSheet.kt @@ -0,0 +1,109 @@ +package app.suhasdissa.vibeyou.ui.screens.player + +import android.view.SoundEffectConstants +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.ExpandMore +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.Divider +import androidx.compose.material3.ElevatedCard +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import app.suhasdissa.vibeyou.R +import app.suhasdissa.vibeyou.backend.viewmodel.PlayerViewModel +import kotlinx.coroutines.launch + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SongOptionsSheet( + onDismissRequest: () -> Unit, + playerViewModel: PlayerViewModel = viewModel(factory = PlayerViewModel.Factory) +) { + val playerSheetState = rememberModalBottomSheetState( + skipPartiallyExpanded = true + ) + val scope = rememberCoroutineScope() + val view = LocalView.current + ModalBottomSheet( + onDismissRequest = onDismissRequest, + sheetState = playerSheetState, + shape = RoundedCornerShape(8.dp), + tonalElevation = 0.dp, + dragHandle = null + ) { + CenterAlignedTopAppBar(navigationIcon = { + IconButton({ + view.playSoundEffect(SoundEffectConstants.CLICK) + scope.launch { + playerSheetState.hide() + }.invokeOnCompletion { + onDismissRequest() + } + }) { + Icon( + Icons.Rounded.ExpandMore, + contentDescription = null + ) + } + }, title = { Text(stringResource(R.string.song_options)) }) + Divider(Modifier.fillMaxWidth()) + + val scrollState = rememberScrollState() + Column( + modifier = Modifier + .verticalScroll(scrollState) + .padding(horizontal = 12.dp, vertical = 10.dp) + ) { + var speed by remember { + mutableFloatStateOf(playerViewModel.getPlaybackParams().speed) + } + var pitch by remember { + mutableFloatStateOf(playerViewModel.getPlaybackParams().pitch) + } + fun updatePlaybackParams() = playerViewModel.setPlaybackParams(speed, pitch) + + Text(text = stringResource(R.string.playback_speed), fontSize = 16.sp) + Slider(value = speed, onValueChange = { speed = it; updatePlaybackParams() }, valueRange = 0.25f..4f, steps = 14) + ElevatedCard(modifier = Modifier.align(Alignment.CenterHorizontally)) { + Text( + modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp), + text = "%.2f".format(speed) + ) + } + Text(text = stringResource(R.string.pitch), fontSize = 16.sp) + Slider(value = pitch, onValueChange = { pitch = it; updatePlaybackParams() }, valueRange = 0.5f..2f, steps = 5) + ElevatedCard(modifier = Modifier.align(Alignment.CenterHorizontally)) { + Text( + modifier = Modifier.padding(horizontal = 5.dp, vertical = 2.dp), + text = "%.2f".format(pitch) + ) + } + } + Spacer(modifier = Modifier.height(20.dp)) + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7dbece54..00fede2e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -62,4 +62,6 @@ Change music cache size Music cache limit Unlimited + Playback speed + Pitch \ No newline at end of file