@momo-kits/native-kits 0.113.3-beta.7 → 0.113.3-pu.1

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.
@@ -128,7 +128,11 @@ fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
128
128
  "code" to context?.appCode,
129
129
  "name" to context?.appName,
130
130
  "icon" to context?.appIcon,
131
- "description" to context?.description
131
+ "description" to context?.description,
132
+ "support" to context?.support,
133
+ "toolkitConfig" to context?.toolkitConfig,
134
+ "providerId" to context?.providerId,
135
+ "permissions" to context?.permissions
132
136
  )
133
137
  )
134
138
  ) { response ->
@@ -10,7 +10,6 @@ import androidx.compose.runtime.setValue
10
10
  import androidx.compose.runtime.staticCompositionLocalOf
11
11
  import androidx.compose.ui.unit.Dp
12
12
  import kotlinx.serialization.Serializable
13
- import kotlinx.serialization.json.internal.FormatLanguage
14
13
  import vn.momo.kits.components.TrustBannerData
15
14
  import vn.momo.kits.const.AppStatusBar
16
15
  import vn.momo.kits.const.AppTheme
@@ -58,8 +57,8 @@ data class MiniAppContext(
58
57
 
59
58
  val PlatformApi = staticCompositionLocalOf<Any?> { null }
60
59
 
61
- val AppNavigator = staticCompositionLocalOf<Navigator> {
62
- error("no navigator provided!")
60
+ val AppNavigator = staticCompositionLocalOf<Navigator?> {
61
+ null
63
62
  }
64
63
 
65
64
  val ApplicationContext = staticCompositionLocalOf<MiniAppContext?> {
@@ -70,10 +69,6 @@ val AppConfig = staticCompositionLocalOf<KitConfig?> {
70
69
  null
71
70
  }
72
71
 
73
- val AppLanguage = staticCompositionLocalOf<String?> {
74
- null
75
- }
76
-
77
72
  data class KitConfig(
78
73
  val trustBanner: TrustBannerData? = null,
79
74
  val headerBar: String? = null,
@@ -87,7 +82,6 @@ fun ApplicationContainer(
87
82
  statusBarHeight: Dp? = AppStatusBar.current,
88
83
  applicationContext: MiniAppContext? = null,
89
84
  config: KitConfig? = null,
90
- language: String? = null,
91
85
  content: @Composable () -> Unit,
92
86
  ) {
93
87
  var appTheme by remember { mutableStateOf(theme) }
@@ -116,8 +110,7 @@ fun ApplicationContainer(
116
110
  AppNavigator provides Navigator(),
117
111
  AppStatusBar provides appStatusBarHeight,
118
112
  ApplicationContext provides applicationContext,
119
- AppConfig provides config,
120
- AppLanguage provides language
113
+ AppConfig provides config
121
114
  ) {
122
115
  content()
123
116
  }
@@ -21,6 +21,7 @@ import androidx.compose.foundation.text.KeyboardOptions
21
21
  import androidx.compose.material3.CircularProgressIndicator
22
22
  import androidx.compose.runtime.Composable
23
23
  import androidx.compose.runtime.MutableState
24
+ import androidx.compose.runtime.SideEffect
24
25
  import androidx.compose.runtime.getValue
25
26
  import androidx.compose.runtime.mutableStateOf
26
27
  import androidx.compose.runtime.remember
@@ -36,6 +37,7 @@ import androidx.compose.ui.text.TextStyle
36
37
  import androidx.compose.ui.text.font.FontWeight
37
38
  import androidx.compose.ui.text.input.KeyboardType
38
39
  import androidx.compose.ui.text.input.PasswordVisualTransformation
40
+ import androidx.compose.ui.text.input.TextFieldValue
39
41
  import androidx.compose.ui.text.input.VisualTransformation
40
42
  import androidx.compose.ui.unit.Dp
41
43
  import androidx.compose.ui.unit.dp
@@ -154,7 +156,7 @@ fun ErrorView(errorMessage: String, errorSpacing: Boolean, hintText: String) {
154
156
 
155
157
  @Composable
156
158
  fun Input(
157
- text: MutableState<String> = remember { mutableStateOf("") },
159
+ text: String = "",
158
160
  floatingValue: String = "",
159
161
  floatingValueColor: Color = AppTheme.current.colors.text.hint,
160
162
  floatingIcon: String = "",
@@ -181,6 +183,11 @@ fun Input(
181
183
  keyboardType: KeyboardType = KeyboardType.Text,
182
184
  modifier: Modifier = Modifier,
183
185
  ) {
186
+ // Handling state based on the `BasicTextField` concept
187
+ var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = text)) }
188
+ val textFieldValue = textFieldValueState.copy(text = text)
189
+ var lastTextValue by remember(text) { mutableStateOf(text) }
190
+
184
191
  var isFocused by remember { mutableStateOf(false) }
185
192
  var passHidden by remember { mutableStateOf(false) }
186
193
  var isBlurred = false
@@ -209,10 +216,10 @@ fun Input(
209
216
  setAutomationId(testId)
210
217
  ) {
211
218
  BasicTextField(
219
+ value = text,
212
220
  enabled = !disabled,
213
221
  readOnly = readOnly,
214
222
  singleLine = true,
215
- value = text.value,
216
223
  textStyle = TextStyle(
217
224
  color = textColor,
218
225
  fontSize = 16.sp,
@@ -229,7 +236,15 @@ fun Input(
229
236
  if (!it.isFocused && isBlurred) onBlur()
230
237
  if (it.isFocused && !isBlurred) isBlurred = true
231
238
  },
232
- onValueChange = onChangeText,
239
+ onValueChange = { newValue ->
240
+ textFieldValueState.copy(text = newValue)
241
+ val stringChangedSinceLastInvocation = lastTextValue != newValue
242
+ lastTextValue = newValue
243
+
244
+ if (stringChangedSinceLastInvocation) {
245
+ onChangeText(newValue)
246
+ }
247
+ },
233
248
  decorationBox = { innerTextField ->
234
249
  // Floating Icon
235
250
  if (floatingValue.isNotEmpty() || floatingIcon.isNotEmpty()) {
@@ -291,7 +306,7 @@ fun Input(
291
306
  )
292
307
  }
293
308
  Box(Modifier.weight(1f)) {
294
- if (text.value.isEmpty()) {
309
+ if (textFieldValue.text.isEmpty()) {
295
310
  Text(
296
311
  text = placeholder,
297
312
  style = TextStyle(
@@ -304,7 +319,7 @@ fun Input(
304
319
  }
305
320
  innerTextField()
306
321
  }
307
- if (isFocused && text.value.isNotEmpty()) {
322
+ if (isFocused && textFieldValue.text.isNotEmpty()) {
308
323
  Row {
309
324
  Spacer(Modifier.width(Spacing.XS))
310
325
  Icon(
@@ -312,7 +327,10 @@ fun Input(
312
327
  size = 16.dp,
313
328
  color = AppTheme.current.colors.text.hint,
314
329
  modifier = Modifier.clickable(
315
- onClick = { text.value = "" },
330
+ onClick = {
331
+ onChangeText("")
332
+ textFieldValueState = TextFieldValue(text = "")
333
+ },
316
334
  interactionSource = remember { MutableInteractionSource() },
317
335
  indication = null
318
336
  )
@@ -143,7 +143,7 @@ fun PopupNotify(
143
143
  Modifier
144
144
  .width(22.dp)
145
145
  .height(22.dp)
146
- .offset(x = Spacing.M / 2, y = -Spacing.M)
146
+ .offset(x = -(1).dp, y = (-11).dp)
147
147
  .background(
148
148
  color = AppTheme.current.colors.text.default,
149
149
  shape = RoundedCornerShape(Radius.M)
@@ -20,7 +20,6 @@ import androidx.compose.ui.layout.ContentScale
20
20
  import androidx.compose.ui.unit.dp
21
21
  import androidx.compose.ui.unit.sp
22
22
  import vn.momo.kits.application.AppConfig
23
- import vn.momo.kits.application.AppLanguage
24
23
  import vn.momo.kits.modifier.noFeedbackClickable
25
24
 
26
25
  val defaultBanner = TrustBannerData(
@@ -63,10 +62,10 @@ fun TrustBanner(
63
62
  serviceName: String = "",
64
63
  screenName: String = "",
65
64
  onPress: ((Map<String, String>) -> Unit)? = null,
66
- trackEvent: ((String, Map<String, String>) -> Unit)? = null
65
+ trackEvent: ((String, Map<String, String>) -> Unit)? = null,
66
+ language: String = "vi"
67
67
  ) {
68
68
  val appConfig = AppConfig.current
69
- val language = AppLanguage.current ?: "vi"
70
69
  val trustBanner = appConfig?.trustBanner ?: defaultBanner
71
70
  val trackParams = mapOf(
72
71
  "service_name" to serviceName,
@@ -13,30 +13,30 @@ public class MiniAppContext {
13
13
  public var appName: Any? = nil
14
14
  public var appIcon: String = ""
15
15
  public var description: Any? = nil
16
-
17
- public init(appId: String, appCode: String, appName: Any? = nil, appIcon: String, description: Any? = nil) {
16
+ public var support: Any? = nil
17
+ public var toolkitConfig: Any? = nil
18
+ public var providerId: String = ""
19
+ public var permissions: Any? = nil
20
+
21
+ public init(appId: String, appCode: String, appName: Any? = nil, appIcon: String, description: Any? = nil, support: Any? = nil, toolkitConfig: Any? = nil, providerId: String, permissions: Any? = nil) {
18
22
  self.appId = appId
19
23
  self.appCode = appCode
20
24
  self.appName = appName
21
25
  self.appIcon = appIcon
22
26
  self.description = description
27
+ self.support = support
28
+ self.toolkitConfig = toolkitConfig
29
+ self.providerId = providerId
30
+ self.permissions = permissions
23
31
  }
24
32
  }
25
33
 
26
- public class KitConfig {
27
- public var trustBanner: TrustBannerData? = nil
28
- public var headerBar: String? = nil
29
- public var headerGradient: String? = nil
30
- }
31
-
32
34
  public class ApplicationEnvironment: ObservableObject {
33
35
  let applicationContext: MiniAppContext?
34
- let composeApi: KitComposeApi?
35
- let config: KitConfig?
36
-
37
- public init(applicationContext: MiniAppContext? = nil, composeApi: KitComposeApi? = nil, config: KitConfig? = nil) {
36
+ let composeApi: KitComposeApi
37
+
38
+ public init(applicationContext: MiniAppContext?, composeApi: KitComposeApi) {
38
39
  self.applicationContext = applicationContext
39
40
  self.composeApi = composeApi
40
- self.config = config
41
41
  }
42
42
  }
@@ -6,7 +6,7 @@ public struct HeaderRightData {
6
6
  var useMore: Bool
7
7
  var tools: [ToolGroup]
8
8
  var toolCallback: ((String) -> Void)?
9
-
9
+
10
10
  public init(
11
11
  useShortcut: Bool = false,
12
12
  useMore: Bool = false,
@@ -25,7 +25,7 @@ public struct Tool {
25
25
  var icon: String
26
26
  var showBadge: Bool
27
27
  var name: [String: String]
28
-
28
+
29
29
  public init(
30
30
  key: String,
31
31
  icon: String,
@@ -37,7 +37,7 @@ public struct Tool {
37
37
  self.showBadge = showBadge
38
38
  self.name = name
39
39
  }
40
-
40
+
41
41
  func toMap() -> [String: Any] {
42
42
  return [
43
43
  "key": key,
@@ -51,7 +51,7 @@ public struct Tool {
51
51
  public struct ToolGroup {
52
52
  var title: [String: String]
53
53
  var items: [Tool]
54
-
54
+
55
55
  public init(
56
56
  title: [String: String] = [:],
57
57
  items: [Tool]
@@ -59,7 +59,7 @@ public struct ToolGroup {
59
59
  self.title = title
60
60
  self.items = items
61
61
  }
62
-
62
+
63
63
  func toMap() -> [String: Any] {
64
64
  return [
65
65
  "title": title,
@@ -79,7 +79,7 @@ public struct HeaderRight: View {
79
79
  self.headerRight = headerRight
80
80
  }
81
81
  var headerRight: HeaderRightData?
82
-
82
+
83
83
  public var body: some View {
84
84
  HStack(alignment: .center) {
85
85
  ToolkitHeaderRight(headerRight: headerRight)
@@ -92,17 +92,17 @@ struct ToolkitHeaderRight: View {
92
92
  @State private var isFavorite: Bool = false
93
93
  @State private var isLoading: Bool = false
94
94
  @EnvironmentObject private var environment: ApplicationEnvironment
95
-
95
+
96
96
  // MARK: - Actions
97
97
  private func onPressShortcut() {
98
- environment.composeApi?.request(
98
+ environment.composeApi.request(
99
99
  funcName: "onToolAction",
100
100
  params: ["item": ["key": "onFavorite"]]
101
101
  ) { _ in
102
102
  isFavorite.toggle()
103
103
  }
104
104
  }
105
-
105
+
106
106
  private func onPressHelpCenter() {
107
107
  let context = environment.applicationContext
108
108
  let paramMap: [String: Any] = [
@@ -112,19 +112,20 @@ struct ToolkitHeaderRight: View {
112
112
  "icon": context?.appIcon,
113
113
  "description": context?.description
114
114
  ]
115
- environment.composeApi?.request(
115
+ environment.composeApi.request(
116
116
  funcName: "showHelpCenter",
117
117
  params: paramMap
118
118
  ) { _ in }
119
119
  }
120
-
120
+
121
121
  private func onPressClose() {
122
- environment.composeApi?.request(
122
+ environment.composeApi.request(
123
123
  funcName: "dismiss",
124
124
  params: ""
125
125
  ) { _ in }
126
126
  }
127
-
127
+
128
+
128
129
  private func onPressMore() {
129
130
  let context = environment.applicationContext
130
131
  let params: [String: Any] = [
@@ -134,10 +135,14 @@ struct ToolkitHeaderRight: View {
134
135
  "code": context?.appCode,
135
136
  "name": context?.appName,
136
137
  "icon": context?.appIcon,
137
- "description": context?.description
138
+ "description": context?.description,
139
+ "support": context?.support,
140
+ "toolkitConfig": context?.toolkitConfig,
141
+ "providerId": context?.providerId,
142
+ "permissions": context?.permissions
138
143
  ]
139
144
  ]
140
- environment.composeApi?.request(
145
+ environment.composeApi.request(
141
146
  funcName: "showTools",
142
147
  params: params
143
148
  ) { response in
@@ -152,12 +157,12 @@ struct ToolkitHeaderRight: View {
152
157
  }
153
158
  }
154
159
  }
155
-
160
+
156
161
  private func getNavigationButtonConfig() -> NavigationButtonConfig {
157
162
  let totalTools = headerRight?.tools.reduce(0) { $0 + $1.items.count } ?? 0
158
163
  var icon = isFavorite ? "pin_star_checked" : "pin_star"
159
164
  var onClickHandler: () -> Void = onPressShortcut
160
-
165
+
161
166
  if totalTools > 1 || headerRight?.useMore == true {
162
167
  icon = "navigation_more_icon"
163
168
  onClickHandler = onPressMore
@@ -167,16 +172,16 @@ struct ToolkitHeaderRight: View {
167
172
  headerRight?.toolCallback?(singleTool.key)
168
173
  }
169
174
  }
170
-
175
+
171
176
  return NavigationButtonConfig(icon: icon, onPress: onClickHandler)
172
177
  }
173
-
178
+
174
179
  var body: some View {
175
180
  let navButtonConfig = getNavigationButtonConfig()
176
181
  let showBadge = headerRight?.tools.contains { group in
177
182
  group.items.contains { $0.showBadge }
178
183
  } ?? false
179
-
184
+
180
185
  HStack(alignment: .center) {
181
186
  if headerRight?.useShortcut == true {
182
187
  NavigationButton(
@@ -186,18 +191,18 @@ struct ToolkitHeaderRight: View {
186
191
  onClick: navButtonConfig.onPress
187
192
  )
188
193
  }
189
-
194
+
190
195
  HStack(alignment: .center, spacing: 0) {
191
196
  Icon(source: "help_center", size: 20)
192
197
  .padding(4)
193
198
  .onTapGesture {
194
199
  onPressHelpCenter()
195
200
  }
196
-
201
+
197
202
  Rectangle()
198
203
  .fill(Colors.black20)
199
204
  .frame(width: 0.5, height: 12)
200
-
205
+
201
206
  Icon(source: "16_navigation_close_circle", size: 20, color: Colors.black17)
202
207
  .padding(4)
203
208
  .onTapGesture {
@@ -221,7 +226,7 @@ struct NavigationButton: View {
221
226
  var icon: String
222
227
  var showBadge: Bool
223
228
  var onClick: () -> Void
224
-
229
+
225
230
  var body: some View {
226
231
  ZStack {
227
232
  Circle()
@@ -230,9 +235,9 @@ struct NavigationButton: View {
230
235
  Circle()
231
236
  .stroke(Color.black.opacity(0.2), lineWidth: 0.2)
232
237
  )
233
-
238
+
234
239
  Icon(source: icon, size: 16)
235
-
240
+
236
241
  if showBadge {
237
242
  BadgeDot(size: .small)
238
243
  .offset(x: -2, y: -2)
@@ -46,8 +46,8 @@ public struct PopupDisplay: View {
46
46
  .overlay(RoundedRectangle(cornerRadius: Radius.L).stroke(Colors.black01, lineWidth: 1))
47
47
  }.foregroundColor(Colors.black20)
48
48
  .background(Colors.black01.cornerRadius(15))
49
- .padding(.trailing, -Spacing.M )
50
- .padding(.top, -Spacing.M)
49
+ .padding(.trailing, -11)
50
+ .padding(.top, -11)
51
51
  .zIndex(1)
52
52
  .accessibility(identifier: "ic_popup_close")
53
53
 
@@ -10,9 +10,9 @@ public struct SwipeCellModifier: ViewModifier {
10
10
  var trailingSideGroup: [SwipeCellActionItem] = []
11
11
  @Binding var currentUserInteractionCellID: String?
12
12
  var settings: SwipeCellSettings = .init()
13
-
13
+
14
14
  @State private var offsetX: CGFloat = 0
15
-
15
+
16
16
  let generator = UINotificationFeedbackGenerator()
17
17
  @State private var hapticFeedbackOccurred: Bool = false
18
18
  @State private var openSideLock: SwipeGroupSide?
@@ -22,15 +22,23 @@ public struct SwipeCellModifier: ViewModifier {
22
22
  if self.leadingSideGroup.isEmpty == false && self.offsetX != 0 {
23
23
  self.swipeToRevealArea(swipeItemGroup: self.leadingSideGroup, side: .leading)
24
24
  }
25
-
25
+
26
26
  if self.trailingSideGroup.isEmpty == false && self.offsetX != 0 {
27
27
  self.swipeToRevealArea(swipeItemGroup: self.trailingSideGroup, side: .trailing)
28
28
  }
29
-
29
+
30
30
  content
31
31
  .offset(x: self.offsetX)
32
- .gesture(DragGesture(minimumDistance: 30, coordinateSpace: .local).onChanged(self.dragOnChanged(value:)).onEnded(dragOnEnded(value:)))
33
-
32
+ .simultaneousGesture(
33
+ DragGesture(minimumDistance: 30, coordinateSpace: .local)
34
+ .onChanged { value in
35
+ self.dragOnChanged(value: value)
36
+ }
37
+ .onEnded { value in
38
+ self.dragOnEnded(value: value)
39
+ }
40
+ )
41
+
34
42
  }.frame(width: cellWidth)
35
43
  .edgesIgnoringSafeArea(.horizontal)
36
44
  .clipped()
@@ -43,7 +51,7 @@ public struct SwipeCellModifier: ViewModifier {
43
51
  }
44
52
  }
45
53
  }
46
-
54
+
47
55
  internal func swipeToRevealArea(swipeItemGroup: [SwipeCellActionItem], side: SwipeGroupSide)->some View {
48
56
  HStack {
49
57
  if side == .trailing {
@@ -63,17 +71,17 @@ public struct SwipeCellModifier: ViewModifier {
63
71
  }
64
72
  }
65
73
  }.opacity(self.swipeRevealAreaOpacity(side: side))
66
-
74
+
67
75
  if side == .leading {
68
76
  Spacer()
69
77
  }
70
78
  }
71
79
  }
72
-
80
+
73
81
  internal func buttonContentView(item: SwipeCellActionItem, group: [SwipeCellActionItem], side: SwipeGroupSide)->some View {
74
82
  ZStack {
75
83
  item.backgroundColor
76
-
84
+
77
85
  HStack {
78
86
  if self.warnSwipeOutCondition(side: side, hasSwipeOut: item.swipeOutAction) && item.swipeOutButtonView != nil {
79
87
  item.swipeOutButtonView!()
@@ -81,28 +89,28 @@ public struct SwipeCellModifier: ViewModifier {
81
89
  item.buttonView()
82
90
  }
83
91
  }
84
-
92
+
85
93
  }.frame(width: self.itemButtonWidth(item: item, itemGroup: group, side: side))
86
94
  }
87
-
95
+
88
96
  internal func menuWidth(side: SwipeGroupSide)->CGFloat {
89
97
  switch side {
90
98
  case .leading:
91
99
  return self.leadingSideGroup.map { $0.buttonWidth }.reduce(0, +)
92
-
100
+
93
101
  case .trailing:
94
102
  return self.trailingSideGroup.map { $0.buttonWidth }.reduce(0, +)
95
103
  }
96
104
  }
97
-
105
+
98
106
  // MARK: drag gesture
99
-
107
+
100
108
  internal func dragOnChanged(value: DragGesture.Value) {
101
109
  let horizontalTranslation = value.translation.width
102
110
  if self.nonDraggableCondition(horizontalTranslation: horizontalTranslation) {
103
111
  return
104
112
  }
105
-
113
+
106
114
  if self.openSideLock != nil {
107
115
  // if one side is open, we need to add the menu width!
108
116
  let menuWidth = self.openSideLock == .leading ? self.menuWidth(side: .leading) : self.menuWidth(side: .trailing)
@@ -110,9 +118,9 @@ public struct SwipeCellModifier: ViewModifier {
110
118
  self.triggerHapticFeedbackIfNeeded(horizontalTranslation: horizontalTranslation)
111
119
  return
112
120
  }
113
-
121
+
114
122
  self.triggerHapticFeedbackIfNeeded(horizontalTranslation: horizontalTranslation)
115
-
123
+
116
124
  if horizontalTranslation > 8 || horizontalTranslation < -8 { // makes sure the swipe cell doesn't open too easily
117
125
  self.currentUserInteractionCellID = self.id
118
126
  self.offsetX = horizontalTranslation
@@ -120,11 +128,11 @@ public struct SwipeCellModifier: ViewModifier {
120
128
  self.offsetX = 0
121
129
  }
122
130
  }
123
-
131
+
124
132
  internal func nonDraggableCondition(horizontalTranslation: CGFloat)->Bool {
125
133
  return self.offsetX == 0 && (self.leadingSideGroup.isEmpty && horizontalTranslation > 0 || self.trailingSideGroup.isEmpty && horizontalTranslation < 0)
126
134
  }
127
-
135
+
128
136
  internal func dragOnEnded(value: DragGesture.Value) {
129
137
  let swipeOutTriggerValue = self.cellWidth * self.settings.swipeOutTriggerRatio
130
138
 
@@ -139,7 +147,7 @@ public struct SwipeCellModifier: ViewModifier {
139
147
  } else {
140
148
  self.lockSideMenu(side: .leading)
141
149
  }
142
-
150
+
143
151
  } else {
144
152
  // leading group emtpy
145
153
  self.setOffsetX(value: 0)
@@ -160,7 +168,7 @@ public struct SwipeCellModifier: ViewModifier {
160
168
  }
161
169
  }
162
170
  }
163
-
171
+
164
172
  internal func triggerHapticFeedbackIfNeeded(horizontalTranslation: CGFloat) {
165
173
  let side: SwipeGroupSide = horizontalTranslation > 0 ? .leading : .trailing
166
174
  let group = side == .leading ? self.leadingSideGroup : self.trailingSideGroup
@@ -170,7 +178,7 @@ public struct SwipeCellModifier: ViewModifier {
170
178
  self.hapticFeedbackOccurred = true
171
179
  }
172
180
  }
173
-
181
+
174
182
  internal func swipeOutItemWithHapticFeedback(group: [SwipeCellActionItem])->SwipeCellActionItem? {
175
183
  if let item = group.filter({ $0.swipeOutAction == true }).first {
176
184
  if item.swipeOutHapticFeedbackType != nil {
@@ -179,7 +187,7 @@ public struct SwipeCellModifier: ViewModifier {
179
187
  }
180
188
  return nil
181
189
  }
182
-
190
+
183
191
  internal func swipeOutAction(item: SwipeCellActionItem, sideFactor: CGFloat) {
184
192
  if item.swipeOutIsDestructive {
185
193
  let swipeOutWidth = cellWidth + 10
@@ -188,12 +196,12 @@ public struct SwipeCellModifier: ViewModifier {
188
196
  } else {
189
197
  self.setOffsetX(value: 0) // open side lock set in function!
190
198
  }
191
-
199
+
192
200
  DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
193
201
  item.actionCallback()
194
202
  }
195
203
  }
196
-
204
+
197
205
  internal func lockSideMenu(side: SwipeGroupSide) {
198
206
  self.setOffsetX(value: side.sideFactor * self.menuWidth(side: side))
199
207
  self.openSideLock = side
@@ -214,7 +222,7 @@ public struct SwipeCellModifier: ViewModifier {
214
222
  let dynamicButtonWidth = self.dynamicButtonWidth(item: item, itemCount: itemGroup.count, side: side)
215
223
  let triggerValue = self.cellWidth * settings.swipeOutTriggerRatio
216
224
  let swipeOutActionCondition = side == .leading ? self.offsetX > triggerValue : self.offsetX < -triggerValue
217
-
225
+
218
226
  if item.swipeOutAction && swipeOutActionCondition {
219
227
  return self.offsetX.magnitude + settings.addWidthMargin
220
228
  } else if swipeOutActionCondition && item.swipeOutAction == false && itemGroup.contains(where: { $0.swipeOutAction == true }) {
@@ -223,12 +231,12 @@ public struct SwipeCellModifier: ViewModifier {
223
231
  return dynamicButtonWidth
224
232
  }
225
233
  }
226
-
234
+
227
235
  internal func dynamicButtonWidth(item: SwipeCellActionItem, itemCount: Int, side: SwipeGroupSide)->CGFloat {
228
236
  let menuWidth = self.menuWidth(side: side)
229
237
  return (self.offsetX.magnitude + settings.addWidthMargin) * (item.buttonWidth / menuWidth)
230
238
  }
231
-
239
+
232
240
  internal func warnSwipeOutCondition(side: SwipeGroupSide, hasSwipeOut: Bool)->Bool {
233
241
  if hasSwipeOut == false {
234
242
  return false
@@ -236,11 +244,11 @@ public struct SwipeCellModifier: ViewModifier {
236
244
  let triggerValue = self.cellWidth * settings.swipeOutTriggerRatio
237
245
  return (side == .trailing && self.offsetX < -triggerValue) || (side == .leading && self.offsetX > triggerValue)
238
246
  }
239
-
247
+
240
248
  internal func swipeRevealAreaOpacity(side: SwipeGroupSide)->Double {
241
249
  switch side {
242
250
  case .leading:
243
-
251
+
244
252
  return self.offsetX > 5 ? 1 : 0
245
253
  case .trailing:
246
254
  return self.offsetX < -5 ? 1 : 0
@@ -11,110 +11,179 @@ import SDWebImageSwiftUI
11
11
 
12
12
  let jsonUrl = "https://static.momocdn.net/app/json/component-kits/design_system.json"
13
13
 
14
- public struct TrustBannerData: Decodable {
15
- let content: [String: String]
16
- let subContent: [String: String]
14
+ struct TrustBannerData: Decodable {
15
+ let trustBanner: TrustBanner
16
+ }
17
+
18
+ struct TrustBanner: Decodable {
19
+ let content: Dictionary<String, String>
17
20
  let pciImage: String
18
21
  let sslImage: String
19
22
  let urlConfig: String
23
+ let featureCode: String
20
24
  let icons: [String]
21
25
  let momoImage: String
22
-
26
+
23
27
  enum CodingKeys: String, CodingKey {
24
- case content
25
- case subContent
26
- case pciImage
27
- case sslImage
28
- case icons
29
- case momoImage
28
+ case content, pciImage, sslImage, icons, momoImage
30
29
  case urlConfig = "url_config"
30
+ case featureCode = "feature_code"
31
31
  }
32
32
  }
33
33
 
34
- let defaultBanner = TrustBannerData(
35
- content: ["vi": "An toàn tài sản & Bảo mật thông tin của bạn là ưu tiên hàng đầu của MoMo.",
36
- "en": "Ensuring financial security and data privacy is MoMo's highest priority."],
37
- subContent: [
38
- "vi": "Tìm hiểu thêm",
39
- "en": "Learn more"
40
- ],
34
+ let defaultBanner = TrustBanner(
35
+ content: ["vi": "Bảo mật thông tin & An toàn tài sản của bạn là ưu tiên hàng đầu của MoMo.",
36
+ "en": "Your data security and money safety are MoMo's top priorities."],
41
37
  pciImage: "https://static.momocdn.net/app/img/kits/trustBanner/pci.png",
42
38
  sslImage: "https://static.momocdn.net/app/img/kits/trustBanner/ssl.png",
43
- urlConfig: "login_and_security",
39
+ urlConfig: "https://momo.vn/an-toan-bao-mat",
40
+ featureCode: "login_and_security",
44
41
  icons: [
45
42
  "https://static.momocdn.net/app/img/kits/trustBanner/ic_viettinbank.png",
46
43
  "https://static.momocdn.net/app/img/kits/trustBanner/ic_agribank.png",
47
44
  "https://static.momocdn.net/app/img/kits/trustBanner/ic_vietcombank.png",
48
45
  "https://static.momocdn.net/app/img/kits/trustBanner/ic_bidv.png"
49
46
  ],
50
- momoImage: "https://static.momocdn.net/app/img/kits/trustBanner/ic_secu.png"
47
+ momoImage: "https://static.momocdn.net/app/img/kits/trustBanner/ic_momo_secu.png"
51
48
  )
52
49
 
53
- public struct TrustBanner: View {
54
- @EnvironmentObject var applicationEnvironment: ApplicationEnvironment
55
-
56
- var onPress: ((String)->Void)?
57
-
58
- public init(onPress: ((String)->Void)?) {
50
+ let defaultData = TrustBannerData(trustBanner: defaultBanner)
51
+
52
+ class TrustBannerViewModel: ObservableObject {
53
+ @Published var trustBanner: TrustBannerData?
54
+
55
+ init() {
56
+ fetchData(from: jsonUrl)
57
+ }
58
+
59
+ func fetchData(from urlString: String) {
60
+ guard let url = URL(string: urlString) else { return }
61
+
62
+ URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
63
+ guard let self = self else { return }
64
+
65
+ if let error = error {
66
+ print("Data fetching error: \(error)")
67
+ return
68
+ }
69
+
70
+ guard let data = data else {
71
+ print("No data received")
72
+ return
73
+ }
74
+ do {
75
+ let decodedData = try JSONDecoder().decode(TrustBannerData.self, from: data)
76
+ DispatchQueue.main.async {
77
+ self.trustBanner = decodedData
78
+ }
79
+ } catch {
80
+ print("Decoding error: \(error)")
81
+ }
82
+ }.resume()
83
+ }
84
+ }
85
+
86
+ public struct TrustBannerView: View {
87
+ var serviceName: String? = ""
88
+ var screenName: String? = ""
89
+ var onPress: ((_ params: NSDictionary) -> Void)?
90
+ var trackEvent: ((_ eventName: String, _ params: NSDictionary) -> Void)?
91
+ var language: String?
92
+
93
+ @ObservedObject private var viewModel = TrustBannerViewModel()
94
+
95
+ public init(serviceName: String?,
96
+ screenName: String?,
97
+ onPress: ((_ params: NSDictionary) -> Void)?,
98
+ trackEvent: ((_ eventName: String,_ params: NSDictionary) -> Void)?, language: String? = "vi") {
99
+ if let trackEvent = trackEvent {
100
+ trackEvent("service_component_displayed", ["service_name": serviceName ?? "", "screen_name": screenName ?? "", "component_name": "logo_trust"])
101
+ }
59
102
  self.onPress = onPress
103
+ self.trackEvent = trackEvent
104
+ self.screenName = screenName
105
+ self.serviceName = serviceName
106
+ self.language = language
60
107
  }
61
-
62
- var language: String? = "vi"
63
-
64
- func onPressSecurity() {
65
- if (onPress != nil) {
66
- onPress!(applicationEnvironment.config?.trustBanner?.urlConfig ?? defaultBanner.urlConfig)
108
+
109
+ public var body: some View {
110
+ if let trustBanner = viewModel.trustBanner {
111
+ TrustBannerContentView(trustBanner: trustBanner.trustBanner, onPress: onPress, trackEvent: trackEvent, serviceName: serviceName, screenName: screenName, language: language)
67
112
  }
68
113
  }
114
+ }
115
+
116
+ struct TrustBannerContentView: View {
117
+ var trustBanner: TrustBanner
118
+ var onPress: ((_ params: NSDictionary) -> Void)?
119
+ var trackEvent: ((_ eventName: String, _ params: NSDictionary) -> Void)?
120
+ var serviceName: String?
121
+ var screenName: String?
122
+ var language: String?
69
123
 
70
-
71
- public var body: some View {
72
- HStack(alignment: .center, spacing: 8) {
73
- WebImage(url: URL(string: applicationEnvironment.config?.trustBanner?.momoImage ?? defaultBanner.momoImage))
74
- .resizable()
75
- .scaledToFit()
76
- .frame(width: 64, height: 64)
77
- VStack(alignment: .leading){
78
- Text(applicationEnvironment.config?.trustBanner?.content[language ?? "vi"] as? String ?? defaultBanner.content[language ?? "vi"])
79
- .foregroundColor(Color(hex: "484848"))
80
- .lineLimit(2)
81
- .font(.system(size: 13, weight: Font.Weight.regular))
82
- .lineSpacing(3)
83
- .padding(.bottom, 8)
84
- HStack {
85
- HStack(spacing: 0) {
86
- Text(applicationEnvironment.config?.trustBanner?.subContent[language ?? "vi"] as? String ?? defaultBanner.subContent[language ?? "vi"])
87
- .foregroundColor(Colors.pink03)
88
- .font(.system(size: 13, weight: .bold))
89
- .padding(.trailing, 4)
90
-
91
- Icon(source: "arrow_chevron_right_small", color: Colors.pink03)
92
- }.onTapGesture {
93
- onPressSecurity()
94
- }
95
-
96
- Spacer()
124
+ var body: some View {
125
+ GeometryReader { geo in
126
+ HStack(alignment: .center, spacing: 8) {
127
+ WebImage(url: URL(string: trustBanner.momoImage))
128
+ .resizable()
129
+ .scaledToFit()
130
+ .frame(width: 64, height: 64)
131
+ VStack(alignment: .leading, spacing: 8){
132
+ Text(trustBanner.content[language ?? "vi"] ?? "")
133
+ .foregroundColor(Color(hex: 0xFF484848))
134
+ .lineLimit(2)
135
+ .font(.system(size: 12, weight: Font.Weight.regular))
136
+ .frame(width: geo.size.width - 24 - 64 - 8, height: 40,alignment: .leading)
97
137
  HStack{
98
- WebImage(url: URL(string: applicationEnvironment.config?.trustBanner?.pciImage ?? defaultBanner.pciImage))
99
- .resizable()
100
- .scaledToFit()
101
- .frame(width: 24, height: 20)
102
- WebImage(url: URL(string: applicationEnvironment.config?.trustBanner?.sslImage ?? defaultBanner.sslImage))
103
- .resizable()
104
- .scaledToFit()
105
- .frame(width: 52, height: 20)
138
+ HStack{
139
+ WebImage(url: URL(string: trustBanner.pciImage))
140
+ .resizable()
141
+ .scaledToFit()
142
+ .frame(width: 52, height: 20)
143
+ WebImage(url: URL(string: trustBanner.sslImage))
144
+ .resizable()
145
+ .scaledToFit()
146
+ .frame(width: 52, height: 20)
147
+ }
148
+ Spacer()
149
+ HStack(spacing: -6){
150
+ ForEach(trustBanner.icons, id: \.self) { iconURL in
151
+ WebImage(url: URL(string: iconURL))
152
+ .resizable()
153
+ .scaledToFit()
154
+ .frame(width:24, height: 24)
155
+ .background(Color.white)
156
+ .cornerRadius(12)
157
+ .overlay(
158
+ Circle().stroke(Color(hex: 0xFFE8E8E8), lineWidth: 1)
159
+ )
160
+ }
161
+ Text("36+")
162
+ .font(.system(size: 10, weight: Font.Weight.regular))
163
+ .frame(width: 24, height: 24, alignment: .center)
164
+ .foregroundColor(Color(hex: 0xFFEB2F96))
165
+ .background(Color(hex: 0xFFFDEAF4))
166
+ .cornerRadius(12)
167
+ .overlay(
168
+ Circle()
169
+ .stroke(Color(hex: 0xFFE8E8E8), lineWidth: 1)
170
+ )
171
+ }.frame(width: 96, height: 24)
106
172
  }
107
173
  }
108
-
109
174
  }
110
-
111
- }
112
- .padding(.all, 12)
113
- .background(Color(hex: "F2F8FF"))
114
- .cornerRadius(12)
115
- .onTapGesture {
116
- onPressSecurity()
175
+ .frame(width: geo.size.width - 24)
176
+ .padding(EdgeInsets(top: 12, leading: 12, bottom: 12, trailing: 12))
177
+ .background(Color(hex: 0xFFF2F8FF))
178
+ .cornerRadius(12)
179
+ .onTapGesture {
180
+ if let trackEvent = trackEvent {
181
+ trackEvent("service_component_clicked", ["service_name": self.serviceName ?? "", "screen_name": self.screenName ?? "", "component_name": "logo_trust", "url_config": trustBanner.urlConfig])
182
+ }
183
+ if let onPress = onPress {
184
+ onPress(["service_name": self.serviceName ?? "", "screen_name": self.screenName ?? "", "component_name": "logo_trust", "url_config": trustBanner.urlConfig, "feature_code": trustBanner.featureCode])
185
+ }
186
+ }
117
187
  }
118
- .frame(maxWidth: UIScreen.main.bounds.size.width)
119
188
  }
120
189
  }
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
- #Tue Apr 23 16:40:15 ICT 2024
8
- sdk.dir=/Users/hung.dao1/Library/Android/sdk
7
+ #Thu Jan 09 10:43:10 ICT 2025
8
+ sdk.dir=/Users/sophia/Library/Android/sdk
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.113.3-beta.7",
3
+ "version": "0.113.3-pu.1",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},