@apexnative/navigation 1.0.0-alpha.6 → 1.0.0-alpha.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/ApexNavigation.podspec +2 -1
  2. package/CHANGELOG.md +7 -0
  3. package/android/CLAUDE.md +19 -10
  4. package/android/CORE.md +3 -2
  5. package/android/consumer-rules.pro +6 -0
  6. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
  7. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
  8. package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
  9. package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
  10. package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
  11. package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
  12. package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
  13. package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
  14. package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
  15. package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
  16. package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
  17. package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
  18. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +4 -0
  19. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
  20. package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
  21. package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
  22. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
  23. package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
  24. package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +329 -55
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +26 -5
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +46 -7
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +7 -4
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +36 -38
  39. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
  40. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
  41. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
  42. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
  43. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
  44. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
  45. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
  46. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
  47. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
  48. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
  49. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
  50. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
  51. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
  52. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
  53. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
  54. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
  55. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
  56. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
  57. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
  58. package/android/src/main/jni/ANNContentHeights.cpp +82 -0
  59. package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
  60. package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
  61. package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
  62. package/android/src/main/res/values/styles.xml +1 -1
  63. package/android/src/main/res/values-night/styles.xml +1 -1
  64. package/cli/doctor/checks/app.js +16 -1
  65. package/cli/project.js +4 -0
  66. package/cli/setup/index.js +5 -0
  67. package/cli/setup/steps/metro.js +46 -0
  68. package/docs-md/architecture/native-screens.md +4 -0
  69. package/docs-md/architecture/theme-and-context.md +131 -12
  70. package/docs-md/architecture/transitions.md +43 -4
  71. package/docs-md/flows/bottom-sheets.md +4 -2
  72. package/docs-md/fundamentals/screens-and-props.md +9 -1
  73. package/docs-md/guides/cli.md +3 -1
  74. package/docs-md/help/troubleshooting.md +1 -1
  75. package/docs-md/index.json +29 -1
  76. package/docs-md/screen-ui/back-and-gestures.md +3 -1
  77. package/docs-md/screen-ui/bar-buttons.md +1 -1
  78. package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
  79. package/docs-md/start/installation.md +16 -1
  80. package/ios/Animations/ANNFrameDriver.h +55 -15
  81. package/ios/Animations/ANNFrameDriver.mm +214 -60
  82. package/ios/Animations/ANNTransitionAnimator.h +26 -5
  83. package/ios/Animations/ANNTransitionAnimator.mm +49 -3
  84. package/ios/Animations/ANNTransitionPlan.h +2 -2
  85. package/ios/Animations/ANNTransitionPlan.mm +10 -0
  86. package/ios/Animations/Elements/ANNElementFinder.h +2 -2
  87. package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
  88. package/ios/Animations/Elements/ANNElementTransition.h +3 -3
  89. package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
  90. package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
  91. package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
  92. package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
  93. package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
  94. package/ios/CLAUDE.md +15 -7
  95. package/ios/CORE.md +1 -1
  96. package/ios/Core/ANNAppDelegate.mm +0 -2
  97. package/ios/Core/ANNDeepLinkQueue.h +6 -5
  98. package/ios/Core/ANNDeepLinkQueue.mm +22 -10
  99. package/ios/Core/ANNSceneDelegate.mm +2 -2
  100. package/ios/Core/ApexNavigation.mm +6 -2
  101. package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
  102. package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
  103. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
  104. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
  105. package/ios/Layouts/Stack/ANNStackController.h +8 -0
  106. package/ios/Layouts/Stack/ANNStackController.mm +153 -11
  107. package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
  108. package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
  109. package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
  110. package/ios/Options/ANNAnimationOptions.h +2 -1
  111. package/ios/Options/ANNAnimationOptions.mm +1 -1
  112. package/ios/Options/ANNOptions.h +7 -0
  113. package/ios/Options/ANNOptions.mm +45 -0
  114. package/ios/Options/ANNOptionsReader.mm +2 -4
  115. package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
  116. package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
  117. package/ios/Options/Params/ColorParser.h +4 -3
  118. package/ios/Options/Params/ColorParser.mm +22 -4
  119. package/ios/React/ANNSurfaceHostingView.h +8 -0
  120. package/ios/React/ANNSurfaceHostingView.mm +28 -1
  121. package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
  122. package/ios/TurboModules/ANNTurboModule.mm +46 -0
  123. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
  124. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
  125. package/lib/module/ARCHITECTURE.md +9 -3
  126. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  127. package/lib/module/api/app/AppRegistry.js +14 -2
  128. package/lib/module/api/app/AppRegistry.js.map +1 -1
  129. package/lib/module/api/app/createNavigation.js +188 -66
  130. package/lib/module/api/app/createNavigation.js.map +1 -1
  131. package/lib/module/api/events/navigationEvents.js +6 -2
  132. package/lib/module/api/events/navigationEvents.js.map +1 -1
  133. package/lib/module/api/linking/linking.js +11 -0
  134. package/lib/module/api/linking/linking.js.map +1 -1
  135. package/lib/module/api/screens/defineScreen.js +45 -3
  136. package/lib/module/api/screens/defineScreen.js.map +1 -1
  137. package/lib/module/api/screens/nativeScreens.js +31 -6
  138. package/lib/module/api/screens/nativeScreens.js.map +1 -1
  139. package/lib/module/api/screens/registerScreens.js +172 -35
  140. package/lib/module/api/screens/registerScreens.js.map +1 -1
  141. package/lib/module/api/theme/theme.js +5 -0
  142. package/lib/module/api/theme/theme.js.map +1 -1
  143. package/lib/module/engine/commands.js +21 -1
  144. package/lib/module/engine/commands.js.map +1 -1
  145. package/lib/module/engine/layout.js +7 -2
  146. package/lib/module/engine/layout.js.map +1 -1
  147. package/lib/module/engine/nativeScreens.js +9 -0
  148. package/lib/module/engine/nativeScreens.js.map +1 -1
  149. package/lib/module/engine/options.js +5 -4
  150. package/lib/module/engine/options.js.map +1 -1
  151. package/lib/module/engine/optionsChange.js +102 -0
  152. package/lib/module/engine/optionsChange.js.map +1 -0
  153. package/lib/module/engine/refresh.js +85 -0
  154. package/lib/module/engine/refresh.js.map +1 -0
  155. package/lib/module/engine/rememberedOptions.js +60 -0
  156. package/lib/module/engine/rememberedOptions.js.map +1 -1
  157. package/lib/module/engine/screens.js +26 -0
  158. package/lib/module/engine/screens.js.map +1 -1
  159. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
  160. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  161. package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
  162. package/lib/typescript/api/app/createNavigation.d.ts +5 -0
  163. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  164. package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
  165. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  166. package/lib/typescript/api/index.d.ts +1 -1
  167. package/lib/typescript/api/index.d.ts.map +1 -1
  168. package/lib/typescript/api/linking/linking.d.ts +8 -0
  169. package/lib/typescript/api/linking/linking.d.ts.map +1 -1
  170. package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
  171. package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
  172. package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
  173. package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
  174. package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
  175. package/lib/typescript/api/theme/theme.d.ts +2 -0
  176. package/lib/typescript/api/theme/theme.d.ts.map +1 -1
  177. package/lib/typescript/engine/commands.d.ts +5 -0
  178. package/lib/typescript/engine/commands.d.ts.map +1 -1
  179. package/lib/typescript/engine/layout.d.ts.map +1 -1
  180. package/lib/typescript/engine/nativeScreens.d.ts +5 -0
  181. package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
  182. package/lib/typescript/engine/optionsChange.d.ts +28 -0
  183. package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
  184. package/lib/typescript/engine/refresh.d.ts +35 -0
  185. package/lib/typescript/engine/refresh.d.ts.map +1 -0
  186. package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
  187. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  188. package/lib/typescript/engine/screens.d.ts +7 -0
  189. package/lib/typescript/engine/screens.d.ts.map +1 -1
  190. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  191. package/lib/typescript/interfaces/options/animations.d.ts +40 -15
  192. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  193. package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
  194. package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
  195. package/lib/typescript/interfaces/options/modals.d.ts +6 -7
  196. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  197. package/lib/typescript/interfaces/options/stack.d.ts +8 -3
  198. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  199. package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
  200. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  201. package/metro/acceptPlugin.js +57 -0
  202. package/metro/babelTransformer.js +45 -0
  203. package/metro/index.js +36 -0
  204. package/package.json +3 -1
  205. package/src/ARCHITECTURE.md +9 -3
  206. package/src/adapters/NativeANNTurboModule.ts +7 -0
  207. package/src/api/app/AppRegistry.ts +16 -2
  208. package/src/api/app/createNavigation.ts +198 -66
  209. package/src/api/events/navigationEvents.ts +9 -3
  210. package/src/api/index.ts +1 -0
  211. package/src/api/linking/linking.ts +22 -1
  212. package/src/api/screens/defineScreen.ts +49 -5
  213. package/src/api/screens/nativeScreens.ts +40 -4
  214. package/src/api/screens/registerScreens.tsx +170 -42
  215. package/src/api/theme/theme.ts +5 -0
  216. package/src/engine/commands.ts +21 -0
  217. package/src/engine/layout.ts +11 -2
  218. package/src/engine/nativeScreens.ts +10 -1
  219. package/src/engine/options.ts +6 -8
  220. package/src/engine/optionsChange.ts +107 -0
  221. package/src/engine/refresh.ts +97 -0
  222. package/src/engine/rememberedOptions.ts +70 -0
  223. package/src/engine/screens.tsx +24 -0
  224. package/src/interfaces/ComponentEvents.ts +2 -2
  225. package/src/interfaces/options/animations.ts +40 -15
  226. package/src/interfaces/options/bottomTabs.ts +2 -2
  227. package/src/interfaces/options/buttons.ts +2 -2
  228. package/src/interfaces/options/modals.ts +6 -7
  229. package/src/interfaces/options/stack.ts +8 -3
  230. package/src/interfaces/options/topBar.ts +14 -0
  231. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
  232. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
  233. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
  234. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
  235. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
  236. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
