commonMain.ru.alexgladkov.odyssey.compose.controllers.ModalRootController.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of odyssey-compose Show documentation
Show all versions of odyssey-compose Show documentation
Lightweight multiplatform navigation library (jvm, android, ios)
package ru.alexgladkov.odyssey.compose.controllers
import kotlinx.coroutines.flow.MutableStateFlow
import ru.alexgladkov.odyssey.compose.Render
import ru.alexgladkov.odyssey.compose.RootController
import ru.alexgladkov.odyssey.compose.navigation.modal_navigation.AlertConfiguration
import ru.alexgladkov.odyssey.compose.navigation.modal_navigation.CustomModalConfiguration
import ru.alexgladkov.odyssey.compose.navigation.modal_navigation.ModalSheetConfiguration
import ru.alexgladkov.odyssey.core.extensions.CFlow
import ru.alexgladkov.odyssey.core.extensions.wrap
sealed class ModalDialogState {
object Idle : ModalDialogState()
object Open : ModalDialogState()
data class Close(val animate: Boolean = true) : ModalDialogState()
}
/**
* Class helper to use with compose for bottom modal sheet
* @param maxHeight - maxHeight in Float. use null for wrap by content
* @param cornerRadius - card corner radius in dp
* @param threshold - threshold for closing modal bottom sheet
* @param alpha - screamer alpha
* @param closeOnBackdropClick - true if you want to close on backdrop click
* @param closeOnSwipe - true if you want to close on swipe
* @param content - composable content
*/
internal data class ModalSheetBundle(
override val key: String,
override val dialogState: ModalDialogState,
override val animationTime: Int,
override val content: Render,
val maxHeight: Float?,
val threshold: Float,
val closeOnBackdropClick: Boolean,
val alpha: Float,
val cornerRadius: Int,
val closeOnSwipe: Boolean = true,
val backContent: Render? = null,
) : ModalBundle
/**
* Class helper to use with compose for alert dialog
* @param maxHeight - maxHeight in Float. use null for wrap by content
* @param maxWidth - maxWidth in Float, use null for wrap by content
* @param closeOnBackdropClick - true if you want to close on backdrop click
* @param cornerRadius - card corner radius in dp
* @param alpha - scrimer alpha
* @param content - composable content
*/
internal data class AlertBundle(
override val key: String,
override val dialogState: ModalDialogState,
override val animationTime: Int,
override val content: Render,
val maxHeight: Float?,
val maxWidth: Float?,
val closeOnBackdropClick: Boolean,
val alpha: Float,
val cornerRadius: Int,
) : ModalBundle
/**
* Class helper to use with modal for custom modal
*/
internal data class CustomModalBundle(
override val key: String,
override val dialogState: ModalDialogState,
override val animationTime: Int,
override val content: Render
) : ModalBundle
/**
* Common interface for any modal screens
* @see ModalDialogState
*/
sealed interface ModalBundle {
val key: String
/**
* composable content
*/
val content: Render
/**
* time for all animations
*/
val animationTime: Int
/**
* current dialog state
*/
val dialogState: ModalDialogState
}
@Deprecated("see ModalController", ReplaceWith("ModalController"))
class ModalSheetController : ModalController()
/**
* Class controller for bottom modal sheet
*/
open class ModalController {
private var _backStack = mutableListOf()
private val _currentStack: MutableStateFlow> = MutableStateFlow(emptyList())
val currentStack: CFlow> = _currentStack.wrap()
internal fun presentNew(
modalSheetConfiguration: ModalSheetConfiguration,
content: Render
) {
_backStack.add(modalSheetConfiguration.wrap(randomizeKey(), content))
redrawStack()
}
internal fun presentNew(
alertConfiguration: AlertConfiguration,
content: Render
) {
_backStack.add(alertConfiguration.wrap(randomizeKey(), content))
redrawStack()
}
internal fun presentNew(
customConfiguration: CustomModalConfiguration,
content: Render
) {
_backStack.add(customConfiguration.wrap(randomizeKey(), content))
redrawStack()
}
@Deprecated("@see popBackStack with key param", ReplaceWith("popBackStack(key = KEY)"))
fun popBackStack(animate: Boolean = true) {
popBackStack(key = null, animate = animate)
}
fun popBackStack(key: String? = null, animate: Boolean = true) {
setTopDialogState(modalDialogState = ModalDialogState.Close(animate), key)
}
internal fun setTopDialogState(modalDialogState: ModalDialogState, key: String? = null) {
if (modalDialogState is ModalDialogState.Close && !modalDialogState.animate) {
finishCloseAction(key)
return
}
val last = if (key != null) _backStack.firstOrNull { it.key == key }
else _backStack
.lastOrNull { modalDialogState !is ModalDialogState.Close || it.dialogState != modalDialogState }
if (last == null) return
val index = _backStack.indexOf(last)
val newState =
when (last) {
is ModalSheetBundle -> last.copy(dialogState = modalDialogState)
is AlertBundle -> last.copy(dialogState = modalDialogState)
is CustomModalBundle -> last.copy(dialogState = modalDialogState)
}
_backStack[index] = newState
redrawStack()
}
/** Removes last modal from backstack */
internal fun finishCloseAction(key: String?) {
when {
key != null -> {
val index = _backStack.indexOfFirst { it.key == key }
if (index != -1)
_backStack.removeAt(index)
}
_backStack.isNotEmpty() -> _backStack.removeLast()
}
redrawStack()
}
fun clearBackStack() {
_backStack.clear()
}
fun isEmpty() = _backStack.none { it.dialogState !is ModalDialogState.Close }
private fun redrawStack() {
val newStack = ArrayList().apply {
addAll(_backStack)
}
_currentStack.value = newStack
}
companion object{
internal fun randomizeKey() = RootController.randomizeKey("modal_")
}
}
internal fun ModalSheetConfiguration.wrap(key: String, with: Render): ModalBundle = ModalSheetBundle(
key = key,
maxHeight = maxHeight,
closeOnBackdropClick = closeOnBackdropClick,
closeOnSwipe = closeOnSwipe,
threshold = threshold,
animationTime = animationTime,
cornerRadius = cornerRadius,
alpha = alpha,
backContent = backContent,
dialogState = ModalDialogState.Idle,
content = with
)
internal fun AlertConfiguration.wrap(key: String, with: Render): ModalBundle = AlertBundle(
key = key,
maxHeight = maxHeight,
maxWidth = maxWidth,
animationTime = animationTime,
dialogState = ModalDialogState.Idle,
closeOnBackdropClick = closeOnBackdropClick,
cornerRadius = cornerRadius,
alpha = alpha,
content = with
)
@Suppress("unused")
internal fun CustomModalConfiguration.wrap(key: String, with: Render): ModalBundle = CustomModalBundle(
key = key, content = with, dialogState = ModalDialogState.Idle, animationTime = animationTime
)
© 2015 - 2025 Weber Informatics LLC | Privacy Policy