Skip to content

Commit

Permalink
ui: Added top app bar in the mediaplayer page
Browse files Browse the repository at this point in the history
Signed-off-by: Gabriel Fontán <[email protected]>
  • Loading branch information
BobbyESP committed May 5, 2024
1 parent 62ed746 commit 7d92ef9
Show file tree
Hide file tree
Showing 11 changed files with 182 additions and 82 deletions.
8 changes: 4 additions & 4 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ object MediaPlayerModule {
fun providePlayer(
@ApplicationContext context: Context, audioAttributes: AudioAttributes
): ExoPlayer = ExoPlayer.Builder(context)
.setSeekBackIncrementMs(5000)
.setSeekForwardIncrementMs(5000)
.setAudioAttributes(audioAttributes, true)
.setHandleAudioBecomingNoisy(true)
.setTrackSelector(DefaultTrackSelector(context))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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.util.UnstableApi
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaLibraryService.MediaLibrarySession
import androidx.media3.session.MediaSession
import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionResult
Expand All @@ -26,7 +26,7 @@ import javax.inject.Inject

class MediaLibrarySessionCallback @Inject constructor(
@ApplicationContext val context: Context,
) : MediaLibraryService.MediaLibrarySession.Callback {
) : MediaLibrarySession.Callback {

private val availableCommands = listOf(
CommandToggleLibrary,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class MediaServiceHandler @Inject constructor(
val isPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
val shuffleModeEnabled = MutableStateFlow(false)
val repeatMode = MutableStateFlow(REPEAT_MODE_OFF)
val canSkipNext: Boolean
get() = player.hasNextMediaItem()

val canSkipPrevious: Boolean = false

private var job: Job? = null
private val scope = CoroutineScope(Dispatchers.Main)
Expand All @@ -69,6 +73,7 @@ class MediaServiceHandler @Inject constructor(
}

override fun onIsPlayingChanged(isPlaying: Boolean) {
mediaSessionInterface.updateNotificationLayout()
_mediaState.update {
MediaState.Playing(isPlaying)
}
Expand All @@ -82,6 +87,7 @@ class MediaServiceHandler @Inject constructor(
* Stops the player, clears the media queue, and releases resources.
*/
fun killPlayer() {
player.removeListener(this)
player.stop()
player.clearMediaItems()
player.release()
Expand Down Expand Up @@ -305,10 +311,10 @@ class MediaServiceHandler @Inject constructor(
const val ACTION_TOGGLE_LIKE = "TOGGLE_LIKE"
const val ACTION_TOGGLE_SHUFFLE = "TOGGLE_SHUFFLE"
const val ACTION_TOGGLE_REPEAT_MODE = "TOGGLE_REPEAT_MODE"
val CommandToggleLibrary = SessionCommand(ACTION_TOGGLE_LIBRARY, Bundle.EMPTY)
val CommandToggleLike = SessionCommand(ACTION_TOGGLE_LIKE, Bundle.EMPTY)
val CommandToggleShuffle = SessionCommand(ACTION_TOGGLE_SHUFFLE, Bundle.EMPTY)
val CommandToggleRepeatMode = SessionCommand(ACTION_TOGGLE_REPEAT_MODE, Bundle.EMPTY)
val CommandToggleLibrary = SessionCommand(ACTION_TOGGLE_LIBRARY, Bundle())
val CommandToggleLike = SessionCommand(ACTION_TOGGLE_LIKE, Bundle())
val CommandToggleShuffle = SessionCommand(ACTION_TOGGLE_SHUFFLE, Bundle())
val CommandToggleRepeatMode = SessionCommand(ACTION_TOGGLE_REPEAT_MODE, Bundle())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.bobbyesp.mediaplayer.service

import android.content.Intent
import android.os.Binder
import android.util.Log
import androidx.media3.common.Player
import androidx.media3.common.Player.REPEAT_MODE_ALL
import androidx.media3.common.Player.REPEAT_MODE_OFF
Expand Down Expand Up @@ -36,13 +35,9 @@ class MediaplayerService : MediaLibraryService(), MediaSessionLayoutHandler {
lateinit var mediaServiceHandler: MediaServiceHandler

override fun updateNotificationLayout() {
Log.i("MediaplayerService", "Updating notification layout")
Log.i("MediaplayerService", "Shuffle mode: ${mediaSession.player.shuffleModeEnabled}")
Log.i("MediaplayerService", "Repeat mode: ${mediaSession.player.repeatMode}")

val commandButtons = ImmutableList.of<CommandButton>(
CommandButton.Builder()
.setDisplayName(getString(if (mediaSession.player.shuffleModeEnabled) R.string.action_shuffle_off else R.string.action_shuffle_on))
.setDisplayName(getString(if (mediaSession.player.shuffleModeEnabled) R.string.action_shuffle_on else R.string.action_shuffle_off))
.setIconResId(if (mediaSession.player.shuffleModeEnabled) R.drawable.shuffle_on else R.drawable.shuffle)
.setSessionCommand(CommandToggleShuffle).build(),
CommandButton.Builder()
Expand Down
15 changes: 11 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@
android:name=".App"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:enableOnBackInvokedCallback="true"
android:theme="@style/Theme.Metadator.Starting"
tools:targetApi="34">
<activity
android:name=".MainActivity"
android:exported="true"
android:configChanges="orientation"
android:exported="true"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand All @@ -49,7 +49,14 @@

<service
android:name="com.bobbyesp.mediaplayer.service.MediaplayerService"
android:foregroundServiceType="mediaPlayback" />
android:exported="false"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
<action android:name="androidx.media3.session.MediaLibraryService" />
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>

</application>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package com.bobbyesp.metadator.presentation.pages

import androidx.compose.animation.Crossfade
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
Expand All @@ -26,7 +25,6 @@ import my.nanihadesuka.compose.LazyColumnScrollbar
import my.nanihadesuka.compose.LazyGridVerticalScrollbar
import my.nanihadesuka.compose.ScrollbarSelectionActionable

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun MediaStorePage(
modifier: Modifier = Modifier,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,60 +108,61 @@ fun HomePage(
val listIsFirstItemVisible by remember { derivedStateOf { mediaStoreLazyColumnState.firstVisibleItemIndex == 0 } }

Scaffold(modifier = modifier.fillMaxSize(), topBar = {
CenterAlignedTopAppBar(navigationIcon = {
IconButton(onClick = {
scope.launch {
drawerState.open()
}
}) {
Icon(
imageVector = Icons.Rounded.Menu,
contentDescription = stringResource(id = R.string.open_navigation)
)
}
}, title = {
Column(
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
) {
Text(
text = stringResource(id = R.string.app_name).uppercase(),
fontWeight = FontWeight.SemiBold,
fontFamily = FontFamily.Monospace,
style = MaterialTheme.typography.titleLarge.copy(
letterSpacing = 4.sp,
),
)
AutoResizableText(
text = stringResource(id = R.string.app_desc).uppercase(),
fontWeight = FontWeight.Normal,
fontFamily = FontFamily.Monospace,
style = MaterialTheme.typography.bodySmall.copy(
letterSpacing = 2.sp,
),
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
)
}
}, actions = {
IconButton(
onClick = {
moreOptionsVisible = !moreOptionsVisible
CenterAlignedTopAppBar(
navigationIcon = {
IconButton(onClick = {
scope.launch {
drawerState.open()
}
}) {
Icon(
imageVector = Icons.Rounded.MoreVert, contentDescription = stringResource(
id = R.string.open_more_options
Icon(
imageVector = Icons.Rounded.Menu,
contentDescription = stringResource(id = R.string.open_navigation)
)
)
}
AnimatedDropdownMenu(
expanded = moreOptionsVisible, onDismissRequest = {
moreOptionsVisible = false
}) {
DropdownMenuContent(onLayoutChanged = {
desiredLayout = it
})
}
}
}, title = {
Column(
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
) {
Text(
text = stringResource(id = R.string.app_name).uppercase(),
fontWeight = FontWeight.SemiBold,
fontFamily = FontFamily.Monospace,
style = MaterialTheme.typography.titleLarge.copy(
letterSpacing = 4.sp,
),
)
AutoResizableText(
text = stringResource(id = R.string.app_desc).uppercase(),
fontWeight = FontWeight.Normal,
fontFamily = FontFamily.Monospace,
style = MaterialTheme.typography.bodySmall.copy(
letterSpacing = 2.sp,
),
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f)
)
}
}, actions = {
IconButton(
onClick = {
moreOptionsVisible = !moreOptionsVisible
}) {
Icon(
imageVector = Icons.Rounded.MoreVert, contentDescription = stringResource(
id = R.string.open_more_options
)
)
}
AnimatedDropdownMenu(
expanded = moreOptionsVisible, onDismissRequest = {
moreOptionsVisible = false
}) {
DropdownMenuContent(onLayoutChanged = {
desiredLayout = it
})
}

})
})
}, floatingActionButton = {
when (desiredLayout) {
LayoutType.Grid -> {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.bobbyesp.metadator.presentation.pages.mediaplayer

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
Expand All @@ -8,35 +13,110 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.KeyboardDoubleArrowUp
import androidx.compose.material.icons.rounded.Menu
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.bobbyesp.metadator.R
import com.bobbyesp.metadator.presentation.common.LocalDrawerState
import com.bobbyesp.metadator.presentation.common.LocalPlayerAwareWindowInsets
import com.bobbyesp.metadator.presentation.components.cards.songs.HorizontalSongCard
import com.bobbyesp.metadator.presentation.pages.mediaplayer.mediaplayer.MediaplayerSheet
import com.bobbyesp.ui.components.bottomsheet.draggable.DraggableBottomSheetState
import kotlinx.coroutines.launch
import my.nanihadesuka.compose.LazyColumnScrollbar
import my.nanihadesuka.compose.ScrollbarSelectionActionable

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MediaplayerPage(
viewModel: MediaplayerViewModel,
mediaPlayerSheetState: DraggableBottomSheetState
) {
val mediaStoreLazyColumnState = rememberLazyListState()
val isFirstItemVisible by remember { derivedStateOf { mediaStoreLazyColumnState.firstVisibleItemIndex == 0 } }

val songs = viewModel.songsFlow.collectAsStateWithLifecycle(initialValue = emptyList()).value

val insets = LocalPlayerAwareWindowInsets.current
val drawerState = LocalDrawerState.current

val scope = rememberCoroutineScope()

Box(
modifier = Modifier.fillMaxSize()
) {
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
CenterAlignedTopAppBar(
navigationIcon = {
IconButton(onClick = {
scope.launch {
drawerState.open()
}
}) {
Icon(
imageVector = Icons.Rounded.Menu,
contentDescription = stringResource(id = R.string.open_navigation)
)
}
},
title = {
Column(
horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally,
) {
Text(
text = stringResource(id = R.string.mediaplayer).uppercase(),
fontWeight = FontWeight.SemiBold,
fontFamily = FontFamily.Monospace,
style = MaterialTheme.typography.titleLarge.copy(
letterSpacing = 4.sp,
),
)
}
}
)
},
floatingActionButton = {
AnimatedVisibility(
visible = !isFirstItemVisible,
enter = fadeIn() + scaleIn(),
exit = fadeOut() + scaleOut()
) {
FloatingActionButton(onClick = {
scope.launch {
mediaStoreLazyColumnState.animateScrollToItem(0)
}
}) {
Icon(
imageVector = Icons.Rounded.KeyboardDoubleArrowUp,
contentDescription = stringResource(
id = R.string.scroll_to_top
)
)
}
}
},
contentWindowInsets = insets,
) {
Column(
Expand Down
Loading

0 comments on commit 7d92ef9

Please sign in to comment.