Skip to content

Commit

Permalink
Merge pull request #117 from arkivanov/fix-ui-state-not-restored-on-c…
Browse files Browse the repository at this point in the history
…onfiguration-change-with-animations

Fix Compose UI state not restored on configuration change with animations
  • Loading branch information
arkivanov authored Apr 16, 2021
2 parents 6244075 + b690679 commit efa62ba
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ fun <C : Any, T : Any> childAnimation(
indicesMap[routerState.activeChild] = routerState.backStack.size

PageAnimation(
key = routerState.activeChild,
page = routerState.activeChild,
key = routerState.activeChild.configuration,
animationSpec = animationSpec,
arranger = { new, old ->
val indexOfNew = indicesMap[new] ?: Int.MAX_VALUE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,72 +20,92 @@ import com.arkivanov.decompose.ExperimentalDecomposeApi

@Composable
internal fun <T : Any> PageAnimation(
key: T,
page: T,
key: Any,
animationSpec: FiniteAnimationSpec<Float>,
arranger: PageArranger<T>,
animator: PageAnimator<T>,
content: @Composable (key: T) -> Unit
) {
val keys = remember { MutableKeys(newKey = key) }
PageAnimation(
page = KeyedPage(page = page, key = key),
animationSpec = animationSpec,
arranger = arranger,
animator = animator,
content = content
)
}

@Composable
private fun <T : Any> PageAnimation(
page: KeyedPage<T>,
animationSpec: FiniteAnimationSpec<Float>,
arranger: PageArranger<T>,
animator: PageAnimator<T>,
content: @Composable (page: T) -> Unit
) {
val pages = remember { MutablePages(newPage = page) }

if (key != keys.newKey) {
keys.oldKey = keys.newKey
keys.newKey = key
if (page != pages.newPage) {
pages.oldPage = pages.newPage
pages.newPage = page
}

val newKey = keys.newKey
val oldKey = keys.oldKey
val newPage: KeyedPage<T> = pages.newPage
val oldPage: KeyedPage<T>? = pages.oldPage

PageAnimation(
oldKey = oldKey,
newKey = newKey,
oldPage = oldPage,
newPage = newPage,
animationSpec = animationSpec,
arrangement = if (oldKey == null) PageArrangement.FOLLOWING else arranger(newKey, oldKey),
arrangement = if (oldPage == null) PageArrangement.FOLLOWING else arranger(newPage.page, oldPage.page),
animator = animator,
content = content
)
}

@Composable
private fun <T : Any> PageAnimation(
oldKey: T?,
newKey: T,
oldPage: KeyedPage<T>?,
newPage: KeyedPage<T>,
animationSpec: AnimationSpec<Float>,
arrangement: PageArrangement,
animator: PageAnimator<T>,
content: @Composable (key: T) -> Unit
content: @Composable (page: T) -> Unit
) {
val animationState = remember(newKey) { AnimationState(if (oldKey == null) 1F else 0F) }
val animationState = remember(newPage.key) { AnimationState(if (oldPage == null) 1F else 0F) }

var items by remember(newKey) {
var items by remember(newPage.key) {
mutableStateOf(
listOfNotNull(
if (oldKey != null) {
if (oldPage != null) {
AnimationItem(
key = oldKey,
page = oldPage.page,
key = oldPage.key,
arrangement = arrangement.invert(),
direction = PageAnimationDirection.EXIT
)
} else {
null
},
AnimationItem(
key = newKey,
page = newPage.page,
key = newPage.key,
arrangement = arrangement,
direction = PageAnimationDirection.ENTER
)
).sortedBy { it.arrangement }
)
}

LaunchedEffect(newKey) {
LaunchedEffect(newPage.key) {
animationState.animateTo(
targetValue = 1F,
animationSpec = animationSpec,
sequentialAnimation = !animationState.isFinished
)

items = items.filter { it.key == newKey }
items = items.filter { it.key == newPage.key }
}

PageAnimationFrame(
Expand Down Expand Up @@ -118,24 +138,30 @@ private fun <T : Any> PageAnimationFrame(
PageAnimationDirection.EXIT -> 1F - animationValue
}

animator(item.key, factor, item.arrangement, item.direction) { modifier ->
animator(item.page, factor, item.arrangement, item.direction) { modifier ->
Box(modifier) {
content(item.key)
content(item.page)
}
}
}
}
}
}

private data class KeyedPage<out T>(
val page: T,
val key: Any
)

private class AnimationItem<out T>(
val key: T,
val page: T,
val key: Any,
val arrangement: PageArrangement,
val direction: PageAnimationDirection
)

private class MutableKeys<T : Any>(
var newKey: T
private class MutablePages<T : Any>(
var newPage: T
) {
var oldKey: T? = null
var oldPage: T? = null
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ fun <C : Any, T : Any> childAnimation(
indicesMap[routerState.activeChild] = routerState.backStack.size

PageAnimation(
key = routerState.activeChild,
page = routerState.activeChild,
key = routerState.activeChild.configuration,
animationSpec = animationSpec,
arranger = { new, old ->
val indexOfNew = indicesMap[new] ?: Int.MAX_VALUE
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,72 +20,92 @@ import com.arkivanov.decompose.ExperimentalDecomposeApi

@Composable
internal fun <T : Any> PageAnimation(
key: T,
page: T,
key: Any,
animationSpec: FiniteAnimationSpec<Float>,
arranger: PageArranger<T>,
animator: PageAnimator<T>,
content: @Composable (key: T) -> Unit
) {
val keys = remember { MutableKeys(newKey = key) }
PageAnimation(
page = KeyedPage(page = page, key = key),
animationSpec = animationSpec,
arranger = arranger,
animator = animator,
content = content
)
}

@Composable
private fun <T : Any> PageAnimation(
page: KeyedPage<T>,
animationSpec: FiniteAnimationSpec<Float>,
arranger: PageArranger<T>,
animator: PageAnimator<T>,
content: @Composable (page: T) -> Unit
) {
val pages = remember { MutablePages(newPage = page) }

if (key != keys.newKey) {
keys.oldKey = keys.newKey
keys.newKey = key
if (page != pages.newPage) {
pages.oldPage = pages.newPage
pages.newPage = page
}

val newKey = keys.newKey
val oldKey = keys.oldKey
val newPage: KeyedPage<T> = pages.newPage
val oldPage: KeyedPage<T>? = pages.oldPage

PageAnimation(
oldKey = oldKey,
newKey = newKey,
oldPage = oldPage,
newPage = newPage,
animationSpec = animationSpec,
arrangement = if (oldKey == null) PageArrangement.FOLLOWING else arranger(newKey, oldKey),
arrangement = if (oldPage == null) PageArrangement.FOLLOWING else arranger(newPage.page, oldPage.page),
animator = animator,
content = content
)
}

@Composable
private fun <T : Any> PageAnimation(
oldKey: T?,
newKey: T,
oldPage: KeyedPage<T>?,
newPage: KeyedPage<T>,
animationSpec: AnimationSpec<Float>,
arrangement: PageArrangement,
animator: PageAnimator<T>,
content: @Composable (key: T) -> Unit
content: @Composable (page: T) -> Unit
) {
val animationState = remember(newKey) { AnimationState(if (oldKey == null) 1F else 0F) }
val animationState = remember(newPage.key) { AnimationState(if (oldPage == null) 1F else 0F) }

var items by remember(newKey) {
var items by remember(newPage.key) {
mutableStateOf(
listOfNotNull(
if (oldKey != null) {
if (oldPage != null) {
AnimationItem(
key = oldKey,
page = oldPage.page,
key = oldPage.key,
arrangement = arrangement.invert(),
direction = PageAnimationDirection.EXIT
)
} else {
null
},
AnimationItem(
key = newKey,
page = newPage.page,
key = newPage.key,
arrangement = arrangement,
direction = PageAnimationDirection.ENTER
)
).sortedBy { it.arrangement }
)
}

LaunchedEffect(newKey) {
LaunchedEffect(newPage.key) {
animationState.animateTo(
targetValue = 1F,
animationSpec = animationSpec,
sequentialAnimation = !animationState.isFinished
)

items = items.filter { it.key == newKey }
items = items.filter { it.key == newPage.key }
}

PageAnimationFrame(
Expand Down Expand Up @@ -118,24 +138,30 @@ private fun <T : Any> PageAnimationFrame(
PageAnimationDirection.EXIT -> 1F - animationValue
}

animator(item.key, factor, item.arrangement, item.direction) { modifier ->
animator(item.page, factor, item.arrangement, item.direction) { modifier ->
Box(modifier) {
content(item.key)
content(item.page)
}
}
}
}
}
}

private data class KeyedPage<out T>(
val page: T,
val key: Any
)

private class AnimationItem<out T>(
val key: T,
val page: T,
val key: Any,
val arrangement: PageArrangement,
val direction: PageAnimationDirection
)

private class MutableKeys<T : Any>(
var newKey: T
private class MutablePages<T : Any>(
var newPage: T
) {
var oldKey: T? = null
var oldPage: T? = null
}

0 comments on commit efa62ba

Please sign in to comment.