@@ -7,17 +7,13 @@ import android.app.Activity
7
7
  import android.content.Context
8
8
  import android.content.pm.ApplicationInfo
9
9
  import android.graphics.Color
10
- import android.graphics.drawable.BitmapDrawable
11
10
  import android.graphics.drawable.Drawable
12
- import android.view.Menu
13
- import android.view.MenuItem
14
11
  import android.view.MotionEvent
15
12
  import android.view.View
16
13
  import android.view.ViewGroup
17
14
  import android.view.animation.LinearInterpolator
18
15
  import android.view.animation.PathInterpolator
19
16
  import android.widget.FrameLayout
20
- import android.widget.ImageView
21
17
  import androidx.appcompat.content.res.AppCompatResources
22
18
  import androidx.core.graphics.drawable.DrawableCompat
23
19
  import androidx.core.view.WindowInsetsCompat
@@ -59,6 +55,7 @@ import dev.apexnative.navigation.views.stack.topbar.TitleBar
59
55
  import dev.apexnative.navigation.views.stack.topbar.TopBar
60
56
  import dev.apexnative.navigation.views.stack.topbar.TopBarDefaults
61
57
  import dev.apexnative.navigation.views.stack.topbar.titlebar.BarButtonComponentCell
58
+ import dev.apexnative.navigation.views.stack.topbar.titlebar.BarMenu
62
59
  import dev.apexnative.navigation.views.stack.topbar.titlebar.ButtonBar
