Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Made the player screen background dynamic #520

Merged
merged 3 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import org.listenbrainz.android.ui.theme.ListenBrainzTheme
fun TopBar(
navController: NavController = rememberNavController(),
searchBarState: SearchBarState,
backgroundColor: Color=Color.Transparent,
context: Context = LocalContext.current,
) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
Expand Down Expand Up @@ -58,7 +59,7 @@ fun TopBar(
tint = Color.Unspecified)
}
},
backgroundColor = Color.Transparent,
backgroundColor =backgroundColor,
contentColor = MaterialTheme.colorScheme.onSurface,
elevation = 0.dp,
actions = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
Expand Down Expand Up @@ -63,10 +64,12 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
Expand Down Expand Up @@ -118,11 +121,21 @@ fun BrainzPlayerBackDropScreen(
mutableFloatStateOf(0F)
}
val repeatMode by brainzPlayerViewModel.repeatMode.collectAsStateWithLifecycle()
val context = LocalContext.current
val defaultBackgroundColor = MaterialTheme.colorScheme.background
val isSystemInDarkTheme = isSystemInDarkTheme()
GautamCoder4019k marked this conversation as resolved.
Show resolved Hide resolved

/** 56.dp is default bottom navigation height. 70.dp is our mini player's height. */
val headerHeight by animateDpAsState(targetValue = if (currentlyPlayingSong.title == "null" && currentlyPlayingSong.artist == "null") 56.dp else 126.dp)
val isPlaying = brainzPlayerViewModel.isPlaying.collectAsState().value

