Skip to content

Commit 45c6f38

Browse files
committed
style(android): apply immersive album art background to player screen
- Made album art a top-aligned immersive background covering the top 65% of the screen\n- Applied a vertical gradient overlay fading to the background color to smoothly blend the image\n- Moved all text, progress, and playback controls to align to the bottom of the screen\n- Moved the source app icon chip above the track title for a cleaner layout
1 parent 04b599b commit 45c6f38

1 file changed

Lines changed: 126 additions & 157 deletions

File tree

Android/app/src/main/java/com/jayfunc/carpecast/MainActivity.kt

Lines changed: 126 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -313,16 +313,68 @@ fun PlayerScreen(state: MediaState) {
313313
}
314314

315315
Box(
316-
modifier = Modifier
317-
.fillMaxSize()
318-
.padding(16.dp),
319-
contentAlignment = Alignment.Center
316+
modifier = Modifier.fillMaxSize()
320317
) {
318+
// 1. Immersive Background
319+
if (state.albumArt != null) {
320+
Image(
321+
bitmap = state.albumArt!!.asImageBitmap(),
322+
contentDescription = null,
323+
contentScale = ContentScale.Crop,
324+
modifier = Modifier
325+
.fillMaxWidth()
326+
.fillMaxHeight(0.65f)
327+
.align(Alignment.TopCenter)
328+
)
329+
// Gradient Overlay
330+
Box(
331+
modifier = Modifier
332+
.fillMaxWidth()
333+
.fillMaxHeight(0.65f)
334+
.align(Alignment.TopCenter)
335+
.background(
336+
Brush.verticalGradient(
337+
colors = listOf(
338+
MaterialTheme.colorScheme.background.copy(alpha = 0.2f),
339+
MaterialTheme.colorScheme.background.copy(alpha = 0.6f),
340+
MaterialTheme.colorScheme.background
341+
)
342+
)
343+
)
344+
)
345+
} else {
346+
// Placeholder
347+
Box(
348+
modifier = Modifier
349+
.fillMaxWidth()
350+
.fillMaxHeight(0.65f)
351+
.align(Alignment.TopCenter)
352+
.background(
353+
Brush.verticalGradient(
354+
colors = listOf(
355+
MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
356+
MaterialTheme.colorScheme.background
357+
)
358+
)
359+
),
360+
contentAlignment = Alignment.Center
361+
) {
362+
Icon(
363+
painter = painterResource(id = R.drawable.ic_album_placeholder),
364+
contentDescription = null,
365+
modifier = Modifier.size(120.dp).offset(y = (-40).dp),
366+
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.3f)
367+
)
368+
}
369+
}
321370

