arkivanov / Decompose

Kotlin Multiplatform lifecycle-aware business logic components (aka BLoCs) with routing (navigation) and pluggable UI (Jetpack Compose, SwiftUI, JS React, etc.)
https://arkivanov.github.io/Decompose
Apache License 2.0
2.21k stars 84 forks source link

Wear Support #378

Closed yschimke closed 1 year ago

yschimke commented 1 year ago

Trying to implement Wear Navigation in https://github.com/xxfast/NYTimes-KMP/pull/15/files which builds on Decompose, but the Wear swipe to dismiss isn't well supported.

For compose navigation there is SwipeDismissableNavHost

https://developer.android.com/training/wearables/compose/navigation

This uses SwipeToDismissBox

https://developer.android.com/training/wearables/compose/swipe-to-dismiss

But the effect isn't quite right in two ways

1) It should show the screen below when isBackground is true 2) the SwipeToDismiss isn't the signal to navigate back, it's actually part of the transition.

Screen_recording_20230430_090758.webm

arkivanov commented 1 year ago

Thanks for reporting! I remember there was some support of Wear in that project. Is it related? How is it related to Decompose and what kind of help you would like? Decompose in its core just exposes a list of children, it's up to the developer how to render it.

yschimke commented 1 year ago

OK, so maybe for NYTimes-KMP, I don't know the codebase well. But was trying to fix it.

From that link, the isBackground below is missing, and therefore ignored. But also doesn't help, since swiping to dismiss should close at the same time, not trigger a follow up close.

  SwipeToDismissBox(onDismissed = onBack) { isBackground ->
    StoryView(
      state = state,
      onRefresh = viewModel::onRefresh,
      onBack = onBack,
      onSave = viewModel::onSave
    )
  }

Will I basically need to implement SwipeDismissableNavHost?

I'll take a look at what is involved.

arkivanov commented 1 year ago

It doesn't look like you need SwipeDismissableNavHost, Decompose should be replacing it. I never used that API, but after checking the docs, you should be able to just use SwipeToDismissBox. You should pass proper arguments:

backgroundKey - the configuration of the last child in the backStack, or SwipeToDismissKeys.Background if the back stack is empty. contentKey - the configuration of the active child. hasBackground - basically pass backStack.isNotEmpty()

Then in the lambda block, render the last child in the back stack of isBackground is true, or the active child otherwise.

yschimke commented 1 year ago

How to I make the dismiss immediate, and not repeated with a second animation? Just disable animation here?

    Children(
      stack = router.stack.value,
      animation = stackAnimation(slide()),
    ) { child ->
arkivanov commented 1 year ago

I think you should use either Children or SwipeToDismissBox, but not both.

yschimke commented 1 year ago

That worked nicely, although one bug to fix in Wear Compose

https://github.com/xxfast/NYTimes-KMP/blob/5815aaa944031d5e67e1e9f701b9e388f3d85a9a/app/wear/src/main/kotlin/io/github/xxfast/nytimes/wear/screens/home/HomeScreen.kt

  val router: Router<StoryHomeScreen> = rememberRouter(StoryHomeScreen::class, listOf(List))
  val stack = router.stack.value
  val previous = stack.backStack.lastOrNull()?.configuration
  val current = stack.active.configuration

  val state = SwipeToDismissBoxState()
  SwipeToDismissBox(
    state = state,
    backgroundKey = previous ?: SwipeToDismissKeys.Background,
    contentKey = current,
    hasBackground = previous != null,
    onDismissed = { router.pop() },
    backgroundScrimColor = MaterialTheme.colors.background.fudge(stack.items.size)
  ) { isBackground ->
    val configuration = if (isBackground) {
      previous
    } else {
      current
    }

    if (configuration != null) {
      // Screen content here
  }