@apexnative/navigation 1.0.0-alpha.4 → 1.0.0-alpha.6
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.
- package/CHANGELOG.md +38 -0
- package/README.md +1 -1
- package/android/CLAUDE.md +23 -14
- package/android/CORE.md +32 -19
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +60 -13
- package/android/src/main/java/dev/apexnative/navigation/options/AnimationsOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
- package/android/src/main/java/dev/apexnative/navigation/options/HardwareBackButtonOptions.kt +9 -1
- package/android/src/main/java/dev/apexnative/navigation/options/StackAnimationOptions.kt +40 -2
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +28 -13
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +39 -20
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -6
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +19 -51
- package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +30 -39
- package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +34 -7
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationState.kt +154 -0
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +12 -10
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +10 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +22 -21
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt +103 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +119 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +7 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackCheck.kt +50 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +45 -14
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +25 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +13 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/ScreenSearch.kt +3 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +2 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarComponents.kt +5 -51
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +4 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
- package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
- package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomBottomTabItemView.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +9 -2
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +11 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +318 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +18 -20
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +17 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +15 -22
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +84 -35
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +49 -40
- package/cli/doctor/checks/splash.js +4 -22
- package/cli/splash/index.js +3 -1
- package/docs-md/architecture/transitions.md +4 -2
- package/docs-md/containers/modals.md +4 -2
- package/docs-md/containers/overlays.md +1 -1
- package/docs-md/containers/side-menu.md +1 -1
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/bottom-sheets.md +1 -1
- package/docs-md/fundamentals/starting-the-app.md +1 -1
- package/docs-md/guides/cli.md +1 -2
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +23 -2
- package/docs-md/index.json +12 -0
- package/docs-md/migration/from-expo-router.md +1 -1
- package/docs-md/migration/from-react-native-navigation.md +1 -1
- package/docs-md/migration/from-react-navigation.md +2 -2
- package/docs-md/screen-ui/back-and-gestures.md +36 -0
- package/docs-md/screen-ui/bar-buttons.md +4 -4
- package/docs-md/screen-ui/search.md +1 -1
- package/docs-md/screen-ui/system-ui.md +16 -11
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -2
- package/docs-md/screen-ui/top-bar.md +8 -4
- package/docs-md/start/installation.md +1 -1
- package/ios/Animations/ANNFrameDriver.mm +2 -0
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +70 -4
- package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
- package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
- package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -4
- package/ios/Animations/Interpolators/ANNViewTracks.mm +49 -9
- package/ios/CLAUDE.md +17 -15
- package/ios/CORE.md +30 -26
- package/ios/Contract/ANNCommandAnswer.h +9 -2
- package/ios/Contract/ANNCommandAnswer.mm +25 -12
- package/ios/Contract/ANNCommandRunner.h +8 -7
- package/ios/Contract/ANNCommandRunner.mm +21 -27
- package/ios/Contract/ANNLayoutNode.h +3 -3
- package/ios/Contract/ANNLayoutReader.h +3 -3
- package/ios/Core/ANNAppearanceDispatcher.h +5 -4
- package/ios/Core/ANNAppearanceDispatcher.mm +12 -1
- package/ios/Core/ANNJavaScriptCalls.h +11 -12
- package/ios/Core/ANNJavaScriptCalls.mm +7 -16
- package/ios/Core/ANNNavigationState.h +4 -6
- package/ios/Core/ANNNavigationState.mm +0 -4
- package/ios/Core/ANNReloadCoordinator.mm +4 -8
- package/ios/Core/ApexNavigation+Internal.h +1 -1
- package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
- package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
- package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
- package/ios/Layouts/Root/ANNSplashViewController.h +5 -3
- package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
- package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
- package/ios/Layouts/Screen/ANNComponentViewController.mm +9 -22
- package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
- package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
- package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
- package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
- package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
- package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
- package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
- package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
- package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
- package/ios/Layouts/Stack/ANNPreviewPush.mm +142 -27
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
- package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
- package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
- package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
- package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
- package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
- package/ios/Options/ANNBottomTabsOptions.h +2 -0
- package/ios/Options/ANNBottomTabsOptions.mm +1 -0
- package/ios/Options/ANNOptions.h +18 -0
- package/ios/Options/ANNOptions.mm +112 -1
- package/ios/Options/ANNOptionsReader.mm +110 -0
- package/ios/Options/Params/ANNParam.h +15 -0
- package/ios/Options/Params/ANNParam.mm +61 -0
- package/ios/Options/Params/Color.h +3 -0
- package/ios/Options/Params/Color.mm +21 -0
- package/ios/Options/Params/Image.h +4 -0
- package/ios/Options/Params/Image.mm +20 -0
- package/ios/React/ANNReactView.mm +6 -25
- package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
- package/ios/React/ANNSurfaceHostingView.h +6 -0
- package/ios/React/ANNSurfaceHostingView.mm +116 -0
- package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
- package/ios/TopBar/ANNBarAppearance.h +2 -1
- package/ios/TopBar/ANNBarAppearance.mm +111 -30
- package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
- package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
- package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
- package/ios/TopBar/ANNBarButtons.h +5 -3
- package/ios/TopBar/ANNBarButtons.mm +186 -17
- package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
- package/ios/TopBar/ANNMainList.h +3 -0
- package/ios/TopBar/ANNMainList.mm +4 -0
- package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
- package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
- package/ios/TopBar/ANNSearchPresenter.mm +2 -2
- package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
- package/ios/TopBar/ANNTopBarPresenter.h +3 -2
- package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
- package/ios/TurboModules/ANNTurboModule.mm +20 -16
- package/lib/module/ARCHITECTURE.md +8 -4
- package/lib/module/api/commands/commands.js +54 -6
- package/lib/module/api/commands/commands.js.map +1 -1
- package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
- package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
- package/lib/module/api/options/OptionsChain.js +2 -2
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/places.js +8 -0
- package/lib/module/engine/places.js.map +1 -1
- package/lib/module/engine/screens.js +29 -4
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +3 -3
- package/lib/typescript/api/commands/commands.d.ts.map +1 -1
- package/lib/typescript/api/commands/presentation.d.ts +4 -3
- package/lib/typescript/api/commands/presentation.d.ts.map +1 -1
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
- package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/places.d.ts +2 -0
- package/lib/typescript/engine/places.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +20 -6
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/Options.d.ts +1 -1
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +30 -9
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +18 -5
- package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +8 -8
- package/lib/typescript/interfaces/options/stack.d.ts +9 -0
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/system.d.ts +8 -5
- package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
- package/lib/typescript/interfaces/options/topBar.d.ts +32 -29
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +8 -4
- package/src/api/app/createNavigation.ts +3 -3
- package/src/api/commands/commands.ts +48 -7
- package/src/api/commands/presentation.ts +4 -3
- package/src/api/hooks/useSafeAreaInsets.ts +4 -3
- package/src/api/options/OptionsChain.ts +2 -2
- package/src/engine/commands.ts +30 -6
- package/src/engine/places.ts +9 -0
- package/src/engine/screens.tsx +29 -8
- package/src/interfaces/Options.ts +1 -0
- package/src/interfaces/options/animations.ts +31 -9
- package/src/interfaces/options/bottomTabs.ts +18 -5
- package/src/interfaces/options/buttons.ts +8 -8
- package/src/interfaces/options/stack.ts +9 -0
- package/src/interfaces/options/system.ts +8 -5
- package/src/interfaces/options/toolbar.ts +2 -2
- package/src/interfaces/options/topBar.ts +32 -29
- package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationStateReporter.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/react/events/StateHold.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
- package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
- package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
|
@@ -3,13 +3,14 @@ package dev.apexnative.navigation.react
|
|
|
3
3
|
import android.util.Log
|
|
4
4
|
import com.facebook.react.bridge.Promise
|
|
5
5
|
import dev.apexnative.navigation.react.events.EventEmitter
|
|
6
|
-
import dev.apexnative.navigation.react.events.
|
|
6
|
+
import dev.apexnative.navigation.react.events.NavigationState
|
|
7
7
|
import dev.apexnative.navigation.utils.NavigationLog
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* The one answer of a command with a promise: [resolve] or [reject], once; after that, and once the JavaScript it came
|
|
11
|
-
* from went ([generation]), nothing.
|
|
12
|
-
* a [CommandListener] answers through [asListener]. Main
|
|
11
|
+
* from went ([generation]), nothing. It owns the command's one state step ([reportStateNow]). [CommandRunner] makes it
|
|
12
|
+
* on the main thread for each command; area code that takes a [CommandListener] answers through [asListener]. Main
|
|
13
|
+
* thread only.
|
|
13
14
|
*/
|
|
14
15
|
internal class CommandAnswer(
|
|
15
16
|
/** The method of the command: `push`, `setRoot`, … */
|
|
@@ -27,19 +28,36 @@ internal class CommandAnswer(
|
|
|
27
28
|
var isAnswered = false
|
|
28
29
|
private set
|
|
29
30
|
|
|
31
|
+
// The state step of this answer ran ([reportStateNow]).
|
|
32
|
+
private var stateReported = false
|
|
33
|
+
|
|
30
34
|
/** Whether an answer now would reach the JavaScript the command came from. */
|
|
31
35
|
val canAnswer: Boolean
|
|
32
36
|
get() = !isAnswered && ReloadCoordinator.isCurrent(generation)
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
39
|
+
* The navigation state now, sent when it changed, once per answer: the end of the command's transition asks for it
|
|
40
|
+
* after the DidAppear it sends and before the transition's own events (`AppearanceDispatcher.endTransition`);
|
|
41
|
+
* otherwise [resolve] asks for it before the promise. A later call sends nothing, and nothing goes for a JavaScript
|
|
42
|
+
* that went. A failure of the step is logged and costs only the state.
|
|
43
|
+
*/
|
|
44
|
+
fun reportStateNow() {
|
|
45
|
+
if (stateReported) return
|
|
46
|
+
stateReported = true
|
|
47
|
+
if (!ReloadCoordinator.isCurrent(generation)) return
|
|
48
|
+
guarded("report the navigation state") { events.stateNow() }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Resolves the promise with [value]. For the commands that change what shows (`CommandContract.STATE`), the
|
|
53
|
+
* navigation state goes first, when it changed and the command's transition didn't send it already
|
|
54
|
+
* ([reportStateNow]), so JavaScript has it when its `await` goes on; `ANN.CommandCompleted` follows the promise,
|
|
55
|
+
* for the commands that show or dismiss. A failure of either is logged; the promise goes all the same.
|
|
38
56
|
*/
|
|
39
57
|
fun resolve(value: Any?) {
|
|
40
58
|
if (!canAnswer) return
|
|
41
59
|
isAnswered = true
|
|
42
|
-
if (commandName in CommandContract.STATE)
|
|
60
|
+
if (commandName in CommandContract.STATE) reportStateNow()
|
|
43
61
|
promise.resolve(value)
|
|
44
62
|
val commandId = commandId
|
|
45
63
|
if (commandName in CommandContract.COMPLETED && commandId != null) {
|
|
@@ -69,18 +87,15 @@ internal class CommandAnswer(
|
|
|
69
87
|
|
|
70
88
|
/** What goes to JavaScript around the answer of a command. Main thread only. */
|
|
71
89
|
internal interface AnswerEvents {
|
|
72
|
-
/**
|
|
73
|
-
fun
|
|
90
|
+
/** The answer's one state step ([CommandAnswer.reportStateNow]): the navigation state now, when it changed. */
|
|
91
|
+
fun stateNow()
|
|
74
92
|
|
|
75
93
|
/** After the promise resolved: `ANN.CommandCompleted`. */
|
|
76
94
|
fun commandCompleted(commandName: String, commandId: String, completionTime: Long)
|
|
77
95
|
|
|
78
96
|
/** Through the sender of the JavaScript that runs now. */
|
|
79
97
|
object Current : AnswerEvents {
|
|
80
|
-
override fun
|
|
81
|
-
val emitter = EventEmitter.current()
|
|
82
|
-
if (emitter.canSend) NavigationStateReporter.updateNow(emitter)
|
|
83
|
-
}
|
|
98
|
+
override fun stateNow() = NavigationState.updateNow()
|
|
84
99
|
|
|
85
100
|
override fun commandCompleted(commandName: String, commandId: String, completionTime: Long) {
|
|
86
101
|
EventEmitter.current().emitCommandCompleted(commandName, commandId, completionTime)
|
|
@@ -9,7 +9,6 @@ import android.os.Build
|
|
|
9
9
|
import androidx.lifecycle.DefaultLifecycleObserver
|
|
10
10
|
import androidx.lifecycle.Lifecycle
|
|
11
11
|
import androidx.lifecycle.LifecycleOwner
|
|
12
|
-
import com.facebook.react.ReactApplication
|
|
13
12
|
import com.facebook.react.bridge.Arguments
|
|
14
13
|
import com.facebook.react.bridge.WritableMap
|
|
15
14
|
import dev.apexnative.navigation.react.events.EventContract
|
|
@@ -22,7 +21,8 @@ import java.lang.ref.WeakReference
|
|
|
22
21
|
* sensor (Android 11+), in degrees to a tenth as on iOS. Android has no public state of a closed device: closed is a
|
|
23
22
|
* window without a fold, or an angle under 90° ([valueOf]). No sensor and no fold: no hinge, `{}`. It runs while
|
|
24
23
|
* JavaScript has a hook mounted (`setHingeUpdates`), the sensor only while the activity is resumed too; the same value
|
|
25
|
-
* is not sent twice.
|
|
24
|
+
* is not sent twice. The values go to the JavaScript that turned the updates on (`EventEmitter.current` then): after a
|
|
25
|
+
* reload, until the updates stop, they go nowhere. Main thread only, but [get].
|
|
26
26
|
*/
|
|
27
27
|
internal object Hinge {
|
|
28
28
|
/** A value of the hinge: its state, and its angle in degrees to a tenth; none while the state is unknown. */
|
|
@@ -33,6 +33,8 @@ internal object Hinge {
|
|
|
33
33
|
private const val CLOSED_BELOW = 90f
|
|
34
34
|
|
|
35
35
|
private var enabled = false
|
|
36
|
+
// The sender of the JavaScript that turned the updates on; none while they are off.
|
|
37
|
+
private var emitter: EventEmitter? = null
|
|
36
38
|
private var activity: WeakReference<Activity>? = null
|
|
37
39
|
private var folds: WindowFolds? = null
|
|
38
40
|
private var sensorManager: SensorManager? = null
|
|
@@ -63,9 +65,11 @@ internal object Hinge {
|
|
|
63
65
|
fun setEnabled(activity: Activity?, enabled: Boolean) {
|
|
64
66
|
this.enabled = enabled
|
|
65
67
|
if (!enabled) {
|
|
68
|
+
emitter = null
|
|
66
69
|
detach()
|
|
67
70
|
return
|
|
68
71
|
}
|
|
72
|
+
emitter = EventEmitter.current()
|
|
69
73
|
activity?.let(::attachTo)
|
|
70
74
|
update()
|
|
71
75
|
}
|
|
@@ -134,8 +138,7 @@ internal object Hinge {
|
|
|
134
138
|
val value = valueOf(folds.folds.firstOrNull(), sensor != null, angle)
|
|
135
139
|
if (value == last) return
|
|
136
140
|
last = value
|
|
137
|
-
|
|
138
|
-
EventEmitter(reactContext).emitHingeChanged(toMap(value))
|
|
141
|
+
emitter?.emitHingeChanged(toMap(value))
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
/**
|
|
@@ -236,11 +236,11 @@ class NavigationTurboModule(reactContext: ReactApplicationContext) : NativeANNTu
|
|
|
236
236
|
check = {},
|
|
237
237
|
) { _, answer -> navigator().stackCommands.setColumnShown(componentId, column, shown, answer) }
|
|
238
238
|
|
|
239
|
-
override fun getArrangementState(componentId: String): WritableMap =
|
|
239
|
+
override fun getArrangementState(componentId: String): WritableMap = ScreenEnvironment.getArrangementState(componentId)
|
|
240
240
|
|
|
241
|
-
override fun getScreenInsets(componentId: String): WritableMap =
|
|
241
|
+
override fun getScreenInsets(componentId: String): WritableMap = ScreenEnvironment.getScreenInsets(componentId)
|
|
242
242
|
|
|
243
|
-
override fun getScreenRegions(componentId: String): WritableMap =
|
|
243
|
+
override fun getScreenRegions(componentId: String): WritableMap = ScreenEnvironment.getScreenRegions(componentId)
|
|
244
244
|
|
|
245
245
|
// With the activity the user sees, if there is one; the hinge remembers it and goes on with the next activity
|
|
246
246
|
// (`Hinge.attach`).
|
|
@@ -23,11 +23,15 @@ import dev.apexnative.navigation.viewcontrollers.viewcontroller.ScrollEventListe
|
|
|
23
23
|
* is made with the view, from the app's React host, and fills it; it starts once, as the view joins a window, and stops
|
|
24
24
|
* once, on the main thread, as the view is destroyed.
|
|
25
25
|
*
|
|
26
|
-
* Ready once the safe area view of the screen told [ScreenReadiness] that the content
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
26
|
+
* Ready once the safe area view of the screen, in this view's own surface, told [ScreenReadiness] that the content
|
|
27
|
+
* rendered into it: the signal of another root with the same id (an old one, another live root of a bar component)
|
|
28
|
+
* never makes this one ready. A screen without a safe area view (no view ever told anything of its id) is ready once
|
|
29
|
+
* the root view of its surface has children, read on this view's own layout passes. A root that renders at once
|
|
30
|
+
* ([rendersAtOnce]: a component of a bar, a tab's cell) marks its id so from before its surface starts until it stops.
|
|
31
|
+
* Ready once, ready for good; what waits for it ([whenReady]) runs then, once.
|
|
32
|
+
* Without a signal or children, the limits of what waits decide: the view has no timer of its own. It owns the
|
|
33
|
+
* record of its appearance, which the dispatcher of the appearance events holds until the content is ready
|
|
34
|
+
* (`AppearanceDispatcher`).
|
|
31
35
|
*/
|
|
32
36
|
@SuppressLint("ViewConstructor")
|
|
33
37
|
class ReactView @JvmOverloads constructor(
|
|
@@ -36,6 +40,8 @@ class ReactView @JvmOverloads constructor(
|
|
|
36
40
|
override val componentName: String,
|
|
37
41
|
/** Props given by the native side, over `componentId`: the state of a tab's cell. */
|
|
38
42
|
initialProps: Bundle? = null,
|
|
43
|
+
/** The root renders at once, without waiting for its layout: a component of a bar or a tab's cell. */
|
|
44
|
+
private val rendersAtOnce: Boolean = false,
|
|
39
45
|
) : FrameLayout(context), IReactView {
|
|
40
46
|
private val scrollEventListener = ScrollEventListener()
|
|
41
47
|
private var destroyed = false
|
|
@@ -44,7 +50,14 @@ class ReactView @JvmOverloads constructor(
|
|
|
44
50
|
// What waited for the readiness was told; a question of isReady alone tells nothing, so nothing runs inside it.
|
|
45
51
|
private var readyTold = false
|
|
46
52
|
private val readyListeners = mutableListOf<Runnable>()
|
|
47
|
-
private var
|
|
53
|
+
private var listening = false
|
|
54
|
+
|
|
55
|
+
// What this root hears of its id: only the signals of its own surface.
|
|
56
|
+
private val readiness = object : ScreenReadiness.Listener {
|
|
57
|
+
override fun accepts(source: Int): Boolean = ownSource()?.let { it == source } ?: true
|
|
58
|
+
|
|
59
|
+
override fun onContentRendered() = checkReadiness()
|
|
60
|
+
}
|
|
48
61
|
|
|
49
62
|
// None without the app's host (the unit tests).
|
|
50
63
|
private val surface: ReactSurface?
|
|
@@ -59,8 +72,17 @@ class ReactView @JvmOverloads constructor(
|
|
|
59
72
|
*/
|
|
60
73
|
internal var contentChildCount: () -> Int = { (surface?.view as? ViewGroup)?.childCount ?: 0 }
|
|
61
74
|
|
|
75
|
+
/**
|
|
76
|
+
* The id of this root's React surface, which the signals of its safe area view carry: React Native gives it as
|
|
77
|
+
* the surface starts (none matches before) and never gives it again. Null without a surface: every signal of the
|
|
78
|
+
* id is its own. The unit tests give their own.
|
|
79
|
+
*/
|
|
80
|
+
internal var ownSource: () -> Int? = { surface?.surfaceID }
|
|
81
|
+
|
|
62
82
|
init {
|
|
63
83
|
require(componentId.isNotEmpty()) { "a React root needs the id of its screen" }
|
|
84
|
+
// Before the surface exists, so its safe area view never asks before the mark is there.
|
|
85
|
+
if (rendersAtOnce) ScreenReadiness.setRendersAtOnce(componentId, this, true)
|
|
64
86
|
val props = Bundle().apply {
|
|
65
87
|
initialProps?.let(::putAll)
|
|
66
88
|
putString(COMPONENT_ID, componentId)
|
|
@@ -103,14 +125,9 @@ class ReactView @JvmOverloads constructor(
|
|
|
103
125
|
if (readyTold) return listener.run()
|
|
104
126
|
readyListeners += listener
|
|
105
127
|
if (isReady()) return checkReadiness()
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
override fun onContentRendered() = checkReadiness()
|
|
111
|
-
}
|
|
112
|
-
readinessListener = readiness
|
|
113
|
-
ScreenReadiness.setListener(componentId, readiness)
|
|
128
|
+
if (listening) return
|
|
129
|
+
listening = true
|
|
130
|
+
ScreenReadiness.addListener(componentId, this, readiness)
|
|
114
131
|
}
|
|
115
132
|
|
|
116
133
|
/**
|
|
@@ -126,23 +143,24 @@ class ReactView @JvmOverloads constructor(
|
|
|
126
143
|
waiting.forEach(Runnable::run)
|
|
127
144
|
}
|
|
128
145
|
|
|
129
|
-
// The safe area view said so; without one, the root view of the surface has children.
|
|
130
|
-
private fun hasRendered(): Boolean = when (ScreenReadiness.isContentRendered(componentId)) {
|
|
146
|
+
// The safe area view of this root's surface said so; without one, the root view of the surface has children.
|
|
147
|
+
private fun hasRendered(): Boolean = when (ScreenReadiness.isContentRendered(componentId, readiness)) {
|
|
131
148
|
true -> true
|
|
132
149
|
false -> false
|
|
133
150
|
null -> contentChildCount() > 0
|
|
134
151
|
}
|
|
135
152
|
|
|
136
153
|
private fun stopListening() {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
ScreenReadiness.removeListener(componentId,
|
|
154
|
+
if (!listening) return
|
|
155
|
+
listening = false
|
|
156
|
+
ScreenReadiness.removeListener(componentId, this)
|
|
140
157
|
}
|
|
141
158
|
|
|
142
159
|
/**
|
|
143
160
|
* Stops the content, once, on the main thread: its appearance ends first (DidDisappear if its WillAppear went,
|
|
144
161
|
* else what is held goes with it), then the surface stops and leaves the host, so a reload doesn't start it again
|
|
145
|
-
* in the new JavaScript.
|
|
162
|
+
* in the new JavaScript. A root that renders at once takes its mark away: the last one of its id takes what was
|
|
163
|
+
* said of the id with it.
|
|
146
164
|
*/
|
|
147
165
|
override fun destroy() {
|
|
148
166
|
if (destroyed) return
|
|
@@ -150,6 +168,7 @@ class ReactView @JvmOverloads constructor(
|
|
|
150
168
|
destroyed = true
|
|
151
169
|
readyListeners.clear()
|
|
152
170
|
stopListening()
|
|
171
|
+
if (rendersAtOnce) ScreenReadiness.setRendersAtOnce(componentId, this, false)
|
|
153
172
|
val surface = surface ?: return
|
|
154
173
|
if (started) surface.stop()
|
|
155
174
|
surface.detach()
|
|
@@ -4,7 +4,7 @@ import com.facebook.react.bridge.ReactContext
|
|
|
4
4
|
import dev.apexnative.navigation.NavigationActivity
|
|
5
5
|
import dev.apexnative.navigation.nativescreen.NativeScreenCalls
|
|
6
6
|
import dev.apexnative.navigation.react.events.EventEmitter
|
|
7
|
-
import dev.apexnative.navigation.react.events.
|
|
7
|
+
import dev.apexnative.navigation.react.events.NavigationState
|
|
8
8
|
import dev.apexnative.navigation.utils.UiThread
|
|
9
9
|
import dev.apexnative.navigation.viewcontrollers.navigator.BackCallbackState
|
|
10
10
|
import dev.apexnative.navigation.viewcontrollers.navigator.Navigator
|
|
@@ -39,7 +39,7 @@ internal object ReloadCoordinator {
|
|
|
39
39
|
/**
|
|
40
40
|
* The JavaScript goes (a reload, or the host is destroyed). Any thread: the number grows at once; the layouts of
|
|
41
41
|
* every navigation activity go on the main thread, with the "reloading" mark, and so do the listeners of the
|
|
42
|
-
* JavaScript that went (back, hinge).
|
|
42
|
+
* JavaScript that went (back, hinge) and the holds of the navigation state its `setRoot` left.
|
|
43
43
|
*/
|
|
44
44
|
fun onJavaScriptGoes() {
|
|
45
45
|
advance()
|
|
@@ -48,6 +48,7 @@ internal object ReloadCoordinator {
|
|
|
48
48
|
launches.contextGone()
|
|
49
49
|
try {
|
|
50
50
|
Navigators.all().forEach { it.onReload() }
|
|
51
|
+
NavigationState.javaScriptWent()
|
|
51
52
|
BackCallbackState.setJsListeners(false)
|
|
52
53
|
Hinge.setEnabled(Navigators.current(), false)
|
|
53
54
|
} finally {
|
|
@@ -59,13 +60,15 @@ internal object ReloadCoordinator {
|
|
|
59
60
|
/**
|
|
60
61
|
* The developer tools of [activity] reload the JavaScript ("R R", the reload broadcast): the number grows first, so
|
|
61
62
|
* the navigation of the activity goes without a word to the JavaScript that goes, then its layers go
|
|
62
|
-
* ([NavigationActivity.onReload]). The host's own reset follows as the
|
|
63
|
+
* ([NavigationActivity.onReload]) with the holds of the navigation state. The host's own reset follows as the
|
|
64
|
+
* reload destroys it. Main thread.
|
|
63
65
|
*/
|
|
64
66
|
fun resetForDeveloperReload(activity: NavigationActivity) {
|
|
65
67
|
advance()
|
|
66
68
|
Navigator.setReloading(true)
|
|
67
69
|
try {
|
|
68
70
|
activity.onReload()
|
|
71
|
+
NavigationState.javaScriptWent()
|
|
69
72
|
} finally {
|
|
70
73
|
Navigator.setReloading(false)
|
|
71
74
|
}
|
|
@@ -79,8 +82,6 @@ internal object ReloadCoordinator {
|
|
|
79
82
|
UiThread.runOnMain {
|
|
80
83
|
launches.contextReady(context)
|
|
81
84
|
NativeScreenCalls.failAll { name, componentId -> ErrorTexts.nativeCallReloaded(name, componentId) }
|
|
82
|
-
// Scroll listeners that found no event dispatcher listen now.
|
|
83
|
-
ScrollEvents.dispatcherReady()
|
|
84
85
|
Navigators.all().filter { it.isNavigationResumed }.forEach(::launchIfDue)
|
|
85
86
|
}
|
|
86
87
|
}
|
|
@@ -92,7 +93,7 @@ internal object ReloadCoordinator {
|
|
|
92
93
|
*/
|
|
93
94
|
fun launchIfDue(activity: NavigationActivity) {
|
|
94
95
|
val context = launches.takeDue(activity, activity.isNavigationResumed) ?: return
|
|
95
|
-
|
|
96
|
+
NavigationState.reset()
|
|
96
97
|
EventEmitter(context).emitAppLaunched()
|
|
97
98
|
}
|
|
98
99
|
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
package dev.apexnative.navigation.react
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableArray
|
|
5
|
+
import com.facebook.react.bridge.WritableMap
|
|
6
|
+
import dev.apexnative.navigation.react.events.EventContract
|
|
7
|
+
import dev.apexnative.navigation.react.events.EventEmitter
|
|
8
|
+
import java.util.concurrent.ConcurrentHashMap
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* What JavaScript knows of each screen's place, by the screen's id, in one record: its safe area (the insets JavaScript
|
|
12
|
+
* gets and the padding of its safe area view, `useSafeAreaInsets`), its reserved regions (`useReservedRegions`) and,
|
|
13
|
+
* in a pair, its arrangement (`useArrangement`). The core measures them (`ScreenSafeArea`, [ScreenRegions], the pair's
|
|
14
|
+
* plan) on the main thread and keeps them here; JavaScript reads them from its own thread for a screen's first render
|
|
15
|
+
* and gets every change as an event. The record is immutable and replaced at once, so a reader on another thread sees
|
|
16
|
+
* one whole record. When the screen goes, [remove] clears all of it. As on iOS (`ANNScreenEnvironment`).
|
|
17
|
+
*/
|
|
18
|
+
internal object ScreenEnvironment {
|
|
19
|
+
/** How much covers each edge of a screen's content, in dp. */
|
|
20
|
+
data class Insets(val top: Double, val right: Double, val bottom: Double, val left: Double)
|
|
21
|
+
|
|
22
|
+
/** A reserved region, in dp from the top left of the screen's content. */
|
|
23
|
+
data class Region(val x: Double, val y: Double, val width: Double, val height: Double)
|
|
24
|
+
|
|
25
|
+
/** Where a screen divides (`division`) and where hardware covers it (`occlusion`). */
|
|
26
|
+
data class Regions(val division: List<Region>, val occlusion: List<Region>) {
|
|
27
|
+
val isEmpty: Boolean get() = division.isEmpty() && occlusion.isEmpty()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Which screen of its pair a screen is, how the pair lies, whether it lies over the other one and is hidden. */
|
|
31
|
+
data class Arrangement(val placement: String, val layout: String, val front: Boolean, val hidden: Boolean)
|
|
32
|
+
|
|
33
|
+
/** Where the changes JavaScript hears go: the events of the screen's JavaScript, or a test's record. */
|
|
34
|
+
interface Changes {
|
|
35
|
+
fun insetsChanged(screenId: String, insets: Insets)
|
|
36
|
+
|
|
37
|
+
fun regionsChanged(screenId: String, regions: Regions)
|
|
38
|
+
|
|
39
|
+
fun arrangementChanged(screenId: String, arrangement: Arrangement)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// One screen's record; null for a fact not known yet (not laid out, outside a pair).
|
|
43
|
+
private data class Record(
|
|
44
|
+
val insets: Insets? = null,
|
|
45
|
+
val padding: Insets? = null,
|
|
46
|
+
val regions: Regions? = null,
|
|
47
|
+
val arrangement: Arrangement? = null,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
private val NO_REGIONS = Regions(emptyList(), emptyList())
|
|
51
|
+
private val records = ConcurrentHashMap<String, Record>()
|
|
52
|
+
// Main thread only.
|
|
53
|
+
private val paddingListeners = HashMap<String, MutableSet<() -> Unit>>()
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Keeps the screen's [insets] for JavaScript (what still covers its content) and the [padding] of its safe area
|
|
57
|
+
* view. Calls the screen's padding listeners when the padding changed, then tells JavaScript when the insets did.
|
|
58
|
+
* Main thread only.
|
|
59
|
+
*/
|
|
60
|
+
fun setInsets(screenId: String, insets: Insets, padding: Insets, changes: Changes) {
|
|
61
|
+
val before = update(screenId) { it.copy(insets = insets, padding = padding) }
|
|
62
|
+
if (before?.padding != padding) paddingListeners[screenId]?.toList()?.forEach { it() }
|
|
63
|
+
if (before?.insets != insets) changes.insetsChanged(screenId, insets)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Keeps the screen's [regions], and tells JavaScript when they changed; none at first is what JavaScript reads
|
|
68
|
+
* before the first layout, so it isn't told. Main thread only.
|
|
69
|
+
*/
|
|
70
|
+
fun setRegions(screenId: String, regions: Regions, changes: Changes) {
|
|
71
|
+
val before = update(screenId) { it.copy(regions = regions) }?.regions
|
|
72
|
+
if (before == regions || (before == null && regions.isEmpty)) return
|
|
73
|
+
changes.regionsChanged(screenId, regions)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Keeps the screen's [arrangement], and tells JavaScript when it changed and [notify] holds. Main thread only. */
|
|
77
|
+
fun setArrangement(screenId: String, arrangement: Arrangement, notify: Boolean, changes: Changes) {
|
|
78
|
+
val before = update(screenId) { it.copy(arrangement = arrangement) }?.arrangement
|
|
79
|
+
if (before == arrangement || !notify) return
|
|
80
|
+
changes.arrangementChanged(screenId, arrangement)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The padding of the screen's safe area view; none before the screen was laid out. Any thread. */
|
|
84
|
+
fun padding(screenId: String): Insets? = records[screenId]?.padding
|
|
85
|
+
|
|
86
|
+
/** The insets JavaScript gets; none before the screen was laid out. Any thread. */
|
|
87
|
+
fun insets(screenId: String): Insets? = records[screenId]?.insets
|
|
88
|
+
|
|
89
|
+
/** The screen's reserved regions; none before its first layout. Any thread. */
|
|
90
|
+
fun regions(screenId: String): Regions? = records[screenId]?.regions
|
|
91
|
+
|
|
92
|
+
/** The screen's arrangement; none outside a pair. Any thread. */
|
|
93
|
+
fun arrangement(screenId: String): Arrangement? = records[screenId]?.arrangement
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Calls `listener` when the padding of the screen changes; returns the function that stops it. Main thread only.
|
|
97
|
+
*/
|
|
98
|
+
fun listenToPadding(screenId: String, listener: () -> Unit): () -> Unit {
|
|
99
|
+
paddingListeners.getOrPut(screenId) { LinkedHashSet() }.add(listener)
|
|
100
|
+
return {
|
|
101
|
+
paddingListeners[screenId]?.let {
|
|
102
|
+
it.remove(listener)
|
|
103
|
+
if (it.isEmpty()) paddingListeners.remove(screenId)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Forgets everything kept for the screen. Its padding listeners stay: a live safe area view keeps listening while
|
|
110
|
+
* it keeps the id, and stops itself.
|
|
111
|
+
*/
|
|
112
|
+
fun remove(screenId: String) {
|
|
113
|
+
records.remove(screenId)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** `getScreenInsets`: `{ top, right, bottom, left }`, empty before the screen was laid out. Any thread. */
|
|
117
|
+
fun getScreenInsets(screenId: String): WritableMap = insets(screenId)?.let(::toMap) ?: Arguments.createMap()
|
|
118
|
+
|
|
119
|
+
/** `getScreenRegions`: `{ division, occlusion }`, empty lists before the first layout. Any thread. */
|
|
120
|
+
fun getScreenRegions(screenId: String): WritableMap = toMap(regions(screenId) ?: NO_REGIONS)
|
|
121
|
+
|
|
122
|
+
/** `getArrangementState`: `{ placement, layout, front, hidden }`, empty outside a pair. Any thread. */
|
|
123
|
+
fun getArrangementState(screenId: String): WritableMap =
|
|
124
|
+
arrangement(screenId)?.let(::toMap) ?: Arguments.createMap()
|
|
125
|
+
|
|
126
|
+
/** The changes as events through [emitter], the one of the JavaScript the screen was made for. */
|
|
127
|
+
fun changesOf(emitter: EventEmitter): Changes = object : Changes {
|
|
128
|
+
override fun insetsChanged(screenId: String, insets: Insets) =
|
|
129
|
+
emitter.emitScreenInsetsChanged(screenId, toMap(insets))
|
|
130
|
+
|
|
131
|
+
override fun regionsChanged(screenId: String, regions: Regions) =
|
|
132
|
+
emitter.emitScreenRegionsChanged(screenId, toMap(regions))
|
|
133
|
+
|
|
134
|
+
override fun arrangementChanged(screenId: String, arrangement: Arrangement) =
|
|
135
|
+
emitter.emitArrangementChanged(screenId, toMap(arrangement))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Replaces the screen's record at once with [change] of it; returns the record before, null for a new screen.
|
|
139
|
+
private fun update(screenId: String, change: (Record) -> Record): Record? {
|
|
140
|
+
var before: Record? = null
|
|
141
|
+
records.compute(screenId) { _, old ->
|
|
142
|
+
before = old
|
|
143
|
+
change(old ?: Record())
|
|
144
|
+
}
|
|
145
|
+
return before
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private fun toMap(value: Insets): WritableMap = Arguments.createMap().apply {
|
|
149
|
+
putDouble(EventContract.ScreenInsetsChanged.TOP, value.top)
|
|
150
|
+
putDouble(EventContract.ScreenInsetsChanged.RIGHT, value.right)
|
|
151
|
+
putDouble(EventContract.ScreenInsetsChanged.BOTTOM, value.bottom)
|
|
152
|
+
putDouble(EventContract.ScreenInsetsChanged.LEFT, value.left)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
private fun toMap(value: Regions): WritableMap = Arguments.createMap().apply {
|
|
156
|
+
putArray(EventContract.ScreenRegionsChanged.DIVISION, toArray(value.division))
|
|
157
|
+
putArray(EventContract.ScreenRegionsChanged.OCCLUSION, toArray(value.occlusion))
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
private fun toArray(regions: List<Region>): WritableArray = Arguments.createArray().apply {
|
|
161
|
+
for (region in regions) {
|
|
162
|
+
pushMap(Arguments.createMap().apply {
|
|
163
|
+
putDouble(EventContract.ScreenRegionRect.X, region.x)
|
|
164
|
+
putDouble(EventContract.ScreenRegionRect.Y, region.y)
|
|
165
|
+
putDouble(EventContract.ScreenRegionRect.WIDTH, region.width)
|
|
166
|
+
putDouble(EventContract.ScreenRegionRect.HEIGHT, region.height)
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private fun toMap(value: Arrangement): WritableMap = Arguments.createMap().apply {
|
|
172
|
+
putString(EventContract.ArrangementChanged.PLACEMENT, value.placement)
|
|
173
|
+
putString(EventContract.ArrangementChanged.LAYOUT, value.layout)
|
|
174
|
+
putBoolean(EventContract.ArrangementChanged.FRONT, value.front)
|
|
175
|
+
putBoolean(EventContract.ArrangementChanged.HIDDEN, value.hidden)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -3,38 +3,33 @@ package dev.apexnative.navigation.react
|
|
|
3
3
|
import android.app.Activity
|
|
4
4
|
import android.graphics.Rect
|
|
5
5
|
import androidx.core.view.WindowInsetsCompat
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import com.facebook.react.bridge.WritableArray
|
|
9
|
-
import com.facebook.react.bridge.WritableMap
|
|
10
|
-
import dev.apexnative.navigation.react.events.EventContract
|
|
6
|
+
import dev.apexnative.navigation.react.ScreenEnvironment.Region
|
|
7
|
+
import dev.apexnative.navigation.react.ScreenEnvironment.Regions
|
|
11
8
|
import dev.apexnative.navigation.react.events.EventEmitter
|
|
12
9
|
import dev.apexnative.navigation.utils.WindowFolds
|
|
13
|
-
import java.util.concurrent.ConcurrentHashMap
|
|
14
10
|
|
|
15
11
|
/**
|
|
16
|
-
*
|
|
17
|
-
* (a fold the window is divided at, `division`) and where hardware covers it (a fold that hides what is
|
|
18
|
-
* camera cutout: `occlusion`). Only those on the screen, cut to it: a fold at the edge of a screen of a
|
|
19
|
-
* it. Measured on the main thread as each screen lays out, and again after a change of the window's
|
|
20
|
-
* the window out);
|
|
21
|
-
* `ANN.ScreenRegionsChanged`.
|
|
12
|
+
* Measures the reserved regions of each screen (`useReservedRegions`), in dp from the top left of its content: where
|
|
13
|
+
* it divides (a fold the window is divided at, `division`) and where hardware covers it (a fold that hides what is
|
|
14
|
+
* under it, the camera cutout: `occlusion`). Only those on the screen, cut to it: a fold at the edge of a screen of a
|
|
15
|
+
* pair is not on it. Measured on the main thread as each screen lays out, and again after a change of the window's
|
|
16
|
+
* folds (which lays the window out); [ScreenEnvironment] keeps them, answers JavaScript's reads and tells it of every
|
|
17
|
+
* change as `ANN.ScreenRegionsChanged`.
|
|
22
18
|
*/
|
|
23
19
|
internal object ScreenRegions {
|
|
24
|
-
data class Region(val x: Double, val y: Double, val width: Double, val height: Double)
|
|
25
|
-
|
|
26
|
-
data class Regions(val division: List<Region>, val occlusion: List<Region>) {
|
|
27
|
-
val isEmpty: Boolean get() = division.isEmpty() && occlusion.isEmpty()
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
private val NONE = Regions(emptyList(), emptyList())
|
|
31
|
-
private val regions = ConcurrentHashMap<String, Regions>()
|
|
32
|
-
|
|
33
20
|
/**
|
|
34
21
|
* Measures the regions of the screen whose content the layout puts at [content] in the window (pixels), with the
|
|
35
|
-
* window's [insets] (the camera cutout), and tells JavaScript when they
|
|
22
|
+
* window's [insets] (the camera cutout), and keeps them in [ScreenEnvironment], which tells JavaScript when they
|
|
23
|
+
* changed through [emitter], the one of the JavaScript the screen was made for: silent once it went. Main thread
|
|
24
|
+
* only.
|
|
36
25
|
*/
|
|
37
|
-
fun update(
|
|
26
|
+
fun update(
|
|
27
|
+
activity: Activity,
|
|
28
|
+
screenId: String,
|
|
29
|
+
content: Rect,
|
|
30
|
+
insets: WindowInsetsCompat?,
|
|
31
|
+
emitter: EventEmitter,
|
|
32
|
+
) {
|
|
38
33
|
val folds = WindowFolds.of(activity)?.folds.orEmpty()
|
|
39
34
|
val cutouts = insets?.displayCutout?.boundingRects.orEmpty()
|
|
40
35
|
val density = activity.resources.displayMetrics.density
|
|
@@ -43,18 +38,7 @@ internal object ScreenRegions {
|
|
|
43
38
|
occlusion = (folds.filter { it.occludes }.map { it.bounds } + cutouts)
|
|
44
39
|
.mapNotNull { onScreen(it, content, density) },
|
|
45
40
|
)
|
|
46
|
-
|
|
47
|
-
// None at first is what JavaScript reads before the first layout.
|
|
48
|
-
if (previous == value || (previous == null && value.isEmpty)) return
|
|
49
|
-
val reactContext = (activity.application as? ReactApplication)?.reactHost?.currentReactContext ?: return
|
|
50
|
-
EventEmitter(reactContext).emitScreenRegionsChanged(screenId, toMap(value))
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** `{ division, occlusion }`, empty lists for an unknown screen and before its first layout. Any thread. */
|
|
54
|
-
fun get(screenId: String): WritableMap = toMap(regions[screenId] ?: NONE)
|
|
55
|
-
|
|
56
|
-
fun remove(screenId: String) {
|
|
57
|
-
regions.remove(screenId)
|
|
41
|
+
ScreenEnvironment.setRegions(screenId, value, ScreenEnvironment.changesOf(emitter))
|
|
58
42
|
}
|
|
59
43
|
|
|
60
44
|
// The part of [bounds] (window pixels) on the screen, in dp from its top left; null off it. A fold without width
|
|
@@ -69,20 +53,4 @@ internal object ScreenRegions {
|
|
|
69
53
|
height = part.height() / density.toDouble(),
|
|
70
54
|
)
|
|
71
55
|
}
|
|
72
|
-
|
|
73
|
-
private fun toMap(value: Regions): WritableMap = Arguments.createMap().apply {
|
|
74
|
-
putArray(EventContract.ScreenRegionsChanged.DIVISION, toArray(value.division))
|
|
75
|
-
putArray(EventContract.ScreenRegionsChanged.OCCLUSION, toArray(value.occlusion))
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
private fun toArray(regions: List<Region>): WritableArray = Arguments.createArray().apply {
|
|
79
|
-
for (region in regions) {
|
|
80
|
-
pushMap(Arguments.createMap().apply {
|
|
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)
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
56
|
}
|