63
60
  import dev.apexnative.navigation.views.stack.topbar.titlebar.IconBackgroundDrawable
64
61
  import kotlin.math.roundToInt
@@ -142,8 +139,8 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
142
139
  this.layout = layout
143
140
  val bar = layout.topBar
144
141
  this.bar = bar
145
- leftButtons = ButtonBar(bar.leftButtonBar, true, ::loadIcon, Buttons())
146
- rightButtons = ButtonBar(bar.rightButtonBar, false, ::loadIcon, Buttons())
142
+ leftButtons = ButtonBar(bar.leftButtonBar, ::loadIcon, Buttons())
143
+ rightButtons = ButtonBar(bar.rightButtonBar, ::loadIcon, Buttons())
147
144
  visibility = TopBarVisibility(VisibilityTarget(bar) { collapse?.reset() })
148
145
  rowTransition = BarRowTransition(RowTarget(bar).also { rowTarget = it })
149
146
  collapse = TopBarCollapseBehavior(bar)
@@ -212,8 +209,8 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
212
209
  * bar as it is goes with [leaving], and the bar, which takes [incoming]'s options next, is drawn flat as [incoming]
213
210
  * moves — under [leaving] on a pop ([StackDisplay.BarTravel.Back]) (`StackLayout.barTravels`). No picture when the
214
211
  * bar doesn't show. Nothing travels when the new top hides the bar by a side of its own
215
- * (`animations.*.topBar.exit`), which plays instead, or when the bar can't be drawn in software (a `HARDWARE`
216
- * bitmap in it): the bar takes the options in its place.
212
+ * (`animations.*.topBar.exit`), which plays instead, or when the bar can't be recorded (a `HARDWARE` bitmap in it
213
+ * on Android 8): the bar takes the options in its place.
217
214
  */
218
215
  private fun beginTravel(
219
216
  bar: TopBar,
@@ -238,10 +235,10 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
238
235
 
239
236
  // The bar's picture where the bar is in the stack, its moves counted.
240
237
  private fun pictureOfBar(bar: TopBar): Drawable? {
241
- val bitmap = bar.picture() ?: return null
238
+ val picture = bar.picture() ?: return null
242
239
  val left = (bar.left + bar.translationX).roundToInt()
243
240
  val top = (bar.top + bar.translationY).roundToInt()
244
- return BitmapDrawable(bar.resources, bitmap).apply { setBounds(left, top, left + bitmap.width, top + bitmap.height) }
241
+ return picture.apply { setBounds(left, top, left + intrinsicWidth, top + intrinsicHeight) }
245
242
  }
246
243
 
247
244
  // The bar's travel ends: the picture goes, the bar is drawn in its place again with its shadow.
@@ -284,6 +281,8 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
284
281
  val bar = bar ?: return
285
282
  val screen = host.optionsScreen(entry)
286
283
  if (screen !== shownScreen) return apply(entry)
284
+ // An open menu has the colors of before.
285
+ if (delta.topBar.menuBackgroundColor.hasValue() || delta.topBar.menuTextColor.hasValue()) closeMenus()
287
286
  val options = host.resolvedOf(entry)
288
287
  applyAll(bar, entry, screen, options, merge = true, delta = delta)
289
288
  }
@@ -293,6 +292,8 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
293
292
  * now.
294
293
  */
295
294
  fun repaint(entry: ViewController<*>?) {
295
+ // An open menu has the old colors: it closes.
296
+ closeMenus()
296
297
  keys.clear()
297
298
  apply(entry)
298
299
  val options = entry?.let(host::resolvedOf) ?: return
@@ -582,16 +583,15 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
582
583
  keys[Part.Left] = leftKey
583
584
  reportWithoutId(topBar.buttons.left, "leftButtons")
584
585
  leftButtons?.set(
585
- left.map { button(screen, it, roots) },
586
+ ButtonPlaces.line(left, left = true).map { button(screen, it, roots) },
586
587
  emptyList(),
587
588
  null,
588
589
  merge && topBar.animateLeftButtons.isTrue,
589
590
  )
590
591
  }
591
592
  val overflow = topBar.overflowMenu?.takeIf { it.items.isNotEmpty() }
592
- val menu = bar.rightButtonBar.menu
593
593
  search.applyMagnifier()
594
- val taken = (0 until menu.size()).count { menu.getItem(it).itemId != Menu.NONE }
594
+ val taken = if (bar.rightButtonBar.trailingView != null) 1 else 0
595
595
  val configuration = context.resources.configuration
596
596
  val plan = ButtonPlaces.plan(
597
597
  right.map { ButtonPlaces.Button(ButtonPlaces.priorityOf(it.priority.get(null)), it.pinned.isTrue) },
@@ -612,12 +612,11 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
612
612
  keys[Part.Right] = rightKey
613
613
  reportWithoutId(topBar.buttons.right, "rightButtons")
614
614
  rightButtons?.set(
615
- plan.inBar.map { button(screen, right[it], roots) },
615
+ ButtonPlaces.line(plan.inBar, left = false).map { button(screen, right[it], roots) },
616
616
  plan.inOverflow.map { button(screen, right[it], roots) },
617
617
  overflow,
618
618
  merge && topBar.animateRightButtons.isTrue,
619
619
  )
620
- search.applyMagnifier()
621
620
  rightButtons?.setOverflowColor(overflowColor)
622
621
  }
623
622
  }
@@ -639,9 +638,9 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
639
638
  ).joinToString()
640
639
  if (keys[Part.Back] == key) return
641
640
  keys[Part.Back] = key
