@momo-kits/native-kits 0.114.1-beta.1 → 0.114.2-beta.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.
@@ -41,7 +41,6 @@ kotlin {
41
41
  implementation(libs.coil.multiplatform.core)
42
42
  implementation(libs.coil.multiplatform.network.ktor)
43
43
  implementation(libs.jetbrains.serialization.json)
44
- implementation(libs.kotlinx.datetime)
45
44
  }
46
45
  }
47
46
  val androidMain by getting {
@@ -88,32 +88,8 @@ fun HeaderRight(
88
88
  }
89
89
  }
90
90
 
91
- private fun getNavigationButtonConfig(
92
- headerRight: HeaderRightData? = null,
93
- isFavorite: Boolean = false,
94
- onPressShortcut: () -> Unit,
95
- onPressMore: () -> Unit
96
- ): NavigationButtonConfig {
97
- val totalTools = headerRight?.tools?.sumOf { it.items.size } ?: 0
98
- var icon = if (isFavorite) "pin_star_checked" else "pin_star"
99
- var onClickHandler = onPressShortcut
100
- if (totalTools > 1 || headerRight?.useMore == true) {
101
- icon = "navigation_more_icon"
102
- onClickHandler = onPressMore
103
- } else if (totalTools == 1 && !headerRight?.tools.isNullOrEmpty()) {
104
- val singleTool = headerRight?.tools?.first()?.items?.firstOrNull()
105
- if (singleTool != null) {
106
- icon = singleTool.icon
107
- onClickHandler = {
108
- headerRight.toolCallback?.invoke(singleTool.key)
109
- }
110
- }
111
- }
112
- return NavigationButtonConfig(icon, onClickHandler)
113
- }
114
-
115
91
  @Composable
116
- private fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
92
+ fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
117
93
  var isFavorite by remember { mutableStateOf(false) }
118
94
  val isLoading by remember { mutableStateOf(false) }
119
95
  val api = PlatformApi.current as? ComposeApi
@@ -172,8 +148,34 @@ private fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
172
148
  }
173
149
  }
174
150
 