LaunchedEffect(currentlyPlayingSong) {
brainzPlayerViewModel.getBackGroundColorForPlayer(
currentlyPlayingSong.albumArt,
defaultBackgroundColor,
context,
isSystemInDarkTheme = isSystemInDarkTheme
)
}
BackdropScaffold(
modifier = Modifier.padding(top = paddingValues.calculateTopPadding()),
frontLayerShape = RectangleShape,
Expand All @@ -136,7 +149,7 @@ fun BrainzPlayerBackDropScreen(
backLayerContent()
}
},
frontLayerBackgroundColor = MaterialTheme.colorScheme.background,
frontLayerBackgroundColor = defaultBackgroundColor,
appBar = {},
persistentAppBar = false,
frontLayerContent = {
Expand All @@ -149,7 +162,14 @@ fun BrainzPlayerBackDropScreen(
currentlyPlayingSong = currentlyPlayingSong,
isShuffled = isShuffled,
repeatMode = repeatMode,
backdropScaffoldState = backdropScaffoldState
backdropScaffoldState = backdropScaffoldState,
backgroundBrush = Brush.verticalGradient(
colors = listOf(
brainzPlayerViewModel.playerBackGroundColor,
defaultBackgroundColor
)
),
dynamicBackground = brainzPlayerViewModel.playerBackGroundColor
)
val songList = brainzPlayerViewModel.mediaItem.collectAsState().value.data ?: listOf()
SongViewPager(
Expand All @@ -172,6 +192,8 @@ fun PlayerScreen(
isShuffled: Boolean,
repeatMode: RepeatMode,
backdropScaffoldState: BackdropScaffoldState,
backgroundBrush: Brush,
dynamicBackground: Color = MaterialTheme.colorScheme.background
) {
val coroutineScope = rememberCoroutineScope()
val playlistViewModel = hiltViewModel<PlaylistViewModel>()
Expand All @@ -189,17 +211,17 @@ fun PlayerScreen(
println("Playlist is empty")
}

if(backdropScaffoldState.isConcealed){
if (backdropScaffoldState.isConcealed) {
BackHandler {
coroutineScope.launch {
backdropScaffoldState.reveal()
}
}
}
LazyColumn {
LazyColumn(modifier = Modifier.background(brush = backgroundBrush)) {
item {
songList.data?.let {
AlbumArtViewPager(currentlyPlayingSong, pagerState)
AlbumArtViewPager(currentlyPlayingSong, pagerState, dynamicBackground)
}
}
item {
Expand Down Expand Up @@ -539,12 +561,15 @@ fun PlayerScreen(

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun AlbumArtViewPager(currentlyPlayingSong: Song, pagerState: PagerState) {
fun AlbumArtViewPager(
currentlyPlayingSong: Song,
pagerState: PagerState,
dynamicBackground: Color
) {
HorizontalPager(
state = pagerState,
modifier = Modifier
.fillMaxWidth()
.background(ListenBrainzTheme.colorScheme.background),
) { page ->
Column(
Modifier
Expand All @@ -556,7 +581,7 @@ fun AlbumArtViewPager(currentlyPlayingSong: Song, pagerState: PagerState) {
.padding(top = 20.dp)
.width(300.dp)
.clip(RoundedCornerShape(20.dp))
.background(MaterialTheme.colorScheme.background)
.background(dynamicBackground)
.graphicsLayer {
// Calculate the absolute offset for the current page from the
// scroll position. We use the absolute value which allows us to mirror
Expand All @@ -583,7 +608,7 @@ fun AlbumArtViewPager(currentlyPlayingSong: Song, pagerState: PagerState) {
) {
AsyncImage(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.background(dynamicBackground)
.fillMaxSize()
.padding()
.clip(shape = RoundedCornerShape(20.dp))
Expand All @@ -607,7 +632,8 @@ fun AlbumArtViewPager(currentlyPlayingSong: Song, pagerState: PagerState) {
fun AlbumArtViewPagerPreview() {
AlbumArtViewPager(
currentlyPlayingSong = Song.preview(),
pagerState = rememberPagerState { 3 }
pagerState = rememberPagerState { 3 },
dynamicBackground = MaterialTheme.colorScheme.background
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.captionBar
import androidx.compose.foundation.layout.safeDrawingPadding
Expand All @@ -25,6 +28,7 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
Expand All @@ -48,6 +52,7 @@ import org.listenbrainz.android.ui.screens.search.rememberSearchBarState
import org.listenbrainz.android.ui.theme.ListenBrainzTheme
import org.listenbrainz.android.util.Utils.isServiceRunning
import org.listenbrainz.android.util.Utils.openAppSystemSettings
import org.listenbrainz.android.viewmodel.BrainzPlayerViewModel
import org.listenbrainz.android.viewmodel.DashBoardViewModel

@AndroidEntryPoint
Expand Down Expand Up @@ -158,7 +163,11 @@ class MainActivity : ComponentActivity() {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
val username = dashBoardViewModel.username

val brainzPlayerViewModel: BrainzPlayerViewModel by viewModels()
val animatedBackgroundColor by animateColorAsState(
targetValue = if (backdropScaffoldState.isConcealed) brainzPlayerViewModel.playerBackGroundColor else Color.Transparent,
animationSpec = tween(durationMillis = 500)
)
Scaffold(
modifier = Modifier.safeDrawingPadding(),
topBar = {
Expand All @@ -167,7 +176,8 @@ class MainActivity : ComponentActivity() {
searchBarState = when (currentDestination?.route) {
AppNavigationItem.BrainzPlayer.route -> brainzplayerSearchBarState
else -> searchBarState
}
},
backgroundColor = animatedBackgroundColor
)
},
bottomBar = {
Expand Down Expand Up @@ -199,6 +209,7 @@ class MainActivity : ComponentActivity() {
BrainzPlayerBackDropScreen(
backdropScaffoldState = backdropScaffoldState,
paddingValues = it,
brainzPlayerViewModel = brainzPlayerViewModel
) {
AppNavigation(
navController = navController,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.listenbrainz.android.viewmodel

import android.content.Context
import android.graphics.Color.parseColor
import android.graphics.drawable.BitmapDrawable
import android.os.Build
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.session.PlaybackStateCompat.REPEAT_MODE_ALL
Expand All @@ -12,8 +15,13 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.palette.graphics.Palette
import coil.ImageLoader
import coil.request.ImageRequest
import coil.request.SuccessResult
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -67,6 +75,8 @@ class BrainzPlayerViewModel @Inject constructor(
val repeatMode = brainzPlayerServiceConnection.repeatModeState
var isSearching by mutableStateOf(false)

var playerBackGroundColor by mutableStateOf(Color.White)
GautamCoder4019k marked this conversation as resolved.
Show resolved Hide resolved

init {
updatePlayerPosition()
_mediaItems.value = Resource.loading()
Expand Down Expand Up @@ -94,6 +104,54 @@ class BrainzPlayerViewModel @Inject constructor(
}
}

fun getBackGroundColorForPlayer(
albumArtUrl: String?,
defaultColor: Color,
context: Context,
isSystemInDarkTheme: Boolean = true
) {
viewModelScope.launch {
var dominantColor: Color = defaultColor
val loader = ImageLoader(context)
val request = ImageRequest.Builder(context)
.data(albumArtUrl)
.allowHardware(false)
.build()
val result = loader.execute(request)
val bitmap = (result as? SuccessResult)?.drawable?.let { drawable ->
(drawable as? BitmapDrawable)?.bitmap
}
bitmap?.let {
dominantColor = if (isSystemInDarkTheme)
Color(
parseColor(
parseColorSwatch(
Palette.from(it).generate().darkMutedSwatch
)
)
)
else
Color(
parseColor(
parseColorSwatch(
Palette.from(it).generate().lightMutedSwatch
)
)
)
}
playerBackGroundColor = dominantColor
}
}

private fun parseColorSwatch(color: Palette.Swatch?): String {
return if (color != null) {
val parsedColor = Integer.toHexString(color.rgb)
return "#$parsedColor"
} else {
"#FFFFFF"
}
}

fun skipToNextSong() {
brainzPlayerServiceConnection.transportControls.skipToNext()
// Updating currently playing song.
Expand Down
Loading