@momo-kits/native-kits 0.152.4-beta.9 → 0.152.4-scale.3

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.
Files changed (26) hide show
  1. package/compose/src/commonMain/kotlin/vn/momo/kits/application/HeaderRight.kt +2 -0
  2. package/compose/src/commonMain/kotlin/vn/momo/kits/application/LiteScreen.kt +2 -2
  3. package/compose/src/commonMain/kotlin/vn/momo/kits/application/Screen.kt +246 -78
  4. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Badge.kt +3 -2
  5. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Chip.kt +21 -19
  6. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Icon.kt +12 -1
  7. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Input.kt +22 -11
  8. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputDropDown.kt +6 -2
  9. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputOTP.kt +4 -1
  10. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputPhoneNumber.kt +19 -24
  11. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputSearch.kt +24 -19
  12. package/compose/src/commonMain/kotlin/vn/momo/kits/components/InputTextArea.kt +25 -18
  13. package/compose/src/commonMain/kotlin/vn/momo/kits/components/Text.kt +3 -3
  14. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Theme.kt +0 -2
  15. package/compose/src/commonMain/kotlin/vn/momo/kits/const/Typography.kt +42 -7
  16. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +106 -36
  17. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScaleSizeScope.kt +8 -3
  18. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/StackScreen.kt +34 -10
  19. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/bottomtab/BottomTabBar.kt +11 -10
  20. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/HeaderUser.kt +1 -1
  21. package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/component/SnackBar.kt +125 -0
  22. package/ios/Badge/Badge.swift +0 -6
  23. package/ios/Badge/BadgeRibbon.swift +77 -9
  24. package/ios/Button/Button.swift +7 -7
  25. package/local.properties +8 -0
  26. package/package.json +1 -1
@@ -115,11 +115,11 @@ internal fun StackScreen(
115
115
  HeaderBackground()
116
116
  }
117
117
 
118
- Box(Modifier.zIndex(4f)) {
118
+ Box(Modifier.zIndex(5f)) {
119
119
  Header()
120
120
  }
121
121
 
122
- Column (Modifier.zIndex(5f)) {
122
+ Column (Modifier.zIndex(6f)) {
123
123
  SearchAnimated(isScrollInProgress = scrollInProcess)
124
124
  }
125
125
 
@@ -127,14 +127,15 @@ internal fun StackScreen(
127
127
  MainContent(content = content)
128
128
  }
129
129
 
130
- Box(Modifier.zIndex(5f)){
131
- FloatingContent()
130
+ Box(Modifier.zIndex(4f).align(Alignment.BottomCenter)) {
131
+ FooterContent()
132
132
  }
133
133
 
134
- Box(Modifier.zIndex(6f).fillMaxSize()){
135
- if (bottomTabIndex != -1) return@Box
136
- OverplayComponentRegistry.OverlayComponent()
134
+ Box(Modifier.zIndex(7f)){
135
+ FloatingContent()
137
136
  }
137
+
138
+ OverplayView(bottomTabIndex = bottomTabIndex, id = id)
138
139
  }
139
140
  }
140
141
  }
@@ -191,9 +192,9 @@ fun ColumnScope.MainContent(content: @Composable ()-> Unit){
191
192
  }
192
193
 
193
194
  if (options.footerComponent != null){
194
- val isKeyboardVisible = isKeyboardVisible()
195
- val bottomPadding = min(AppNavigationBar.current, if (isKeyboardVisible) 0.dp else 21.dp)
196
- Footer(footerComponent = options.footerComponent, bottomPadding = bottomPadding)
195
+ val footerHeight = LocalFooterHeightPx.current
196
+ val footerHeightDp = with(density) { footerHeight.value.toDp() }
197
+ Spacer(Modifier.height(footerHeightDp))
197
198
  }
198
199
  }
199
200
 
@@ -217,6 +218,29 @@ fun ScreenContent(content: @Composable () -> Unit){
217
218
  }
218
219
  }
219
220
 
