diff --git a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/di/MediaPlayerModule.kt b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/di/MediaPlayerModule.kt index 85d1376..9b80c37 100644 --- a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/di/MediaPlayerModule.kt +++ b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/di/MediaPlayerModule.kt @@ -11,7 +11,6 @@ import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.trackselection.DefaultTrackSelector import androidx.media3.session.MediaLibraryService.MediaLibrarySession -import androidx.media3.session.MediaSession import com.bobbyesp.mediaplayer.service.ConnectionHandler import com.bobbyesp.mediaplayer.service.MediaLibrarySessionCallback import com.bobbyesp.mediaplayer.service.MediaServiceHandler @@ -61,11 +60,11 @@ object MediaPlayerModule { @Provides @Singleton - fun provideMediaSession( + fun provideMediaLibrarySession( @ApplicationContext context: Context, player: ExoPlayer, mediaLibrarySessionCallback: MediaLibrarySessionCallback, - ): MediaSession = + ): MediaLibrarySession = MediaLibrarySession.Builder(context, player, mediaLibrarySessionCallback) .setSessionActivity( PendingIntent.getActivity( diff --git a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/MediaServiceHandler.kt b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/MediaServiceHandler.kt index 80554ac..9a741ac 100644 --- a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/MediaServiceHandler.kt +++ b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/MediaServiceHandler.kt @@ -2,10 +2,12 @@ package com.bobbyesp.mediaplayer.service import android.os.Bundle import androidx.media3.common.MediaItem -import androidx.media3.common.MediaMetadata import androidx.media3.common.Player import androidx.media3.common.Player.EVENT_POSITION_DISCONTINUITY import androidx.media3.common.Player.EVENT_TIMELINE_CHANGED +import androidx.media3.common.Player.REPEAT_MODE_ALL +import androidx.media3.common.Player.REPEAT_MODE_OFF +import androidx.media3.common.Player.REPEAT_MODE_ONE import androidx.media3.common.Player.STATE_IDLE import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer @@ -15,7 +17,7 @@ import androidx.media3.exoplayer.analytics.PlaybackStatsListener import androidx.media3.exoplayer.source.ShuffleOrder import androidx.media3.session.SessionCommand import com.bobbyesp.mediaplayer.ext.toMediaItem -import com.bobbyesp.mediaplayer.service.notifications.MediaSessionLayoutHandler +import com.bobbyesp.mediaplayer.service.notifications.customLayout.MediaSessionLayoutHandler import com.bobbyesp.mediaplayer.service.queue.EmptyQueue import com.bobbyesp.mediaplayer.service.queue.Queue import kotlinx.coroutines.CoroutineScope @@ -47,7 +49,9 @@ class MediaServiceHandler @Inject constructor( val currentMediaItem = MutableStateFlow(null) - val isThePlayerPlaying: MutableStateFlow = MutableStateFlow(false) + val isPlaying: MutableStateFlow = MutableStateFlow(false) + val shuffleModeEnabled = MutableStateFlow(false) + val repeatMode = MutableStateFlow(REPEAT_MODE_OFF) private var job: Job? = null private val scope = CoroutineScope(Dispatchers.Main) @@ -59,15 +63,16 @@ class MediaServiceHandler @Inject constructor( init { player.addListener(this) + repeatMode.update { player.repeatMode } + shuffleModeEnabled.update { player.shuffleModeEnabled } job = Job() } override fun onIsPlayingChanged(isPlaying: Boolean) { - mediaSessionInterface.updateNotificationLayout() _mediaState.update { MediaState.Playing(isPlaying) } - isThePlayerPlaying.update { + this.isPlaying.update { isPlaying } super.onIsPlayingChanged(isPlaying) @@ -91,15 +96,6 @@ class MediaServiceHandler @Inject constructor( player.prepare() } - /** - * Sets a list of media items as the current queue and prepares the player for playback. - * @param mediaItems The list of media items to be set. - */ - fun setMediaItems(mediaItems: List) { - player.setMediaItems(mediaItems) - player.prepare() - } - fun playQueue(queue: Queue, playWhenReady: Boolean = true) { currentQueue = queue queueTitle = null @@ -152,46 +148,6 @@ class MediaServiceHandler @Inject constructor( } } - /** - * Adds a media item to the end of the queue and prepares the player for playback. - * @param mediaItem The media item to be added. - */ - fun addMediaItem(mediaItem: MediaItem) { - player.addMediaItem(mediaItem) - player.prepare() - } - - /** - * Removes a media item from the queue. - * @param index The media item to be removed. - */ - fun removeMediaItemAtIndex(index: Int) { - player.removeMediaItem(index) - } - - /** - * Moves a media item within the queue. - * @param currentIndex The current index of the media item. - * @param newIndex The new index for the media item. - */ - fun moveMediaItem(currentIndex: Int, newIndex: Int) { - player.moveMediaItem(currentIndex, newIndex) - } - - /** - * Skips to the next media item in the queue. - */ - fun skipToNext() { - player.seekToNext() - } - - /** - * Skips to the previous media item in the queue. - */ - fun skipToPrevious() { - player.seekToPrevious() - } - /** * Seeks to a specific position in the current media item. * @param positionMs The position to seek to, in milliseconds. @@ -229,31 +185,21 @@ class MediaServiceHandler @Inject constructor( is PlayerEvent.UpdateProgress -> { player.seekTo(playerEvent.updatedProgress) } - } - } - fun getActualMediaItem(): MediaItem? { - return player.currentMediaItem - } - - fun getActualMediaItemIndex(): Int { - return player.currentMediaItemIndex - } - - fun getActualMediaItemMetadata(): MediaMetadata? { - return player.currentMediaItem?.mediaMetadata - } - - fun getActualMediaItemDuration(): Long { - return player.duration - } - - fun getActualMediaItemPosition(): Long { - return player.currentPosition - } + PlayerEvent.ToggleRepeat -> { + val repeatMode = when (player.repeatMode) { + REPEAT_MODE_OFF -> REPEAT_MODE_ONE + REPEAT_MODE_ONE -> REPEAT_MODE_ALL + REPEAT_MODE_ALL -> REPEAT_MODE_OFF + else -> REPEAT_MODE_OFF + } + player.repeatMode = repeatMode + } - fun getActualMediaItemPositionPercentage(): Float { - return player.currentPosition / player.duration.toFloat() + PlayerEvent.ToggleShuffle -> { + player.shuffleModeEnabled = !player.shuffleModeEnabled + } + } } override fun onEvents(player: Player, events: Player.Events) { @@ -300,6 +246,7 @@ class MediaServiceHandler @Inject constructor( override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) { // Update the notification layout mediaSessionInterface.updateNotificationLayout() + this.shuffleModeEnabled.update { shuffleModeEnabled } // If shuffle mode is enabled if (shuffleModeEnabled) { @@ -327,6 +274,7 @@ class MediaServiceHandler @Inject constructor( override fun onRepeatModeChanged(repeatMode: Int) { mediaSessionInterface.updateNotificationLayout() + this.repeatMode.update { repeatMode } } private suspend fun startProgressUpdate() = job.run { @@ -369,6 +317,8 @@ sealed class PlayerEvent { data object Stop : PlayerEvent() data object Next : PlayerEvent() data object Previous : PlayerEvent() + data object ToggleShuffle : PlayerEvent() + data object ToggleRepeat : PlayerEvent() data class UpdateProgress(val updatedProgress: Long) : PlayerEvent() } diff --git a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/MediaplayerService.kt b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/MediaplayerService.kt index 0a21f2e..62e51a6 100644 --- a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/MediaplayerService.kt +++ b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/MediaplayerService.kt @@ -9,21 +9,22 @@ import androidx.media3.common.Player.REPEAT_MODE_OFF import androidx.media3.common.Player.REPEAT_MODE_ONE import androidx.media3.common.util.UnstableApi import androidx.media3.session.CommandButton +import androidx.media3.session.MediaLibraryService import androidx.media3.session.MediaSession -import androidx.media3.session.MediaSessionService import com.bobbyesp.mediaplayer.R import com.bobbyesp.mediaplayer.service.MediaServiceHandler.Companion.CommandToggleRepeatMode import com.bobbyesp.mediaplayer.service.MediaServiceHandler.Companion.CommandToggleShuffle import com.bobbyesp.mediaplayer.service.notifications.MediaNotificationManager -import com.bobbyesp.mediaplayer.service.notifications.MediaSessionLayoutHandler +import com.bobbyesp.mediaplayer.service.notifications.customLayout.MediaSessionLayoutHandler +import com.google.common.collect.ImmutableList import dagger.hilt.android.AndroidEntryPoint import javax.inject.Inject @UnstableApi @AndroidEntryPoint -class MediaplayerService : MediaSessionService(), MediaSessionLayoutHandler { +class MediaplayerService : MediaLibraryService(), MediaSessionLayoutHandler { @Inject - lateinit var mediaSession: MediaSession + lateinit var mediaSession: MediaLibrarySession @Inject lateinit var notificationManager: MediaNotificationManager @@ -39,44 +40,38 @@ class MediaplayerService : MediaSessionService(), MediaSessionLayoutHandler { Log.i("MediaplayerService", "Shuffle mode: ${mediaSession.player.shuffleModeEnabled}") Log.i("MediaplayerService", "Repeat mode: ${mediaSession.player.repeatMode}") - mediaSession.setCustomLayout( - listOf( - CommandButton.Builder() - .setDisplayName(getString(if (mediaSession.player.shuffleModeEnabled) R.string.action_shuffle_off else R.string.action_shuffle_on)) - .setIconResId(if (mediaSession.player.shuffleModeEnabled) R.drawable.shuffle_on else R.drawable.shuffle) - .setSessionCommand(CommandToggleShuffle) - .build(), - CommandButton.Builder() - .setDisplayName( - getString( - when (mediaSession.player.repeatMode) { - REPEAT_MODE_OFF -> R.string.repeat_mode_off - REPEAT_MODE_ONE -> R.string.repeat_mode_one - REPEAT_MODE_ALL -> R.string.repeat_mode_all - else -> throw IllegalStateException() - } - ) - ) - .setIconResId( + val commandButtons = ImmutableList.of( + CommandButton.Builder() + .setDisplayName(getString(if (mediaSession.player.shuffleModeEnabled) R.string.action_shuffle_off else R.string.action_shuffle_on)) + .setIconResId(if (mediaSession.player.shuffleModeEnabled) R.drawable.shuffle_on else R.drawable.shuffle) + .setSessionCommand(CommandToggleShuffle).build(), + CommandButton.Builder() + .setDisplayName( + getString( when (mediaSession.player.repeatMode) { - REPEAT_MODE_OFF -> R.drawable.repeat - REPEAT_MODE_ONE -> R.drawable.repeat_one_on - REPEAT_MODE_ALL -> R.drawable.repeat_on + REPEAT_MODE_OFF -> R.string.repeat_mode_off + REPEAT_MODE_ONE -> R.string.repeat_mode_one + REPEAT_MODE_ALL -> R.string.repeat_mode_all else -> throw IllegalStateException() } ) - .setSessionCommand(CommandToggleRepeatMode) - .build() - ) + ).setIconResId( + when (mediaSession.player.repeatMode) { + REPEAT_MODE_OFF -> R.drawable.repeat + REPEAT_MODE_ONE -> R.drawable.repeat_one_on + REPEAT_MODE_ALL -> R.drawable.repeat_on + else -> throw IllegalStateException() + } + ).setSessionCommand(CommandToggleRepeatMode).build() ) + mediaSession.setCustomLayout(commandButtons) } @UnstableApi override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { connectionHandler.connect(mediaServiceHandler) notificationManager.startNotificationService( - mediaSessionService = this, - mediaSession = mediaSession + mediaSessionService = this, mediaSession = mediaSession ) mediaServiceHandler.setMediaSessionInterface(this) return super.onStartCommand(intent, flags, startId) @@ -101,7 +96,7 @@ class MediaplayerService : MediaSessionService(), MediaSessionLayoutHandler { stopSelf() } - override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession = + override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession = mediaSession inner class MusicBinder : Binder() { diff --git a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaSessionLayoutHandler.kt b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/customLayout/MediaSessionLayoutHandler.kt similarity index 52% rename from app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaSessionLayoutHandler.kt rename to app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/customLayout/MediaSessionLayoutHandler.kt index c2edefe..ca4c381 100644 --- a/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/MediaSessionLayoutHandler.kt +++ b/app/mediaplayer/src/main/java/com/bobbyesp/mediaplayer/service/notifications/customLayout/MediaSessionLayoutHandler.kt @@ -1,4 +1,4 @@ -package com.bobbyesp.mediaplayer.service.notifications +package com.bobbyesp.mediaplayer.service.notifications.customLayout interface MediaSessionLayoutHandler { fun updateNotificationLayout() diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/components/others/RepeatStateIcon.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/components/others/RepeatStateIcon.kt new file mode 100644 index 0000000..332780a --- /dev/null +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/components/others/RepeatStateIcon.kt @@ -0,0 +1,45 @@ +package com.bobbyesp.metadator.presentation.components.others + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Repeat +import androidx.compose.material.icons.rounded.RepeatOne +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.res.stringResource +import androidx.media3.common.Player.REPEAT_MODE_ALL +import androidx.media3.common.Player.REPEAT_MODE_OFF +import androidx.media3.common.Player.REPEAT_MODE_ONE + +@Composable +fun RepeatStateIcon( + modifier: Modifier = Modifier, + repeatMode: Int +) { + when (repeatMode) { + REPEAT_MODE_OFF -> { + Icon( + imageVector = Icons.Rounded.Repeat, + contentDescription = stringResource(id = com.bobbyesp.mediaplayer.R.string.repeat_mode_off), + modifier = modifier.alpha(0.5f) + ) + } + + REPEAT_MODE_ONE -> { + Icon( + imageVector = Icons.Rounded.RepeatOne, + contentDescription = stringResource(id = com.bobbyesp.mediaplayer.R.string.repeat_mode_one), + modifier = modifier + ) + } + + REPEAT_MODE_ALL -> { + Icon( + imageVector = Icons.Rounded.Repeat, + contentDescription = stringResource(id = com.bobbyesp.mediaplayer.R.string.repeat_mode_all), + modifier = modifier + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/components/others/ShuffleStateIcon.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/components/others/ShuffleStateIcon.kt new file mode 100644 index 0000000..c3d6af0 --- /dev/null +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/components/others/ShuffleStateIcon.kt @@ -0,0 +1,33 @@ +package com.bobbyesp.metadator.presentation.components.others + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Shuffle +import androidx.compose.material.icons.rounded.ShuffleOn +import androidx.compose.material3.Icon +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource + +@Composable +fun ShuffleStateIcon( + modifier: Modifier = Modifier, + isShuffleEnabled: Boolean +) { + when (isShuffleEnabled) { + true -> { + Icon( + imageVector = Icons.Rounded.ShuffleOn, + contentDescription = stringResource(id = com.bobbyesp.mediaplayer.R.string.action_shuffle_on), + modifier = modifier + ) + } + + false -> { + Icon( + imageVector = Icons.Rounded.Shuffle, + contentDescription = stringResource(id = com.bobbyesp.mediaplayer.R.string.action_shuffle_off), + modifier = modifier + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/MediaplayerViewModel.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/MediaplayerViewModel.kt index feca4b3..1e3f46a 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/MediaplayerViewModel.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/MediaplayerViewModel.kt @@ -7,8 +7,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata +import androidx.media3.common.Player.REPEAT_MODE_OFF import androidx.media3.common.util.UnstableApi -import androidx.media3.session.MediaSession +import androidx.media3.session.MediaLibraryService.MediaLibrarySession import com.bobbyesp.mediaplayer.service.ConnectionHandler import com.bobbyesp.mediaplayer.service.MediaServiceHandler import com.bobbyesp.mediaplayer.service.MediaState @@ -34,7 +35,7 @@ import javax.inject.Inject class MediaplayerViewModel @Inject constructor( @ApplicationContext private val applicationContext: Context, private val serviceHandler: MediaServiceHandler, - private val mediaSession: MediaSession, + private val mediaSession: MediaLibrarySession, val connectionHandler: ConnectionHandler ) : ViewModel() { private val mutableMediaplayerPageState = MutableStateFlow(MediaplayerPageState()) @@ -42,8 +43,11 @@ class MediaplayerViewModel @Inject constructor( val songsFlow = applicationContext.contentResolver.observeSongs() - val isPlaying = serviceHandler.isThePlayerPlaying - val playingSong = serviceHandler.currentMediaItem.asStateFlow() + val songBeingPlayed = serviceHandler.currentMediaItem.asStateFlow() + + val isPlaying = serviceHandler.isPlaying + val isShuffleEnabled = serviceHandler.shuffleModeEnabled + val repeatMode = serviceHandler.repeatMode data class MediaplayerPageState( val uiState: PlayerState = PlayerState.Initial, @@ -67,7 +71,9 @@ class MediaplayerViewModel @Inject constructor( is MediaState.Ready -> { mutableMediaplayerPageState.update { it.copy( - uiState = PlayerState.Ready(duration = mediaState.duration) + uiState = PlayerState.Ready( + duration = mediaState.duration, + ) ) } } @@ -82,6 +88,18 @@ class MediaplayerViewModel @Inject constructor( } } + fun toggleShuffle() { + viewModelScope.launch { + serviceHandler.onPlayerEvent(PlayerEvent.ToggleShuffle) + } + } + + fun toggleRepeat() { + viewModelScope.launch { + serviceHandler.onPlayerEvent(PlayerEvent.ToggleRepeat) + } + } + fun playOrderedQueue(firstSong: Song) { playQueue(firstSong) viewModelScope.launch { @@ -144,6 +162,7 @@ class MediaplayerViewModel @Inject constructor( private fun loadQueueSongs(songs: List) { val mediaItems = songs.map { song -> MediaItem.Builder() + .setCustomCacheKey(song.id.toString()) .setUri(Uri.fromFile(File(song.path))) .setMediaMetadata( MediaMetadata.Builder() @@ -210,7 +229,9 @@ class MediaplayerViewModel @Inject constructor( val progress: Float = 0f, val progressString: String = "00:00", val duration: Long = 0L, - val isPlaying: Boolean = false + val isPlaying: Boolean = false, + val isShuffleEnabled: Boolean = false, + val repeatMode: Int = REPEAT_MODE_OFF ) : PlayerState } } \ No newline at end of file diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/MediaplayerConstants.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/MediaplayerConstants.kt index 50104f8..2b2b281 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/MediaplayerConstants.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/MediaplayerConstants.kt @@ -11,7 +11,8 @@ import com.bobbyesp.ui.motion.materialSharedAxisXIn import com.bobbyesp.ui.motion.materialSharedAxisXOut val CollapsedPlayerHeight = 84.dp -val SeekToButtonSize = 42.dp +val SeekToButtonSize = 48.dp +val PlayerCommandsButtonSize = 48.dp val PlayerAnimationSpec: AnimationSpec = tween( durationMillis = 750, diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/MediaplayerSheet.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/MediaplayerSheet.kt index 3c30ccd..a148f51 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/MediaplayerSheet.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/MediaplayerSheet.kt @@ -18,7 +18,7 @@ fun MediaplayerSheet( modifier: Modifier = Modifier, state: DraggableBottomSheetState, viewModel: MediaplayerViewModel ) { val playingSong = - viewModel.playingSong.collectAsStateWithLifecycle().value?.mediaMetadata ?: return + viewModel.songBeingPlayed.collectAsStateWithLifecycle().value?.mediaMetadata ?: return val connectionState = viewModel.connectionHandler.connectionState.collectAsStateWithLifecycle().value diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/PlayerControls.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/PlayerControls.kt index 1b1e281..aa2b772 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/PlayerControls.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/PlayerControls.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -41,6 +42,8 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.bobbyesp.metadator.R import com.bobbyesp.metadator.presentation.components.buttons.PlayPauseAnimatedButton +import com.bobbyesp.metadator.presentation.components.others.RepeatStateIcon +import com.bobbyesp.metadator.presentation.components.others.ShuffleStateIcon import com.bobbyesp.metadator.presentation.pages.mediaplayer.MediaplayerViewModel import com.bobbyesp.ui.components.text.MarqueeText import com.bobbyesp.ui.components.text.MarqueeTextGradientOptions @@ -55,14 +58,16 @@ fun PlayerControls( viewModel: MediaplayerViewModel, ) { val scope = rememberCoroutineScope() + val viewState = viewModel.pageViewState.collectAsStateWithLifecycle().value val playerState = viewState.uiState - val playingSong = viewModel.playingSong.collectAsStateWithLifecycle().value?.mediaMetadata - val readyState = playerState as? MediaplayerViewModel.PlayerState.Ready + val progress = readyState?.progress ?: 0f + val playingSong = viewModel.songBeingPlayed.collectAsStateWithLifecycle().value?.mediaMetadata + var sliderPosition by remember { mutableStateOf(null) } @@ -78,6 +83,8 @@ fun PlayerControls( } val isPlaying = viewModel.isPlaying.collectAsStateWithLifecycle().value + val isShuffleEnabled = viewModel.isShuffleEnabled.collectAsStateWithLifecycle().value + val repeatMode = viewModel.repeatMode.collectAsStateWithLifecycle().value val transitionState = remember { MutableTransitionState(playingSong) } @@ -180,13 +187,22 @@ fun PlayerControls( Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(6.dp, Alignment.CenterHorizontally), + horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.CenterHorizontally), verticalAlignment = Alignment.CenterVertically ) { + IconButton( + modifier = Modifier.size(PlayerCommandsButtonSize), + onClick = viewModel::toggleShuffle + ) { + ShuffleStateIcon( + modifier = Modifier, + isShuffleEnabled = isShuffleEnabled + ) + } IconButton(modifier = Modifier.size(SeekToButtonSize), onClick = { viewModel.seekToPrevious() }) { Icon( - modifier = Modifier.size(SeekToButtonSize), + modifier = Modifier.fillMaxSize(), imageVector = Icons.Rounded.SkipPrevious, contentDescription = stringResource(id = R.string.seek_to_previous) ) @@ -198,11 +214,21 @@ fun PlayerControls( modifier = Modifier.size(SeekToButtonSize), onClick = { viewModel.seekToNext() }) { Icon( - modifier = Modifier.size(SeekToButtonSize), + modifier = Modifier.fillMaxSize(), imageVector = Icons.Rounded.SkipNext, contentDescription = stringResource(id = R.string.seek_to_previous) ) } + + IconButton( + modifier = Modifier.size(PlayerCommandsButtonSize), + onClick = viewModel::toggleRepeat + ) { + RepeatStateIcon( + modifier = Modifier, + repeatMode = repeatMode + ) + } } } } diff --git a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/views/MediaplayerExpandedContent.kt b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/views/MediaplayerExpandedContent.kt index b6fcd39..6efc1e1 100644 --- a/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/views/MediaplayerExpandedContent.kt +++ b/app/src/main/java/com/bobbyesp/metadator/presentation/pages/mediaplayer/mediaplayer/views/MediaplayerExpandedContent.kt @@ -68,7 +68,7 @@ fun MediaplayerExpandedContent( mutableStateOf(MediaplayerSheetView.FULL_PLAYER) } val scope = rememberCoroutineScope() - val playingSong = viewModel.playingSong.collectAsStateWithLifecycle().value?.mediaMetadata + val playingSong = viewModel.songBeingPlayed.collectAsStateWithLifecycle().value?.mediaMetadata val config = LocalConfiguration.current diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 086429e..0173048 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -51,4 +51,7 @@ Volver a la anterior Cerrar Más + Cola de música + Alternar modo aleatorio + Alternar modo de repetición \ 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 15ae105..54adea6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -51,5 +51,7 @@ Close More Music queue + Toggle shuffle + Toggle repeat \ No newline at end of file