EhViewer icon indicating copy to clipboard operation
EhViewer copied to clipboard

Avoid some undesirable shared element transitions

Open FooIbar opened this issue 1 year ago • 11 comments

FooIbar avatar Aug 03 '24 14:08 FooIbar

This implementation is terrible, never let id explicitly travel so many places.

revonateB0T avatar Aug 03 '24 23:08 revonateB0T

First we introduce a global SETFilter which use dsl to explicitly construct a list of Triple (keyGenerator, NavGraphNode, NavGraphNode)

val SETFilter = SETFilter {
  val listScreen = listof(GalleryListScreen, FavouritesScreen...)
  // Blacklist:
  listScreen.permutations { a, b ->
    thumbItem between a and b
  }
  // Whitelist:
  listScreen.forEach { 
    thumbItem between it and GalleryDetailScreen
  }
}

Things to be discussed: SETFilter should be a blacklist of transitions should not animated or a whitelist of transitions should animated?

revonateB0T avatar Aug 04 '24 00:08 revonateB0T

The following part is SETFilter how to work. thumbItem is a global TransitionItemKeyGenerator. EhAsyncThumb takes it to generate key and append it at head of SET key, the generated key is the same unless someone reset it. The important part is we track navController once outside DestinationHost, detect all current NavBackStackEntry changes, and filter the change with the content SETFilter recorded For whitelist mode if changes is filtered, where the change is not in whitelist, we reset thumbItem key generator(note that the reset must be executed before Composer walk through new Screen), so the key generated for new screen is different from origin one, so no SET, vice versa.

revonateB0T avatar Aug 04 '24 00:08 revonateB0T

Through the Filter framework we will successfully decouple (something should animates between some Screen explicitly set) and the Screen itself

revonateB0T avatar Aug 04 '24 00:08 revonateB0T

Through the Filter framework we will successfully decouple (something should animates between some Screen explicitly set) and the Screen itself

Should we? We might want fine-grained control over the transitions, such as only running them when explicitly initiated by user click. That is, no transitions when navigation is initiated by random pick/clipboard URL/etc., as transitions starting from an unexpected position are counterintuitive.

FooIbar avatar Aug 04 '24 05:08 FooIbar

Through the Filter framework we will successfully decouple (something should animates between some Screen explicitly set) and the Screen itself

Should we? We might want fine-grained control over the transitions, such as only running them when explicitly initiated by user click. That is, no transitions when navigation is initiated by random pick/clipboard URL/etc., as transitions starting from an unexpected position are counterintuitive.

Nonetheless explicitly pass the key through Widgets, Screens and Navigations will increase much unnecessary complexity of each Screen, let's design something more declarative.

revonateB0T avatar Aug 04 '24 05:08 revonateB0T

Screen
└── Lazy(List|Grid)
    └── (List|Grid)Item
        └── Thumb

We need to distinguish between list/grid items, so the key should be generated on the call site of (List|Grid)Item.

FooIbar avatar Aug 04 '24 07:08 FooIbar

Screen
└── Lazy(List|Grid)
    └── (List|Grid)Item
        └── Thumb

We need to distinguish between list/grid items, so the key should be generated on the call site of (List|Grid)Item.

I'd like to avoid distinguish them if the cost is increased complexity and unnatural code. If we just want fine-grained control maybe current shared element transition API is not suitable for us, as we just want the one explicitly set animates, that's something imperative(not declarative).

revonateB0T avatar Aug 04 '24 08:08 revonateB0T

We need to distinguish between list/grid items, so the key should be generated on the call site of (List|Grid)Item.

I'd like to avoid distinguish them if the cost is increased complexity and unnatural code. If we just want fine-grained control maybe current shared element transition API is not suitable for us, as we just want the one explicitly set animates, that's something imperative(not declarative).

The crossfade will glitch if they use the same keys, so we can't really avoid it.

FooIbar avatar Aug 05 '24 09:08 FooIbar

We need to distinguish between list/grid items, so the key should be generated on the call site of (List|Grid)Item.

I'd like to avoid distinguish them if the cost is increased complexity and unnatural code. If we just want fine-grained control maybe current shared element transition API is not suitable for us, as we just want the one explicitly set animates, that's something imperative(not declarative).

The crossfade will glitch if they use the same keys, so we can't really avoid it.

Maybe we just remove the togetherWith inside Crossfade?

revonateB0T avatar Aug 05 '24 13:08 revonateB0T

We need to distinguish between list/grid items, so the key should be generated on the call site of (List|Grid)Item.

I'd like to avoid distinguish them if the cost is increased complexity and unnatural code. If we just want fine-grained control maybe current shared element transition API is not suitable for us, as we just want the one explicitly set animates, that's something imperative(not declarative).

The crossfade will glitch if they use the same keys, so we can't really avoid it.

Maybe we just remove the togetherWith inside Crossfade?

It's already removed.

FooIbar avatar Aug 05 '24 14:08 FooIbar

