@apexnative/navigation 1.0.0-alpha.1 → 1.0.0-alpha.2

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 (125) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +147 -51
  3. package/android/CLAUDE.md +10 -4
  4. package/android/CORE.md +4 -1
  5. package/android/RECIPES.md +4 -3
  6. package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +5 -4
  7. package/android/src/main/java/dev/apexnative/navigation/react/ErrorTexts.kt +75 -60
  8. package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +3 -2
  9. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +1 -1
  10. package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +5 -4
  11. package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +7 -6
  12. package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +3 -35
  13. package/android/src/main/java/dev/apexnative/navigation/react/events/EventContract.kt +226 -0
  14. package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +101 -87
  15. package/android/src/main/java/dev/apexnative/navigation/react/events/ScreenAppearance.kt +0 -6
  16. package/android/src/main/java/dev/apexnative/navigation/utils/UiUtils.kt +3 -3
  17. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabItems.kt +3 -3
  18. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +12 -8
  19. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +12 -11
  20. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +2 -2
  21. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +25 -0
  22. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +1 -1
  23. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +1 -3
  24. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +4 -7
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuPresenter.kt +16 -7
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuRules.kt +8 -1
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +2 -1
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +2 -1
  29. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +5 -5
  30. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStep.kt +31 -0
  31. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +85 -27
  32. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +5 -4
  33. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionTargets.kt +9 -6
  34. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionViews.kt +100 -0
  35. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +45 -1
  36. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +3 -5
  37. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +3 -5
  38. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +3 -5
  39. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +2 -0
  40. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +4 -6
  41. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +4 -6
  42. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +4 -3
  43. package/cli/app.js +1 -1
  44. package/cli/project.js +6 -2
  45. package/cli/setup/index.js +13 -0
  46. package/cli/setup/steps/appDelegate.js +66 -11
  47. package/cli/setup/steps/sceneDelegate.js +108 -0
  48. package/docs-md/architecture/transitions.md +1 -1
  49. package/docs-md/guides/deep-links.md +15 -33
  50. package/docs-md/start/installation.md +1 -0
  51. package/ios/Animations/ANNTransitionAnimator.mm +13 -11
  52. package/ios/Animations/ANNTransitionWait.mm +6 -7
  53. package/ios/Animations/Elements/ANNElementFinder.h +57 -0
  54. package/ios/Animations/Elements/ANNElementFinder.mm +174 -2
  55. package/ios/Animations/Elements/ANNElementTransition.h +7 -5
  56. package/ios/Animations/Elements/ANNElementTransition.mm +17 -19
  57. package/ios/Animations/Elements/ANNSharedElementFlight.h +13 -8
  58. package/ios/Animations/Elements/ANNSharedElementFlight.mm +59 -24
  59. package/ios/CLAUDE.md +10 -2
  60. package/ios/CORE.md +3 -2
  61. package/ios/Contract/ANNCommandAnswer.h +4 -3
  62. package/ios/Contract/ANNCommandAnswer.mm +12 -2
  63. package/ios/Contract/ANNCommandRunner.h +2 -2
  64. package/ios/Contract/ANNCommandRunner.mm +1 -1
  65. package/ios/Contract/ANNErrorTexts.h +6 -2
  66. package/ios/Contract/ANNErrorTexts.mm +18 -10
  67. package/ios/Core/ANNDeepLinkQueue.h +20 -1
  68. package/ios/Core/ANNDeepLinkQueue.mm +107 -12
  69. package/ios/Core/ANNErrorHandler.h +8 -3
  70. package/ios/Core/ANNErrorHandler.mm +17 -1
  71. package/ios/Core/ANNEventContract.h +246 -0
  72. package/ios/Core/ANNEventContract.mm +225 -0
  73. package/ios/Core/ANNEventEmitter.h +5 -1
  74. package/ios/Core/ANNEventEmitter.mm +117 -80
  75. package/ios/Core/ANNHinge.mm +6 -4
  76. package/ios/Core/ANNSceneDelegate.mm +25 -15
  77. package/ios/Core/ANNSystemUI.h +3 -2
  78. package/ios/Core/ANNSystemUI.mm +7 -0
  79. package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +6 -4
  80. package/ios/Layouts/Modal/ANNModalCoordinator.mm +1 -1
  81. package/ios/Layouts/Modal/ANNModalPresentation.h +8 -5
  82. package/ios/Layouts/Modal/ANNModalPresentation.mm +12 -12
  83. package/ios/Layouts/Modal/ANNSheetPresenter.mm +83 -1
  84. package/ios/Layouts/Root/ANNRootCoordinator.mm +12 -3
  85. package/ios/Layouts/Screen/ANNComponentPresenter.h +11 -3
  86. package/ios/Layouts/Screen/ANNComponentPresenter.mm +34 -6
  87. package/ios/Layouts/Screen/ANNComponentViewController.mm +28 -7
  88. package/ios/Layouts/Screen/ANNNativeScreenContext.mm +7 -5
  89. package/ios/Layouts/Screen/ANNScreenInsets.mm +6 -4
  90. package/ios/Layouts/Screen/ANNScreenRegions.mm +9 -6
  91. package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -7
  92. package/ios/Options/ANNSheetOptions.h +4 -1
  93. package/ios/Public/ApexNavigation/ANNSceneDelegate.h +3 -0
  94. package/ios/RECIPES.md +2 -1
  95. package/ios/TurboModules/ANNTurboEventEmitter.mm +4 -35
  96. package/ios/TurboModules/ANNTurboModule.mm +5 -2
  97. package/lib/module/ARCHITECTURE.md +7 -4
  98. package/lib/module/api/app/createNavigation.js +2 -0
  99. package/lib/module/api/app/createNavigation.js.map +1 -1
  100. package/lib/module/api/linking/dispatchedLinks.js +23 -0
  101. package/lib/module/api/linking/dispatchedLinks.js.map +1 -0
  102. package/lib/module/interfaces/contract/errors.json +275 -0
  103. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  104. package/lib/typescript/api/linking/dispatchedLinks.d.ts +7 -0
  105. package/lib/typescript/api/linking/dispatchedLinks.d.ts.map +1 -0
  106. package/lib/typescript/interfaces/Events.d.ts +9 -0
  107. package/lib/typescript/interfaces/Events.d.ts.map +1 -1
  108. package/lib/typescript/interfaces/options/animations.d.ts +17 -13
  109. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  110. package/lib/typescript/interfaces/options/modals.d.ts +15 -11
  111. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  112. package/lib/typescript/interfaces/options/sideMenu.d.ts +7 -6
  113. package/lib/typescript/interfaces/options/sideMenu.d.ts.map +1 -1
  114. package/lib/typescript/interfaces/options/system.d.ts +4 -3
  115. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  116. package/package.json +1 -1
  117. package/src/ARCHITECTURE.md +7 -4
  118. package/src/api/app/createNavigation.ts +2 -0
  119. package/src/api/linking/dispatchedLinks.ts +18 -0
  120. package/src/interfaces/Events.ts +10 -0
  121. package/src/interfaces/contract/errors.json +275 -0
  122. package/src/interfaces/options/animations.ts +17 -13
  123. package/src/interfaces/options/modals.ts +15 -11
  124. package/src/interfaces/options/sideMenu.ts +7 -6
  125. package/src/interfaces/options/system.ts +4 -3
