@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.
Files changed (236) hide show
  1. package/ApexNavigation.podspec +2 -1
  2. package/CHANGELOG.md +7 -0
  3. package/android/CLAUDE.md +19 -10
  4. package/android/CORE.md +3 -2
  5. package/android/consumer-rules.pro +6 -0
  6. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
  7. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
  8. package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
  9. package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
  10. package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
  11. package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
  12. package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
  13. package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
  14. package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
  15. package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
  16. package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
  17. package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
  18. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +4 -0
  19. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
  20. package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
  21. package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
  22. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
  23. package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
  24. package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +329 -55
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +26 -5
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +46 -7
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +7 -4
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +36 -38
  39. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
  40. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
  41. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
  42. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
  43. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
  44. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
  45. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
  46. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
  47. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
  48. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
  49. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
  50. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
  51. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
  52. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
  53. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
  54. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
  55. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
  56. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
  57. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
  58. package/android/src/main/jni/ANNContentHeights.cpp +82 -0
  59. package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
  60. package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
  61. package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
  62. package/android/src/main/res/values/styles.xml +1 -1
  63. package/android/src/main/res/values-night/styles.xml +1 -1
  64. package/cli/doctor/checks/app.js +16 -1
  65. package/cli/project.js +4 -0
  66. package/cli/setup/index.js +5 -0
  67. package/cli/setup/steps/metro.js +46 -0
  68. package/docs-md/architecture/native-screens.md +4 -0
  69. package/docs-md/architecture/theme-and-context.md +131 -12
  70. package/docs-md/architecture/transitions.md +43 -4
  71. package/docs-md/flows/bottom-sheets.md +4 -2
  72. package/docs-md/fundamentals/screens-and-props.md +9 -1
  73. package/docs-md/guides/cli.md +3 -1
  74. package/docs-md/help/troubleshooting.md +1 -1
  75. package/docs-md/index.json +29 -1
  76. package/docs-md/screen-ui/back-and-gestures.md +3 -1
  77. package/docs-md/screen-ui/bar-buttons.md +1 -1
  78. package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
  79. package/docs-md/start/installation.md +16 -1
  80. package/ios/Animations/ANNFrameDriver.h +55 -15
  81. package/ios/Animations/ANNFrameDriver.mm +214 -60
  82. package/ios/Animations/ANNTransitionAnimator.h +26 -5
  83. package/ios/Animations/ANNTransitionAnimator.mm +49 -3
  84. package/ios/Animations/ANNTransitionPlan.h +2 -2
  85. package/ios/Animations/ANNTransitionPlan.mm +10 -0
  86. package/ios/Animations/Elements/ANNElementFinder.h +2 -2
  87. package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
  88. package/ios/Animations/Elements/ANNElementTransition.h +3 -3
  89. package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
  90. package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
  91. package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
  92. package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
  93. package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
  94. package/ios/CLAUDE.md +15 -7
  95. package/ios/CORE.md +1 -1
  96. package/ios/Core/ANNAppDelegate.mm +0 -2
  97. package/ios/Core/ANNDeepLinkQueue.h +6 -5
  98. package/ios/Core/ANNDeepLinkQueue.mm +22 -10
  99. package/ios/Core/ANNSceneDelegate.mm +2 -2
  100. package/ios/Core/ApexNavigation.mm +6 -2
  101. package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
  102. package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
  103. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
  104. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
  105. package/ios/Layouts/Stack/ANNStackController.h +8 -0
  106. package/ios/Layouts/Stack/ANNStackController.mm +153 -11
  107. package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
  108. package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
  109. package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
  110. package/ios/Options/ANNAnimationOptions.h +2 -1
  111. package/ios/Options/ANNAnimationOptions.mm +1 -1
  112. package/ios/Options/ANNOptions.h +7 -0
  113. package/ios/Options/ANNOptions.mm +45 -0
  114. package/ios/Options/ANNOptionsReader.mm +2 -4
  115. package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
  116. package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
  117. package/ios/Options/Params/ColorParser.h +4 -3
  118. package/ios/Options/Params/ColorParser.mm +22 -4
  119. package/ios/React/ANNSurfaceHostingView.h +8 -0
  120. package/ios/React/ANNSurfaceHostingView.mm +28 -1
  121. package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
  122. package/ios/TurboModules/ANNTurboModule.mm +46 -0
  123. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
  124. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
  125. package/lib/module/ARCHITECTURE.md +9 -3
  126. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  127. package/lib/module/api/app/AppRegistry.js +14 -2
  128. package/lib/module/api/app/AppRegistry.js.map +1 -1
  129. package/lib/module/api/app/createNavigation.js +188 -66
  130. package/lib/module/api/app/createNavigation.js.map +1 -1
  131. package/lib/module/api/events/navigationEvents.js +6 -2
  132. package/lib/module/api/events/navigationEvents.js.map +1 -1
  133. package/lib/module/api/linking/linking.js +11 -0
  134. package/lib/module/api/linking/linking.js.map +1 -1
  135. package/lib/module/api/screens/defineScreen.js +45 -3
  136. package/lib/module/api/screens/defineScreen.js.map +1 -1
  137. package/lib/module/api/screens/nativeScreens.js +31 -6
  138. package/lib/module/api/screens/nativeScreens.js.map +1 -1
  139. package/lib/module/api/screens/registerScreens.js +172 -35
  140. package/lib/module/api/screens/registerScreens.js.map +1 -1
  141. package/lib/module/api/theme/theme.js +5 -0
  142. package/lib/module/api/theme/theme.js.map +1 -1
  143. package/lib/module/engine/commands.js +21 -1
  144. package/lib/module/engine/commands.js.map +1 -1
  145. package/lib/module/engine/layout.js +7 -2
  146. package/lib/module/engine/layout.js.map +1 -1
  147. package/lib/module/engine/nativeScreens.js +9 -0
  148. package/lib/module/engine/nativeScreens.js.map +1 -1
  149. package/lib/module/engine/options.js +5 -4
  150. package/lib/module/engine/options.js.map +1 -1
  151. package/lib/module/engine/optionsChange.js +102 -0
  152. package/lib/module/engine/optionsChange.js.map +1 -0
  153. package/lib/module/engine/refresh.js +85 -0
  154. package/lib/module/engine/refresh.js.map +1 -0
  155. package/lib/module/engine/rememberedOptions.js +60 -0
  156. package/lib/module/engine/rememberedOptions.js.map +1 -1
  157. package/lib/module/engine/screens.js +26 -0
  158. package/lib/module/engine/screens.js.map +1 -1
  159. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
  160. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  161. package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
  162. package/lib/typescript/api/app/createNavigation.d.ts +5 -0
  163. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  164. package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
  165. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  166. package/lib/typescript/api/index.d.ts +1 -1
  167. package/lib/typescript/api/index.d.ts.map +1 -1
  168. package/lib/typescript/api/linking/linking.d.ts +8 -0
  169. package/lib/typescript/api/linking/linking.d.ts.map +1 -1
  170. package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
  171. package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
  172. package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
  173. package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
  174. package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
  175. package/lib/typescript/api/theme/theme.d.ts +2 -0
  176. package/lib/typescript/api/theme/theme.d.ts.map +1 -1
  177. package/lib/typescript/engine/commands.d.ts +5 -0
  178. package/lib/typescript/engine/commands.d.ts.map +1 -1
  179. package/lib/typescript/engine/layout.d.ts.map +1 -1
  180. package/lib/typescript/engine/nativeScreens.d.ts +5 -0
  181. package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
  182. package/lib/typescript/engine/optionsChange.d.ts +28 -0
  183. package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
  184. package/lib/typescript/engine/refresh.d.ts +35 -0
  185. package/lib/typescript/engine/refresh.d.ts.map +1 -0
  186. package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
  187. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  188. package/lib/typescript/engine/screens.d.ts +7 -0
  189. package/lib/typescript/engine/screens.d.ts.map +1 -1
  190. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  191. package/lib/typescript/interfaces/options/animations.d.ts +40 -15
  192. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  193. package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
  194. package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
  195. package/lib/typescript/interfaces/options/modals.d.ts +6 -7
  196. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  197. package/lib/typescript/interfaces/options/stack.d.ts +8 -3
  198. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  199. package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
  200. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  201. package/metro/acceptPlugin.js +57 -0
  202. package/metro/babelTransformer.js +45 -0
  203. package/metro/index.js +36 -0
  204. package/package.json +3 -1
  205. package/src/ARCHITECTURE.md +9 -3
  206. package/src/adapters/NativeANNTurboModule.ts +7 -0
  207. package/src/api/app/AppRegistry.ts +16 -2
  208. package/src/api/app/createNavigation.ts +198 -66
  209. package/src/api/events/navigationEvents.ts +9 -3
  210. package/src/api/index.ts +1 -0
  211. package/src/api/linking/linking.ts +22 -1
  212. package/src/api/screens/defineScreen.ts +49 -5
  213. package/src/api/screens/nativeScreens.ts +40 -4
  214. package/src/api/screens/registerScreens.tsx +170 -42
  215. package/src/api/theme/theme.ts +5 -0
  216. package/src/engine/commands.ts +21 -0
  217. package/src/engine/layout.ts +11 -2
  218. package/src/engine/nativeScreens.ts +10 -1
  219. package/src/engine/options.ts +6 -8
  220. package/src/engine/optionsChange.ts +107 -0
  221. package/src/engine/refresh.ts +97 -0
  222. package/src/engine/rememberedOptions.ts +70 -0
  223. package/src/engine/screens.tsx +24 -0
  224. package/src/interfaces/ComponentEvents.ts +2 -2
  225. package/src/interfaces/options/animations.ts +40 -15
  226. package/src/interfaces/options/bottomTabs.ts +2 -2
  227. package/src/interfaces/options/buttons.ts +2 -2
  228. package/src/interfaces/options/modals.ts +6 -7
  229. package/src/interfaces/options/stack.ts +8 -3
  230. package/src/interfaces/options/topBar.ts +14 -0
  231. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
  232. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
  233. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
  234. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
  235. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
  236. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
