@momo-kits/native-kits 0.167.1-duo.2-debug → 0.167.1-duo.20-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 +36 -8
- package/README.md +2 -2
- package/compose/build.gradle.kts +15 -1
- package/compose/build.gradle.kts.backup +14 -0
- package/compose/compose.podspec +1 -1
- 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/LiteScreen.kt +168 -52
- 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 +24 -14
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Image.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 +53 -26
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +12 -2
- 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 +4 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +10 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +3 -17
- package/compose/src/commonMain/kotlin/vn/momo/kits/layout/ScreenLayout.kt +173 -21
- 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/StackScreen.kt +153 -48
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTab.kt +114 -71
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +8 -9
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoBarCompression.kt +98 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/DuoTabBar.kt +312 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/Header.kt +84 -18
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderBackground.kt +33 -13
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderJourney.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderRight.kt +51 -19
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderTitle.kt +4 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SideBar.kt +46 -23
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +2 -1
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/ReservedRegions.kt +13 -0
- package/compose/src/iosMain/kotlin/vn/momo/kits/const/HostFont.ios.kt +38 -0
- 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 +94 -0
- package/gradle.properties +1 -1
- package/ios/Application/ApplicationEnvironment.swift +17 -0
- package/ios/Application/MaxApi.swift +12 -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 +0 -1
- 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 +160 -147
- package/ios/Input/InputPhoneNumber.swift +12 -9
- package/ios/Input/InputSearch.swift +102 -75
- package/ios/Input/InputTextArea.swift +128 -145
- package/ios/InputDropDown/InputDropDown.swift +10 -7
- package/ios/InputMoney/InputMoney.swift +33 -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 +380 -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 +128 -9
- package/ios/Navigation/BottomTab/BottomTab.swift +185 -0
- package/ios/{Application/Navigation → Navigation}/BottomTab/BottomTabBar.swift +15 -7
- package/ios/Navigation/BottomTab/DuoBarCompression.swift +105 -0
- package/ios/Navigation/BottomTab/DuoTabBar.swift +304 -0
- package/ios/{Application → Navigation/Component}/FloatingButton.swift +14 -3
- package/ios/Navigation/Component/Header.swift +531 -0
- package/ios/Navigation/Component/HeaderBackground.swift +133 -0
- package/ios/Navigation/Component/HeaderJourney.swift +73 -0
- package/ios/Navigation/Component/HeaderLocation.swift +48 -0
- package/ios/Navigation/Component/HeaderRight.swift +377 -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 +5 -6
- package/ios/Navigation/HostBackGestureBridge.swift +78 -0
- package/ios/{Application/Navigation/Overplay → Navigation}/ModalScreen.swift +5 -2
- package/ios/Navigation/Navigation.swift +204 -0
- package/ios/Navigation/NavigationContainer.swift +194 -0
- package/ios/{Application/Navigation → Navigation}/Navigator.swift +177 -91
- package/ios/Navigation/StackScreen.swift +820 -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 -136
- package/ios/Popup/PopupInput.swift +10 -13
- package/ios/Popup/PopupLayout.swift +140 -0
- package/ios/Popup/PopupNotify.swift +56 -73
- 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 +6 -1
- 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 +106 -54
- package/ios/Title/Title.swift +6 -17
- package/ios/Tooltip/Tooltip.swift +29 -14
- package/ios/Typography/Text.swift +61 -19
- package/ios/Uploader/Uploader.swift +3 -1
- package/ios/native-kits.podspec +2 -1
- package/ios-demo/MoMoUIKitsDemo.podspec +1 -1
- 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 +1 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/IPhoneDuoDemo.swift +173 -0
- package/ios-demo/Sources/MoMoUIKitsDemo/SampleDemos/NavigationDemo.swift +77 -1
- package/package.json +1 -1
- 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/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/{Application/Navigation → Navigation}/BottomTab/CurvedContainer.swift +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
package vn.momo.kits.navigation.bottomtab
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.runtime.Stable
|
|
5
|
+
import androidx.compose.runtime.compositionLocalOf
|
|
6
|
+
import androidx.compose.runtime.getValue
|
|
7
|
+
import androidx.compose.runtime.mutableStateOf
|
|
8
|
+
import androidx.compose.runtime.remember
|
|
9
|
+
import androidx.compose.runtime.setValue
|
|
10
|
+
import androidx.compose.ui.unit.Dp
|
|
11
|
+
import vn.momo.kits.const.Spacing
|
|
12
|
+
import vn.momo.kits.layout.DuoMetrics
|
|
13
|
+
import vn.momo.kits.layout.ScreenLayout
|
|
14
|
+
|
|
15
|
+
// iPhone Duo: which side bar bar starts compressed when the strip cannot hold both whole.
|
|
16
|
+
enum class DuoBarCompressionBehavior { TOOLBAR, TAB_BAR }
|
|
17
|
+
|
|
18
|
+
private enum class DuoBar { TOOLBAR, TAB_BAR }
|
|
19
|
+
|
|
20
|
+
@Stable
|
|
21
|
+
internal class DuoBarCompression(
|
|
22
|
+
val toolbarCompressed: Boolean = false,
|
|
23
|
+
val tabBarCompressed: Boolean = false,
|
|
24
|
+
// the open rail needs the toolbar button slot too: only Back stays
|
|
25
|
+
val toolbarHidden: Boolean = false,
|
|
26
|
+
val expandToolbar: () -> Unit = {},
|
|
27
|
+
val expandTabBar: () -> Unit = {},
|
|
28
|
+
// a rail opened from the compressed state closes once a tab is picked
|
|
29
|
+
val onTabSelected: () -> Unit = {},
|
|
30
|
+
// side bar columns report their full height so the tab bar knows what is left
|
|
31
|
+
val setToolbarHeight: (Dp) -> Unit = {},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
internal val LocalDuoBarCompression = compositionLocalOf { DuoBarCompression() }
|
|
35
|
+
|
|
36
|
+
// space kept between the top and the bottom groups (HIG Vertical controls)
|
|
37
|
+
internal val DUO_GROUP_GAP = Spacing.M
|
|
38
|
+
// Back, then the right toolkit: help + home capsule
|
|
39
|
+
private val DEFAULT_TOOLBAR_HEIGHT = DuoMetrics.controlSize + Spacing.M + DuoMetrics.itemPitch * 2
|
|
40
|
+
// Back, then the single button the toolbar folds into
|
|
41
|
+
private val COMPRESSED_TOOLBAR_HEIGHT = DuoMetrics.controlSize * 2 + Spacing.M
|
|
42
|
+
// Back alone, while a rail opened from the compressed state takes the toolbar slot
|
|
43
|
+
private val BACK_ONLY_HEIGHT = DuoMetrics.controlSize
|
|
44
|
+
|
|
45
|
+
// Port of RN DuoBarCompression (HIG, Designing for iPhone Duo: Vertical controls):
|
|
46
|
+
// - both fit: both stay whole
|
|
47
|
+
// - one fits whole beside the other compressed: one is compressed, tapping it swaps them
|
|
48
|
+
// - otherwise both rest compressed; tapping one opens it for a moment (the rail closes
|
|
49
|
+
// again once a tab is picked)
|
|
50
|
+
@Composable
|
|
51
|
+
internal fun rememberDuoBarCompression(
|
|
52
|
+
behavior: DuoBarCompressionBehavior,
|
|
53
|
+
tabCount: Int,
|
|
54
|
+
hasQr: Boolean,
|
|
55
|
+
layout: ScreenLayout,
|
|
56
|
+
): DuoBarCompression {
|
|
57
|
+
var toolbarHeight by remember { mutableStateOf(DEFAULT_TOOLBAR_HEIGHT) }
|
|
58
|
+
var expanded by remember(behavior) {
|
|
59
|
+
mutableStateOf(if (behavior == DuoBarCompressionBehavior.TOOLBAR) DuoBar.TAB_BAR else DuoBar.TOOLBAR)
|
|
60
|
+
}
|
|
61
|
+
var opened by remember { mutableStateOf<DuoBar?>(null) }
|
|
62
|
+
val setToolbarHeight = remember { { height: Dp -> toolbarHeight = height } }
|
|
63
|
+
val sideBar = layout.sideBar
|
|
64
|
+
|
|
65
|
+
return remember(sideBar, layout.height, tabCount, hasQr, toolbarHeight, expanded, opened) {
|
|
66
|
+
if (sideBar == null) return@remember DuoBarCompression()
|
|
67
|
+
val room = layout.height - sideBar.bottom - sideBar.top - DUO_GROUP_GAP
|
|
68
|
+
val tabBar = duoRailHeight(tabCount, hasQr)
|
|
69
|
+
val compressedTabBar = duoRailHeight(1, hasQr, compressed = true)
|
|
70
|
+
fun fits(toolbar: Dp, tabs: Dp) = toolbar + tabs <= room
|
|
71
|
+
|
|
72
|
+
when {
|
|
73
|
+
fits(toolbarHeight, tabBar) -> DuoBarCompression(setToolbarHeight = setToolbarHeight)
|
|
74
|
+
fits(COMPRESSED_TOOLBAR_HEIGHT, tabBar) && fits(toolbarHeight, compressedTabBar) -> DuoBarCompression(
|
|
75
|
+
toolbarCompressed = expanded == DuoBar.TAB_BAR,
|
|
76
|
+
tabBarCompressed = expanded == DuoBar.TOOLBAR,
|
|
77
|
+
expandToolbar = { expanded = DuoBar.TOOLBAR },
|
|
78
|
+
expandTabBar = { expanded = DuoBar.TAB_BAR },
|
|
79
|
+
setToolbarHeight = setToolbarHeight,
|
|
80
|
+
)
|
|
81
|
+
opened == DuoBar.TAB_BAR && fits(BACK_ONLY_HEIGHT, tabBar) -> DuoBarCompression(
|
|
82
|
+
toolbarCompressed = true,
|
|
83
|
+
toolbarHidden = true,
|
|
84
|
+
expandToolbar = { opened = DuoBar.TOOLBAR },
|
|
85
|
+
expandTabBar = { opened = DuoBar.TAB_BAR },
|
|
86
|
+
onTabSelected = { opened = null },
|
|
87
|
+
setToolbarHeight = setToolbarHeight,
|
|
88
|
+
)
|
|
89
|
+
else -> DuoBarCompression(
|
|
90
|
+
toolbarCompressed = opened != DuoBar.TOOLBAR,
|
|
91
|
+
tabBarCompressed = true,
|
|
92
|
+
expandToolbar = { opened = DuoBar.TOOLBAR },
|
|
93
|
+
expandTabBar = { opened = DuoBar.TAB_BAR },
|
|
94
|
+
setToolbarHeight = setToolbarHeight,
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
package vn.momo.kits.navigation.bottomtab
|
|
2
|
+
|
|
3
|
+
import androidx.compose.animation.core.Spring
|
|
4
|
+
import androidx.compose.animation.core.animateDpAsState
|
|
5
|
+
import androidx.compose.animation.core.spring
|
|
6
|
+
import androidx.compose.foundation.background
|
|
7
|
+
import androidx.compose.foundation.border
|
|
8
|
+
import androidx.compose.foundation.layout.Arrangement
|
|
9
|
+
import androidx.compose.foundation.layout.Box
|
|
10
|
+
import androidx.compose.foundation.layout.BoxScope
|
|
11
|
+
import androidx.compose.foundation.layout.BoxWithConstraints
|
|
12
|
+
import androidx.compose.foundation.layout.Column
|
|
13
|
+
import androidx.compose.foundation.layout.Row
|
|
14
|
+
import androidx.compose.foundation.layout.RowScope
|
|
15
|
+
import androidx.compose.foundation.layout.absolutePadding
|
|
16
|
+
import androidx.compose.foundation.layout.fillMaxHeight
|
|
17
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
18
|
+
import androidx.compose.foundation.layout.height
|
|
19
|
+
import androidx.compose.foundation.layout.offset
|
|
20
|
+
import androidx.compose.foundation.layout.padding
|
|
21
|
+
import androidx.compose.foundation.layout.size
|
|
22
|
+
import androidx.compose.foundation.layout.width
|
|
23
|
+
import androidx.compose.foundation.shape.CircleShape
|
|
24
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
25
|
+
import androidx.compose.runtime.Composable
|
|
26
|
+
import androidx.compose.runtime.compositionLocalOf
|
|
27
|
+
import androidx.compose.runtime.getValue
|
|
28
|
+
import androidx.compose.ui.AbsoluteAlignment
|
|
29
|
+
import androidx.compose.ui.Alignment
|
|
30
|
+
import androidx.compose.ui.Modifier
|
|
31
|
+
import androidx.compose.ui.draw.clip
|
|
32
|
+
import androidx.compose.ui.draw.shadow
|
|
33
|
+
import androidx.compose.ui.graphics.Color
|
|
34
|
+
import androidx.compose.ui.graphics.Shape
|
|
35
|
+
import androidx.compose.ui.text.style.TextOverflow
|
|
36
|
+
import androidx.compose.ui.unit.Dp
|
|
37
|
+
import androidx.compose.ui.unit.dp
|
|
38
|
+
import vn.momo.kits.components.Badge
|
|
39
|
+
import vn.momo.kits.components.BadgeDot
|
|
40
|
+
import vn.momo.kits.components.DotSize
|
|
41
|
+
import vn.momo.kits.components.Icon
|
|
42
|
+
import vn.momo.kits.components.Text
|
|
43
|
+
import vn.momo.kits.const.AppTheme
|
|
44
|
+
import vn.momo.kits.const.Radius
|
|
45
|
+
import vn.momo.kits.const.Spacing
|
|
46
|
+
import vn.momo.kits.const.Typography
|
|
47
|
+
import vn.momo.kits.layout.DuoMetrics
|
|
48
|
+
import vn.momo.kits.layout.ScreenLayout
|
|
49
|
+
import vn.momo.kits.layout.SideBarEdge
|
|
50
|
+
import vn.momo.kits.modifier.noFeedbackClickable
|
|
51
|
+
import vn.momo.kits.modifier.setAutomationId
|
|
52
|
+
|
|
53
|
+
// Figma Fold-Design-System MT1/MT2, mirrored from RN DuoTabBar.tsx.
|
|
54
|
+
private val ITEM_SIZE = DuoMetrics.controlSize
|
|
55
|
+
private val ICON_SIZE = 28.dp
|
|
56
|
+
private val QR_ICON_SIZE = 24.dp
|
|
57
|
+
private val RAIL_PADDING = 6.dp
|
|
58
|
+
private val RAIL_GAP = Spacing.XXS
|
|
59
|
+
// selection bubble overflows the 48 rail cell by 5 above and below
|
|
60
|
+
private val RAIL_BUBBLE_WIDTH = 44.dp
|
|
61
|
+
private val RAIL_BUBBLE_HEIGHT = 58.dp
|
|
62
|
+
private val FLOATING_HEIGHT = 62.dp
|
|
63
|
+
private val FLOATING_PADDING = Spacing.XS
|
|
64
|
+
private val FLOATING_QR_SIZE = 44.dp
|
|
65
|
+
// floating bar spans 6 of the 8 portrait grid columns, like the content
|
|
66
|
+
private const val GRID_COLUMNS = 8
|
|
67
|
+
private const val GRID_SPAN = 6
|
|
68
|
+
// RN reanimated spring { damping: 18, stiffness: 220, mass: 0.8 }
|
|
69
|
+
private val BUBBLE_SPRING = spring<Dp>(dampingRatio = 0.68f, stiffness = 275f, visibilityThreshold = Dp(0.5f))
|
|
70
|
+
|
|
71
|
+
// Space from the window bottom the rail stack and its gap take, so the header side bar stays above it.
|
|
72
|
+
internal val LocalDuoRailReserve = compositionLocalOf { 0.dp }
|
|
73
|
+
|
|
74
|
+
// Rail plus the QR button under it; compressed, the rail is a single button.
|
|
75
|
+
internal fun duoRailHeight(itemCount: Int, hasQr: Boolean, compressed: Boolean = false): Dp {
|
|
76
|
+
val rail = if (compressed) ITEM_SIZE
|
|
77
|
+
else RAIL_PADDING * 2 + ITEM_SIZE * itemCount + RAIL_GAP * (itemCount - 1).coerceAtLeast(0)
|
|
78
|
+
return rail + if (hasQr) Spacing.M + ITEM_SIZE else 0.dp
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Space tab content leaves under the floating bar; 0 when the rail sits in the side strip or off Duo.
|
|
82
|
+
internal fun duoFloatingTabBarInset(layout: ScreenLayout): Dp =
|
|
83
|
+
if (layout.isIPhoneDuo && layout.sideBar == null) FLOATING_HEIGHT + Spacing.M + layout.insetBottom else 0.dp
|
|
84
|
+
|
|
85
|
+
// iPhone Duo tab bar: glass rail in the side bar, or a floating glass bar when unfolded portrait.
|
|
86
|
+
@Composable
|
|
87
|
+
internal fun BoxScope.DuoTabBar(
|
|
88
|
+
layout: ScreenLayout,
|
|
89
|
+
items: List<BottomTabItem>,
|
|
90
|
+
floatingButton: BottomTabFloatingButton?,
|
|
91
|
+
selectedIndex: Int,
|
|
92
|
+
onTabSelected: (Int) -> Unit,
|
|
93
|
+
compression: DuoBarCompression = DuoBarCompression(),
|
|
94
|
+
) {
|
|
95
|
+
if (layout.sideBar != null) {
|
|
96
|
+
DuoRailTabBar(layout, items, floatingButton, selectedIndex, onTabSelected, compression)
|
|
97
|
+
} else {
|
|
98
|
+
DuoFloatingTabBar(layout, items, floatingButton, selectedIndex, onTabSelected)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Vertical bar anchored to the bottom of the side bar, QR button below it (Figma MT1-C/E). When the
|
|
103
|
+
// side bar is short it can shrink to one button showing the current tab (HIG "Tab bar compressed");
|
|
104
|
+
// tapping it expands the rail and compresses the toolbar.
|
|
105
|
+
@Composable
|
|
106
|
+
private fun BoxScope.DuoRailTabBar(
|
|
107
|
+
layout: ScreenLayout,
|
|
108
|
+
items: List<BottomTabItem>,
|
|
109
|
+
floatingButton: BottomTabFloatingButton?,
|
|
110
|
+
selectedIndex: Int,
|
|
111
|
+
onTabSelected: (Int) -> Unit,
|
|
112
|
+
compression: DuoBarCompression,
|
|
113
|
+
) {
|
|
114
|
+
val sideBar = layout.sideBar ?: return
|
|
115
|
+
val isRight = sideBar.edge == SideBarEdge.RIGHT
|
|
116
|
+
|
|
117
|
+
Column(
|
|
118
|
+
modifier = Modifier
|
|
119
|
+
.align(if (isRight) AbsoluteAlignment.BottomRight else AbsoluteAlignment.BottomLeft)
|
|
120
|
+
.width(sideBar.width)
|
|
121
|
+
.padding(bottom = sideBar.bottom)
|
|
122
|
+
.absolutePadding(left = if (isRight) Spacing.M else 0.dp, right = if (isRight) 0.dp else Spacing.M),
|
|
123
|
+
horizontalAlignment = if (isRight) AbsoluteAlignment.Left else AbsoluteAlignment.Right,
|
|
124
|
+
verticalArrangement = Arrangement.spacedBy(Spacing.M),
|
|
125
|
+
) {
|
|
126
|
+
val current = items.getOrNull(selectedIndex)
|
|
127
|
+
if (compression.tabBarCompressed && current != null) {
|
|
128
|
+
Box(
|
|
129
|
+
modifier = Modifier
|
|
130
|
+
.size(ITEM_SIZE)
|
|
131
|
+
.tabBarMaterial(CircleShape)
|
|
132
|
+
.noFeedbackClickable(onClick = compression.expandTabBar)
|
|
133
|
+
.setAutomationId("bottom_tab_bar"),
|
|
134
|
+
contentAlignment = Alignment.Center,
|
|
135
|
+
) {
|
|
136
|
+
DuoTabIcon(current, selected = true)
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
Rail(items, selectedIndex) {
|
|
140
|
+
onTabSelected(it)
|
|
141
|
+
compression.onTabSelected()
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (floatingButton != null) {
|
|
145
|
+
QrButton(floatingButton, size = ITEM_SIZE)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Glass column of tab items with the sliding selection bubble.
|
|
151
|
+
@Composable
|
|
152
|
+
private fun Rail(items: List<BottomTabItem>, selectedIndex: Int, onItemClick: (Int) -> Unit) {
|
|
153
|
+
val bubbleOffset by animateDpAsState(
|
|
154
|
+
targetValue = (ITEM_SIZE + RAIL_GAP) * selectedIndex.coerceAtLeast(0),
|
|
155
|
+
animationSpec = BUBBLE_SPRING,
|
|
156
|
+
label = "DuoRailBubble",
|
|
157
|
+
)
|
|
158
|
+
Box(
|
|
159
|
+
Modifier
|
|
160
|
+
.width(ITEM_SIZE)
|
|
161
|
+
.tabBarMaterial(RoundedCornerShape(Radius.XL))
|
|
162
|
+
.setAutomationId("bottom_tab_bar")
|
|
163
|
+
) {
|
|
164
|
+
Box(
|
|
165
|
+
Modifier
|
|
166
|
+
.offset(
|
|
167
|
+
x = (ITEM_SIZE - RAIL_BUBBLE_WIDTH) / 2,
|
|
168
|
+
y = RAIL_PADDING - (RAIL_BUBBLE_HEIGHT - ITEM_SIZE) / 2 + bubbleOffset,
|
|
169
|
+
)
|
|
170
|
+
.size(RAIL_BUBBLE_WIDTH, RAIL_BUBBLE_HEIGHT)
|
|
171
|
+
.background(AppTheme.current.colors.background.selected, CircleShape)
|
|
172
|
+
)
|
|
173
|
+
Column(
|
|
174
|
+
modifier = Modifier.padding(vertical = RAIL_PADDING),
|
|
175
|
+
verticalArrangement = Arrangement.spacedBy(RAIL_GAP),
|
|
176
|
+
) {
|
|
177
|
+
items.forEachIndexed { index, item ->
|
|
178
|
+
Box(
|
|
179
|
+
modifier = Modifier
|
|
180
|
+
.size(ITEM_SIZE)
|
|
181
|
+
.noFeedbackClickable { onItemClick(index) },
|
|
182
|
+
contentAlignment = Alignment.Center,
|
|
183
|
+
) {
|
|
184
|
+
DuoTabIcon(item, selected = index == selectedIndex)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@Composable
|
|
192
|
+
private fun BoxScope.DuoFloatingTabBar(
|
|
193
|
+
layout: ScreenLayout,
|
|
194
|
+
items: List<BottomTabItem>,
|
|
195
|
+
floatingButton: BottomTabFloatingButton?,
|
|
196
|
+
selectedIndex: Int,
|
|
197
|
+
onTabSelected: (Int) -> Unit,
|
|
198
|
+
) {
|
|
199
|
+
val columnWidth = (layout.width - Spacing.M * 2 - Spacing.M * (GRID_COLUMNS - 1)) / GRID_COLUMNS
|
|
200
|
+
val barWidth = columnWidth * GRID_SPAN + Spacing.M * (GRID_SPAN - 1)
|
|
201
|
+
val useQr = floatingButton != null && items.size % 2 == 0
|
|
202
|
+
val middle = items.size / 2
|
|
203
|
+
val cellCount = items.size + if (useQr) 1 else 0
|
|
204
|
+
val visualIndex = if (useQr && selectedIndex >= middle) selectedIndex + 1 else selectedIndex
|
|
205
|
+
|
|
206
|
+
Box(
|
|
207
|
+
modifier = Modifier
|
|
208
|
+
.align(Alignment.BottomCenter)
|
|
209
|
+
.padding(bottom = layout.insetBottom + Spacing.M)
|
|
210
|
+
.width(barWidth)
|
|
211
|
+
.height(FLOATING_HEIGHT)
|
|
212
|
+
.tabBarMaterial(RoundedCornerShape(FLOATING_HEIGHT / 2))
|
|
213
|
+
.padding(FLOATING_PADDING)
|
|
214
|
+
.setAutomationId("bottom_tab_bar")
|
|
215
|
+
) {
|
|
216
|
+
BoxWithConstraints(Modifier.fillMaxSize()) {
|
|
217
|
+
val cellWidth = maxWidth / cellCount
|
|
218
|
+
val bubbleOffset by animateDpAsState(
|
|
219
|
+
targetValue = cellWidth * visualIndex.coerceAtLeast(0),
|
|
220
|
+
animationSpec = BUBBLE_SPRING,
|
|
221
|
+
label = "DuoFloatingBubble",
|
|
222
|
+
)
|
|
223
|
+
Box(
|
|
224
|
+
Modifier
|
|
225
|
+
.offset(x = bubbleOffset)
|
|
226
|
+
.width(cellWidth)
|
|
227
|
+
.fillMaxHeight()
|
|
228
|
+
.background(AppTheme.current.colors.background.selected, CircleShape)
|
|
229
|
+
)
|
|
230
|
+
Row(Modifier.fillMaxSize()) {
|
|
231
|
+
items.forEachIndexed { index, item ->
|
|
232
|
+
if (useQr && index == middle) {
|
|
233
|
+
Box(Modifier.weight(1f).fillMaxHeight(), contentAlignment = Alignment.Center) {
|
|
234
|
+
QrButton(floatingButton, size = FLOATING_QR_SIZE)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
FloatingCell(item, selected = index == selectedIndex) { onTabSelected(index) }
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@Composable
|
|
245
|
+
private fun RowScope.FloatingCell(item: BottomTabItem, selected: Boolean, onClick: () -> Unit) {
|
|
246
|
+
val colors = AppTheme.current.colors
|
|
247
|
+
Column(
|
|
248
|
+
modifier = Modifier
|
|
249
|
+
.weight(1f)
|
|
250
|
+
.fillMaxHeight()
|
|
251
|
+
.noFeedbackClickable(onClick = onClick),
|
|
252
|
+
horizontalAlignment = Alignment.CenterHorizontally,
|
|
253
|
+
verticalArrangement = Arrangement.spacedBy(Spacing.XXS, Alignment.CenterVertically),
|
|
254
|
+
) {
|
|
255
|
+
DuoTabIcon(item, selected)
|
|
256
|
+
Text(
|
|
257
|
+
text = item.label,
|
|
258
|
+
style = Typography.labelXsMedium,
|
|
259
|
+
color = if (selected) colors.primary else colors.text.default,
|
|
260
|
+
maxLines = 1,
|
|
261
|
+
overflow = TextOverflow.Ellipsis,
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@Composable
|
|
267
|
+
private fun DuoTabIcon(item: BottomTabItem, selected: Boolean) {
|
|
268
|
+
val colors = AppTheme.current.colors
|
|
269
|
+
val badgeLabel = item.badgeLabel?.takeUnless { it.toIntOrNull() == 0 }
|
|
270
|
+
Box {
|
|
271
|
+
Icon(
|
|
272
|
+
source = item.icon,
|
|
273
|
+
size = ICON_SIZE,
|
|
274
|
+
color = if (selected) colors.primary else colors.text.default,
|
|
275
|
+
)
|
|
276
|
+
when {
|
|
277
|
+
badgeLabel == null && !item.showDot -> Unit
|
|
278
|
+
badgeLabel.isNullOrEmpty() -> BadgeDot(
|
|
279
|
+
size = DotSize.Small,
|
|
280
|
+
modifier = Modifier.align(Alignment.TopEnd),
|
|
281
|
+
)
|
|
282
|
+
else -> Badge(
|
|
283
|
+
badgeLabel,
|
|
284
|
+
modifier = Modifier.align(Alignment.TopStart).offset(x = ICON_SIZE / 2, y = -Spacing.XS),
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@Composable
|
|
291
|
+
private fun QrButton(data: BottomTabFloatingButton, size: Dp) {
|
|
292
|
+
Box(
|
|
293
|
+
modifier = Modifier
|
|
294
|
+
.size(size)
|
|
295
|
+
.clip(CircleShape)
|
|
296
|
+
.background(AppTheme.current.colors.primary)
|
|
297
|
+
.noFeedbackClickable(onClick = data.onPress)
|
|
298
|
+
.setAutomationId(data.label),
|
|
299
|
+
contentAlignment = Alignment.Center,
|
|
300
|
+
) {
|
|
301
|
+
Icon(source = data.icon, color = Color.White, size = QR_ICON_SIZE)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// RN falls back to this when native glass is missing; Compose has no backdrop blur, so it always does.
|
|
306
|
+
@Composable
|
|
307
|
+
private fun Modifier.tabBarMaterial(shape: Shape): Modifier {
|
|
308
|
+
val colors = AppTheme.current.colors
|
|
309
|
+
return shadow(3.dp, shape)
|
|
310
|
+
.background(colors.background.surface)
|
|
311
|
+
.border(1.dp, colors.border.default, shape)
|
|
312
|
+
}
|
|
@@ -22,6 +22,8 @@ import androidx.compose.runtime.getValue
|
|
|
22
22
|
import androidx.compose.ui.Alignment
|
|
23
23
|
import androidx.compose.ui.Modifier
|
|
24
24
|
import androidx.compose.ui.draw.clipToBounds
|
|
25
|
+
import androidx.compose.ui.draw.drawBehind
|
|
26
|
+
import androidx.compose.ui.geometry.Offset
|
|
25
27
|
import androidx.compose.ui.graphics.graphicsLayer
|
|
26
28
|
import androidx.compose.ui.graphics.Brush
|
|
27
29
|
import androidx.compose.ui.graphics.Color
|
|
@@ -38,6 +40,7 @@ import vn.momo.kits.const.Colors
|
|
|
38
40
|
import vn.momo.kits.components.PopupNotifyProps
|
|
39
41
|
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
40
42
|
import vn.momo.kits.const.Spacing
|
|
43
|
+
import vn.momo.kits.layout.DuoMetrics
|
|
41
44
|
import vn.momo.kits.layout.getScreenLayout
|
|
42
45
|
import vn.momo.kits.modifier.activeOpacityClickable
|
|
43
46
|
import vn.momo.kits.modifier.conditional
|
|
@@ -47,7 +50,7 @@ import vn.momo.kits.navigation.LocalNavigator
|
|
|
47
50
|
import vn.momo.kits.navigation.LocalOptions
|
|
48
51
|
import vn.momo.kits.navigation.LocalScrollState
|
|
49
52
|
import vn.momo.kits.navigation.getInputSearchType
|
|
50
|
-
import vn.momo.kits.
|
|
53
|
+
import vn.momo.kits.navigation.usesDuoGlassHeader
|
|
51
54
|
import vn.momo.kits.utils.horizontalSafeAreaPadding
|
|
52
55
|
|
|
53
56
|
@InternalApi
|
|
@@ -57,6 +60,9 @@ enum class TitlePosition { LEFT, CENTER }
|
|
|
57
60
|
|
|
58
61
|
private val HEADER_ACTIONS_WIDTH = 172.dp
|
|
59
62
|
|
|
63
|
+
// Extra width the 48pt back and toolkit take on the iPhone Duo status bar row.
|
|
64
|
+
private val STATUS_BAR_ROW_ACTIONS_EXTRA = 55.dp
|
|
65
|
+
|
|
60
66
|
@Suppress("FrequentlyChangingValue")
|
|
61
67
|
@Composable
|
|
62
68
|
internal fun headerOpacity(): Float {
|
|
@@ -80,8 +86,33 @@ internal fun headerOpacity(): Float {
|
|
|
80
86
|
@Composable
|
|
81
87
|
internal fun headerTitleMaxWidth(): Dp {
|
|
82
88
|
val layout = getScreenLayout()
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
val clearance = layout.statusBarClearance
|
|
90
|
+
val clearanceWidth = clearance?.let { it.left + it.right } ?: 0.dp
|
|
91
|
+
if (layout.sideBar != null) return layout.contentWidth - Spacing.M * 2 - clearanceWidth
|
|
92
|
+
return layout.width - HEADER_ACTIONS_WIDTH - (if (clearance != null) STATUS_BAR_ROW_ACTIONS_EXTRA else 0.dp) - clearanceWidth
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Scroll distance over which the Duo glass bar fades in.
|
|
96
|
+
private val DUO_GLASS_FADE_DISTANCE = 16.dp
|
|
97
|
+
|
|
98
|
+
// RN DuoGlassBackground fallback: translucent surface with a hairline once content scrolls under it.
|
|
99
|
+
@Suppress("FrequentlyChangingValue")
|
|
100
|
+
@Composable
|
|
101
|
+
private fun Modifier.duoGlassBar(): Modifier {
|
|
102
|
+
val scrollState = LocalScrollState.current
|
|
103
|
+
val fadePx = with(LocalDensity.current) { DUO_GLASS_FADE_DISTANCE.toPx() }
|
|
104
|
+
val alpha by animateFloatAsState((scrollState.value / fadePx).coerceIn(0f, 1f), label = "DuoGlassAlpha")
|
|
105
|
+
val colors = AppTheme.current.colors
|
|
106
|
+
return background(colors.background.surface.copy(alpha = 0.8f * alpha))
|
|
107
|
+
.drawBehind {
|
|
108
|
+
val stroke = 0.5.dp.toPx()
|
|
109
|
+
drawLine(
|
|
110
|
+
color = colors.border.default.copy(alpha = alpha),
|
|
111
|
+
start = Offset(0f, size.height - stroke / 2),
|
|
112
|
+
end = Offset(size.width, size.height - stroke / 2),
|
|
113
|
+
strokeWidth = stroke,
|
|
114
|
+
)
|
|
115
|
+
}
|
|
85
116
|
}
|
|
86
117
|
|
|
87
118
|
@Composable
|
|
@@ -92,6 +123,9 @@ internal fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
|
92
123
|
val inputSearchType = getInputSearchType(options)
|
|
93
124
|
val layout = getScreenLayout()
|
|
94
125
|
val useSideBar = layout.sideBar != null
|
|
126
|
+
// iPhone Duo inner portrait: the row sits on the status bar, between its clusters
|
|
127
|
+
val clearance = layout.statusBarClearance
|
|
128
|
+
val statusBarRow = clearance != null && !useSideBar
|
|
95
129
|
val opacity = headerOpacity()
|
|
96
130
|
|
|
97
131
|
val headerColor = getHeaderColor(options.headerType, opacity, options.tintColor, Colors.black_17)
|
|
@@ -111,13 +145,15 @@ internal fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
|
111
145
|
// Centered title needs a full-width overlay to stay screen-centered, not boxed between back button and header right.
|
|
112
146
|
val centerTitle = titlePosition == TitlePosition.CENTER && options.headerTitle is HeaderTitle.Default
|
|
113
147
|
|
|
148
|
+
val useDuoGlass = options.usesDuoGlassHeader(layout)
|
|
114
149
|
val headerImage = AppTheme.current.assets.headerBackground
|
|
115
|
-
.takeUnless { options.headerType is HeaderType.Transparent }
|
|
150
|
+
.takeUnless { options.headerType is HeaderType.Transparent || useDuoGlass }
|
|
116
151
|
|
|
117
152
|
Box(
|
|
118
153
|
Modifier.height(layout.headerHeight)
|
|
119
154
|
.fillMaxWidth()
|
|
120
|
-
.
|
|
155
|
+
.then(if (useDuoGlass) Modifier.duoGlassBar() else Modifier)
|
|
156
|
+
.conditional(headerImage.isNullOrEmpty() && !useDuoGlass) {
|
|
121
157
|
background(background.copy(alpha = animatedAlpha))
|
|
122
158
|
}
|
|
123
159
|
.conditional(IsShowBaseLineDebug) {
|
|
@@ -147,19 +183,31 @@ internal fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
|
147
183
|
modifier = Modifier.height(layout.headerBarHeight)
|
|
148
184
|
.fillMaxWidth()
|
|
149
185
|
.horizontalSafeAreaPadding()
|
|
150
|
-
.padding(
|
|
186
|
+
.padding(
|
|
187
|
+
start = Spacing.M + (clearance?.left ?: 0.dp),
|
|
188
|
+
end = Spacing.M + (clearance?.right ?: 0.dp),
|
|
189
|
+
),
|
|
151
190
|
verticalAlignment = Alignment.CenterVertically,
|
|
152
191
|
horizontalArrangement = Arrangement.SpaceBetween
|
|
153
192
|
) {
|
|
154
193
|
if (!options.hiddenBack && !useSideBar) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
navigator.onBackSafe {
|
|
161
|
-
|
|
162
|
-
|
|
194
|
+
if (statusBarRow) {
|
|
195
|
+
SideBarButton(
|
|
196
|
+
icon = "arrow-back",
|
|
197
|
+
headerColor = headerColor,
|
|
198
|
+
automationId = "btn_navigation_back",
|
|
199
|
+
onClick = { navigator.onBackSafe { } },
|
|
200
|
+
)
|
|
201
|
+
} else {
|
|
202
|
+
BackButton(
|
|
203
|
+
borderColor = headerColor.borderColor,
|
|
204
|
+
backgroundButton = headerColor.backgroundButton,
|
|
205
|
+
tintIconColor = headerColor.tintIconColor,
|
|
206
|
+
onBackHandler = {
|
|
207
|
+
navigator.onBackSafe { }
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
}
|
|
163
211
|
Spacer(Modifier.width(Spacing.S))
|
|
164
212
|
}
|
|
165
213
|
|
|
@@ -176,23 +224,40 @@ internal fun Header(onBackHandler: (() -> Unit)? = null) {
|
|
|
176
224
|
Box(Modifier.onGloballyPositioned {
|
|
177
225
|
if(headerRightWidthPx.intValue != it.size.width) headerRightWidthPx.intValue = it.size.width
|
|
178
226
|
}){
|
|
179
|
-
|
|
180
|
-
|
|
227
|
+
val headerRight = options.headerRight
|
|
228
|
+
when {
|
|
229
|
+
useSideBar && headerRight is HeaderRight.Toolkit -> Unit
|
|
230
|
+
statusBarRow && headerRight is HeaderRight.Toolkit -> Toolkit(
|
|
231
|
+
headerRight = headerRight,
|
|
232
|
+
tintColor = options.tintColor,
|
|
233
|
+
headerColor = headerColor,
|
|
234
|
+
layout = ToolkitLayout.SideBarRow,
|
|
235
|
+
)
|
|
236
|
+
else -> HeaderRight(headerRight, options.tintColor, headerColor)
|
|
181
237
|
}
|
|
182
238
|
}
|
|
183
239
|
}
|
|
184
240
|
if (centerTitle) {
|
|
241
|
+
// on the status bar row the clusters are lopsided, so both sides keep the wider one's room
|
|
242
|
+
val centerInset = if (clearance != null) {
|
|
243
|
+
val back = if (options.hiddenBack || useSideBar) 0.dp else DuoMetrics.controlSize + Spacing.S
|
|
244
|
+
val right = with(LocalDensity.current) { headerRightWidthPx.intValue.toDp() }
|
|
245
|
+
maxOf(clearance.left + back, clearance.right + right) + Spacing.S
|
|
246
|
+
} else {
|
|
247
|
+
0.dp
|
|
248
|
+
}
|
|
185
249
|
Box(
|
|
186
250
|
modifier = Modifier.height(layout.headerBarHeight)
|
|
187
251
|
.fillMaxWidth()
|
|
188
252
|
.horizontalSafeAreaPadding()
|
|
189
|
-
.padding(horizontal = Spacing.M),
|
|
253
|
+
.padding(horizontal = Spacing.M + centerInset),
|
|
190
254
|
contentAlignment = Alignment.Center
|
|
191
255
|
) {
|
|
192
256
|
HeaderContent(
|
|
193
257
|
headerTitle = options.headerTitle,
|
|
194
258
|
tintIconColor = titleColor,
|
|
195
259
|
titlePosition = titlePosition,
|
|
260
|
+
centerWidthFraction = if (clearance != null) 1f else 0.5f,
|
|
196
261
|
modifier = Modifier.fillMaxWidth()
|
|
197
262
|
)
|
|
198
263
|
}
|
|
@@ -226,6 +291,7 @@ internal fun HeaderContent(
|
|
|
226
291
|
headerTitle: HeaderTitle,
|
|
227
292
|
tintIconColor: Color,
|
|
228
293
|
titlePosition: TitlePosition = TitlePosition.LEFT,
|
|
294
|
+
centerWidthFraction: Float = 0.5f,
|
|
229
295
|
modifier: Modifier = Modifier,
|
|
230
296
|
){
|
|
231
297
|
Box(
|
|
@@ -237,7 +303,7 @@ internal fun HeaderContent(
|
|
|
237
303
|
HeaderTitle(
|
|
238
304
|
title = headerTitle.title,
|
|
239
305
|
color = tintIconColor,
|
|
240
|
-
modifier = Modifier.fillMaxWidth(fraction = if (titlePosition == TitlePosition.CENTER)
|
|
306
|
+
modifier = Modifier.fillMaxWidth(fraction = if (titlePosition == TitlePosition.CENTER) centerWidthFraction else 1f),
|
|
241
307
|
textAlign = if (titlePosition == TitlePosition.CENTER) TextAlign.Center else TextAlign.Start
|
|
242
308
|
)
|
|
243
309
|
}
|