Skip to content

Commit

Permalink
fix: animated routing duplicate content recomposition after state res…
Browse files Browse the repository at this point in the history
…toration
  • Loading branch information
programadorthi committed Jun 13, 2024
1 parent 4c6d101 commit 72cbefe
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,9 @@ internal var ApplicationCall.popExitTransition: Animation<ExitTransition>?
attributes.remove(ComposeRoutingPopExitTransitionAttributeKey)
}
}

public val AnimatedContentTransitionScope<ApplicationCall>.nextCall: ApplicationCall
get() = targetState

public val AnimatedContentTransitionScope<ApplicationCall>.previousCall: ApplicationCall
get() = initialState
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,16 @@ package dev.programadorthi.routing.compose.animation
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import dev.programadorthi.routing.compose.CallContent
import dev.programadorthi.routing.compose.Routing
import dev.programadorthi.routing.compose.history.ComposeHistoryMode
import dev.programadorthi.routing.compose.history.restored
import dev.programadorthi.routing.compose.popped
import dev.programadorthi.routing.core.Route
import dev.programadorthi.routing.core.Routing
Expand All @@ -35,54 +32,30 @@ public fun Routing(
popEnterTransition: Animation<EnterTransition> = enterTransition,
popExitTransition: Animation<ExitTransition> = exitTransition,
) {
var restored by rememberSaveable(
key = "state:restored:$routing",
saver =
Saver(
restore = { mutableStateOf(true) },
save = { true },
),
) {
mutableStateOf(false)
}
Routing(
historyMode = historyMode,
routing = routing,
startUri = startUri,
) { call ->
if (restored) {
CallContent(call)
} else {
AnimatedContent(
targetState = call,
transitionSpec = {
val previousCall = initialState
val nextCall = targetState
val enter =
when {
previousCall.popped -> nextCall.popEnterTransition ?: popEnterTransition
else -> nextCall.enterTransition ?: enterTransition
}
val exit =
when {
previousCall.popped ->
previousCall.popExitTransition
?: popExitTransition

else -> previousCall.exitTransition ?: exitTransition
}

AnimatedContent(
targetState = call,
transitionSpec = {
if (nextCall.restored) {
fadeIn() togetherWith fadeOut()
} else if (previousCall.popped) {
val popEnter = nextCall.popEnterTransition ?: popEnterTransition
val popExit = previousCall.popExitTransition ?: popExitTransition
popEnter(this) togetherWith popExit(this)
} else {
val enter = nextCall.enterTransition ?: enterTransition
val exit = previousCall.exitTransition ?: exitTransition
enter(this) togetherWith exit(this)
},
content = { animatedCall ->
CallContent(animatedCall)
},
)
}

SideEffect {
restored = false
}
}
},
content = { animatedCall ->
CallContent(animatedCall)
},
)
}
}

Expand Down
7 changes: 1 addition & 6 deletions integration/compose/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,8 @@ kotlin {
dependencies {
api(projects.resources)
implementation(compose.runtime)
implementation(libs.serialization.json)
}
}

