Skip to content
This repository has been archived by the owner on Aug 5, 2024. It is now read-only.

feat: Refactor Navigation Part 4 - Removing crashes from mutableStates #37

Merged
merged 22 commits into from
Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bf078dc
Fixes modal
hernandazevedozup Oct 25, 2022
52b2a14
Removing shared flow
hernandazevedozup Oct 25, 2022
0877cd3
Removing shared flow
hernandazevedozup Oct 25, 2022
d080b87
upgrading navigation-compose version to fix popToView crash
hernandazevedozup Oct 26, 2022
99dec2f
upgrading navigation-compose version to fix popToView crash
hernandazevedozup Oct 26, 2022
a2bb56f
upgrades gradle wrapper
hernandazevedozup Oct 27, 2022
958c765
Disable jacoco and sonar since its crashing with gradle 7.5.x until f…
hernandazevedozup Oct 27, 2022
4a9f2ed
Simplify navigation
hernandazevedozup Oct 31, 2022
277bd97
Fixes popTo first screen url
hernandazevedozup Oct 31, 2022
17e6ce2
Fixes modal
hernandazevedozup Nov 3, 2022
01accfc
Fixes modal
hernandazevedozup Nov 4, 2022
f20e749
Fixes modal
hernandazevedozup Nov 4, 2022
fa00cd5
Simplify modal
hernandazevedozup Nov 4, 2022
d3eeec5
Simplify modal 2
hernandazevedozup Nov 4, 2022
7db3ff2
Simplify modal 3
hernandazevedozup Nov 4, 2022
6331207
Extract modal state
hernandazevedozup Nov 8, 2022
93b5466
Extract modal state
hernandazevedozup Nov 8, 2022
3241c59
Rafactor json name
hernandazevedozup Nov 8, 2022
029ac66
Mitigation of recomposition
hernandazevedozup Nov 9, 2022
1e19001
Merge remote-tracking branch 'origin/main' into feature/refactor-navi…
hernandazevedozup Nov 10, 2022
db17e9d
Refactor internal
hernandazevedozup Nov 10, 2022
2e48981
Merge remote-tracking branch 'origin/main' into feature/refactor-navi…
hernandazevedozup Nov 11, 2022
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import java.util.UUID

const val SHOW_VIEW = "showView"
const val VIEW_URL = "viewUrl"
const val VIEW_JSON_DESCRIPTION = "json"
const val JSON = "json"
const val SHOW_VIEW_DESTINATION_PARAM = "${SHOW_VIEW}?${VIEW_URL}"
const val SHOW_VIEW_DESTINATION = "$SHOW_VIEW_DESTINATION_PARAM={${VIEW_URL}}"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package br.zup.com.nimbus.compose.internal

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.tween
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
Expand All @@ -18,15 +18,14 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import br.zup.com.nimbus.compose.CoroutineDispatcherLib
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

