From d767f60d3580f39ae0347b4df4ce9969613ac4df Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Thu, 25 Jun 2026 01:04:17 +0530 Subject: [PATCH] Apply profile mesh background to loading screens --- .../commonMain/kotlin/com/nuvio/app/App.kt | 17 +++- .../app/core/ui/ProfileMeshBackground.kt | 91 +++++++++++++++++++ .../profiles/ProfileSelectionScreen.kt | 23 ++--- 3 files changed, 116 insertions(+), 15 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/ProfileMeshBackground.kt diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index 3abbb4eb..8a02385b 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -99,6 +99,7 @@ import com.nuvio.app.core.ui.configurePlatformImageLoader import com.nuvio.app.core.ui.NuvioToastHost import com.nuvio.app.core.ui.NuvioToastController import com.nuvio.app.core.ui.NuvioFloatingPrompt +import com.nuvio.app.core.ui.ProfileMeshBackground import com.nuvio.app.core.ui.TraktListPickerDialog import com.nuvio.app.core.ui.NuvioTheme import com.nuvio.app.core.ui.NuvioTokens @@ -166,6 +167,7 @@ import com.nuvio.app.features.profiles.ProfileEditScreen import com.nuvio.app.features.profiles.ProfileRepository import com.nuvio.app.features.profiles.ProfileSelectionScreen import com.nuvio.app.features.profiles.ProfileSwitcherTab +import com.nuvio.app.features.profiles.parseHexColor import com.nuvio.app.features.profiles.profileAvatarImageUrl import com.nuvio.app.features.search.SearchScreen import com.nuvio.app.features.settings.SettingsScreen @@ -733,6 +735,10 @@ private fun MainAppContent( } } val profileState by ProfileRepository.state.collectAsStateWithLifecycle() + val launchOverlayProfileColor = remember(profileState.activeProfile, profileState.profiles) { + val sourceProfile = profileState.activeProfile ?: profileState.profiles.firstOrNull() + sourceProfile?.avatarColorHex?.let(::parseHexColor) ?: Color(0xFF1E88E5) + } val playerSettingsUiState by remember { PlayerSettingsRepository.ensureLoaded() PlayerSettingsRepository.uiState @@ -2913,7 +2919,10 @@ private fun MainAppContent( enter = fadeIn(), exit = fadeOut(androidx.compose.animation.core.tween(400)), ) { - AppLaunchOverlay(modifier = Modifier.fillMaxSize()) + AppLaunchOverlay( + profileColor = launchOverlayProfileColor, + modifier = Modifier.fillMaxSize(), + ) } // Auto-dismiss profile switch overlay @@ -3237,15 +3246,19 @@ private fun TabletTopPillItem( @Composable private fun AppLaunchOverlay( + profileColor: Color, modifier: Modifier = Modifier, ) { val tokens = MaterialTheme.nuvio Box( modifier = modifier - .background(tokens.colors.background) .zIndex(NuvioTokens.Z.dialog), contentAlignment = Alignment.Center, ) { + ProfileMeshBackground( + profileColor = profileColor, + modifier = Modifier.fillMaxSize(), + ) Column( horizontalAlignment = Alignment.CenterHorizontally, ) { diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/ProfileMeshBackground.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/ProfileMeshBackground.kt new file mode 100644 index 00000000..f276ff16 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/ProfileMeshBackground.kt @@ -0,0 +1,91 @@ +package com.nuvio.app.core.ui + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.lerp + +@Composable +fun ProfileMeshBackground( + profileColor: Color, + modifier: Modifier = Modifier, +) { + val animatedProfileColor by animateColorAsState( + targetValue = profileColor, + animationSpec = tween(durationMillis = 520), + label = "profileMeshBackgroundColor", + ) + val baseColor = Color.Black + val primaryMeshColor = lerp(baseColor, animatedProfileColor, 0.58f) + val secondaryMeshColor = lerp(animatedProfileColor, MaterialTheme.colorScheme.secondary, 0.32f) + val tertiaryMeshColor = lerp(animatedProfileColor, MaterialTheme.colorScheme.tertiary, 0.28f) + + Box( + modifier = modifier + .fillMaxSize() + .drawBehind { + val maxDimension = maxOf(size.width, size.height) + + drawRect(color = baseColor, size = size) + drawRect( + brush = Brush.radialGradient( + colorStops = arrayOf( + 0f to primaryMeshColor.copy(alpha = 0.46f), + 0.38f to primaryMeshColor.copy(alpha = 0.2f), + 0.72f to primaryMeshColor.copy(alpha = 0.05f), + 1f to Color.Transparent, + ), + center = Offset(size.width * -0.08f, size.height * -0.02f), + radius = maxDimension * 0.7f, + ), + size = size, + ) + drawRect( + brush = Brush.radialGradient( + colorStops = arrayOf( + 0f to animatedProfileColor.copy(alpha = 0.24f), + 0.44f to animatedProfileColor.copy(alpha = 0.09f), + 0.78f to animatedProfileColor.copy(alpha = 0.02f), + 1f to Color.Transparent, + ), + center = Offset(size.width * 0.16f, size.height * 0.18f), + radius = maxDimension * 0.46f, + ), + size = size, + ) + drawRect( + brush = Brush.radialGradient( + colorStops = arrayOf( + 0f to secondaryMeshColor.copy(alpha = 0.16f), + 0.5f to secondaryMeshColor.copy(alpha = 0.05f), + 1f to Color.Transparent, + ), + center = Offset(size.width * 0.9f, size.height * 0.12f), + radius = maxDimension * 0.36f, + ), + size = size, + ) + drawRect( + brush = Brush.radialGradient( + colorStops = arrayOf( + 0f to tertiaryMeshColor.copy(alpha = 0.08f), + 0.52f to tertiaryMeshColor.copy(alpha = 0.03f), + 1f to Color.Transparent, + ), + center = Offset(size.width * 0.28f, size.height * 0.4f), + radius = maxDimension * 0.28f, + ), + size = size, + ) + }, + ) +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileSelectionScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileSelectionScreen.kt index 195ba674..25761a45 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileSelectionScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/profiles/ProfileSelectionScreen.kt @@ -46,7 +46,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale @@ -59,6 +58,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil3.compose.AsyncImage import com.nuvio.app.core.auth.AuthRepository import com.nuvio.app.core.auth.AuthState +import com.nuvio.app.core.ui.ProfileMeshBackground import kotlinx.coroutines.delay import kotlinx.coroutines.launch import nuvio.composeapp.generated.resources.* @@ -100,26 +100,23 @@ fun ProfileSelectionScreen( } val statusBarTop = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val backgroundProfileColor = remember(profileState.activeProfile, profileState.profiles) { + val sourceProfile = profileState.activeProfile ?: profileState.profiles.firstOrNull() + sourceProfile?.avatarColorHex?.let(::parseHexColor) ?: Color(0xFF1E88E5) + } BoxWithConstraints( modifier = modifier - .fillMaxSize() - .background( - Brush.verticalGradient( - colors = listOf( - MaterialTheme.colorScheme.background, - MaterialTheme.colorScheme.background, - MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.15f), - ), - ), - ) - .padding(top = statusBarTop), + .fillMaxSize(), ) { val isTabletLayout = maxWidth >= 768.dp + ProfileMeshBackground(profileColor = backgroundProfileColor) + Column( modifier = Modifier .fillMaxSize() + .padding(top = statusBarTop) .then( if (isTabletLayout) { Modifier @@ -137,7 +134,7 @@ fun ProfileSelectionScreen( text = stringResource(Res.string.profile_who_is_watching), style = MaterialTheme.typography.headlineLarge.copy( fontSize = 30.sp, - letterSpacing = (-0.5).sp, + letterSpacing = 0.sp, ), color = MaterialTheme.colorScheme.onBackground, fontWeight = FontWeight.Bold,