jvmMain {
dependencies {
implementation(compose.runtimeSaveable)
implementation(libs.serialization.json)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable
import dev.programadorthi.routing.compose.history.platformPush
import dev.programadorthi.routing.compose.history.platformReplace
import dev.programadorthi.routing.compose.history.platformReplaceAll
import dev.programadorthi.routing.compose.history.restored
import dev.programadorthi.routing.compose.history.shouldNeglect
import dev.programadorthi.routing.core.Route
import dev.programadorthi.routing.core.RouteMethod
Expand Down Expand Up @@ -72,6 +73,12 @@ public suspend fun <T> PipelineContext<Unit, ApplicationCall>.composable(
call.resource = resource
call.content = body

// Try clear restored flag on previous call
val previousCall = routing.callStack.lastOrNull()
if (previousCall?.restored == true) {
previousCall.restored = false
}

if (call.shouldNeglect()) {
return
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ public fun Routing.pop(result: Any? = null) {
poppedCall?.popped = true
poppedCall?.popResult = result

val last = callStack.lastOrNull() ?: return@popOnPlatform
if (last.content == null) {
execute(last)
val lastCall = callStack.lastOrNull() ?: return@popOnPlatform
if (lastCall.content == null) {
call(
name = lastCall.name,
uri = lastCall.uri,
parameters = lastCall.parameters,
routeMethod = RouteMethod.Replace,
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import dev.programadorthi.routing.core.application.Application
import dev.programadorthi.routing.core.application.ApplicationCall
import io.ktor.util.AttributeKey

internal val ComposeHistoryModeAttributeKey: AttributeKey<ComposeHistoryMode> =
private val ComposeHistoryModeAttributeKey: AttributeKey<ComposeHistoryMode> =
AttributeKey("ComposeHistoryModeAttributeKey")

internal val ComposeHistoryNeglectAttributeKey: AttributeKey<Boolean> =
AttributeKey("ComposeHistoryNeglectAttributeKey")

internal val ComposeHistoryRestoredCallAttributeKey: AttributeKey<Boolean> =
private val ComposeHistoryRestoredCallAttributeKey: AttributeKey<Boolean> =
AttributeKey("ComposeHistoryRestoredCallAttributeKey")

internal var Application.historyMode: ComposeHistoryMode
Expand All @@ -33,8 +33,8 @@ internal var ApplicationCall.neglect: Boolean
attributes.put(ComposeHistoryNeglectAttributeKey, value)
}

internal var ApplicationCall.restored: Boolean
public var ApplicationCall.restored: Boolean
get() = attributes.getOrNull(ComposeHistoryRestoredCallAttributeKey) ?: false
set(value) {
internal set(value) {
attributes.put(ComposeHistoryRestoredCallAttributeKey, value)
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,13 @@ internal data class ComposeHistoryState(
)

internal fun ComposeHistoryState.toCall(application: Application): ApplicationCall {
val call =
ApplicationCall(
application = application,
name = name,
uri = uri,
routeMethod = RouteMethod.parse(routeMethod),
parameters = parametersOf(parameters),
)
call.restored = true
return call
return ApplicationCall(
application = application,
name = name,
uri = uri,
routeMethod = RouteMethod.parse(routeMethod),
parameters = parametersOf(parameters),
)
}

internal fun ApplicationCall.toHistoryState(): ComposeHistoryState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlin.coroutines.suspendCoroutine

private const val HASH_PREFIX = "/#"

internal actual fun ApplicationCall.shouldNeglect(): Boolean = neglect || restored
internal actual fun ApplicationCall.shouldNeglect(): Boolean = neglect

internal actual suspend fun ApplicationCall.platformPush(routing: Routing) {
window.history.pushState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import dev.programadorthi.routing.compose.callStack
import dev.programadorthi.routing.compose.content
import dev.programadorthi.routing.compose.push
import dev.programadorthi.routing.core.RouteMethod
import dev.programadorthi.routing.core.Routing
import dev.programadorthi.routing.core.application
import dev.programadorthi.routing.core.application.ApplicationCall
import dev.programadorthi.routing.core.call
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

internal actual fun ApplicationCall.shouldNeglect(): Boolean = restored
internal actual fun ApplicationCall.shouldNeglect(): Boolean = false

internal actual suspend fun ApplicationCall.platformPush(routing: Routing) {
routing.callStack.add(this)
Expand Down Expand Up @@ -52,16 +55,37 @@ internal actual fun Routing.restoreState(startUri: String) {
},
),
)
LaunchedEffect(Unit) {
val lastCall = history.lastOrNull()
when {
lastCall?.restored == true -> {
callStack.clear()
callStack.addAll(history)
execute(lastCall)
}

else -> push(path = startUri)
LaunchedEffect(routingPath) {
when (history.lastOrNull()) {
null -> push(path = startUri)
else -> restoreLastCall(history)
}
}
}

private fun Routing.restoreLastCall(history: List<ApplicationCall>) {
val lastCall = history.last()
lastCall.restored = true
val hasContent = lastCall.content != null
callStack.clear()
callStack.addAll(
history.subList(
fromIndex = 0,
toIndex =
if (hasContent) {
history.size
} else {
history.lastIndex
},
),
)
if (!hasContent) {
call(
name = lastCall.name,
uri = lastCall.uri,
parameters = lastCall.parameters,
attributes = lastCall.attributes,
routeMethod = RouteMethod.Push,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import dev.programadorthi.routing.compose.push
import dev.programadorthi.routing.core.Routing
import dev.programadorthi.routing.core.application.ApplicationCall

internal actual fun ApplicationCall.shouldNeglect(): Boolean = restored
internal actual fun ApplicationCall.shouldNeglect(): Boolean = false

internal actual suspend fun ApplicationCall.platformPush(routing: Routing) {
routing.callStack.add(this)
Expand Down

0 comments on commit 72cbefe

Please sign in to comment.