@@ -7,15 +7,14 @@ import android.util.TypedValue
7
7
  import android.view.View
8
8
  import android.view.ViewGroup
9
9
  import androidx.appcompat.widget.AppCompatTextView
10
- import com.google.android.material.appbar.MaterialToolbar
11
- import dev.apexnative.navigation.utils.MaterialTheme
12
10
  import dev.apexnative.navigation.utils.UiUtils
11
+ import dev.apexnative.navigation.views.stack.topbar.titlebar.ButtonRow
13
12
  import kotlin.math.max
14
13
  import kotlin.math.min
15
14
 
16
15
  /**
17
16
  * The row of the bar: the left buttons with the back button at the start ([leftButtonBar]), the right buttons and "⋮"
18
- * at the end ([rightButtonBar]), both Material 3 toolbars, and between them the title and the subtitle, or a React
17
+ * at the end ([rightButtonBar]), each a [ButtonRow], and between them the title and the subtitle, or a React
19
18
  * title ([titleComponent]). It lays them out in its own coordinates, so a shift of the row (`topBar.title.topMargin`)
20
19
  * moves them all; [sideInsets] keep them off the system bars and the cutout at the sides. Right to left, everything
21
20
  * mirrors. Main thread only.
@@ -26,10 +25,10 @@ class TitleBar(context: Context) : ViewGroup(context) {
26
25
  enum class Alignment { Start, Center, Fill }
27
26
 
28
27
  /** The back button (its navigation button) and the left buttons. */
