Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 2 additions & 2 deletions app/src/main/java/com/london/app/navigation/NavHostGraph.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -52,7 +52,7 @@ fun NavHostGraph() {
)
}
},
snackBar = { data: SnackBarData -> CustomSnackBarUI(data = data) }
snackBar = { data: SnackBarData -> DefaultSnackBar(data = data) }
) { innerPadding ->
CompositionLocalProvider(LocalNavController provides navController) {
NavHost(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<SnackBarController> {
error("No SnackbarController provided.")
val LocalSnackbarController = compositionLocalOf<SnackBarController> {
error("No SnackBarController provided")
}

@Composable
fun rememberSnackBarController(): SnackBarController = LocalSnackbarController.current
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<SnackBarData?>(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
)
}

Expand All @@ -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(
Expand All @@ -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)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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))
)
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
@@ -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<SnackBarData>()
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()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,22 @@ 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
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(
Expand Down Expand Up @@ -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,
)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
)
}
}
}
Loading
Loading