221
+ @Composable
222
+ fun FooterContent(){
223
+ val options = LocalOptions.current
224
+ if (options.footerComponent != null){
225
+ val isKeyboardVisible = isKeyboardVisible()
226
+ val bottomPadding = min(AppNavigationBar.current, if (isKeyboardVisible) 0.dp else 21.dp)
227
+ Footer(footerComponent = options.footerComponent, bottomPadding = bottomPadding)
228
+ }
229
+ }
230
+
231
+ @Composable
232
+ fun OverplayView(bottomTabIndex: Int, id: Int){
233
+ val overplayType = OverplayComponentRegistry.getOverplayType()
234
+
235
+ if (overplayType != null) {
236
+ Box(Modifier.zIndex(if (overplayType == OverplayComponentType.SNACK_BAR) 3f else 8f).fillMaxSize()){
237
+ if (bottomTabIndex != -1) return@Box
238
+ if (OverplayComponentRegistry.currentRootId() != id) return@Box
239
+ OverplayComponentRegistry.OverlayComponent()
240
+ }
241
+ }
242
+ }
243
+
220
244
  @Composable
221
245
  fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp) {
222
246
  if (footerComponent == null) return
@@ -4,6 +4,7 @@ import androidx.compose.animation.core.FastOutSlowInEasing
4
4
  import androidx.compose.animation.core.animateDpAsState
5
5
  import androidx.compose.animation.core.tween
6
6
  import androidx.compose.foundation.background
7
+ import androidx.compose.foundation.border
7
8
  import androidx.compose.foundation.layout.Arrangement
8
9
  import androidx.compose.foundation.layout.Box
9
10
  import androidx.compose.foundation.layout.Column
@@ -39,6 +40,7 @@ import vn.momo.kits.const.Colors
39
40
  import vn.momo.kits.const.Radius
40
41
  import vn.momo.kits.const.Spacing
41
42
  import vn.momo.kits.const.Typography
43
+ import vn.momo.kits.const.scaleSize
42
44
  import vn.momo.kits.modifier.noFeedbackClickable
43
45
  import vn.momo.kits.platform.getScreenDimensions
44
46
 
@@ -71,6 +73,7 @@ fun BottomTabBar(
71
73
  animationSpec = tween(durationMillis = 250, easing = FastOutSlowInEasing),
72
74
  label = "IndicatorX"
73
75
  )
76
+ val bottomTabBarHeight = scaleSize(BOTTOM_TAB_BAR_HEIGHT.toFloat())
74
77
 
75
78
  Box(
76
79
  contentAlignment = Alignment.BottomCenter
@@ -156,17 +159,15 @@ fun TabBarItem(item: BottomTabItem, selected: Boolean, onClick: () -> Unit) {
156
159
  ) {
157
160
  Icon(
158
161
  source = item.icon,
162
+ modifier = Modifier.weight(1f),
159
163
  color = if (selected) AppTheme.current.colors.primary else AppTheme.current.colors.text.hint)
160
- Spacer(modifier = Modifier.height(6.dp))
161
- Box(modifier = Modifier.height(14.dp), contentAlignment = Alignment.Center){
162
- Text(
163
- text = item.label,
164
- color = if (selected) AppTheme.current.colors.primary else AppTheme.current.colors.text.hint,
165
- style = Typography.labelXsMedium,
166
- maxLines = 1,
167
- overflow = TextOverflow.Ellipsis
168
- )
169
- }
164
+ Text(
165
+ text = item.label,
166
+ color = if (selected) AppTheme.current.colors.primary else AppTheme.current.colors.text.hint,
167
+ style = Typography.labelXsMedium,
168
+ maxLines = 1,
169
+ overflow = TextOverflow.Ellipsis
170
+ )
170
171
  }
171
172
  if(item.badgeLabel != null){
172
173
  Box(modifier = Modifier
@@ -50,7 +50,7 @@ fun HeaderUser(
50
50
  return TitleUserShimmer()
51
51
  }
52
52
 
53
- val maxWidth = getScreenDimensions().width.dp - scaleSize(172.dp)- (data.icons.size * 16).dp
53
+ val maxWidth = getScreenDimensions().width.dp - 172.dp
54
54
 
55
55
  Row(
56
56
  modifier = Modifier
@@ -0,0 +1,125 @@
1
+ package vn.momo.kits.navigation.component
2
+
3
+ import androidx.compose.animation.core.Animatable
4
+ import androidx.compose.animation.core.tween
5
+ import androidx.compose.foundation.layout.Box
6
+ import androidx.compose.foundation.layout.fillMaxWidth
7
+ import androidx.compose.foundation.layout.offset
8
+ import androidx.compose.runtime.Composable
9
+ import androidx.compose.runtime.DisposableEffect
10
+ import androidx.compose.runtime.LaunchedEffect
11
+ import androidx.compose.runtime.getValue
12
+ import androidx.compose.runtime.mutableStateOf
13
+ import androidx.compose.runtime.remember
14
+ import androidx.compose.runtime.rememberCoroutineScope
15
+ import androidx.compose.runtime.setValue
16
+ import androidx.compose.ui.Modifier
17
+ import androidx.compose.ui.layout.onGloballyPositioned
18
+ import androidx.compose.ui.platform.LocalDensity
19
+ import androidx.compose.ui.unit.IntOffset
20
+ import androidx.compose.ui.unit.dp
21
+ import androidx.compose.ui.unit.min
22
+ import kotlinx.coroutines.delay
23
+ import kotlinx.coroutines.launch
24
+ import vn.momo.kits.const.AppNavigationBar
25
+ import vn.momo.kits.navigation.LocalFooterHeightPx
26
+ import vn.momo.kits.navigation.LocalNavigator
27
+ import vn.momo.kits.navigation.LocalOptions
28
+ import vn.momo.kits.navigation.OverplayComponentRegistry
29
+
30
+ sealed class SnackBar(open val duration: Long? = null) {
31
+ data class Custom(
32
+ val content: @Composable () -> Unit,
33
+ override val duration: Long? = 3000L
34
+ ) : SnackBar(duration)
35
+
36
+ data class Toast(
37
+ override val duration: Long? = 3000L
38
+ ) : SnackBar(duration)
39
+ }
40
+
41
+ @Composable
42
+ fun SnackBar(
43
+ data: SnackBar,
44
+ onDismiss: (() -> Unit)?
45
+ ) {
46
+ val navigator = LocalNavigator.current
47
+ val options = LocalOptions.current
48
+ val density = LocalDensity.current
49
+ val footerHeightPxState = LocalFooterHeightPx.current
50
+ val navigationBar = AppNavigationBar.current
51
+
52
+ val footerHeight = if (options.footerComponent != null) {
53
+ footerHeightPxState.value
54
+ } else {
55
+ with(density){ min(navigationBar, 21.dp).toPx() }
56
+ }.toInt()
57
+
58
+ var startPosition by remember { mutableStateOf(Float.MAX_VALUE) }
59
+ val targetPosition = 0f
60
+
61
+ var offsetY by remember { mutableStateOf(Animatable(startPosition)) }
62
+ val coroutineScope = rememberCoroutineScope()
63
+
64
+ suspend fun openEvent() {
65
+ offsetY.snapTo(startPosition)
66
+ offsetY.animateTo(
67
+ targetValue = targetPosition,
68
+ animationSpec = tween(
69
+ durationMillis = 350,
70
+ )
71
+ )
72
+ }
73
+
74
+ fun closeEvent() {
75
+ coroutineScope.launch {
76
+ offsetY.animateTo(
77
+ targetValue = startPosition,
78
+ animationSpec = tween(
79
+ durationMillis = 200,
80
+ )
81
+ )
82
+ navigator.hideSnackBar()
83
+ }
84
+ }
85
+
86
+ LaunchedEffect(startPosition) {
87
+ if (startPosition != Float.MAX_VALUE){
88
+ openEvent()
89
+ }
90
+ }
91
+
92
+ LaunchedEffect(data.duration) {
93
+ val duration = data.duration
94
+ if (duration != null) {
95
+ delay(duration)
96
+ navigator.hideSnackBar()
97
+ }
98
+ }
99
+
100
+ DisposableEffect(Unit) {
101
+ OverplayComponentRegistry.bindClose {
102
+ closeEvent()
103
+ }
104
+ onDispose {
105
+ onDismiss?.invoke()
106
+ }
107
+ }
108
+
109
+ Box(
110
+ modifier = Modifier
111
+ .offset { IntOffset(0, offsetY.value.toInt() - footerHeight) }
112
+ .onGloballyPositioned {
113
+ if (startPosition != it.size.height.toFloat()) {
114
+ startPosition = it.size.height.toFloat()
115
+ offsetY = Animatable(startPosition)
116
+ }
117
+ }
118
+ .fillMaxWidth()
119
+ ) {
120
+ when (data) {
121
+ is SnackBar.Custom -> data.content()
122
+ is SnackBar.Toast -> {}
123
+ }
124
+ }
125
+ }
@@ -64,12 +64,6 @@ public struct Badge: View {
64
64
  .stroke(Colors.black01, lineWidth: 1)
65
65
  )
66
66
  }
67
-
68
- // Helper function for scaling sizes
69
- // TODO: Implement proper scaling logic based on your design system
70
- private func scaleSize(_ size: CGFloat) -> CGFloat {
71
- return size
72
- }
73
67
  }
74
68
 
75
69
  // MARK: - Preview
@@ -1,5 +1,74 @@
1
1
  import SwiftUI
2
2
 
3
+ // MARK: - Custom Shape for Rounded Corners
4
+ struct RoundedCorners: Shape {
5
+ var topLeft: CGFloat = 0.0
6
+ var topRight: CGFloat = 0.0
7
+ var bottomLeft: CGFloat = 0.0
8
+ var bottomRight: CGFloat = 0.0
9
+
10
+ func path(in rect: CGRect) -> Path {
11
+ var path = Path()
12
+
13
+ let width = rect.size.width
14
+ let height = rect.size.height
15
+
16
+ // Start from top left
17
+ path.move(to: CGPoint(x: topLeft, y: 0))
18
+
19
+ // Top edge and top right corner
20
+ path.addLine(to: CGPoint(x: width - topRight, y: 0))
21
+ if topRight > 0 {
22
+ path.addArc(
23
+ center: CGPoint(x: width - topRight, y: topRight),
24
+ radius: topRight,
25
+ startAngle: Angle(degrees: -90),
26
+ endAngle: Angle(degrees: 0),
27
+ clockwise: false
28
+ )
29
+ }
30
+
31
+ // Right edge and bottom right corner
32
+ path.addLine(to: CGPoint(x: width, y: height - bottomRight))
33
+ if bottomRight > 0 {
34
+ path.addArc(
35
+ center: CGPoint(x: width - bottomRight, y: height - bottomRight),
36
+ radius: bottomRight,
37
+ startAngle: Angle(degrees: 0),
38
+ endAngle: Angle(degrees: 90),
39
+ clockwise: false
40
+ )
41
+ }
42
+
43
+ // Bottom edge and bottom left corner
44
+ path.addLine(to: CGPoint(x: bottomLeft, y: height))
45
+ if bottomLeft > 0 {
46
+ path.addArc(
47
+ center: CGPoint(x: bottomLeft, y: height - bottomLeft),
48
+ radius: bottomLeft,
49
+ startAngle: Angle(degrees: 90),
50
+ endAngle: Angle(degrees: 180),
51
+ clockwise: false
52
+ )
53
+ }
54
+
55
+ // Left edge and top left corner
56
+ path.addLine(to: CGPoint(x: 0, y: topLeft))
57
+ if topLeft > 0 {
58
+ path.addArc(
59
+ center: CGPoint(x: topLeft, y: topLeft),
60
+ radius: topLeft,
61
+ startAngle: Angle(degrees: 180),
62
+ endAngle: Angle(degrees: 270),
63
+ clockwise: false
64
+ )
65
+ }
66
+
67
+ path.closeSubpath()
68
+ return path
69
+ }
70
+ }
71
+
3
72
  public enum RibbonPosition {
4
73
  case topLeft
5
74
  case topRight
@@ -76,14 +145,13 @@ public struct BadgeRibbon: View {
76
145
  .frame(height: RibbonConstants.roundHeight)
77
146
  .padding(.trailing, RibbonConstants.roundPaddingEnd)
78
147
  .background(
79
- RoundedRectangle(cornerRadius: Radius.M)
80
- .fill(backgroundColor)
81
- .mask(
82
- HStack(spacing: 0) {
83
- Rectangle()
84
- RoundedRectangle(cornerRadius: Radius.M)
85
- }
86
- )
148
+ RoundedCorners(
149
+ topLeft: 0,
150
+ topRight: RibbonConstants.roundRightRadius,
151
+ bottomLeft: 0,
152
+ bottomRight: RibbonConstants.roundRightRadius
153
+ )
154
+ .fill(backgroundColor)
87
155
  )
88
156
  }
89
157
 
@@ -139,7 +207,7 @@ private struct RibbonConstants {
139
207
  static let ribbonHeight: CGFloat = 20
140
208
  static let roundHeight: CGFloat = 16
141
209
  static let skewBodyHeight: CGFloat = 16
142
- static let roundRightRadius: CGFloat = 12
210
+ static let roundRightRadius: CGFloat = 8
143
211
  static let roundPaddingEnd: CGFloat = 6
144
212
  static let skewTailWidth: CGFloat = 8
145
213
  static let skewTailHeight: CGFloat = 16
@@ -1,6 +1,6 @@
1
1
  import Foundation
2
- import SwiftUI
3
2
  import Lottie
3
+ import SwiftUI
4
4
 
5
5
  // MARK: - ButtonType
6
6
 
@@ -33,7 +33,7 @@ public extension ButtonType {
33
33
  func backgroundColor(loading: Bool) -> Color {
34
34
  switch self {
35
35
  case .disabled: return Colors.black05.opacity(loading ? 0.75 : 1)
36
- case .primary: return Colors.primary.opacity(loading ? 0.75 : 1)
36
+ case .primary: return Colors.primary.opacity(loading ? 0.75 : 1)
37
37
  case .secondary: return Colors.card.opacity(loading ? 0.75 : 1)
38
38
  case .outline: return Colors.card.opacity(loading ? 0.75 : 1)
39
39
  case .tonal: return Colors.primaryLight.opacity(loading ? 0.75 : 1)
@@ -116,13 +116,13 @@ extension ButtonSize {
116
116
  }
117
117
 
118
118
  public struct Button: View {
119
-
120
119
  var title: String
121
120
  var action: () -> Void
122
121
  var type: ButtonType
123
122
  var size: ButtonSize
124
123
  var iconLeft: AnyView?
125
124
  var iconRight: AnyView?
125
+ var isFull: Bool
126
126
  var loading: Bool
127
127
 
128
128
  public init(
@@ -132,6 +132,7 @@ public struct Button: View {
132
132
  size: ButtonSize = .large,
133
133
  iconLeft: AnyView? = nil,
134
134
  iconRight: AnyView? = nil,
135
+ isFull: Bool = true,
135
136
  loading: Bool = false
136
137
  ) {
137
138
  self.title = title
@@ -140,6 +141,7 @@ public struct Button: View {
140
141
  self.size = size
141
142
  self.iconLeft = iconLeft
142
143
  self.iconRight = iconRight
144
+ self.isFull = isFull
143
145
  self.loading = loading
144
146
  }
145
147
 
@@ -156,7 +158,6 @@ public struct Button: View {
156
158
  }
157
159
 
158
160
  public var body: some View {
159
-
160
161
  let loadingOnLeft = shouldLoadingOnLeft(iconLeft, iconRight)
161
162
 
162
163
  let bg = type.backgroundColor(loading: loading)
@@ -165,12 +166,11 @@ public struct Button: View {
165
166
  let borderWidth = type.borderWidth
166
167
 
167
168
  SwiftUI.Button(action: {
168
- if !loading && type != .disabled {
169
+ if !loading, type != .disabled {
169
170
  action()
170
171
  }
171
172
  }) {
172
173
  HStack(spacing: size.iconSpacing) {
173
-
174
174
  if loading && loadingOnLeft {
175
175
  LottieView(name: "lottie_circle_loader", loopMode: .loop)
176
176
  .frame(width: size.iconSize, height: size.iconSize)
@@ -195,7 +195,7 @@ public struct Button: View {
195
195
  iconRight.frame(width: size.iconSize, height: size.iconSize)
196
196
  }
197
197
  }
198
- .frame(maxWidth: .infinity)
198
+ .frame(maxWidth: isFull ? .infinity : nil)
199
199
  .padding(.horizontal, size.padding)
200
200
  .frame(height: size.height)
201
201
  .background(bg)
@@ -0,0 +1,8 @@
1
+ ## This file must *NOT* be checked into Version Control Systems,
2
+ # as it contains information specific to your local configuration.
3
+ #
4
+ # Location of the SDK. This is only used by Gradle.
5
+ # For customization when using a Version Control System, please read the
6
+ # header note.
7
+ #Wed Sep 10 18:28:25 ICT 2025
8
+ sdk.dir=/Users/phuc/Library/Android/sdk
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.152.4-beta.9",
3
+ "version": "0.152.4-scale.3",
4
4
  "private": false,
5
5
  "dependencies": {
6
6
  "@momo-platform/native-max-api": "1.0.18"