@momo-kits/native-kits 0.300.3-debug → 0.300.4-debug
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/CLAUDE.md +51 -7
- package/README.md +2 -2
- package/compose/build.gradle.kts +10 -2
- package/compose/build.gradle.kts.backup +8 -0
- package/compose/compose.podspec +1 -3
- package/compose/src/androidMain/kotlin/vn/momo/kits/const/HostFont.android.kt +4 -3
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +2 -0
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/ReservedRegions.android.kt +6 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Context.kt +0 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/DesignSystemKits.kt +62 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +351 -111
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +20 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Button.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +4 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Information.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +9 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputMoney.kt +11 -12
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +10 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +6 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupNotify.kt +55 -22
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +7 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Radio.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +13 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +13 -5
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Switch.kt +3 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +14 -6
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tag.kt +19 -21
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Tooltip.kt +5 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/DateTimePickerUtils.kt +3 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/datetimepicker/WheelPicker.kt +7 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/HostFont.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +20 -15
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/Item.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +306 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/BottomSheet.kt +43 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ModalScreen.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigation.kt +10 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/NavigationContainer.kt +6 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +6 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +197 -60
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +132 -83
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +54 -62
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +97 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +377 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +122 -24
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +34 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +3 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderLocation.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +113 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HelpCenterRequest.kt +37 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +192 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/ToolkitRequests.kt +8 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +20 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/utils/Utils.kt +13 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/HelpCenterRequestTest.kt +44 -0
- package/compose/src/commonTest/kotlin/vn/momo/kits/navigation/component/ToolkitRequestsTest.kt +15 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +3 -1
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +4 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/ReservedRegions.ios.kt +138 -0
- package/gradle/libs.versions.toml +3 -14
- package/gradle/wrapper/gradle-wrapper.properties +0 -2
- package/gradle.properties +3 -5
- package/ios/Application/ApplicationEnvironment.swift +24 -6
- package/ios/Application/DesignSystemKits.swift +100 -0
- package/ios/Application/DesignSystemKitsPayload.swift +68 -0
- package/ios/Application/MaxApi.swift +30 -3
- package/ios/Avatar/Avatar.swift +4 -3
- package/ios/Badge/Badge.swift +6 -4
- package/ios/Badge/BadgeDot.swift +1 -1
- package/ios/Badge/BadgeRibbon.swift +8 -2
- package/ios/Button/Button.swift +15 -12
- package/ios/CalculatorKeyboard/CalculatorKeyboard.swift +64 -18
- package/ios/Carousel/Carousel.swift +29 -12
- package/ios/Checkbox/Checkbox.swift +7 -3
- package/ios/Chip/Chip.swift +5 -4
- package/ios/Collapse/Collapse.swift +70 -19
- package/ios/Colors+Radius+Spacing/Colors.swift +10 -1
- package/ios/Colors+Radius+Spacing/Radius.swift +6 -0
- package/ios/DateTimePicker/DateTimePicker.swift +54 -20
- package/ios/DateTimePicker/WheelPicker.swift +103 -105
- package/ios/Divider/Divider.swift +37 -4
- package/ios/Extensions/Color++.swift +38 -9
- package/ios/Extensions/OptionalEnvironmentObject.swift +12 -0
- package/ios/Extensions/SkeletonUICompat.swift +11 -0
- package/ios/Icon/Icon.swift +21 -23
- package/ios/Icon/Icons.swift +1329 -0
- package/ios/IconButton/IconButton.swift +15 -7
- package/ios/Image/Image.swift +7 -23
- package/ios/Information/Information.swift +27 -9
- package/ios/Input/ErrorView.swift +2 -2
- package/ios/Input/Input.swift +171 -148
- package/ios/Input/InputPhoneNumber.swift +10 -9
- package/ios/Input/InputSearch.swift +107 -78
- package/ios/Input/InputTextArea.swift +133 -148
- package/ios/InputDropDown/InputDropDown.swift +10 -7
- package/ios/InputMoney/InputMoney.swift +32 -11
- package/ios/InputOTP/InputOTP.swift +22 -12
- package/ios/Layout/Card.swift +50 -0
- package/ios/Layout/GridContext.swift +187 -3
- package/ios/Layout/Item.swift +5 -4
- package/ios/Layout/ScreenLayout.swift +415 -0
- package/ios/Layout/Section.swift +4 -100
- package/ios/Loader/Loader.swift +5 -9
- package/ios/Lottie/LottieView.swift +14 -2
- package/ios/{Application/Navigation/Overplay → Navigation}/BottomSheet.swift +169 -29
- package/ios/Navigation/BottomTab/BottomTab.swift +332 -0
- package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +18 -7
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +104 -0
- package/ios/Navigation/BottomTab/DuoTabBar.swift +329 -0
- package/ios/{Application → Navigation/Component}/FloatingButton.swift +43 -54
- package/ios/Navigation/Component/Header.swift +545 -0
- package/ios/Navigation/Component/HeaderBackground.swift +154 -0
- package/ios/Navigation/Component/HeaderJourney.swift +73 -0
- package/ios/Navigation/Component/HeaderLocation.swift +48 -0
- package/ios/Navigation/Component/HeaderRight.swift +425 -0
- package/ios/Navigation/Component/HeaderTitle.swift +36 -0
- package/ios/{Application/Navigation → Navigation/Component}/HeaderUser.swift +21 -24
- package/ios/Navigation/Component/SideBar.swift +183 -0
- package/ios/{Application/Navigation/Overplay → Navigation/Component}/SnackBar.swift +8 -8
- package/ios/Navigation/HostBackGestureBridge.swift +78 -0
- package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +9 -2
- package/ios/Navigation/Navigation.swift +204 -0
- package/ios/Navigation/NavigationContainer.swift +195 -0
- package/ios/{Application/Navigation → Navigation}/Navigator.swift +207 -107
- package/ios/Navigation/StackScreen.swift +930 -0
- package/ios/Navigation/StackTransition.swift +400 -0
- package/ios/Navigation/SwipeBackHaptic.swift +33 -0
- package/ios/{Application/Navigation → Navigation}/Tracking/ScreenTracker.swift +12 -5
- package/ios/OTPKeyboard/OTPKeyboard.swift +8 -11
- package/ios/Pagination/PaginationScroll.swift +2 -2
- package/ios/Popup/PopupDisplay.swift +72 -135
- package/ios/Popup/PopupInput.swift +10 -13
- package/ios/Popup/PopupLayout.swift +138 -0
- package/ios/Popup/PopupNotify.swift +59 -69
- package/ios/Popup/PopupPromotion.swift +31 -47
- package/ios/PopupView/FullscreenPopup.swift +13 -19
- package/ios/PopupView/PopupView.swift +1 -1
- package/ios/PopupView/Utils++.swift +0 -8
- package/ios/ProgressInfo/ProgressInfo.swift +4 -2
- package/ios/Radio/Radio.swift +2 -1
- package/ios/Rating/Rating.swift +9 -1
- package/ios/Resources/dot_loading.json +558 -0
- package/ios/Resources/lottie_circle_loader.json +1 -0
- package/ios/ScrollIndicator/ScrollIndicator.swift +11 -10
- package/ios/Skeleton/Skeleton.swift +22 -27
- package/ios/Slider/Slider.swift +16 -18
- package/ios/Stepper/Stepper.swift +7 -3
- package/ios/Steps/Steps.swift +8 -6
- package/ios/SuggestAction/SuggestAction.swift +5 -0
- package/ios/Swipeable/SwipeCell.swift +10 -6
- package/ios/Switch/Switch.swift +22 -20
- package/ios/TabView/TabView.swift +102 -21
- package/ios/Tag/Tag.swift +15 -4
- package/ios/Template/Logo/Logo.swift +2 -1
- package/ios/Template/TrustBanner/TrustBanner.swift +109 -55
- package/ios/Title/Title.swift +6 -17
- package/ios/Tooltip/Tooltip.swift +29 -14
- package/ios/Typography/FontScale.swift +17 -14
- package/ios/Typography/Text.swift +156 -20
- package/ios/Typography/Typography.swift +2 -2
- package/ios/Uploader/Uploader.swift +3 -1
- package/ios/native-kits.podspec +2 -3
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -3
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/ContentView.swift +1 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/DuoTabDemo.swift +80 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/Home.swift +2 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupDisplayDemo.swift +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupNotifyDemo.swift +2 -2
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/PopupPromotionDemo.swift +2 -0
- package/package.json +1 -1
- package/publish.sh +19 -2
- package/scripts/gen-ios-icons.mjs +36 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/AnimationSearchInput.kt +0 -52
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/FloatingButton.kt +0 -183
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Header.kt +0 -207
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderAnimated.kt +0 -39
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderBackground.kt +0 -86
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderDefault.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderExtended.kt +0 -76
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +0 -293
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderTitle.kt +0 -33
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/NavigationContainer.kt +0 -110
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +0 -378
- package/compose/src/commonMain/kotlin/vn/momo/kits/application/useHeaderSearchAnimation.kt +0 -69
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitDesignSystem.kt +0 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/KitFontScale.kt +0 -22
- package/ios/Application/Components.swift +0 -270
- package/ios/Application/ComposeApi.swift +0 -22
- package/ios/Application/HeaderRight.swift +0 -306
- package/ios/Application/Navigation/BottomTab/BottomTab.swift +0 -104
- package/ios/Application/Navigation/HeaderBackProps.swift +0 -66
- package/ios/Application/Navigation/HeaderTitle.swift +0 -57
- package/ios/Application/Navigation/NavigationContainer.swift +0 -213
- package/ios/Application/Navigation/NavigationOptions.swift +0 -109
- package/ios/Application/Navigation/NavigatorManager.swift +0 -22
- package/ios/Application/Navigation/component/Header.swift +0 -178
- package/ios/Application/Navigation/component/HeaderBackground.swift +0 -83
- package/ios/Application/Navigation/component/HeaderColor.swift +0 -65
- package/ios/Application/Navigation/component/HeaderRightWidthKey.swift +0 -11
- package/ios/Application/Navigation/component/NavigationFooter.swift +0 -90
- package/ios/Application/Navigation/component/NavigationHeaderRight.swift +0 -196
- package/ios/Application/Screen.swift +0 -250
- package/ios/Application/StackScreen.swift +0 -492
- package/ios/Typography/FontScaleStore.swift +0 -58
- package/ios/Typography/KitDesignSystem.swift +0 -17
- /package/ios/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
|
@@ -3,23 +3,27 @@ package vn.momo.kits.navigation.component
|
|
|
3
3
|
import androidx.compose.animation.core.animateFloatAsState
|
|
4
4
|
import androidx.compose.foundation.background
|
|
5
5
|
import androidx.compose.foundation.layout.Box
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxHeight
|
|
6
7
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
7
8
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
8
9
|
import androidx.compose.foundation.layout.height
|
|
9
10
|
import androidx.compose.runtime.Composable
|
|
10
11
|
import androidx.compose.runtime.getValue
|
|
11
12
|
import androidx.compose.ui.Modifier
|
|
12
|
-
import androidx.compose.ui.
|
|
13
|
+
import androidx.compose.ui.draw.clipToBounds
|
|
14
|
+
import androidx.compose.ui.graphics.graphicsLayer
|
|
13
15
|
import androidx.compose.ui.graphics.Brush
|
|
14
16
|
import androidx.compose.ui.graphics.Color
|
|
15
17
|
import androidx.compose.ui.platform.LocalDensity
|
|
16
18
|
import androidx.compose.ui.unit.dp
|
|
17
19
|
import vn.momo.kits.components.Image
|
|
18
|
-
import vn.momo.kits.const.AppStatusBar
|
|
19
20
|
import vn.momo.kits.const.AppTheme
|
|
21
|
+
import vn.momo.kits.layout.getScreenLayout
|
|
20
22
|
import vn.momo.kits.modifier.conditional
|
|
21
23
|
import vn.momo.kits.navigation.LocalOptions
|
|
22
24
|
import vn.momo.kits.navigation.LocalScrollState
|
|
25
|
+
import vn.momo.kits.navigation.getInputSearchType
|
|
26
|
+
import vn.momo.kits.navigation.usesDuoGlassHeader
|
|
23
27
|
|
|
24
28
|
enum class AnimatedHeaderRatio(val value: Float){
|
|
25
29
|
RATIO_16_9(16f / 9f),
|
|
@@ -29,6 +33,9 @@ enum class AnimatedHeaderRatio(val value: Float){
|
|
|
29
33
|
|
|
30
34
|
internal val HEADER_BACKGROUND_HEIGHT = 154.dp
|
|
31
35
|
|
|
36
|
+
// RN DuoScrollGradient: extended gradient runs this far below the header
|
|
37
|
+
private val DUO_GRADIENT_EXTRA = 52.dp
|
|
38
|
+
|
|
32
39
|
@Suppress("FrequentlyChangingValue")
|
|
33
40
|
@Composable
|
|
34
41
|
internal fun HeaderBackground() {
|
|
@@ -38,7 +45,8 @@ internal fun HeaderBackground() {
|
|
|
38
45
|
val scrollState = LocalScrollState.current
|
|
39
46
|
val backgroundColor = options.backgroundColor ?: AppTheme.current.colors.background.default
|
|
40
47
|
|
|
41
|
-
val
|
|
48
|
+
val layout = getScreenLayout()
|
|
49
|
+
val minHeight = layout.headerHeight
|
|
42
50
|
val maxHeight = HEADER_BACKGROUND_HEIGHT
|
|
43
51
|
val opacity by animateFloatAsState(
|
|
44
52
|
targetValue = (1 - (scrollState.value * 1f / HEADER_HEIGHT * 1f)).coerceIn(0f, 1f),
|
|
@@ -57,20 +65,32 @@ internal fun HeaderBackground() {
|
|
|
57
65
|
else -> 0.dp
|
|
58
66
|
}
|
|
59
67
|
|
|
60
|
-
|
|
68
|
+
// iPhone Duo glass header: the gradient leaves with the content, as the quick return header does
|
|
69
|
+
val useDuoGlass = options.usesDuoGlassHeader(layout)
|
|
70
|
+
// RN HeaderExtendHeader with search on the side bar layout: the gradient fills the header box,
|
|
71
|
+
// which for extended shrinks from 52pt below the header over the first 100pt of scroll
|
|
72
|
+
val duoSearch = layout.sideBar != null && getInputSearchType(options) != InputSearchType.None
|
|
73
|
+
val fillsBox = useDuoGlass || duoSearch
|
|
74
|
+
val duoModifier = when {
|
|
75
|
+
useDuoGlass -> {
|
|
76
|
+
val extra = if (options.headerType is HeaderType.Extended) DUO_GRADIENT_EXTRA else 0.dp
|
|
77
|
+
Modifier.height(minHeight + extra).graphicsLayer { translationY = -scrollState.value.toFloat() }
|
|
78
|
+
}
|
|
79
|
+
duoSearch && options.headerType is HeaderType.Extended -> {
|
|
80
|
+
val progress = (with(density) { scrollState.value.toDp() } / 100.dp).coerceIn(0f, 1f)
|
|
81
|
+
Modifier.height(minHeight + DUO_GRADIENT_EXTRA * (1 - progress))
|
|
82
|
+
}
|
|
83
|
+
duoSearch -> Modifier.height(minHeight)
|
|
84
|
+
else -> Modifier.height(height)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
Box(Modifier.fillMaxWidth().then(duoModifier).clipToBounds().background(backgroundColor)) {
|
|
88
|
+
// on Duo the fade spans the gradient box; a banner image keeps its 375:154 shape, clipped
|
|
61
89
|
Box(Modifier
|
|
62
|
-
.height(maxHeight)
|
|
63
90
|
.fillMaxWidth()
|
|
91
|
+
.then(if (fillsBox && !theme.isHeaderImage()) Modifier.fillMaxHeight() else Modifier.height(maxHeight))
|
|
64
92
|
.conditional(!theme.isHeaderImage()) {
|
|
65
|
-
background(Brush.
|
|
66
|
-
colors = listOf(
|
|
67
|
-
color,
|
|
68
|
-
backgroundColor
|
|
69
|
-
),
|
|
70
|
-
start = Offset(0f, 0f),
|
|
71
|
-
end = Offset(0f, with(density) { maxHeight.toPx() })
|
|
72
|
-
)
|
|
73
|
-
)
|
|
93
|
+
background(Brush.verticalGradient(colors = listOf(color, backgroundColor)))
|
|
74
94
|
}
|
|
75
95
|
){
|
|
76
96
|
if (!theme.isHeaderImage()) return@Box
|
|
@@ -23,7 +23,6 @@ import vn.momo.kits.components.Text
|
|
|
23
23
|
import vn.momo.kits.const.Spacing
|
|
24
24
|
import vn.momo.kits.const.Typography
|
|
25
25
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
26
|
-
import vn.momo.kits.platform.getScreenDimensions
|
|
27
26
|
|
|
28
27
|
@Composable
|
|
29
28
|
internal fun HeaderJourney(
|
|
@@ -33,7 +32,7 @@ internal fun HeaderJourney(
|
|
|
33
32
|
if (data !is HeaderTitle.Journey) return
|
|
34
33
|
if (data.isLoading) return TitleJourneyShimmer()
|
|
35
34
|
|
|
36
|
-
val maxWidth =
|
|
35
|
+
val maxWidth = headerTitleMaxWidth()
|
|
37
36
|
val tint = parseHeaderTint(data.tintColor) ?: tintIconColor
|
|
38
37
|
val iconTint = parseHeaderTint(data.iconColor) ?: tint
|
|
39
38
|
|
|
@@ -98,7 +97,8 @@ internal fun parseHeaderTint(value: String?): Color? {
|
|
|
98
97
|
val hex = when (raw.length) {
|
|
99
98
|
3 -> "FF" + raw.map { "$it$it" }.joinToString("")
|
|
100
99
|
6 -> "FF$raw"
|
|
101
|
-
|
|
100
|
+
// CSS #RRGGBBAA, as RN passes it; Compose wants AARRGGBB
|
|
101
|
+
8 -> raw.substring(6) + raw.substring(0, 6)
|
|
102
102
|
else -> return null
|
|
103
103
|
}
|
|
104
104
|
val v = hex.toLongOrNull(16) ?: return null
|
|
@@ -22,7 +22,6 @@ import vn.momo.kits.components.Text
|
|
|
22
22
|
import vn.momo.kits.const.Spacing
|
|
23
23
|
import vn.momo.kits.const.Typography
|
|
24
24
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
25
|
-
import vn.momo.kits.platform.getScreenDimensions
|
|
26
25
|
|
|
27
26
|
@Composable
|
|
28
27
|
internal fun HeaderLocation(
|
|
@@ -32,7 +31,7 @@ internal fun HeaderLocation(
|
|
|
32
31
|
if (data !is HeaderTitle.Location) return
|
|
33
32
|
if (data.isLoading) return TitleLocationShimmer()
|
|
34
33
|
|
|
35
|
-
val maxWidth =
|
|
34
|
+
val maxWidth = headerTitleMaxWidth()
|
|
36
35
|
val tint = parseHeaderTint(data.tintColor) ?: tintIconColor
|
|
37
36
|
|
|
38
37
|
Column(
|
|
@@ -6,6 +6,7 @@ import androidx.compose.foundation.background
|
|
|
6
6
|
import androidx.compose.foundation.border
|
|
7
7
|
import androidx.compose.foundation.layout.Arrangement
|
|
8
8
|
import androidx.compose.foundation.layout.Box
|
|
9
|
+
import androidx.compose.foundation.layout.Column
|
|
9
10
|
import androidx.compose.foundation.layout.Row
|
|
10
11
|
import androidx.compose.foundation.layout.height
|
|
11
12
|
import androidx.compose.foundation.layout.offset
|
|
@@ -36,7 +37,9 @@ import androidx.compose.ui.Alignment
|
|
|
36
37
|
import androidx.compose.ui.Modifier
|
|
37
38
|
import androidx.compose.ui.draw.clip
|
|
38
39
|
import androidx.compose.ui.graphics.Color
|
|
40
|
+
import androidx.compose.ui.unit.Dp
|
|
39
41
|
import androidx.compose.ui.unit.dp
|
|
42
|
+
import vn.momo.kits.layout.DuoMetrics
|
|
40
43
|
import vn.momo.kits.application.LocalContext
|
|
41
44
|
import vn.momo.kits.application.MiniAppContext
|
|
42
45
|
import vn.momo.kits.components.BadgeDot
|
|
@@ -70,11 +73,32 @@ fun HeaderRight(
|
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
|
|
76
|
+
private const val HELP_CENTER_APP_ID = "vn.momo.helpcenter"
|
|
77
|
+
|
|
78
|
+
private fun usesHelpCenter(context: MiniAppContext?): Boolean =
|
|
79
|
+
context != null && context.appId != HELP_CENTER_APP_ID
|
|
80
|
+
|
|
81
|
+
private fun showsShortcut(headerRight: HeaderRight.Toolkit, context: MiniAppContext?): Boolean =
|
|
82
|
+
headerRight.useShortcut && !(headerRight.useMore && context == null)
|
|
83
|
+
|
|
84
|
+
// Height of the vertical toolkit: the optional shortcut, then the help/home capsule.
|
|
85
|
+
@Composable
|
|
86
|
+
internal fun toolkitColumnHeight(headerRight: HeaderRight.Toolkit): Dp {
|
|
87
|
+
val context = LocalContext.current
|
|
88
|
+
val shortcut = if (showsShortcut(headerRight, context)) DuoMetrics.controlSize + Spacing.M else 0.dp
|
|
89
|
+
val groupCount = if (usesHelpCenter(context)) 2 else 1
|
|
90
|
+
return shortcut + if (groupCount > 1) DuoMetrics.itemPitch * groupCount else DuoMetrics.controlSize
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// SideBarColumn: the iPhone Duo side bar; SideBarRow: the Duo header row on the status bar, same 48pt controls.
|
|
94
|
+
internal enum class ToolkitLayout { Bar, SideBarColumn, SideBarRow }
|
|
95
|
+
|
|
73
96
|
@Composable
|
|
74
97
|
internal fun Toolkit(
|
|
75
98
|
headerRight: HeaderRight.Toolkit,
|
|
76
99
|
tintColor: Color? = null,
|
|
77
|
-
headerColor: HeaderColor
|
|
100
|
+
headerColor: HeaderColor,
|
|
101
|
+
layout: ToolkitLayout = ToolkitLayout.Bar,
|
|
78
102
|
) {
|
|
79
103
|
val api = LocalMaxApi.current
|
|
80
104
|
val context = LocalContext.current
|
|
@@ -120,6 +144,23 @@ internal fun Toolkit(
|
|
|
120
144
|
}
|
|
121
145
|
// AI-GENERATED END: retain custom tool data and typed callback outcomes
|
|
122
146
|
|
|
147
|
+
fun onPressHelpCenter() {
|
|
148
|
+
if (context == null) return
|
|
149
|
+
// AI-GENERATED START: preserve Help Center context and own its event subscription
|
|
150
|
+
val client = api ?: return
|
|
151
|
+
requests.observe({ callback -> client.showBottomSheet(context.helpCenterRequest(), callback) }) {}
|
|
152
|
+
// AI-GENERATED END: preserve Help Center context and own its event subscription
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fun onPressClose() {
|
|
156
|
+
// AI-GENERATED START: preserve current-container and whole-stack dismissal
|
|
157
|
+
api?.dismiss(if (headerRight.useCloseIcon) DismissTarget.Current() else DismissTarget.All) {}
|
|
158
|
+
// AI-GENERATED END: preserve current-container and whole-stack dismissal
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
val useHelpCenter = usesHelpCenter(context)
|
|
162
|
+
val closeIcon = if (headerRight.useCloseIcon) "16_navigation_close_circle" else "16_basic_home"
|
|
163
|
+
|
|
123
164
|
val navButtonConfig = getNavigationButtonConfig(
|
|
124
165
|
headerRight,
|
|
125
166
|
::onPressShortcut,
|
|
@@ -130,12 +171,7 @@ internal fun Toolkit(
|
|
|
130
171
|
group.items.any { it.showBadge }
|
|
131
172
|
}
|
|
132
173
|
|
|
133
|
-
val isShowShortcut
|
|
134
|
-
derivedStateOf {
|
|
135
|
-
(headerRight.useShortcut == true) &&
|
|
136
|
-
!(headerRight.useMore == true && context == null)
|
|
137
|
-
}
|
|
138
|
-
}
|
|
174
|
+
val isShowShortcut = showsShortcut(headerRight, context)
|
|
139
175
|
|
|
140
176
|
val icon by remember(navButtonConfig.icon, isFavorite) {
|
|
141
177
|
derivedStateOf {
|
|
@@ -144,6 +180,47 @@ internal fun Toolkit(
|
|
|
144
180
|
}
|
|
145
181
|
}
|
|
146
182
|
|
|
183
|
+
if (layout != ToolkitLayout.Bar) {
|
|
184
|
+
val horizontal = layout == ToolkitLayout.SideBarRow
|
|
185
|
+
val controls = @Composable {
|
|
186
|
+
if (isShowShortcut) {
|
|
187
|
+
SideBarButton(
|
|
188
|
+
icon = icon,
|
|
189
|
+
headerColor = headerColor,
|
|
190
|
+
enabled = !isLoading,
|
|
191
|
+
showBadge = showBadge,
|
|
192
|
+
onClick = navButtonConfig.onPress,
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
SideBarGroup(headerColor = headerColor, itemCount = if (useHelpCenter) 2 else 1, horizontal = horizontal) { item ->
|
|
196
|
+
if (useHelpCenter) {
|
|
197
|
+
SideBarGroupItem(
|
|
198
|
+
icon = "help_center",
|
|
199
|
+
headerColor = headerColor,
|
|
200
|
+
automationId = "btn_navigation_help_center",
|
|
201
|
+
onClick = ::onPressHelpCenter,
|
|
202
|
+
modifier = item,
|
|
203
|
+
)
|
|
204
|
+
}
|
|
205
|
+
SideBarGroupItem(
|
|
206
|
+
icon = closeIcon,
|
|
207
|
+
headerColor = headerColor,
|
|
208
|
+
automationId = "btn_navigation_close",
|
|
209
|
+
onClick = ::onPressClose,
|
|
210
|
+
modifier = item,
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (horizontal) {
|
|
215
|
+
Row(horizontalArrangement = Arrangement.spacedBy(Spacing.M), verticalAlignment = Alignment.CenterVertically) {
|
|
216
|
+
controls()
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
Column(verticalArrangement = Arrangement.spacedBy(Spacing.M)) { controls() }
|
|
220
|
+
}
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
147
224
|
Row(
|
|
148
225
|
verticalAlignment = Alignment.CenterVertically
|
|
149
226
|
) {
|
|
@@ -167,20 +244,12 @@ internal fun Toolkit(
|
|
|
167
244
|
.clip(shape = RoundedCornerShape(14.dp))
|
|
168
245
|
.background(headerColor.backgroundButton)
|
|
169
246
|
) {
|
|
170
|
-
if (
|
|
247
|
+
if (useHelpCenter) {
|
|
171
248
|
Icon(
|
|
172
249
|
source = "help_center",
|
|
173
250
|
size = 20.dp,
|
|
174
251
|
color = headerColor.tintIconColor,
|
|
175
|
-
modifier = Modifier.padding(4.dp).activeOpacityClickable
|
|
176
|
-
// AI-GENERATED START: open support through the typed action
|
|
177
|
-
api?.let { client ->
|
|
178
|
-
requests.launch({ callback ->
|
|
179
|
-
client.performToolAction(ToolAction.SUPPORT, context.appCode, callback)
|
|
180
|
-
}) {}
|
|
181
|
-
}
|
|
182
|
-
// AI-GENERATED END: open support through the typed action
|
|
183
|
-
}
|
|
252
|
+
modifier = Modifier.padding(4.dp).activeOpacityClickable(onClick = ::onPressHelpCenter)
|
|
184
253
|
)
|
|
185
254
|
Box(
|
|
186
255
|
modifier = Modifier
|
|
@@ -190,14 +259,10 @@ internal fun Toolkit(
|
|
|
190
259
|
)
|
|
191
260
|
}
|
|
192
261
|
Icon(
|
|
193
|
-
source =
|
|
262
|
+
source = closeIcon,
|
|
194
263
|
size = 20.dp,
|
|
195
264
|
color = headerColor.tintIconColor,
|
|
196
|
-
modifier = Modifier.padding(4.dp).activeOpacityClickable
|
|
197
|
-
// AI-GENERATED START: preserve dismiss-all without selecting Home
|
|
198
|
-
api?.dismiss(DismissTarget.All) {}
|
|
199
|
-
// AI-GENERATED END: preserve dismiss-all without selecting Home
|
|
200
|
-
}
|
|
265
|
+
modifier = Modifier.padding(4.dp).activeOpacityClickable(onClick = ::onPressClose)
|
|
201
266
|
)
|
|
202
267
|
}
|
|
203
268
|
}
|
|
@@ -205,16 +270,36 @@ internal fun Toolkit(
|
|
|
205
270
|
|
|
206
271
|
|
|
207
272
|
|
|
273
|
+
// Colors follow the enclosing header, so it fits a `HeaderRight.Custom` next to the toolkit.
|
|
274
|
+
@Composable
|
|
275
|
+
fun NavigationButton(
|
|
276
|
+
icon: String,
|
|
277
|
+
onClick: () -> Unit,
|
|
278
|
+
showBadge: Boolean = false,
|
|
279
|
+
disabled: Boolean = false,
|
|
280
|
+
modifier: Modifier = Modifier,
|
|
281
|
+
) {
|
|
282
|
+
NavigationButton(
|
|
283
|
+
disabled = disabled,
|
|
284
|
+
icon = icon,
|
|
285
|
+
showBadge = showBadge,
|
|
286
|
+
onClick = onClick,
|
|
287
|
+
headerColor = LocalHeaderColor.current ?: getHeaderColor(HeaderType.Default(), 1f, null, Colors.black_17),
|
|
288
|
+
modifier = modifier,
|
|
289
|
+
)
|
|
290
|
+
}
|
|
291
|
+
|
|
208
292
|
@Composable
|
|
209
293
|
internal fun NavigationButton(
|
|
210
294
|
disabled: Boolean,
|
|
211
295
|
icon: String,
|
|
212
296
|
showBadge: Boolean? = false,
|
|
213
297
|
onClick: () -> Unit,
|
|
214
|
-
headerColor: HeaderColor
|
|
298
|
+
headerColor: HeaderColor,
|
|
299
|
+
modifier: Modifier = Modifier,
|
|
215
300
|
) {
|
|
216
301
|
Box(
|
|
217
|
-
modifier =
|
|
302
|
+
modifier = modifier
|
|
218
303
|
.size(28.dp)
|
|
219
304
|
.activeOpacityClickable(enabled = !disabled, onClick = onClick)
|
|
220
305
|
) {
|
|
@@ -287,7 +372,9 @@ sealed interface HeaderRight {
|
|
|
287
372
|
val useMore: Boolean = false,
|
|
288
373
|
val useSystemTools: Boolean = true,
|
|
289
374
|
val tools: List<ToolGroup> = emptyList(),
|
|
290
|
-
val toolCallback: ((String) -> Unit)? = { _ -> }
|
|
375
|
+
val toolCallback: ((String) -> Unit)? = { _ -> },
|
|
376
|
+
// Close dismisses only this mini app instead of returning home.
|
|
377
|
+
val useCloseIcon: Boolean = false,
|
|
291
378
|
) : HeaderRight
|
|
292
379
|
}
|
|
293
380
|
|
|
@@ -8,13 +8,13 @@ import androidx.compose.ui.text.style.TextAlign
|
|
|
8
8
|
import androidx.compose.ui.text.style.TextOverflow
|
|
9
9
|
import androidx.compose.ui.unit.sp
|
|
10
10
|
import androidx.compose.ui.zIndex
|
|
11
|
+
import vn.momo.kits.application.DesignSystemKits
|
|
11
12
|
import vn.momo.kits.components.Text
|
|
12
|
-
import vn.momo.kits.const.KitDesignSystem
|
|
13
13
|
import vn.momo.kits.const.Typography
|
|
14
14
|
import vn.momo.kits.modifier.setAutomationId
|
|
15
15
|
|
|
16
16
|
internal val headerFontFamily: String?
|
|
17
|
-
get() = if (
|
|
17
|
+
get() = if (DesignSystemKits.getConfigs().isWhiteList) "MoMoTrustDisplay" else null
|
|
18
18
|
|
|
19
19
|
@Composable
|
|
20
20
|
internal fun HeaderTitle(
|
|
@@ -32,7 +32,6 @@ import vn.momo.kits.const.Typography
|
|
|
32
32
|
import vn.momo.kits.const.getFontFamily
|
|
33
33
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
34
34
|
import vn.momo.kits.navigation.component.HeaderTitle.User
|
|
35
|
-
import vn.momo.kits.platform.getScreenDimensions
|
|
36
35
|
|
|
37
36
|
@Composable
|
|
38
37
|
internal fun HeaderUser(
|
|
@@ -44,7 +43,7 @@ internal fun HeaderUser(
|
|
|
44
43
|
return TitleUserShimmer()
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
val maxWidth =
|
|
46
|
+
val maxWidth = headerTitleMaxWidth()
|
|
48
47
|
|
|
49
48
|
Row(
|
|
50
49
|
modifier = Modifier
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// File: HelpCenterRequest.kt
|
|
2
|
+
// Created At: 2026-10-19 +07:00
|
|
3
|
+
// Created By: AI | AI Agent: Codex | Model: GPT-6
|
|
4
|
+
// AI-GENERATED START: preserve the toolkit Help Center payload in the typed request
|
|
5
|
+
package vn.momo.kits.navigation.component
|
|
6
|
+
|
|
7
|
+
import kotlinx.serialization.json.JsonArray
|
|
8
|
+
import kotlinx.serialization.json.JsonElement
|
|
9
|
+
import kotlinx.serialization.json.JsonNull
|
|
10
|
+
import kotlinx.serialization.json.JsonObject
|
|
11
|
+
import kotlinx.serialization.json.JsonPrimitive
|
|
12
|
+
import vn.momo.kits.application.MiniAppContext
|
|
13
|
+
import vn.momo.maxapi.contract.navigation.model.BottomSheetRequest
|
|
14
|
+
import vn.momo.maxapi.contract.navigation.model.HelpCenterContext
|
|
15
|
+
|
|
16
|
+
internal fun MiniAppContext.helpCenterRequest() = BottomSheetRequest.HelpCenter(
|
|
17
|
+
HelpCenterContext(
|
|
18
|
+
appId = appId,
|
|
19
|
+
code = appCode,
|
|
20
|
+
name = appName?.helpCenterJson(),
|
|
21
|
+
icon = appIcon,
|
|
22
|
+
description = description?.helpCenterJson(),
|
|
23
|
+
),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
private fun Any?.helpCenterJson(): JsonElement = when (this) {
|
|
27
|
+
null -> JsonNull
|
|
28
|
+
is JsonElement -> this
|
|
29
|
+
is String -> JsonPrimitive(this)
|
|
30
|
+
is Boolean -> JsonPrimitive(this)
|
|
31
|
+
is Number -> JsonPrimitive(this)
|
|
32
|
+
is Map<*, *> -> JsonObject(entries.associate { (key, value) -> key.toString() to value.helpCenterJson() })
|
|
33
|
+
is Iterable<*> -> JsonArray(map { it.helpCenterJson() })
|
|
34
|
+
is Array<*> -> JsonArray(map { it.helpCenterJson() })
|
|
35
|
+
else -> JsonPrimitive(toString())
|
|
36
|
+
}
|
|
37
|
+
// AI-GENERATED END: preserve the toolkit Help Center payload in the typed request
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
package vn.momo.kits.navigation.component
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.border
|
|
5
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
6
|
+
import androidx.compose.foundation.layout.Box
|
|
7
|
+
import androidx.compose.foundation.layout.BoxScope
|
|
8
|
+
import androidx.compose.foundation.layout.Column
|
|
9
|
+
import androidx.compose.foundation.layout.Row
|
|
10
|
+
import androidx.compose.foundation.layout.Spacer
|
|
11
|
+
import androidx.compose.foundation.layout.absolutePadding
|
|
12
|
+
import androidx.compose.foundation.layout.offset
|
|
13
|
+
import androidx.compose.foundation.layout.size
|
|
14
|
+
import androidx.compose.foundation.layout.width
|
|
15
|
+
import androidx.compose.foundation.shape.CircleShape
|
|
16
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
17
|
+
import androidx.compose.runtime.Composable
|
|
18
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
19
|
+
import androidx.compose.ui.AbsoluteAlignment
|
|
20
|
+
import androidx.compose.ui.Alignment
|
|
21
|
+
import androidx.compose.ui.Modifier
|
|
22
|
+
import androidx.compose.ui.draw.clip
|
|
23
|
+
import androidx.compose.ui.draw.shadow
|
|
24
|
+
import androidx.compose.ui.graphics.Shape
|
|
25
|
+
import androidx.compose.ui.layout.layout
|
|
26
|
+
import androidx.compose.ui.unit.Dp
|
|
27
|
+
import androidx.compose.ui.unit.constrainHeight
|
|
28
|
+
import androidx.compose.ui.unit.dp
|
|
29
|
+
import vn.momo.kits.components.BadgeDot
|
|
30
|
+
import vn.momo.kits.components.DotSize
|
|
31
|
+
import vn.momo.kits.components.Icon
|
|
32
|
+
import vn.momo.kits.const.Colors
|
|
33
|
+
import vn.momo.kits.const.Radius
|
|
34
|
+
import vn.momo.kits.const.Spacing
|
|
35
|
+
import vn.momo.kits.layout.DuoMetrics
|
|
36
|
+
import vn.momo.kits.layout.SideBarEdge
|
|
37
|
+
import vn.momo.kits.layout.getScreenLayout
|
|
38
|
+
import vn.momo.kits.modifier.activeOpacityClickable
|
|
39
|
+
import vn.momo.kits.modifier.conditional
|
|
40
|
+
import vn.momo.kits.modifier.setAutomationId
|
|
41
|
+
import vn.momo.kits.navigation.LocalNavigator
|
|
42
|
+
import vn.momo.kits.navigation.LocalOptions
|
|
43
|
+
import vn.momo.kits.navigation.bottomtab.LocalDuoBarCompression
|
|
44
|
+
|
|
45
|
+
private val SIDE_BAR_ICON_SIZE = 24.dp
|
|
46
|
+
|
|
47
|
+
// iPhone Duo: back and toolkit leave the bar for a column in the side safe area, as the system bars do.
|
|
48
|
+
// bottomInset is what footer / tab bar / home indicator take from the bottom, gap included; the column stays above it.
|
|
49
|
+
@Composable
|
|
50
|
+
internal fun BoxScope.HeaderSideBar(bottomInset: Dp, modifier: Modifier = Modifier) {
|
|
51
|
+
val options = LocalOptions.current
|
|
52
|
+
val navigator = LocalNavigator.current
|
|
53
|
+
val screenLayout = getScreenLayout()
|
|
54
|
+
val sideBar = screenLayout.sideBar ?: return
|
|
55
|
+
val toolkit = options.headerRight as? HeaderRight.Toolkit
|
|
56
|
+
val empty = options.headerType == HeaderType.None || (options.hiddenBack && toolkit == null)
|
|
57
|
+
val compression = LocalDuoBarCompression.current
|
|
58
|
+
val fullHeight = if (empty) 0.dp else DuoMetrics.controlSize + (toolkit?.let { Spacing.M + toolkitColumnHeight(it) } ?: 0.dp)
|
|
59
|
+
LaunchedEffect(fullHeight, compression.setToolbarHeight) { compression.setToolbarHeight(fullHeight) }
|
|
60
|
+
if (empty) return
|
|
61
|
+
|
|
62
|
+
val headerColor = getHeaderColor(options.headerType, headerOpacity(), options.tintColor, Colors.black_17)
|
|
63
|
+
val isRight = sideBar.edge == SideBarEdge.RIGHT
|
|
64
|
+
|
|
65
|
+
Column(
|
|
66
|
+
modifier = modifier
|
|
67
|
+
.align(if (isRight) AbsoluteAlignment.TopRight else AbsoluteAlignment.TopLeft)
|
|
68
|
+
.width(sideBar.width)
|
|
69
|
+
.layout { measurable, constraints ->
|
|
70
|
+
val placeable = measurable.measure(constraints.copy(minHeight = 0))
|
|
71
|
+
val maxY = screenLayout.height.roundToPx() - bottomInset.roundToPx() - placeable.height
|
|
72
|
+
val y = minOf(sideBar.top.roundToPx(), maxY).coerceAtLeast(0)
|
|
73
|
+
layout(placeable.width, constraints.constrainHeight(y + placeable.height)) {
|
|
74
|
+
placeable.place(0, y)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
.absolutePadding(left = if (isRight) Spacing.M else 0.dp, right = if (isRight) 0.dp else Spacing.M),
|
|
78
|
+
horizontalAlignment = if (isRight) AbsoluteAlignment.Left else AbsoluteAlignment.Right,
|
|
79
|
+
verticalArrangement = Arrangement.spacedBy(Spacing.M),
|
|
80
|
+
) {
|
|
81
|
+
if (options.hiddenBack) {
|
|
82
|
+
// keep the slot so the toolkit does not jump between root and pushed screens
|
|
83
|
+
Spacer(Modifier.size(DuoMetrics.controlSize))
|
|
84
|
+
} else {
|
|
85
|
+
SideBarButton(
|
|
86
|
+
icon = "arrow-back",
|
|
87
|
+
headerColor = headerColor,
|
|
88
|
+
automationId = "btn_navigation_back",
|
|
89
|
+
onClick = { navigator.onBackSafe { } },
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
when {
|
|
93
|
+
toolkit == null || compression.toolbarHidden -> Unit
|
|
94
|
+
// HIG "Toolbar compressed": tools fold into one button, distinct from the useMore icon
|
|
95
|
+
compression.toolbarCompressed -> SideBarButton(
|
|
96
|
+
icon = "navigation_menu",
|
|
97
|
+
headerColor = headerColor,
|
|
98
|
+
showBadge = toolkit.tools.any { group -> group.items.any { it.showBadge } },
|
|
99
|
+
automationId = "btn_navigation_toolbar",
|
|
100
|
+
onClick = compression.expandToolbar,
|
|
101
|
+
)
|
|
102
|
+
else -> Toolkit(
|
|
103
|
+
headerRight = toolkit,
|
|
104
|
+
tintColor = options.tintColor,
|
|
105
|
+
headerColor = headerColor,
|
|
106
|
+
layout = ToolkitLayout.SideBarColumn,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
@Composable
|
|
113
|
+
internal fun SideBarButton(
|
|
114
|
+
icon: String,
|
|
115
|
+
headerColor: HeaderColor,
|
|
116
|
+
onClick: () -> Unit,
|
|
117
|
+
enabled: Boolean = true,
|
|
118
|
+
showBadge: Boolean = false,
|
|
119
|
+
automationId: String? = null,
|
|
120
|
+
) {
|
|
121
|
+
Box {
|
|
122
|
+
Box(
|
|
123
|
+
modifier = Modifier
|
|
124
|
+
.size(DuoMetrics.controlSize)
|
|
125
|
+
.sideBarGlass(headerColor, CircleShape)
|
|
126
|
+
.activeOpacityClickable(enabled = enabled, onClick = onClick)
|
|
127
|
+
.conditional(automationId != null) { setAutomationId(automationId.orEmpty()) },
|
|
128
|
+
contentAlignment = Alignment.Center,
|
|
129
|
+
) {
|
|
130
|
+
Icon(source = icon, size = SIDE_BAR_ICON_SIZE, color = headerColor.tintIconColor)
|
|
131
|
+
}
|
|
132
|
+
if (showBadge) {
|
|
133
|
+
BadgeDot(
|
|
134
|
+
size = DotSize.Small,
|
|
135
|
+
modifier = Modifier.align(Alignment.TopEnd).offset(x = -Spacing.XXS, y = Spacing.XXS),
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Capsule grouping related controls, one item per 50pt: vertical in the side bar, horizontal on a header row.
|
|
142
|
+
@Composable
|
|
143
|
+
internal fun SideBarGroup(
|
|
144
|
+
headerColor: HeaderColor,
|
|
145
|
+
itemCount: Int,
|
|
146
|
+
horizontal: Boolean = false,
|
|
147
|
+
content: @Composable (itemModifier: Modifier) -> Unit,
|
|
148
|
+
) {
|
|
149
|
+
val length = if (itemCount > 1) DuoMetrics.itemPitch * itemCount else DuoMetrics.controlSize
|
|
150
|
+
val item = length / itemCount.coerceAtLeast(1)
|
|
151
|
+
val glass = Modifier.sideBarGlass(headerColor, RoundedCornerShape(Radius.XL))
|
|
152
|
+
if (horizontal) {
|
|
153
|
+
Row(Modifier.size(width = length, height = DuoMetrics.controlSize).then(glass)) {
|
|
154
|
+
content(Modifier.size(width = item, height = DuoMetrics.controlSize))
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
Column(Modifier.size(width = DuoMetrics.controlSize, height = length).then(glass)) {
|
|
158
|
+
content(Modifier.size(width = DuoMetrics.controlSize, height = item))
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
@Composable
|
|
164
|
+
internal fun SideBarGroupItem(
|
|
165
|
+
icon: String,
|
|
166
|
+
headerColor: HeaderColor,
|
|
167
|
+
automationId: String,
|
|
168
|
+
onClick: () -> Unit,
|
|
169
|
+
modifier: Modifier = Modifier,
|
|
170
|
+
) {
|
|
171
|
+
Box(
|
|
172
|
+
modifier = modifier
|
|
173
|
+
.activeOpacityClickable(onClick = onClick)
|
|
174
|
+
.setAutomationId(automationId),
|
|
175
|
+
contentAlignment = Alignment.Center,
|
|
176
|
+
) {
|
|
177
|
+
Icon(source = icon, size = SIDE_BAR_ICON_SIZE, color = headerColor.tintIconColor)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Dark glass under light icons (banner headers), light glass otherwise. The light one is opaque:
|
|
182
|
+
// Compose has no backdrop blur and a translucent fill shows its own shadow through (RN uses 85% white).
|
|
183
|
+
private fun Modifier.sideBarGlass(headerColor: HeaderColor, shape: Shape): Modifier =
|
|
184
|
+
if (headerColor.tintIconColor == Colors.black_01) {
|
|
185
|
+
clip(shape)
|
|
186
|
+
.background(Colors.black_20.copy(alpha = 0.5f))
|
|
187
|
+
.border(0.5.dp, Colors.black_01.copy(alpha = 0.2f), shape)
|
|
188
|
+
} else {
|
|
189
|
+
shadow(3.dp, shape)
|
|
190
|
+
.background(Colors.black_01)
|
|
191
|
+
.border(0.5.dp, Colors.black_20.copy(alpha = 0.08f), shape)
|
|
192
|
+
}
|
|
@@ -23,6 +23,14 @@ internal class ToolkitRequests {
|
|
|
23
23
|
if (!active) created.cancel() else if (!completed) handles.add(created)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// AI-GENERATED START: retain subscriptions across nonterminal sheet events
|
|
27
|
+
fun <T> observe(request: (ApiCallback<T>) -> ApiRequestHandle, onResult: ApiCallback<T>) {
|
|
28
|
+
if (!active) return
|
|
29
|
+
val handle = request { result -> if (active) onResult(result) }
|
|
30
|
+
if (active) handles.add(handle) else handle.cancel()
|
|
31
|
+
}
|
|
32
|
+
// AI-GENERATED END: retain subscriptions across nonterminal sheet events
|
|
33
|
+
|
|
26
34
|
fun close() {
|
|
27
35
|
active = false
|
|
28
36
|
val pending = handles.toList()
|
|
@@ -14,6 +14,9 @@ data class ScreenDimension(
|
|
|
14
14
|
|
|
15
15
|
expect fun getPlatformName(): String
|
|
16
16
|
|
|
17
|
+
// iPhone idiom only; iPad windows and Android never get the iPhone / iPhone Duo layout.
|
|
18
|
+
expect fun isPhoneIdiom(): Boolean
|
|
19
|
+
|
|
17
20
|
@Composable
|
|
18
21
|
expect fun getScreenDimensions(): ScreenDimension
|
|
19
22
|
|