371+
// 2. Foreground Content
322372
Column(
323-
modifier = Modifier.fillMaxSize(),
373+
modifier = Modifier
374+
.fillMaxSize()
375+
.padding(horizontal = 24.dp),
324376
horizontalAlignment = Alignment.CenterHorizontally,
325-
verticalArrangement = Arrangement.Center
377+
verticalArrangement = Arrangement.Bottom
326378
) {
327379
if (!hasPermission) {
328380
Card(
@@ -354,157 +406,87 @@ fun PlayerScreen(state: MediaState) {
354406
}
355407
}
356408

357-
358-
359409
Crossfade(
360410
targetState = trackId, animationSpec = tween(500), label = "TrackCrossfade",
361-
modifier = Modifier.weight(1f)
411+
modifier = Modifier.fillMaxWidth()
362412
) { _ ->
363413
Column(
364-
horizontalAlignment = Alignment.CenterHorizontally,
365-
modifier = Modifier.fillMaxSize()
414+
modifier = Modifier.fillMaxWidth(),
415+
horizontalAlignment = Alignment.Start
366416
) {
367-
Box(
368-
modifier = Modifier
369-
.weight(1f)
370-
.fillMaxWidth(0.8f),
371-
contentAlignment = Alignment.Center
372-
) {
373-
Box(modifier = Modifier
374-
.fillMaxWidth()
375-
.aspectRatio(1f)) {
376-
if (state.albumArt != null) {
377-
Image(
378-
bitmap = state.albumArt!!.asImageBitmap(),
379-
contentDescription = "Album Art",
380-
contentScale = ContentScale.Crop,
381-
modifier = Modifier
382-
.fillMaxSize()
383-
.shadow(
384-
elevation = 24.dp,
385-
shape = RoundedCornerShape(24.dp),
386-
ambientColor = MaterialTheme.colorScheme.primary,
387-
spotColor = MaterialTheme.colorScheme.primary
388-
)
389-
.clip(RoundedCornerShape(24.dp))
390-
)
391-
} else {
392-
Box(
393-
modifier = Modifier
394-
.fillMaxSize()
395-
.shadow(elevation = 8.dp, shape = RoundedCornerShape(24.dp))
396-
.clip(RoundedCornerShape(24.dp))
397-
.background(MaterialTheme.colorScheme.surfaceVariant),
398-
contentAlignment = Alignment.Center
417+
// App Source Chip
418+
if (state.packageName != null) {
419+
val pm = context.packageManager
420+
val appDetails = remember(state.packageName) {
421+
try {
422+
val appInfo = pm.getApplicationInfo(state.packageName, 0)
423+
val appLabel = pm.getApplicationLabel(appInfo).toString()
424+
val icon = pm.getApplicationIcon(appInfo).toBitmap().asImageBitmap()
425+
Pair(appLabel, icon)
426+
} catch (e: PackageManager.NameNotFoundException) { null }
427+
}
428+
429+
if (appDetails != null) {
430+
val (appLabel, icon) = appDetails
431+
Surface(
432+
shape = RoundedCornerShape(50),
433+
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.6f),
434+
contentColor = MaterialTheme.colorScheme.onSurface,
435+
modifier = Modifier
436+
.padding(bottom = 16.dp)
437+
.clip(RoundedCornerShape(50))
438+
.clickable {
439+
val intent = pm.getLaunchIntentForPackage(state.packageName)
440+
if (intent != null) context.startActivity(intent)
441+
},
442+
shadowElevation = 0.dp
443+
) {
444+
Row(
445+
verticalAlignment = Alignment.CenterVertically,
446+
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp)
399447
) {
400-
Icon(
401-
painter = painterResource(id = R.drawable.ic_album_placeholder),
402-
contentDescription = "Placeholder",
403-
modifier = Modifier.size(96.dp),
404-
tint = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)
448+
Image(bitmap = icon, contentDescription = null, modifier = Modifier.size(16.dp))
449+
Spacer(modifier = Modifier.width(6.dp))
450+
Text(
451+
text = appLabel,
452+
style = MaterialTheme.typography.labelMedium.copy(fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
405453
)
406454
}
407455
}
408-
409-
if (state.packageName != null) {
410-
val pm = context.packageManager
411-
val appDetails = remember(state.packageName) {
412-
try {
413-
val appInfo = pm.getApplicationInfo(state.packageName, 0)
414-
val appLabel = pm.getApplicationLabel(appInfo).toString()
415-
val icon = pm.getApplicationIcon(appInfo).toBitmap()
416-
.asImageBitmap()
417-
Pair(appLabel, icon)
418-
} catch (e: PackageManager.NameNotFoundException) {
419-
null
420-
}
421-
}
422-
423-
if (appDetails != null) {
424-
val (appLabel, icon) = appDetails
425-
Surface(
426-
shape = RoundedCornerShape(50),
427-
color = MaterialTheme.colorScheme.surface.copy(alpha = 0.85f),
428-
contentColor = MaterialTheme.colorScheme.onSurface,
429-
modifier = Modifier
430-
.align(Alignment.BottomStart)
431-
.padding(12.dp)
432-
.clip(RoundedCornerShape(50))
433-
.clickable {
434-
val intent =
435-
pm.getLaunchIntentForPackage(state.packageName)
436-
if (intent != null) {
437-
context.startActivity(intent)
438-
}
439-
},
440-
shadowElevation = 0.dp
441-
) {
442-
Row(
443-
verticalAlignment = Alignment.CenterVertically,
444-
modifier = Modifier.padding(
445-
horizontal = 10.dp,
446-
vertical = 6.dp
447-
)
448-
) {
449-
Image(
450-
bitmap = icon,
451-
contentDescription = null,
452-
modifier = Modifier.size(16.dp)
453-
)
454-
Spacer(modifier = Modifier.width(6.dp))
455-
Text(
456-
text = appLabel,
457-
style = MaterialTheme.typography.labelMedium.copy(
458-
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
459-
)
460-
)
461-
}
462-
}
463-
}
464-
}
465456
}
466457
}
467458

468-
Spacer(modifier = Modifier.height(24.dp))
469-
470-
Column(
471-
modifier = Modifier
472-
.fillMaxWidth(0.8f)
473-
.height(96.dp),
474-
horizontalAlignment = Alignment.Start,
475-
verticalArrangement = Arrangement.Center
476-
) {
477-
FadingMarqueeText(
478-
text = state.title.ifEmpty { stringResource(R.string.no_media_playing) },
479-
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
480-
)
481-
Spacer(modifier = Modifier.height(4.dp))
459+
// Text Info
460+
FadingMarqueeText(
461+
text = state.title.ifEmpty { stringResource(R.string.no_media_playing) },
462+
style = MaterialTheme.typography.headlineMedium.copy(fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
463+
)
464+
Spacer(modifier = Modifier.height(4.dp))
465+
FadingMarqueeText(
466+
text = state.artist.ifEmpty { stringResource(R.string.unknown_artist) },
467+
style = MaterialTheme.typography.titleMedium,
468+
color = MaterialTheme.colorScheme.onSurfaceVariant
469+
)
470+
if (state.album.isNotEmpty()) {
482471
FadingMarqueeText(
483-
text = state.artist.ifEmpty { stringResource(R.string.unknown_artist) },
484-
style = MaterialTheme.typography.titleMedium,
485-
color = MaterialTheme.colorScheme.onSurfaceVariant
472+
text = state.album,
473+
style = MaterialTheme.typography.bodyMedium,
474+
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
486475
)
487-
if (state.album.isNotEmpty()) {
488-
FadingMarqueeText(
489-
text = state.album,
490-
style = MaterialTheme.typography.bodyMedium,
491-
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f)
492-
)
493-
}
494476
}
495477
}
496478
}
497479

498480
Spacer(modifier = Modifier.height(24.dp))
499481

500-
Column(modifier = Modifier.fillMaxWidth(0.8f)) {
501-
val progress =
502-
if (state.duration > 0) state.position.toFloat() / state.duration else 0f
482+
// Progress
483+
Column(modifier = Modifier.fillMaxWidth()) {
484+
val progress = if (state.duration > 0) state.position.toFloat() / state.duration else 0f
503485
LinearProgressIndicator(
504486
progress = progress.coerceIn(0f, 1f),
505487
modifier = Modifier
506488
.fillMaxWidth()
507-
.height(8.dp)
489+
.height(6.dp)
508490
.clip(RoundedCornerShape(50)),
509491
color = MaterialTheme.colorScheme.primary,
510492
trackColor = MaterialTheme.colorScheme.primaryContainer
@@ -513,7 +495,7 @@ fun PlayerScreen(state: MediaState) {
513495
Row(
514496
modifier = Modifier
515497
.fillMaxWidth()
516-
.padding(top = 12.dp),
498+
.padding(top = 8.dp),
517499
horizontalArrangement = Arrangement.SpaceBetween
518500
) {
519501
Text(
@@ -531,29 +513,22 @@ fun PlayerScreen(state: MediaState) {
531513

532514
Spacer(modifier = Modifier.height(24.dp))
533515

516+
// Controls
534517
Row(
535-
modifier = Modifier.fillMaxWidth(0.8f),
518+
modifier = Modifier.fillMaxWidth(),
536519
horizontalArrangement = Arrangement.SpaceEvenly,
537520
verticalAlignment = Alignment.CenterVertically
538521
) {
539-
IconButton(
540-
onClick = { sendCommand(context, "ACTION_PREV") },
541-
modifier = Modifier.size(56.dp)
542-
) {
543-
Icon(
544-
painterResource(R.drawable.ic_skip_previous),
545-
contentDescription = "Prev",
546-
modifier = Modifier.size(36.dp),
547-
tint = MaterialTheme.colorScheme.onSurface
548-
)
522+
IconButton(onClick = { sendCommand(context, "ACTION_PREV") }, modifier = Modifier.size(56.dp)) {
523+
Icon(painterResource(R.drawable.ic_skip_previous), contentDescription = "Prev", modifier = Modifier.size(36.dp), tint = MaterialTheme.colorScheme.onSurface)
549524
}
550525

551526
Surface(
552527
shape = androidx.compose.foundation.shape.CircleShape,
553528
color = MaterialTheme.colorScheme.primary,
554529
contentColor = MaterialTheme.colorScheme.onPrimary,
555530
modifier = Modifier
556-
.size(80.dp)
531+
.size(72.dp)
557532
.clip(androidx.compose.foundation.shape.CircleShape)
558533
.clickable { sendCommand(context, "ACTION_PLAY_PAUSE") },
559534
shadowElevation = 8.dp
@@ -562,23 +537,17 @@ fun PlayerScreen(state: MediaState) {
562537
Icon(
563538
painterResource(if (state.isPlaying) R.drawable.ic_pause else R.drawable.ic_play),
564539
contentDescription = "Play/Pause",
565-
modifier = Modifier.size(40.dp)
540+
modifier = Modifier.size(36.dp)
566541
)
567542
}
568543
}
569544

570-
IconButton(
571-
onClick = { sendCommand(context, "ACTION_NEXT") },
572-
modifier = Modifier.size(56.dp)
573-
) {
574-
Icon(
575-
painterResource(R.drawable.ic_skip_next),
576-
contentDescription = "Next",
577-
modifier = Modifier.size(36.dp),
578-
tint = MaterialTheme.colorScheme.onSurface
579-
)
545+
IconButton(onClick = { sendCommand(context, "ACTION_NEXT") }, modifier = Modifier.size(56.dp)) {
546+
Icon(painterResource(R.drawable.ic_skip_next), contentDescription = "Next", modifier = Modifier.size(36.dp), tint = MaterialTheme.colorScheme.onSurface)
580547
}
581548
}
549+
550+
Spacer(modifier = Modifier.height(32.dp))
582551
}
583552
}
584553
}

0 commit comments

Comments
 (0)