@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
|
@@ -3,13 +3,15 @@ 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
|
|
@@ -20,6 +22,8 @@ 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
|
|
@@ -97,7 +97,8 @@ internal fun parseHeaderTint(value: String?): Color? {
|
|
|
97
97
|
val hex = when (raw.length) {
|
|
98
98
|
3 -> "FF" + raw.map { "$it$it" }.joinToString("")
|
|
99
99
|
6 -> "FF$raw"
|
|
100
|
-
|
|
100
|
+
// CSS #RRGGBBAA, as RN passes it; Compose wants AARRGGBB
|
|
101
|
+
8 -> raw.substring(6) + raw.substring(0, 6)
|
|
101
102
|
else -> return null
|
|
102
103
|
}
|
|
103
104
|
val v = hex.toLongOrNull(16) ?: return null
|
|
@@ -26,7 +26,9 @@ import androidx.compose.ui.Alignment
|
|
|
26
26
|
import androidx.compose.ui.Modifier
|
|
27
27
|
import androidx.compose.ui.draw.clip
|
|
28
28
|
import androidx.compose.ui.graphics.Color
|
|
29
|
+
import androidx.compose.ui.unit.Dp
|
|
29
30
|
import androidx.compose.ui.unit.dp
|
|
31
|
+
import vn.momo.kits.layout.DuoMetrics
|
|
30
32
|
import vn.momo.kits.application.LocalContext
|
|
31
33
|
import vn.momo.kits.application.MiniAppContext
|
|
32
34
|
import vn.momo.kits.components.BadgeDot
|
|
@@ -60,12 +62,32 @@ fun HeaderRight(
|
|
|
60
62
|
}
|
|
61
63
|
}
|
|
62
64
|
|
|
65
|
+
private const val HELP_CENTER_APP_ID = "vn.momo.helpcenter"
|
|
66
|
+
|
|
67
|
+
private fun usesHelpCenter(context: MiniAppContext?): Boolean =
|
|
68
|
+
context != null && context.appId != HELP_CENTER_APP_ID
|
|
69
|
+
|
|
70
|
+
private fun showsShortcut(headerRight: HeaderRight.Toolkit, context: MiniAppContext?): Boolean =
|
|
71
|
+
headerRight.useShortcut && !(headerRight.useMore && context == null)
|
|
72
|
+
|
|
73
|
+
// Height of the vertical toolkit: the optional shortcut, then the help/home capsule.
|
|
74
|
+
@Composable
|
|
75
|
+
internal fun toolkitColumnHeight(headerRight: HeaderRight.Toolkit): Dp {
|
|
76
|
+
val context = LocalContext.current
|
|
77
|
+
val shortcut = if (showsShortcut(headerRight, context)) DuoMetrics.controlSize + Spacing.M else 0.dp
|
|
78
|
+
val groupCount = if (usesHelpCenter(context)) 2 else 1
|
|
79
|
+
return shortcut + if (groupCount > 1) DuoMetrics.itemPitch * groupCount else DuoMetrics.controlSize
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// SideBarColumn: the iPhone Duo side bar; SideBarRow: the Duo header row on the status bar, same 48pt controls.
|
|
83
|
+
internal enum class ToolkitLayout { Bar, SideBarColumn, SideBarRow }
|
|
84
|
+
|
|
63
85
|
@Composable
|
|
64
86
|
internal fun Toolkit(
|
|
65
87
|
headerRight: HeaderRight.Toolkit,
|
|
66
88
|
tintColor: Color? = null,
|
|
67
89
|
headerColor: HeaderColor,
|
|
68
|
-
|
|
90
|
+
layout: ToolkitLayout = ToolkitLayout.Bar,
|
|
69
91
|
) {
|
|
70
92
|
val api = LocalMaxApi.current
|
|
71
93
|
val context = LocalContext.current
|
|
@@ -118,10 +140,13 @@ internal fun Toolkit(
|
|
|
118
140
|
) {}
|
|
119
141
|
}
|
|
120
142
|
|
|
121
|
-
fun
|
|
122
|
-
api?.dismissAll {
|
|
143
|
+
fun onPressClose() {
|
|
144
|
+
if (headerRight.useCloseIcon) api?.dismiss { } else api?.dismissAll { }
|
|
123
145
|
}
|
|
124
146
|
|
|
147
|
+
val useHelpCenter = usesHelpCenter(context)
|
|
148
|
+
val closeIcon = if (headerRight.useCloseIcon) "16_navigation_close_circle" else "16_basic_home"
|
|
149
|
+
|
|
125
150
|
val navButtonConfig = getNavigationButtonConfig(
|
|
126
151
|
headerRight,
|
|
127
152
|
::onPressShortcut,
|
|
@@ -132,12 +157,7 @@ internal fun Toolkit(
|
|
|
132
157
|
group.items.any { it.showBadge }
|
|
133
158
|
}
|
|
134
159
|
|
|
135
|
-
val isShowShortcut
|
|
136
|
-
derivedStateOf {
|
|
137
|
-
(headerRight.useShortcut == true) &&
|
|
138
|
-
!(headerRight.useMore == true && context == null)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
160
|
+
val isShowShortcut = showsShortcut(headerRight, context)
|
|
141
161
|
|
|
142
162
|
val icon by remember(navButtonConfig.icon, isFavorite) {
|
|
143
163
|
derivedStateOf {
|
|
@@ -146,8 +166,9 @@ internal fun Toolkit(
|
|
|
146
166
|
}
|
|
147
167
|
}
|
|
148
168
|
|
|
149
|
-
if (
|
|
150
|
-
|
|
169
|
+
if (layout != ToolkitLayout.Bar) {
|
|
170
|
+
val horizontal = layout == ToolkitLayout.SideBarRow
|
|
171
|
+
val controls = @Composable {
|
|
151
172
|
if (isShowShortcut) {
|
|
152
173
|
SideBarButton(
|
|
153
174
|
icon = icon,
|
|
@@ -157,23 +178,32 @@ internal fun Toolkit(
|
|
|
157
178
|
onClick = navButtonConfig.onPress,
|
|
158
179
|
)
|
|
159
180
|
}
|
|
160
|
-
SideBarGroup(headerColor = headerColor, itemCount = if (
|
|
161
|
-
if (
|
|
181
|
+
SideBarGroup(headerColor = headerColor, itemCount = if (useHelpCenter) 2 else 1, horizontal = horizontal) { item ->
|
|
182
|
+
if (useHelpCenter) {
|
|
162
183
|
SideBarGroupItem(
|
|
163
184
|
icon = "help_center",
|
|
164
185
|
headerColor = headerColor,
|
|
165
186
|
automationId = "btn_navigation_help_center",
|
|
166
187
|
onClick = ::onPressHelpCenter,
|
|
188
|
+
modifier = item,
|
|
167
189
|
)
|
|
168
190
|
}
|
|
169
191
|
SideBarGroupItem(
|
|
170
|
-
icon =
|
|
192
|
+
icon = closeIcon,
|
|
171
193
|
headerColor = headerColor,
|
|
172
194
|
automationId = "btn_navigation_close",
|
|
173
|
-
onClick = ::
|
|
195
|
+
onClick = ::onPressClose,
|
|
196
|
+
modifier = item,
|
|
174
197
|
)
|
|
175
198
|
}
|
|
176
199
|
}
|
|
200
|
+
if (horizontal) {
|
|
201
|
+
Row(horizontalArrangement = Arrangement.spacedBy(Spacing.M), verticalAlignment = Alignment.CenterVertically) {
|
|
202
|
+
controls()
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
Column(verticalArrangement = Arrangement.spacedBy(Spacing.M)) { controls() }
|
|
206
|
+
}
|
|
177
207
|
return
|
|
178
208
|
}
|
|
179
209
|
|
|
@@ -200,7 +230,7 @@ internal fun Toolkit(
|
|
|
200
230
|
.clip(shape = RoundedCornerShape(14.dp))
|
|
201
231
|
.background(headerColor.backgroundButton)
|
|
202
232
|
) {
|
|
203
|
-
if (
|
|
233
|
+
if (useHelpCenter) {
|
|
204
234
|
Icon(
|
|
205
235
|
source = "help_center",
|
|
206
236
|
size = 20.dp,
|
|
@@ -215,10 +245,10 @@ internal fun Toolkit(
|
|
|
215
245
|
)
|
|
216
246
|
}
|
|
217
247
|
Icon(
|
|
218
|
-
source =
|
|
248
|
+
source = closeIcon,
|
|
219
249
|
size = 20.dp,
|
|
220
250
|
color = headerColor.tintIconColor,
|
|
221
|
-
modifier = Modifier.padding(4.dp).activeOpacityClickable(onClick = ::
|
|
251
|
+
modifier = Modifier.padding(4.dp).activeOpacityClickable(onClick = ::onPressClose)
|
|
222
252
|
)
|
|
223
253
|
}
|
|
224
254
|
}
|
|
@@ -308,7 +338,9 @@ sealed interface HeaderRight {
|
|
|
308
338
|
val useMore: Boolean = false,
|
|
309
339
|
val useSystemTools: Boolean = true,
|
|
310
340
|
val tools: List<ToolGroup> = emptyList(),
|
|
311
|
-
val toolCallback: ((String) -> Unit)? = { _ -> }
|
|
341
|
+
val toolCallback: ((String) -> Unit)? = { _ -> },
|
|
342
|
+
// Close dismisses only this mini app instead of returning home.
|
|
343
|
+
val useCloseIcon: Boolean = false,
|
|
312
344
|
) : HeaderRight
|
|
313
345
|
}
|
|
314
346
|
|
|
@@ -9,10 +9,12 @@ import androidx.compose.ui.text.style.TextOverflow
|
|
|
9
9
|
import androidx.compose.ui.unit.sp
|
|
10
10
|
import androidx.compose.ui.zIndex
|
|
11
11
|
import vn.momo.kits.components.Text
|
|
12
|
+
import vn.momo.kits.const.KitDesignSystem
|
|
12
13
|
import vn.momo.kits.const.Typography
|
|
13
14
|
import vn.momo.kits.modifier.setAutomationId
|
|
14
15
|
|
|
15
|
-
internal
|
|
16
|
+
internal val headerFontFamily: String?
|
|
17
|
+
get() = if (KitDesignSystem.isWhiteList) "MoMoTrustDisplay" else null
|
|
16
18
|
|
|
17
19
|
@Composable
|
|
18
20
|
internal fun HeaderTitle(
|
|
@@ -29,7 +31,7 @@ internal fun HeaderTitle(
|
|
|
29
31
|
fontSize = 15.sp,
|
|
30
32
|
lineHeight = 22.sp,
|
|
31
33
|
),
|
|
32
|
-
fontFamily =
|
|
34
|
+
fontFamily = headerFontFamily,
|
|
33
35
|
color = color,
|
|
34
36
|
maxLines = 1,
|
|
35
37
|
overflow = TextOverflow.Ellipsis
|
|
@@ -6,17 +6,16 @@ import androidx.compose.foundation.layout.Arrangement
|
|
|
6
6
|
import androidx.compose.foundation.layout.Box
|
|
7
7
|
import androidx.compose.foundation.layout.BoxScope
|
|
8
8
|
import androidx.compose.foundation.layout.Column
|
|
9
|
-
import androidx.compose.foundation.layout.
|
|
9
|
+
import androidx.compose.foundation.layout.Row
|
|
10
10
|
import androidx.compose.foundation.layout.Spacer
|
|
11
11
|
import androidx.compose.foundation.layout.absolutePadding
|
|
12
|
-
import androidx.compose.foundation.layout.fillMaxWidth
|
|
13
|
-
import androidx.compose.foundation.layout.height
|
|
14
12
|
import androidx.compose.foundation.layout.offset
|
|
15
13
|
import androidx.compose.foundation.layout.size
|
|
16
14
|
import androidx.compose.foundation.layout.width
|
|
17
15
|
import androidx.compose.foundation.shape.CircleShape
|
|
18
16
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
19
17
|
import androidx.compose.runtime.Composable
|
|
18
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
20
19
|
import androidx.compose.ui.AbsoluteAlignment
|
|
21
20
|
import androidx.compose.ui.Alignment
|
|
22
21
|
import androidx.compose.ui.Modifier
|
|
@@ -41,18 +40,24 @@ import vn.momo.kits.modifier.conditional
|
|
|
41
40
|
import vn.momo.kits.modifier.setAutomationId
|
|
42
41
|
import vn.momo.kits.navigation.LocalNavigator
|
|
43
42
|
import vn.momo.kits.navigation.LocalOptions
|
|
43
|
+
import vn.momo.kits.navigation.bottomtab.LocalDuoBarCompression
|
|
44
44
|
|
|
45
45
|
private val SIDE_BAR_ICON_SIZE = 24.dp
|
|
46
46
|
|
|
47
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; the column stays above it.
|
|
48
|
+
// bottomInset is what footer / tab bar / home indicator take from the bottom, gap included; the column stays above it.
|
|
49
49
|
@Composable
|
|
50
50
|
internal fun BoxScope.HeaderSideBar(bottomInset: Dp, modifier: Modifier = Modifier) {
|
|
51
51
|
val options = LocalOptions.current
|
|
52
52
|
val navigator = LocalNavigator.current
|
|
53
|
-
val
|
|
53
|
+
val screenLayout = getScreenLayout()
|
|
54
|
+
val sideBar = screenLayout.sideBar ?: return
|
|
54
55
|
val toolkit = options.headerRight as? HeaderRight.Toolkit
|
|
55
|
-
|
|
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
|
|
56
61
|
|
|
57
62
|
val headerColor = getHeaderColor(options.headerType, headerOpacity(), options.tintColor, Colors.black_17)
|
|
58
63
|
val isRight = sideBar.edge == SideBarEdge.RIGHT
|
|
@@ -63,7 +68,7 @@ internal fun BoxScope.HeaderSideBar(bottomInset: Dp, modifier: Modifier = Modifi
|
|
|
63
68
|
.width(sideBar.width)
|
|
64
69
|
.layout { measurable, constraints ->
|
|
65
70
|
val placeable = measurable.measure(constraints.copy(minHeight = 0))
|
|
66
|
-
val maxY =
|
|
71
|
+
val maxY = screenLayout.height.roundToPx() - bottomInset.roundToPx() - placeable.height
|
|
67
72
|
val y = minOf(sideBar.top.roundToPx(), maxY).coerceAtLeast(0)
|
|
68
73
|
layout(placeable.width, constraints.constrainHeight(y + placeable.height)) {
|
|
69
74
|
placeable.place(0, y)
|
|
@@ -84,8 +89,22 @@ internal fun BoxScope.HeaderSideBar(bottomInset: Dp, modifier: Modifier = Modifi
|
|
|
84
89
|
onClick = { navigator.onBackSafe { } },
|
|
85
90
|
)
|
|
86
91
|
}
|
|
87
|
-
|
|
88
|
-
|
|
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
|
+
)
|
|
89
108
|
}
|
|
90
109
|
}
|
|
91
110
|
}
|
|
@@ -119,34 +138,38 @@ internal fun SideBarButton(
|
|
|
119
138
|
}
|
|
120
139
|
}
|
|
121
140
|
|
|
122
|
-
// Capsule grouping related controls, one item per 50pt.
|
|
141
|
+
// Capsule grouping related controls, one item per 50pt: vertical in the side bar, horizontal on a header row.
|
|
123
142
|
@Composable
|
|
124
143
|
internal fun SideBarGroup(
|
|
125
144
|
headerColor: HeaderColor,
|
|
126
145
|
itemCount: Int,
|
|
127
|
-
|
|
146
|
+
horizontal: Boolean = false,
|
|
147
|
+
content: @Composable (itemModifier: Modifier) -> Unit,
|
|
128
148
|
) {
|
|
129
|
-
val
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
.
|
|
135
|
-
|
|
136
|
-
|
|
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
|
+
}
|
|
137
161
|
}
|
|
138
162
|
|
|
139
163
|
@Composable
|
|
140
|
-
internal fun
|
|
164
|
+
internal fun SideBarGroupItem(
|
|
141
165
|
icon: String,
|
|
142
166
|
headerColor: HeaderColor,
|
|
143
167
|
automationId: String,
|
|
144
168
|
onClick: () -> Unit,
|
|
169
|
+
modifier: Modifier = Modifier,
|
|
145
170
|
) {
|
|
146
171
|
Box(
|
|
147
|
-
modifier =
|
|
148
|
-
.weight(1f)
|
|
149
|
-
.fillMaxWidth()
|
|
172
|
+
modifier = modifier
|
|
150
173
|
.activeOpacityClickable(onClick = onClick)
|
|
151
174
|
.setAutomationId(automationId),
|
|
152
175
|
contentAlignment = Alignment.Center,
|
|
@@ -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
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
package vn.momo.kits.platform
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.runtime.Immutable
|
|
5
|
+
import androidx.compose.ui.unit.Dp
|
|
6
|
+
|
|
7
|
+
@Immutable
|
|
8
|
+
internal data class ReservedRegion(val x: Dp, val y: Dp, val width: Dp, val height: Dp)
|
|
9
|
+
|
|
10
|
+
// Regions the system occludes (iOS 27.1+), in the coordinates of the Compose root view: the
|
|
11
|
+
// iPhone Duo camera, and the status bar cluster as it grows for a call or a Live Activity.
|
|
12
|
+
@Composable
|
|
13
|
+
internal expect fun rememberReservedRegions(): List<ReservedRegion>
|
|
@@ -4,6 +4,12 @@ package vn.momo.kits.const
|
|
|
4
4
|
|
|
5
5
|
import androidx.compose.runtime.Composable
|
|
6
6
|
import androidx.compose.runtime.remember
|
|
7
|
+
// AI-GENERATED START: imports for the SF Pro -> iOS system font mapping (ITMS-91182)
|
|
8
|
+
import androidx.compose.ui.text.ExperimentalTextApi
|
|
9
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
10
|
+
import androidx.compose.ui.text.platform.SystemFont
|
|
11
|
+
import platform.UIKit.UIFont
|
|
12
|
+
// AI-GENERATED END: imports for the SF Pro -> iOS system font mapping (ITMS-91182)
|
|
7
13
|
import androidx.compose.ui.text.font.FontFamily
|
|
8
14
|
import androidx.compose.ui.text.platform.Font
|
|
9
15
|
import kotlinx.cinterop.ExperimentalForeignApi
|
|
@@ -14,8 +20,40 @@ import platform.Foundation.NSData
|
|
|
14
20
|
import platform.Foundation.dataWithContentsOfFile
|
|
15
21
|
import platform.posix.memcpy
|
|
16
22
|
|
|
23
|
+
// AI-GENERATED START: SF Pro -> iOS system font mapping (ITMS-91182)
|
|
24
|
+
/**
|
|
25
|
+
* Apple cấm app nhúng font của Apple (ITMS-91182) và font hệ thống iOS chính là SF Pro, nên file SF Pro
|
|
26
|
+
* được thay bằng system font cùng weight thay vì đọc từ bundle — host không cần (và không được) ship nó.
|
|
27
|
+
*
|
|
28
|
+
* Weight đi theo tên face của Apple (Ultralight nhẹ hơn Thin), khớp thang weight CoreText mà Skia dùng
|
|
29
|
+
* (100 = UltraLight, 200 = Thin), để ra đúng face mà tên file chỉ định.
|
|
30
|
+
* Phải mang weight trong Font vì `Text` bỏ `fontWeight` khỏi style (weight nằm sẵn trong font).
|
|
31
|
+
*/
|
|
32
|
+
private val systemFontWeights: Map<String, FontWeight> = mapOf(
|
|
33
|
+
"SFProText-Ultralight.ttf" to FontWeight.Thin,
|
|
34
|
+
"SFProText-Thin.ttf" to FontWeight.ExtraLight,
|
|
35
|
+
"SFProText-Light.ttf" to FontWeight.Light,
|
|
36
|
+
"SFProText-Regular.ttf" to FontWeight.Normal,
|
|
37
|
+
"SFProText-Medium.ttf" to FontWeight.Medium,
|
|
38
|
+
"SFProText-Semibold.ttf" to FontWeight.SemiBold,
|
|
39
|
+
"SFProText-Bold.ttf" to FontWeight.Bold,
|
|
40
|
+
"SFProText-Heavy.ttf" to FontWeight.ExtraBold,
|
|
41
|
+
"SFProText-Black.ttf" to FontWeight.Black,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
/** Family của system font (`.AppleSystemUIFont`), lấy qua API chính thức thay vì hard-code tên ẩn. */
|
|
45
|
+
private val systemFontFamilyName: String by lazy { UIFont.systemFontOfSize(14.0).familyName }
|
|
46
|
+
|
|
47
|
+
@OptIn(ExperimentalTextApi::class)
|
|
48
|
+
private fun systemFontFamily(weight: FontWeight): FontFamily =
|
|
49
|
+
FontFamily(SystemFont(identity = systemFontFamilyName, weight = weight))
|
|
50
|
+
// AI-GENERATED END: SF Pro -> iOS system font mapping (ITMS-91182)
|
|
51
|
+
|
|
17
52
|
@Composable
|
|
18
53
|
internal actual fun hostFontFamily(fileName: String): FontFamily? = remember(fileName) {
|
|
54
|
+
// AI-GENERATED START: SF Pro -> iOS system font mapping (ITMS-91182)
|
|
55
|
+
systemFontWeights[fileName]?.let { weight -> return@remember systemFontFamily(weight) }
|
|
56
|
+
// AI-GENERATED END: SF Pro -> iOS system font mapping (ITMS-91182)
|
|
19
57
|
val name = fileName.substringBeforeLast('.')
|
|
20
58
|
val ext = fileName.substringAfterLast('.', "")
|
|
21
59
|
// Font từ font-source nằm rời ở bundle root, không nằm trong compose-resources.
|
|
@@ -39,11 +39,15 @@ import platform.Foundation.NSBundle
|
|
|
39
39
|
import platform.UIKit.UIColor
|
|
40
40
|
import platform.UIKit.UIDevice
|
|
41
41
|
import platform.UIKit.UIScreen
|
|
42
|
+
import platform.UIKit.UIUserInterfaceIdiomPhone
|
|
42
43
|
import vn.momo.kits.navigation.AttachHostBackGesture
|
|
43
44
|
import vn.momo.kits.navigation.Navigator
|
|
44
45
|
|
|
45
46
|
actual fun getPlatformName(): String = "iOS"
|
|
46
47
|
|
|
48
|
+
actual fun isPhoneIdiom(): Boolean =
|
|
49
|
+
UIDevice.currentDevice.userInterfaceIdiom == UIUserInterfaceIdiomPhone
|
|
50
|
+
|
|
47
51
|
@OptIn(ExperimentalForeignApi::class)
|
|
48
52
|
@Composable
|
|
49
53
|
actual fun getScreenDimensions(): ScreenDimension = memScoped {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
package vn.momo.kits.platform
|
|
2
|
+
|
|
3
|
+
import androidx.compose.runtime.Composable
|
|
4
|
+
import androidx.compose.runtime.DisposableEffect
|
|
5
|
+
import androidx.compose.runtime.getValue
|
|
6
|
+
import androidx.compose.runtime.mutableStateOf
|
|
7
|
+
import androidx.compose.runtime.remember
|
|
8
|
+
import androidx.compose.runtime.setValue
|
|
9
|
+
import androidx.compose.ui.uikit.LocalUIViewController
|
|
10
|
+
import androidx.compose.ui.unit.dp
|
|
11
|
+
import kotlinx.cinterop.BetaInteropApi
|
|
12
|
+
import kotlinx.cinterop.ExperimentalForeignApi
|
|
13
|
+
import kotlinx.cinterop.interpretObjCPointer
|
|
14
|
+
import kotlinx.cinterop.objcPtr
|
|
15
|
+
import kotlinx.cinterop.readValue
|
|
16
|
+
import kotlinx.cinterop.useContents
|
|
17
|
+
import platform.CoreGraphics.CGRectZero
|
|
18
|
+
import platform.Foundation.NSArray
|
|
19
|
+
import platform.Foundation.NSSelectorFromString
|
|
20
|
+
import platform.Foundation.NSValue
|
|
21
|
+
import platform.Foundation.valueForKey
|
|
22
|
+
import platform.UIKit.CGRectValue
|
|
23
|
+
import platform.UIKit.UIView
|
|
24
|
+
import platform.UIKit.UIViewAutoresizingFlexibleHeight
|
|
25
|
+
import platform.UIKit.UIViewAutoresizingFlexibleWidth
|
|
26
|
+
import platform.darwin.NSObject
|
|
27
|
+
import platform.Foundation.NSClassFromString
|
|
28
|
+
|
|
29
|
+
// The API is newer than the SDK Kotlin/Native binds, so it is reached through the runtime.
|
|
30
|
+
@OptIn(ExperimentalForeignApi::class)
|
|
31
|
+
private val regionsOfKind = NSSelectorFromString("reservedRegionsOfKind:")
|
|
32
|
+
|
|
33
|
+
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
|
|
34
|
+
private fun UIView.occlusionRegions(target: UIView): List<ReservedRegion> {
|
|
35
|
+
if (window == null || !respondsToSelector(regionsOfKind)) return emptyList()
|
|
36
|
+
// a class object fails `as? NSObjectProtocol` (K/N checks it as an instance), so it is reinterpreted
|
|
37
|
+
val kindClass = NSClassFromString("UIViewReservedRegionKind")
|
|
38
|
+
?.let { interpretObjCPointer<NSObject>(it.objcPtr()) } ?: return emptyList()
|
|
39
|
+
val kind = kindClass.performSelector(NSSelectorFromString("occlusionRegionKind")) ?: return emptyList()
|
|
40
|
+
val regions = when (val result = performSelector(regionsOfKind, withObject = kind)) {
|
|
41
|
+
is List<*> -> result
|
|
42
|
+
is NSArray -> List(result.count.toInt()) { result.objectAtIndex(it.toULong()) }
|
|
43
|
+
else -> return emptyList()
|
|
44
|
+
}
|
|
45
|
+
return regions.mapNotNull { region ->
|
|
46
|
+
val frame = ((region as? NSObject)?.valueForKey("frame") as? NSValue)?.CGRectValue ?: return@mapNotNull null
|
|
47
|
+
convertRect(frame, toView = target).useContents {
|
|
48
|
+
ReservedRegion(origin.x.dp, origin.y.dp, size.width.dp, size.height.dp)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Invisible full-size probe: reading the regions in layoutSubviews makes UIKit run it again
|
|
54
|
+
// when they change, so nothing polls.
|
|
55
|
+
@OptIn(ExperimentalForeignApi::class)
|
|
56
|
+
private class ReservedRegionsProbe(
|
|
57
|
+
private val onChange: (List<ReservedRegion>) -> Unit,
|
|
58
|
+
) : UIView(frame = CGRectZero.readValue()) {
|
|
59
|
+
private var last: List<ReservedRegion>? = null
|
|
60
|
+
|
|
61
|
+
init {
|
|
62
|
+
userInteractionEnabled = false
|
|
63
|
+
autoresizingMask = UIViewAutoresizingFlexibleWidth or UIViewAutoresizingFlexibleHeight
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun layoutSubviews() {
|
|
67
|
+
super.layoutSubviews()
|
|
68
|
+
val regions = superview?.let { occlusionRegions(it) } ?: emptyList()
|
|
69
|
+
if (regions != last) {
|
|
70
|
+
last = regions
|
|
71
|
+
onChange(regions)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
override fun didMoveToWindow() {
|
|
76
|
+
super.didMoveToWindow()
|
|
77
|
+
setNeedsLayout()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@OptIn(ExperimentalForeignApi::class)
|
|
82
|
+
@Composable
|
|
83
|
+
internal actual fun rememberReservedRegions(): List<ReservedRegion> {
|
|
84
|
+
if (!isPhoneIdiom()) return emptyList()
|
|
85
|
+
val host = LocalUIViewController.current.view
|
|
86
|
+
var regions by remember { mutableStateOf(emptyList<ReservedRegion>()) }
|
|
87
|
+
DisposableEffect(host) {
|
|
88
|
+
val probe = ReservedRegionsProbe { regions = it }
|
|
89
|
+
probe.setFrame(host.bounds)
|
|
90
|
+
host.insertSubview(probe, atIndex = 0)
|
|
91
|
+
onDispose { probe.removeFromSuperview() }
|
|
92
|
+
}
|
|
93
|
+
return regions
|
|
94
|
+
}
|
package/gradle.properties
CHANGED
|
@@ -41,6 +41,12 @@ public class KitConfig {
|
|
|
41
41
|
public var trustBanner: TrustBannerData? = nil
|
|
42
42
|
public var headerBar: String? = nil
|
|
43
43
|
public var headerGradient: String? = nil
|
|
44
|
+
|
|
45
|
+
public init(trustBanner: TrustBannerData? = nil, headerBar: String? = nil, headerGradient: String? = nil) {
|
|
46
|
+
self.trustBanner = trustBanner
|
|
47
|
+
self.headerBar = headerBar
|
|
48
|
+
self.headerGradient = headerGradient
|
|
49
|
+
}
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
public class ApplicationEnvironment: ObservableObject {
|
|
@@ -59,6 +65,17 @@ public class ApplicationEnvironment: ObservableObject {
|
|
|
59
65
|
}
|
|
60
66
|
}
|
|
61
67
|
|
|
68
|
+
extension ApplicationEnvironment {
|
|
69
|
+
/// Closes the mini app: through `composeApi` as before, else `maxApi` (Compose `maxApi.dismiss`).
|
|
70
|
+
func requestDismiss() {
|
|
71
|
+
if let composeApi {
|
|
72
|
+
composeApi.requestCallback(funcName: "dismiss", params: nil, onResponse: nil)
|
|
73
|
+
} else {
|
|
74
|
+
maxApi?.dismiss(completion: nil)
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
62
79
|
private struct ApplicationEnvironmentKey: EnvironmentKey {
|
|
63
80
|
static let defaultValue = ApplicationEnvironment()
|
|
64
81
|
}
|