642
- val toolbar = bar.leftButtonBar
641
+ val row = bar.leftButtonBar
643
642
  if (!shown) {
644
- toolbar.navigationIcon = null
643
+ row.navigationIcon = null
645
644
  return
646
645
  }
647
646
  val own = back.icon.get(null)?.let(::loadIcon)?.mutate()
@@ -668,19 +667,13 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
668
667
  dp(background.height.get(0.0)),
669
668
  )
670
669
  } ?: icon
671
- toolbar.navigationIcon = drawable
672
- // Taking the icon, the navigation button gives it its own direction, which is stale while the button is new
673
- // or the bar's direction has just changed; the toolbar corrects it only after the icon was drawn. So the arrow
674
- // gets the bar's direction at once, on the drawable the button shows; a background passes it to the arrow.
675
- val held = toolbar.navigationIcon ?: drawable
676
- DrawableCompat.setLayoutDirection(held, arrowDirection)
677
- if (held !== drawable) DrawableCompat.setLayoutDirection(drawable, arrowDirection)
678
- toolbar.navigationContentDescription = label
679
- // The toolbar uses its navigation button again from screen to screen: its id is set every time.
680
- for (index in 0 until toolbar.childCount) {
681
- val child = toolbar.getChildAt(index)
682
- if ((child as? ImageView)?.drawable === held) TestIds.apply(child, testId)
683
- }
670
+ row.navigationIcon = drawable
671
+ // Taking the icon, the back button gives it its own direction, which is stale while the button is new or the
672
+ // bar's direction has just changed; the button corrects it only once its direction is resolved again. So the
673
+ // arrow gets the bar's direction at once; a background passes it to the arrow.
674
+ DrawableCompat.setLayoutDirection(drawable, arrowDirection)
675
+ row.navigationContentDescription = label
676
+ row.navigationButton?.let { TestIds.apply(it, testId) }
684
677
  }
685
678
 
686
679
  // The background (`topBar.background.component`).
@@ -719,15 +712,14 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
719
712
 
720
713
  private fun pictureUnderRow(bar: TopBar, leaving: ViewController<*>) {
721
714
  val view = leaving.peekView() ?: return
722
- val (bitmap, top) = bar.pictureUnderRow() ?: return
715
+ val (picture, top) = bar.pictureUnderRow() ?: return
723
716
  val barPlace = IntArray(2)
724
717
  val viewPlace = IntArray(2)
725
718
  bar.getLocationInWindow(barPlace)
726
719
  view.getLocationInWindow(viewPlace)
727
- val picture = BitmapDrawable(bar.resources, bitmap)
728
720
  val left = barPlace[0] - viewPlace[0]
729
721
  val y = barPlace[1] + top - viewPlace[1]
730
- picture.setBounds(left, y, left + bitmap.width, y + bitmap.height)
722
+ picture.setBounds(left, y, left + picture.intrinsicWidth, y + picture.intrinsicHeight)
731
723
  view.overlay.add(picture)
732
724
  underRow = leaving to picture
733
725
  }
@@ -1037,11 +1029,16 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
1037
1029
  return (buttons.left.orEmpty() + buttons.right.orEmpty()).firstOrNull { it.id.get(null) == buttonId }?.menu
1038
1030
  }
1039
1031
 
1040
- // The search's magnifier, the one item of the bar with an id of its own: `<topBar.searchBar.testID>.button`.
1041
- override fun testIdOf(item: MenuItem): String? {
1042
- if (item.itemId == Menu.NONE) return null
1043
- val entry = shownEntry ?: return null
1044
- return host.resolvedOf(entry).topBar.searchBar.testID.get(null)?.let { "$it.button" }
1032
+ // Read as a menu opens, so they follow light and dark mode: `menuBackgroundColor`, else the bar's color as it
1033
+ // shows (none when it lets something through), and then `menuTextColor` or else the title's color, the pair
1034
+ // the bar shows; with neither, the theme's.
1035
+ override fun menuColors(): BarMenu.Colors {
1036
+ val topBar = shownEntry?.let(host::resolvedOf)?.topBar ?: return BarMenu.Colors(null, null)
1037
+ val context = bar?.context ?: activity
1038
+ val text = topBar.menuTextColor.get(context, null)
1039
+ topBar.menuBackgroundColor.get(context, null)?.let { return BarMenu.Colors(it, text) }
1040
+ val barColor = bar?.barColor?.takeIf { Color.alpha(it) == OPAQUE } ?: return BarMenu.Colors(null, text)
1041
+ return BarMenu.Colors(barColor, text ?: topBar.title.color.get(context, null))
1045
1042
  }
1046
1043
  }
1047
1044
 
@@ -1183,6 +1180,7 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
1183
1180
 
