@momo-kits/native-kits 0.113.1-beta.6 → 0.113.1-beta.7

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.
@@ -0,0 +1,189 @@
1
+ package vn.momo.kits.application
2
+
3
+ import androidx.compose.animation.animateColorAsState
4
+ import androidx.compose.foundation.background
5
+ import androidx.compose.foundation.border
6
+ import androidx.compose.foundation.clickable
7
+ import androidx.compose.foundation.layout.Arrangement
8
+ import androidx.compose.foundation.layout.Box
9
+ import androidx.compose.foundation.layout.BoxWithConstraints
10
+ import androidx.compose.foundation.layout.Row
11
+ import androidx.compose.foundation.layout.Spacer
12
+ import androidx.compose.foundation.layout.fillMaxWidth
13
+ import androidx.compose.foundation.layout.height
14
+ import androidx.compose.foundation.layout.padding
15
+ import androidx.compose.foundation.layout.size
16
+ import androidx.compose.foundation.layout.width
17
+ import androidx.compose.foundation.shape.RoundedCornerShape
18
+ import androidx.compose.runtime.Composable
19
+ import androidx.compose.runtime.LaunchedEffect
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.runtime.snapshotFlow
25
+ import androidx.compose.ui.Alignment
26
+ import androidx.compose.ui.Modifier
27
+ import androidx.compose.ui.graphics.Color
28
+ import androidx.compose.ui.graphics.graphicsLayer
29
+ import androidx.compose.ui.semantics.semantics
30
+ import androidx.compose.ui.semantics.testTag
31
+ import androidx.compose.ui.unit.dp
32
+ import androidx.compose.ui.zIndex
33
+ import kotlinx.coroutines.flow.distinctUntilChanged
34
+ import kotlinx.coroutines.flow.map
35
+ import vn.momo.kits.components.Icon
36
+ import vn.momo.kits.components.InputSearch
37
+ import vn.momo.kits.components.InputSearchProps
38
+ import vn.momo.kits.const.AppStatusBar
39
+ import vn.momo.kits.const.AppTheme
40
+ import vn.momo.kits.const.Colors
41
+ import vn.momo.kits.const.Spacing
42
+ import vn.momo.kits.modifier.setAutomationId
43
+ import vn.momo.kits.platform.getStatusBarHeight
44
+
45
+ enum class TitlePosition {
46
+ LEFT, CENTER,
47
+ }
48
+
49
+ data class AnimatedHeader(
50
+ val type: HeaderType = HeaderType.DEFAULT,
51
+ val composable: @Composable (scrollState: Int) -> Unit = {}
52
+ )
53
+
54
+ @Composable
55
+ fun Header(
56
+ headerType: HeaderType = HeaderType.DEFAULT,
57
+ titlePosition: TitlePosition,
58
+ title: String,
59
+ headerRight: @Composable (() -> Unit)? = null,
60
+ goBack: (() -> Unit)? = null,
61
+ opacity: Float? = null,
62
+ animatedHeader: AnimatedHeader? = null,
63
+ scrollState: Int = 0,
64
+ inputSearchProps: InputSearchProps? = null,
65
+ useAnimationSearch: Boolean = false
66
+ ) {
67
+ val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
68
+ val tintIconColor = AppTheme.current.colors.text.default
69
+ val backgroundButton = Colors.black_01.copy(alpha = 0.6f)
70
+ val miniAppContext = ApplicationContext.current
71
+
72
+ var colorFraction by remember { mutableStateOf(0f) }
73
+
74
+ LaunchedEffect(scrollState) {
75
+ snapshotFlow { scrollState }
76
+ .map { (it / 50f).coerceIn(0f, 1f) }
77
+ .distinctUntilChanged()
78
+ .collect { fraction -> colorFraction = fraction }
79
+ }
80
+
81
+ val backgroundSearch by animateColorAsState(
82
+ targetValue = animateColor(
83
+ Colors.black_01,
84
+ AppTheme.current.colors.background.default,
85
+ colorFraction
86
+ )
87
+ )
88
+
89
+ if (headerType != HeaderType.NONE) {
90
+ BoxWithConstraints(
91
+ Modifier.height(statusBarHeight + 52.dp)
92
+ .fillMaxWidth()
93
+ .zIndex(10f),
94
+ contentAlignment = Alignment.BottomCenter
95
+ ) {
96
+ Row(
97
+ modifier = Modifier.height(52.dp)
98
+ .fillMaxWidth()
99
+ .padding(horizontal = Spacing.M),
100
+ verticalAlignment = Alignment.CenterVertically,
101
+ horizontalArrangement = Arrangement.SpaceBetween
102
+ ) {
103
+ Box {
104
+ if (goBack != null) {
105
+ Box(
106
+ modifier = Modifier
107
+ .size(28.dp)
108
+ .then(
109
+ Modifier.border(
110
+ 0.2.dp,
111
+ Colors.black_20.copy(alpha = 0.2f),
112
+ RoundedCornerShape(100)
113
+ )
114
+ )
115
+ .background(
116
+ backgroundButton,
117
+ RoundedCornerShape(100)
118
+ )
119
+ .clickable(
120
+ onClick = goBack
121
+ )
122
+ .padding(Spacing.XS)
123
+ .setAutomationId("btn_navigation_back"),
124
+ contentAlignment = Alignment.Center
125
+ ) {
126
+ Icon(
127
+ source = "arrow-back",
128
+ color = Colors.black_20,
129
+ size = 20.dp,
130
+ )
131
+ }
132
+ }
133
+ }
134
+
135
+ if (inputSearchProps == null && miniAppContext != null) {
136
+ Box(Modifier.graphicsLayer {
137
+ alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
138
+ }) {
139
+ headerRight?.invoke()
140
+ }
141
+ }
142
+ }
143
+
144
+ // Title
145
+ Row(
146
+ modifier = Modifier.height(52.dp)
147
+ .fillMaxWidth()
148
+ .padding(horizontal = Spacing.M),
149
+ verticalAlignment = Alignment.CenterVertically,
150
+ ) {
151
+ if (goBack != null && titlePosition == TitlePosition.LEFT) {
152
+ Spacer(Modifier.width(40.dp))
153
+ }
154
+ if (inputSearchProps != null && !useAnimationSearch) {
155
+ InputSearch(inputSearchProps = inputSearchProps.copy(backgroundColor = backgroundSearch))
156
+ } else {
157
+ Box(
158
+ Modifier.weight(1f).graphicsLayer {
159
+ alpha = if (useAnimationSearch && inputSearchProps != null) {
160
+ 1f - (opacity ?: 1f)
161
+ } else {
162
+ if (animatedHeader !== null) opacity ?: 1f else 1f
163
+ }
164
+ },
165
+ contentAlignment = if (titlePosition == TitlePosition.LEFT) Alignment.TopStart else Alignment.Center
166
+ ) {
167
+ HeaderTitle(
168
+ title = title,
169
+ color = tintIconColor,
170
+ modifier = Modifier.semantics {
171
+ testTag = "title_navigation_header"
172
+ }
173
+ )
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+
182
+ fun animateColor(start: Color, stop: Color, fraction: Float): Color {
183
+ return Color(
184
+ red = start.red + fraction * (stop.red - start.red),
185
+ green = start.green + fraction * (stop.green - start.green),
186
+ blue = start.blue + fraction * (stop.blue - start.blue),
187
+ alpha = start.alpha + fraction * (stop.alpha - start.alpha)
188
+ )
189
+ }
@@ -0,0 +1,70 @@
1
+ package vn.momo.kits.application
2
+
3
+ import androidx.compose.foundation.layout.Box
4
+ import androidx.compose.foundation.layout.fillMaxWidth
5
+ import androidx.compose.runtime.Composable
6
+ import androidx.compose.ui.Modifier
7
+ import androidx.compose.ui.graphics.Color
8
+ import androidx.compose.ui.unit.Dp
9
+ import androidx.compose.ui.unit.dp
10
+ import vn.momo.kits.components.InputSearchProps
11
+ import vn.momo.kits.const.AppStatusBar
12
+ import vn.momo.kits.const.AppTheme
13
+ import vn.momo.kits.platform.getScreenDimensions
14
+ import vn.momo.kits.platform.getStatusBarHeight
15
+
16
+ private const val SCREEN_PADDING = 12
17
+ private const val BACK_WIDTH = 28
18
+
19
+ @Composable
20
+ fun HeaderBackground(
21
+ isBack: Boolean = true,
22
+ headerType: HeaderType = HeaderType.DEFAULT,
23
+ scrollState: Int,
24
+ inputSearchProps: InputSearchProps? = null,
25
+ headerRightWidth: Dp = 0.dp,
26
+ useAnimationSearch: Boolean = false
27
+ ) {
28
+ val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
29
+ val screenWidth = getScreenDimensions().width
30
+ val leftPosition = if (isBack) (BACK_WIDTH + 20).dp else 20.dp
31
+ val searchWidth = screenWidth - (SCREEN_PADDING * 2)
32
+
33
+ val animations = useHeaderSearchAnimation(
34
+ scrollState = scrollState,
35
+ statusBarHeight = statusBarHeight,
36
+ leftPosition = leftPosition,
37
+ searchWidth = searchWidth,
38
+ headerRightWidth = headerRightWidth
39
+ )
40
+
41
+ val backgroundColor = if (scrollState == 0) {
42
+ Color.Transparent
43
+ } else {
44
+ AppTheme.current.colors.background.surface
45
+ }
46
+
47
+ when (headerType) {
48
+ HeaderType.DEFAULT -> {
49
+ val height = statusBarHeight + 52.dp
50
+ HeaderDefault(
51
+ height = height,
52
+ animations = animations,
53
+ backgroundColor = backgroundColor
54
+ )
55
+ }
56
+
57
+ HeaderType.EXTENDED -> {
58
+ HeaderExtended(
59
+ animations = animations,
60
+ inputSearchProps = inputSearchProps,
61
+ useAnimationSearch = useAnimationSearch,
62
+ backgroundColor = backgroundColor
63
+ )
64
+ }
65
+
66
+ else -> {
67
+ Box(modifier = Modifier.fillMaxWidth())
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,63 @@
1
+ package vn.momo.kits.application
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.layout.Box
5
+ import androidx.compose.foundation.layout.fillMaxWidth
6
+ import androidx.compose.foundation.layout.height
7
+ import androidx.compose.runtime.Composable
8
+ import androidx.compose.ui.Modifier
9
+ import androidx.compose.ui.draw.alpha
10
+ import androidx.compose.ui.geometry.Offset
11
+ import androidx.compose.ui.graphics.Brush
12
+ import androidx.compose.ui.graphics.Color
13
+ import androidx.compose.ui.layout.ContentScale
14
+ import androidx.compose.ui.platform.LocalDensity
15
+ import androidx.compose.ui.unit.Dp
16
+ import androidx.compose.ui.unit.dp
17
+ import vn.momo.kits.components.Image
18
+ import vn.momo.kits.components.Options
19
+ import vn.momo.kits.const.AppTheme
20
+ import vn.momo.kits.utils.bottomBorder
21
+
22
+ @Composable
23
+ fun HeaderDefault(
24
+ height: Dp,
25
+ backgroundColor: Color,
26
+ animations: HeaderAnimations
27
+ ) {
28
+ Box(
29
+ modifier = Modifier.fillMaxWidth().height(height)
30
+ .background(backgroundColor)
31
+ .bottomBorder(
32
+ strokeWidth = 1.dp,
33
+ color = AppTheme.current.colors.border.default
34
+ )
35
+ ) {
36
+ Box(
37
+ modifier = Modifier
38
+ .height(height)
39
+ .fillMaxWidth()
40
+ .alpha(animations.opacity)
41
+ .background(
42
+ Brush.linearGradient(
43
+ colors = listOf(
44
+ Color(0xFFFDCADE),
45
+ Color(0x00FDCADE)
46
+ ),
47
+ start = Offset(0f, 0f),
48
+ end = Offset(0f, with(LocalDensity.current) { 154.dp.toPx() })
49
+ )
50
+ )
51
+ )
52
+ if (AppTheme.current.assets.headerBackground != null) {
53
+ Image(
54
+ loading = false,
55
+ source = AppTheme.current.assets.headerBackground!!,
56
+ modifier = Modifier.fillMaxWidth().height(height),
57
+ options = Options(
58
+ contentScale = ContentScale.FillWidth
59
+ )
60
+ )
61
+ }
62
+ }
63
+ }
@@ -0,0 +1,102 @@
1
+ package vn.momo.kits.application
2
+
3
+ import androidx.compose.foundation.background
4
+ import androidx.compose.foundation.layout.Box
5
+ import androidx.compose.foundation.layout.fillMaxWidth
6
+ import androidx.compose.foundation.layout.height
7
+ import androidx.compose.foundation.layout.offset
8
+ import androidx.compose.foundation.layout.padding
9
+ import androidx.compose.foundation.layout.width
10
+ import androidx.compose.foundation.shape.RoundedCornerShape
11
+ import androidx.compose.runtime.Composable
12
+ import androidx.compose.ui.Alignment
13
+ import androidx.compose.ui.Modifier
14
+ import androidx.compose.ui.draw.alpha
15
+ import androidx.compose.ui.geometry.Offset
16
+ import androidx.compose.ui.graphics.Brush
17
+ import androidx.compose.ui.graphics.Color
18
+ import androidx.compose.ui.layout.ContentScale
19
+ import androidx.compose.ui.platform.LocalDensity
20
+ import androidx.compose.ui.unit.dp
21
+ import vn.momo.kits.components.Image
22
+ import vn.momo.kits.components.InputSearch
23
+ import vn.momo.kits.components.InputSearchProps
24
+ import vn.momo.kits.components.Options
25
+ import vn.momo.kits.const.AppTheme
26
+ import vn.momo.kits.const.Radius
27
+ import vn.momo.kits.const.Spacing
28
+
29
+ @Composable
30
+ fun HeaderExtended(
31
+ animations: HeaderAnimations,
32
+ inputSearchProps: InputSearchProps? = null,
33
+ useAnimationSearch: Boolean = false,
34
+ backgroundColor: Color = Color.Transparent
35
+ ){
36
+ Box(
37
+ modifier = Modifier.fillMaxWidth().height(animations.height.dp)
38
+ ) {
39
+ Box(
40
+ modifier = Modifier
41
+ .fillMaxWidth()
42
+ .height(154.dp)
43
+ .alpha(animations.opacity)
44
+ .background(
45
+ Brush.linearGradient(
46
+ colors = listOf(
47
+ Color(0xFFFDCADE),
48
+ Color(0x00FDCADE)
49
+ ),
50
+ start = Offset(0f, 0f),
51
+ end = Offset(0f, with(LocalDensity.current) { 154.dp.toPx() })
52
+ )
53
+ )
54
+ ) {
55
+ if (AppTheme.current.assets.headerBackground != null) {
56
+ Box(
57
+ modifier = Modifier
58
+ .fillMaxWidth()
59
+ .background(backgroundColor)
60
+ ) {
61
+ Image(
62
+ loading = false,
63
+ source = AppTheme.current.assets.headerBackground!!,
64
+ modifier = Modifier.fillMaxWidth().height(154.dp),
65
+ options = Options(
66
+ contentScale = ContentScale.FillWidth
67
+ )
68
+ )
69
+ }
70
+ }
71
+ }
72
+
73
+ if (inputSearchProps != null && useAnimationSearch) {
74
+ Box(
75
+ modifier = Modifier
76
+ .height(52.dp)
77
+ .offset(
78
+ x = animations.translateX.dp
79
+ )
80
+ .padding(vertical = Spacing.S)
81
+ .align(Alignment.BottomStart)
82
+ ) {
83
+ Box(
84
+ modifier = Modifier
85
+ .width(animations.width.dp)
86
+ .background(
87
+ color = animations.backgroundSearch,
88
+ shape = RoundedCornerShape(Radius.XL)
89
+ )
90
+ ) {
91
+ InputSearch(
92
+ inputSearchProps = inputSearchProps.copy(
93
+ showButtonText = false,
94
+ backgroundColor = Color.Transparent
95
+ )
96
+ )
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+
@@ -0,0 +1,27 @@
1
+ package vn.momo.kits.application
2
+
3
+ import androidx.compose.runtime.Composable
4
+ import androidx.compose.ui.Modifier
5
+ import androidx.compose.ui.graphics.Color
6
+ import androidx.compose.ui.text.style.TextAlign
7
+ import androidx.compose.ui.zIndex
8
+ import vn.momo.kits.components.Text
9
+ import vn.momo.kits.const.Typography
10
+
11
+
12
+ @Composable
13
+ fun HeaderTitle(
14
+ title: String = "",
15
+ modifier: Modifier = Modifier,
16
+ textAlign: TextAlign = TextAlign.Start,
17
+ color: Color? = null,
18
+ ) {
19
+ Text(
20
+ modifier = Modifier.then(modifier).zIndex(1f),
21
+ text = title,
22
+ textAlign = textAlign,
23
+ style = Typography.actionS,
24
+ color = color,
25
+ maxLines = 1
26
+ )
27
+ }
@@ -84,7 +84,7 @@ fun Screen(
84
84
  val bottomPadding = min(indicator, 21.dp)
85
85
 
86
86
  val opacity by animateFloatAsState(
87
- targetValue = ((scrollState.value / 52f)).coerceIn(0f, 1f),
87
+ targetValue = ((scrollState.value / 114f)).coerceIn(0f, 1f)
88
88
  )
89
89
 
90
90
  val headerAnimated =
@@ -0,0 +1,61 @@
1
+ package vn.momo.kits.application
2
+
3
+ import androidx.compose.animation.animateColorAsState
4
+ import androidx.compose.animation.core.animateFloatAsState
5
+ import androidx.compose.runtime.Composable
6
+ import androidx.compose.ui.graphics.Color
7
+ import androidx.compose.ui.unit.Dp
8
+ import vn.momo.kits.const.AppTheme
9
+ import vn.momo.kits.const.Colors
10
+ import androidx.compose.runtime.getValue
11
+
12
+ data class HeaderAnimations(
13
+ val opacity: Float,
14
+ val backgroundSearch: Color,
15
+ val translateX: Float,
16
+ val width: Float,
17
+ val height: Float
18
+ )
19
+
20
+ @Composable
21
+ fun useHeaderSearchAnimation(
22
+ scrollState: Int,
23
+ statusBarHeight: Dp,
24
+ leftPosition: Dp,
25
+ searchWidth: Int,
26
+ headerRightWidth: Dp
27
+ ): HeaderAnimations {
28
+ val opacityAni by animateFloatAsState(
29
+ targetValue = (1 - (scrollState / 52f)).coerceIn(0f, 1f)
30
+ )
31
+
32
+ val backgroundSearch by animateColorAsState(
33
+ targetValue = animateColor(
34
+ AppTheme.current.colors.background.default,
35
+ Colors.black_01,
36
+ opacityAni
37
+ )
38
+ )
39
+
40
+ val animatedTranslateX by animateFloatAsState(
41
+ targetValue = (scrollState / 52f).coerceIn(0f, 1f) * (leftPosition.value - 12) + 12
42
+ )
43
+
44
+ val animatedWidth by animateFloatAsState(
45
+ targetValue = (scrollState / 52f).coerceIn(0f, 1f) *
46
+ ((searchWidth - leftPosition.value - headerRightWidth.value + 12) - searchWidth) + searchWidth
47
+ )
48
+
49
+ val animatedHeight by animateFloatAsState(
50
+ targetValue = (1 - (scrollState / 52f).coerceIn(0f, 1f)) *
51
+ (statusBarHeight.value + 94 - (statusBarHeight.value + 52)) + (statusBarHeight.value + 52)
52
+ )
53
+
54
+ return HeaderAnimations(
55
+ opacity = opacityAni,
56
+ backgroundSearch = backgroundSearch,
57
+ translateX = animatedTranslateX,
58
+ width = animatedWidth,
59
+ height = animatedHeight
60
+ )
61
+ }
@@ -115,7 +115,7 @@ fun PopupNotify(
115
115
  )
116
116
 
117
117
  Column(modifier = Modifier.padding(Spacing.XL)) {
118
- Text(style = Typography.titleXs, maxLines = 2, text = title)
118
+ Text(style = Typography.titleXs, maxLines = 1, text = title)
119
119
  content(Modifier.padding(top = Spacing.S))
120
120
  if (error.isNotEmpty()) {
121
121
  Text(
@@ -98,8 +98,9 @@ private extension View {
98
98
  case .secondary:
99
99
  return AnyView(self
100
100
  .background(Colors.card)
101
- .foregroundColor(Colors.primary)
101
+ .foregroundColor(Colors.text)
102
102
  .cornerRadius(8)
103
+ .overlay(RoundedRectangle(cornerRadius: Radius.S).stroke(Colors.black06, lineWidth: 1))
103
104
  .padding(2)
104
105
  )
105
106
  case .outline:
@@ -43,7 +43,6 @@ public struct PopupDisplay: View {
43
43
  Image(systemName: "xmark.circle.fill")
44
44
  .resizable()
45
45
  .frame(width: 16, height: 16)
46
- .overlay(RoundedRectangle(cornerRadius: Radius.S).stroke(Colors.black01, lineWidth: 1))
47
46
  }.foregroundColor(Colors.black20)
48
47
  .background(Colors.black01.cornerRadius(8))
49
48
  .offset(x: 6, y: -6)
@@ -53,17 +52,15 @@ public struct PopupDisplay: View {
53
52
  WebImage(url: URL(string: url), isAnimating: .constant(true))
54
53
  .resizable()
55
54
  .scaledToFill()
56
- .frame(maxWidth: .infinity)
55
+ .frame(maxWidth: .infinity, maxHeight: 164)
57
56
  .clipShape(RoundedCorner(radius: 8, corners: [.topLeft, .topRight]))
58
- .aspectRatio(2, contentMode: .fit)
59
57
 
60
58
  VStack(alignment: .leading) {
61
59
  Text(title)
62
60
  .foregroundColor(.black)
63
- .font(.title3)
64
- .padding(.top, 16)
65
- .padding(.bottom, 8)
66
- .lineLimit(2)
61
+ .font(.headerText1)
62
+ .padding(.top, 12)
63
+ .padding(.bottom, 24)
67
64
 
68
65
  Text(description)
69
66
  .foregroundColor(.black)
@@ -90,8 +87,10 @@ public struct PopupDisplay: View {
90
87
  }.clipped()
91
88
  }
92
89
  }
93
- .background(Color.white.cornerRadius(15))
94
- .padding(.horizontal, 24)
90
+ .background(Color.white.cornerRadius(8))
91
+ .shadow(color: .black.opacity(0.08), radius: 2, x: 0, y: 0)
92
+ .shadow(color: .black.opacity(0.16), radius: 24, x: 0, y: 0)
93
+ .padding(.horizontal, 40)
95
94
  }
96
95
 
97
96
  // MARK: Internal
@@ -107,7 +106,7 @@ public struct PopupDisplay: View {
107
106
  var onClose: () -> Void
108
107
  var actionButtonTitle: String
109
108
  var closeButtonTitle: String
110
-
109
+
111
110
  var RowTextButtons: some View {
112
111
  HStack {
113
112
  SwiftUI.Button(action: onPressCloseButton) {
@@ -154,10 +153,7 @@ public struct PopupDisplay: View {
154
153
 
155
154
  var ColumnButtons: some View {
156
155
  VStack(alignment: .trailing) {
157
- Button(
158
- title: actionButtonTitle,
159
- action: onPressAction
160
- )
156
+ Button(title: actionButtonTitle, action: onPressAction)
161
157
  Button(title: closeButtonTitle, action: onPressCloseButton, type: .secondary)
162
158
  }
163
159
  .frame(maxWidth: .infinity, alignment: .trailing)
package/local.properties CHANGED
@@ -4,5 +4,5 @@
4
4
  # Location of the SDK. This is only used by Gradle.
5
5
  # For customization when using a Version Control System, please read the
6
6
  # header note.
7
- #Thu Jan 09 10:43:10 ICT 2025
8
- sdk.dir=/Users/sophia/Library/Android/sdk
7
+ #Sun Mar 02 11:30:14 ICT 2025
8
+ sdk.dir=/Users/nguyenhoangson/Library/Android/sdk
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.113.1-beta.6",
3
+ "version": "0.113.1-beta.7",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},
@@ -1,379 +0,0 @@
1
- package vn.momo.kits.application
2
-
3
- import androidx.compose.animation.animateColorAsState
4
- import androidx.compose.animation.core.animateFloatAsState
5
- import androidx.compose.foundation.background
6
- import androidx.compose.foundation.border
7
- import androidx.compose.foundation.clickable
8
- import androidx.compose.foundation.layout.Arrangement
9
- import androidx.compose.foundation.layout.Box
10
- import androidx.compose.foundation.layout.BoxWithConstraints
11
- import androidx.compose.foundation.layout.Row
12
- import androidx.compose.foundation.layout.Spacer
13
- import androidx.compose.foundation.layout.fillMaxWidth
14
- import androidx.compose.foundation.layout.height
15
- import androidx.compose.foundation.layout.offset
16
- import androidx.compose.foundation.layout.padding
17
- import androidx.compose.foundation.layout.size
18
- import androidx.compose.foundation.layout.width
19
- import androidx.compose.foundation.shape.RoundedCornerShape
20
- import androidx.compose.runtime.Composable
21
- import androidx.compose.runtime.LaunchedEffect
22
- import androidx.compose.runtime.getValue
23
- import androidx.compose.runtime.mutableStateOf
24
- import androidx.compose.runtime.remember
25
- import androidx.compose.runtime.setValue
26
- import androidx.compose.runtime.snapshotFlow
27
- import androidx.compose.ui.Alignment
28
- import androidx.compose.ui.Modifier
29
- import androidx.compose.ui.draw.alpha
30
- import androidx.compose.ui.geometry.Offset
31
- import androidx.compose.ui.graphics.Brush
32
- import androidx.compose.ui.graphics.Color
33
- import androidx.compose.ui.graphics.graphicsLayer
34
- import androidx.compose.ui.layout.ContentScale
35
- import androidx.compose.ui.platform.LocalDensity
36
- import androidx.compose.ui.semantics.semantics
37
- import androidx.compose.ui.semantics.testTag
38
- import androidx.compose.ui.text.style.TextAlign
39
- import androidx.compose.ui.unit.Dp
40
- import androidx.compose.ui.unit.dp
41
- import androidx.compose.ui.zIndex
42
- import kotlinx.coroutines.flow.distinctUntilChanged
43
- import kotlinx.coroutines.flow.map
44
- import vn.momo.kits.components.Icon
45
- import vn.momo.kits.components.Image
46
- import vn.momo.kits.components.InputSearch
47
- import vn.momo.kits.components.InputSearchProps
48
- import vn.momo.kits.components.Options
49
- import vn.momo.kits.components.Text
50
- import vn.momo.kits.const.AppStatusBar
51
- import vn.momo.kits.const.AppTheme
52
- import vn.momo.kits.const.Colors
53
- import vn.momo.kits.const.Radius
54
- import vn.momo.kits.const.Spacing
55
- import vn.momo.kits.const.Typography
56
- import vn.momo.kits.modifier.setAutomationId
57
- import vn.momo.kits.platform.getScreenDimensions
58
- import vn.momo.kits.platform.getStatusBarHeight
59
- import vn.momo.kits.utils.bottomBorder
60
-
61
- enum class TitlePosition {
62
- LEFT, CENTER,
63
- }
64
-
65
- private const val SCREEN_PADDING = 12
66
- private const val BACK_WIDTH = 28
67
-
68
- data class AnimatedHeader(
69
- val type: HeaderType = HeaderType.DEFAULT,
70
- val composable: @Composable (scrollState: Int) -> Unit = {}
71
- )
72
-
73
- @Composable
74
- fun HeaderBackground(
75
- isBack: Boolean = true,
76
- headerType: HeaderType = HeaderType.DEFAULT,
77
- scrollState: Int,
78
- inputSearchProps: InputSearchProps? = null,
79
- headerRightWidth: Dp = 0.dp,
80
- useAnimationSearch: Boolean = false
81
- ) {
82
- val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
83
- val screenWidth = getScreenDimensions().width
84
- val leftPosition = if (isBack) (BACK_WIDTH + 20).dp else 20.dp
85
- val searchWidth = screenWidth - (SCREEN_PADDING * 2)
86
-
87
- val opacityAni by animateFloatAsState(
88
- targetValue = (1 - (scrollState / 52f)).coerceIn(0f, 1f),
89
- )
90
-
91
- val backgroundColor by animateColorAsState(
92
- targetValue = animateColor(
93
- Color.Transparent,
94
- AppTheme.current.colors.background.surface,
95
- opacityAni
96
- ),
97
- )
98
-
99
- val backgroundSearch by animateColorAsState(
100
- targetValue = animateColor(
101
- AppTheme.current.colors.background.default,
102
- Colors.black_01,
103
- opacityAni
104
- ),
105
- )
106
-
107
- val animatedTranslateX by animateFloatAsState(
108
- targetValue = (scrollState / 52f).coerceIn(0f, 1f) * (leftPosition.value - 12) + 12,
109
- )
110
-
111
- val animatedWidth by animateFloatAsState(
112
- targetValue = (scrollState / 52f).coerceIn(
113
- 0f,
114
- 1f
115
- ) * ((searchWidth - leftPosition.value - headerRightWidth.value + 12) - searchWidth) + searchWidth,
116
- )
117
-
118
- val animatedHeight by animateFloatAsState(
119
- targetValue = (1 - (scrollState / 52f).coerceIn(
120
- 0f,
121
- 1f
122
- )) * (statusBarHeight.value + 94 - (statusBarHeight.value + 52)) + (statusBarHeight.value + 52),
123
- )
124
-
125
- when (headerType) {
126
- HeaderType.DEFAULT -> {
127
- val height = statusBarHeight + 52.dp
128
- Box(
129
- modifier = Modifier.fillMaxWidth().height(height)
130
- .background(backgroundColor)
131
- .bottomBorder(
132
- strokeWidth = 1.dp,
133
- color = AppTheme.current.colors.border.default
134
- )
135
- ) {
136
- Box(
137
- modifier = Modifier
138
- .height(height)
139
- .fillMaxWidth()
140
- .alpha(opacityAni)
141
- .background(
142
- Brush.linearGradient(
143
- colors = listOf(
144
- Color(0xFFFDCADE),
145
- Color(0x00FDCADE)
146
- ),
147
- start = Offset(0f, 0f),
148
- end = Offset(0f, with(LocalDensity.current) { 154.dp.toPx() })
149
- )
150
- )
151
- )
152
- if (AppTheme.current.assets.headerBackground != null) {
153
- Image(
154
- loading = false,
155
- source = AppTheme.current.assets.headerBackground!!,
156
- modifier = Modifier.fillMaxWidth().height(height),
157
- options = Options(
158
- contentScale = ContentScale.FillWidth
159
- )
160
- )
161
- }
162
- }
163
- }
164
-
165
- HeaderType.EXTENDED -> {
166
- Box(
167
- modifier = Modifier.fillMaxWidth().height(animatedHeight.dp)
168
- ) {
169
- Box(
170
- modifier = Modifier
171
- .fillMaxWidth()
172
- .height(154.dp)
173
- .alpha(opacityAni)
174
- .background(
175
- Brush.linearGradient(
176
- colors = listOf(
177
- Color(0xFFFDCADE),
178
- Color(0x00FDCADE)
179
- ),
180
- start = Offset(0f, 0f),
181
- end = Offset(0f, with(LocalDensity.current) { 154.dp.toPx() })
182
- )
183
- )
184
- ) {
185
- if (AppTheme.current.assets.headerBackground != null) {
186
- Box(
187
- modifier = Modifier
188
- .fillMaxWidth()
189
- .background(backgroundColor)
190
- ) {
191
- Image(
192
- loading = false,
193
- source = AppTheme.current.assets.headerBackground!!,
194
- modifier = Modifier.fillMaxWidth().height(154.dp),
195
- options = Options(
196
- contentScale = ContentScale.FillWidth
197
- )
198
- )
199
- }
200
- }
201
- }
202
-
203
- if (inputSearchProps != null && useAnimationSearch) {
204
- Box(
205
- modifier = Modifier
206
- .height(52.dp)
207
- .offset(
208
- x = animatedTranslateX.dp
209
- )
210
- .padding(vertical = Spacing.S)
211
- .align(Alignment.BottomStart)
212
- ) {
213
- Box(
214
- modifier = Modifier
215
- .width(animatedWidth.dp)
216
- .background(
217
- color = backgroundSearch,
218
- shape = RoundedCornerShape(Radius.XL)
219
- )
220
- ) {
221
- InputSearch(
222
- inputSearchProps = inputSearchProps.copy(
223
- showButtonText = false,
224
- backgroundColor = Color.Transparent
225
- )
226
- )
227
- }
228
- }
229
- }
230
- }
231
- }
232
-
233
- else -> {
234
- Box(modifier = Modifier.fillMaxWidth())
235
- }
236
- }
237
- }
238
-
239
- @Composable
240
- fun Header(
241
- headerType: HeaderType = HeaderType.DEFAULT,
242
- titlePosition: TitlePosition,
243
- title: String,
244
- headerRight: @Composable (() -> Unit)? = null,
245
- goBack: (() -> Unit)? = null,
246
- opacity: Float? = null,
247
- animatedHeader: AnimatedHeader? = null,
248
- scrollState: Int = 0,
249
- inputSearchProps: InputSearchProps? = null,
250
- useAnimationSearch: Boolean = false
251
- ) {
252
- val statusBarHeight = AppStatusBar.current ?: getStatusBarHeight()
253
- val tintIconColor = AppTheme.current.colors.text.default
254
- val backgroundButton = Colors.black_01.copy(alpha = 0.6f)
255
- val miniAppContext = ApplicationContext.current
256
-
257
- var colorFraction by remember { mutableStateOf(0f) }
258
-
259
- LaunchedEffect(scrollState) {
260
- snapshotFlow { scrollState }
261
- .map { (it / 50f).coerceIn(0f, 1f) }
262
- .distinctUntilChanged()
263
- .collect { fraction -> colorFraction = fraction }
264
- }
265
-
266
- if (headerType != HeaderType.NONE) {
267
- BoxWithConstraints(
268
- Modifier.height(statusBarHeight + 52.dp)
269
- .fillMaxWidth()
270
- .zIndex(10f),
271
- contentAlignment = Alignment.BottomCenter
272
- ) {
273
- Row(
274
- modifier = Modifier.height(52.dp)
275
- .fillMaxWidth()
276
- .padding(horizontal = Spacing.M),
277
- verticalAlignment = Alignment.CenterVertically,
278
- horizontalArrangement = Arrangement.SpaceBetween
279
- ) {
280
- Box {
281
- if (goBack != null) {
282
- Box(
283
- modifier = Modifier
284
- .size(28.dp)
285
- .then(
286
- Modifier.border(
287
- 0.2.dp,
288
- Colors.black_20.copy(alpha = 0.2f),
289
- RoundedCornerShape(100)
290
- )
291
- )
292
- .background(
293
- backgroundButton,
294
- RoundedCornerShape(100)
295
- )
296
- .clickable(
297
- onClick = goBack
298
- )
299
- .padding(Spacing.XS)
300
- .setAutomationId("btn_navigation_back"),
301
- contentAlignment = Alignment.Center
302
- ) {
303
- Icon(
304
- source = "arrow-back",
305
- color = Colors.black_20,
306
- size = 20.dp,
307
- )
308
- }
309
- }
310
- }
311
-
312
- if (inputSearchProps == null && miniAppContext != null) {
313
- Box(Modifier.graphicsLayer {
314
- alpha = if (animatedHeader !== null) opacity ?: 1f else 1f
315
- }) {
316
- headerRight?.invoke()
317
- }
318
- }
319
- }
320
-
321
- // Title
322
- Row(
323
- modifier = Modifier.height(52.dp)
324
- .fillMaxWidth()
325
- .padding(horizontal = Spacing.M),
326
- verticalAlignment = Alignment.CenterVertically,
327
- ) {
328
- if (goBack != null && titlePosition == TitlePosition.LEFT) {
329
- Spacer(Modifier.width(40.dp))
330
- } else {
331
- Box(
332
- Modifier.weight(1f).graphicsLayer {
333
- alpha = if (useAnimationSearch && inputSearchProps != null) {
334
- 1f - (opacity ?: 1f)
335
- } else {
336
- if (animatedHeader !== null) opacity ?: 1f else 1f
337
- }
338
- },
339
- contentAlignment = if (titlePosition == TitlePosition.LEFT) Alignment.TopStart else Alignment.Center
340
- ) {
341
- HeaderTitle(
342
- title = title,
343
- color = tintIconColor,
344
- modifier = Modifier.semantics {
345
- testTag = "title_navigation_header"
346
- }
347
- )
348
- }
349
- }
350
- }
351
- }
352
- }
353
- }
354
-
355
- @Composable
356
- fun HeaderTitle(
357
- title: String = "",
358
- modifier: Modifier = Modifier,
359
- textAlign: TextAlign = TextAlign.Start,
360
- color: Color? = null,
361
- ) {
362
- Text(
363
- modifier = Modifier.then(modifier).zIndex(1f),
364
- text = title,
365
- textAlign = textAlign,
366
- style = Typography.actionS,
367
- color = color,
368
- maxLines = 1
369
- )
370
- }
371
-
372
- fun animateColor(start: Color, stop: Color, fraction: Float): Color {
373
- return Color(
374
- red = start.red + fraction * (stop.red - start.red),
375
- green = start.green + fraction * (stop.green - start.green),
376
- blue = start.blue + fraction * (stop.blue - start.blue),
377
- alpha = start.alpha + fraction * (stop.alpha - start.alpha)
378
- )
379
- }