@apexnative/navigation 1.0.0-alpha.5 → 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 +32 -0
- package/README.md +1 -1
- package/android/CLAUDE.md +31 -20
- package/android/CORE.md +12 -8
- package/android/consumer-rules.pro +6 -0
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +36 -5
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
- 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 +7 -3
- 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/react/ReloadCoordinator.kt +0 -2
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +12 -49
- package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -6
- 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/utils/ViewExtensions.kt +0 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +9 -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 +13 -10
- 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 +7 -8
- 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 +443 -58
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +33 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
- 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/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 +50 -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 +10 -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/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/CustomTabRow.kt +72 -10
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +129 -179
- 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 +67 -3
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +346 -36
- 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 -42
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextMorph.kt +176 -0
- 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 +46 -5
- package/docs-md/containers/modals.md +2 -0
- 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 +4 -2
- package/docs-md/fundamentals/screens-and-props.md +9 -1
- package/docs-md/fundamentals/starting-the-app.md +1 -1
- package/docs-md/guides/cli.md +3 -1
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +22 -1
- package/docs-md/index.json +41 -1
- 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 +17 -1
- package/docs-md/screen-ui/bar-buttons.md +5 -5
- 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 +4 -2
- package/docs-md/screen-ui/top-bar.md +8 -4
- package/docs-md/start/installation.md +16 -1
- package/ios/Animations/ANNFrameDriver.h +55 -15
- package/ios/Animations/ANNFrameDriver.mm +216 -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 +249 -45
- 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 +9 -6
- package/ios/Animations/Interpolators/ANNViewTracks.mm +100 -30
- package/ios/CLAUDE.md +32 -22
- package/ios/CORE.md +25 -21
- 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/ANNAppDelegate.mm +0 -2
- package/ios/Core/ANNDeepLinkQueue.h +6 -5
- package/ios/Core/ANNDeepLinkQueue.mm +22 -10
- package/ios/Core/ANNJavaScriptCalls.h +11 -12
- package/ios/Core/ANNJavaScriptCalls.mm +7 -16
- package/ios/Core/ANNReloadCoordinator.mm +4 -8
- package/ios/Core/ANNSceneDelegate.mm +2 -2
- package/ios/Core/ApexNavigation+Internal.h +1 -1
- package/ios/Core/ApexNavigation.mm +6 -2
- 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/ANNRootCoordinator.mm +45 -15
- package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
- package/ios/Layouts/Screen/ANNComponentViewController.mm +13 -23
- 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 +57 -5
- 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/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/ANNAnimationOptions.h +2 -1
- package/ios/Options/ANNAnimationOptions.mm +1 -1
- package/ios/Options/ANNBottomTabsOptions.h +2 -0
- package/ios/Options/ANNBottomTabsOptions.mm +1 -0
- package/ios/Options/ANNOptions.h +25 -0
- package/ios/Options/ANNOptions.mm +157 -1
- package/ios/Options/ANNOptionsReader.mm +108 -0
- package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
- package/ios/Options/ANNStackPopAnimationOptions.mm +11 -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/ColorParser.h +4 -3
- package/ios/Options/Params/ColorParser.mm +22 -4
- 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 +14 -0
- package/ios/React/ANNSurfaceHostingView.mm +144 -1
- package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
- 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 +66 -16
- 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 +17 -7
- 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/hooks/useSafeAreaInsets.js +4 -3
- package/lib/module/api/hooks/useSafeAreaInsets.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/options/OptionsChain.js +2 -2
- 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 +42 -2
- 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 +55 -4
- 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 +8 -3
- 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/hooks/useSafeAreaInsets.d.ts +4 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.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/options/OptionsChain.d.ts +2 -2
- 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 +27 -6
- 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 +44 -14
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +19 -6
- package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +10 -10
- 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/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 +46 -29
- 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 +17 -7
- package/src/adapters/NativeANNTurboModule.ts +7 -0
- package/src/api/app/AppRegistry.ts +16 -2
- package/src/api/app/createNavigation.ts +201 -69
- package/src/api/events/navigationEvents.ts +9 -3
- package/src/api/hooks/useSafeAreaInsets.ts +4 -3
- package/src/api/index.ts +1 -0
- package/src/api/linking/linking.ts +22 -1
- package/src/api/options/OptionsChain.ts +2 -2
- 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 +51 -6
- 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 +53 -8
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/options/animations.ts +44 -14
- package/src/interfaces/options/bottomTabs.ts +19 -6
- package/src/interfaces/options/buttons.ts +10 -10
- package/src/interfaces/options/modals.ts +6 -7
- package/src/interfaces/options/stack.ts +8 -3
- package/src/interfaces/options/system.ts +8 -5
- package/src/interfaces/options/toolbar.ts +2 -2
- package/src/interfaces/options/topBar.ts +46 -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/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
- 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 -136
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -86
- 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 -64
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +0 -177
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -137
- package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
- package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
|
@@ -2,11 +2,12 @@ package dev.apexnative.navigation.views.stack.topbar
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
|
-
import android.graphics.Bitmap
|
|
6
5
|
import android.graphics.Canvas
|
|
7
6
|
import android.graphics.Color
|
|
7
|
+
import android.graphics.Picture
|
|
8
8
|
import android.graphics.Rect
|
|
9
9
|
import android.graphics.drawable.ColorDrawable
|
|
10
|
+
import android.graphics.drawable.PictureDrawable
|
|
10
11
|
import android.os.Build
|
|
11
12
|
import android.view.ContextThemeWrapper
|
|
12
13
|
import android.view.Gravity
|
|
@@ -16,10 +17,11 @@ import android.view.ViewGroup
|
|
|
16
17
|
import android.widget.FrameLayout
|
|
17
18
|
import android.widget.ImageView
|
|
18
19
|
import android.widget.LinearLayout
|
|
19
|
-
import
|
|
20
|
+
import androidx.appcompat.widget.AppCompatImageView
|
|
20
21
|
import dev.apexnative.navigation.R
|
|
21
22
|
import dev.apexnative.navigation.utils.MaterialTheme
|
|
22
23
|
import dev.apexnative.navigation.utils.UiUtils
|
|
24
|
+
import dev.apexnative.navigation.views.stack.topbar.titlebar.ButtonRow
|
|
23
25
|
|
|
24
26
|
/**
|
|
25
27
|
* The top bar of a stack: one per stack, showing what its screen on top sets. Top to bottom: the strip under the status
|
|
@@ -47,7 +49,7 @@ class TopBar(context: Context) : FrameLayout(themed(context)) {
|
|
|
47
49
|
private val largeTitleHolder = FrameLayout(this.context)
|
|
48
50
|
private val searchHolder = FrameLayout(this.context)
|
|
49
51
|
private val border = View(this.context)
|
|
50
|
-
private val snapshot =
|
|
52
|
+
private val snapshot = Snapshot(this.context)
|
|
51
53
|
private var searchArea: View? = null
|
|
52
54
|
private var barHeight = UiUtils.dpToPx(this.context, DEFAULT_HEIGHT_DP)
|
|
53
55
|
private var searchTakesBar = false
|
|
@@ -58,12 +60,12 @@ class TopBar(context: Context) : FrameLayout(themed(context)) {
|
|
|
58
60
|
// `topBar.elevation`, in pixels: the bar's elevation unless it is flat.
|
|
59
61
|
private var shadow = 0f
|
|
60
62
|
|
|
61
|
-
/** The
|
|
62
|
-
val rightButtonBar:
|
|
63
|
+
/** The right buttons, the search's magnifier and "⋮". */
|
|
64
|
+
val rightButtonBar: ButtonRow
|
|
63
65
|
get() = titleBar.rightButtonBar
|
|
64
66
|
|
|
65
|
-
/** The
|
|
66
|
-
val leftButtonBar:
|
|
67
|
+
/** The back button and the left buttons. */
|
|
68
|
+
val leftButtonBar: ButtonRow
|
|
67
69
|
get() = titleBar.leftButtonBar
|
|
68
70
|
|
|
69
71
|
/** The strip under the status bar at the top of the bar, in pixels: the bar is under the status bar. */
|
|
@@ -305,32 +307,27 @@ class TopBar(context: Context) : FrameLayout(themed(context)) {
|
|
|
305
307
|
layoutParams = params
|
|
306
308
|
}
|
|
307
309
|
|
|
308
|
-
// The row's picture in a transition.
|
|
310
|
+
// The row's picture in a transition: the drawing commands of the row as it is, not its pixels. Nothing as large
|
|
311
|
+
// as the bar is allocated or filled in on the main thread, and the renderer plays the commands each frame.
|
|
309
312
|
|
|
310
313
|
/**
|
|
311
|
-
* Lays a picture of the row and the large title over them, laid out at once; false when the bar can't be
|
|
312
|
-
*
|
|
314
|
+
* Lays a picture of the row and the large title over them, laid out at once; false when the bar can't be recorded:
|
|
315
|
+
* no picture then.
|
|
313
316
|
*/
|
|
314
317
|
fun takeRowSnapshot(withLargeTitle: Boolean = true): Boolean {
|
|
315
318
|
val width = rowArea.width
|
|
316
319
|
val large = withLargeTitle && isLargeTitleShown
|
|
317
320
|
val height = rowArea.height + if (large) largeTitleHolder.height else 0
|
|
318
321
|
if (width <= 0 || height <= 0) return false
|
|
319
|
-
val picture =
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
rowArea.
|
|
323
|
-
|
|
324
|
-
canvas.translate(0f, rowArea.height.toFloat())
|
|
325
|
-
largeTitleHolder.draw(canvas)
|
|
326
|
-
}
|
|
322
|
+
val picture = record(width, height) { canvas ->
|
|
323
|
+
rowArea.draw(canvas)
|
|
324
|
+
if (large) {
|
|
325
|
+
canvas.translate(0f, rowArea.height.toFloat())
|
|
326
|
+
largeTitleHolder.draw(canvas)
|
|
327
327
|
}
|
|
328
|
-
}
|
|
329
|
-
// A HARDWARE bitmap in the bar: it can't be drawn on a software canvas.
|
|
330
|
-
return false
|
|
331
|
-
}
|
|
328
|
+
} ?: return false
|
|
332
329
|
val top = content.top + rowArea.top
|
|
333
|
-
snapshot.
|
|
330
|
+
snapshot.setImageDrawable(PictureDrawable(picture))
|
|
334
331
|
snapshot.alpha = 1f
|
|
335
332
|
snapshot.translationX = 0f
|
|
336
333
|
snapshot.translationY = rowArea.translationY
|
|
@@ -348,29 +345,43 @@ class TopBar(context: Context) : FrameLayout(themed(context)) {
|
|
|
348
345
|
|
|
349
346
|
/**
|
|
350
347
|
* A picture of the bar under its row — the background, the large title, the search, the line — and where it starts
|
|
351
|
-
* in the bar, for a push to a screen without a large title; none when the bar can't be
|
|
348
|
+
* in the bar, for a push to a screen without a large title; none when the bar can't be recorded. Its intrinsic
|
|
349
|
+
* size is its size; it draws at the top left of its bounds.
|
|
352
350
|
*/
|
|
353
|
-
fun pictureUnderRow(): Pair<
|
|
351
|
+
fun pictureUnderRow(): Pair<PictureDrawable, Int>? = pictureFrom(content.top + rowArea.bottom)
|
|
354
352
|
|
|
355
353
|
/**
|
|
356
354
|
* A picture of the whole bar, for the screen it leaves with in a transition of the stack's own; none when the bar
|
|
357
|
-
* can't be
|
|
355
|
+
* can't be recorded.
|
|
358
356
|
*/
|
|
359
|
-
fun picture():
|
|
357
|
+
fun picture(): PictureDrawable? = pictureFrom(0)?.first
|
|
360
358
|
|
|
361
|
-
private fun pictureFrom(top: Int): Pair<
|
|
359
|
+
private fun pictureFrom(top: Int): Pair<PictureDrawable, Int>? {
|
|
362
360
|
val width = width
|
|
363
361
|
val height = height - top
|
|
364
362
|
if (width <= 0 || height <= 0) return null
|
|
365
|
-
|
|
366
|
-
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
|
367
|
-
val canvas = Canvas(bitmap)
|
|
363
|
+
val picture = record(width, height) { canvas ->
|
|
368
364
|
canvas.translate(0f, -top.toFloat())
|
|
369
365
|
draw(canvas)
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
366
|
+
} ?: return null
|
|
367
|
+
return PictureDrawable(picture) to top
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// The commands [draw] gives, cut to [width] × [height]. A hardware bitmap in the bar is kept as it is from Android
|
|
371
|
+
// 9 on (the picture then plays only on the screen's hardware canvas, where the bar's pictures are drawn); Android 8
|
|
372
|
+
// can't record one, and there is no picture then.
|
|
373
|
+
private fun record(width: Int, height: Int, draw: (Canvas) -> Unit): Picture? = try {
|
|
374
|
+
Picture().also {
|
|
375
|
+
val canvas = it.beginRecording(width, height)
|
|
376
|
+
try {
|
|
377
|
+
canvas.clipRect(0, 0, width, height)
|
|
378
|
+
draw(canvas)
|
|
379
|
+
} finally {
|
|
380
|
+
it.endRecording()
|
|
381
|
+
}
|
|
373
382
|
}
|
|
383
|
+
} catch (e: IllegalArgumentException) {
|
|
384
|
+
null
|
|
374
385
|
}
|
|
375
386
|
|
|
376
387
|
/** The row's picture goes. */
|
|
@@ -410,6 +421,12 @@ class TopBar(context: Context) : FrameLayout(themed(context)) {
|
|
|
410
421
|
heightChanged = true
|
|
411
422
|
}
|
|
412
423
|
|
|
424
|
+
// The row's picture: it fades as one image. Its commands overlap (a button's background under its icon), and drawn
|
|
425
|
+
// one by one at the view's opacity each would show through the next.
|
|
426
|
+
private class Snapshot(context: Context) : AppCompatImageView(context) {
|
|
427
|
+
override fun hasOverlappingRendering(): Boolean = true
|
|
428
|
+
}
|
|
429
|
+
|
|
413
430
|
// The area of the row: it takes the touches without passing them on while the row is transparent.
|
|
414
431
|
private class RowArea(context: Context) : FrameLayout(context) {
|
|
415
432
|
var blocksTouches = false
|
|
@@ -8,12 +8,12 @@ import dev.apexnative.navigation.utils.UiUtils
|
|
|
8
8
|
import kotlin.math.max
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* The cell of a React button in the bar (`topBar.leftButtons[].component`)
|
|
12
|
-
*
|
|
13
|
-
* React root, or later a view of the screen's own tree) measures `AT_MOST` — the width at most what the row
|
|
14
|
-
* height at most the row's — or exactly its `component.width` / `component.height` when the options give
|
|
15
|
-
* ([contentWidth], [contentHeight], pixels). The cell takes the
|
|
16
|
-
* rejects a root with an id). It never looks inside the content.
|
|
11
|
+
* The cell of a React button in the bar (`topBar.leftButtons[].component`), in the row of its side as an icon button
|
|
12
|
+
* stands there: 12 dp on each side, at least 48 dp wide, as high as the row gives, the content in its middle. The
|
|
13
|
+
* content (a React root, or later a view of the screen's own tree) measures `AT_MOST` — the width at most what the row
|
|
14
|
+
* gives, the height at most the row's — or exactly its `component.width` / `component.height` when the options give
|
|
15
|
+
* them ([contentWidth], [contentHeight], pixels). The cell takes the button's `testID`; the content never gets one
|
|
16
|
+
* (React Native rejects a root with an id). It never looks inside the content.
|
|
17
17
|
*/
|
|
18
18
|
@SuppressLint("ViewConstructor")
|
|
19
19
|
internal class BarButtonComponentCell(context: Context) : ViewGroup(context) {
|
package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
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.TypedArray
|
|
6
|
+
import android.graphics.drawable.Drawable
|
|
7
|
+
import android.view.Gravity
|
|
8
|
+
import android.widget.Button
|
|
9
|
+
import androidx.appcompat.R as AppCompatR
|
|
10
|
+
import androidx.appcompat.widget.AppCompatTextView
|
|
11
|
+
import androidx.appcompat.widget.TooltipCompat
|
|
12
|
+
import androidx.core.widget.TextViewCompat
|
|
13
|
+
import dev.apexnative.navigation.utils.UiUtils
|
|
14
|
+
import kotlin.math.roundToInt
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A button of the bar with an icon or a text, drawn as a toolbar draws the items of its menu: the theme's
|
|
18
|
+
* `actionButtonStyle` (48 dp at least, 12 dp at the sides, the bar's ripple), its `actionMenuTextAppearance` and
|
|
19
|
+
* `actionMenuTextColor`, the icon at most 32 dp and in the middle. With an icon the text doesn't show; it labels the
|
|
20
|
+
* button for accessibility and is its tooltip instead, unless the button has a label of its own. Main thread only.
|
|
21
|
+
*/
|
|
22
|
+
@SuppressLint("ViewConstructor")
|
|
23
|
+
internal class BarButtonView(context: Context) :
|
|
24
|
+
AppCompatTextView(context, null, AppCompatR.attr.actionButtonStyle) {
|
|
25
|
+
private val minimumWidth = minWidth
|
|
26
|
+
private val maxIconSize = UiUtils.dpToPx(context, MAX_ICON_DP).roundToInt()
|
|
27
|
+
private val sidePadding = UiUtils.dpToPx(context, SIDE_DP).roundToInt()
|
|
28
|
+
private var icon: Drawable? = null
|
|
29
|
+
|
|
30
|
+
init {
|
|
31
|
+
gravity = Gravity.CENTER
|
|
32
|
+
isFocusable = true
|
|
33
|
+
val vertical = UiUtils.dpToPx(context, VERTICAL_DP).roundToInt()
|
|
34
|
+
setPaddingRelative(sidePadding, vertical, sidePadding, vertical)
|
|
35
|
+
restyle()
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The text appearance and color of the theme as it is now: a light/dark switch changes them. */
|
|
39
|
+
fun restyle() {
|
|
40
|
+
themed(AppCompatR.attr.actionMenuTextAppearance) { getResourceId(0, 0) }
|
|
41
|
+
.takeIf { it != 0 }
|
|
42
|
+
?.let { TextViewCompat.setTextAppearance(this, it) }
|
|
43
|
+
themed(AppCompatR.attr.actionMenuTextColor) { getColorStateList(0) }?.let(::setTextColor)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private fun <T> themed(attr: Int, read: TypedArray.() -> T): T {
|
|
47
|
+
val values = context.obtainStyledAttributes(intArrayOf(attr))
|
|
48
|
+
return try {
|
|
49
|
+
values.read()
|
|
50
|
+
} finally {
|
|
51
|
+
values.recycle()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* What the button shows: [icon], or [title] without one. [label] is its accessibility label, else the title when
|
|
57
|
+
* only the icon shows; that title is its tooltip too. [tooltip] false leaves the long press to the caller.
|
|
58
|
+
*/
|
|
59
|
+
fun show(title: CharSequence, icon: Drawable?, label: CharSequence?, tooltip: Boolean) {
|
|
60
|
+
this.icon = icon?.also { fit(it) }
|
|
61
|
+
setCompoundDrawablesRelative(icon, null, null, null)
|
|
62
|
+
text = if (icon == null) title else null
|
|
63
|
+
val hidden = title.takeIf { icon != null && it.isNotEmpty() }
|
|
64
|
+
contentDescription = label?.takeIf { it.isNotEmpty() } ?: hidden
|
|
65
|
+
TooltipCompat.setTooltipText(this, if (tooltip) hidden else null)
|
|
66
|
+
requestLayout()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// TalkBack and UI tests see a button, as they see a toolbar's item.
|
|
70
|
+
override fun getAccessibilityClassName(): CharSequence = Button::class.java.name
|
|
71
|
+
|
|
72
|
+
// An icon larger than the bar's item takes keeps its shape at 32 dp.
|
|
73
|
+
private fun fit(icon: Drawable) {
|
|
74
|
+
var width = icon.intrinsicWidth.coerceAtLeast(0)
|
|
75
|
+
var height = icon.intrinsicHeight.coerceAtLeast(0)
|
|
76
|
+
if (width > maxIconSize) {
|
|
77
|
+
height = (height * maxIconSize.toFloat() / width).roundToInt()
|
|
78
|
+
width = maxIconSize
|
|
79
|
+
}
|
|
80
|
+
if (height > maxIconSize) {
|
|
81
|
+
width = (width * maxIconSize.toFloat() / height).roundToInt()
|
|
82
|
+
height = maxIconSize
|
|
83
|
+
}
|
|
84
|
+
icon.setBounds(0, 0, width, height)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
88
|
+
val centered = icon?.takeIf { text.isNullOrEmpty() }
|
|
89
|
+
if (centered == null) setPaddingRelative(sidePadding, paddingTop, sidePadding, paddingBottom)
|
|
90
|
+
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
91
|
+
// At least the theme's width, within what the row gives.
|
|
92
|
+
val mode = MeasureSpec.getMode(widthMeasureSpec)
|
|
93
|
+
val target = if (mode == MeasureSpec.AT_MOST) {
|
|
94
|
+
minOf(MeasureSpec.getSize(widthMeasureSpec), minimumWidth)
|
|
95
|
+
} else {
|
|
96
|
+
minimumWidth
|
|
97
|
+
}
|
|
98
|
+
if (mode != MeasureSpec.EXACTLY && minimumWidth > 0 && measuredWidth < target) {
|
|
99
|
+
super.onMeasure(MeasureSpec.makeMeasureSpec(target, MeasureSpec.EXACTLY), heightMeasureSpec)
|
|
100
|
+
}
|
|
101
|
+
// The icon alone stands in the middle of the button.
|
|
102
|
+
if (centered != null) {
|
|
103
|
+
val start = (measuredWidth - centered.bounds.width()) / 2
|
|
104
|
+
setPaddingRelative(start, paddingTop, paddingEnd, paddingBottom)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private companion object {
|
|
109
|
+
const val MAX_ICON_DP = 32f
|
|
110
|
+
const val SIDE_DP = 12f
|
|
111
|
+
const val VERTICAL_DP = 4f
|
|
112
|
+
}
|
|
113
|
+
}
|
package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
package dev.apexnative.navigation.views.stack.topbar.titlebar
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.res.ColorStateList
|
|
5
|
+
import android.graphics.drawable.ColorDrawable
|
|
6
|
+
import android.graphics.drawable.Drawable
|
|
7
|
+
import android.view.LayoutInflater
|
|
8
|
+
import android.view.View
|
|
9
|
+
import android.view.ViewGroup
|
|
10
|
+
import android.widget.BaseAdapter
|
|
11
|
+
import android.widget.CompoundButton
|
|
12
|
+
import android.widget.FrameLayout
|
|
13
|
+
import android.widget.ImageView
|
|
14
|
+
import android.widget.PopupWindow
|
|
15
|
+
import android.widget.TextView
|
|
16
|
+
import androidx.appcompat.widget.ListPopupWindow
|
|
17
|
+
import androidx.core.graphics.ColorUtils
|
|
18
|
+
import androidx.core.widget.ImageViewCompat
|
|
19
|
+
import dev.apexnative.navigation.R
|
|
20
|
+
import dev.apexnative.navigation.utils.TestIds
|
|
21
|
+
import kotlin.math.max
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A menu of the top bar, the menu of a button or "⋮": a popup list under [anchor] as AppCompat shows a popup menu, with
|
|
25
|
+
* the theme's style of [styleAttr] (`popupMenuStyle`, or `actionOverflowMenuStyle` for "⋮": its background, shadow,
|
|
26
|
+
* offsets and place over the anchor) and [gravity] against the anchor. A submenu takes the menu's place, under its title.
|
|
27
|
+
* Its [Colors] go over the theme's. Main thread only.
|
|
28
|
+
*/
|
|
29
|
+
internal class BarMenu(
|
|
30
|
+
val context: Context,
|
|
31
|
+
private val anchor: View,
|
|
32
|
+
private val gravity: Int,
|
|
33
|
+
private val styleAttr: Int,
|
|
34
|
+
val colors: Colors,
|
|
35
|
+
) {
|
|
36
|
+
/** The menu's colors over the theme's; none keeps the theme's. */
|
|
37
|
+
class Colors(val background: Int?, val text: Int?)
|
|
38
|
+
|
|
39
|
+
/** How an item shows its check: none, a check box, or a radio button of a single selection. */
|
|
40
|
+
enum class Check { None, Box, Radio }
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* An item of the menu: [title], [icon] (tinted already), [enabled], its [check] and whether it is [checked], and
|
|
44
|
+
* either [submenu] (its items, under its title) or [onPick]. [testId] goes on the item's view, [label] is its
|
|
45
|
+
* accessibility label.
|
|
46
|
+
*/
|
|
47
|
+
class Entry(
|
|
48
|
+
val title: CharSequence,
|
|
49
|
+
val icon: Drawable?,
|
|
50
|
+
val enabled: Boolean = true,
|
|
51
|
+
val check: Check = Check.None,
|
|
52
|
+
val checked: Boolean = false,
|
|
53
|
+
val submenu: List<Entry>? = null,
|
|
54
|
+
val testId: String? = null,
|
|
55
|
+
val label: CharSequence? = null,
|
|
56
|
+
val onPick: () -> Unit = {},
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
private var popup: ListPopupWindow? = null
|
|
60
|
+
// The level that shows is the top one, not a submenu.
|
|
61
|
+
private var atTop = false
|
|
62
|
+
// The text color of the options, dimmed as Material 3 dims a disabled item.
|
|
63
|
+
private val itemTextColors = colors.text?.let {
|
|
64
|
+
ColorStateList(
|
|
65
|
+
arrayOf(intArrayOf(-android.R.attr.state_enabled), intArrayOf()),
|
|
66
|
+
intArrayOf(ColorUtils.setAlphaComponent(it, DISABLED_ALPHA), it),
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
// The menu goes from one level to the next: the level that closes doesn't close the menu.
|
|
70
|
+
private var switching = false
|
|
71
|
+
|
|
72
|
+
/** The menu closed: picked, dismissed, or taken away. */
|
|
73
|
+
var onDismiss: (() -> Unit)? = null
|
|
74
|
+
|
|
75
|
+
/** The menu shows. */
|
|
76
|
+
val isShowing: Boolean
|
|
77
|
+
get() = popup?.isShowing == true
|
|
78
|
+
|
|
79
|
+
/** Shows [entries], the top level, with their icons when [icons]. */
|
|
80
|
+
fun show(entries: List<Entry>, icons: Boolean = entries.any { it.icon != null }) =
|
|
81
|
+
showLevel(entries, null, icons)
|
|
82
|
+
|
|
83
|
+
/** The top level, while it shows, holds [entries] now; a submenu that shows stays. */
|
|
84
|
+
fun update(entries: List<Entry>, icons: Boolean = entries.any { it.icon != null }) {
|
|
85
|
+
val popup = popup?.takeIf { atTop && it.isShowing } ?: return
|
|
86
|
+
val adapter = Adapter(entries, icons)
|
|
87
|
+
popup.setAdapter(adapter)
|
|
88
|
+
popup.setContentWidth(contentWidth(adapter))
|
|
89
|
+
popup.show()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Closes the menu. */
|
|
93
|
+
fun dismiss() {
|
|
94
|
+
popup?.dismiss()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private fun showLevel(entries: List<Entry>, title: CharSequence?, icons: Boolean) {
|
|
98
|
+
val adapter = Adapter(entries, icons)
|
|
99
|
+
val popup = ListPopupWindow(context, null, styleAttr)
|
|
100
|
+
popup.anchorView = anchor
|
|
101
|
+
popup.setDropDownGravity(gravity)
|
|
102
|
+
popup.isModal = true
|
|
103
|
+
// As AppCompat's menus: the menu goes over an open keyboard, which stays.
|
|
104
|
+
popup.inputMethodMode = PopupWindow.INPUT_METHOD_NOT_NEEDED
|
|
105
|
+
popup.setAdapter(adapter)
|
|
106
|
+
popup.setContentWidth(contentWidth(adapter))
|
|
107
|
+
colors.background?.let { color ->
|
|
108
|
+
val background = popup.background?.mutate()
|
|
109
|
+
if (background != null) {
|
|
110
|
+
background.setTint(color)
|
|
111
|
+
popup.setBackgroundDrawable(background)
|
|
112
|
+
} else {
|
|
113
|
+
popup.setBackgroundDrawable(ColorDrawable(color))
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
popup.setOnItemClickListener { parent, _, position, _ ->
|
|
117
|
+
val entry = parent.adapter.getItem(position) as? Entry ?: return@setOnItemClickListener
|
|
118
|
+
val submenu = entry.submenu
|
|
119
|
+
if (submenu != null) {
|
|
120
|
+
// The submenu shows where the menu was, as AppCompat shows it.
|
|
121
|
+
switching = true
|
|
122
|
+
popup.dismiss()
|
|
123
|
+
switching = false
|
|
124
|
+
showLevel(submenu, entry.title, submenu.any { it.icon != null })
|
|
125
|
+
} else {
|
|
126
|
+
popup.dismiss()
|
|
127
|
+
entry.onPick()
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
popup.setOnDismissListener {
|
|
131
|
+
if (this.popup === popup) this.popup = null
|
|
132
|
+
if (!switching) onDismiss?.invoke()
|
|
133
|
+
}
|
|
134
|
+
this.popup = popup
|
|
135
|
+
atTop = title == null
|
|
136
|
+
popup.show()
|
|
137
|
+
if (title != null) {
|
|
138
|
+
// The list exists once the popup shows; a header added then shows at once.
|
|
139
|
+
popup.listView?.let { list ->
|
|
140
|
+
list.addHeaderView(header(title, list), null, false)
|
|
141
|
+
popup.show()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// As wide as the widest item, at most half the screen or 320 dp (AppCompat's rule for its menus).
|
|
147
|
+
private fun contentWidth(adapter: Adapter): Int {
|
|
148
|
+
val parent = FrameLayout(context)
|
|
149
|
+
val spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)
|
|
150
|
+
val metrics = context.resources.displayMetrics
|
|
151
|
+
val most = max(metrics.widthPixels / 2, (MAX_WIDTH_DP * metrics.density).toInt())
|
|
152
|
+
var width = 0
|
|
153
|
+
for (index in 0 until adapter.count) {
|
|
154
|
+
val view = adapter.getView(index, null, parent)
|
|
155
|
+
view.measure(spec, spec)
|
|
156
|
+
width = max(width, view.measuredWidth)
|
|
157
|
+
if (width >= most) return most
|
|
158
|
+
}
|
|
159
|
+
return width
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private fun header(title: CharSequence, parent: ViewGroup): View {
|
|
163
|
+
val view = LayoutInflater.from(context).inflate(R.layout.apex_navigation_menu_header, parent, false)
|
|
164
|
+
view.findViewById<TextView>(R.id.apex_navigation_menu_title).apply {
|
|
165
|
+
text = title
|
|
166
|
+
itemTextColors?.let(::setTextColor)
|
|
167
|
+
}
|
|
168
|
+
return view
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// The icons show when [icons], as in AppCompat's menus with optional icons shown.
|
|
172
|
+
private inner class Adapter(private val entries: List<Entry>, private val icons: Boolean) : BaseAdapter() {
|
|
173
|
+
override fun getCount(): Int = entries.size
|
|
174
|
+
|
|
175
|
+
override fun getItem(position: Int): Entry = entries[position]
|
|
176
|
+
|
|
177
|
+
override fun getItemId(position: Int): Long = position.toLong()
|
|
178
|
+
|
|
179
|
+
override fun areAllItemsEnabled(): Boolean = false
|
|
180
|
+
|
|
181
|
+
override fun isEnabled(position: Int): Boolean = entries[position].enabled
|
|
182
|
+
|
|
183
|
+
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
|
184
|
+
val view = convertView
|
|
185
|
+
?: LayoutInflater.from(context).inflate(R.layout.apex_navigation_menu_item, parent, false)
|
|
186
|
+
val entry = entries[position]
|
|
187
|
+
view.isEnabled = entry.enabled
|
|
188
|
+
view.findViewById<TextView>(R.id.apex_navigation_menu_title).apply {
|
|
189
|
+
text = entry.title
|
|
190
|
+
itemTextColors?.let(::setTextColor)
|
|
191
|
+
}
|
|
192
|
+
view.findViewById<ImageView>(R.id.apex_navigation_menu_icon).apply {
|
|
193
|
+
setImageDrawable(entry.icon)
|
|
194
|
+
visibility = if (icons && entry.icon != null) View.VISIBLE else View.GONE
|
|
195
|
+
}
|
|
196
|
+
view.findViewById<ImageView>(R.id.apex_navigation_menu_arrow).apply {
|
|
197
|
+
visibility = if (entry.submenu != null) View.VISIBLE else View.GONE
|
|
198
|
+
ImageViewCompat.setImageTintList(this, colors.text?.let(ColorStateList::valueOf))
|
|
199
|
+
}
|
|
200
|
+
check(view.findViewById(R.id.apex_navigation_menu_check), entry, Check.Box)
|
|
201
|
+
check(view.findViewById(R.id.apex_navigation_menu_radio), entry, Check.Radio)
|
|
202
|
+
view.contentDescription = entry.label
|
|
203
|
+
TestIds.apply(view, entry.testId)
|
|
204
|
+
return view
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private fun check(button: CompoundButton, entry: Entry, kind: Check) {
|
|
208
|
+
val shown = entry.check == kind
|
|
209
|
+
button.visibility = if (shown) View.VISIBLE else View.GONE
|
|
210
|
+
if (shown) button.isChecked = entry.checked
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private companion object {
|
|
215
|
+
// AppCompat's `abc_config_prefDialogWidth`.
|
|
216
|
+
const val MAX_WIDTH_DP = 320
|
|
217
|
+
const val DISABLED_ALPHA = 97
|
|
218
|
+
}
|
|
219
|
+
}
|