@@ -7,6 +7,7 @@ import com.facebook.react.ReactApplication
7
7
  import com.facebook.react.bridge.Arguments
8
8
  import com.facebook.react.bridge.WritableArray
9
9
  import com.facebook.react.bridge.WritableMap
10
+ import dev.apexnative.navigation.react.events.EventContract
10
11
  import dev.apexnative.navigation.react.events.EventEmitter
11
12
  import dev.apexnative.navigation.utils.WindowFolds
12
13
  import java.util.concurrent.ConcurrentHashMap
@@ -70,17 +71,17 @@ internal object ScreenRegions {
70
71
  }
71
72
 
72
73
  private fun toMap(value: Regions): WritableMap = Arguments.createMap().apply {
73
- putArray("division", toArray(value.division))
74
- putArray("occlusion", toArray(value.occlusion))
74
+ putArray(EventContract.ScreenRegionsChanged.DIVISION, toArray(value.division))
75
+ putArray(EventContract.ScreenRegionsChanged.OCCLUSION, toArray(value.occlusion))
75
76
  }
76
77
 
77
78
  private fun toArray(regions: List<Region>): WritableArray = Arguments.createArray().apply {
78
79
  for (region in regions) {
79
80
  pushMap(Arguments.createMap().apply {
80
- putDouble("x", region.x)
81
- putDouble("y", region.y)
82
- putDouble("width", region.width)
83
- putDouble("height", region.height)
81
+ putDouble(EventContract.ScreenRegionRect.X, region.x)
82
+ putDouble(EventContract.ScreenRegionRect.Y, region.y)
83
+ putDouble(EventContract.ScreenRegionRect.WIDTH, region.width)
84
+ putDouble(EventContract.ScreenRegionRect.HEIGHT, region.height)
84
85
  })
85
86
  }
86
87
  }