151
+ fun getNavigationButtonConfig(
152
+ tools: List<ToolGroup>? = emptyList(),
153
+ useMore: Boolean? = false,
154
+ isFavorite: Boolean = false,
155
+ onPressShortcut: () -> Unit,
156
+ onPressMore: () -> Unit
157
+ ): NavigationButtonConfig {
158
+ val totalTools = tools?.sumOf { it.items.size } ?: 0
159
+ var icon = if (isFavorite) "pin_star_checked" else "pin_star"
160
+ var onClickHandler = onPressShortcut
161
+ if (totalTools > 1 || useMore == true) {
162
+ icon = "navigation_more_icon"
163
+ onClickHandler = onPressMore
164
+ } else if (totalTools == 1 && !tools.isNullOrEmpty()) {
165
+ val singleTool = tools.first().items.firstOrNull()
166
+ if (singleTool != null) {
167
+ icon = singleTool.icon
168
+ onClickHandler = {
169
+ headerRight?.toolCallback?.invoke(singleTool.key)
170
+ }
171
+ }
172
+ }
173
+ return NavigationButtonConfig(icon, onClickHandler)
174
+ }
175
+
175
176
  val navButtonConfig = getNavigationButtonConfig(
176
- headerRight,
177
+ headerRight?.tools,
178
+ headerRight?.useMore,
177
179
  isFavorite,
178
180
  ::onPressShortcut,
179
181
  ::onPressMore
@@ -186,7 +188,7 @@ private fun ToolkitHeaderRight(headerRight: HeaderRightData? = null) {
186
188
  val toolkitWidth = if (context !== null) 65.dp else 28.dp
187
189
 
188
190
  var isShowShortcut = headerRight?.useShortcut == true
189
-
191
+ //backup case context null of more icon
190
192
  if (headerRight?.useMore == true && context == null) {
191
193
  isShowShortcut = false
192
194
  }
@@ -262,7 +264,7 @@ fun NavigationButton(
262
264
  ) {
263
265
  Icon(
264
266
  source = icon,
265
- size = 20.dp
267
+ size = 16.dp
266
268
  )
267
269
  }
268
270
 
@@ -9,6 +9,7 @@ import androidx.compose.runtime.remember
9
9
  import androidx.compose.runtime.setValue
10
10
  import androidx.compose.runtime.staticCompositionLocalOf
11
11
  import androidx.compose.ui.unit.Dp
12
+ import kotlinx.serialization.Serializable
12
13
  import vn.momo.kits.components.TrustBannerData
13
14
  import vn.momo.kits.const.AppStatusBar
14
15
  import vn.momo.kits.const.AppTheme
@@ -26,35 +27,20 @@ interface ComposeApi {
26
27
  }
27
28
 
28
29
  class Navigator {
29
- fun push(content: @Composable () -> Unit) {
30
- //implement
31
- }
32
- fun present(content: @Composable () -> Unit) {
33
- //implement
34
- }
35
- fun reset(content: @Composable () -> Unit) {
36
- //implement
37
- }
38
- fun pop() {
39
- //implement
40
- }
41
- fun replace(content: @Composable () -> Unit) {
42
- //implement
43
- }
44
- fun popToTop() {
45
- //implement
46
- }
30
+ fun push(content: @Composable () -> Unit) {}
31
+ fun present(content: @Composable () -> Unit) {}
32
+ fun reset(content: @Composable () -> Unit) {}
33
+ fun pop() {}
34
+ fun replace(content: @Composable () -> Unit) {}
35
+ fun popToTop() {}
47
36
  fun showModal(
48
37
  onClose: () -> Unit = {},
49
38
  canBackgroundClose: Boolean = true,
50
39
  content: @Composable () -> Unit,
51
40
  ) {
52
- //implement
53
41
  }
54
42
 
55
- fun showBottomSheet(content: @Composable () -> Unit) {
56
- //implement
57
- }
43
+ fun showBottomSheet(content: @Composable () -> Unit) {}
58
44
  }
59
45
 
60
46
  data class MiniAppContext(
@@ -71,8 +57,8 @@ data class MiniAppContext(
71
57
 
72
58
  val PlatformApi = staticCompositionLocalOf<Any?> { null }
73
59
 
74
- val AppNavigator = staticCompositionLocalOf<Navigator> {
75
- error("no navigator provided!")
60
+ val AppNavigator = staticCompositionLocalOf<Navigator?> {
61
+ null
76
62
  }
77
63
 
78
64
  val ApplicationContext = staticCompositionLocalOf<MiniAppContext?> {
@@ -83,10 +69,6 @@ val AppConfig = staticCompositionLocalOf<KitConfig?> {
83
69
  null
84
70
  }
85
71
 
86
- val AppLanguage = staticCompositionLocalOf<String?> {
87
- null
88
- }
89
-
90
72
  data class KitConfig(
91
73
  val trustBanner: TrustBannerData? = null,
92
74
  val headerBar: String? = null,
@@ -100,7 +82,6 @@ fun ApplicationContainer(
100
82
  statusBarHeight: Dp? = AppStatusBar.current,
101
83
  applicationContext: MiniAppContext? = null,
102
84
  config: KitConfig? = null,
103
- language: String? = null,
104
85
  content: @Composable () -> Unit,
105
86
  ) {
106
87
  var appTheme by remember { mutableStateOf(theme) }
@@ -129,8 +110,7 @@ fun ApplicationContainer(
129
110
  AppNavigator provides Navigator(),
130
111
  AppStatusBar provides appStatusBarHeight,
131
112
  ApplicationContext provides applicationContext,
132
- AppConfig provides config,
133
- AppLanguage provides language
113
+ AppConfig provides config
134
114
  ) {
135
115
  content()
136
116
  }
@@ -64,7 +64,7 @@ fun Screen(
64
64
  onContentLayout: ((LayoutCoordinates) -> Unit)? = null,
65
65
  useAvoidKeyboard: Boolean = true,
66
66
  footer: @Composable (() -> Unit)? = null,
67
- headerRight: @Composable (() -> Unit)? = null,
67
+ headerRight: @Composable (() -> Unit)? = { HeaderRight() },
68
68
  fabProps: FabProps? = null,
69
69
  animatedHeader: AnimatedHeader? = null,
70
70
  layoutOffset: Dp = 56.dp,
@@ -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
  )
@@ -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,12 +13,12 @@ public class MiniAppContext {
13
13
  public var appName: Any? = nil
14
14
  public var appIcon: String = ""
15
15
  public var description: Any? = nil
16
- public var support: [String: Any] = [:]
17
- public var toolkitConfig: [String: Any] = [:]
18
- public var providerId: String = "momo"
19
- public var permissions: [[String: Any]] = []
20
-
21
- public init(appId: String, appCode: String, appName: Any? = nil, appIcon: String, description: Any? = nil, support: [String: Any] = [:], toolkitConfig: [String: Any] = [:], providerId: String = "momo", permissions: [[String: Any]] = []) {
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) {
22
22
  self.appId = appId
23
23
  self.appCode = appCode
24
24
  self.appName = appName
@@ -31,20 +31,12 @@ public class MiniAppContext {
31
31
  }
32
32
  }
33
33
 
34
- public class KitConfig {
35
- public var trustBanner: TrustBannerData? = nil
36
- public var headerBar: String? = nil
37
- public var headerGradient: String? = nil
38
- }
39
-
40
34
  public class ApplicationEnvironment: ObservableObject {
41
35
  let applicationContext: MiniAppContext?
42
- let composeApi: KitComposeApi?
43
- let config: KitConfig?
44
-
45
- public init(applicationContext: MiniAppContext? = nil, composeApi: KitComposeApi? = nil, config: KitConfig? = nil) {
36
+ let composeApi: KitComposeApi
37
+
38
+ public init(applicationContext: MiniAppContext?, composeApi: KitComposeApi) {
46
39
  self.applicationContext = applicationContext
47
40
  self.composeApi = composeApi
48
- self.config = config
49
41
  }
50
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] = [
@@ -135,13 +136,13 @@ struct ToolkitHeaderRight: View {
135
136
  "name": context?.appName,
136
137
  "icon": context?.appIcon,
137
138
  "description": context?.description,
138
- "support": context?.support,
139
- "toolkitConfig": context?.toolkitConfig,
140
- "providerId": context?.providerId,
141
- "permissions": context?.permissions
139
+ "support": context?.support,
140
+ "toolkitConfig": context?.toolkitConfig,
141
+ "providerId": context?.providerId,
142
+ "permissions": context?.permissions
142
143
  ]
143
144
  ]
144
- environment.composeApi?.request(
145
+ environment.composeApi.request(
145
146
  funcName: "showTools",
146
147
  params: params
147
148
  ) { response in
@@ -156,12 +157,12 @@ struct ToolkitHeaderRight: View {
156
157
  }
157
158
  }
158
159
  }
159
-
160
+
160
161
  private func getNavigationButtonConfig() -> NavigationButtonConfig {
161
162
  let totalTools = headerRight?.tools.reduce(0) { $0 + $1.items.count } ?? 0
162
163
  var icon = isFavorite ? "pin_star_checked" : "pin_star"
163
164
  var onClickHandler: () -> Void = onPressShortcut
164
-
165
+
165
166
  if totalTools > 1 || headerRight?.useMore == true {
166
167
  icon = "navigation_more_icon"
167
168
  onClickHandler = onPressMore
@@ -171,16 +172,16 @@ struct ToolkitHeaderRight: View {
171
172
  headerRight?.toolCallback?(singleTool.key)
172
173
  }
173
174
  }
174
-
175
+
175
176
  return NavigationButtonConfig(icon: icon, onPress: onClickHandler)
176
177
  }
177
-
178
+
178
179
  var body: some View {
179
180
  let navButtonConfig = getNavigationButtonConfig()
180
181
  let showBadge = headerRight?.tools.contains { group in
181
182
  group.items.contains { $0.showBadge }
182
183
  } ?? false
183
-
184
+
184
185
  HStack(alignment: .center) {
185
186
  if headerRight?.useShortcut == true {
186
187
  NavigationButton(
@@ -190,18 +191,18 @@ struct ToolkitHeaderRight: View {
190
191
  onClick: navButtonConfig.onPress
191
192
  )
192
193
  }
193
-
194
+
194
195
  HStack(alignment: .center, spacing: 0) {
195
196
  Icon(source: "help_center", size: 20)
196
197
  .padding(4)
197
198
  .onTapGesture {
198
199
  onPressHelpCenter()
199
200
  }
200
-
201
+
201
202
  Rectangle()
202
203
  .fill(Colors.black20)
203
204
  .frame(width: 0.5, height: 12)
204
-
205
+
205
206
  Icon(source: "16_navigation_close_circle", size: 20, color: Colors.black17)
206
207
  .padding(4)
207
208
  .onTapGesture {
@@ -225,7 +226,7 @@ struct NavigationButton: View {
225
226
  var icon: String
226
227
  var showBadge: Bool
227
228
  var onClick: () -> Void
228
-
229
+
229
230
  var body: some View {
230
231
  ZStack {
231
232
  Circle()
@@ -234,9 +235,9 @@ struct NavigationButton: View {
234
235
  Circle()
235
236
  .stroke(Color.black.opacity(0.2), lineWidth: 0.2)
236
237
  )
237
-
238
+
238
239
  Icon(source: icon, size: 16)
239
-
240
+
240
241
  if showBadge {
241
242
  BadgeDot(size: .small)
242
243
  .offset(x: -2, y: -2)