From 7a76d9d38c9f5db963581ca8d2e826d062592cd5 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:49:39 +0530 Subject: [PATCH] fix: adjust toast ui --- .../commonMain/kotlin/com/nuvio/app/App.kt | 7 ++ .../com/nuvio/app/core/ui/NuvioComponents.kt | 95 +++++++++++++++---- .../collection/CollectionEditorScreen.kt | 53 +++++++---- .../settings/HomescreenSettingsPage.kt | 10 +- 4 files changed, 124 insertions(+), 41 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index 4c136a02..77fc354a 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -76,6 +76,7 @@ import com.nuvio.app.core.ui.NuvioContinueWatchingActionSheet import com.nuvio.app.core.ui.NuvioPosterActionSheet import com.nuvio.app.core.ui.PlatformBackHandler import com.nuvio.app.core.ui.configurePlatformImageLoader +import com.nuvio.app.core.ui.NuvioToastHost import com.nuvio.app.core.ui.TraktListPickerDialog import com.nuvio.app.core.ui.NuvioTheme import com.nuvio.app.features.auth.AuthScreen @@ -1375,6 +1376,12 @@ private fun MainAppContent( profileSwitchLoading = false } } + + NuvioToastHost( + modifier = Modifier + .align(Alignment.TopCenter) + .zIndex(20f), + ) } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioComponents.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioComponents.kt index 5fd24e83..d015d089 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioComponents.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/core/ui/NuvioComponents.kt @@ -2,6 +2,7 @@ package com.nuvio.app.core.ui import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.MutableTransitionState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideInVertically @@ -46,6 +47,11 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -59,6 +65,9 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.ArrowBack +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow @Composable fun NuvioScreen( @@ -436,42 +445,94 @@ fun NuvioStatusModal( } @Composable -fun NuvioPinnedCollectionToast( - visible: Boolean, - onDismiss: () -> Unit, - message: String = "Remove pin to top from collection to move", +fun NuvioToastHost( + modifier: Modifier = Modifier, ) { - LaunchedEffect(visible) { - if (visible) { - kotlinx.coroutines.delay(2500L) - onDismiss() + val toast by NuvioToastController.currentToast.collectAsState() + val statusBarTop = WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + val visibilityState = remember { MutableTransitionState(false) } + var renderedToast by remember { mutableStateOf(null) } + + LaunchedEffect(toast?.id) { + val currentToast = toast + if (currentToast != null) { + renderedToast = currentToast + visibilityState.targetState = true + delay(currentToast.durationMillis) + NuvioToastController.dismiss(currentToast.id) + } else { + visibilityState.targetState = false + } + } + + LaunchedEffect( + visibilityState.currentState, + visibilityState.targetState, + visibilityState.isIdle, + ) { + if (visibilityState.isIdle && !visibilityState.currentState && !visibilityState.targetState) { + renderedToast = null } } AnimatedVisibility( - visible = visible, + visibleState = visibilityState, + modifier = modifier, enter = fadeIn() + slideInVertically { -it }, exit = fadeOut() + slideOutVertically { -it }, ) { + val currentToast = renderedToast ?: return@AnimatedVisibility Box( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), - contentAlignment = Alignment.Center, + .padding(top = statusBarTop + 12.dp) + .padding(horizontal = 16.dp), + contentAlignment = Alignment.TopCenter, ) { Surface( - shape = RoundedCornerShape(12.dp), - color = MaterialTheme.colorScheme.inverseSurface, + shape = RoundedCornerShape(18.dp), + color = MaterialTheme.colorScheme.surfaceContainerHigh, tonalElevation = 6.dp, - shadowElevation = 4.dp, + shadowElevation = 10.dp, ) { Text( - text = message, - modifier = Modifier.padding(horizontal = 16.dp, vertical = 10.dp), + text = currentToast.message, + modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.inverseOnSurface, + color = MaterialTheme.colorScheme.onSurface, ) } } } } + +data class NuvioToastMessage( + val id: Long, + val message: String, + val durationMillis: Long, +) + +object NuvioToastController { + private val _currentToast = MutableStateFlow(null) + val currentToast = _currentToast.asStateFlow() + private var nextToastId = 0L + + fun show( + message: String, + durationMillis: Long = 2500L, + ) { + nextToastId += 1L + _currentToast.value = NuvioToastMessage( + id = nextToastId, + message = message, + durationMillis = durationMillis, + ) + } + + fun dismiss(id: Long? = null) { + val activeToast = _currentToast.value ?: return + if (id == null || activeToast.id == id) { + _currentToast.value = null + } + } +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt index a3c03f7f..88694fff 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/collection/CollectionEditorScreen.kt @@ -8,10 +8,12 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding @@ -60,6 +62,7 @@ import com.nuvio.app.core.ui.NuvioScreen import com.nuvio.app.core.ui.NuvioScreenHeader import com.nuvio.app.core.ui.NuvioSectionLabel import com.nuvio.app.core.ui.NuvioSurfaceCard +import com.nuvio.app.core.ui.nuvioPlatformExtraBottomPadding import com.nuvio.app.features.home.PosterShape import sh.calvin.reorderable.ReorderableCollectionItemScope import sh.calvin.reorderable.ReorderableItem @@ -72,6 +75,7 @@ fun CollectionEditorScreen( onBack: () -> Unit, ) { val state by CollectionEditorRepository.uiState.collectAsState() + val bottomInset = nuvioPlatformExtraBottomPadding LaunchedEffect(collectionId) { CollectionEditorRepository.initialize(collectionId) @@ -93,16 +97,18 @@ fun CollectionEditorScreen( ) } - NuvioScreen { - stickyHeader { - NuvioScreenHeader( - title = if (state.isNew) "New Collection" else "Edit Collection", - onBack = onBack, - ) - } + Box(modifier = Modifier.fillMaxSize()) { + NuvioScreen( + modifier = Modifier.fillMaxSize(), + ) { + stickyHeader { + NuvioScreenHeader( + title = if (state.isNew) "New Collection" else "Edit Collection", + onBack = onBack, + ) + } - // Title - item { + item { NuvioInputField( value = state.title, onValueChange = { CollectionEditorRepository.setTitle(it) }, @@ -110,8 +116,7 @@ fun CollectionEditorScreen( ) } - // Backdrop URL - item { + item { NuvioInputField( value = state.backdropImageUrl, onValueChange = { CollectionEditorRepository.setBackdropImageUrl(it) }, @@ -119,8 +124,7 @@ fun CollectionEditorScreen( ) } - // Pin to Top - item { + item { NuvioSurfaceCard { Row( modifier = Modifier @@ -325,9 +329,25 @@ fun CollectionEditorScreen( } } - // Save button - item { - Spacer(modifier = Modifier.height(8.dp)) + item { + Spacer(modifier = Modifier.height(96.dp + bottomInset)) + } + } + + Surface( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth(), + color = MaterialTheme.colorScheme.background.copy(alpha = 0.96f), + tonalElevation = 6.dp, + shadowElevation = 10.dp, + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 12.dp) + .padding(bottom = bottomInset), + ) { NuvioPrimaryButton( text = if (state.isNew) "Create Collection" else "Save Changes", enabled = state.title.isNotBlank(), @@ -337,6 +357,7 @@ fun CollectionEditorScreen( } }, ) + } } } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/HomescreenSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/HomescreenSettingsPage.kt index 2f4b658f..4ea4b783 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/HomescreenSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/HomescreenSettingsPage.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.nuvio.app.core.ui.NuvioActionLabel -import com.nuvio.app.core.ui.NuvioPinnedCollectionToast +import com.nuvio.app.core.ui.NuvioToastController import com.nuvio.app.features.home.HomeCatalogSettingsItem import com.nuvio.app.features.home.HomeCatalogSettingsRepository import com.nuvio.app.features.home.components.HomeEmptyStateCard @@ -114,20 +114,14 @@ internal fun LazyListScope.homescreenSettingsContent( ) }, ) { - var showPinnedToast by remember { mutableStateOf(false) } val hapticFeedback = LocalHapticFeedback.current - NuvioPinnedCollectionToast( - visible = showPinnedToast, - onDismiss = { showPinnedToast = false }, - ) - HomescreenCatalogList( isTablet = isTablet, items = items, onPinnedDragAttempt = { hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress) - showPinnedToast = true + NuvioToastController.show("Remove pin to top from collection to move") }, ) }