@apexnative/navigation 1.0.0-alpha.6 → 1.0.0-alpha.8
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 +15 -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 +86 -39
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarVisibility.kt +9 -2
- 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 +19 -8
- 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/ANNUtils.h +7 -0
- package/ios/Core/ANNUtils.mm +10 -0
- package/ios/Core/ApexNavigation.mm +6 -2
- package/ios/Layouts/Overlay/ANNOverlayCoordinator.mm +1 -7
- 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/Layouts/Tabs/ANNTabsPreloadWindow.mm +2 -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/ANNSlotHost.mm +1 -3
- 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 +193 -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 +185 -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/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
|
+
}
|
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
|