@momo-kits/native-kits 0.167.1-duo.3-debug → 0.167.1-duo.4

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 (189) hide show
  1. package/CLAUDE.md +17 -8
  2. package/ios/Application/ApplicationEnvironment.swift +6 -0
  3. package/ios/Application/Components.swift +1 -3
  4. package/ios/Application/HeaderRight.swift +19 -27
  5. package/ios/Application/Navigation/BottomTab/BottomTab.swift +9 -30
  6. package/ios/Application/Navigation/BottomTab/BottomTabBar.swift +2 -5
  7. package/ios/Application/Navigation/HeaderUser.swift +1 -7
  8. package/ios/Application/Navigation/NavigationContainer.swift +45 -45
  9. package/ios/Application/Navigation/Navigator.swift +122 -29
  10. package/ios/Application/Navigation/NavigatorManager.swift +13 -6
  11. package/ios/Application/Navigation/Overplay/BottomSheet.swift +4 -41
  12. package/ios/Application/Navigation/Overplay/ModalScreen.swift +1 -5
  13. package/ios/Application/Navigation/component/Header.swift +4 -12
  14. package/ios/Application/Navigation/component/HeaderBackground.swift +2 -3
  15. package/ios/Application/Navigation/component/NavigationFooter.swift +1 -1
  16. package/ios/Application/Navigation/component/NavigationHeaderRight.swift +9 -61
  17. package/ios/Application/StackScreen.swift +18 -135
  18. package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +59 -5
  19. package/ios/Colors+Radius+Spacing/Colors.swift +1 -1
  20. package/ios/Extensions/Color++.swift +20 -9
  21. package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
  22. package/ios/Icon/Icon.swift +14 -22
  23. package/ios/Icon/Icons.swift +1329 -0
  24. package/ios/Image/Image.swift +16 -5
  25. package/ios/Lottie/LottieView.swift +4 -2
  26. package/ios/Popup/PopupDisplay.swift +68 -84
  27. package/ios/Popup/PopupNotify.swift +32 -43
  28. package/ios/Popup/PopupPromotion.swift +6 -18
  29. package/ios/Resources/dot_loading.json +558 -0
  30. package/ios/Resources/lottie_circle_loader.json +1 -0
  31. package/ios/Template/TrustBanner/TrustBanner.swift +69 -31
  32. package/ios/native-kits.podspec +1 -1
  33. package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
  34. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +0 -1
  35. package/package.json +1 -1
  36. package/scripts/gen-ios-icons.mjs +36 -0
  37. package/.claude/scheduled_tasks.lock +0 -1
  38. package/.claude/settings.local.json +0 -185
  39. package/build.gradle.kts +0 -11
  40. package/compose/build.gradle.kts +0 -204
  41. package/compose/build.gradle.kts.backup +0 -204
  42. package/compose/compose.podspec +0 -47
  43. package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +0 -19
  44. package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -6
  45. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/BackGesture.android.kt +0 -12
  46. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/OsFontScale.android.kt +0 -7
  47. package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +0 -153
  48. package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
  49. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -124
  50. package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
  51. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
  52. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
  53. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
  54. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
  55. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
  56. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
  57. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
  58. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +0 -831
  59. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Localize.kt +0 -269
  60. package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
  61. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
  62. package/compose/src/commonMain/kotlin/vn/momo/kits/application/WidgetContainer.kt +0 -56
  63. package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
  64. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +0 -157
  65. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +0 -82
  66. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeDot.kt +0 -32
  67. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BadgeRibbon.kt +0 -338
  68. package/compose/src/commonMain/kotlin/vn/momo/kits/components/BaselineView.kt +0 -175
  69. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +0 -356
  70. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +0 -113
  71. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CheckBox.kt +0 -89
  72. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +0 -128
  73. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +0 -209
  74. package/compose/src/commonMain/kotlin/vn/momo/kits/components/CupertinoOverscroll.kt +0 -542
  75. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Divider.kt +0 -49
  76. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +0 -76
  77. package/compose/src/commonMain/kotlin/vn/momo/kits/components/IconButton.kt +0 -125
  78. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.kt +0 -231
  79. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +0 -189
  80. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +0 -475
  81. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +0 -114
  82. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +0 -266
  83. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +0 -240
  84. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +0 -223
  85. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +0 -264
  86. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +0 -185
  87. package/compose/src/commonMain/kotlin/vn/momo/kits/components/LazyColumnWithBouncing.kt +0 -343
  88. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +0 -108
  89. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationDot.kt +0 -56
  90. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationNumber.kt +0 -41
  91. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationScroll.kt +0 -84
  92. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PaginationWhiteDot.kt +0 -40
  93. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +0 -361
  94. package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +0 -108
  95. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +0 -310
  96. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +0 -59
  97. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +0 -87
  98. package/compose/src/commonMain/kotlin/vn/momo/kits/components/ScaleSizeScope.kt +0 -21
  99. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Skeleton.kt +0 -90
  100. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +0 -323
  101. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +0 -220
  102. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +0 -473
  103. package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +0 -122
  104. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +0 -199
  105. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +0 -91
  106. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +0 -552
  107. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +0 -88
  108. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +0 -125
  109. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Title.kt +0 -207
  110. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +0 -498
  111. package/compose/src/commonMain/kotlin/vn/momo/kits/components/TrustBanner.kt +0 -176
  112. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +0 -182
  113. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePicker.kt +0 -193
  114. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerTypes.kt +0 -29
  115. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +0 -234
  116. package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +0 -188
  117. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Colors.kt +0 -325
  118. package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +0 -16
  119. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
  120. package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
  121. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Radius.kt +0 -12
  122. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +0 -16
  123. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -188
  124. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +0 -297
  125. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Card.kt +0 -54
  126. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +0 -50
  127. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +0 -187
  128. package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Section.kt +0 -41
  129. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +0 -52
  130. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Clickable.kt +0 -66
  131. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Conditional.kt +0 -11
  132. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/DeprecatedModifier.kt +0 -104
  133. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Shadow.kt +0 -48
  134. package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/Size.kt +0 -51
  135. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +0 -338
  136. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/HostBackGestureBridge.kt +0 -58
  137. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +0 -137
  138. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +0 -106
  139. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +0 -205
  140. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +0 -393
  141. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -8
  142. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +0 -648
  143. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackTransition.kt +0 -148
  144. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/SwipeBackHaptic.kt +0 -35
  145. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +0 -194
  146. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +0 -232
  147. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/CurvedContainer.kt +0 -86
  148. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +0 -281
  149. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/FloatingButton.kt +0 -169
  150. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +0 -408
  151. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +0 -108
  152. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +0 -105
  153. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +0 -74
  154. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +0 -354
  155. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +0 -37
  156. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +0 -363
  157. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +0 -169
  158. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +0 -132
  159. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/tracking/ScreenTracker.kt +0 -127
  160. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/BackGesture.kt +0 -12
  161. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ComposeLottieAnimation.kt +0 -91
  162. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/OsFontScale.kt +0 -9
  163. package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +0 -74
  164. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Icons.kt +0 -1329
  165. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Resources.kt +0 -55
  166. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Tracking.kt +0 -15
  167. package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +0 -118
  168. package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +0 -40
  169. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/HostBackGesture.ios.kt +0 -45
  170. package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +0 -32
  171. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/BackGesture.ios.kt +0 -38
  172. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/OsFontScale.ios.kt +0 -66
  173. package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +0 -217
  174. package/gradle/libs.versions.toml +0 -69
  175. package/gradle/wrapper/gradle-wrapper.jar +0 -0
  176. package/gradle/wrapper/gradle-wrapper.properties +0 -8
  177. package/gradle.properties +0 -26
  178. package/gradlew +0 -252
  179. package/gradlew.bat +0 -94
  180. package/ios/Application/Layout/ScreenLayout.swift +0 -196
  181. package/ios/Application/Navigation/BottomTab/DuoTabBar.swift +0 -275
  182. package/ios/Application/Navigation/component/DuoScrollGradient.swift +0 -55
  183. package/ios/Application/Navigation/component/SideBar.swift +0 -155
  184. package/ios/Popup/PopupLayout.swift +0 -45
  185. package/ios-demo/README.md +0 -120
  186. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +0 -76
  187. package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +0 -170
  188. package/local.properties +0 -8
  189. package/settings.gradle.kts +0 -64