1184
1181
  private companion object {
1185
1182
  const val DEFAULT_HEIGHT_DP = 64.0
1183
+ const val OPAQUE = 255
1186
1184
  const val DEFAULT_TITLE_SP = 22.0
1187
1185
  const val DEFAULT_SUBTITLE_SP = 14.0
1188
1186
  const val CENTER = "center"
@@ -2,150 +2,78 @@ package dev.apexnative.navigation.views.element
2
2
 
3
3
  import android.animation.Animator
4
4
  import android.animation.AnimatorListenerAdapter
5
- import android.graphics.Rect
6
5
  import android.os.Build
7
6
  import android.util.Log
8
7
  import android.view.View
9
8
  import android.view.ViewGroup
10
- import com.facebook.react.views.view.ReactViewGroup
11
9
  import dev.apexnative.navigation.options.ElementTransitionOptions
12
10
  import dev.apexnative.navigation.options.LayoutAnimation
13
11
  import dev.apexnative.navigation.options.SharedElementTransitionOptions
14
12
  import dev.apexnative.navigation.react.events.AppearanceDispatcher
15
13
  import dev.apexnative.navigation.utils.DevelopmentLog
16
14
  import dev.apexnative.navigation.utils.NavigationLog
15
+ import dev.apexnative.navigation.utils.SystemAnimations
17
16
  import dev.apexnative.navigation.viewcontrollers.viewcontroller.ViewController
18
17
  import dev.apexnative.navigation.views.animations.AnimationLayer
19
18
  import dev.apexnative.navigation.views.animations.SideAnimator
20
19
  import dev.apexnative.navigation.views.element.animators.SharedAnimatorFactory
20
+ import java.util.Collections
21
+ import java.util.WeakHashMap
21
22
 
22
23
  /**
23
- * The elements of one transition: the element transitions and the targets of the shared elements found by their
24
- * `nativeID` (depth first, the first match) in the whole view of the screen each side shows — an element in the screen
25
- * that goes first, then in the one that comes; a pair's `fromId` in the screen that goes, its `toId` in the one that
26
- * comes; each screen is walked once for all of them ([TransitionViews]). What isn't found, what is in another
27
- * transition's layer and an image that isn't loaded is skipped; so is an element whose start fails ([elementStep]):
28
- * it goes back at once and the rest plays. Each view found moves into a [TransitionLayer] at its place on the screen,
29
- * in the order its hierarchy draws it, and an [ElementStub] takes its index in its parent. A pair's `from` stays where
30
- * it is, hidden while its copy flies. [finish] puts every view back at its index with its layout, its pivot and the
31
- * values of its properties read before — unless React removed its stub meanwhile: then it is gone for good —, shows
32
- * the `from`s again and takes the layer away. Main thread only.
24
+ * The elements of one transition: the element transitions and the shared pairs found by their `nativeID` (depth first,
25
+ * the first match) in the whole view of the screen each side shows — an element in the screen that goes first, then in
26
+ * the one that comes; a pair's `fromId` in the screen that goes, its `toId` in the one that comes; each screen is
27
+ * walked once for all of them ([TransitionViews]). What isn't found, what another transition plays now and an image
28
+ * that isn't loaded is skipped; so is an element whose start fails ([elementStep]): it gets its values back at once and
29
+ * the rest plays. No view leaves its parent, as on iOS. An element plays where it is, in its screen: its properties
30
+ * animate about its center and it moves with its screen. A pair flies as a copy drawn in a [TransitionLayer] over the
31
+ * screens, the copies in the order their targets are drawn; its `to` is hidden from the start, its `from` from the
32
+ * start of its flight. [finish] gives every element the values of its properties read before, shows the pairs' views
33
+ * again and takes the layer away. Main thread only.
33
34
  */
34
- internal class ElementTransition private constructor(private val layer: TransitionLayer) {
35
- // A view in the layer: where it came from and what it was.
36
- private class Moved(
37
- val view: View,
38
- val stub: ElementStub,
39
- val params: ViewGroup.LayoutParams,
40
- val frame: Rect,
41
- val values: FloatArray,
42
- val pivotSet: Boolean,
43
- // Where its parent's origin is in the layer: what `x` and `y` are counted from.
44
- val originX: Int,
45
- val originY: Int,
46
- )
35
+ internal class ElementTransition private constructor(
36
+ private val layer: TransitionLayer?,
37
+ // The views of the screens, as far as the parents that cut a pair's views count.
38
+ private val fromRoot: View?,
39
+ private val toRoot: View?,
40
+ ) {
41
+ // An element playing where it is: what it was.
42
+ private class InPlace(val view: View, val values: FloatArray, val pivotSet: Boolean)
47
43
 
48
- private val moved = ArrayList<Moved>()
49
- private val hidden = ArrayList<Pair<View, Float>>()
44
+ private val inPlace = ArrayList<InPlace>()
45
+ private val taken = ArrayList<View>()
46
+ // The views of the pairs hidden while their copies fly, with the alpha each had.
47
+ private val hidden = LinkedHashMap<View, Float>()
50
48
  private var finished = false
51
49
 
52
50
  /** The animators of the elements and of the shared elements, each with its own time. */
53
51
  val animators = ArrayList<Animator>()
54
52
 
55
- /** Every view back, the `from`s seen, the layer gone; at the end of the transition and at any stop. Once. */
53
+ /**
54
+ * Every element's values back, the pairs' views seen, the layer gone; at the end of the transition and at any stop.
55
+ * Once.
56
+ */
56
57
  fun finish() {
57
58
  if (finished) return
58
59
  finished = true
59
- for ((view, alpha) in hidden) view.alpha = alpha
60
+ for ((view, alpha) in hidden) show(view, alpha)
60
61
  hidden.clear()
61
- // Each view goes back on its own: one that can't (its parent gone wrong) doesn't keep the others in the layer.
62
- for (item in moved.asReversed()) {
62
+ // Each element on its own: one that can't get its values back doesn't keep the others from theirs.
63
+ for (item in inPlace.asReversed()) {
63
64
  try {
64
- giveBack(item)
65
+ restore(item.view, item.values, item.pivotSet)
65
66
  } catch (e: RuntimeException) {
66
- Log.w(NavigationLog.TAG, "[ApexNavigation] An element of a transition couldn't go back", e)
67
+ Log.w(NavigationLog.TAG, "[ApexNavigation] An element of a transition couldn't get its values back", e)
67
68
  }
68
69
  }
69
- moved.clear()
70
- layer.detach()
70
+ inPlace.clear()
71
+ playing -= taken.toSet()
72
+ taken.clear()
73
+ layer?.detach()
71
74
  }
72
75
 
73
- private fun moveIntoLayer(view: View): Moved? {
74
- val parent = view.parent as? ViewGroup ?: return null
75
- // A parent that clips its subviews keeps its own list of children, which a stub swapped in would leave
76
- // behind: its element stays where it is.
77
- if ((parent as? ReactViewGroup)?.removeClippedSubviews == true) return null
78
- val index = parent.indexOfChild(view)
79
- if (index < 0) return null
80
- val onScreen = IntArray(2)
81
- parent.getLocationOnScreen(onScreen)
82
- val origin = layer.originOnScreen
83
- val originX = onScreen[0] - parent.scrollX - origin.x
84
- val originY = onScreen[1] - parent.scrollY - origin.y
85
- val frame = Rect(view.left, view.top, view.right, view.bottom)
86
- val params = view.layoutParams ?: ViewGroup.LayoutParams(view.width, view.height)
87
- val pivotSet = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && view.isPivotSet
88
- val values = floatArrayOf(
89
- view.alpha, view.translationX, view.translationY, view.scaleX, view.scaleY, view.rotation,
90
- view.rotationX, view.rotationY, view.pivotX, view.pivotY,
91
- )
92
- val stub = ElementStub(view.context, view)
93
- try {
94
- parent.removeViewAt(index)
95
- parent.addView(stub, index, params)
96
- stub.layout(frame.left, frame.top, frame.right, frame.bottom)
97
- val left = originX + frame.left
98
- val top = originY + frame.top
99
- layer.put(view, left, top, frame.width(), frame.height())
100
- } catch (e: RuntimeException) {
101
- putBack(view, stub, parent, index, params, frame)
102
- throw e
103
- }
104
- // Scale and rotations turn about the center.
105
- view.pivotX = frame.width() / 2f
106
- view.pivotY = frame.height() / 2f
107
- return Moved(view, stub, params, frame, values, pivotSet, originX, originY).also { moved += it }
108
- }
109
-
110
- // A move that failed half way undone: the view back at its index in its parent, the stub gone.
111
- private fun putBack(
112
- view: View,
113
- stub: ElementStub,
114
- parent: ViewGroup,
115
- index: Int,
116
- params: ViewGroup.LayoutParams,
117
- frame: Rect,
118
- ) {
119
- if (view.parent === layer) layer.removeView(view)
120
- val stubIndex = parent.indexOfChild(stub)
121
- if (stubIndex >= 0) parent.removeViewAt(stubIndex)
122
- if (view.parent == null) {
123
- parent.addView(view, index.coerceAtMost(parent.childCount), params)
124
- view.layout(frame.left, frame.top, frame.right, frame.bottom)
125
- }
126
- }
127
-
128
- private fun giveBack(item: Moved) {
129
- val view = item.view
130
- // A commit of Fabric meanwhile laid it out in its parent's coordinates: the newest such frame holds.
131
- val reactFrame = if (view.parent === layer) layer.frameFromReact(view) else null
132
- if (view.parent === layer) layer.removeView(view)
133
- restore(view, item)
134
- val parent = item.stub.parent as? ViewGroup ?: return
135
- val index = parent.indexOfChild(item.stub)
136
- if (index < 0) return
137
- parent.removeViewAt(index)
138
- parent.addView(view, index, item.params)
139
- val frame = reactFrame ?: item.frame
140
- view.measure(
141
- View.MeasureSpec.makeMeasureSpec(frame.width(), View.MeasureSpec.EXACTLY),
142
- View.MeasureSpec.makeMeasureSpec(frame.height(), View.MeasureSpec.EXACTLY),
143
- )
144
- view.layout(frame.left, frame.top, frame.right, frame.bottom)
145
- }
146
-
147
- private fun restore(view: View, item: Moved) {
148
- val values = item.values
76
+ private fun restore(view: View, values: FloatArray, pivotSet: Boolean) {
149
77
  view.alpha = values[ALPHA]
150
78
  view.translationX = values[TRANSLATION_X]
151
79
  view.translationY = values[TRANSLATION_Y]
@@ -154,7 +82,7 @@ internal class ElementTransition private constructor(private val layer: Transiti
154
82
  view.rotation = values[ROTATION]
155
83
  view.rotationX = values[ROTATION_X]
156
84
  view.rotationY = values[ROTATION_Y]
157
- if (!item.pivotSet && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
85
+ if (!pivotSet && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
158
86
  view.resetPivot()
159
87
  } else {
160
88
  view.pivotX = values[PIVOT_X]
@@ -165,50 +93,71 @@ internal class ElementTransition private constructor(private val layer: Transiti
165
93
  // An element with views in it fades through a hardware layer only where its alpha would draw it into an offscreen
166
94
  // buffer at every frame (AnimationLayer: its rendering overlaps). React's views overlap only with
167
95
  // `needsOffscreenAlphaCompositing`, so most elements fade without one and nothing they draw outside their bounds (a
168
- // box shadow) is clipped. A view without children fades its own drawing at no extra cost.
169
- private fun addElement(options: ElementTransitionOptions, item: Moved) {
170
- val base = SideAnimator.Base(xOffset = item.originX.toFloat(), yOffset = item.originY.toFloat())
171
- val animator = SideAnimator.build(item.view, options, base) ?: return
172
- if (item.view is ViewGroup) AnimationLayer.during(item.view, animator)
96
+ // box shadow) is clipped. A view without children fades its own drawing at no extra cost. Scale and rotations turn
97
+ // about the element's center; `x` and `y` are its place in its own parent.
98
+ private fun addElement(options: ElementTransitionOptions, view: View) {
99
+ inPlace += InPlace(view, valuesOf(view), pivotSetOf(view))
100
+ view.pivotX = view.width / 2f
101
+ view.pivotY = view.height / 2f
102
+ val animator = SideAnimator.build(view, options, SideAnimator.Base()) ?: return
103
+ if (view is ViewGroup) AnimationLayer.during(view, animator)
173
104
  animators += animator
174
105
  }
175
106
 
176
- // The pair's flight: `to` hidden until it starts, `from` hidden from its start; false when it doesn't fly.
107
+ // An element whose start failed gets its values back now.
108
+ private fun giveValuesBack(view: View) {
109
+ val item = inPlace.lastOrNull { it.view === view } ?: return
110
+ inPlace -= item
111
+ try {
112
+ restore(view, item.values, item.pivotSet)
113
+ } catch (e: RuntimeException) {
114
+ Log.w(NavigationLog.TAG, "[ApexNavigation] An element of a transition couldn't get its values back", e)
115
+ }
116
+ }
117
+
118
+ // The pair's flight: `to` hidden from now, `from` from its start, both seen again when it stops or its copy can't
119
+ // be drawn. Nothing changes when it doesn't fly.
177
120
  private fun addPair(
178
121
  options: SharedElementTransitionOptions,
179
122
  from: View,
180
- item: Moved,
181
- toParent: View?,
123
+ to: View,
124
+ name: String,
182
125
  fallbackDuration: Long,
183
- ): Boolean {
184
- val to = item.view
126
+ ) {
127
+ val layer = layer ?: return
185
128
  val flight = SharedAnimatorFactory.create(
186
- from, to, toParent, item.stub, layer, options, fallbackDuration, DevelopmentLog::warn,
187
- ) ?: return false
188
- val alpha = item.values[ALPHA]
129
+ from, to, fromRoot, toRoot, layer, options, fallbackDuration, DevelopmentLog::warn,
130
+ ) ?: return
189
131
  val fromAlpha = from.alpha
190
- to.alpha = 0f
191
- hidden += from to fromAlpha
132
+ val seenAgain = {
133
+ flight.picture.shown = false
134
+ hidden.remove(from)?.let { show(from, it) }
135
+ hidden.remove(to)?.let { show(to, it) }
136
+ layer.invalidate()
137
+ }
138
+ flight.picture.onFailed = { e ->
139
+ elementSkipped(name, e.toString())
140
+ seenAgain()
141
+ }
192
142
  flight.animator.addListener(object : AnimatorListenerAdapter() {
193
143
  private var stopped = false
194
144
 
195
145
  override fun onAnimationStart(animation: Animator) {
196
- if (stopped) return
146
+ if (stopped || finished) return
147
+ hidden[from] = fromAlpha
197
148
  from.alpha = 0f
198
- if (!flight.drawsPictures) to.alpha = alpha
199
149
  }
200
150
 
201
- // A flight whose start or frame failed stops (`MoveAnimator`), maybe inside its own start: `from` shows
202
- // again in its screen, and a `to` that moves itself in its place, as at any stop (a flight of pictures
203
- // shows its `to` at its end).
151
+ // A flight whose start or frame failed stops (`MoveAnimator`), maybe inside its own start.
204
152
  override fun onAnimationCancel(animation: Animator) {
205
153
  stopped = true
206
- from.alpha = fromAlpha
207
- if (!flight.drawsPictures) to.alpha = alpha
154
+ seenAgain()
208
155
  }
209
156
  })
157
+ layer.add(flight.picture)
158
+ hidden[to] = to.alpha
159
+ to.alpha = 0f
210
160
  animators += flight.animator
211
- return true
212
161
  }
213
162
 
214
163
  companion object {
@@ -223,10 +172,26 @@ internal class ElementTransition private constructor(private val layer: Transiti
223
172
  private const val PIVOT_X = 8
224
173
  private const val PIVOT_Y = 9
225
174
 
175
+ // The views the transitions that run now play: another transition leaves them alone.
176
+ private val playing: MutableSet<View> = Collections.newSetFromMap(WeakHashMap())
177
+
178
+ private fun valuesOf(view: View) = floatArrayOf(
179
+ view.alpha, view.translationX, view.translationY, view.scaleX, view.scaleY, view.rotation,
180
+ view.rotationX, view.rotationY, view.pivotX, view.pivotY,
181
+ )
182
+
183
+ private fun pivotSetOf(view: View) = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && view.isPivotSet
184
+
185
+ // A view hidden for a flight shows again with its alpha, unless React gave it another one meanwhile. React
186
+ // writes the same alpha: an opacity it commits during the flight shows the view beside its copy until the end.
187
+ private fun show(view: View, alpha: Float) {
188
+ if (view.alpha == 0f) view.alpha = alpha
189
+ }
190
+
226
191
  /**
227
- * The elements of [animation] from [from] to [to], moved into a layer that joins [host] at [layerIndex] (its
228
- * top without one); none when none of them is found. A shared element without a duration takes
229
- * [fallbackDuration], the screen's fade.
192
+ * The elements of [animation] from [from] to [to]; the copies of the pairs in a layer that joins [host] at
193
+ * [layerIndex] (its top without one). None when none of them is found or the system's animations are off. A
194
+ * shared element without a duration takes [fallbackDuration], the screen's fade.
230
195
  */
231
196
  fun start(
232
197
  animation: LayoutAnimation,
@@ -236,6 +201,7 @@ internal class ElementTransition private constructor(private val layer: Transiti
236
201
  layerIndex: Int?,
237
202
  fallbackDuration: Long,
238
203
  ): ElementTransition? {
204
+ if (SystemAnimations.areOff(host.context)) return null
239
205
  val fromView = from?.let { AppearanceDispatcher.visibleNode(it).peekView() }
240
206
  val toView = AppearanceDispatcher.visibleNode(to).peekView()
241
207
  val transitions = animation.elementTransitions.transitions
@@ -245,27 +211,28 @@ internal class ElementTransition private constructor(private val layer: Transiti
245
211
  val elements = ArrayList<Pair<ElementTransitionOptions, View>>()
246
212
  for (element in transitions) {
247
213
  if (element.id.isEmpty()) continue
248
- val view = found(views.leaving(element.id)) ?: found(views.arriving(element.id)) ?: continue
249
- if (TransitionLayer.holds(view) || !taken.add(view)) continue
214
+ val view = views.leaving(element.id) ?: views.arriving(element.id) ?: continue
215
+ if (view in playing || !taken.add(view)) continue
250
216
  elements += element to view
251
217
  }
252
218
  val pairs = ArrayList<Triple<SharedElementTransitionOptions, View, View>>()
253
219
  for (pair in shared) {
254
220
  val fromId = pair.fromId.get(null) ?: continue
255
221
  val toId = pair.toId.get(null) ?: continue
256
- val source = found(views.leaving(fromId)) ?: continue
257
- val target = found(views.arriving(toId)) ?: continue
258
- if (source === target || TransitionLayer.holds(source) || TransitionLayer.holds(target)) continue
222
+ val source = views.leaving(fromId) ?: continue
223
+ val target = views.arriving(toId) ?: continue
224
+ if (source === target || source in playing || target in playing) continue
259
225
  if (target in taken || source in taken) continue
260
226
  if (!loaded(source, fromId) || !loaded(target, toId)) continue
227
+ taken += source
261
228
  taken += target
262
229
  pairs += Triple(pair, source, target)
263
230
  }
264
231
  if (elements.isEmpty() && pairs.isEmpty()) return null
265
- val layer = TransitionLayer(host.context)
266
- val transition = ElementTransition(layer)
232
+ val layer = if (pairs.isEmpty()) null else TransitionLayer(host.context)
233
+ val transition = ElementTransition(layer, fromView, toView)
267
234
  try {
268
- layer.attachTo(host, layerIndex ?: host.childCount)
235
+ layer?.attachTo(host, layerIndex ?: host.childCount)
269
236
  transition.fill(host, elements, pairs, fallbackDuration)
270
237
  } catch (e: RuntimeException) {
271
238
  transition.finish()
@@ -275,8 +242,8 @@ internal class ElementTransition private constructor(private val layer: Transiti
275
242
  }
276
243
 
277
244
  /**
278
- * [start], and when that fails, a line in the log and none: the screens play without their elements; every view
279
- * moved is back already.
245
+ * [start], and when that fails, a line in the log and none: the screens play without their elements; every
246
+ * element has its values back already.
280
247
  */
281
248
  fun startOrLog(
282
249
  animation: LayoutAnimation,
@@ -292,8 +259,6 @@ internal class ElementTransition private constructor(private val layer: Transiti
292
259
  null
293
260
  }
294
261
 
295
- private fun found(view: View?): View? = view?.takeUnless { it is ElementStub }
296
-
297
262
  // An image not loaded drops out of the transition; its wait logged it already, else it is logged now.
298
263
  private fun loaded(view: View, id: String): Boolean {
299
264
  if (ImageLoads.isLoaded(view)) return true
@@ -302,45 +267,23 @@ internal class ElementTransition private constructor(private val layer: Transiti
302
267
  }
303
268
  }
304
269
 
305
- // The views into the layer in the order they are drawn, then their animators; a pair that doesn't fly goes back at
306
- // once. Each element's steps run on their own ([elementStep]): one that fails goes back, the others play.
270
+ // The elements' animators where they are, then the pairs' flights, their copies in the order their targets are
271
+ // drawn. Each element's steps run on their own ([elementStep]): one that fails is left as it was, the others play.
307
272
  private fun fill(
308
273
  host: ViewGroup,
309
274
  elements: List<Pair<ElementTransitionOptions, View>>,
310
275
  pairs: List<Triple<SharedElementTransitionOptions, View, View>>,
311
276
  fallbackDuration: Long,
312
277
  ) {
313
- val names = HashMap<View, String>()
314
- for ((options, view) in elements) names[view] = elementName(options.id)
315
- for ((options, _, target) in pairs) names[target] = pairName(options.fromId.get(""), options.toId.get(""))
316
- val views = elements.map { it.second } + pairs.map { it.third }
317
- val parents = views.associateWith { it.parent as? View }
318
- val placed = HashMap<View, Moved>()
319
- for (view in DrawOrder.sorted(views, host) { it }) {
320
- var item: Moved? = null
321
- if (elementStep(names.getValue(view)) { item = moveIntoLayer(view) }) item?.let { placed[view] = it }
322
- }
278
+ for ((_, view) in elements) taken += view
279
+ for ((_, source, target) in pairs) taken += listOf(source, target)
280
+ playing += taken
323
281
  for ((options, view) in elements) {
324
- val item = placed[view] ?: continue
325
- if (!elementStep(names.getValue(view)) { addElement(options, item) }) goBack(item)
326
- }
327
- for ((options, source, target) in pairs) {
328
- val item = placed[target] ?: continue
329
- var flies = false
330
- elementStep(names.getValue(target)) {
331
- flies = addPair(options, source, item, parents[target], fallbackDuration)
332
- }
333
- if (!flies) goBack(item)
282
+ if (!elementStep(elementName(options.id)) { addElement(options, view) }) giveValuesBack(view)
334
283
  }
335
- }
336
-
337
- // An element that doesn't play leaves the layer now; one that can't go back is logged, the others go on.
338
- private fun goBack(item: Moved) {
339
- moved -= item
340
- try {
341
- giveBack(item)
342
- } catch (e: RuntimeException) {
343
- Log.w(NavigationLog.TAG, "[ApexNavigation] An element of a transition couldn't go back", e)
284
+ for ((options, source, target) in DrawOrder.sorted(pairs, host) { it.third }) {
285
+ val name = pairName(options.fromId.get(""), options.toId.get(""))
286
+ elementStep(name) { addPair(options, source, target, name, fallbackDuration) }
344
287
  }
345
288
  }
346
289
  }