Skip to content

Commit

Permalink
Add snackbar controller
Browse files Browse the repository at this point in the history
  • Loading branch information
joaomanaia committed Sep 22, 2024
1 parent f4a3401 commit 375cadb
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
Expand Down Expand Up @@ -47,6 +49,7 @@ internal fun CompactContainer(
selectedItem: NavigationItem.Item?,
userDiamonds: UInt = 0u,
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
snackbarHostState: SnackbarHostState,
content: @Composable (PaddingValues) -> Unit
) {
val scope = rememberCoroutineScope()
Expand Down Expand Up @@ -76,6 +79,9 @@ internal fun CompactContainer(
) {
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
topBar = {
CenterAlignedTopAppBar(
title = {
Expand Down Expand Up @@ -168,7 +174,8 @@ private fun CompactContainerPreview() {
primaryItems = getPrimaryItems(),
otherItems = otherItems,
selectedItem = selectedItem,
userDiamonds = 100u
userDiamonds = 100u,
snackbarHostState = SnackbarHostState()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.PermanentNavigationDrawer
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
Expand Down Expand Up @@ -37,6 +39,7 @@ internal fun ExpandedContainer(
otherItems: ImmutableList<NavigationItem>,
selectedItem: NavigationItem.Item?,
userDiamonds: UInt = 0u,
snackbarHostState: SnackbarHostState,
content: @Composable (PaddingValues) -> Unit
) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(
Expand Down Expand Up @@ -66,6 +69,9 @@ internal fun ExpandedContainer(
) {
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
topBar = {
CenterAlignedTopAppBar(
title = {
Expand Down Expand Up @@ -115,7 +121,8 @@ private fun MediumContainerPreview() {
primaryItems = getPrimaryItems(),
otherItems = otherItems,
selectedItem = selectedItem,
userDiamonds = 100u
userDiamonds = 100u,
snackbarHostState = SnackbarHostState()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.NavigationRail
import androidx.compose.material3.NavigationRailItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
Expand Down Expand Up @@ -48,6 +50,7 @@ internal fun MediumContainer(
selectedItem: NavigationItem.Item?,
userDiamonds: UInt = 0u,
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
snackbarHostState: SnackbarHostState,
content: @Composable (PaddingValues) -> Unit
) {
val scope = rememberCoroutineScope()
Expand Down Expand Up @@ -106,6 +109,9 @@ internal fun MediumContainer(

Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
},
topBar = {
CenterAlignedTopAppBar(
scrollBehavior = scrollBehavior,
Expand Down Expand Up @@ -156,6 +162,7 @@ private fun MediumContainerPreview() {
primaryItems = getPrimaryItems(),
otherItems = otherItems,
selectedItem = selectedItem,
snackbarHostState = SnackbarHostState()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,33 @@ import androidx.compose.material.icons.rounded.Today
import androidx.compose.material.icons.rounded.ViewModule
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.navigation.NavController
import com.infinitepower.newquiz.comparison_quiz.destinations.ComparisonQuizListScreenDestination
import com.infinitepower.newquiz.core.R
import com.infinitepower.newquiz.core.navigation.NavDrawerBadgeItem
import com.infinitepower.newquiz.core.navigation.NavigationItem
import com.infinitepower.newquiz.core.navigation.ScreenType
import com.infinitepower.newquiz.core.ui.ObserveAsEvents
import com.infinitepower.newquiz.core.ui.SnackbarController
import com.infinitepower.newquiz.feature.daily_challenge.destinations.DailyChallengeScreenDestination
import com.infinitepower.newquiz.feature.settings.destinations.SettingsScreenDestination
import com.infinitepower.newquiz.feature.maze.destinations.MazeScreenDestination
import com.infinitepower.newquiz.feature.profile.destinations.ProfileScreenDestination
import com.infinitepower.newquiz.feature.settings.destinations.SettingsScreenDestination
import com.infinitepower.newquiz.multi_choice_quiz.destinations.MultiChoiceQuizListScreenDestination
import com.infinitepower.newquiz.wordle.destinations.WordleListScreenDestination
import com.ramcosta.composedestinations.spec.DestinationSpec
import com.ramcosta.composedestinations.utils.currentDestinationAsState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.launch

internal fun getPrimaryItems(): ImmutableList<NavigationItem.Item> = persistentListOf(
NavigationItem.Item(
Expand Down Expand Up @@ -108,6 +115,8 @@ internal fun NavigationContainer(
userDiamonds: UInt,
content: @Composable (PaddingValues) -> Unit
) {
val scope = rememberCoroutineScope()

val destination by navController.currentDestinationAsState()

val primaryItems = remember { getPrimaryItems() }
Expand All @@ -125,6 +134,24 @@ internal fun NavigationContainer(
selectedItem != null && selectedItem.screenType == ScreenType.NORMAL
}

val snackbarHostState = remember { SnackbarHostState() }
ObserveAsEvents(flow = SnackbarController.events, snackbarHostState) { event ->
scope.launch {
snackbarHostState.currentSnackbarData?.dismiss()

val result = snackbarHostState.showSnackbar(
message = event.message,
actionLabel = event.action?.name,
withDismissAction = event.withDismissAction,
duration = event.duration
)

if (result == SnackbarResult.ActionPerformed) {
event.action?.action?.invoke()
}
}
}

if (navigationVisible) {
when (windowWidthSize) {
WindowWidthSizeClass.Compact -> CompactContainer(
Expand All @@ -133,6 +160,7 @@ internal fun NavigationContainer(
otherItems = otherItems,
selectedItem = selectedItem,
userDiamonds = userDiamonds,
snackbarHostState = snackbarHostState,
content = content
)

Expand All @@ -142,6 +170,7 @@ internal fun NavigationContainer(
otherItems = otherItems,
selectedItem = selectedItem,
userDiamonds = userDiamonds,
snackbarHostState = snackbarHostState,
content = content
)

Expand All @@ -151,12 +180,16 @@ internal fun NavigationContainer(
otherItems = otherItems,
selectedItem = selectedItem,
userDiamonds = userDiamonds,
snackbarHostState = snackbarHostState,
content = content
)
}
} else {
Scaffold(
content = content
content = content,
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import androidx.lifecycle.viewModelScope
import androidx.work.WorkManager
import com.infinitepower.newquiz.comparison_quiz.core.workers.ComparisonQuizEndGameWorker
import com.infinitepower.newquiz.core.game.ComparisonQuizCore
import com.infinitepower.newquiz.core.ui.SnackbarController
import com.infinitepower.newquiz.core.ui.SnackbarEvent
import com.infinitepower.newquiz.core.user_services.InsufficientDiamondsException
import com.infinitepower.newquiz.core.user_services.UserService
import com.infinitepower.newquiz.data.worker.UpdateGlobalEventDataWorker
Expand Down Expand Up @@ -130,6 +132,7 @@ class ComparisonQuizViewModel @Inject constructor(
comparisonQuizCore.onAnswerClicked(event.item)
}
}

is ComparisonQuizUiEvent.ShowSkipQuestionDialog -> getUserDiamonds()
is ComparisonQuizUiEvent.DismissSkipQuestionDialog -> {
_uiState.update { currentState ->
Expand All @@ -139,13 +142,16 @@ class ComparisonQuizViewModel @Inject constructor(
)
}
}

is ComparisonQuizUiEvent.SkipQuestion -> {
viewModelScope.launch {
try {
comparisonQuizCore.skip()
} catch (e: InsufficientDiamondsException) {
e.printStackTrace()
// TODO: Show error dialog
SnackbarController.sendEvent(
event = SnackbarEvent(message = "Insufficient diamonds")
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.infinitepower.newquiz.core.ui

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext

@Composable
fun <T> ObserveAsEvents(
flow: Flow<T>,
vararg keys: Any?,
onEvent: (T) -> Unit
) {
val lifecycleOwner = LocalLifecycleOwner.current

LaunchedEffect(lifecycleOwner.lifecycle, *keys, flow) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
withContext(Dispatchers.Main.immediate) {
flow.collect(onEvent)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.infinitepower.newquiz.core.ui

import androidx.compose.material3.SnackbarDuration
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.receiveAsFlow

object SnackbarController {
private val _events = Channel<SnackbarEvent>()
val events = _events.receiveAsFlow()

suspend fun sendEvent(event: SnackbarEvent) {
_events.send(event)
}
}

data class SnackbarEvent(
val message: String,
val action: SnackbarAction? = null,
val withDismissAction: Boolean = false,
val duration: SnackbarDuration = if (action == null) SnackbarDuration.Short else SnackbarDuration.Indefinite
)

data class SnackbarAction(
val name: String,
val action: () -> Unit
)
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import androidx.work.WorkInfo
import androidx.work.WorkManager
import com.infinitepower.newquiz.core.analytics.AnalyticsEvent
import com.infinitepower.newquiz.core.analytics.AnalyticsHelper
import com.infinitepower.newquiz.core.ui.SnackbarController
import com.infinitepower.newquiz.core.ui.SnackbarEvent
import com.infinitepower.newquiz.data.worker.multichoicequiz.DownloadMultiChoiceQuestionsWorker
import com.infinitepower.newquiz.domain.repository.multi_choice_quiz.saved_questions.SavedMultiChoiceQuestionsRepository
import com.infinitepower.newquiz.model.multi_choice_quiz.MultiChoiceQuestion
Expand Down Expand Up @@ -108,6 +110,12 @@ class SavedMultiChoiceQuestionsViewModel @Inject constructor(
workManager
.getWorkInfoByIdFlow(downloadQuestionsRequest.id)
.onEach { info ->
if (info.state == WorkInfo.State.SUCCEEDED) {
SnackbarController.sendEvent(
event = SnackbarEvent(message = "Downloaded successfully")
)
}

_uiState.update {
it.copy(
downloadingQuestions = info.state == WorkInfo.State.RUNNING
Expand Down

0 comments on commit 375cadb

Please sign in to comment.