@@ -1,648 +0,0 @@
1
- package vn.momo.kits.navigation
2
-
3
- import androidx.compose.animation.animateColorAsState
4
- import androidx.compose.foundation.ScrollState
5
- import androidx.compose.foundation.background
6
- import androidx.compose.foundation.border
7
- import androidx.compose.foundation.gestures.ScrollableState
8
- import androidx.compose.foundation.gestures.animateScrollBy
9
- import androidx.compose.foundation.layout.Box
10
- import androidx.compose.foundation.layout.Column
11
- import androidx.compose.foundation.layout.ColumnScope
12
- import androidx.compose.foundation.layout.Spacer
13
- import androidx.compose.foundation.layout.WindowInsets
14
- import androidx.compose.foundation.layout.asPaddingValues
15
- import androidx.compose.foundation.layout.aspectRatio
16
- import androidx.compose.foundation.layout.fillMaxSize
17
- import androidx.compose.foundation.layout.fillMaxWidth
18
- import androidx.compose.foundation.layout.height
19
- import androidx.compose.foundation.layout.ime
20
- import androidx.compose.foundation.layout.imePadding
21
- import androidx.compose.foundation.layout.offset
22
- import androidx.compose.foundation.layout.padding
23
- import androidx.compose.foundation.lazy.LazyListState
24
- import androidx.compose.foundation.rememberScrollState
25
- import androidx.compose.foundation.verticalScroll
26
- import androidx.compose.material.ExperimentalMaterialApi
27
- import androidx.compose.runtime.Composable
28
- import androidx.compose.runtime.CompositionLocalProvider
29
- import androidx.compose.runtime.LaunchedEffect
30
- import androidx.compose.runtime.State
31
- import androidx.compose.runtime.getValue
32
- import androidx.compose.runtime.mutableIntStateOf
33
- import androidx.compose.runtime.mutableStateOf
34
- import androidx.compose.runtime.remember
35
- import androidx.compose.runtime.rememberUpdatedState
36
- import androidx.compose.runtime.snapshotFlow
37
- import androidx.compose.runtime.staticCompositionLocalOf
38
- import androidx.compose.ui.Alignment
39
- import androidx.compose.ui.Modifier
40
- import androidx.compose.ui.graphics.Brush
41
- import androidx.compose.ui.graphics.Color
42
- import androidx.compose.ui.graphics.lerp
43
- import androidx.compose.ui.layout.onGloballyPositioned
44
- import androidx.compose.ui.platform.LocalDensity
45
- import androidx.compose.ui.unit.Dp
46
- import androidx.compose.ui.unit.dp
47
- import androidx.compose.ui.unit.lerp
48
- import androidx.compose.ui.zIndex
49
- import kotlinx.coroutines.flow.Flow
50
- import kotlinx.coroutines.flow.filter
51
- import kotlinx.coroutines.launch
52
- import vn.momo.kits.components.InputSearch
53
- import vn.momo.kits.const.AppNavigationBar
54
- import vn.momo.kits.const.AppTheme
55
- import vn.momo.kits.application.IsShowBaseLineDebug
56
- import vn.momo.kits.const.Colors
57
- import vn.momo.kits.const.Spacing
58
- import vn.momo.kits.modifier.conditional
59
- import vn.momo.kits.modifier.hideKeyboardOnTap
60
- import vn.momo.kits.navigation.bottomtab.BOTTOM_TAB_BAR_HEIGHT
61
- import vn.momo.kits.navigation.component.FABPosition
62
- import vn.momo.kits.navigation.component.FloatingButton
63
- import vn.momo.kits.navigation.component.HEADER_HEIGHT
64
- import vn.momo.kits.navigation.component.Header
65
- import vn.momo.kits.navigation.component.HeaderBackground
66
- import vn.momo.kits.navigation.component.HeaderRight
67
- import vn.momo.kits.navigation.component.HeaderSideBar
68
- import vn.momo.kits.navigation.bottomtab.LocalDuoRailHeight
69
- import vn.momo.kits.navigation.bottomtab.duoFloatingTabBarInset
70
- import vn.momo.kits.navigation.component.HeaderType
71
- import vn.momo.kits.navigation.component.InputSearchType
72
- import vn.momo.kits.platform.BackHandler
73
- import vn.momo.kits.platform.supportsImePadding
74
- import vn.momo.kits.navigation.tracking.ScreenTracker
75
- import vn.momo.kits.navigation.tracking.ScreenTrackingState
76
- import kotlinx.coroutines.delay
77
- import vn.momo.kits.application.LocalContext
78
- import vn.momo.kits.utils.horizontalSafeAreaPadding
79
- import vn.momo.kits.layout.LocalScreenLayout
80
- import vn.momo.kits.layout.ScreenLayout
81
- import vn.momo.kits.layout.consumeHorizontalInsets
82
- import vn.momo.kits.layout.getScreenLayout
83
- import kotlin.time.Clock
84
- import kotlin.time.Duration.Companion.milliseconds
85
- import kotlin.time.ExperimentalTime
86
-
87
- internal val LocalFooterHeightPx = staticCompositionLocalOf { mutableIntStateOf(0) }
88
- internal val LocalHeaderRightWidthPx = staticCompositionLocalOf { mutableIntStateOf(0) }
89
-
90
- // Space a tab's content leaves for the tab bar and home indicator. On iPhone Duo the floating bar
91
- // overlays the content and the rail lives in the side strip, so only their own inset applies.
92
- @Composable
93
- private fun bottomTabChildInset(): Dp {
94
- val keyboard = keyboardSizeState().value
95
- val navigationBar = AppNavigationBar.current
96
- val layout = getScreenLayout()
97
- return when {
98
- layout.isIPhoneDuo && layout.sideBar == null -> (duoFloatingTabBarInset(layout) - keyboard).coerceAtLeast(0.dp)
99
- layout.isIPhoneDuo -> (navigationBar - keyboard).coerceAtLeast(0.dp)
100
- else -> (BOTTOM_TAB_BAR_HEIGHT.dp + Spacing.S - keyboard).coerceAtLeast(0.dp) +
101
- (navigationBar - keyboard).coerceAtLeast(0.dp)
102
- }
103
- }
104
-
105
- @OptIn(ExperimentalMaterialApi::class, ExperimentalTime::class)
106
- @Composable
107
- internal fun StackScreen(
108
- content: @Composable () -> Unit,
109
- navigationOptions: NavigationOptions? = null,
110
- id: Int = -1,
111
- name: String = "",
112
- bottomTabIndex: Int = -1,
113
- onBackHandler: (() -> Unit)? = null,
114
- ) {
115
- val navigator = LocalNavigator.current
116
- val maxApi = LocalMaxApi.current
117
- val context = LocalContext.current
118
- val screenLayout = getScreenLayout()
119
- val density = LocalDensity.current
120
- val screenRegistry = LocalDynamicScreenRegistry.current
121
- val navigation = remember { Navigation(id = id, bottomTabIndex = bottomTabIndex, initOptions = navigationOptions, registry = screenRegistry) }
122
-
123
- val options by navigation.currentOptions
124
- val isBottomTabChild = bottomTabIndex != -1
125
- val isBottomTabRoot = navigation.isBottomTabRoot
126
-
127
- // Auto tracking state
128
- val trackingState = remember {
129
- ScreenTrackingState().apply {
130
- time = Clock.System.now().toEpochMilliseconds()
131
- }
132
- }
133
-
134
- // Determine action: push or back
135
- val action = remember {
136
- if (ScreenTracker.getLastScreenName() != null) "push" else "back"
137
- }
138
-
139
- // Auto tracking effects
140
- LaunchedEffect(name) {
141
- // Track screen navigated immediately
142
- ScreenTracker.trackScreenNavigated(
143
- maxApi = maxApi,
144
- context = context,
145
- screenName = name,
146
- action = action,
147
- state = trackingState
148
- )
149
-
150
- // Track screen displayed after 2 seconds (debounce)
151
- delay(2000.milliseconds)
152
- val loadTime = Clock.System.now().toEpochMilliseconds() - trackingState.time
153
- ScreenTracker.trackScreenDisplayed(
154
- maxApi = maxApi,
155
- context = context,
156
- screenName = name,
157
- duration = loadTime,
158
- state = trackingState
159
- )
160
-
161
- // Track screen interacted after displayed
162
- val interactionTime = Clock.System.now().toEpochMilliseconds() - trackingState.time
163
- ScreenTracker.trackScreenInteracted(
164
- maxApi = maxApi,
165
- context = context,
166
- screenName = name,
167
- duration = interactionTime - loadTime,
168
- totalDuration = interactionTime,
169
- state = trackingState
170
- )
171
- }
172
-
173
- val limit = with(density) {
174
- screenLayout.headerHeight.toPx()
175
- }.toInt()
176
-
177
- val lazyListState = options.scrollData.scrollState as? LazyListState
178
- val (scrollState, _) = if (lazyListState != null)
179
- lazyListState.proxyScrollState(limit, 1)
180
- else
181
- (options.scrollData.scrollState as? ScrollState ?: rememberScrollState()).proxyLimitedScrollState(limit, 1)
182
- val snapState: ScrollableState = lazyListState ?: scrollState
183
-
184
- val footerHeightPx = remember { mutableIntStateOf(0) }
185
- val headerRightWidthPx = remember { mutableIntStateOf(0) }
186
-
187
- if (navigation.options.scrollData.scrollToTopCallback != null) {
188
- RegisterScrollToTop(navigation.options.scrollData.scrollToTopCallback)
189
- }
190
-
191
- val canPop = navigator.canPop()
192
-
193
- val hasCustomBackHandling = !SwipeBackConfig.enabled ||
194
- OverplayComponentRegistry.isOverplayShowing() ||
195
- !canPop
196
-
197
- BackHandler(hasCustomBackHandling) { navigator.onBackSafe() }
198
-
199
- CompositionLocalProvider(
200
- StackScreenScrollableState provides options.scrollData.scrollState,
201
- LocalNavigation provides navigation,
202
- LocalScrollState provides scrollState,
203
- LocalOptions provides options,
204
- LocalFooterHeightPx provides footerHeightPx,
205
- LocalHeaderRightWidthPx provides headerRightWidthPx
206
- ) {
207
- Box(
208
- Modifier
209
- .fillMaxSize()
210
- .background(options.backgroundColor ?: AppTheme.current.colors.background.default)
211
- .conditional(options.keyboardOptions.keyboardShouldPersistTaps) {
212
- hideKeyboardOnTap()
213
- }
214
- .conditional(options.keyboardOptions.useAvoidKeyboard && supportsImePadding()) {
215
- imePadding()
216
- }
217
- ) {
218
- Box(Modifier.zIndex(1f)) {
219
- HeaderBackground()
220
- }
221
-
222
- Box(Modifier.zIndex(4f)) {
223
- Header(onBackHandler)
224
- }
225
-
226
- HeaderSideBar(
227
- bottomInset = maxOf(bottomBarsHeight(isBottomTabChild), if (isBottomTabChild) LocalDuoRailHeight.current else 0.dp),
228
- modifier = Modifier.zIndex(4f),
229
- )
230
-
231
- Column(Modifier.zIndex(5f).horizontalSafeAreaPadding()) {
232
- SearchAnimated(snapState = snapState)
233
- }
234
-
235
- Column(Modifier.zIndex(2f).fillMaxSize()) {
236
- MainContent(content = content, isBottomTabRoot = isBottomTabRoot, isBottomTabChild = isBottomTabChild)
237
- FooterContent()
238
- }
239
-
240
- Box(Modifier.zIndex(6f).horizontalSafeAreaPadding()) {
241
- FloatingContent(isBottomTabChild = isBottomTabChild)
242
- }
243
-
244
- OverplayView(isBottomTabChild = isBottomTabChild, id = id)
245
- }
246
- }
247
- }
248
-
249
- // Height the footer, tab bar or home indicator takes from the bottom of the screen.
250
- @Composable
251
- private fun bottomBarsHeight(isBottomTabChild: Boolean): Dp {
252
- val options = LocalOptions.current
253
- val navigationBar = AppNavigationBar.current
254
- return when {
255
- options.hasFooter() -> with(LocalDensity.current) { LocalFooterHeightPx.current.intValue.toDp() }
256
- isBottomTabChild -> bottomTabChildInset()
257
- else -> navigationBar
258
- }
259
- }
260
-
261
- @Suppress("FrequentlyChangingValue")
262
- @Composable
263
- internal fun FloatingContent(isBottomTabChild: Boolean) {
264
- val options = LocalOptions.current
265
- val scrollState = LocalScrollState.current
266
-
267
- val fabProps = options.floatingButtonProps ?: return
268
- val bottomPadding = fabProps.bottom ?: (Spacing.M + bottomBarsHeight(isBottomTabChild))
269
-
270
- FloatingButton(
271
- scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
272
- onClick = fabProps.onClick,
273
- containerColor = AppTheme.current.colors.primary,
274
- bottom = bottomPadding,
275
- icon = fabProps.icon,
276
- iconColor = fabProps.iconColor,
277
- text = fabProps.label,
278
- size = fabProps.size,
279
- position = fabProps.position ?: FABPosition.END,
280
- )
281
- }
282
-
283
- @Composable
284
- internal fun ColumnScope.MainContent(content: @Composable () -> Unit, isBottomTabRoot: Boolean, isBottomTabChild: Boolean) {
285
- val options = LocalOptions.current
286
- val inputSearchType = getInputSearchType(options)
287
- val density = LocalDensity.current
288
- val scrollState = LocalScrollState.current
289
- val navigationBar = AppNavigationBar.current
290
- val screenLayout = getScreenLayout()
291
- val useDuoGlass = options.usesDuoGlassHeader(screenLayout)
292
-
293
- Spacer(
294
- Modifier.height(
295
- if (!useDuoGlass && (options.headerType is HeaderType.DefaultOrExtended || (options.headerType is HeaderType.Transparent && !options.headerType.isFullScreenContent)))
296
- screenLayout.headerHeight else 0.dp
297
- )
298
- )
299
- if (inputSearchType == InputSearchType.Animated) {
300
- val scrollDp = with(density) { scrollState.value.toDp() }
301
-
302
- Spacer(Modifier.height((HEADER_HEIGHT.dp - scrollDp).coerceIn(0.dp, HEADER_HEIGHT.dp)))
303
- }
304
- // a bottom tab root stays full width: the tab bar and each tab's own StackScreen handle the insets
305
- Column(
306
- Modifier
307
- .fillMaxWidth()
308
- .weight(1f)
309
- .conditional(!isBottomTabRoot) {
310
- horizontalSafeAreaPadding()
311
- }
312
- .conditional(options.scrollData.scrollable && options.scrollData.scrollState is ScrollState) {
313
- verticalScroll(scrollState)
314
- }
315
- .conditional(useDuoGlass) {
316
- padding(top = screenLayout.headerHeight)
317
- }
318
- .padding(
319
- bottom = when {
320
- options.hasFooter() || isBottomTabRoot -> 0.dp
321
- isBottomTabChild -> bottomTabChildInset()
322
- !options.scrollData.scrollable && options.scrollData.scrollState == null -> 0.dp
323
- else -> navigationBar
324
- }
325
- )
326
- ) {
327
- CompositionLocalProvider(
328
- LocalScreenLayout provides if (isBottomTabRoot) screenLayout else screenLayout.consumeHorizontalInsets()
329
- ) {
330
- ScreenContent(content = content)
331
- }
332
- }
333
-
334
- }
335
-
336
- @Composable
337
- internal fun ScreenContent(content: @Composable () -> Unit) {
338
- val scrollState = LocalScrollState.current
339
- val options = LocalOptions.current
340
-
341
- if (options.headerType is HeaderType.Animated) {
342
- val animatedHeader = options.headerType
343
- Box {
344
- val bannerModifier = animatedHeader.height
345
- ?.let { Modifier.fillMaxWidth().height(it) }
346
- ?: Modifier.fillMaxWidth().aspectRatio(animatedHeader.aspectRatio.value)
347
- Box(bannerModifier) {
348
- animatedHeader.composable.invoke(scrollState.value)
349
- }
350
- Box(Modifier.padding(top = getScreenLayout().headerHeight + animatedHeader.layoutOffSet)) {
351
- content()
352
- }
353
- }
354
- } else {
355
- content()
356
- }
357
- }
358
-
359
- @Composable
360
- internal fun FooterContent() {
361
- val options = LocalOptions.current
362
- if (options.hasFooter()) {
363
- val keyboardSize = keyboardSizeState()
364
- // the Duo floating tab bar overlays tab content, so a tab's footer sits above it
365
- val duoTabInset = if (LocalNavigation.current.bottomTabIndex != -1) duoFloatingTabBarInset(getScreenLayout()) else 0.dp
366
- val bottomPadding = (maxOf(AppNavigationBar.current, duoTabInset) - keyboardSize.value).coerceAtLeast(0.dp)
367
- Footer(
368
- footerComponent = options.footerComponent,
369
- bottomPadding = bottomPadding,
370
- transparent = options.footerTransparent,
371
- useMargin = options.footerUseMargin
372
- )
373
- }
374
- }
375
-
376
- @Composable
377
- internal fun keyboardSizeState(): State<Dp> {
378
- val bottom = WindowInsets.ime.asPaddingValues()
379
- return rememberUpdatedState(bottom.calculateBottomPadding())
380
- }
381
-
382
- @Composable
383
- internal fun OverplayView(isBottomTabChild: Boolean, id: Int) {
384
- val overplayType = OverplayComponentRegistry.getOverplayType()
385
-
386
- if (overplayType != null) {
387
- Box(Modifier.zIndex(if (overplayType == OverplayComponentType.SNACK_BAR) 3f else 7f).fillMaxSize()) {
388
- if (isBottomTabChild) return@Box
389
- if (OverplayComponentRegistry.currentRootId() != id) return@Box
390
- OverplayComponentRegistry.OverlayComponent()
391
- }
392
- }
393
- }
394
-
395
- @Composable
396
- fun Footer(
397
- footerComponent: @Composable (() -> Unit)?,
398
- bottomPadding: Dp,
399
- transparent: Boolean = false,
400
- useMargin: Boolean = true,
401
- ) {
402
- if (footerComponent == null) return
403
-
404
- val footerHeightPx = LocalFooterHeightPx.current
405
-
406
- val shadowBrush = remember {
407
- Brush.verticalGradient(
408
- colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.05f))
409
- )
410
- }
411
-
412
- val backgroundColor = if (transparent) Color.Transparent else AppTheme.current.colors.background.surface
413
-
414
- Box(Modifier.onGloballyPositioned {
415
- if (footerHeightPx.intValue != it.size.height) footerHeightPx.intValue = it.size.height
416
- }) {
417
- Box(
418
- Modifier
419
- .fillMaxWidth()
420
- .background(backgroundColor)
421
- .conditional(IsShowBaseLineDebug) {
422
- border(1.dp, Colors.blue_03)
423
- }
424
- .horizontalSafeAreaPadding()
425
- .padding(
426
- top = if (useMargin) Spacing.S else 0.dp,
427
- start = if (useMargin) Spacing.M else 0.dp,
428
- end = if (useMargin) Spacing.M else 0.dp,
429
- bottom = bottomPadding + if (useMargin) Spacing.S else 0.dp,
430
- )
431
- ) {
432
- footerComponent.invoke()
433
- }
434
-
435
- if (!transparent) {
436
- Box(
437
- modifier = Modifier
438
- .fillMaxWidth()
439
- .height(6.dp)
440
- .offset(x = 0.dp, y = (-6).dp)
441
- .background(shadowBrush)
442
- )
443
- }
444
- }
445
- }
446
-
447
- data class InputSearchLayoutParams(
448
- val topPadding: Dp,
449
- val startPadding: Dp,
450
- val endPadding: Dp
451
- )
452
-
453
- @Suppress("FrequentlyChangingValue")
454
- @Composable
455
- internal fun SearchAnimated(
456
- snapState: ScrollableState
457
- ) {
458
- val scrollState = LocalScrollState.current
459
- val options = LocalOptions.current
460
- val navigator = LocalNavigator.current
461
- val density = LocalDensity.current
462
-
463
- val scrollDp = with(density) { scrollState.value.toDp() }
464
-
465
- val inputSearchType = getInputSearchType(options)
466
- val headerRightWidthPx = LocalHeaderRightWidthPx.current
467
- val headerRightWidthDp = with(density) { headerRightWidthPx.intValue.toDp() }
468
-
469
- if (inputSearchType == InputSearchType.None) return
470
- val inputSearchProps = (options.headerType as? HeaderType.DefaultOrExtended)?.inputSearchProps ?: return
471
-
472
- val screenLayout = getScreenLayout()
473
- val useSideBar = screenLayout.sideBar != null
474
- // collapsed, the search is centered in the bar row
475
- val minTopPadding = screenLayout.headerStatusBarHeight + (screenLayout.headerBarHeight - HEADER_HEIGHT.dp) / 2
476
- val maxTopPadding = screenLayout.headerHeight
477
- val minStartPadding = Spacing.M
478
- val maxStartPadding = if (options.hiddenBack || useSideBar) Spacing.M else 48.dp // button back: size + left +right = 28 + 12 + 8 = 48
479
- val minEndPadding = Spacing.M
480
- val maxEndPadding = when {
481
- options.headerRight is HeaderRight.None -> Spacing.M
482
- useSideBar && options.headerRight is HeaderRight.Toolkit -> Spacing.M
483
- else -> headerRightWidthDp + Spacing.M * 2
484
- }
485
-
486
- val targetColor = lerp(
487
- AppTheme.current.colors.background.surface,
488
- AppTheme.current.colors.background.default,
489
- (scrollDp / minTopPadding.coerceAtLeast(1.dp)).coerceIn(0f, 1f)
490
- )
491
- val animatedColor by animateColorAsState(targetValue = targetColor, label = "BackgroundColor")
492
-
493
- val layoutParams = when (inputSearchType) {
494
- InputSearchType.Header -> {
495
- InputSearchLayoutParams(
496
- topPadding = minTopPadding,
497
- startPadding = maxStartPadding,
498
- endPadding = maxEndPadding
499
- )
500
- }
501
-
502
- InputSearchType.Animated -> {
503
- val collapsePx = with(density) { HEADER_HEIGHT.dp.roundToPx() }
504
- val progress = (scrollState.value / collapsePx.toFloat()).coerceIn(0f, 1f)
505
-
506
- LaunchedEffect(snapState, collapsePx) {
507
- snapshotFlow { snapState.isScrollInProgress }
508
- .filter { !it }
509
- .collect {
510
- val offset = snapState.headerOffsetPx() ?: return@collect
511
- if (offset <= 0 || offset >= collapsePx) return@collect
512
- val target = if (offset < collapsePx / 2) 0 else collapsePx
513
- launch { snapState.animateScrollBy((target - offset).toFloat()) }
514
- }
515
- }
516
-
517
- InputSearchLayoutParams(
518
- topPadding = lerp(maxTopPadding, minTopPadding, progress),
519
- startPadding = lerp(minStartPadding, maxStartPadding, progress),
520
- endPadding = lerp(minEndPadding, maxEndPadding, progress)
521
- )
522
- }
523
-
524
- InputSearchType.None -> return
525
- }
526
-
527
- Spacer(Modifier.height(layoutParams.topPadding))
528
- Box(
529
- modifier = Modifier
530
- .height(HEADER_HEIGHT.dp)
531
- .fillMaxWidth()
532
- .padding(
533
- start = layoutParams.startPadding,
534
- end = layoutParams.endPadding
535
- ),
536
- contentAlignment = Alignment.Center
537
- ) {
538
- InputSearch(
539
- inputSearchProps = inputSearchProps.copy(
540
- backgroundColor = animatedColor,
541
- onPressButtonText = {
542
- navigator.pop()
543
- }
544
- )
545
- )
546
- }
547
- }
548
-
549
- private fun ScrollableState.headerOffsetPx(): Int? = when (this) {
550
- is LazyListState -> if (firstVisibleItemIndex == 0) firstVisibleItemScrollOffset else null
551
- is ScrollState -> value
552
- else -> null
553
- }
554
-
555
- private fun quantize(value: Int, stepPx: Int): Int {
556
- if (stepPx <= 1) return value
557
- return (value / stepPx) * stepPx
558
- }
559
-
560
- @Composable
561
- internal fun LazyListState.proxyScrollState(
562
- thresholdPx: Int = 200,
563
- stepPx: Int = 4
564
- ): Pair<ScrollState, Boolean> = proxyScroll(
565
- source = this,
566
- thresholdPx = thresholdPx,
567
- stepPx = stepPx,
568
- isScrollInProgress = { isScrollInProgress },
569
- rawTargetFlow = {
570
- snapshotFlow {
571
- if (firstVisibleItemIndex == 0) firstVisibleItemScrollOffset
572
- else SCROLLED_PAST_FIRST_ITEM_THRESHOLD
573
- }
574
- },
575
- )
576
-
577
- @Composable
578
- internal fun ScrollState.proxyLimitedScrollState(
579
- thresholdPx: Int = 200,
580
- stepPx: Int = 4
581
- ): Pair<ScrollState, Boolean> = proxyScroll(
582
- source = this,
583
- thresholdPx = thresholdPx,
584
- stepPx = stepPx,
585
- isScrollInProgress = { isScrollInProgress },
586
- rawTargetFlow = { snapshotFlow { value } },
587
- )
588
-
589
- @Composable
590
- private fun proxyScroll(
591
- source: Any,
592
- thresholdPx: Int,
593
- stepPx: Int,
594
- isScrollInProgress: () -> Boolean,
595
- rawTargetFlow: () -> Flow<Int>,
596
- ): Pair<ScrollState, Boolean> {
597
- val proxy = rememberScrollState()
598
- val locked = remember { mutableStateOf(false) }
599
-
600
- LaunchedEffect(source, proxy, thresholdPx, stepPx) {
601
- rawTargetFlow().collect { rawTarget ->
602
- val shouldLock = rawTarget > thresholdPx
603
- if (locked.value != shouldLock) locked.value = shouldLock
604
-
605
- val desired = if (shouldLock) thresholdPx else quantize(rawTarget, stepPx)
606
-
607
- if (proxy.value != desired) {
608
- proxy.scrollTo(desired.coerceAtLeast(0))
609
- }
610
- }
611
- }
612
-
613
- val inProgress = remember { mutableStateOf(false) }
614
- LaunchedEffect(source, thresholdPx) {
615
- snapshotFlow { isScrollInProgress() }
616
- .collect { progressing ->
617
- inProgress.value = if (locked.value) false else progressing
618
- }
619
- }
620
-
621
- return proxy to inProgress.value
622
- }
623
-
624
- private const val SCROLLED_PAST_FIRST_ITEM_THRESHOLD = 10_000
625
-
626
- internal val LocalScrollState = staticCompositionLocalOf<ScrollState> { error("No Scroll State provided") }
627
- internal val LocalOptions = staticCompositionLocalOf<NavigationOptions> { error("No NavigationOptions provided") }
628
-
629
- val StackScreenScrollableState = staticCompositionLocalOf<ScrollableState?> { null }
630
-
631
- // iPhone Duo side bar: content scrolls under a glass header instead of starting below it (RN Screen).
632
- internal fun NavigationOptions.usesDuoGlassHeader(layout: ScreenLayout): Boolean =
633
- layout.sideBar != null && headerType is HeaderType.DefaultOrExtended &&
634
- getInputSearchType(this) == InputSearchType.None &&
635
- scrollData.scrollable && scrollData.scrollState is ScrollState
636
-
637
- internal fun getInputSearchType(options: NavigationOptions): InputSearchType {
638
- return when (val headerType = options.headerType) {
639
- is HeaderType.DefaultOrExtended -> when {
640
- headerType.inputSearchProps == null -> InputSearchType.None
641
- headerType.useAnimated -> InputSearchType.Animated
642
- else -> InputSearchType.Header
643
- }
644
-
645
- else -> InputSearchType.None
646
- }
647
- }
648
-