@momo-kits/native-kits 0.157.5-debug → 0.157.6-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/compose/build.gradle.kts +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/platform/Platform.android.kt +7 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Avatar.kt +157 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Carousel.kt +123 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Collapse.kt +224 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Loader.kt +108 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/PopupPromotion.kt +2 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/ProgressInfo.kt +338 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Rating.kt +87 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Slider.kt +348 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Stepper.kt +256 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Steps.kt +494 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/SuggestAction.kt +131 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Swipe.kt +215 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/TabView.kt +531 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/components/Uploader.kt +192 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Spacing.kt +3 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +5 -2
- package/compose/src/commonMain/kotlin/vn/momo/kits/modifier/AutomationId.kt +2 -11
- package/compose/src/commonMain/kotlin/vn/momo/kits/platform/Platform.kt +5 -1
- package/compose/src/iosMain/kotlin/vn/momo/kits/platform/Platform.ios.kt +12 -0
- package/gradle.properties +1 -1
- package/ios/Popup/PopupPromotion.swift +2 -2
- package/package.json +1 -1
package/compose/build.gradle.kts
CHANGED
|
@@ -14,6 +14,8 @@ import androidx.compose.ui.graphics.Color
|
|
|
14
14
|
import androidx.compose.ui.graphics.NativePaint
|
|
15
15
|
import androidx.compose.ui.graphics.toArgb
|
|
16
16
|
import androidx.compose.ui.platform.LocalConfiguration
|
|
17
|
+
import androidx.compose.ui.semantics.contentDescription
|
|
18
|
+
import androidx.compose.ui.semantics.semantics
|
|
17
19
|
import androidx.compose.ui.unit.Dp
|
|
18
20
|
import androidx.compose.ui.unit.dp
|
|
19
21
|
import com.airbnb.lottie.LottieProperty
|
|
@@ -108,3 +110,8 @@ actual fun NativePaint.setColor(color: Color){
|
|
|
108
110
|
this.color = color.toArgb()
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
actual fun Modifier.semantics(accessibilityId: String): Modifier {
|
|
114
|
+
return semantics {
|
|
115
|
+
contentDescription = accessibilityId
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
package vn.momo.kits.components
|
|
2
|
+
|
|
3
|
+
import androidx.compose.foundation.background
|
|
4
|
+
import androidx.compose.foundation.border
|
|
5
|
+
import androidx.compose.foundation.layout.Box
|
|
6
|
+
import androidx.compose.foundation.layout.fillMaxSize
|
|
7
|
+
import androidx.compose.foundation.layout.offset
|
|
8
|
+
import androidx.compose.foundation.layout.size
|
|
9
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
10
|
+
import androidx.compose.runtime.Composable
|
|
11
|
+
import androidx.compose.runtime.remember
|
|
12
|
+
import androidx.compose.ui.Alignment
|
|
13
|
+
import androidx.compose.ui.Modifier
|
|
14
|
+
import androidx.compose.ui.draw.clip
|
|
15
|
+
import androidx.compose.ui.semantics.contentDescription
|
|
16
|
+
import androidx.compose.ui.semantics.semantics
|
|
17
|
+
import androidx.compose.ui.unit.dp
|
|
18
|
+
import androidx.compose.ui.zIndex
|
|
19
|
+
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
20
|
+
import vn.momo.kits.const.AppTheme
|
|
21
|
+
import vn.momo.kits.const.Colors
|
|
22
|
+
import vn.momo.kits.const.Spacing
|
|
23
|
+
import vn.momo.kits.const.Typography
|
|
24
|
+
import vn.momo.kits.modifier.conditional
|
|
25
|
+
import vn.momo.kits.modifier.setAutomationId
|
|
26
|
+
|
|
27
|
+
private const val LOGO_MOMO_URL =
|
|
28
|
+
"https://static.momocdn.net/app/img/kits/logo_momo_circle.png"
|
|
29
|
+
private const val ICON_SUPPORT_DEFAULT_URL =
|
|
30
|
+
"https://static.momocdn.net/app/img/kits/_indicator.png"
|
|
31
|
+
|
|
32
|
+
enum class AvatarSize(val sizeDp: Int, val iconSizeDp: Int) {
|
|
33
|
+
SIZE_24(24, 16),
|
|
34
|
+
SIZE_32(32, 16),
|
|
35
|
+
SIZE_40(40, 24),
|
|
36
|
+
SIZE_48(48, 32),
|
|
37
|
+
SIZE_56(56, 32),
|
|
38
|
+
SIZE_72(72, 40),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Composable
|
|
42
|
+
fun Avatar(
|
|
43
|
+
modifier: Modifier = Modifier,
|
|
44
|
+
size: AvatarSize = AvatarSize.SIZE_32,
|
|
45
|
+
rounded: Boolean = true,
|
|
46
|
+
name: String? = null,
|
|
47
|
+
image: String? = null,
|
|
48
|
+
showIconMomo: Boolean = false,
|
|
49
|
+
showIconSupport: Boolean = false,
|
|
50
|
+
iconSupport: String = ICON_SUPPORT_DEFAULT_URL,
|
|
51
|
+
accessibilityLabel: String? = null,
|
|
52
|
+
) {
|
|
53
|
+
val theme = AppTheme.current
|
|
54
|
+
|
|
55
|
+
val cornerRadius = remember(rounded, size) {
|
|
56
|
+
if (rounded) (size.sizeDp / 2).dp else Spacing.XS
|
|
57
|
+
}
|
|
58
|
+
val shape = remember(cornerRadius) { RoundedCornerShape(cornerRadius) }
|
|
59
|
+
|
|
60
|
+
val badgeSizeDp = remember(size) { (size.iconSizeDp / 2).dp }
|
|
61
|
+
val badgeShape = remember(size) {
|
|
62
|
+
RoundedCornerShape((size.iconSizeDp / 4).dp)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
val shouldShowIconTop = showIconMomo && rounded
|
|
66
|
+
val shouldShowIconBottom = showIconSupport && rounded
|
|
67
|
+
|
|
68
|
+
val initials = remember(name) {
|
|
69
|
+
name?.let { avatarInitials(it) }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
val automationId = remember(accessibilityLabel, name) {
|
|
73
|
+
accessibilityLabel ?: "Avatar${if (name != null) "/$name" else ""}"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
Box(
|
|
77
|
+
modifier = modifier
|
|
78
|
+
.size(size.sizeDp.dp)
|
|
79
|
+
.conditional(IsShowBaseLineDebug) { border(1.dp, Colors.blue_03) }
|
|
80
|
+
.setAutomationId(automationId)
|
|
81
|
+
.semantics { contentDescription = automationId },
|
|
82
|
+
) {
|
|
83
|
+
// Inner box with clip — holds avatar content
|
|
84
|
+
Box(
|
|
85
|
+
modifier = Modifier
|
|
86
|
+
.fillMaxSize()
|
|
87
|
+
.clip(shape)
|
|
88
|
+
.background(color = Colors.pink_09)
|
|
89
|
+
.border(width = 1.dp, color = theme.colors.border.default, shape = shape),
|
|
90
|
+
contentAlignment = Alignment.Center,
|
|
91
|
+
) {
|
|
92
|
+
when {
|
|
93
|
+
image != null -> {
|
|
94
|
+
Image(
|
|
95
|
+
source = image,
|
|
96
|
+
modifier = Modifier
|
|
97
|
+
.fillMaxSize()
|
|
98
|
+
.clip(shape)
|
|
99
|
+
.semantics { contentDescription = "$automationId|img" },
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
name != null -> {
|
|
104
|
+
Text(
|
|
105
|
+
text = initials ?: "",
|
|
106
|
+
style = Typography.descriptionXsRegular,
|
|
107
|
+
color = Colors.pink_03,
|
|
108
|
+
modifier = Modifier
|
|
109
|
+
.semantics { contentDescription = "$automationId|text" },
|
|
110
|
+
)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
else -> {
|
|
114
|
+
Icon(
|
|
115
|
+
source = "basic_person",
|
|
116
|
+
size = size.iconSizeDp.dp,
|
|
117
|
+
color = Colors.pink_03,
|
|
118
|
+
modifier = Modifier
|
|
119
|
+
.semantics { contentDescription = "$automationId|icon" },
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Badge icons — outside clip so they are not cut by border
|
|
126
|
+
if (shouldShowIconTop) {
|
|
127
|
+
Image(
|
|
128
|
+
source = LOGO_MOMO_URL,
|
|
129
|
+
modifier = Modifier
|
|
130
|
+
.zIndex(1f)
|
|
131
|
+
.align(Alignment.TopEnd)
|
|
132
|
+
.offset(x = 1.dp, y = (-1).dp)
|
|
133
|
+
.size(badgeSizeDp)
|
|
134
|
+
.clip(badgeShape)
|
|
135
|
+
.semantics { contentDescription = "$automationId|top-icon" },
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (shouldShowIconBottom) {
|
|
140
|
+
Image(
|
|
141
|
+
source = iconSupport,
|
|
142
|
+
modifier = Modifier
|
|
143
|
+
.zIndex(1f)
|
|
144
|
+
.align(Alignment.BottomEnd)
|
|
145
|
+
.offset(x = 1.dp, y = 1.dp)
|
|
146
|
+
.size(badgeSizeDp)
|
|
147
|
+
.clip(badgeShape)
|
|
148
|
+
.semantics { contentDescription = "$automationId|bottom-icon" },
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private fun avatarInitials(name: String): String {
|
|
155
|
+
val words = name.trim().split(" ").filter { it.isNotEmpty() }
|
|
156
|
+
return words.takeLast(2).joinToString("") { it.first().uppercaseChar().toString() }
|
|
157
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
package vn.momo.kits.components
|
|
2
|
+
|
|
3
|
+
import androidx.compose.animation.core.animateFloatAsState
|
|
4
|
+
import androidx.compose.animation.core.tween
|
|
5
|
+
import androidx.compose.foundation.border
|
|
6
|
+
import androidx.compose.foundation.layout.Box
|
|
7
|
+
import androidx.compose.foundation.layout.PaddingValues
|
|
8
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
9
|
+
import androidx.compose.foundation.pager.HorizontalPager
|
|
10
|
+
import androidx.compose.foundation.pager.rememberPagerState
|
|
11
|
+
import androidx.compose.runtime.Composable
|
|
12
|
+
import androidx.compose.runtime.LaunchedEffect
|
|
13
|
+
import androidx.compose.runtime.getValue
|
|
14
|
+
import androidx.compose.runtime.mutableStateOf
|
|
15
|
+
import androidx.compose.runtime.remember
|
|
16
|
+
import androidx.compose.runtime.setValue
|
|
17
|
+
import androidx.compose.runtime.snapshotFlow
|
|
18
|
+
import androidx.compose.ui.Modifier
|
|
19
|
+
import androidx.compose.ui.graphics.graphicsLayer
|
|
20
|
+
import androidx.compose.ui.unit.dp
|
|
21
|
+
import kotlinx.coroutines.delay
|
|
22
|
+
import kotlinx.coroutines.isActive
|
|
23
|
+
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
24
|
+
import vn.momo.kits.const.Colors
|
|
25
|
+
import vn.momo.kits.modifier.conditional
|
|
26
|
+
import kotlin.math.absoluteValue
|
|
27
|
+
|
|
28
|
+
@Composable
|
|
29
|
+
fun Carousel(
|
|
30
|
+
itemCount: Int,
|
|
31
|
+
modifier: Modifier = Modifier,
|
|
32
|
+
loop: Boolean = false,
|
|
33
|
+
autoplay: Boolean = false,
|
|
34
|
+
autoplayInterval: Long = 3000L,
|
|
35
|
+
enableSnap: Boolean = true,
|
|
36
|
+
scrollEnabled: Boolean = true,
|
|
37
|
+
inactiveSlideOpacity: Float = 1f,
|
|
38
|
+
inactiveSlideScale: Float = 1f,
|
|
39
|
+
contentPadding: PaddingValues = PaddingValues(0.dp),
|
|
40
|
+
onSnapToItem: (Int) -> Unit = {},
|
|
41
|
+
onScrollIndexChanged: (Int) -> Unit = {},
|
|
42
|
+
content: @Composable (index: Int) -> Unit,
|
|
43
|
+
) {
|
|
44
|
+
if (itemCount <= 0) return
|
|
45
|
+
|
|
46
|
+
val virtualPageCount = if (loop) itemCount * 1000 else itemCount
|
|
47
|
+
val startPage = if (loop) (virtualPageCount / 2) - ((virtualPageCount / 2) % itemCount) else 0
|
|
48
|
+
|
|
49
|
+
val pagerState = rememberPagerState(initialPage = startPage) { virtualPageCount }
|
|
50
|
+
|
|
51
|
+
// Map virtual page to real index
|
|
52
|
+
val realIndex: (Int) -> Int = { page -> page % itemCount }
|
|
53
|
+
|
|
54
|
+
// Track user interaction for autoplay pause
|
|
55
|
+
var isUserScrolling by remember { mutableStateOf(false) }
|
|
56
|
+
|
|
57
|
+
// Autoplay
|
|
58
|
+
if (autoplay && itemCount > 1) {
|
|
59
|
+
LaunchedEffect(autoplayInterval, isUserScrolling) {
|
|
60
|
+
if (isUserScrolling) return@LaunchedEffect
|
|
61
|
+
while (isActive) {
|
|
62
|
+
delay(autoplayInterval)
|
|
63
|
+
if (!isUserScrolling) {
|
|
64
|
+
val nextPage = pagerState.currentPage + 1
|
|
65
|
+
if (nextPage < virtualPageCount) {
|
|
66
|
+
pagerState.animateScrollToPage(nextPage)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Track scroll state for callbacks
|
|
74
|
+
LaunchedEffect(pagerState) {
|
|
75
|
+
snapshotFlow { pagerState.isScrollInProgress }.collect { scrolling ->
|
|
76
|
+
isUserScrolling = scrolling
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
LaunchedEffect(pagerState) {
|
|
81
|
+
snapshotFlow { pagerState.currentPage }.collect { page ->
|
|
82
|
+
val real = realIndex(page)
|
|
83
|
+
onSnapToItem(real)
|
|
84
|
+
onScrollIndexChanged(real)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
HorizontalPager(
|
|
89
|
+
state = pagerState,
|
|
90
|
+
modifier = modifier
|
|
91
|
+
.fillMaxWidth()
|
|
92
|
+
.conditional(IsShowBaseLineDebug) { border(1.dp, Colors.blue_03) },
|
|
93
|
+
contentPadding = contentPadding,
|
|
94
|
+
userScrollEnabled = scrollEnabled,
|
|
95
|
+
beyondViewportPageCount = if (loop) 1 else 0,
|
|
96
|
+
) { page ->
|
|
97
|
+
val real = realIndex(page)
|
|
98
|
+
|
|
99
|
+
val pageOffset = ((pagerState.currentPage - page) + pagerState.currentPageOffsetFraction).absoluteValue
|
|
100
|
+
|
|
101
|
+
val scale by animateFloatAsState(
|
|
102
|
+
targetValue = if (pageOffset < 0.5f) 1f else inactiveSlideScale,
|
|
103
|
+
animationSpec = tween(150),
|
|
104
|
+
label = "carouselScale"
|
|
105
|
+
)
|
|
106
|
+
val alpha by animateFloatAsState(
|
|
107
|
+
targetValue = if (pageOffset < 0.5f) 1f else inactiveSlideOpacity,
|
|
108
|
+
animationSpec = tween(150),
|
|
109
|
+
label = "carouselAlpha"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
Box(
|
|
113
|
+
modifier = Modifier
|
|
114
|
+
.graphicsLayer {
|
|
115
|
+
scaleX = scale
|
|
116
|
+
scaleY = scale
|
|
117
|
+
this.alpha = alpha
|
|
118
|
+
}
|
|
119
|
+
) {
|
|
120
|
+
content(real)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
package vn.momo.kits.components
|
|
2
|
+
|
|
3
|
+
import androidx.compose.animation.AnimatedVisibility
|
|
4
|
+
import androidx.compose.animation.core.animateFloatAsState
|
|
5
|
+
import androidx.compose.animation.core.tween
|
|
6
|
+
import androidx.compose.animation.expandVertically
|
|
7
|
+
import androidx.compose.animation.shrinkVertically
|
|
8
|
+
import androidx.compose.foundation.background
|
|
9
|
+
import androidx.compose.foundation.border
|
|
10
|
+
import androidx.compose.foundation.layout.Box
|
|
11
|
+
import androidx.compose.foundation.layout.Column
|
|
12
|
+
import androidx.compose.foundation.layout.Row
|
|
13
|
+
import androidx.compose.foundation.layout.Spacer
|
|
14
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
15
|
+
import androidx.compose.foundation.layout.padding
|
|
16
|
+
import androidx.compose.foundation.layout.size
|
|
17
|
+
import androidx.compose.foundation.layout.width
|
|
18
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
19
|
+
import androidx.compose.runtime.Composable
|
|
20
|
+
import androidx.compose.runtime.getValue
|
|
21
|
+
import androidx.compose.runtime.mutableStateOf
|
|
22
|
+
import androidx.compose.runtime.remember
|
|
23
|
+
import androidx.compose.runtime.setValue
|
|
24
|
+
import androidx.compose.ui.Alignment
|
|
25
|
+
import androidx.compose.ui.Modifier
|
|
26
|
+
import androidx.compose.ui.draw.clip
|
|
27
|
+
import androidx.compose.ui.draw.rotate
|
|
28
|
+
import androidx.compose.ui.graphics.Color
|
|
29
|
+
import androidx.compose.ui.text.TextStyle
|
|
30
|
+
import androidx.compose.ui.unit.dp
|
|
31
|
+
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
32
|
+
import vn.momo.kits.const.AppTheme
|
|
33
|
+
import vn.momo.kits.const.Colors
|
|
34
|
+
import vn.momo.kits.const.Radius
|
|
35
|
+
import vn.momo.kits.const.Spacing
|
|
36
|
+
import vn.momo.kits.const.Typography
|
|
37
|
+
import vn.momo.kits.modifier.activeOpacityClickable
|
|
38
|
+
import vn.momo.kits.modifier.conditional
|
|
39
|
+
import vn.momo.kits.modifier.setAutomationId
|
|
40
|
+
|
|
41
|
+
enum class CollapseTitleSize {
|
|
42
|
+
Small, Medium, Large
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Composable
|
|
46
|
+
fun Collapse(
|
|
47
|
+
title: String,
|
|
48
|
+
modifier: Modifier = Modifier,
|
|
49
|
+
description: String? = null,
|
|
50
|
+
image: String? = null,
|
|
51
|
+
imageSize: Int = 24,
|
|
52
|
+
subTitle: String? = null,
|
|
53
|
+
tagLabel: String? = null,
|
|
54
|
+
tagColor: TagColor = TagColor.Default,
|
|
55
|
+
showBorder: Boolean = false,
|
|
56
|
+
expandDefault: Boolean = false,
|
|
57
|
+
titleSize: CollapseTitleSize = CollapseTitleSize.Medium,
|
|
58
|
+
useBackgroundCollapseButton: Boolean = false,
|
|
59
|
+
backgroundColor: Color? = null,
|
|
60
|
+
titleColor: Color? = null,
|
|
61
|
+
onPress: ((isExpanded: Boolean) -> Unit)? = null,
|
|
62
|
+
accessibilityId: String? = null,
|
|
63
|
+
content: (@Composable () -> Unit)? = null,
|
|
64
|
+
) {
|
|
65
|
+
val theme = AppTheme.current
|
|
66
|
+
|
|
67
|
+
var expanded by remember { mutableStateOf(expandDefault) }
|
|
68
|
+
|
|
69
|
+
val chevronRotation by animateFloatAsState(
|
|
70
|
+
targetValue = if (expanded) 180f else 0f,
|
|
71
|
+
animationSpec = tween(durationMillis = 300),
|
|
72
|
+
label = "chevronRotation"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
val resolvedBackgroundColor = remember(backgroundColor, theme) {
|
|
76
|
+
backgroundColor ?: theme.colors.background.surface
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
val resolvedTitleStyle: TextStyle = remember(titleSize) {
|
|
80
|
+
when (titleSize) {
|
|
81
|
+
CollapseTitleSize.Small -> Typography.headerSSemibold
|
|
82
|
+
CollapseTitleSize.Medium -> Typography.headerDefaultBold
|
|
83
|
+
CollapseTitleSize.Large -> Typography.headerMBold
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
val borderShape = remember { RoundedCornerShape(Radius.S) }
|
|
88
|
+
|
|
89
|
+
val automationId = remember(accessibilityId, title) {
|
|
90
|
+
accessibilityId ?: "Collapse/$title"
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Column(
|
|
94
|
+
modifier = modifier
|
|
95
|
+
.fillMaxWidth()
|
|
96
|
+
.clip(borderShape)
|
|
97
|
+
.background(resolvedBackgroundColor, shape = borderShape)
|
|
98
|
+
.conditional(showBorder) {
|
|
99
|
+
border(
|
|
100
|
+
width = 1.dp,
|
|
101
|
+
color = theme.colors.border.default,
|
|
102
|
+
shape = borderShape
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
.conditional(IsShowBaseLineDebug) {
|
|
106
|
+
border(1.dp, Colors.blue_03)
|
|
107
|
+
}
|
|
108
|
+
.setAutomationId(automationId)
|
|
109
|
+
) {
|
|
110
|
+
Row(
|
|
111
|
+
modifier = Modifier
|
|
112
|
+
.fillMaxWidth()
|
|
113
|
+
.activeOpacityClickable {
|
|
114
|
+
onPress?.invoke(expanded)
|
|
115
|
+
expanded = !expanded
|
|
116
|
+
}
|
|
117
|
+
.padding(horizontal = Spacing.M, vertical = Spacing.S)
|
|
118
|
+
.setAutomationId("$automationId|touch"),
|
|
119
|
+
verticalAlignment = Alignment.Top
|
|
120
|
+
) {
|
|
121
|
+
if (image != null) {
|
|
122
|
+
Image(
|
|
123
|
+
source = image,
|
|
124
|
+
modifier = Modifier
|
|
125
|
+
.size(imageSize.dp)
|
|
126
|
+
.clip(RoundedCornerShape(Radius.XS))
|
|
127
|
+
.setAutomationId("$automationId|img")
|
|
128
|
+
)
|
|
129
|
+
Spacer(modifier = Modifier.width(Spacing.S))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Column(modifier = Modifier.weight(1f)) {
|
|
133
|
+
Text(
|
|
134
|
+
text = title,
|
|
135
|
+
style = resolvedTitleStyle,
|
|
136
|
+
color = titleColor ?: theme.colors.text.default,
|
|
137
|
+
accessibilityId = "$automationId|text"
|
|
138
|
+
)
|
|
139
|
+
if (description != null) {
|
|
140
|
+
Text(
|
|
141
|
+
text = description,
|
|
142
|
+
style = Typography.bodyDefaultRegular,
|
|
143
|
+
color = theme.colors.text.secondary,
|
|
144
|
+
maxLines = 2,
|
|
145
|
+
accessibilityId = "$automationId|description-text"
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
val hasInfo = subTitle != null || tagLabel != null
|
|
151
|
+
if (hasInfo) {
|
|
152
|
+
Spacer(modifier = Modifier.width(Spacing.S))
|
|
153
|
+
Box(contentAlignment = Alignment.CenterEnd) {
|
|
154
|
+
if (subTitle != null) {
|
|
155
|
+
Text(
|
|
156
|
+
text = subTitle,
|
|
157
|
+
style = Typography.bodyDefaultRegular,
|
|
158
|
+
color = theme.colors.text.secondary,
|
|
159
|
+
maxLines = 1,
|
|
160
|
+
accessibilityId = "$automationId|sub-title-text"
|
|
161
|
+
)
|
|
162
|
+
} else if (tagLabel != null) {
|
|
163
|
+
Tag(
|
|
164
|
+
label = tagLabel,
|
|
165
|
+
color = tagColor
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
Spacer(modifier = Modifier.width(Spacing.S))
|
|
172
|
+
if (useBackgroundCollapseButton) {
|
|
173
|
+
Box(
|
|
174
|
+
modifier = Modifier
|
|
175
|
+
.size(24.dp)
|
|
176
|
+
.background(
|
|
177
|
+
color = theme.colors.background.tonal,
|
|
178
|
+
shape = RoundedCornerShape(Radius.M)
|
|
179
|
+
),
|
|
180
|
+
contentAlignment = Alignment.Center
|
|
181
|
+
) {
|
|
182
|
+
Icon(
|
|
183
|
+
source = "arrow_chevron_down_small",
|
|
184
|
+
size = 16.dp,
|
|
185
|
+
color = theme.colors.primary,
|
|
186
|
+
modifier = Modifier
|
|
187
|
+
.rotate(chevronRotation)
|
|
188
|
+
.setAutomationId("$automationId|icon")
|
|
189
|
+
)
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
Icon(
|
|
193
|
+
source = "arrow_chevron_down_small",
|
|
194
|
+
size = 24.dp,
|
|
195
|
+
color = theme.colors.text.default,
|
|
196
|
+
modifier = Modifier
|
|
197
|
+
.rotate(chevronRotation)
|
|
198
|
+
.setAutomationId("$automationId|icon")
|
|
199
|
+
)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (content != null) {
|
|
204
|
+
AnimatedVisibility(
|
|
205
|
+
visible = expanded,
|
|
206
|
+
enter = expandVertically(animationSpec = tween(durationMillis = 300)),
|
|
207
|
+
exit = shrinkVertically(animationSpec = tween(durationMillis = 300))
|
|
208
|
+
) {
|
|
209
|
+
Column(
|
|
210
|
+
modifier = Modifier
|
|
211
|
+
.fillMaxWidth()
|
|
212
|
+
.conditional(showBorder) {
|
|
213
|
+
border(
|
|
214
|
+
width = 1.dp,
|
|
215
|
+
color = theme.colors.border.default
|
|
216
|
+
)
|
|
217
|
+
}
|
|
218
|
+
) {
|
|
219
|
+
content()
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
package vn.momo.kits.components
|
|
2
|
+
|
|
3
|
+
import androidx.compose.animation.core.FastOutSlowInEasing
|
|
4
|
+
import androidx.compose.animation.core.animateFloatAsState
|
|
5
|
+
import androidx.compose.animation.core.tween
|
|
6
|
+
import androidx.compose.foundation.background
|
|
7
|
+
import androidx.compose.foundation.border
|
|
8
|
+
import androidx.compose.foundation.layout.Box
|
|
9
|
+
import androidx.compose.foundation.layout.fillMaxWidth
|
|
10
|
+
import androidx.compose.foundation.layout.height
|
|
11
|
+
import androidx.compose.foundation.layout.size
|
|
12
|
+
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
13
|
+
import androidx.compose.runtime.Composable
|
|
14
|
+
import androidx.compose.runtime.getValue
|
|
15
|
+
import androidx.compose.ui.Modifier
|
|
16
|
+
import androidx.compose.ui.draw.clip
|
|
17
|
+
import androidx.compose.ui.graphics.Color
|
|
18
|
+
import androidx.compose.ui.unit.dp
|
|
19
|
+
import vn.momo.kits.application.IsShowBaseLineDebug
|
|
20
|
+
import vn.momo.kits.const.AppTheme
|
|
21
|
+
import vn.momo.kits.const.Colors
|
|
22
|
+
import vn.momo.kits.const.Radius
|
|
23
|
+
import vn.momo.kits.modifier.conditional
|
|
24
|
+
import vn.momo.kits.platform.LottieAnimation
|
|
25
|
+
|
|
26
|
+
enum class LoaderType {
|
|
27
|
+
DOT, SPINNER
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@Composable
|
|
31
|
+
fun Loader(
|
|
32
|
+
type: LoaderType = LoaderType.DOT,
|
|
33
|
+
tintColor: Color? = null,
|
|
34
|
+
modifier: Modifier = Modifier
|
|
35
|
+
) {
|
|
36
|
+
when (type) {
|
|
37
|
+
LoaderType.DOT -> DotLoader(tintColor = tintColor, modifier = modifier)
|
|
38
|
+
LoaderType.SPINNER -> SpinnerLoader(tintColor = tintColor, modifier = modifier)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Composable
|
|
43
|
+
fun DotLoader(
|
|
44
|
+
tintColor: Color? = null,
|
|
45
|
+
modifier: Modifier = Modifier
|
|
46
|
+
) {
|
|
47
|
+
LottieAnimation(
|
|
48
|
+
path = "files/dot_loading",
|
|
49
|
+
tintColor = tintColor,
|
|
50
|
+
modifier = modifier
|
|
51
|
+
.size(width = 52.dp, height = 18.dp)
|
|
52
|
+
.conditional(IsShowBaseLineDebug) {
|
|
53
|
+
border(1.dp, Colors.blue_03)
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Composable
|
|
59
|
+
fun SpinnerLoader(
|
|
60
|
+
tintColor: Color? = null,
|
|
61
|
+
modifier: Modifier = Modifier
|
|
62
|
+
) {
|
|
63
|
+
LottieAnimation(
|
|
64
|
+
path = "files/lottie_circle_loader",
|
|
65
|
+
tintColor = tintColor,
|
|
66
|
+
modifier = modifier
|
|
67
|
+
.size(24.dp)
|
|
68
|
+
.conditional(IsShowBaseLineDebug) {
|
|
69
|
+
border(1.dp, Colors.blue_03)
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Composable
|
|
75
|
+
fun ProgressBar(
|
|
76
|
+
percent: Float = 0f,
|
|
77
|
+
color: Color? = null,
|
|
78
|
+
modifier: Modifier = Modifier
|
|
79
|
+
) {
|
|
80
|
+
val clampedPercent = percent.coerceIn(0f, 100f)
|
|
81
|
+
val animatedProgress by animateFloatAsState(
|
|
82
|
+
targetValue = clampedPercent / 100f,
|
|
83
|
+
animationSpec = tween(durationMillis = 200, easing = FastOutSlowInEasing),
|
|
84
|
+
label = "progressAnimation"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
val trackColor = AppTheme.current.colors.background.default
|
|
88
|
+
val fillColor = color ?: AppTheme.current.colors.primary
|
|
89
|
+
|
|
90
|
+
Box(
|
|
91
|
+
modifier = modifier
|
|
92
|
+
.fillMaxWidth()
|
|
93
|
+
.height(4.dp)
|
|
94
|
+
.clip(RoundedCornerShape(Radius.XXS))
|
|
95
|
+
.background(trackColor)
|
|
96
|
+
.conditional(IsShowBaseLineDebug) {
|
|
97
|
+
border(1.dp, Colors.blue_03)
|
|
98
|
+
}
|
|
99
|
+
) {
|
|
100
|
+
Box(
|
|
101
|
+
modifier = Modifier
|
|
102
|
+
.fillMaxWidth(animatedProgress)
|
|
103
|
+
.height(4.dp)
|
|
104
|
+
.clip(RoundedCornerShape(Radius.XXS))
|
|
105
|
+
.background(fillColor)
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -44,13 +44,13 @@ fun PopupPromotion(
|
|
|
44
44
|
Image(
|
|
45
45
|
source = source,
|
|
46
46
|
modifier = Modifier.fillMaxWidth()
|
|
47
|
-
.aspectRatio(0.
|
|
47
|
+
.aspectRatio(0.72f)
|
|
48
48
|
.clickable(
|
|
49
49
|
interactionSource = remember { MutableInteractionSource() },
|
|
50
50
|
indication = null,
|
|
51
51
|
onClick = onPress
|
|
52
52
|
),
|
|
53
|
-
options = Options(alignment = Alignment.Center, contentScale = ContentScale.
|
|
53
|
+
options = Options(alignment = Alignment.Center, contentScale = ContentScale.FillBounds)
|
|
54
54
|
), null
|
|
55
55
|
)
|
|
56
56
|
Box(
|