@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.
- package/ApexNavigation.podspec +2 -1
- package/CHANGELOG.md +7 -0
- package/android/CLAUDE.md +19 -10
- package/android/CORE.md +3 -2
- package/android/consumer-rules.pro +6 -0
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
- package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
- package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
- package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
- package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
- package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +329 -55
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +26 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +46 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +36 -38
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
- package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
- package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
- package/android/src/main/jni/ANNContentHeights.cpp +82 -0
- package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
- package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
- package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
- package/android/src/main/res/values/styles.xml +1 -1
- package/android/src/main/res/values-night/styles.xml +1 -1
- package/cli/doctor/checks/app.js +16 -1
- package/cli/project.js +4 -0
- package/cli/setup/index.js +5 -0
- package/cli/setup/steps/metro.js +46 -0
- package/docs-md/architecture/native-screens.md +4 -0
- package/docs-md/architecture/theme-and-context.md +131 -12
- package/docs-md/architecture/transitions.md +43 -4
- package/docs-md/flows/bottom-sheets.md +4 -2
- package/docs-md/fundamentals/screens-and-props.md +9 -1
- package/docs-md/guides/cli.md +3 -1
- package/docs-md/help/troubleshooting.md +1 -1
- package/docs-md/index.json +29 -1
- package/docs-md/screen-ui/back-and-gestures.md +3 -1
- package/docs-md/screen-ui/bar-buttons.md +1 -1
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
- package/docs-md/start/installation.md +16 -1
- package/ios/Animations/ANNFrameDriver.h +55 -15
- package/ios/Animations/ANNFrameDriver.mm +214 -60
- package/ios/Animations/ANNTransitionAnimator.h +26 -5
- package/ios/Animations/ANNTransitionAnimator.mm +49 -3
- package/ios/Animations/ANNTransitionPlan.h +2 -2
- package/ios/Animations/ANNTransitionPlan.mm +10 -0
- package/ios/Animations/Elements/ANNElementFinder.h +2 -2
- package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
- package/ios/Animations/Elements/ANNElementTransition.h +3 -3
- package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
- package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
- package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
- package/ios/CLAUDE.md +15 -7
- package/ios/CORE.md +1 -1
- package/ios/Core/ANNAppDelegate.mm +0 -2
- package/ios/Core/ANNDeepLinkQueue.h +6 -5
- package/ios/Core/ANNDeepLinkQueue.mm +22 -10
- package/ios/Core/ANNSceneDelegate.mm +2 -2
- package/ios/Core/ApexNavigation.mm +6 -2
- package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
- package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
- package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
- package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
- package/ios/Layouts/Stack/ANNStackController.h +8 -0
- package/ios/Layouts/Stack/ANNStackController.mm +153 -11
- package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
- package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
- package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
- package/ios/Options/ANNAnimationOptions.h +2 -1
- package/ios/Options/ANNAnimationOptions.mm +1 -1
- package/ios/Options/ANNOptions.h +7 -0
- package/ios/Options/ANNOptions.mm +45 -0
- package/ios/Options/ANNOptionsReader.mm +2 -4
- package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
- package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
- package/ios/Options/Params/ColorParser.h +4 -3
- package/ios/Options/Params/ColorParser.mm +22 -4
- package/ios/React/ANNSurfaceHostingView.h +8 -0
- package/ios/React/ANNSurfaceHostingView.mm +28 -1
- package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
- package/ios/TurboModules/ANNTurboModule.mm +46 -0
- package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
- package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
- package/lib/module/ARCHITECTURE.md +9 -3
- package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
- package/lib/module/api/app/AppRegistry.js +14 -2
- package/lib/module/api/app/AppRegistry.js.map +1 -1
- package/lib/module/api/app/createNavigation.js +188 -66
- package/lib/module/api/app/createNavigation.js.map +1 -1
- package/lib/module/api/events/navigationEvents.js +6 -2
- package/lib/module/api/events/navigationEvents.js.map +1 -1
- package/lib/module/api/linking/linking.js +11 -0
- package/lib/module/api/linking/linking.js.map +1 -1
- package/lib/module/api/screens/defineScreen.js +45 -3
- package/lib/module/api/screens/defineScreen.js.map +1 -1
- package/lib/module/api/screens/nativeScreens.js +31 -6
- package/lib/module/api/screens/nativeScreens.js.map +1 -1
- package/lib/module/api/screens/registerScreens.js +172 -35
- package/lib/module/api/screens/registerScreens.js.map +1 -1
- package/lib/module/api/theme/theme.js +5 -0
- package/lib/module/api/theme/theme.js.map +1 -1
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/layout.js +7 -2
- package/lib/module/engine/layout.js.map +1 -1
- package/lib/module/engine/nativeScreens.js +9 -0
- package/lib/module/engine/nativeScreens.js.map +1 -1
- package/lib/module/engine/options.js +5 -4
- package/lib/module/engine/options.js.map +1 -1
- package/lib/module/engine/optionsChange.js +102 -0
- package/lib/module/engine/optionsChange.js.map +1 -0
- package/lib/module/engine/refresh.js +85 -0
- package/lib/module/engine/refresh.js.map +1 -0
- package/lib/module/engine/rememberedOptions.js +60 -0
- package/lib/module/engine/rememberedOptions.js.map +1 -1
- package/lib/module/engine/screens.js +26 -0
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
- package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +5 -0
- package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
- package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
- package/lib/typescript/api/index.d.ts +1 -1
- package/lib/typescript/api/index.d.ts.map +1 -1
- package/lib/typescript/api/linking/linking.d.ts +8 -0
- package/lib/typescript/api/linking/linking.d.ts.map +1 -1
- package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
- package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
- package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
- package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
- package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
- package/lib/typescript/api/theme/theme.d.ts +2 -0
- package/lib/typescript/api/theme/theme.d.ts.map +1 -1
- package/lib/typescript/engine/commands.d.ts +5 -0
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/layout.d.ts.map +1 -1
- package/lib/typescript/engine/nativeScreens.d.ts +5 -0
- package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
- package/lib/typescript/engine/optionsChange.d.ts +28 -0
- package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
- package/lib/typescript/engine/refresh.d.ts +35 -0
- package/lib/typescript/engine/refresh.d.ts.map +1 -0
- package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
- package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +7 -0
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
- package/lib/typescript/interfaces/options/animations.d.ts +40 -15
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
- package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
- package/lib/typescript/interfaces/options/modals.d.ts +6 -7
- package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/stack.d.ts +8 -3
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/metro/acceptPlugin.js +57 -0
- package/metro/babelTransformer.js +45 -0
- package/metro/index.js +36 -0
- package/package.json +3 -1
- package/src/ARCHITECTURE.md +9 -3
- package/src/adapters/NativeANNTurboModule.ts +7 -0
- package/src/api/app/AppRegistry.ts +16 -2
- package/src/api/app/createNavigation.ts +198 -66
- package/src/api/events/navigationEvents.ts +9 -3
- package/src/api/index.ts +1 -0
- package/src/api/linking/linking.ts +22 -1
- package/src/api/screens/defineScreen.ts +49 -5
- package/src/api/screens/nativeScreens.ts +40 -4
- package/src/api/screens/registerScreens.tsx +170 -42
- package/src/api/theme/theme.ts +5 -0
- package/src/engine/commands.ts +21 -0
- package/src/engine/layout.ts +11 -2
- package/src/engine/nativeScreens.ts +10 -1
- package/src/engine/options.ts +6 -8
- package/src/engine/optionsChange.ts +107 -0
- package/src/engine/refresh.ts +97 -0
- package/src/engine/rememberedOptions.ts +70 -0
- package/src/engine/screens.tsx +24 -0
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/options/animations.ts +40 -15
- package/src/interfaces/options/bottomTabs.ts +2 -2
- package/src/interfaces/options/buttons.ts +2 -2
- package/src/interfaces/options/modals.ts +6 -7
- package/src/interfaces/options/stack.ts +8 -3
- package/src/interfaces/options/topBar.ts +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
package dev.apexnative.navigation.views.stack.topbar.titlebar
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.res.ColorStateList
|
|
6
|
+
import android.graphics.drawable.Drawable
|
|
7
|
+
import android.view.MotionEvent
|
|
8
|
+
import android.view.View
|
|
9
|
+
import android.view.ViewGroup
|
|
10
|
+
import androidx.appcompat.R as AppCompatR
|
|
11
|
+
import androidx.appcompat.widget.AppCompatImageButton
|
|
12
|
+
import androidx.appcompat.widget.AppCompatImageView
|
|
13
|
+
import androidx.appcompat.widget.TooltipCompat
|
|
14
|
+
import androidx.core.graphics.drawable.DrawableCompat
|
|
15
|
+
import androidx.core.widget.ImageViewCompat
|
|
16
|
+
import com.google.android.material.R as MaterialR
|
|
17
|
+
import com.google.android.material.theme.overlay.MaterialThemeOverlay
|
|
18
|
+
import dev.apexnative.navigation.utils.MaterialTheme
|
|
19
|
+
import dev.apexnative.navigation.utils.UiUtils
|
|
20
|
+
import kotlin.math.max
|
|
21
|
+
import kotlin.math.min
|
|
22
|
+
import kotlin.math.roundToInt
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The buttons of one side of the bar's row, from the start of the line: the back button ([navigationIcon]), the
|
|
26
|
+
* buttons ([setButtons]), what the core always puts after them ([setTrailing]: the search's magnifier) and "⋮"
|
|
27
|
+
* ([overflowButton]). They stand in the middle of the row, at most 56 dp high, as a Material 3 toolbar stands its
|
|
28
|
+
* buttons, with the toolbar's theme (its ripples); right to left the line mirrors. Main thread only.
|
|
29
|
+
*/
|
|
30
|
+
@SuppressLint("ViewConstructor")
|
|
31
|
+
class ButtonRow(context: Context) : ViewGroup(themed(context)) {
|
|
32
|
+
private val maxButtonHeight = UiUtils.dpToPx(this.context, MAX_BUTTON_HEIGHT_DP).roundToInt()
|
|
33
|
+
private var navigation: AppCompatImageButton? = null
|
|
34
|
+
private var buttons: List<View> = emptyList()
|
|
35
|
+
private var trailing: View? = null
|
|
36
|
+
private var overflow: OverflowButton? = null
|
|
37
|
+
private var overflowColor: Int? = null
|
|
38
|
+
|
|
39
|
+
/** The back button's icon; none hides the button. */
|
|
40
|
+
var navigationIcon: Drawable?
|
|
41
|
+
get() = navigation?.takeIf { it.visibility == VISIBLE }?.drawable
|
|
42
|
+
set(value) {
|
|
43
|
+
if (value == null) {
|
|
44
|
+
navigation?.let {
|
|
45
|
+
it.setImageDrawable(null)
|
|
46
|
+
it.visibility = GONE
|
|
47
|
+
}
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
val button = navigation ?: AppCompatImageButton(context, null, AppCompatR.attr.toolbarNavigationButtonStyle)
|
|
51
|
+
.also {
|
|
52
|
+
navigation = it
|
|
53
|
+
arrange()
|
|
54
|
+
}
|
|
55
|
+
button.setImageDrawable(value)
|
|
56
|
+
button.visibility = VISIBLE
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The back button's label: its accessibility label and tooltip. */
|
|
60
|
+
var navigationContentDescription: CharSequence?
|
|
61
|
+
get() = navigation?.contentDescription
|
|
62
|
+
set(value) {
|
|
63
|
+
val button = navigation ?: return
|
|
64
|
+
button.contentDescription = value
|
|
65
|
+
TooltipCompat.setTooltipText(button, value)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The back button's view while it shows; none. */
|
|
69
|
+
val navigationButton: View?
|
|
70
|
+
get() = navigation?.takeIf { it.visibility == VISIBLE }
|
|
71
|
+
|
|
72
|
+
private var navigationListener: OnClickListener? = null
|
|
73
|
+
|
|
74
|
+
/** What a press of the back button does. */
|
|
75
|
+
fun setNavigationOnClickListener(listener: OnClickListener?) {
|
|
76
|
+
navigationListener = listener
|
|
77
|
+
navigation?.setOnClickListener(listener)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The buttons, in the line's order from its start. A view in another parent moves here. */
|
|
81
|
+
fun setButtons(views: List<View>) {
|
|
82
|
+
buttons = views
|
|
83
|
+
arrange()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** What stands after the buttons, before "⋮"; none. */
|
|
87
|
+
fun setTrailing(view: View?) {
|
|
88
|
+
if (trailing === view) return
|
|
89
|
+
trailing = view
|
|
90
|
+
arrange()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** What stands after the buttons now. */
|
|
94
|
+
val trailingView: View?
|
|
95
|
+
get() = trailing
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* "⋮", made the first time it is asked for and shown with [setOverflowShown]: the theme's
|
|
99
|
+
* `actionOverflowButtonStyle`, labelled "More options" in the user's language.
|
|
100
|
+
*/
|
|
101
|
+
val overflowButton: View
|
|
102
|
+
get() = overflow ?: OverflowButton(context).also {
|
|
103
|
+
it.visibility = GONE
|
|
104
|
+
overflowColor?.let { color -> ImageViewCompat.setImageTintList(it, ColorStateList.valueOf(color)) }
|
|
105
|
+
overflow = it
|
|
106
|
+
arrange()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** "⋮" shows at the end of the line, or not. */
|
|
110
|
+
fun setOverflowShown(shown: Boolean) {
|
|
111
|
+
if (!shown && overflow == null) return
|
|
112
|
+
overflowButton.visibility = if (shown) VISIBLE else GONE
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The color of "⋮", now or once it is made. */
|
|
116
|
+
fun setOverflowColor(color: Int) {
|
|
117
|
+
overflowColor = color
|
|
118
|
+
overflow?.let { ImageViewCompat.setImageTintList(it, ColorStateList.valueOf(color)) }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// The children in the line's order, so accessibility reads them in it too; a view that stays where it is isn't
|
|
122
|
+
// moved, which would detach it.
|
|
123
|
+
private fun arrange() {
|
|
124
|
+
val wanted = listOfNotNull(navigation) + buttons + listOfNotNull(trailing, overflow)
|
|
125
|
+
for (index in childCount - 1 downTo 0) {
|
|
126
|
+
if (getChildAt(index) !in wanted) removeViewAt(index)
|
|
127
|
+
}
|
|
128
|
+
wanted.forEachIndexed { index, view ->
|
|
129
|
+
if (index < childCount && getChildAt(index) === view) return@forEachIndexed
|
|
130
|
+
(view.parent as? ViewGroup)?.removeView(view)
|
|
131
|
+
addView(view, index, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT))
|
|
132
|
+
}
|
|
133
|
+
navigation?.setOnClickListener(navigationListener)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// As a toolbar, the row takes the touches around its buttons: they don't reach a screen under the bar.
|
|
137
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
138
|
+
override fun onTouchEvent(event: MotionEvent?): Boolean = true
|
|
139
|
+
|
|
140
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
141
|
+
val height = MeasureSpec.getSize(heightMeasureSpec)
|
|
142
|
+
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
|
143
|
+
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
|
|
144
|
+
val buttonHeight = min(height, maxButtonHeight)
|
|
145
|
+
var used = 0
|
|
146
|
+
for (index in 0 until childCount) {
|
|
147
|
+
val child = getChildAt(index)
|
|
148
|
+
if (child.visibility == GONE) continue
|
|
149
|
+
val widthSpec = if (widthMode == MeasureSpec.UNSPECIFIED) {
|
|
150
|
+
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
|
|
151
|
+
} else {
|
|
152
|
+
MeasureSpec.makeMeasureSpec(max(widthSize - used, 0), MeasureSpec.AT_MOST)
|
|
153
|
+
}
|
|
154
|
+
// The back button takes the whole height a toolbar gives its buttons; the others as much as they need.
|
|
155
|
+
val heightMode = if (child === navigation) MeasureSpec.EXACTLY else MeasureSpec.AT_MOST
|
|
156
|
+
child.measure(widthSpec, MeasureSpec.makeMeasureSpec(buttonHeight, heightMode))
|
|
157
|
+
used += child.measuredWidth
|
|
158
|
+
}
|
|
159
|
+
setMeasuredDimension(resolveSize(used, widthMeasureSpec), height)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
|
163
|
+
val width = r - l
|
|
164
|
+
val height = b - t
|
|
165
|
+
val rtl = layoutDirection == LAYOUT_DIRECTION_RTL
|
|
166
|
+
var start = 0
|
|
167
|
+
for (index in 0 until childCount) {
|
|
168
|
+
val child = getChildAt(index)
|
|
169
|
+
if (child.visibility == GONE) continue
|
|
170
|
+
val w = child.measuredWidth
|
|
171
|
+
val x = if (rtl) width - start - w else start
|
|
172
|
+
val top = (height - child.measuredHeight) / 2
|
|
173
|
+
child.layout(x, top, x + w, top + child.measuredHeight)
|
|
174
|
+
start += w
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// "⋮" as a toolbar draws it: its ripple a square around the icon, its label as its tooltip.
|
|
179
|
+
@SuppressLint("ViewConstructor")
|
|
180
|
+
private class OverflowButton(context: Context) :
|
|
181
|
+
AppCompatImageView(context, null, AppCompatR.attr.actionOverflowButtonStyle) {
|
|
182
|
+
init {
|
|
183
|
+
isClickable = true
|
|
184
|
+
isFocusable = true
|
|
185
|
+
TooltipCompat.setTooltipText(this, contentDescription)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
189
|
+
super.onSizeChanged(w, h, oldw, oldh)
|
|
190
|
+
val background = background ?: return
|
|
191
|
+
if (drawable == null) return
|
|
192
|
+
val half = max(w, h) / 2
|
|
193
|
+
val centerX = (w + paddingLeft - paddingRight) / 2
|
|
194
|
+
val centerY = (h + paddingTop - paddingBottom) / 2
|
|
195
|
+
DrawableCompat.setHotspotBounds(background, centerX - half, centerY - half, centerX + half, centerY + half)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private companion object {
|
|
200
|
+
// A toolbar's `maxButtonHeight`.
|
|
201
|
+
const val MAX_BUTTON_HEIGHT_DP = 56f
|
|
202
|
+
|
|
203
|
+
// The theme a Material 3 toolbar gives its buttons (`toolbarStyle`'s overlay: its ripples), kept in the
|
|
204
|
+
// current light or dark mode.
|
|
205
|
+
fun themed(context: Context): Context {
|
|
206
|
+
val wrapped = MaterialThemeOverlay.wrap(
|
|
207
|
+
context,
|
|
208
|
+
null,
|
|
209
|
+
AppCompatR.attr.toolbarStyle,
|
|
210
|
+
MaterialR.style.Widget_Material3_Toolbar,
|
|
211
|
+
)
|
|
212
|
+
if (wrapped !== context) MaterialTheme.followOverlay(wrapped)
|
|
213
|
+
return wrapped
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// The height of React content laid out at a width without a height limit, for the Kotlin side
|
|
2
|
+
// (`dev.apexnative.navigation.react.ContentHeights`): a sheet with the 'content' detent. Fabric lays out a copy of the
|
|
3
|
+
// tree for it, so the shown layout stays as it is: a React root's surface here, a slot's content in the slot's own
|
|
4
|
+
// layout (`ANNSlotShadowNode`), which keeps the height in `ANNSlotContentHeights`.
|
|
5
|
+
|
|
6
|
+
#include <fbjni/fbjni.h>
|
|
7
|
+
#include <jni.h>
|
|
8
|
+
#include <react/fabric/SurfaceHandlerBinding.h>
|
|
9
|
+
#include <react/renderer/components/ApexNavigationSpec/ANNSlotState.h>
|
|
10
|
+
#include <react/renderer/core/LayoutConstraints.h>
|
|
11
|
+
#include <react/renderer/core/LayoutContext.h>
|
|
12
|
+
|
|
13
|
+
#include <limits>
|
|
14
|
+
|
|
15
|
+
using namespace facebook;
|
|
16
|
+
|
|
17
|
+
namespace {
|
|
18
|
+
|
|
19
|
+
std::string slotIdOf(jstring slotId) {
|
|
20
|
+
return slotId ? jni::wrap_alias(slotId)->toStdString() : std::string{};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// A React root's surface: React Native keeps the getter of its surface handler unmangled for the native side.
|
|
24
|
+
struct JReactSurfaceImpl : jni::JavaClass<JReactSurfaceImpl> {
|
|
25
|
+
static constexpr auto kJavaDescriptor = "Lcom/facebook/react/runtime/ReactSurfaceImpl;";
|
|
26
|
+
|
|
27
|
+
const react::SurfaceHandler &surfaceHandler() const {
|
|
28
|
+
static const auto method =
|
|
29
|
+
javaClassStatic()->getMethod<react::SurfaceHandlerBinding::javaobject()>("getSurfaceHandler");
|
|
30
|
+
return method(self())->cthis()->getSurfaceHandler();
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
} // namespace
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The height of the surface's content at [width] points without a height limit, in points: `SurfaceHandler::measure`,
|
|
38
|
+
* as iOS's surface measures for `sizeThatFits`. 0 for a surface that doesn't run; -1 on a failure.
|
|
39
|
+
*/
|
|
40
|
+
extern "C" JNIEXPORT jfloat JNICALL Java_dev_apexnative_navigation_react_ContentHeights_nativeSurfaceHeight(
|
|
41
|
+
JNIEnv * /*env*/, jclass /*type*/, jobject surface, jfloat width, jfloat pointScale, jfloat fontScale, jboolean rtl,
|
|
42
|
+
jboolean swapLeftAndRight) {
|
|
43
|
+
try {
|
|
44
|
+
auto ref = jni::wrap_alias(static_cast<JReactSurfaceImpl::javaobject>(surface));
|
|
45
|
+
react::LayoutConstraints constraints{};
|
|
46
|
+
constraints.minimumSize = react::Size{.width = width, .height = 0};
|
|
47
|
+
constraints.maximumSize = react::Size{.width = width, .height = std::numeric_limits<react::Float>::infinity()};
|
|
48
|
+
constraints.layoutDirection = rtl ? react::LayoutDirection::RightToLeft : react::LayoutDirection::LeftToRight;
|
|
49
|
+
react::LayoutContext context{};
|
|
50
|
+
context.pointScaleFactor = pointScale;
|
|
51
|
+
context.fontSizeMultiplier = fontScale;
|
|
52
|
+
context.swapLeftAndRightInRTL = swapLeftAndRight;
|
|
53
|
+
return ref->surfaceHandler().measure(constraints, context).height;
|
|
54
|
+
} catch (...) {
|
|
55
|
+
jni::translatePendingCppExceptionToJavaException();
|
|
56
|
+
return -1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The slot's content is measured from its next layout on (`ANNSlotContentHeights::request`). */
|
|
61
|
+
extern "C" JNIEXPORT void JNICALL Java_dev_apexnative_navigation_react_ContentHeights_nativeRequestSlot(
|
|
62
|
+
JNIEnv * /*env*/, jclass /*type*/, jstring slotId) {
|
|
63
|
+
react::ANNSlotContentHeights::request(slotIdOf(slotId));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Whether the slot's content is measured. */
|
|
67
|
+
extern "C" JNIEXPORT jboolean JNICALL Java_dev_apexnative_navigation_react_ContentHeights_nativeIsSlotRequested(
|
|
68
|
+
JNIEnv * /*env*/, jclass /*type*/, jstring slotId) {
|
|
69
|
+
return react::ANNSlotContentHeights::isRequested(slotIdOf(slotId));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** The height of the slot's content at its last layout, in points; negative before it. */
|
|
73
|
+
extern "C" JNIEXPORT jfloat JNICALL Java_dev_apexnative_navigation_react_ContentHeights_nativeSlotHeight(
|
|
74
|
+
JNIEnv * /*env*/, jclass /*type*/, jstring slotId) {
|
|
75
|
+
return react::ANNSlotContentHeights::height(slotIdOf(slotId));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The slot's screen went: nothing more is measured for it. */
|
|
79
|
+
extern "C" JNIEXPORT void JNICALL Java_dev_apexnative_navigation_react_ContentHeights_nativeForgetSlot(
|
|
80
|
+
JNIEnv * /*env*/, jclass /*type*/, jstring slotId) {
|
|
81
|
+
react::ANNSlotContentHeights::forget(slotIdOf(slotId));
|
|
82
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- Material Icons "arrow_right" (Apache 2.0, android/MATERIAL_SYMBOLS_LICENSE.txt): the arrow of a submenu item in a
|
|
3
|
+
top bar menu, mirrored right to left. -->
|
|
4
|
+
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
5
|
+
android:width="24dp"
|
|
6
|
+
android:height="24dp"
|
|
7
|
+
android:autoMirrored="true"
|
|
8
|
+
android:tint="?attr/colorControlNormal"
|
|
9
|
+
android:viewportWidth="24"
|
|
10
|
+
android:viewportHeight="24">
|
|
11
|
+
<path
|
|
12
|
+
android:fillColor="@android:color/white"
|
|
13
|
+
android:pathData="M10,17l5,-5 -5,-5v10z" />
|
|
14
|
+
</vector>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!-- The title over the items of a submenu of a top bar menu (BarMenu), as AppCompat shows a submenu's title. -->
|
|
3
|
+
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
4
|
+
android:layout_width="match_parent"
|
|
5
|
+
android:layout_height="?attr/listPreferredItemHeightSmall"
|
|
6
|
+
android:minWidth="196dp"
|
|
7
|
+
android:paddingStart="16dp"
|
|
8
|
+
android:paddingEnd="16dp">
|
|
9
|
+
|
|
10
|
+
<TextView
|
|
11
|
+
android:id="@+id/apex_navigation_menu_title"
|
|
12
|
+
android:layout_width="match_parent"
|
|
13
|
+
android:layout_height="wrap_content"
|
|
14
|
+
android:layout_gravity="center_vertical"
|
|
15
|
+
android:ellipsize="end"
|
|
16
|
+
android:singleLine="true"
|
|
17
|
+
android:textAlignment="viewStart"
|
|
18
|
+
android:textAppearance="?attr/textAppearancePopupMenuHeader" />
|
|
19
|
+
</FrameLayout>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<!--
|
|
3
|
+
An item of a menu of the top bar (BarMenu), as AppCompat lays out a popup menu's item: the icon, the title, the
|
|
4
|
+
submenu's arrow, the check mark of a checkable item. The inflater of the activity turns the check box and the radio
|
|
5
|
+
button into Material ones, as it does for AppCompat's menus.
|
|
6
|
+
-->
|
|
7
|
+
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
8
|
+
android:layout_width="match_parent"
|
|
9
|
+
android:layout_height="?attr/listPreferredItemHeightSmall"
|
|
10
|
+
android:gravity="center_vertical"
|
|
11
|
+
android:minWidth="196dp"
|
|
12
|
+
android:orientation="horizontal"
|
|
13
|
+
android:paddingEnd="16dp">
|
|
14
|
+
|
|
15
|
+
<ImageView
|
|
16
|
+
android:id="@+id/apex_navigation_menu_icon"
|
|
17
|
+
android:layout_width="wrap_content"
|
|
18
|
+
android:layout_height="wrap_content"
|
|
19
|
+
android:layout_marginStart="8dp"
|
|
20
|
+
android:layout_marginTop="8dp"
|
|
21
|
+
android:layout_marginEnd="-8dp"
|
|
22
|
+
android:layout_marginBottom="8dp"
|
|
23
|
+
android:duplicateParentState="true"
|
|
24
|
+
android:importantForAccessibility="no"
|
|
25
|
+
android:scaleType="centerInside"
|
|
26
|
+
android:visibility="gone" />
|
|
27
|
+
|
|
28
|
+
<TextView
|
|
29
|
+
android:id="@+id/apex_navigation_menu_title"
|
|
30
|
+
android:layout_width="0dp"
|
|
31
|
+
android:layout_height="wrap_content"
|
|
32
|
+
android:layout_marginStart="16dp"
|
|
33
|
+
android:layout_weight="1"
|
|
34
|
+
android:duplicateParentState="true"
|
|
35
|
+
android:ellipsize="end"
|
|
36
|
+
android:singleLine="true"
|
|
37
|
+
android:textAlignment="viewStart"
|
|
38
|
+
android:textAppearance="?attr/textAppearanceLargePopupMenu" />
|
|
39
|
+
|
|
40
|
+
<ImageView
|
|
41
|
+
android:id="@+id/apex_navigation_menu_arrow"
|
|
42
|
+
android:layout_width="wrap_content"
|
|
43
|
+
android:layout_height="wrap_content"
|
|
44
|
+
android:layout_marginStart="8dp"
|
|
45
|
+
android:duplicateParentState="true"
|
|
46
|
+
android:importantForAccessibility="no"
|
|
47
|
+
android:scaleType="center"
|
|
48
|
+
android:src="@drawable/apex_navigation_ic_arrow_right"
|
|
49
|
+
android:visibility="gone" />
|
|
50
|
+
|
|
51
|
+
<CheckBox
|
|
52
|
+
android:id="@+id/apex_navigation_menu_check"
|
|
53
|
+
android:layout_width="wrap_content"
|
|
54
|
+
android:layout_height="wrap_content"
|
|
55
|
+
android:clickable="false"
|
|
56
|
+
android:duplicateParentState="true"
|
|
57
|
+
android:focusable="false"
|
|
58
|
+
android:visibility="gone" />
|
|
59
|
+
|
|
60
|
+
<RadioButton
|
|
61
|
+
android:id="@+id/apex_navigation_menu_radio"
|
|
62
|
+
android:layout_width="wrap_content"
|
|
63
|
+
android:layout_height="wrap_content"
|
|
64
|
+
android:clickable="false"
|
|
65
|
+
android:duplicateParentState="true"
|
|
66
|
+
android:focusable="false"
|
|
67
|
+
android:visibility="gone" />
|
|
68
|
+
</LinearLayout>
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<style name="ThemeOverlay.ApexNavigation.TopBar" parent="ThemeOverlay.Material3.Toolbar.Surface" />
|
|
9
9
|
|
|
10
10
|
<!--
|
|
11
|
-
The popup theme of the bar's
|
|
11
|
+
The popup theme of the bar's "⋮" menu, when the app's Material 3 theme is fixed to light or to
|
|
12
12
|
dark and doesn't follow the device's mode: a day/night overlay that gives the menu Material 3's colors of the
|
|
13
13
|
current mode where the app's theme shows the other one, and leaves the app's colors in the mode it shows
|
|
14
14
|
(values-night has the dark side). An app theme that follows the mode needs none: the menu takes the bar's theme.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
2
2
|
<resources>
|
|
3
|
-
<!-- The popup themes of the bar's
|
|
3
|
+
<!-- The popup themes of the bar's "⋮" in dark mode: see values/styles.xml. -->
|
|
4
4
|
<style name="ThemeOverlay.ApexNavigation.TopBar.PopupOverLight" parent="ThemeOverlay.Material3.Dark" />
|
|
5
5
|
<style name="ThemeOverlay.ApexNavigation.TopBar.PopupOverDark" parent="" />
|
|
6
6
|
</resources>
|
package/cli/doctor/checks/app.js
CHANGED
|
@@ -100,11 +100,12 @@ function appChecks({ root, run }) {
|
|
|
100
100
|
const project = findProject(root, run);
|
|
101
101
|
const plans = planProject(project);
|
|
102
102
|
const app = plans.find(({ file }) => file !== undefined && file === project.app);
|
|
103
|
+
const metro = plans.find(({ file }) => file !== undefined && file === project.metroConfig);
|
|
103
104
|
const manifest = plans.find(({ file }) => file !== undefined && file === project.manifest);
|
|
104
105
|
const native = plans.filter((plan) => plan.platform !== 'js' && plan !== manifest);
|
|
105
106
|
const types = plans.filter(({ name }) => name === ENV_FILE || name === 'tsconfig.json');
|
|
106
107
|
const entry = plans.filter(
|
|
107
|
-
(plan) => plan.platform === 'js' && plan !== app && !types.includes(plan)
|
|
108
|
+
(plan) => plan.platform === 'js' && plan !== app && plan !== metro && !types.includes(plan)
|
|
108
109
|
);
|
|
109
110
|
checks.push(filesCheck('Native files', native), filesCheck('Entry point', entry));
|
|
110
111
|
const typed = typesCheck(root, types);
|
|
@@ -120,6 +121,20 @@ function appChecks({ root, run }) {
|
|
|
120
121
|
});
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
// Without the navigation's Metro setup an edit that isn't a boundary of Fast Refresh reloads the app.
|
|
125
|
+
if (metro && metro.status !== 'done') {
|
|
126
|
+
checks.push({
|
|
127
|
+
group: 'app',
|
|
128
|
+
title: 'Fast Refresh',
|
|
129
|
+
status: 'warn',
|
|
130
|
+
detail: `${metro.name} doesn't use withApexNavigation: some edits reload the app`,
|
|
131
|
+
fix:
|
|
132
|
+
metro.status === 'manual'
|
|
133
|
+
? { label: 'Wrap the config metro.config.js exports in withApexNavigation' }
|
|
134
|
+
: SETUP,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
123
138
|
// Predictive back is optional: without it the navigation works, and Android plays no back animation of its own.
|
|
124
139
|
if (manifest && manifest.status !== 'done') {
|
|
125
140
|
checks.push({
|
package/cli/project.js
CHANGED
|
@@ -22,6 +22,7 @@ const { exec } = require('./exec');
|
|
|
22
22
|
* infoPlist?: string;
|
|
23
23
|
* entry?: string;
|
|
24
24
|
* app?: string;
|
|
25
|
+
* metroConfig?: string;
|
|
25
26
|
* typescript: boolean;
|
|
26
27
|
* xcodeProject?: string;
|
|
27
28
|
* androidPackage?: string;
|
|
@@ -75,6 +76,9 @@ function findProject(root, run = exec) {
|
|
|
75
76
|
app: ['App.tsx', 'App.ts', 'App.jsx', 'App.js']
|
|
76
77
|
.map((name) => path.join(root, name))
|
|
77
78
|
.find((file) => fs.existsSync(file)),
|
|
79
|
+
metroConfig: ['metro.config.js', 'metro.config.cjs']
|
|
80
|
+
.map((name) => path.join(root, name))
|
|
81
|
+
.find((file) => fs.existsSync(file)),
|
|
78
82
|
typescript: ['App.tsx', 'App.ts', 'tsconfig.json'].some((name) =>
|
|
79
83
|
fs.existsSync(path.join(root, name))
|
|
80
84
|
),
|
package/cli/setup/index.js
CHANGED
|
@@ -16,6 +16,7 @@ const { planEntry, navigationFile, HOW_TO: ENTRY_HOW_TO } = require('./steps/ent
|
|
|
16
16
|
const { planEnv, targetOf } = require('./steps/env');
|
|
17
17
|
const { planInfoPlist } = require('./steps/infoPlist');
|
|
18
18
|
const { planMainActivity } = require('./steps/mainActivity');
|
|
19
|
+
const { planMetro } = require('./steps/metro');
|
|
19
20
|
const { planSceneDelegate } = require('./steps/sceneDelegate');
|
|
20
21
|
const { planMainApplication } = require('./steps/mainApplication');
|
|
21
22
|
const { planManifest } = require('./steps/manifest');
|
|
@@ -134,6 +135,10 @@ function planProject(project) {
|
|
|
134
135
|
plans.push(...planEnv(project.root, navigation, ts));
|
|
135
136
|
// The template's App renders React Native's status bar; one moved elsewhere or written anew has nothing to change.
|
|
136
137
|
if (project.app) plans.push(planFile('App.tsx', 'js', project.app, planApp, APP_HOW_TO));
|
|
138
|
+
// Fast Refresh for every edit; an app without a Metro config of its own has nothing to change.
|
|
139
|
+
if (project.metroConfig) {
|
|
140
|
+
plans.push(planFile('metro.config.js', 'js', project.metroConfig, planMetro, ''));
|
|
141
|
+
}
|
|
137
142
|
return plans;
|
|
138
143
|
}
|
|
139
144
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/** @typedef {import('./types').StepResult} StepResult */
|
|
5
|
+
|
|
6
|
+
const REQUIRE = "const { withApexNavigation } = require('@apexnative/navigation/metro');";
|
|
7
|
+
|
|
8
|
+
// The config the file exports: `module.exports = …;`, its last statement.
|
|
9
|
+
const EXPORT = /^module\.exports\s*=\s*([\s\S]+?);?\s*$/m;
|
|
10
|
+
|
|
11
|
+
const HOW_TO = `Wrap the config metro.config.js exports, so an edit updates the app by Fast Refresh instead of
|
|
12
|
+
reloading it:
|
|
13
|
+
|
|
14
|
+
${REQUIRE}
|
|
15
|
+
|
|
16
|
+
module.exports = withApexNavigation(mergeConfig(getDefaultConfig(__dirname), config));`;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* metro.config.js: the config it exports goes through withApexNavigation, which puts the navigation's Babel
|
|
20
|
+
* transformer in front of the app's own.
|
|
21
|
+
* @param {string} text
|
|
22
|
+
* @returns {StepResult}
|
|
23
|
+
*/
|
|
24
|
+
function planMetro(text) {
|
|
25
|
+
if (text.includes('withApexNavigation')) return { status: 'done', note: 'already set up' };
|
|
26
|
+
const found = EXPORT.exec(text);
|
|
27
|
+
// Only an export that ends the file: one in the middle may be changed by what comes after it.
|
|
28
|
+
if (!found || text.slice(found.index + found[0].length).trim() !== '') {
|
|
29
|
+
return { status: 'manual', note: 'changed by hand, see below', howTo: HOW_TO };
|
|
30
|
+
}
|
|
31
|
+
const exported = found[1].trim();
|
|
32
|
+
const before = text.slice(0, found.index);
|
|
33
|
+
const requires = [...before.matchAll(/^.*require\(.*\);?$/gm)];
|
|
34
|
+
const last = requires[requires.length - 1];
|
|
35
|
+
const head =
|
|
36
|
+
last === undefined
|
|
37
|
+
? `${REQUIRE}\n\n${before}`
|
|
38
|
+
: `${before.slice(0, last.index + last[0].length)}\n${REQUIRE}${before.slice(last.index + last[0].length)}`;
|
|
39
|
+
return {
|
|
40
|
+
status: 'change',
|
|
41
|
+
next: `${head}module.exports = withApexNavigation(${exported});\n`,
|
|
42
|
+
note: 'Fast Refresh for every edit',
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
module.exports = { planMetro, HOW_TO };
|
|
@@ -255,6 +255,10 @@ A wrong name or a missing prop is an error of the compiler, not of the app at ru
|
|
|
255
255
|
- **Which are left out:** a screen with a required prop that is a function, and one with a prop whose type has no native form (see [Types](#types)). `codegen` names each in a warning. Native code still opens it by its name, `push("Settings", props: [:])`, with its props as a plain dictionary or map.
|
|
256
256
|
- **After a change** of the screens or their props, run `codegen` again; `doctor` says when the generated code is behind.
|
|
257
257
|
|
|
258
|
+
## Fast Refresh
|
|
259
|
+
|
|
260
|
+
`nativeScreen` returns a component that runs `useProps`. An edit of the hook or of the options applies in place, with the app running. A change of the native name, or adding or removing `useProps`, restarts the app (a reload).
|
|
261
|
+
|
|
258
262
|
## UIKit and Android views
|
|
259
263
|
|
|
260
264
|
A screen of UIKit or Android views gets the same `screen`, and follows its props as they change:
|