Friendly ping

FooIbar avatar Aug 13 '24 12:08 FooIbar

Friendly ping

I'd just suggest we extract "remove togetherWith inside CrossFade" to another PR and merge it first, I strongly disapprove passing id String across the whole Screen & NavArgs as it will introduce much complexity and maintenance burden.

revonateB0T avatar Aug 13 '24 14:08 revonateB0T

Friendly ping

I'd just suggest we extract "remove togetherWith inside CrossFade" to another PR and merge it first, I strongly disapprove passing id String across the whole Screen & NavArgs as it will introduce much complexity and maintenance burden.

The problem is, removing togetherWith alone does not work. It will glitch as long as list and grid items share the same keys.

FooIbar avatar Aug 13 '24 15:08 FooIbar

Basically, these are what we need:

  1. list and grid items have different keys
  2. non-detail screens have different keys

FooIbar avatar Aug 15 '24 14:08 FooIbar

Basically, these are what we need:

  1. list and grid items have different keys
  2. non-detail screens have different keys

Is it possible to recompose with different shared element transition key before(suspend and await next composition) navigate to other screen / do crossfade transition?

revonateB0T avatar Aug 16 '24 03:08 revonateB0T

Basically, these are what we need:

  1. list and grid items have different keys
  2. non-detail screens have different keys

Is it possible to recompose with different shared element transition key before(suspend and await next composition) navigate to other screen / do crossfade transition?

If so, maybe we can design a graceful framework to handle this.

revonateB0T avatar Aug 16 '24 03:08 revonateB0T

Basically, these are what we need:

  1. list and grid items have different keys
  2. non-detail screens have different keys

Is it possible to recompose with different shared element transition key before(suspend and await next composition) navigate to other screen / do crossfade transition?

That sounds hacky. And how we gonna pass the new key to detail screen?

FooIbar avatar Aug 16 '24 06:08 FooIbar

Basically, these are what we need:

  1. list and grid items have different keys
  2. non-detail screens have different keys

Is it possible to recompose with different shared element transition key before(suspend and await next composition) navigate to other screen / do crossfade transition?

That sounds hacky. And how we gonna pass the new key to detail screen?

Let's extract two model. The first model is something like original shared element transition key, UI element have same key means they are similar thus can do shared element transitions, let's call the key contentKey. The second one is connector, connector connects NodeGenerator, so the two NodeGenerators connected by connector will generate same syntheticKey for same contentKey thus can do shared element transitions. We combine these two models to generate key and do SET.

revonateB0T avatar Aug 16 '24 07:08 revonateB0T

For implementation details, class NodeGenerator() { // Key is the first model's key, means content is the same fun generate(contentKey:String): Node }

class Node( val contentKey: String val syntheticKey: String )

A connector connects two NodeGenerators, the NodeGenerator tracks all active(remembered) nodes generated by itself. When NodeGenerator generate Nodes, it will firstly search from the opposite NodeGenerator connected with connector, if opposite NodeGenerator have a Node that contains the same contentKey, the newly generated Node will use its syntheticKey, otherwise syntheticKey is always unique(maybe implemented with a self-inc atomicInt).

revonateB0T avatar Aug 16 '24 07:08 revonateB0T

For example, We have a global NodeGenerator for ListScreen val listGenerator: NodeGenerator there's two thumbs in ListScreen thus two Nodes generated Node A: contentKey gid1 syntheticKey uniqueID1 Node B: contentKey gid2 syntheticKey uniqueID2

We switched to grid, as listGenerator is not connected to listGenerator, newly generated two Nodes still have unique synthetic id Node C: contentKey gid1 syntheticKey uniqueID3 Node D: contentKey gid2 syntheticKey uniqueID4 As syntheticKey is different, thus no shared element transitions.

revonateB0T avatar Aug 16 '24 07:08 revonateB0T

For now we have Node C: contentKey gid1 syntheticKey uniqueID3 Node D: contentKey gid2 syntheticKey uniqueID4

Then we navigate to DetailScreen with gid1. DetailScreen have its global NodeGenerator val detailGenerator: NodeGenerator detailGenerator is connected to listGenerator so newly generated detail header thumb node Node E contentKey gid1 syntheticKey uniqueID3 E and C have same contentKey, obviously. detailGenerator search from opposite -- listGenerator and find C, so E takes C's syntheticKey. Since C and E's syntheticKey is the same, shared element transition works.

revonateB0T avatar Aug 16 '24 08:08 revonateB0T

That sounds a lot more complicated. You can make another PR if you feel it's necessary.

FooIbar avatar Aug 16 '24 08:08 FooIbar

That sounds a lot more complicated. You can make another PR if you feel it's necessary.

But this can avoid complexity being exponential increased if we want more dedicated shared element transitions. I would like to implement it but I have no environment recently, if this is a release blocker maybe we need to temporarily disable SET as temporarily solution.

revonateB0T avatar Aug 16 '24 09:08 revonateB0T