@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/ButtonBar.kt
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
package dev.apexnative.navigation.views.stack.topbar.titlebar
|
|
2
2
|
|
|
3
|
-
import android.annotation.SuppressLint
|
|
4
3
|
import android.content.Context
|
|
5
4
|
import android.graphics.Color
|
|
6
5
|
import android.graphics.Paint
|
|
@@ -15,20 +14,12 @@ import android.text.style.MetricAffectingSpan
|
|
|
15
14
|
import android.transition.AutoTransition
|
|
16
15
|
import android.transition.TransitionManager
|
|
17
16
|
import android.util.TypedValue
|
|
17
|
+
import android.view.ContextThemeWrapper
|
|
18
18
|
import android.view.Gravity
|
|
19
|
-
import android.view.Menu
|
|
20
|
-
import android.view.MenuItem
|
|
21
19
|
import android.view.View
|
|
22
|
-
import android.view.ViewGroup
|
|
23
20
|
import androidx.appcompat.R as AppCompatR
|
|
24
21
|
import androidx.appcompat.content.res.AppCompatResources
|
|
25
|
-
import androidx.appcompat.view.menu.MenuView
|
|
26
|
-
import androidx.appcompat.widget.ActionMenuView
|
|
27
|
-
import androidx.appcompat.widget.PopupMenu
|
|
28
|
-
import androidx.appcompat.widget.TooltipCompat
|
|
29
22
|
import androidx.core.graphics.drawable.DrawableCompat
|
|
30
|
-
import androidx.core.view.MenuItemCompat
|
|
31
|
-
import com.google.android.material.appbar.MaterialToolbar
|
|
32
23
|
import dev.apexnative.navigation.R
|
|
33
24
|
import dev.apexnative.navigation.options.ButtonMenuOptions
|
|
34
25
|
import dev.apexnative.navigation.options.ButtonOptions
|
|
@@ -36,19 +27,17 @@ import dev.apexnative.navigation.options.parsers.TypefaceLoader
|
|
|
36
27
|
import dev.apexnative.navigation.utils.MaterialTheme
|
|
37
28
|
import dev.apexnative.navigation.utils.TestIds
|
|
38
29
|
import dev.apexnative.navigation.utils.UiUtils
|
|
39
|
-
import java.util.WeakHashMap
|
|
40
30
|
import kotlin.math.roundToInt
|
|
41
31
|
|
|
42
32
|
/**
|
|
43
|
-
* The buttons of one side of the bar in its
|
|
44
|
-
* a
|
|
45
|
-
* first in "⋮". A press sends the button's id; a button with a `menu` opens
|
|
46
|
-
*
|
|
47
|
-
*
|
|
33
|
+
* The buttons of one side of the bar in its [ButtonRow]: each button a view of its own — a text or an icon (on its
|
|
34
|
+
* background) as a toolbar draws its items ([BarButtonView]), or a React button's cell —, in the bar or in "⋮" as
|
|
35
|
+
* [set] says, with the `overflowMenu` items first in "⋮". A press sends the button's id; a button with a `menu` opens
|
|
36
|
+
* it under the button (in "⋮": at the bar's end), or on a long press with `menuOnLongPress`. A button keeps its view
|
|
37
|
+
* while its id stays, and the view takes its `testID` as it is set. Main thread only.
|
|
48
38
|
*/
|
|
49
39
|
internal class ButtonBar(
|
|
50
|
-
private val
|
|
51
|
-
private val left: Boolean,
|
|
40
|
+
private val row: ButtonRow,
|
|
52
41
|
private val loadIcon: (String) -> Drawable?,
|
|
53
42
|
private val host: Host,
|
|
54
43
|
) {
|
|
@@ -63,97 +52,139 @@ internal class ButtonBar(
|
|
|
63
52
|
/** The menu of the button [buttonId] as the screen's options give it now; none. */
|
|
64
53
|
fun menuOf(buttonId: String): ButtonMenuOptions?
|
|
65
54
|
|
|
66
|
-
/** The
|
|
67
|
-
fun
|
|
55
|
+
/** The colors of the bar's menus as the screen's options give them now (`topBar.menu*Color`). */
|
|
56
|
+
fun menuColors(): BarMenu.Colors
|
|
68
57
|
}
|
|
69
58
|
|
|
70
59
|
/** One button as the bar shows it: its options with the side's colors on them, and its React cell if it has one. */
|
|
71
60
|
class Button(val options: ButtonOptions, val cell: BarButtonComponentCell?)
|
|
72
61
|
|
|
73
|
-
private val context: Context =
|
|
62
|
+
private val context: Context = row.context
|
|
74
63
|
private val typefaces = TypefaceLoader(context)
|
|
75
|
-
|
|
76
|
-
private var
|
|
77
|
-
private var
|
|
78
|
-
private var
|
|
79
|
-
|
|
80
|
-
private
|
|
81
|
-
|
|
82
|
-
init {
|
|
83
|
-
toolbar.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ -> applyViewIds() }
|
|
84
|
-
}
|
|
64
|
+
// The views of the buttons in the bar by their ids, kept from one set to the next.
|
|
65
|
+
private var views = HashMap<String, BarButtonView>()
|
|
66
|
+
private var overflowMenu: ButtonMenuOptions? = null
|
|
67
|
+
private var inOverflow: List<ButtonOptions> = emptyList()
|
|
68
|
+
private var popup: BarMenu? = null
|
|
69
|
+
private var overflowPopup: BarMenu? = null
|
|
85
70
|
|
|
86
71
|
/**
|
|
87
|
-
* The buttons of the side: [
|
|
88
|
-
* the bar),
|
|
89
|
-
* goes with an `AutoTransition` (`animateLeftButtons` / `animateRightButtons`).
|
|
72
|
+
* The buttons of the side: [line] in the bar, in the line's order from its start; [inOverflow] in "⋮" (the right
|
|
73
|
+
* side; the left one keeps all in the bar), in the list's order; [overflow], the items of `overflowMenu` (right
|
|
74
|
+
* side). [animated]: the change goes with an `AutoTransition` (`animateLeftButtons` / `animateRightButtons`).
|
|
90
75
|
*/
|
|
91
|
-
fun set(
|
|
92
|
-
if (animated &&
|
|
93
|
-
val
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
order = it.items.size
|
|
76
|
+
fun set(line: List<Button>, inOverflow: List<Button>, overflow: ButtonMenuOptions?, animated: Boolean) {
|
|
77
|
+
if (animated && row.isLaidOut) TransitionManager.beginDelayedTransition(row, AutoTransition())
|
|
78
|
+
val kept = this.views
|
|
79
|
+
this.views = HashMap()
|
|
80
|
+
val views = line.mapNotNull { button ->
|
|
81
|
+
val id = button.options.id.get(null) ?: return@mapNotNull null
|
|
82
|
+
button.cell?.let { return@mapNotNull cellOf(it, button.options) }
|
|
83
|
+
val view = kept.remove(id)?.takeIf { id !in this.views } ?: BarButtonView(context)
|
|
84
|
+
this.views[id] = view
|
|
85
|
+
bind(view, id, button.options)
|
|
102
86
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
87
|
+
row.setButtons(views)
|
|
88
|
+
overflowMenu = overflow
|
|
89
|
+
this.inOverflow = inOverflow.map { it.options }.filter { it.id.get(null) != null }
|
|
90
|
+
val showsOverflow = overflow != null || this.inOverflow.isNotEmpty()
|
|
91
|
+
if (showsOverflow) {
|
|
92
|
+
val button = row.overflowButton
|
|
93
|
+
button.setOnClickListener { openOverflow(it) }
|
|
94
|
+
TestIds.apply(button, overflow?.testID)
|
|
95
|
+
}
|
|
96
|
+
row.setOverflowShown(showsOverflow)
|
|
97
|
+
// An open "⋮" shows what it holds now.
|
|
98
|
+
overflowPopup?.let { menu ->
|
|
99
|
+
if (showsOverflow) {
|
|
100
|
+
menu.update(overflowEntries(menu.context, menu.colors.text), overflowIcons())
|
|
101
|
+
} else {
|
|
102
|
+
menu.dismiss()
|
|
103
|
+
}
|
|
109
104
|
}
|
|
110
|
-
toolbar.post { applyViewIds() }
|
|
111
105
|
}
|
|
112
106
|
|
|
113
107
|
/** The color of "⋮" (`rightButtonColor`, else `colorOnSurfaceVariant`). */
|
|
114
|
-
fun setOverflowColor(color: Int)
|
|
115
|
-
val icon = toolbar.overflowIcon ?: return
|
|
116
|
-
val tinted = DrawableCompat.wrap(icon.mutate())
|
|
117
|
-
DrawableCompat.setTint(tinted, color)
|
|
118
|
-
toolbar.overflowIcon = tinted
|
|
119
|
-
}
|
|
108
|
+
fun setOverflowColor(color: Int) = row.setOverflowColor(color)
|
|
120
109
|
|
|
121
110
|
/** An open menu of the side closes: the bar shows another screen. */
|
|
122
111
|
fun closeMenus() {
|
|
123
112
|
popup?.dismiss()
|
|
124
113
|
popup = null
|
|
125
|
-
|
|
114
|
+
overflowPopup?.dismiss()
|
|
115
|
+
overflowPopup = null
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// A React button's cell: its label and its `testID` on the cell, the press its content's own.
|
|
119
|
+
private fun cellOf(cell: BarButtonComponentCell, options: ButtonOptions): View {
|
|
120
|
+
cell.contentDescription = options.accessibilityLabel.get(null)
|
|
121
|
+
TestIds.apply(cell, options.testID.get(null))
|
|
122
|
+
return cell
|
|
126
123
|
}
|
|
127
124
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
val item = menu.add(BUTTONS_GROUP, Menu.NONE, order, titleOf(options))
|
|
132
|
-
itemIds[item] = options
|
|
125
|
+
// A text or icon button: its look, its press and long press, its `testID`. The long press shows the tooltip, or
|
|
126
|
+
// opens the menu of a button with `menuOnLongPress`.
|
|
127
|
+
private fun bind(view: BarButtonView, id: String, options: ButtonOptions): View {
|
|
133
128
|
val enabled = !options.enabled.isFalse
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
options.accessibilityLabel.get(null)?.let { MenuItemCompat.setContentDescription(item, it) }
|
|
147
|
-
}
|
|
148
|
-
item.setOnMenuItemClickListener {
|
|
149
|
-
val menuOptions = host.menuOf(id)
|
|
150
|
-
if (cell == null && menuOptions != null && !options.menuOnLongPress.isTrue) {
|
|
151
|
-
openMenu(id, menuOptions, viewOf(item))
|
|
152
|
-
} else if (cell == null) {
|
|
153
|
-
host.pressed(id)
|
|
129
|
+
view.isEnabled = enabled
|
|
130
|
+
view.restyle()
|
|
131
|
+
val longPressMenu = options.menuOnLongPress.isTrue && host.menuOf(id) != null
|
|
132
|
+
view.setOnLongClickListener(null)
|
|
133
|
+
// A listener, even none, makes the view long-clickable: TalkBack would offer a long press that does nothing.
|
|
134
|
+
view.isLongClickable = false
|
|
135
|
+
view.show(titleOf(options), iconOf(options, enabled), options.accessibilityLabel.get(null), !longPressMenu)
|
|
136
|
+
if (longPressMenu) {
|
|
137
|
+
view.setOnLongClickListener { pressed ->
|
|
138
|
+
val menu = host.menuOf(id) ?: return@setOnLongClickListener false
|
|
139
|
+
openMenu(id, menu, pressed)
|
|
140
|
+
true
|
|
154
141
|
}
|
|
155
|
-
true
|
|
156
142
|
}
|
|
143
|
+
view.setOnClickListener { pressed -> press(id, options, pressed) }
|
|
144
|
+
TestIds.apply(view, options.testID.get(null))
|
|
145
|
+
return view
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// A press: the button's menu opens under [anchor] (at the bar's end without one), or the press goes to the screen.
|
|
149
|
+
private fun press(id: String, options: ButtonOptions, anchor: View?) {
|
|
150
|
+
val menu = host.menuOf(id)
|
|
151
|
+
if (menu != null && !options.menuOnLongPress.isTrue) openMenu(id, menu, anchor) else host.pressed(id)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// "⋮" opens as a toolbar opens it: the theme's overflow popup from the end of the button, in the bar's popup theme.
|
|
155
|
+
private fun openOverflow(anchor: View) {
|
|
156
|
+
closeMenus()
|
|
157
|
+
val popupTheme = MaterialTheme.popupTheme(context)
|
|
158
|
+
val themed = if (popupTheme != 0) ContextThemeWrapper(context, popupTheme) else context
|
|
159
|
+
val colors = host.menuColors()
|
|
160
|
+
val menu = BarMenu(themed, anchor, Gravity.END, AppCompatR.attr.actionOverflowMenuStyle, colors)
|
|
161
|
+
menu.onDismiss = { if (overflowPopup === menu) overflowPopup = null }
|
|
162
|
+
overflowPopup = menu
|
|
163
|
+
menu.show(overflowEntries(themed, colors.text), overflowIcons())
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// The icons of "⋮" show when an `overflowMenu` item has one, the moved buttons' icons with them.
|
|
167
|
+
private fun overflowIcons(): Boolean = overflowMenu?.items.orEmpty().any { it.icon != null }
|
|
168
|
+
|
|
169
|
+
// The `overflowMenu` items, then the buttons that don't fit in the bar, in the list's order; a button keeps its own
|
|
170
|
+
// color.
|
|
171
|
+
private fun overflowEntries(themed: Context, textColor: Int?): List<BarMenu.Entry> {
|
|
172
|
+
val items = overflowMenu?.let {
|
|
173
|
+
ButtonMenus.entries(themed, it, textColor, loadIcon) { id -> host.picked(null, id) }
|
|
174
|
+
}.orEmpty()
|
|
175
|
+
val buttons = inOverflow.mapNotNull { options ->
|
|
176
|
+
val id = options.id.get(null) ?: return@mapNotNull null
|
|
177
|
+
val enabled = !options.enabled.isFalse
|
|
178
|
+
BarMenu.Entry(
|
|
179
|
+
titleOf(options),
|
|
180
|
+
iconOf(options, enabled),
|
|
181
|
+
enabled = enabled,
|
|
182
|
+
testId = options.testID.get(null),
|
|
183
|
+
label = options.accessibilityLabel.get(null),
|
|
184
|
+
onPick = { press(id, options, null) },
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
return items + buttons
|
|
157
188
|
}
|
|
158
189
|
|
|
159
190
|
// The text of a button: its font (missing weight or italic made synthetically), its size in sp, its color, its
|
|
@@ -218,93 +249,12 @@ internal class ButtonBar(
|
|
|
218
249
|
if (options.items.isEmpty()) return
|
|
219
250
|
popup?.dismiss()
|
|
220
251
|
val themed = MaterialTheme.context(context)
|
|
221
|
-
val
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
ButtonMenus.fill(themed, menu.menu, Menu.NONE, 0, options, null, loadIcon) { id -> host.picked(buttonId, id) }
|
|
227
|
-
menu.setOnDismissListener { if (popup === menu) popup = null }
|
|
252
|
+
val colors = host.menuColors()
|
|
253
|
+
val gravity = if (anchor != null) Gravity.START else Gravity.END
|
|
254
|
+
val menu = BarMenu(themed, anchor ?: row, gravity, AppCompatR.attr.popupMenuStyle, colors)
|
|
255
|
+
menu.onDismiss = { if (popup === menu) popup = null }
|
|
228
256
|
popup = menu
|
|
229
|
-
menu.show()
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// The view of [item] in the bar; none in "⋮" or before the bar laid it out.
|
|
233
|
-
private fun viewOf(item: MenuItem): View? {
|
|
234
|
-
val menuView = actionMenuView() ?: return null
|
|
235
|
-
for (index in 0 until menuView.childCount) {
|
|
236
|
-
val child = menuView.getChildAt(index)
|
|
237
|
-
if (itemOf(child) === item || item.actionView === child) return child
|
|
238
|
-
}
|
|
239
|
-
return null
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
private fun actionMenuView(): ActionMenuView? {
|
|
243
|
-
for (index in 0 until toolbar.childCount) {
|
|
244
|
-
val child = toolbar.getChildAt(index)
|
|
245
|
-
if (child is ActionMenuView) return child
|
|
246
|
-
}
|
|
247
|
-
return null
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// AppCompat gives an item's view no public way back to its item: the item view's own interface, which only
|
|
251
|
-
// AppCompat's own views implement, is read and never changed.
|
|
252
|
-
@SuppressLint("RestrictedApi")
|
|
253
|
-
private fun itemOf(view: View): MenuItem? = (view as? MenuView.ItemView)?.itemData
|
|
254
|
-
|
|
255
|
-
// The `testID`s of the item views: the bar makes its views again, so after every layout; a view used again without
|
|
256
|
-
// one loses the one it had. "⋮" by AppCompat's description of it. A long press opens the menu of a button with
|
|
257
|
-
// `menuOnLongPress`: the handler finds the item by the view pressed.
|
|
258
|
-
private fun applyViewIds() {
|
|
259
|
-
val menuView = actionMenuView() ?: return
|
|
260
|
-
if (watched !== menuView) {
|
|
261
|
-
watched = menuView
|
|
262
|
-
menuView.setOnHierarchyChangeListener(object : ViewGroup.OnHierarchyChangeListener {
|
|
263
|
-
override fun onChildViewAdded(parent: View?, child: View?) {
|
|
264
|
-
toolbar.post { applyViewIds() }
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
override fun onChildViewRemoved(parent: View?, child: View?) = Unit
|
|
268
|
-
})
|
|
269
|
-
}
|
|
270
|
-
val overflowDescription = context.getString(AppCompatR.string.abc_action_menu_overflow_description)
|
|
271
|
-
for (index in 0 until menuView.childCount) {
|
|
272
|
-
val child = menuView.getChildAt(index)
|
|
273
|
-
val item = itemOf(child) ?: itemIds.keys.firstOrNull { it.actionView === child }
|
|
274
|
-
val options = item?.let(itemIds::get)
|
|
275
|
-
when {
|
|
276
|
-
options != null -> {
|
|
277
|
-
TestIds.apply(child, options.testID.get(null))
|
|
278
|
-
if (child !is BarButtonComponentCell) longPressOf(child, options)
|
|
279
|
-
}
|
|
280
|
-
child.contentDescription == overflowDescription -> TestIds.apply(child, overflowTestId)
|
|
281
|
-
item != null -> TestIds.apply(child, host.testIdOf(item))
|
|
282
|
-
else -> Unit
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// The toolbar uses its item views again for other items: the handler finds the button by the view pressed, at the
|
|
288
|
-
// press, and a view that no longer needs it gets the toolbar's tooltip back.
|
|
289
|
-
private fun longPressOf(view: View, options: ButtonOptions) {
|
|
290
|
-
val id = options.id.get(null)
|
|
291
|
-
if (id != null && options.menuOnLongPress.isTrue && host.menuOf(id) != null) {
|
|
292
|
-
hooked[view] = true
|
|
293
|
-
view.setOnLongClickListener { pressed -> openOnLongPress(pressed) }
|
|
294
|
-
return
|
|
295
|
-
}
|
|
296
|
-
if (hooked.remove(view) == null) return
|
|
297
|
-
view.setOnLongClickListener(null)
|
|
298
|
-
itemOf(view)?.let { TooltipCompat.setTooltipText(view, MenuItemCompat.getTooltipText(it) ?: it.title) }
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
private fun openOnLongPress(pressed: View): Boolean {
|
|
302
|
-
val options = itemOf(pressed)?.let(itemIds::get) ?: return false
|
|
303
|
-
val id = options.id.get(null) ?: return false
|
|
304
|
-
if (!options.menuOnLongPress.isTrue) return false
|
|
305
|
-
val menu = host.menuOf(id) ?: return false
|
|
306
|
-
openMenu(id, menu, pressed)
|
|
307
|
-
return true
|
|
257
|
+
menu.show(ButtonMenus.entries(themed, options, colors.text, loadIcon) { id -> host.picked(buttonId, id) })
|
|
308
258
|
}
|
|
309
259
|
|
|
310
260
|
// A font of the options on a button's text, with the weight and the slant the typeface lacks drawn synthetically.
|
|
@@ -323,10 +273,6 @@ internal class ButtonBar(
|
|
|
323
273
|
}
|
|
324
274
|
|
|
325
275
|
private companion object {
|
|
326
|
-
const val BUTTONS_GROUP = 1
|
|
327
|
-
const val OVERFLOW_GROUP = 2
|
|
328
|
-
const val OVERFLOW_ORDER = 100
|
|
329
|
-
const val BAR_ORDER = 1000
|
|
330
276
|
const val CLOSE = "close"
|
|
331
277
|
const val BOLD_WEIGHT = 600
|
|
332
278
|
const val ITALIC_SKEW = -0.25f
|
package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
package dev.apexnative.navigation.views.stack.topbar.titlebar
|
|
2
2
|
|
|
3
|
-
import android.annotation.SuppressLint
|
|
4
3
|
import android.content.Context
|
|
5
4
|
import android.graphics.Color
|
|
6
5
|
import android.graphics.drawable.Drawable
|
|
7
6
|
import android.text.SpannableString
|
|
8
7
|
import android.text.Spanned
|
|
9
8
|
import android.text.style.ForegroundColorSpan
|
|
10
|
-
import android.view.Menu
|
|
11
|
-
import android.view.MenuItem
|
|
12
|
-
import androidx.appcompat.view.menu.MenuBuilder
|
|
13
9
|
import com.google.android.material.color.MaterialColors
|
|
14
10
|
import dev.apexnative.navigation.options.ButtonMenuOptions
|
|
15
11
|
import dev.apexnative.navigation.utils.MaterialTheme
|
|
@@ -17,78 +13,52 @@ import androidx.appcompat.R as AppCompatR
|
|
|
17
13
|
import com.google.android.material.R as MaterialR
|
|
18
14
|
|
|
19
15
|
/**
|
|
20
|
-
*
|
|
21
|
-
* the destructive ones red, the checked ones with a
|
|
22
|
-
*
|
|
16
|
+
* Turns the items of a bar menu (the overflow menu, the menu of a button) into the entries of a [BarMenu]: their
|
|
17
|
+
* titles and icons, the destructive ones red, the checked ones with a check mark, the items of a `singleSelection` menu
|
|
18
|
+
* with radio buttons. Icons take `textColor` (`topBar.menuTextColor`), else the theme's on-surface-variant color. A
|
|
19
|
+
* pick of an item calls `onPick` with its id; the caller writes a `singleSelection` choice into the screen's options.
|
|
23
20
|
*/
|
|
24
|
-
object ButtonMenus {
|
|
25
|
-
fun
|
|
21
|
+
internal object ButtonMenus {
|
|
22
|
+
fun entries(
|
|
26
23
|
context: Context,
|
|
27
|
-
menu: Menu,
|
|
28
|
-
group: Int,
|
|
29
|
-
firstOrder: Int,
|
|
30
24
|
options: ButtonMenuOptions,
|
|
31
|
-
|
|
25
|
+
textColor: Int?,
|
|
32
26
|
loadIcon: (String) -> Drawable?,
|
|
33
27
|
onPick: (String) -> Unit,
|
|
34
|
-
)
|
|
35
|
-
addElements(context, menu, group, firstOrder, options.items, options.singleSelection, showAsAction, loadIcon, onPick)
|
|
36
|
-
}
|
|
28
|
+
): List<BarMenu.Entry> = entriesOf(context, options.items, options.singleSelection, textColor, loadIcon, onPick)
|
|
37
29
|
|
|
38
|
-
private fun
|
|
30
|
+
private fun entriesOf(
|
|
39
31
|
context: Context,
|
|
40
|
-
menu: Menu,
|
|
41
|
-
group: Int,
|
|
42
|
-
firstOrder: Int,
|
|
43
32
|
elements: List<ButtonMenuOptions.Element>,
|
|
44
33
|
singleSelection: Boolean,
|
|
45
|
-
|
|
34
|
+
textColor: Int?,
|
|
46
35
|
loadIcon: (String) -> Drawable?,
|
|
47
36
|
onPick: (String) -> Unit,
|
|
48
|
-
) {
|
|
49
|
-
val
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (showAsAction != null) item.setShowAsAction(showAsAction)
|
|
73
|
-
}
|
|
74
|
-
if (singleSelection) {
|
|
75
|
-
menu.setGroupCheckable(group, true, true)
|
|
76
|
-
// The group made the submenus checkable too: they open, and have no checkmark.
|
|
77
|
-
submenus.forEach { it.isCheckable = false }
|
|
37
|
+
): List<BarMenu.Entry> = elements.map { element ->
|
|
38
|
+
val destructive = element is ButtonMenuOptions.Item && element.destructive
|
|
39
|
+
val tint = if (destructive) errorColor(context) else textColor ?: iconColor(context)
|
|
40
|
+
val icon = element.icon?.let(loadIcon)?.mutate()?.also { it.setTint(tint) }
|
|
41
|
+
when (element) {
|
|
42
|
+
is ButtonMenuOptions.Submenu -> BarMenu.Entry(
|
|
43
|
+
element.title,
|
|
44
|
+
icon,
|
|
45
|
+
submenu = entriesOf(context, element.items, element.singleSelection, textColor, loadIcon, onPick),
|
|
46
|
+
testId = element.testID,
|
|
47
|
+
)
|
|
48
|
+
is ButtonMenuOptions.Item -> BarMenu.Entry(
|
|
49
|
+
title(context, element),
|
|
50
|
+
icon,
|
|
51
|
+
enabled = !element.disabled,
|
|
52
|
+
check = when {
|
|
53
|
+
singleSelection -> BarMenu.Check.Radio
|
|
54
|
+
element.checked -> BarMenu.Check.Box
|
|
55
|
+
else -> BarMenu.Check.None
|
|
56
|
+
},
|
|
57
|
+
checked = element.checked,
|
|
58
|
+
testId = element.testID,
|
|
59
|
+
onPick = { onPick(element.id) },
|
|
60
|
+
)
|
|
78
61
|
}
|
|
79
|
-
if (elements.any { it.icon != null }) showIcons(menu)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** Some item of the menu, or of a submenu, has an icon. */
|
|
83
|
-
fun hasIcons(options: ButtonMenuOptions): Boolean = hasIcons(options.items)
|
|
84
|
-
|
|
85
|
-
private fun hasIcons(elements: List<ButtonMenuOptions.Element>): Boolean =
|
|
86
|
-
elements.any { it.icon != null || (it is ButtonMenuOptions.Submenu && hasIcons(it.items)) }
|
|
87
|
-
|
|
88
|
-
// Material's menus have their icons at the start of the items; the toolbar's menus leave them out by default.
|
|
89
|
-
@SuppressLint("RestrictedApi")
|
|
90
|
-
private fun showIcons(menu: Menu) {
|
|
91
|
-
(menu as? MenuBuilder)?.setOptionalIconsVisible(true)
|
|
92
62
|
}
|
|
93
63
|
|
|
94
64
|
private fun title(context: Context, item: ButtonMenuOptions.Item): CharSequence {
|