/**
* Figured out by trial and error
Expand All @@ -40,65 +39,58 @@ private const val DIALOG_BUILD_TIME = 300L
@Composable
internal fun ModalTransitionDialog(
onDismissRequest: () -> Unit,
onCanDismissRequest: () -> Boolean,
modifier: Modifier = Modifier
.fillMaxSize()
.background(Color.White),
dismissOnBackPress: Boolean = true,
modifier: Modifier = Modifier.fillMaxSize(),
modalTransitionDialogHelper: ModalTransitionDialogHelper = ModalTransitionDialogHelper(),
content: @Composable (ModalTransitionDialogHelper) -> Unit,
content: @Composable (ModalTransitionDialogHelper) -> Unit
) {

val onCloseFlow: MutableStateFlow<Boolean> = remember { MutableStateFlow(false) }
val coroutineScope: CoroutineScope = rememberCoroutineScope()
val animateContentBackTrigger = remember { mutableStateOf(false) }

LaunchedEffect(key1 = Unit) {
withContext(CoroutineDispatcherLib.backgroundPool) {
launch {
delay(DIALOG_BUILD_TIME)
animateContentBackTrigger.value = true
}
launch {
onCloseFlow.collectLatest { shouldClose ->
if(shouldClose)
startDismissWithExitAnimation(animateContentBackTrigger, onDismissRequest)
}
launch {
delay(DIALOG_BUILD_TIME)
animateContentBackTrigger.value = true
}
launch {
onCloseFlow.collectLatest { shouldClose ->
if(shouldClose)
startDismissWithExitAnimation(animateContentBackTrigger, onDismissRequest)
}
}
}

Dialog(
onDismissRequest = {
coroutineScope.launch(CoroutineDispatcherLib.backgroundPool) {
startDismissWithExitAnimation(animateContentBackTrigger,
onDismissRequest,
onCanDismissRequest)
coroutineScope.launch {
startDismissWithExitAnimation(animateContentBackTrigger, onDismissRequest)
}
},
properties = DialogProperties(usePlatformDefaultWidth = false,
dismissOnBackPress = dismissOnBackPress,
dismissOnClickOutside = false)
) {
Box(modifier = modifier) {
AnimatedModalBottomSheetTransition(
visible = animateContentBackTrigger.value) {
modalTransitionDialogHelper.coroutineScope = coroutineScope
modalTransitionDialogHelper.onCloseFlow = onCloseFlow
content(modalTransitionDialogHelper)
}
AnimatedModalBottomSheetTransition(
modifier = modifier,
visible = animateContentBackTrigger.value) {
modalTransitionDialogHelper.onCloseFlow = onCloseFlow
modalTransitionDialogHelper.coroutineScope = coroutineScope
content(modalTransitionDialogHelper)
}
}
}

private suspend fun startDismissWithExitAnimation(
animateContentBackTrigger: MutableState<Boolean>,
onDismissRequest: () -> Unit,
onCanDismissRequest: () -> Boolean = { true },
onDismissRequest: () -> Unit
) {
if (onCanDismissRequest()) {
animateContentBackTrigger.value = false
delay(ANIMATION_TIME)
onDismissRequest()
}
animateContentBackTrigger.value = false
delay(ANIMATION_TIME)
onDismissRequest()
}

/**
Expand All @@ -110,27 +102,28 @@ internal class ModalTransitionDialogHelper {
var coroutineScope: CoroutineScope? = null
var onCloseFlow: MutableStateFlow<Boolean>? = null
fun triggerAnimatedClose() {
coroutineScope?.launch(CoroutineDispatcherLib.backgroundPool) {
coroutineScope?.launch {
onCloseFlow?.tryEmit(true)
}
}
}

internal const val ANIMATION_TIME = 500L
internal const val DELAY_SHOW_CONTENT = ANIMATION_TIME + 100L

@OptIn(ExperimentalAnimationApi::class)
@Composable
internal fun AnimatedModalBottomSheetTransition(
visible: Boolean,
content: @Composable AnimatedVisibilityScope.() -> Unit,
modifier: Modifier = Modifier
.fillMaxSize()
.background(Color.White),
content: @Composable AnimatedVisibilityScope.() -> Unit
) {
var animateContentShowTrigger by remember { mutableStateOf(false) }
if (visible) {
LaunchedEffect(key1 = Unit) {
withContext(CoroutineDispatcherLib.backgroundPool) {
delay(ANIMATION_TIME)
animateContentShowTrigger = true
}
delay(DELAY_SHOW_CONTENT)
animateContentShowTrigger = true
}
}
AnimatedVisibility(
Expand All @@ -144,8 +137,11 @@ internal fun AnimatedModalBottomSheetTransition(
targetOffsetY = { fullHeight -> fullHeight }
),
content = {
if (animateContentShowTrigger)
content()
Box(modifier = modifier) {
if (animateContentShowTrigger)
content()
}

}
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,28 @@ package br.zup.com.nimbus.compose.internal

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
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.graphics.Color
import androidx.compose.ui.unit.dp
import com.zup.nimbus.core.network.ViewRequest

@Composable
internal fun NimbusModalView(
nimbusViewModel: NimbusViewModel,
modalParentHelper: ModalTransitionDialogHelper,
viewRequest: ViewRequest,
onDismiss: () -> Unit,
modifier: Modifier = Modifier
.fillMaxSize()
.background(Color.White)
) {
var nimbusViewModelModalState: NimbusViewModelModalState by remember {
mutableStateOf(NimbusViewModelModalState.HiddenModalState)
}
val modalTransitionDialogHelper = ModalTransitionDialogHelper()
val navHostHelper = NimbusNavHostHelper()

if (nimbusViewModelModalState is NimbusViewModelModalState.OnShowModalModalState) {
val showModalState =
(nimbusViewModelModalState as? NimbusViewModelModalState.OnShowModalModalState)
ModalTransitionDialog(
modalTransitionDialogHelper = modalTransitionDialogHelper,
onDismissRequest = {
nimbusViewModel.setModalHiddenState()
},
onCanDismissRequest = {
//Can dismiss the modal if we cannot pop more pages from navigation host
!navHostHelper.pop()
}
onDismissRequest = onDismiss,
) {
NimbusNavHost(
modalParentHelper = modalTransitionDialogHelper,
nimbusNavHostHelper = navHostHelper,
viewRequest = showModalState?.viewRequest,
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
.background(Color.White)
.padding(16.dp),
modalParentHelper = it,
viewRequest = viewRequest,
modifier = modifier
)
}
} else if (nimbusViewModelModalState is NimbusViewModelModalState.OnHideModalState) {
modalParentHelper.triggerAnimatedClose()
}

CollectFlow(nimbusViewModel.nimbusViewModelModalState) {
if (it != nimbusViewModelModalState)
nimbusViewModelModalState = it
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package br.zup.com.nimbus.compose.internal

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
Expand All @@ -13,8 +17,11 @@ import br.zup.com.nimbus.compose.Nimbus
import br.zup.com.nimbus.compose.NimbusTheme.nimbus
import br.zup.com.nimbus.compose.ProvideNavigatorState
import br.zup.com.nimbus.compose.SHOW_VIEW_DESTINATION
import br.zup.com.nimbus.compose.VIEW_JSON_DESCRIPTION
import br.zup.com.nimbus.compose.JSON
import br.zup.com.nimbus.compose.NimbusTheme
import br.zup.com.nimbus.compose.VIEW_URL
import br.zup.com.nimbus.compose.model.NimbusPageState
import br.zup.com.nimbus.compose.model.Page
import com.zup.nimbus.core.network.ViewRequest
import java.util.UUID

Expand All @@ -41,6 +48,9 @@ internal fun NimbusNavHost(
navigationState.handleNavigation(navController)
}

val nimbusViewModelModalState: NimbusViewModelModalState by
nimbusViewModel.nimbusViewModelModalState.collectAsState()

NimbusDisposableEffect(
onCreate = {
initNavHost(nimbusViewModel, viewRequest, json)
Expand All @@ -57,19 +67,29 @@ internal fun NimbusNavHost(
route = SHOW_VIEW_DESTINATION,
arguments = listOf(navArgument(VIEW_URL) {
type = NavType.StringType
defaultValue = viewRequest?.url ?: VIEW_JSON_DESCRIPTION
defaultValue = viewRequest?.url ?: JSON
})
) { backStackEntry ->
nimbusViewModel.getPageBy(
NimbusBackHandler(onDismiss =
{
modalParentHelper.triggerAnimatedClose()
})

nimbusViewModelModalState.HandleModalState(
onDismiss = {
nimbusViewModel.setModalHiddenState()
},
onHideModal = {
modalParentHelper.triggerAnimatedClose()
}
)

val page = nimbusViewModel.getPageBy(
backStackEntry.getPageUrl()
)?.let { page ->
NimbusBackHandler()
page.Compose()
NimbusModalView(
nimbusViewModel = nimbusViewModel,
modalParentHelper = modalParentHelper
)
}
)

val pageRemember by remember(page?.id) { mutableStateOf(page) }
pageRemember?.Compose()
}
}
}
Expand All @@ -89,7 +109,7 @@ private fun configureNavHostHelper(
private fun initNavHost(
nimbusViewModel: NimbusViewModel,
viewRequest: ViewRequest?,
json: String
json: String,
) {

if (viewRequest != null)
Expand All @@ -102,12 +122,12 @@ private fun initNavHost(
/**
* This helper can be used to control some behaviour from outside the NimbusNavHost composable
*/
internal class NimbusNavHostHelper {
class NimbusNavHostHelper {

var nimbusNavHostExecutor: NimbusNavHostExecutor? = null
fun isFirstScreen(): Boolean = nimbusNavHostExecutor?.isFirstScreen() ?: false
var nimbusNavHostExecutor: NimbusNavHostExecutor? = null
fun isFirstScreen(): Boolean = nimbusNavHostExecutor?.isFirstScreen() ?: false

fun pop(): Boolean = nimbusNavHostExecutor?.pop() ?: false
fun pop(): Boolean = nimbusNavHostExecutor?.pop() ?: false

interface NimbusNavHostExecutor {
fun isFirstScreen(): Boolean
Expand Down
Loading