@@ -210,43 +210,18 @@ internal object AppearanceDispatcher {
210
210
  // A transition whose caller sends the state itself ends: the DidDisappears it sends ask for no update.
211
211
  private var holdsState = false
212
212
 
213
- // A side menu's drawer jumps open: the appearances that start in it send no WillAppear.
214
- private var skipsWillAppear = false
215
-
216
213
  // The native screens that told JavaScript they went: once per node.
217
214
  private val released: MutableSet<ViewController<*>> = Collections.newSetFromMap(WeakHashMap())
218
215
 
219
216
  // Appearance of a record.
220
217
 
221
- /**
222
- * The record starts to appear: WillAppear, now when it is ready, else held. Only once per appearance; none inside
223
- * [withoutWillAppear].
224
- */
218
+ /** The record starts to appear: WillAppear, now when it is ready, else held. Only once per appearance. */
225
219
  fun willAppear(record: ScreenAppearance) {
226
220
  if (record.phase != Phase.Hidden) return
227
221
  record.phase = Phase.Appearing
228
- if (skipsWillAppear) {
229
- record.skippedWill = true
230
- return
231
- }
232
222
  if (record.isReady) send(record, Event.WillAppear) else hold(record, Event.WillAppear)
233
223
  }
234
224
 
235
- /**
236
- * Runs [block], in which the appearances that start send no WillAppear: their DidAppear is their first event, and
237
- * their DidDisappear goes only once that DidAppear went. A side menu's drawer that opens without animation does so
238
- * on Android; the screens' options and the components of their bars go as at any appearance.
239
- */
240
- fun withoutWillAppear(block: () -> Unit) {
241
- val outer = skipsWillAppear
242
- skipsWillAppear = true
243
- try {
244
- block()
245
- } finally {
246
- skipsWillAppear = outer
247
- }
248
- }
249
-
250
225
  /**
251
226
  * The record has appeared: DidAppear, after a WillAppear (asked now when it wasn't), now when nothing is held and
252
227
  * it is ready, else held behind what is held. Only once per appearance.
@@ -261,19 +236,12 @@ internal object AppearanceDispatcher {
261
236
  /**
262
237
  * The record has disappeared. Nothing when it didn't appear. When its WillAppear is still held, JavaScript never
263
238
  * heard it appear: what is held goes away and nothing is sent. Otherwise a held DidAppear goes away and
264
- * DidDisappear is sent: a swipe that was cancelled gives WillAppear, then DidDisappear. An appearance without its
265
- * WillAppear ([withoutWillAppear]) appeared once its DidAppear went.
239
+ * DidDisappear is sent: a swipe that was cancelled gives WillAppear, then DidDisappear.
266
240
  */
267
241
  fun didDisappear(record: ScreenAppearance) {
268
242
  if (record.phase == Phase.Hidden) return
269
- val wasShown = record.phase == Phase.Shown
270
243
  record.phase = Phase.Hidden
271
- val appeared = if (record.skippedWill) {
272
- wasShown && Event.DidAppear !in record.held
273
- } else {
274
- Event.WillAppear !in record.held
275
- }
276
- record.skippedWill = false
244
+ val appeared = Event.WillAppear !in record.held
277
245
  record.held.clear()
278
246
  cancelLimit(record)
279
247
  // What waited for its DidAppear goes now: none will be sent for this appearance.
@@ -0,0 +1,226 @@
1
+ // Made by scripts/contract.mjs from the event types of src/interfaces/: change them and run the script again.
2
+
3
+ package dev.apexnative.navigation.react.events
4
+
5
+ /**
6
+ * The names of the events `ANN.<Name>` ([EVENT]) and the keys of their payloads, as the types of `src/interfaces/` give
7
+ * them; what each field means is its JSDoc there. The object types a field holds have keys of their own. The sender
8
+ * and the builders of payloads take every name and key from here: `EventContract.CommandCompleted.COMMAND_ID`. The
9
+ * events and fields only iOS sends are not here.
10
+ */
11
+ internal object EventContract {
12
+ /** `ANN.AppLaunched`. */
13
+ object AppLaunched {
14
+ const val EVENT = "ANN.AppLaunched"
15
+ }
16
+
17
+ /** `ANN.ArrangementChanged`. */
18
+ object ArrangementChanged {
19
+ const val EVENT = "ANN.ArrangementChanged"
20
+ const val COMPONENT_ID = "componentId"
21
+ const val PLACEMENT = "placement"
22
+ const val LAYOUT = "layout"
23
+ const val FRONT = "front"
24
+ const val HIDDEN = "hidden"
25
+ }
26
+
27
+ /** `ANN.BottomTabLongPressed`. */
28
+ object BottomTabLongPressed {
29
+ const val EVENT = "ANN.BottomTabLongPressed"
30
+ const val SELECTED_TAB_INDEX = "selectedTabIndex"
31
+ const val BOTTOM_TABS_ID = "bottomTabsId"
32
+ }
33
+
34
+ /** `ANN.BottomTabPressed`. */
35
+ object BottomTabPressed {
36
+ const val EVENT = "ANN.BottomTabPressed"
37
+ const val TAB_INDEX = "tabIndex"
38
+ const val BOTTOM_TABS_ID = "bottomTabsId"
39
+ /** Optional. */
40
+ const val SCREEN_ID = "screenId"
41
+ const val WAS_SELECTED = "wasSelected"
42
+ }
43
+
44
+ /** `ANN.BottomTabSelected`. */
45
+ object BottomTabSelected {
46
+ const val EVENT = "ANN.BottomTabSelected"
47
+ const val SELECTED_TAB_INDEX = "selectedTabIndex"
48
+ const val UNSELECTED_TAB_INDEX = "unselectedTabIndex"
49
+ const val BOTTOM_TABS_ID = "bottomTabsId"
50
+ }
51
+
52
+ /** `ANN.CommandCompleted`. */
53
+ object CommandCompleted {
54
+ const val EVENT = "ANN.CommandCompleted"
55
+ const val COMMAND_NAME = "commandName"
56
+ const val COMMAND_ID = "commandId"
57
+ const val COMPLETION_TIME = "completionTime"
58
+ }
59
+
60
+ /** `ANN.ComponentDidAppear`. */
61
+ object ComponentDidAppear {
62
+ const val EVENT = "ANN.ComponentDidAppear"
63
+ const val COMPONENT_ID = "componentId"
64
+ const val COMPONENT_NAME = "componentName"
65
+ const val COMPONENT_TYPE = "componentType"
66
+ }
67
+
68
+ /** `ANN.ComponentDidDisappear`. */
69
+ object ComponentDidDisappear {
70
+ const val EVENT = "ANN.ComponentDidDisappear"
71
+ const val COMPONENT_ID = "componentId"
72
+ const val COMPONENT_NAME = "componentName"
73
+ const val COMPONENT_TYPE = "componentType"
74
+ }
75
+
76
+ /** `ANN.ComponentWillAppear`. */
77
+ object ComponentWillAppear {
78
+ const val EVENT = "ANN.ComponentWillAppear"
79
+ const val COMPONENT_ID = "componentId"
80
+ const val COMPONENT_NAME = "componentName"
81
+ const val COMPONENT_TYPE = "componentType"
82
+ }
83
+
84
+ /** `ANN.HingeChanged`. */
85
+ object HingeChanged {
86
+ const val EVENT = "ANN.HingeChanged"
87
+ /** Optional. */
88
+ const val STATE = "state"
89
+ /** Optional. */
90
+ const val ANGLE = "angle"
91
+ }
92
+
93
+ /** `ANN.ModalAttemptedToDismiss`. */
94
+ object ModalAttemptedToDismiss {
95
+ const val EVENT = "ANN.ModalAttemptedToDismiss"
96
+ const val COMPONENT_ID = "componentId"
97
+ }
98
+
99
+ /** `ANN.ModalDismissed`. */
100
+ object ModalDismissed {
101
+ const val EVENT = "ANN.ModalDismissed"
102
+ const val COMPONENT_ID = "componentId"
103
+ const val COMPONENT_NAME = "componentName"
104
+ const val MODALS_DISMISSED = "modalsDismissed"
105
+ }
106
+
107
+ /** `ANN.NativeScreenCall`. */
108
+ object NativeScreenCall {
109
+ const val EVENT = "ANN.NativeScreenCall"
110
+ const val COMPONENT_ID = "componentId"
111
+ const val CALL_ID = "callId"
112
+ const val KIND = "kind"
113
+ const val NAME = "name"
114
+ const val ARGS = "args"
115
+ }
116
+
117
+ /** `ANN.NativeScreenReleased`. */
118
+ object NativeScreenReleased {
119
+ const val EVENT = "ANN.NativeScreenReleased"
120
+ const val COMPONENT_ID = "componentId"
121
+ }
122
+
123
+ /** `ANN.NavigationButtonPressed`. */
124
+ object NavigationButtonPressed {
125
+ const val EVENT = "ANN.NavigationButtonPressed"
126
+ const val COMPONENT_ID = "componentId"
127
+ const val BUTTON_ID = "buttonId"
128
+ }
129
+
130
+ /** `ANN.NavigationStateChanged`. */
131
+ object NavigationStateChanged {
132
+ const val EVENT = "ANN.NavigationStateChanged"
133
+ const val SCREEN = "screen"
134
+ const val TAB = "tab"
135
+ }
136
+
137
+ /** The object of `ANN.NavigationStateChanged.screen`. */
138
+ object NavigationStateChangedScreen {
139
+ const val ID = "id"
140
+ const val NAME = "name"
141
+ }
142
+
143
+ /** `ANN.ScreenInsetsChanged`. */
144
+ object ScreenInsetsChanged {
145
+ const val EVENT = "ANN.ScreenInsetsChanged"
146
+ const val COMPONENT_ID = "componentId"
147
+ const val TOP = "top"
148
+ const val RIGHT = "right"
149
+ const val BOTTOM = "bottom"
150
+ const val LEFT = "left"
151
+ }
152
+
153
+ /** `ANN.ScreenPopped`. */
154
+ object ScreenPopped {
155
+ const val EVENT = "ANN.ScreenPopped"
156
+ const val COMPONENT_ID = "componentId"
157
+ }
158
+
159
+ /** The object of `ANN.ScreenRegionsChanged.division`. */
160
+ object ScreenRegionRect {
161
+ const val X = "x"
162
+ const val Y = "y"
163
+ const val WIDTH = "width"
164
+ const val HEIGHT = "height"
165
+ }
166
+
167
+ /** `ANN.ScreenRegionsChanged`. */
168
+ object ScreenRegionsChanged {
169
+ const val EVENT = "ANN.ScreenRegionsChanged"
170
+ const val COMPONENT_ID = "componentId"
171
+ const val DIVISION = "division"
172
+ const val OCCLUSION = "occlusion"
173
+ }
174
+
175
+ /** `ANN.SearchBarCancelPressed`. */
176
+ object SearchBarCancelPressed {
177
+ const val EVENT = "ANN.SearchBarCancelPressed"
178
+ const val COMPONENT_ID = "componentId"
179
+ }
180
+
181
+ /** `ANN.SearchBarSubmitted`. */
182
+ object SearchBarSubmitted {
183
+ const val EVENT = "ANN.SearchBarSubmitted"
184
+ const val COMPONENT_ID = "componentId"
185
+ const val TEXT = "text"
186
+ /** Optional. */
187
+ const val SCOPE = "scope"
188
+ }
189
+
190
+ /** `ANN.SearchBarUpdated`. */
191
+ object SearchBarUpdated {
192
+ const val EVENT = "ANN.SearchBarUpdated"
193
+ const val COMPONENT_ID = "componentId"
194
+ const val TEXT = "text"
195
+ const val IS_FOCUSED = "isFocused"
196
+ /** Optional. */
197
+ const val SCOPE = "scope"
198
+ }
199
+
200
+ /** `ANN.SearchSuggestionPicked`. */
201
+ object SearchSuggestionPicked {
202
+ const val EVENT = "ANN.SearchSuggestionPicked"
203
+ const val COMPONENT_ID = "componentId"
204
+ const val SUGGESTION_ID = "suggestionId"
205
+ }
206
+
207
+ /** `ANN.SheetDetentChanged`. */
208
+ object SheetDetentChanged {
209
+ const val EVENT = "ANN.SheetDetentChanged"
210
+ const val COMPONENT_ID = "componentId"
211
+ const val DETENT = "detent"
212
+ }
213
+
214
+ /** `ANN.SlotDestroyed`. */
215
+ object SlotDestroyed {
216
+ const val EVENT = "ANN.SlotDestroyed"
217
+ const val COMPONENT_ID = "componentId"
218
+ }
219
+
220
+ /** `ANN.SplitViewColumnsStacked`. */
221
+ object SplitViewColumnsStacked {
222
+ const val EVENT = "ANN.SplitViewColumnsStacked"
223
+ const val COMPONENT_ID = "componentId"
224
+ const val STACKED = "stacked"
225
+ }
226
+ }
@@ -12,147 +12,159 @@ import org.json.JSONArray
12
12
  /**
13
13
  * Sends the events `ANN.<Name>` to the JavaScript of [reactContext] through React Native's device event emitter, in
14
14
  * the order they are sent: sent from the main thread only. Without a context, or when its React instance is gone, an
15
- * event is dropped with one line in the log. The names and the fields are the contract. The core sends through
16
- * [current]; modules keep making their own.
15
+ * event is dropped with one line in the log. The names and the keys come from [EventContract], made from the types of
16
+ * `src/interfaces/`. The core sends through [current]; modules keep making their own.
17
17
  */
18
18
  class EventEmitter(private val reactContext: ReactContext?) {
19
19
  /** Whether an event sent now reaches JavaScript. */
20
20
  val canSend: Boolean
21
21
  get() = reactContext?.hasActiveReactInstance() == true
22
22
 
23
- fun emitAppLaunched() = emit(APP_LAUNCHED, Arguments.createMap())
23
+ fun emitAppLaunched() = emit(EventContract.AppLaunched.EVENT, Arguments.createMap())
24
24
 
25
25
  fun emitCommandCompleted(commandName: String, commandId: String, completionTime: Long) =
26
- emit(COMMAND_COMPLETED, Arguments.createMap().apply {
27
- putString("commandName", commandName)
28
- putString("commandId", commandId)
29
- putDouble("completionTime", completionTime.toDouble())
26
+ emit(EventContract.CommandCompleted.EVENT, Arguments.createMap().apply {
27
+ putString(EventContract.CommandCompleted.COMMAND_NAME, commandName)
28
+ putString(EventContract.CommandCompleted.COMMAND_ID, commandId)
29
+ putDouble(EventContract.CommandCompleted.COMPLETION_TIME, completionTime.toDouble())
30
30
  })
31
31
 
32
32
  fun emitComponentWillAppear(componentId: String, componentName: String, type: ComponentType) =
33
- emit(COMPONENT_WILL_APPEAR, component(componentId, componentName, type))
33
+ emit(EventContract.ComponentWillAppear.EVENT, component(componentId, componentName, type))
34
34
 
35
35
  fun emitComponentDidAppear(componentId: String, componentName: String, type: ComponentType) =
36
- emit(COMPONENT_DID_APPEAR, component(componentId, componentName, type))
36
+ emit(EventContract.ComponentDidAppear.EVENT, component(componentId, componentName, type))
37
37
 
38
38
  fun emitComponentDidDisappear(componentId: String, componentName: String, type: ComponentType) =
39
- emit(COMPONENT_DID_DISAPPEAR, component(componentId, componentName, type))
39
+ emit(EventContract.ComponentDidDisappear.EVENT, component(componentId, componentName, type))
40
40
 
41
41
  /** The screen the user sees (`null` for none) and the selected tab of the tabs it is in (`null` out of tabs). */
42
42
  fun emitNavigationStateChanged(screenId: String?, screenName: String?, tab: Int?) =
43
- emit(NAVIGATION_STATE_CHANGED, Arguments.createMap().apply {
43
+ emit(EventContract.NavigationStateChanged.EVENT, Arguments.createMap().apply {
44
+ val keys = EventContract.NavigationStateChanged
44
45
  if (screenId == null) {
45
- putNull("screen")
46
+ putNull(keys.SCREEN)
46
47
  } else {
47
- putMap("screen", Arguments.createMap().apply {
48
- putString("id", screenId)
49
- putString("name", screenName)
48
+ putMap(keys.SCREEN, Arguments.createMap().apply {
49
+ putString(EventContract.NavigationStateChangedScreen.ID, screenId)
50
+ putString(EventContract.NavigationStateChangedScreen.NAME, screenName)
50
51
  })
51
52
  }
52
- if (tab == null) putNull("tab") else putInt("tab", tab)
53
+ if (tab == null) putNull(keys.TAB) else putInt(keys.TAB, tab)
53
54
  })
54
55
 
55
- fun emitScreenPopped(componentId: String) = emit(SCREEN_POPPED, withId(componentId))
56
+ fun emitScreenPopped(componentId: String) = emit(EventContract.ScreenPopped.EVENT, withId(componentId))
56
57
 
57
58
  fun emitNavigationButtonPressed(componentId: String, buttonId: String) =
58
- emit(NAVIGATION_BUTTON_PRESSED, withId(componentId).apply { putString("buttonId", buttonId) })
59
+ emit(EventContract.NavigationButtonPressed.EVENT, withId(componentId).apply {
60
+ putString(EventContract.NavigationButtonPressed.BUTTON_ID, buttonId)
61
+ })
59
62
 
60
63
  fun emitBottomTabSelected(selectedTabIndex: Int, unselectedTabIndex: Int, bottomTabsId: String) =
61
- emit(BOTTOM_TAB_SELECTED, Arguments.createMap().apply {
62
- putInt("selectedTabIndex", selectedTabIndex)
63
- putInt("unselectedTabIndex", unselectedTabIndex)
64
- putString("bottomTabsId", bottomTabsId)
64
+ emit(EventContract.BottomTabSelected.EVENT, Arguments.createMap().apply {
65
+ putInt(EventContract.BottomTabSelected.SELECTED_TAB_INDEX, selectedTabIndex)
66
+ putInt(EventContract.BottomTabSelected.UNSELECTED_TAB_INDEX, unselectedTabIndex)
67
+ putString(EventContract.BottomTabSelected.BOTTOM_TABS_ID, bottomTabsId)
65
68
  })
66
69
 
67
70
  fun emitBottomTabPressed(tabIndex: Int, bottomTabsId: String, screenId: String?, wasSelected: Boolean) =
68
- emit(BOTTOM_TAB_PRESSED, Arguments.createMap().apply {
69
- putInt("tabIndex", tabIndex)
70
- putString("bottomTabsId", bottomTabsId)
71
- screenId?.let { putString("screenId", it) }
72
- putBoolean("wasSelected", wasSelected)
71
+ emit(EventContract.BottomTabPressed.EVENT, Arguments.createMap().apply {
72
+ putInt(EventContract.BottomTabPressed.TAB_INDEX, tabIndex)
73
+ putString(EventContract.BottomTabPressed.BOTTOM_TABS_ID, bottomTabsId)
74
+ screenId?.let { putString(EventContract.BottomTabPressed.SCREEN_ID, it) }
75
+ putBoolean(EventContract.BottomTabPressed.WAS_SELECTED, wasSelected)
73
76
  })
74
77
 
75
78
  fun emitBottomTabLongPressed(selectedTabIndex: Int, bottomTabsId: String) =
76
- emit(BOTTOM_TAB_LONG_PRESSED, Arguments.createMap().apply {
77
- putInt("selectedTabIndex", selectedTabIndex)
78
- putString("bottomTabsId", bottomTabsId)
79
+ emit(EventContract.BottomTabLongPressed.EVENT, Arguments.createMap().apply {
80
+ putInt(EventContract.BottomTabLongPressed.SELECTED_TAB_INDEX, selectedTabIndex)
81
+ putString(EventContract.BottomTabLongPressed.BOTTOM_TABS_ID, bottomTabsId)
79
82
  })
80
83
 
81
84
  /** The id and the name (without `slot:`) of the screen the modal showed, and how many modals went. */
82
85
  fun emitModalDismissed(componentId: String, componentName: String, modalsDismissed: Int) =
83
- emit(MODAL_DISMISSED, withId(componentId).apply {
84
- putString("componentName", componentName)
85
- putInt("modalsDismissed", modalsDismissed)
86
+ emit(EventContract.ModalDismissed.EVENT, withId(componentId).apply {
87
+ putString(EventContract.ModalDismissed.COMPONENT_NAME, componentName)
88
+ putInt(EventContract.ModalDismissed.MODALS_DISMISSED, modalsDismissed)
86
89
  })
87
90
 
88
- fun emitModalAttemptedToDismiss(componentId: String) = emit(MODAL_ATTEMPTED_TO_DISMISS, withId(componentId))
91
+ fun emitModalAttemptedToDismiss(componentId: String) =
92
+ emit(EventContract.ModalAttemptedToDismiss.EVENT, withId(componentId))
89
93
 
90
94
  /** [detent]: `medium`, `large`, or a height in dp. */
91
95
  fun emitSheetDetentChanged(componentId: String, detent: Any) =
92
- emit(SHEET_DETENT_CHANGED, withId(componentId).apply {
96
+ emit(EventContract.SheetDetentChanged.EVENT, withId(componentId).apply {
93
97
  when (detent) {
94
- is Number -> putDouble("detent", detent.toDouble())
95
- else -> putString("detent", detent.toString())
98
+ is Number -> putDouble(EventContract.SheetDetentChanged.DETENT, detent.toDouble())
99
+ else -> putString(EventContract.SheetDetentChanged.DETENT, detent.toString())
96
100
  }
97
101
  })
98
102
 
99
103
  fun emitSearchBarUpdated(componentId: String, text: String, isFocused: Boolean, scope: String?) =
100
- emit(SEARCH_BAR_UPDATED, withId(componentId).apply {
101
- putString("text", text)
102
- putBoolean("isFocused", isFocused)
103
- scope?.let { putString("scope", it) }
104
+ emit(EventContract.SearchBarUpdated.EVENT, withId(componentId).apply {
105
+ putString(EventContract.SearchBarUpdated.TEXT, text)
106
+ putBoolean(EventContract.SearchBarUpdated.IS_FOCUSED, isFocused)
107
+ scope?.let { putString(EventContract.SearchBarUpdated.SCOPE, it) }
104
108
  })
105
109
 
106
110
  fun emitSearchBarSubmitted(componentId: String, text: String, scope: String?) =
107
- emit(SEARCH_BAR_SUBMITTED, withId(componentId).apply {
108
- putString("text", text)
109
- scope?.let { putString("scope", it) }
111
+ emit(EventContract.SearchBarSubmitted.EVENT, withId(componentId).apply {
112
+ putString(EventContract.SearchBarSubmitted.TEXT, text)
113
+ scope?.let { putString(EventContract.SearchBarSubmitted.SCOPE, it) }
110
114
  })
111
115
 
112
- fun emitSearchBarCancelPressed(componentId: String) = emit(SEARCH_BAR_CANCEL_PRESSED, withId(componentId))
116
+ fun emitSearchBarCancelPressed(componentId: String) =
117
+ emit(EventContract.SearchBarCancelPressed.EVENT, withId(componentId))
113
118
 
114
119
  fun emitSearchSuggestionPicked(componentId: String, suggestionId: String) =
115
- emit(SEARCH_SUGGESTION_PICKED, withId(componentId).apply { putString("suggestionId", suggestionId) })
120
+ emit(EventContract.SearchSuggestionPicked.EVENT, withId(componentId).apply {
121
+ putString(EventContract.SearchSuggestionPicked.SUGGESTION_ID, suggestionId)
122
+ })
116
123
 
117
124
  fun emitSplitViewColumnsStacked(componentId: String, stacked: Boolean) =
118
- emit(SPLIT_VIEW_COLUMNS_STACKED, withId(componentId).apply { putBoolean("stacked", stacked) })
125
+ emit(EventContract.SplitViewColumnsStacked.EVENT, withId(componentId).apply {
126
+ putBoolean(EventContract.SplitViewColumnsStacked.STACKED, stacked)
127
+ })
119
128
 
120
- fun emitSlotDestroyed(componentId: String) = emit(SLOT_DESTROYED, withId(componentId))
129
+ fun emitSlotDestroyed(componentId: String) = emit(EventContract.SlotDestroyed.EVENT, withId(componentId))
121
130
 
122
131
  /** [insets]: `{ top, right, bottom, left }`. */
123
132
  fun emitScreenInsetsChanged(componentId: String, insets: WritableMap) =
124
- emit(SCREEN_INSETS_CHANGED, withId(componentId).apply { merge(insets) })
133
+ emit(EventContract.ScreenInsetsChanged.EVENT, withId(componentId).apply { merge(insets) })
125
134
 
126
135
  /** [regions]: `{ division, occlusion }`. */
127
136
  fun emitScreenRegionsChanged(componentId: String, regions: WritableMap) =
128
- emit(SCREEN_REGIONS_CHANGED, withId(componentId).apply { merge(regions) })
137
+ emit(EventContract.ScreenRegionsChanged.EVENT, withId(componentId).apply { merge(regions) })
129
138
 
130
139
  /** [state]: `{ placement, layout, front, hidden }`. */
131
140
  fun emitArrangementChanged(componentId: String, state: WritableMap) =
132
- emit(ARRANGEMENT_CHANGED, withId(componentId).apply { merge(state) })
141
+ emit(EventContract.ArrangementChanged.EVENT, withId(componentId).apply { merge(state) })
133
142
 
134
143
  /** [value]: `{ state, angle? }`, or `{}` without a hinge. */
135
- fun emitHingeChanged(value: WritableMap) = emit(HINGE_CHANGED, value)
144
+ fun emitHingeChanged(value: WritableMap) = emit(EventContract.HingeChanged.EVENT, value)
136
145
 
137
146
  /** [kind]: `function` or `command`; [arguments] as JSON, which JavaScript gets as `args`. */
138
147
  fun emitNativeScreenCall(componentId: String, callId: String, kind: String, name: String, arguments: JSONArray) =
139
- emit(NATIVE_SCREEN_CALL, withId(componentId).apply {
140
- putString("callId", callId)
141
- putString("kind", kind)
142
- putString("name", name)
143
- putArray("args", NativeScreenCalls.toArguments(arguments))
148
+ emit(EventContract.NativeScreenCall.EVENT, withId(componentId).apply {
149
+ putString(EventContract.NativeScreenCall.CALL_ID, callId)
150
+ putString(EventContract.NativeScreenCall.KIND, kind)
151
+ putString(EventContract.NativeScreenCall.NAME, name)
152
+ putArray(EventContract.NativeScreenCall.ARGS, NativeScreenCalls.toArguments(arguments))
144
153
  })
145
154
 
146
- fun emitNativeScreenReleased(componentId: String) = emit(NATIVE_SCREEN_RELEASED, withId(componentId))
155
+ fun emitNativeScreenReleased(componentId: String) =
156
+ emit(EventContract.NativeScreenReleased.EVENT, withId(componentId))
147
157
 
158
+ // The three appearance events have the same keys.
148
159
  private fun component(componentId: String, componentName: String, type: ComponentType): WritableMap =
149
160
  withId(componentId).apply {
150
- putString("componentName", componentName)
151
- putString("componentType", type.jsName)
161
+ putString(EventContract.ComponentDidAppear.COMPONENT_NAME, componentName)
162
+ putString(EventContract.ComponentDidAppear.COMPONENT_TYPE, type.jsName)
152
163
  }
153
164
 
165
+ // The events of a screen carry its id under the same key.
154
166
  private fun withId(componentId: String): WritableMap =
155
- Arguments.createMap().apply { putString("componentId", componentId) }
167
+ Arguments.createMap().apply { putString(EventContract.ScreenPopped.COMPONENT_ID, componentId) }
156
168
 
157
169
  private fun emit(name: String, data: WritableMap) {
158
170
  val context = reactContext
@@ -169,31 +181,33 @@ class EventEmitter(private val reactContext: ReactContext?) {
169
181
  fun current(): EventEmitter =
170
182
  EventEmitter(NavigationApplication.instanceOrNull?.reactGatewayOrNull?.currentReactContext)
171
183
 
172
- const val APP_LAUNCHED = "ANN.AppLaunched"
173
- const val COMMAND_COMPLETED = "ANN.CommandCompleted"
174
- const val COMPONENT_WILL_APPEAR = "ANN.ComponentWillAppear"
175
- const val COMPONENT_DID_APPEAR = "ANN.ComponentDidAppear"
176
- const val COMPONENT_DID_DISAPPEAR = "ANN.ComponentDidDisappear"
177
- const val NAVIGATION_STATE_CHANGED = "ANN.NavigationStateChanged"
178
- const val SCREEN_POPPED = "ANN.ScreenPopped"
179
- const val NAVIGATION_BUTTON_PRESSED = "ANN.NavigationButtonPressed"
180
- const val BOTTOM_TAB_SELECTED = "ANN.BottomTabSelected"
181
- const val BOTTOM_TAB_PRESSED = "ANN.BottomTabPressed"
182
- const val BOTTOM_TAB_LONG_PRESSED = "ANN.BottomTabLongPressed"
183
- const val MODAL_DISMISSED = "ANN.ModalDismissed"
184
- const val MODAL_ATTEMPTED_TO_DISMISS = "ANN.ModalAttemptedToDismiss"
185
- const val SHEET_DETENT_CHANGED = "ANN.SheetDetentChanged"
186
- const val SEARCH_BAR_UPDATED = "ANN.SearchBarUpdated"
187
- const val SEARCH_BAR_SUBMITTED = "ANN.SearchBarSubmitted"
188
- const val SEARCH_BAR_CANCEL_PRESSED = "ANN.SearchBarCancelPressed"
189
- const val SEARCH_SUGGESTION_PICKED = "ANN.SearchSuggestionPicked"
190
- const val SPLIT_VIEW_COLUMNS_STACKED = "ANN.SplitViewColumnsStacked"
191
- const val SLOT_DESTROYED = "ANN.SlotDestroyed"
192
- const val SCREEN_INSETS_CHANGED = "ANN.ScreenInsetsChanged"
193
- const val SCREEN_REGIONS_CHANGED = "ANN.ScreenRegionsChanged"
194
- const val ARRANGEMENT_CHANGED = "ANN.ArrangementChanged"
195
- const val HINGE_CHANGED = "ANN.HingeChanged"
196
- const val NATIVE_SCREEN_CALL = "ANN.NativeScreenCall"
197
- const val NATIVE_SCREEN_RELEASED = "ANN.NativeScreenReleased"
184
+ // The names of the events, for modules outside the library; inside it, [EventContract] has them with their
185
+ // keys.
186
+ const val APP_LAUNCHED = EventContract.AppLaunched.EVENT
187
+ const val COMMAND_COMPLETED = EventContract.CommandCompleted.EVENT
188
+ const val COMPONENT_WILL_APPEAR = EventContract.ComponentWillAppear.EVENT
189
+ const val COMPONENT_DID_APPEAR = EventContract.ComponentDidAppear.EVENT
190
+ const val COMPONENT_DID_DISAPPEAR = EventContract.ComponentDidDisappear.EVENT
191
+ const val NAVIGATION_STATE_CHANGED = EventContract.NavigationStateChanged.EVENT
192
+ const val SCREEN_POPPED = EventContract.ScreenPopped.EVENT
193
+ const val NAVIGATION_BUTTON_PRESSED = EventContract.NavigationButtonPressed.EVENT
194
+ const val BOTTOM_TAB_SELECTED = EventContract.BottomTabSelected.EVENT
195
+ const val BOTTOM_TAB_PRESSED = EventContract.BottomTabPressed.EVENT
196
+ const val BOTTOM_TAB_LONG_PRESSED = EventContract.BottomTabLongPressed.EVENT
197
+ const val MODAL_DISMISSED = EventContract.ModalDismissed.EVENT
198
+ const val MODAL_ATTEMPTED_TO_DISMISS = EventContract.ModalAttemptedToDismiss.EVENT
199
+ const val SHEET_DETENT_CHANGED = EventContract.SheetDetentChanged.EVENT
200
+ const val SEARCH_BAR_UPDATED = EventContract.SearchBarUpdated.EVENT
201
+ const val SEARCH_BAR_SUBMITTED = EventContract.SearchBarSubmitted.EVENT
202
+ const val SEARCH_BAR_CANCEL_PRESSED = EventContract.SearchBarCancelPressed.EVENT
203
+ const val SEARCH_SUGGESTION_PICKED = EventContract.SearchSuggestionPicked.EVENT
204
+ const val SPLIT_VIEW_COLUMNS_STACKED = EventContract.SplitViewColumnsStacked.EVENT
205
+ const val SLOT_DESTROYED = EventContract.SlotDestroyed.EVENT
206
+ const val SCREEN_INSETS_CHANGED = EventContract.ScreenInsetsChanged.EVENT
207
+ const val SCREEN_REGIONS_CHANGED = EventContract.ScreenRegionsChanged.EVENT
208
+ const val ARRANGEMENT_CHANGED = EventContract.ArrangementChanged.EVENT
209
+ const val HINGE_CHANGED = EventContract.HingeChanged.EVENT
210
+ const val NATIVE_SCREEN_CALL = EventContract.NativeScreenCall.EVENT
211
+ const val NATIVE_SCREEN_RELEASED = EventContract.NativeScreenReleased.EVENT
198
212
  }
199
213
  }
@@ -45,12 +45,6 @@ internal class ScreenAppearance(
45
45
  /** Cancels the hold limit running for [held]; none when nothing is held. */
46
46
  internal var cancelLimit: (() -> Unit)? = null
47
47
 
48
- /**
49
- * This appearance started without its WillAppear (`AppearanceDispatcher.withoutWillAppear`: a side menu's drawer
50
- * that jumped open); its DidAppear is its first event. Cleared as it is hidden.
51
- */
52
- internal var skippedWill = false
53
-
54
48
  private var ready = content == null
55
49
  private var waitsForContent = false
56
50
 
@@ -22,8 +22,8 @@ object UiUtils {
22
22
  fun pxToDp(context: Context, px: Float): Float = px / context.resources.displayMetrics.density
23
23
 
24
24
  /**
25
- * A size of the options in dp, in whole pixels of the screen of [context]: times `densityDpi / 160`, the fraction
26
- * of a pixel dropped; a size of 0 or less is not converted.
25
+ * A size of the options in dp, in whole pixels of the screen of [context]: times `densityDpi / 160`, rounded to the
26
+ * nearest pixel only here, as iOS places a size in points on whole pixels; a size of 0 or less is not converted.
27
27
  */
28
28
  @JvmStatic
29
29
  fun dpToPixels(context: Context, dp: Double): Int = dpToPixels(dp, context.resources.displayMetrics.densityDpi)
@@ -31,7 +31,7 @@ object UiUtils {
31
31
  /** [dp] in whole pixels at [densityDpi], as [dpToPixels] converts it. */
32
32
  @JvmStatic
33
33
  fun dpToPixels(dp: Double, densityDpi: Int): Int =
34
- if (dp <= 0) dp.toInt() else (dp * densityDpi / BASE_DENSITY_DPI).toInt()
34
+ if (dp <= 0) dp.toInt() else (dp * densityDpi / BASE_DENSITY_DPI).roundToInt()
35
35
 
36
36
  /**
37
37
  * A font size of the options in sp, in pixels of the screen of [context]: it follows the system's text scale,