29
- val leftButtonBar = MaterialToolbar(context).also(::plain)
28
+ val leftButtonBar = ButtonRow(context)
30
29
 
31
30
  /** The right buttons, the search's magnifier and "⋮". */
32
- val rightButtonBar = MaterialToolbar(context).also(::plain)
31
+ val rightButtonBar = ButtonRow(context)
33
32
 
34
33
  /** The title's text: one line, cut with "…" at its end (`topBar.title.text`). */
35
34
  val titleView = AppCompatTextView(context).apply {
@@ -118,8 +117,6 @@ class TitleBar(context: Context) : ViewGroup(context) {
118
117
  val height = MeasureSpec.getSize(heightMeasureSpec)
119
118
  val inner = max(width - sideInsetsBox[0] - sideInsetsBox[1], 0)
120
119
  val exactHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
121
- alignButtons(leftButtonBar, height)
122
- alignButtons(rightButtonBar, height)
123
120
  leftButtonBar.measure(MeasureSpec.makeMeasureSpec(inner, MeasureSpec.AT_MOST), exactHeight)
124
121
  val rightRoom = max(inner - leftButtonBar.measuredWidth, 0)
125
122
  rightButtonBar.measure(MeasureSpec.makeMeasureSpec(rightRoom, MeasureSpec.AT_MOST), exactHeight)
@@ -154,7 +151,7 @@ class TitleBar(context: Context) : ViewGroup(context) {
154
151
  view.layout(x, top, x + w, top + view.measuredHeight)
155
152
  }
156
153
  // The back button and the left buttons at the start of the line, the right buttons at its end: right to left
157
- // the whole row mirrors, and each toolbar mirrors what it holds.
154
+ // the whole row mirrors, and each side mirrors what it holds.
158
155
  val startBar = leftButtonBar.measuredWidth
159
156
  val endBar = rightButtonBar.measuredWidth
160
157
  place(leftButtonBar, 0, 0)
@@ -194,22 +191,6 @@ class TitleBar(context: Context) : ViewGroup(context) {
194
191
  return min(max(middle, startBar), max(inner - endBar - width, startBar))
195
192
  }
196
193
 
197
- // A toolbar stands its back button and its menu at the top of its minimum height, centered in it (`buttonGravity`
198
- // is top): with the row's height as that minimum, they stand in the middle of the row, as the title does.
199
- private fun alignButtons(toolbar: MaterialToolbar, height: Int) {
200
- if (toolbar.minimumHeight != height) toolbar.minimumHeight = height
201
- }
202
-
203
- // A toolbar that holds only buttons: no insets of its own around them (the row places them).
204
- private fun plain(toolbar: MaterialToolbar) {
205
- // Before the toolbar makes its menu, which takes the popup theme once: "⋮" in the current mode.
206
- toolbar.popupTheme = MaterialTheme.popupTheme(toolbar.context)
207
- toolbar.setContentInsetsRelative(0, 0)
208
- toolbar.contentInsetStartWithNavigation = 0
209
- toolbar.contentInsetEndWithActions = 0
210
- toolbar.setPadding(0, 0, 0, 0)
211
- }
212
-
213
194
  private companion object {
214
195
  const val GAP_DP = 16
215
196
  const val TITLE_SIZE_SP = 22f
@@ -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 com.google.android.material.appbar.MaterialToolbar
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 = ImageView(this.context)
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 toolbar of the right buttons: its menu holds them, the overflow items and the search's magnifier. */
62
- val rightButtonBar: MaterialToolbar
63
+ /** The right buttons, the search's magnifier and "⋮". */
64
+ val rightButtonBar: ButtonRow
63
65
  get() = titleBar.rightButtonBar
64
66
 
65
- /** The toolbar of the back button and the left buttons. */
66
- val leftButtonBar: MaterialToolbar
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 drawn in
312
- * software: no picture then.
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 = try {
320
- Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also {
321
- val canvas = Canvas(it)
322
- rowArea.draw(canvas)
323
- if (large) {
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
- } catch (e: IllegalArgumentException) {
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.setImageBitmap(picture)
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 drawn in software.
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<Bitmap, Int>? = pictureFrom(content.top + rowArea.bottom)
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 drawn in software.
355
+ * can't be recorded.
358
356
  */
359
- fun picture(): Bitmap? = pictureFrom(0)?.first
357
+ fun picture(): PictureDrawable? = pictureFrom(0)?.first
360
358
 
361
- private fun pictureFrom(top: Int): Pair<Bitmap, Int>? {
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
- return try {
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
- bitmap to top
371
- } catch (e: IllegalArgumentException) {
372
- null
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`): the action view of the button's menu item,
12
- * as an icon's: 12 dp on each side, at least 48 dp wide, as high as the row, the content in its middle. The content (a
13
- * React root, or later a view of the screen's own tree) measures `AT_MOST` — the width at most what the row gives, the
14
- * height at most the row's — or exactly its `component.width` / `component.height` when the options give them
15
- * ([contentWidth], [contentHeight], pixels). The cell takes the menu's id; the content never gets one (React Native
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) {
@@ -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
+ }
@@ -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
+ }