diff --git a/app/src/main/java/com/london/app/navigation/NavHostGraph.kt b/app/src/main/java/com/london/app/navigation/NavHostGraph.kt index 022fca3ce..baec5fd8f 100644 --- a/app/src/main/java/com/london/app/navigation/NavHostGraph.kt +++ b/app/src/main/java/com/london/app/navigation/NavHostGraph.kt @@ -17,7 +17,7 @@ import com.london.app.navigation.graph.mainNavGraph import com.london.app.navigation.graph.onboardingNavGraph import com.london.app.navigation.graph.splashNavGraph import com.london.designsystem.component.NavBar -import com.london.designsystem.snackbar.CustomSnackBarUI +import com.london.designsystem.snackbar.DefaultSnackBar import com.london.designsystem.snackbar.ScaffoldWithSnackBar import com.london.designsystem.snackbar.SnackBarData import com.london.designsystem.theme.NovixTheme @@ -52,7 +52,7 @@ fun NavHostGraph() { ) } }, - snackBar = { data: SnackBarData -> CustomSnackBarUI(data = data) } + snackBar = { data: SnackBarData -> DefaultSnackBar(data = data) } ) { innerPadding -> CompositionLocalProvider(LocalNavController provides navController) { NavHost( diff --git a/designSystem/src/main/java/com/london/designsystem/component/AppNavBar.kt b/designSystem/src/main/java/com/london/designsystem/component/AppNavBar.kt index ca41f2335..a1086a099 100644 --- a/designSystem/src/main/java/com/london/designsystem/component/AppNavBar.kt +++ b/designSystem/src/main/java/com/london/designsystem/component/AppNavBar.kt @@ -39,7 +39,7 @@ import androidx.compose.ui.draw.blur import androidx.compose.ui.draw.scale import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.navigation.NavBackStackEntry @@ -146,9 +146,7 @@ private fun MovingDotIndicator( itemCount: Int, selectedIconColor: Color ) { - val screenWidth = LocalDensity.current.run { - androidx.compose.ui.platform.LocalConfiguration.current.screenWidthDp.dp - } + val screenWidth = LocalConfiguration.current.screenWidthDp.dp val totalItemsWidth = NavBarDimens.itemWidth * itemCount val remainingSpace = screenWidth - totalItemsWidth @@ -297,7 +295,6 @@ private fun AnimatedNavIcon( @Composable private fun NavBarPreview() { data class MockDestination(val name: String) - var currentSelectedDestination by remember { mutableStateOf(MockDestination("home")) } NovixTheme { diff --git a/designSystem/src/main/java/com/london/designsystem/snackbar/LocalSnackbarController.kt b/designSystem/src/main/java/com/london/designsystem/snackbar/LocalSnackbarController.kt index 29bf79e6e..27b7b2e0f 100644 --- a/designSystem/src/main/java/com/london/designsystem/snackbar/LocalSnackbarController.kt +++ b/designSystem/src/main/java/com/london/designsystem/snackbar/LocalSnackbarController.kt @@ -1,7 +1,11 @@ package com.london.designsystem.snackbar -import androidx.compose.runtime.staticCompositionLocalOf +import androidx.compose.runtime.Composable +import androidx.compose.runtime.compositionLocalOf -val LocalSnackbarController = staticCompositionLocalOf { - error("No SnackbarController provided.") +val LocalSnackbarController = compositionLocalOf { + error("No SnackBarController provided") } + +@Composable +fun rememberSnackBarController(): SnackBarController = LocalSnackbarController.current diff --git a/designSystem/src/main/java/com/london/designsystem/snackbar/ScaffoldWithSnackBar.kt b/designSystem/src/main/java/com/london/designsystem/snackbar/ScaffoldWithSnackBar.kt index 03854515c..b1cbb59f4 100644 --- a/designSystem/src/main/java/com/london/designsystem/snackbar/ScaffoldWithSnackBar.kt +++ b/designSystem/src/main/java/com/london/designsystem/snackbar/ScaffoldWithSnackBar.kt @@ -1,10 +1,6 @@ package com.london.designsystem.snackbar -import androidx.annotation.DrawableRes import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.tween -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize @@ -16,58 +12,25 @@ import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import com.london.designsystem.R +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.london.designsystem.component.Scaffold import com.london.designsystem.component.SnackBar -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch - -data class SnackBarData( - val message: String, - @DrawableRes val icon: Int?, - val snackBarType: SnackBarType, -) - -private class SnackBarManager( - private val coroutineScope: CoroutineScope -) : SnackBarController { - val snackBarData = mutableStateOf(null) - private var job: Job? = null - - override fun showMessage( - message: String, - icon: Int?, - snackBarType: SnackBarType, - onComplete: () -> Unit - ) { - job?.cancel() - job = coroutineScope.launch { - snackBarData.value = SnackBarData(message, icon, snackBarType) - delay(3000L) - onComplete() - snackBarData.value = null - } - } -} +import com.london.designsystem.utils.painter @Composable -fun CustomSnackBarUI(data: SnackBarData) { +fun DefaultSnackBar(data: SnackBarData) { SnackBar( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp) .offset(y = 56.dp), title = data.message, - icon = painterResource(data.icon ?: R.drawable.ic_failed) + icon = data.snackBarType.getDefaultIcon().painter ) } @@ -78,13 +41,14 @@ fun ScaffoldWithSnackBar( bottomBar: @Composable () -> Unit = {}, floatingActionButton: @Composable () -> Unit = {}, containerColor: Color = MaterialTheme.colorScheme.background, + animationConfig: SnackBarAnimationConfig = SnackBarAnimationConfig(), contentColor: Color = contentColorFor(containerColor), snackBar: @Composable (data: SnackBarData) -> Unit, content: @Composable (PaddingValues) -> Unit ) { val coroutineScope = rememberCoroutineScope() - val snackBarManager = remember(coroutineScope) { SnackBarManager(coroutineScope) } - val currentSnackbarData by snackBarManager.snackBarData + val snackBarManager = remember(coroutineScope) { SnackBarControllerImpl(coroutineScope) } + val currentSnackbarData by snackBarManager.state.collectAsStateWithLifecycle() CompositionLocalProvider(LocalSnackbarController provides snackBarManager) { Scaffold( @@ -99,17 +63,13 @@ fun ScaffoldWithSnackBar( content(innerPadding) AnimatedVisibility( - visible = currentSnackbarData != null, - enter = slideInVertically( - initialOffsetY = { -it }, - animationSpec = tween() - ), - exit = slideOutVertically( - targetOffsetY = { -it }, - animationSpec = tween() - ) + visible = currentSnackbarData.isVisible, + enter = animationConfig.enterAnimation, + exit = animationConfig.exitAnimation ) { - currentSnackbarData?.let { snackBar(it) } + currentSnackbarData.data?.let { data -> + snackBar(data) + } } } } diff --git a/designSystem/src/main/java/com/london/designsystem/snackbar/SnackBarAnimationConfig.kt b/designSystem/src/main/java/com/london/designsystem/snackbar/SnackBarAnimationConfig.kt new file mode 100644 index 000000000..d46b808cc --- /dev/null +++ b/designSystem/src/main/java/com/london/designsystem/snackbar/SnackBarAnimationConfig.kt @@ -0,0 +1,15 @@ +package com.london.designsystem.snackbar + +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.core.tween +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically + +data class SnackBarAnimationConfig( + val durationMillis: Int = 300, + val enterAnimation: EnterTransition = + slideInVertically(initialOffsetY = { -it }, animationSpec = tween(durationMillis)), + val exitAnimation: ExitTransition = + slideOutVertically(targetOffsetY = { -2 * it }, animationSpec = tween(durationMillis)) +) diff --git a/designSystem/src/main/java/com/london/designsystem/snackbar/SnackBarController.kt b/designSystem/src/main/java/com/london/designsystem/snackbar/SnackBarController.kt index e10026db5..27d474cca 100644 --- a/designSystem/src/main/java/com/london/designsystem/snackbar/SnackBarController.kt +++ b/designSystem/src/main/java/com/london/designsystem/snackbar/SnackBarController.kt @@ -1,15 +1,42 @@ package com.london.designsystem.snackbar +import androidx.annotation.DrawableRes +import com.london.designsystem.R + interface SnackBarController { - fun showMessage( - message: String, - icon: Int?, - snackBarType: SnackBarType, - onComplete: () -> Unit - ) + fun showSnackBar(snackBarData: SnackBarData) } +data class SnackBarData( + val message: String, + val snackBarType: SnackBarType, + val snackbarDuration: SnackbarDuration = SnackbarDuration.Medium, + val onComplete: () -> Unit = {}, +) + enum class SnackBarType { Success, Error; + + @DrawableRes + fun getDefaultIcon(): Int = when (this) { + Success -> R.drawable.ic_success + Error -> R.drawable.ic_failed + } +} + +enum class SnackbarDuration { + Brief, + Medium, + Extensive, + Indefinite; + + fun toMillis(): Long { + return when (this) { + Brief -> 1500 + Medium -> 3000 + Extensive -> 5000 + Indefinite -> Long.MAX_VALUE + } + } } diff --git a/designSystem/src/main/java/com/london/designsystem/snackbar/SnackBarControllerImpl.kt b/designSystem/src/main/java/com/london/designsystem/snackbar/SnackBarControllerImpl.kt new file mode 100644 index 000000000..32c2c8b87 --- /dev/null +++ b/designSystem/src/main/java/com/london/designsystem/snackbar/SnackBarControllerImpl.kt @@ -0,0 +1,56 @@ +package com.london.designsystem.snackbar + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +data class SnackBarState( + val data: SnackBarData? = null, + val isVisible: Boolean = false +) + +class SnackBarControllerImpl( + private val coroutineScope: CoroutineScope, + private val animationConfig: SnackBarAnimationConfig = SnackBarAnimationConfig() +) : SnackBarController { + private val _state = MutableStateFlow(SnackBarState()) + val state = _state.asStateFlow() + + private var currentJob: Job? = null + private val messageQueue = ArrayDeque() + private val queueMutex = Mutex() + + override fun showSnackBar(snackBarData: SnackBarData) { + coroutineScope.launch { + queueMutex.withLock { + messageQueue.add(snackBarData) + } + + if (currentJob?.isActive != true) { + processNextMessage() + } + } + } + + private fun processNextMessage() { + currentJob = coroutineScope.launch { + val nextMessage = queueMutex.withLock { + messageQueue.removeFirstOrNull() + } ?: return@launch + + _state.value = SnackBarState(data = nextMessage, isVisible = true) + delay(nextMessage.snackbarDuration.toMillis()) + _state.value = _state.value.copy(isVisible = false) + delay(animationConfig.durationMillis.toLong()) + _state.value = SnackBarState() + + nextMessage.onComplete() + processNextMessage() + } + } +} diff --git a/presentation/src/main/java/com/london/presentation/feature/account/rating/MyRatingScreen.kt b/presentation/src/main/java/com/london/presentation/feature/account/rating/MyRatingScreen.kt index e524cf527..28d796d5b 100644 --- a/presentation/src/main/java/com/london/presentation/feature/account/rating/MyRatingScreen.kt +++ b/presentation/src/main/java/com/london/presentation/feature/account/rating/MyRatingScreen.kt @@ -20,13 +20,15 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.london.designsystem.component.NovixChip import com.london.designsystem.component.TopBar +import com.london.designsystem.snackbar.SnackBarData +import com.london.designsystem.snackbar.SnackBarType +import com.london.designsystem.snackbar.rememberSnackBarController import com.london.designsystem.theme.NovixTheme import com.london.designsystem.theme.ThemePreviews import com.london.designsystem.utils.string import com.london.domain.entity.shared.MediaType import com.london.presentation.R import com.london.presentation.shared.BackgroundGradient -import com.london.presentation.shared.SnackBarAnimation import com.london.presentation.shared.base.ErrorState import com.london.presentation.shared.buildscreen.BuildScreen import com.london.presentation.shared.container.MediaLazyVerticalGrid @@ -34,7 +36,6 @@ import com.london.presentation.shared.layout.EmptyGenreLayout import com.london.presentation.utils.Listen import com.london.presentation.utils.detailsTopBar import com.london.presentation.utils.toLocalizedNumbers -import com.london.designsystem.R as dsR @Composable fun MyRatingScreen( @@ -145,12 +146,21 @@ private fun Content( private fun RatingSnackBar(state: MyRatingUiState) { if (!state.isSnackBarVisible) return + val snackBarController = rememberSnackBarController() + if (state.errorState is ErrorState.RequestFailed) { - SnackBarAnimation(message = R.string.rating_delete_fail.string) + snackBarController.showSnackBar( + SnackBarData( + message = R.string.rating_delete_fail.string, + snackBarType = SnackBarType.Error, + ) + ) } else { - SnackBarAnimation( - message = R.string.rating_delete_success.string, - icon = dsR.drawable.ic_success + snackBarController.showSnackBar( + SnackBarData( + message = R.string.rating_delete_success.string, + snackBarType = SnackBarType.Success, + ) ) } } diff --git a/presentation/src/main/java/com/london/presentation/feature/authentication/login/LoginScreen.kt b/presentation/src/main/java/com/london/presentation/feature/authentication/login/LoginScreen.kt index 1d2e82c00..fca756035 100644 --- a/presentation/src/main/java/com/london/presentation/feature/authentication/login/LoginScreen.kt +++ b/presentation/src/main/java/com/london/presentation/feature/authentication/login/LoginScreen.kt @@ -36,10 +36,13 @@ import com.london.designsystem.component.OutlinedTextField import com.london.designsystem.component.Text import com.london.designsystem.component.TopBar import com.london.designsystem.component.button.PrimaryButton +import com.london.designsystem.snackbar.SnackBarData +import com.london.designsystem.snackbar.SnackBarType +import com.london.designsystem.snackbar.rememberSnackBarController import com.london.designsystem.theme.NovixTheme +import com.london.designsystem.utils.string import com.london.presentation.R import com.london.presentation.shared.BackgroundGradient -import com.london.presentation.shared.SnackBarAnimation import com.london.presentation.shared.base.ErrorState import com.london.presentation.utils.Listen import com.london.designsystem.R as dsR @@ -78,6 +81,7 @@ private fun Content( val interactionSourcePassword = remember { MutableInteractionSource() } val isLoadingGeneral = uiState.isLoading || uiState.isGuestLoginLoading val scrollState = rememberScrollState() + val snackBarController = rememberSnackBarController() Box( modifier = Modifier @@ -203,9 +207,14 @@ private fun Content( }) } } + if (uiState.error is ErrorState.RequestFailed) { - val message = uiState.error.message - SnackBarAnimation(message) + snackBarController.showSnackBar( + SnackBarData( + message = R.string.login_failed.string, + snackBarType = SnackBarType.Error, + ) + ) } } } diff --git a/presentation/src/main/java/com/london/presentation/feature/authentication/login/LoginViewModel.kt b/presentation/src/main/java/com/london/presentation/feature/authentication/login/LoginViewModel.kt index e54d3315b..aafcce0ef 100644 --- a/presentation/src/main/java/com/london/presentation/feature/authentication/login/LoginViewModel.kt +++ b/presentation/src/main/java/com/london/presentation/feature/authentication/login/LoginViewModel.kt @@ -1,9 +1,7 @@ package com.london.presentation.feature.authentication.login -import android.app.Application import androidx.compose.ui.text.input.TextFieldValue import com.london.domain.usecase.authentication.AuthenticationUseCase -import com.london.presentation.R import com.london.presentation.shared.base.BaseViewModel import com.london.presentation.shared.base.ErrorState import dagger.hilt.android.lifecycle.HiltViewModel @@ -11,7 +9,6 @@ import javax.inject.Inject @HiltViewModel class LoginViewModel @Inject constructor( - private val context: Application, private val authenticationUseCase: AuthenticationUseCase, ) : BaseViewModel(LoginUiState()), LoginContract { @@ -75,11 +72,11 @@ class LoginViewModel @Inject constructor( } private fun handleLoginAsGuestError() { - updateState { copy(error = ErrorState.RequestFailed(context.getString(R.string.guest_login_failed))) } + updateState { copy(error = ErrorState.RequestFailed()) } } private fun handleLoginError() { - updateState { copy(error = ErrorState.RequestFailed(context.getString(R.string.login_failed))) } + updateState { copy(error = ErrorState.RequestFailed()) } } private fun checkLoginAsGuest(isSuccess: Boolean) { diff --git a/presentation/src/main/java/com/london/presentation/feature/details/movie/MovieDetailsScreen.kt b/presentation/src/main/java/com/london/presentation/feature/details/movie/MovieDetailsScreen.kt index c9fdadce0..e8f76a237 100644 --- a/presentation/src/main/java/com/london/presentation/feature/details/movie/MovieDetailsScreen.kt +++ b/presentation/src/main/java/com/london/presentation/feature/details/movie/MovieDetailsScreen.kt @@ -56,8 +56,12 @@ import com.london.designsystem.component.GuestUserLoginBottomSheet import com.london.designsystem.component.RatingBottomSheet import com.london.designsystem.component.Text import com.london.designsystem.component.TopBar +import com.london.designsystem.snackbar.SnackBarData +import com.london.designsystem.snackbar.SnackBarType +import com.london.designsystem.snackbar.rememberSnackBarController import com.london.designsystem.theme.NovixTheme import com.london.designsystem.theme.noRippleClickable +import com.london.designsystem.utils.string import com.london.domain.entity.shared.MediaType import com.london.presentation.R.drawable import com.london.presentation.R.string.more_like_this @@ -66,7 +70,6 @@ import com.london.presentation.R.string.view_reviews import com.london.presentation.shared.BackgroundGradient import com.london.presentation.shared.CustomBackDropImagePager import com.london.presentation.shared.HomeCard -import com.london.presentation.shared.SnackBarAnimation import com.london.presentation.shared.bookmarkSheet.BookmarkBottomSheet import com.london.presentation.shared.buildscreen.BuildScreen import com.london.presentation.shared.genre.MovieGenreUi @@ -133,6 +136,7 @@ private fun Content( val screenWidthDp = with(LocalDensity.current) { LocalWindowInfo.current.containerSize.width.toDp() } val uriHandler = LocalUriHandler.current + val snackBarController = rememberSnackBarController() val lazyState = rememberLazyGridState() var footerHeight by remember { mutableStateOf(0.dp) } @@ -205,14 +209,18 @@ private fun Content( uiState.isSuccessfullyRated?.let { isSuccessful -> if (isSuccessful) { - SnackBarAnimation( - message = stringResource(R.string.rated_successfully), - icon = R.drawable.ic_success, + snackBarController.showSnackBar( + SnackBarData( + message = R.string.rated_successfully.string, + snackBarType = SnackBarType.Success, + ) ) } else { - SnackBarAnimation( - message = stringResource(R.string.rated_fail), - icon = R.drawable.ic_failed + snackBarController.showSnackBar( + SnackBarData( + message = R.string.rated_fail.string, + snackBarType = SnackBarType.Error, + ) ) } } diff --git a/presentation/src/main/java/com/london/presentation/feature/details/tvshow/episode/EpisodeDetailScreen.kt b/presentation/src/main/java/com/london/presentation/feature/details/tvshow/episode/EpisodeDetailScreen.kt index a71b0544d..47bc76ca7 100644 --- a/presentation/src/main/java/com/london/presentation/feature/details/tvshow/episode/EpisodeDetailScreen.kt +++ b/presentation/src/main/java/com/london/presentation/feature/details/tvshow/episode/EpisodeDetailScreen.kt @@ -42,11 +42,14 @@ import com.london.designsystem.component.GuestUserLoginBottomSheet import com.london.designsystem.component.RatingBottomSheet import com.london.designsystem.component.Text import com.london.designsystem.component.TopBar +import com.london.designsystem.snackbar.SnackBarData +import com.london.designsystem.snackbar.SnackBarType +import com.london.designsystem.snackbar.rememberSnackBarController import com.london.designsystem.theme.NovixTheme +import com.london.designsystem.utils.string import com.london.presentation.R import com.london.presentation.shared.BackgroundGradient import com.london.presentation.shared.CustomBackDropImagePager -import com.london.presentation.shared.SnackBarAnimation import com.london.presentation.shared.base.ErrorState import com.london.presentation.shared.buildscreen.BuildScreen import com.london.presentation.shared.item.ActorItem @@ -106,6 +109,7 @@ private fun Content( ) { val uriHandler = LocalUriHandler.current val lazyListState = rememberLazyListState() + val snackBarController = rememberSnackBarController() val shouldShowBackground by remember { derivedStateOf { lazyListState.firstVisibleItemScrollOffset > 40f || @@ -210,14 +214,18 @@ private fun Content( uiState.isSuccessfullyRated?.let { isRateAddedSuccessfully -> if (isRateAddedSuccessfully) { - SnackBarAnimation( - message = stringResource(Res.string.rated_successfully), - icon = Res.drawable.ic_success + snackBarController.showSnackBar( + SnackBarData( + message = Res.string.rated_successfully.string, + snackBarType = SnackBarType.Success, + ) ) } else { - SnackBarAnimation( - message = stringResource(Res.string.rated_fail), - icon = Res.drawable.ic_failed + snackBarController.showSnackBar( + SnackBarData( + message = Res.string.rated_fail.string, + snackBarType = SnackBarType.Error, + ) ) } } diff --git a/presentation/src/main/java/com/london/presentation/feature/details/tvshow/info/TvShowDetailScreen.kt b/presentation/src/main/java/com/london/presentation/feature/details/tvshow/info/TvShowDetailScreen.kt index 95657dd34..63cd8e62f 100644 --- a/presentation/src/main/java/com/london/presentation/feature/details/tvshow/info/TvShowDetailScreen.kt +++ b/presentation/src/main/java/com/london/presentation/feature/details/tvshow/info/TvShowDetailScreen.kt @@ -57,14 +57,17 @@ import com.london.designsystem.component.Text import com.london.designsystem.component.TopBar import com.london.designsystem.component.UnSuitableEye import com.london.designsystem.component.button.ErrorImage +import com.london.designsystem.snackbar.SnackBarData +import com.london.designsystem.snackbar.SnackBarType +import com.london.designsystem.snackbar.rememberSnackBarController import com.london.designsystem.theme.NovixTheme import com.london.designsystem.theme.noRippleClickable +import com.london.designsystem.utils.string import com.london.domain.entity.shared.MediaType import com.london.domain.entity.tvshow.cast.TvShowCastMember import com.london.domain.entity.tvshow.episode.Episodes import com.london.presentation.shared.BackgroundGradient import com.london.presentation.shared.CustomBackDropImagePager -import com.london.presentation.shared.SnackBarAnimation import com.london.presentation.shared.base.ErrorState import com.london.presentation.shared.buildscreen.BuildScreen import com.london.presentation.shared.genre.TvShowGenreUi @@ -143,6 +146,7 @@ private fun Content( val lazyListState = rememberLazyListState() var footerHeight by remember { mutableStateOf(0.dp) } val density = LocalDensity.current + val snackBarController = rememberSnackBarController() val shouldShowBackground by remember { derivedStateOf { @@ -324,14 +328,18 @@ private fun Content( uiState.isSuccessfullyRated?.let { isSuccessful -> if (isSuccessful) { - SnackBarAnimation( - message = stringResource(R.string.rated_successfully), - icon = R.drawable.ic_success, + snackBarController.showSnackBar( + SnackBarData( + message = R.string.rated_successfully.string, + snackBarType = SnackBarType.Error, + ) ) } else { - SnackBarAnimation( - message = stringResource(R.string.rated_fail), - icon = R.drawable.ic_failed + snackBarController.showSnackBar( + SnackBarData( + message = R.string.rated_fail.string, + snackBarType = SnackBarType.Error, + ) ) } } diff --git a/presentation/src/main/java/com/london/presentation/feature/list/savedlist/ListScreen.kt b/presentation/src/main/java/com/london/presentation/feature/list/savedlist/ListScreen.kt index e064b74b4..aeb9dd9fb 100644 --- a/presentation/src/main/java/com/london/presentation/feature/list/savedlist/ListScreen.kt +++ b/presentation/src/main/java/com/london/presentation/feature/list/savedlist/ListScreen.kt @@ -42,8 +42,9 @@ import com.london.designsystem.component.Text import com.london.designsystem.component.TopBar import com.london.designsystem.component.button.FloatingActionButton import com.london.designsystem.component.button.OutlineButton -import com.london.designsystem.snackbar.LocalSnackbarController +import com.london.designsystem.snackbar.SnackBarData import com.london.designsystem.snackbar.SnackBarType +import com.london.designsystem.snackbar.rememberSnackBarController import com.london.designsystem.theme.NovixTheme import com.london.designsystem.theme.ThemePreviews import com.london.designsystem.utils.painter @@ -86,6 +87,7 @@ private fun Content( contract: ListContract, ) { val pagingItems = state.items.collectAsLazyPagingItems() + val snackBarController = rememberSnackBarController() Box( modifier = Modifier @@ -144,23 +146,23 @@ private fun Content( addListSheetState = state.addListSheetState ) - val snackBarController = LocalSnackbarController.current - if (state.error != null) { - snackBarController.showMessage( - message = R.string.list_add_fail.string, - snackBarType = SnackBarType.Error, - onComplete = contract::resetSnackBarErrorState, - icon = null, + snackBarController.showSnackBar( + SnackBarData( + message = R.string.list_add_fail.string, + snackBarType = SnackBarType.Error, + onComplete = contract::resetSnackBarErrorState, + ) ) } if (state.isSnackBarSuccessVisible) { - snackBarController.showMessage( - message = R.string.list_add_success.string, - snackBarType = SnackBarType.Success, - onComplete = contract::resetSnackBarSuccessState, - icon = com.london.designsystem.R.drawable.ic_success, + snackBarController.showSnackBar( + SnackBarData( + message = R.string.list_add_success.string, + snackBarType = SnackBarType.Success, + onComplete = contract::resetSnackBarSuccessState, + ) ) } } diff --git a/presentation/src/main/java/com/london/presentation/feature/list/viewitems/ViewItemsScreen.kt b/presentation/src/main/java/com/london/presentation/feature/list/viewitems/ViewItemsScreen.kt index 7d240ce56..5ebb9e9e7 100644 --- a/presentation/src/main/java/com/london/presentation/feature/list/viewitems/ViewItemsScreen.kt +++ b/presentation/src/main/java/com/london/presentation/feature/list/viewitems/ViewItemsScreen.kt @@ -15,8 +15,9 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.paging.compose.collectAsLazyPagingItems import com.london.designsystem.component.TopBar -import com.london.designsystem.snackbar.LocalSnackbarController +import com.london.designsystem.snackbar.SnackBarData import com.london.designsystem.snackbar.SnackBarType +import com.london.designsystem.snackbar.rememberSnackBarController import com.london.designsystem.theme.NovixTheme import com.london.designsystem.theme.ThemePreviews import com.london.designsystem.utils.string @@ -57,6 +58,7 @@ private fun Content( contract: ViewListItemsContract, ) { val listItems = state.listItems.collectAsLazyPagingItems() + val snackBarController = rememberSnackBarController() Box( modifier = Modifier.fillMaxSize() @@ -108,32 +110,34 @@ private fun Content( contract = contract, ) - val snackBarController = LocalSnackbarController.current if (state.isSnackBarErrorVisible) { - snackBarController.showMessage( - message = R.string.item_delete_fail.string, - snackBarType = SnackBarType.Error, - onComplete = contract::resetSnackBarErrorState, - icon = null, + snackBarController.showSnackBar( + SnackBarData( + message = R.string.item_delete_fail.string, + snackBarType = SnackBarType.Error, + onComplete = contract::resetSnackBarErrorState, + ) ) } if (state.isMovieSnackBarSuccessVisible) { - snackBarController.showMessage( - message = R.string.item_delete_success.string, - snackBarType = SnackBarType.Success, - onComplete = contract::resetMovieSnackBarSuccessState, - icon = com.london.designsystem.R.drawable.ic_success, + snackBarController.showSnackBar( + SnackBarData( + message = R.string.item_delete_success.string, + snackBarType = SnackBarType.Success, + onComplete = contract::resetMovieSnackBarSuccessState, + ) ) } if (state.isListSnackBarSuccess) { - snackBarController.showMessage( - message = R.string.list_delete_success.string, - snackBarType = SnackBarType.Success, - onComplete = contract::resetListSnackBarSuccessState, - icon = com.london.designsystem.R.drawable.ic_success, + snackBarController.showSnackBar( + SnackBarData( + message = R.string.list_delete_success.string, + snackBarType = SnackBarType.Success, + onComplete = contract::resetListSnackBarSuccessState, + ) ) } } diff --git a/presentation/src/main/java/com/london/presentation/shared/SnackBarAnimation.kt b/presentation/src/main/java/com/london/presentation/shared/SnackBarAnimation.kt deleted file mode 100644 index 5b895b50e..000000000 --- a/presentation/src/main/java/com/london/presentation/shared/SnackBarAnimation.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.london.presentation.shared - -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.tween -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.london.designsystem.R -import com.london.designsystem.component.SnackBar -import kotlinx.coroutines.delay - -@Composable -fun SnackBarAnimation(message: String?, icon: Int? = null) { - var isVisible by remember { mutableStateOf(false) } - LaunchedEffect(message) { - isVisible = true - delay(3000) - isVisible = false - } - AnimatedVisibility( - visible = isVisible, - enter = slideInVertically( - initialOffsetY = { -it }, - animationSpec = tween(durationMillis = 300) - ), - exit = slideOutVertically( - targetOffsetY = { -it }, - animationSpec = tween(durationMillis = 300) - ) - ) { - SnackBar( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .offset(y = 56.dp), - title = message ?: stringResource(R.string.incorrect_password), - icon = painterResource(icon ?: R.drawable.ic_failed) - ) - } -} diff --git a/presentation/src/main/java/com/london/presentation/shared/bookmarkSheet/BookmarkSheet.kt b/presentation/src/main/java/com/london/presentation/shared/bookmarkSheet/BookmarkSheet.kt index 47c3e2327..d160d3378 100644 --- a/presentation/src/main/java/com/london/presentation/shared/bookmarkSheet/BookmarkSheet.kt +++ b/presentation/src/main/java/com/london/presentation/shared/bookmarkSheet/BookmarkSheet.kt @@ -40,8 +40,9 @@ import com.london.designsystem.component.Text import com.london.designsystem.component.button.OutlineButton import com.london.designsystem.component.button.PrimaryButton import com.london.designsystem.component.rememberModalBottomSheetState -import com.london.designsystem.snackbar.LocalSnackbarController +import com.london.designsystem.snackbar.SnackBarData import com.london.designsystem.snackbar.SnackBarType +import com.london.designsystem.snackbar.rememberSnackBarController import com.london.designsystem.theme.NovixTheme import com.london.designsystem.utils.painter import com.london.designsystem.utils.string @@ -139,6 +140,8 @@ private fun BookmarkBottomSheetContent( bookmarkedMovieId: Int, isContentReady: Boolean ) { + val snackBarController = rememberSnackBarController() + Column( modifier = modifier .padding(start = 16.dp, end = 16.dp, bottom = 24.dp) @@ -167,23 +170,23 @@ private fun BookmarkBottomSheetContent( } } - val snackBarController = LocalSnackbarController.current - if (state.isSuccessSnackBarVisible) { - snackBarController.showMessage( - message = R.string.item_add_success.string, - icon = com.london.designsystem.R.drawable.ic_success, - snackBarType = SnackBarType.Success, - onComplete = contract::onSnackBarShown + snackBarController.showSnackBar( + SnackBarData( + message = R.string.item_add_success.string, + snackBarType = SnackBarType.Success, + onComplete = contract::onSnackBarShown + ) ) } if (state.isErrorSnackBarVisible) { - snackBarController.showMessage( - message = R.string.item_add_fail.string, - icon = com.london.designsystem.R.drawable.ic_failed, - snackBarType = SnackBarType.Error, - onComplete = contract::onSnackBarShown + snackBarController.showSnackBar( + SnackBarData( + message = R.string.item_add_fail.string, + snackBarType = SnackBarType.Error, + onComplete = contract::onSnackBarShown + ) ) } } diff --git a/presentation/src/test/java/com/london/presentation/feature/authentication/login/LoginViewModelTest.kt b/presentation/src/test/java/com/london/presentation/feature/authentication/login/LoginViewModelTest.kt index 1e57f477a..b4b067220 100644 --- a/presentation/src/test/java/com/london/presentation/feature/authentication/login/LoginViewModelTest.kt +++ b/presentation/src/test/java/com/london/presentation/feature/authentication/login/LoginViewModelTest.kt @@ -23,17 +23,13 @@ class LoginViewModelTest { private val testDispatcher = StandardTestDispatcher() private lateinit var authenticationUseCase: AuthenticationUseCase - private lateinit var context: Application private lateinit var viewModel: LoginViewModel @Before fun setup() { Dispatchers.setMain(testDispatcher) authenticationUseCase = mockk() - context = mockk() - every { context.getString(R.string.login_failed) } returns "Login failed" - every { context.getString(R.string.guest_login_failed) } returns "Guest login failed" - viewModel = LoginViewModel(context, authenticationUseCase) + viewModel = LoginViewModel(authenticationUseCase) } @After @@ -113,4 +109,4 @@ class LoginViewModelTest { private companion object { const val FORGOT_PASSWORD_URL = "https://www.themoviedb.org/reset-password" } -} \ No newline at end of file +}