@momo-kits/native-kits 0.157.7-debug → 0.157.8-debug

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -40,7 +40,7 @@ kotlin {
40
40
  }
41
41
 
42
42
  cocoapods {
43
- version = "0.157.7-debug"
43
+ version = "0.157.8-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -41,8 +41,10 @@ import vn.momo.kits.const.AppTheme
41
41
  import vn.momo.kits.const.Colors
42
42
  import vn.momo.kits.const.Radius
43
43
  import vn.momo.kits.const.Spacing
44
+ import vn.momo.kits.application.ApplicationContext
44
45
  import vn.momo.kits.application.IsShowBaseLineDebug
45
46
  import vn.momo.kits.const.Typography
47
+ import vn.momo.kits.navigation.tracking.ScreenTracker
46
48
  import vn.momo.kits.modifier.conditional
47
49
  import vn.momo.kits.modifier.noFeedbackClickable
48
50
  import vn.momo.kits.platform.BackHandler
@@ -70,6 +72,18 @@ internal fun BottomSheet(
70
72
 
71
73
  val coroutineScope = rememberCoroutineScope()
72
74
 
75
+ val maxApi = LocalMaxApi.current
76
+ val context = ApplicationContext.current
77
+
78
+ DisposableEffect(Unit) {
79
+ ScreenTracker.trackPopupDisplayed(
80
+ maxApi = maxApi,
81
+ context = context,
82
+ parentScreenName = ScreenTracker.getLastScreenName()
83
+ )
84
+ onDispose { }
85
+ }
86
+
73
87
  suspend fun openEvent(){
74
88
  backgroundAlpha.animateTo(
75
89
  targetValue = 0.3f,
@@ -236,4 +250,4 @@ sealed class BottomHeader {
236
250
  data class Custom(
237
251
  val content: @Composable () -> Unit
238
252
  ) : BottomHeader()
239
- }
253
+ }
@@ -22,10 +22,12 @@ import androidx.compose.ui.draw.scale
22
22
  import androidx.compose.ui.graphics.Color
23
23
  import androidx.compose.ui.unit.dp
24
24
  import kotlinx.coroutines.launch
25
+ import vn.momo.kits.application.ApplicationContext
25
26
  import vn.momo.kits.application.IsShowBaseLineDebug
26
27
  import vn.momo.kits.const.Colors
27
28
  import vn.momo.kits.modifier.conditional
28
29
  import vn.momo.kits.modifier.noFeedbackClickable
30
+ import vn.momo.kits.navigation.tracking.ScreenTracker
29
31
  import vn.momo.kits.platform.BackHandler
30
32
 
31
33
  @Composable
@@ -41,6 +43,18 @@ internal fun ModalScreen(
41
43
 
42
44
  val coroutineScope = rememberCoroutineScope()
43
45
 
46
+ val maxApi = LocalMaxApi.current
47
+ val context = ApplicationContext.current
48
+
49
+ DisposableEffect(Unit) {
50
+ ScreenTracker.trackPopupDisplayed(
51
+ maxApi = maxApi,
52
+ context = context,
53
+ parentScreenName = ScreenTracker.getLastScreenName()
54
+ )
55
+ onDispose { }
56
+ }
57
+
44
58
  fun openEvent() {
45
59
  coroutineScope.launch {
46
60
  launch {
@@ -116,4 +130,4 @@ internal fun ModalScreen(
116
130
  content()
117
131
  }
118
132
  }
119
- }
133
+ }
@@ -72,6 +72,7 @@ val LocalNavigation = staticCompositionLocalOf<Navigation> {
72
72
 
73
73
  @Stable
74
74
  data class NavigationOptions(
75
+ val screenName: String? = null,
75
76
  val onBackHandler: (() -> Unit)? = null,
76
77
  val hiddenBack: Boolean = false,
77
78
  val headerBackProps: HeaderBackProps = HeaderBackProps(),
@@ -38,6 +38,7 @@ import vn.momo.maxapi.IMaxApi
38
38
 
39
39
  @Composable
40
40
  fun NavigationContainer(
41
+ initialScreenName: String,
41
42
  initialScreen: @Composable () -> Unit,
42
43
  options: NavigationOptions? = null,
43
44
  initialTheme: Theme = defaultTheme,
@@ -73,7 +74,7 @@ fun NavigationContainer(
73
74
  }
74
75
  }
75
76
 
76
- val startDestination = DynamicScreenRegistry.register(initialScreen, options)
77
+ val startDestination = DynamicScreenRegistry.register(initialScreenName, initialScreen, options)
77
78
 
78
79
  CompositionLocalProvider(
79
80
  LocalNavigator provides navigator,
@@ -112,6 +113,7 @@ fun NavigationContainer(
112
113
  if (screen != null){
113
114
  StackScreen(
114
115
  id = route.id,
116
+ name = screen.name,
115
117
  content = screen.content,
116
118
  navigationOptions = screen.options
117
119
  )
@@ -140,6 +142,7 @@ fun NavigationContainer(
140
142
  if (screen != null){
141
143
  StackScreen(
142
144
  id = route.id,
145
+ name = screen.name,
143
146
  content = screen.content,
144
147
  navigationOptions = screen.options
145
148
  )
@@ -28,17 +28,17 @@ class Navigator(
28
28
  ) {
29
29
  private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
30
30
 
31
- fun push(content: @Composable () -> Unit, options: NavigationOptions? = null) {
32
- val route = DynamicScreenRegistry.register(content, options)
31
+ fun push(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
32
+ val route = DynamicScreenRegistry.register(screenName, content, options)
33
33
  navController.navigate(DynamicScreenRoute(route.id))
34
34
  }
35
35
 
36
- fun replace(content: @Composable () -> Unit, options: NavigationOptions? = null) {
36
+ fun replace(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
37
37
  if (navController.previousBackStackEntry != null){
38
38
  val latestScreen = DynamicScreenRegistry.getLatestScreen()
39
39
  latestScreen?.let { DynamicScreenRegistry.unregisterScreen(it.id) }
40
40
  navController.popBackStack()
41
- push(content, options)
41
+ push(screenName, content, options)
42
42
  }
43
43
  }
44
44
 
@@ -103,15 +103,15 @@ class Navigator(
103
103
  }
104
104
  }
105
105
 
106
- fun present(content: @Composable () -> Unit, options: NavigationOptions? = null) {
107
- val route = DynamicScreenRegistry.register(content, options)
106
+ fun present(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
107
+ val route = DynamicScreenRegistry.register(screenName, content, options)
108
108
  navController.navigate(DynamicDialogRoute(route.id))
109
109
  }
110
110
 
111
- fun reset(content: @Composable () -> Unit, options: NavigationOptions? = null) {
111
+ fun reset(screenName: String, content: @Composable () -> Unit, options: NavigationOptions? = null) {
112
112
  DynamicScreenRegistry.unregisterAll()
113
113
 
114
- val route = DynamicScreenRegistry.register(content, options)
114
+ val route = DynamicScreenRegistry.register(screenName, content, options)
115
115
  navController.navigate(DynamicScreenRoute(route.id)) {
116
116
  popUpTo(0) { inclusive = true }
117
117
  }
@@ -176,6 +176,7 @@ data class DynamicDialogRoute(val id: Int)
176
176
 
177
177
  data class DynamicScreen(
178
178
  val id: Int,
179
+ val name: String,
179
180
  val content: @Composable () -> Unit,
180
181
  var options: NavigationOptions? = null
181
182
  )
@@ -184,10 +185,11 @@ object DynamicScreenRegistry {
184
185
  private val screens = mutableMapOf<Int, DynamicScreen>()
185
186
  private var idCounter = 1
186
187
 
187
- fun register(content: @Composable () -> Unit, options: NavigationOptions?): DynamicScreenRoute {
188
+ fun register(screenName: String, content: @Composable () -> Unit, options: NavigationOptions?): DynamicScreenRoute {
188
189
  val id = idCounter++
189
190
  screens[id] = DynamicScreen(
190
191
  id = id,
192
+ name = screenName,
191
193
  content = content,
192
194
  options = options
193
195
  )
@@ -63,26 +63,81 @@ import vn.momo.kits.navigation.component.HeaderType
63
63
  import vn.momo.kits.navigation.component.InputSearchType
64
64
  import vn.momo.kits.platform.BackHandler
65
65
  import vn.momo.kits.platform.getAndroidBuildVersion
66
+ import vn.momo.kits.navigation.tracking.ScreenTracker
67
+ import vn.momo.kits.navigation.tracking.ScreenTrackingState
68
+ import kotlinx.coroutines.delay
69
+ import vn.momo.kits.application.ApplicationContext
70
+ import kotlin.time.Clock
71
+ import kotlin.time.ExperimentalTime
66
72
 
67
73
  internal val LocalFooterHeightPx = staticCompositionLocalOf { mutableIntStateOf(0) }
68
74
  internal val LocalHeaderRightWidthPx = staticCompositionLocalOf { mutableIntStateOf(0) }
69
75
 
70
- @OptIn(ExperimentalMaterialApi::class)
76
+ @OptIn(ExperimentalMaterialApi::class, ExperimentalTime::class)
71
77
  @Composable
72
78
  internal fun StackScreen(
73
79
  content: @Composable () -> Unit,
74
80
  navigationOptions: NavigationOptions? = null,
75
81
  id: Int = -1,
82
+ name: String = "",
76
83
  bottomTabIndex: Int = -1,
77
84
  onBackHandler: (() -> Unit)? = null,
78
85
  ) {
79
86
  val navigator = LocalNavigator.current
87
+ val maxApi = LocalMaxApi.current
88
+ val context = ApplicationContext.current
80
89
  val statusBar = AppStatusBar.current
81
90
  val density = LocalDensity.current
82
91
  val navigation = remember { Navigation(id = id, bottomTabIndex = bottomTabIndex, initOptions = navigationOptions) }
83
92
 
84
93
  val options by navigation.currentOptions
85
94
 
95
+ // Auto tracking state
96
+ val trackingState = remember {
97
+ ScreenTrackingState().apply {
98
+ startTime = Clock.System.now().toEpochMilliseconds()
99
+ }
100
+ }
101
+
102
+ // Determine action: push or back
103
+ val action = remember {
104
+ if (ScreenTracker.getLastScreenName() != null) "push" else "back"
105
+ }
106
+
107
+ // Auto tracking effects
108
+ LaunchedEffect(name) {
109
+ // Track screen navigated immediately
110
+ ScreenTracker.trackScreenNavigated(
111
+ maxApi = maxApi,
112
+ context = context,
113
+ screenName = name,
114
+ action = action,
115
+ state = trackingState
116
+ )
117
+
118
+ // Track screen displayed after 2 seconds (debounce)
119
+ delay(2000)
120
+ val loadTime = Clock.System.now().toEpochMilliseconds() - trackingState.startTime
121
+ ScreenTracker.trackScreenDisplayed(
122
+ maxApi = maxApi,
123
+ context = context,
124
+ screenName = name,
125
+ duration = loadTime,
126
+ state = trackingState
127
+ )
128
+
129
+ // Track screen interacted after displayed
130
+ val interactionTime = Clock.System.now().toEpochMilliseconds() - trackingState.startTime
131
+ ScreenTracker.trackScreenInteracted(
132
+ maxApi = maxApi,
133
+ context = context,
134
+ screenName = name,
135
+ duration = interactionTime - loadTime,
136
+ totalDuration = interactionTime,
137
+ state = trackingState
138
+ )
139
+ }
140
+
86
141
  val limit = with(density) {
87
142
  (statusBar).toPx() + HEADER_HEIGHT
88
143
  }.toInt()
@@ -0,0 +1,167 @@
1
+ package vn.momo.kits.navigation.tracking
2
+
3
+ import vn.momo.kits.application.MiniAppContext
4
+ import vn.momo.maxapi.IMaxApi
5
+ import kotlin.time.Clock
6
+ import kotlin.time.ExperimentalTime
7
+
8
+ /**
9
+ * Screen tracking state holder
10
+ */
11
+ class ScreenTrackingState {
12
+ @OptIn(ExperimentalTime::class)
13
+ var startTime: Long = Clock.System.now().toEpochMilliseconds()
14
+ var endTime: Long = startTime
15
+ var releaseNavigated: Boolean = false
16
+ var releaseLoad: Boolean = false
17
+ var releaseInteraction: Boolean = false
18
+ var previousScreenName: String? = null
19
+ var action: String = "push"
20
+ }
21
+
22
+ /**
23
+ * Screen tracker for auto tracking events
24
+ *
25
+ * Tracks 3 main events:
26
+ * - auto_screen_navigated: When screen is focused
27
+ * - auto_screen_displayed: When screen finished loading
28
+ * - auto_screen_interacted: When screen is ready for interaction
29
+ */
30
+ object ScreenTracker {
31
+
32
+ private var lastScreenName: String? = null
33
+
34
+ /**
35
+ * Track screen navigation event
36
+ * Called when screen is focused
37
+ */
38
+ fun trackScreenNavigated(
39
+ maxApi: IMaxApi?,
40
+ context: MiniAppContext?,
41
+ screenName: String,
42
+ action: String,
43
+ state: ScreenTrackingState
44
+ ) {
45
+ if (state.releaseNavigated) return
46
+
47
+ val previousScreen = lastScreenName
48
+ lastScreenName = screenName
49
+
50
+ println("Tracking screen navigated native kit: $screenName")
51
+ maxApi?.track(
52
+ eventName = "auto_screen_navigated",
53
+ params = buildBaseParams(context, screenName).apply {
54
+ put("pre_screen_name", previousScreen)
55
+ put("state", "navigated")
56
+ put("action", action)
57
+ }
58
+ )
59
+
60
+ state.releaseNavigated = true
61
+ state.previousScreenName = previousScreen
62
+ }
63
+
64
+ /**
65
+ * Track screen displayed event
66
+ * Called when screen finished loading all elements
67
+ */
68
+ fun trackScreenDisplayed(
69
+ maxApi: IMaxApi?,
70
+ context: MiniAppContext?,
71
+ screenName: String,
72
+ duration: Long,
73
+ state: ScreenTrackingState
74
+ ) {
75
+ if (state.releaseLoad) return
76
+
77
+ maxApi?.track(
78
+ eventName = "auto_screen_displayed",
79
+ params = buildBaseParams(context, screenName).apply {
80
+ put("state", "load")
81
+ put("duration", duration)
82
+ }
83
+ )
84
+
85
+ state.releaseLoad = true
86
+ }
87
+
88
+ /**
89
+ * Track screen interacted event
90
+ * Called when screen is ready for user interaction
91
+ */
92
+ fun trackScreenInteracted(
93
+ maxApi: IMaxApi?,
94
+ context: MiniAppContext?,
95
+ screenName: String,
96
+ duration: Long,
97
+ totalDuration: Long,
98
+ state: ScreenTrackingState
99
+ ) {
100
+ if (state.releaseInteraction) return
101
+
102
+ maxApi?.track(
103
+ eventName = "auto_screen_interacted",
104
+ params = buildBaseParams(context, screenName).apply {
105
+ put("state", "interaction")
106
+ put("duration", duration)
107
+ put("total_duration", totalDuration)
108
+ }
109
+ )
110
+
111
+ state.releaseInteraction = true
112
+ }
113
+
114
+ /**
115
+ * Build base tracking params from MiniAppContext
116
+ */
117
+ private fun buildBaseParams(
118
+ context: MiniAppContext?,
119
+ screenName: String,
120
+ componentName: String = "Screen"
121
+ ): MutableMap<String, Any?> {
122
+ return mutableMapOf(
123
+ "app_id" to context?.appId,
124
+ "feature_code" to context?.appCode,
125
+ "screen_name" to screenName,
126
+ "component_name" to componentName,
127
+ "tracking_source" to 1
128
+ )
129
+ }
130
+
131
+ /**
132
+ * Track popup/modal displayed event
133
+ *
134
+ * @param popupName name of the popup/modal
135
+ * @param parentScreenName the screen that shows the popup
136
+ */
137
+ fun trackPopupDisplayed(
138
+ maxApi: IMaxApi?,
139
+ context: MiniAppContext?,
140
+ parentScreenName: String?
141
+ ) {
142
+ maxApi?.track(
143
+ eventName = "auto_popup_displayed",
144
+ params = mutableMapOf<String, Any?>(
145
+ "screen_name" to parentScreenName,
146
+ "tracking_source" to 1,
147
+ "app_id" to context?.appId,
148
+ "feature_code" to context?.appCode,
149
+ "kits_version" to null,
150
+ "component_name" to "Modal"
151
+ )
152
+ )
153
+ }
154
+
155
+ /**
156
+ * Get previous screen name for back navigation detection
157
+ */
158
+ fun getLastScreenName(): String? = lastScreenName
159
+
160
+ /**
161
+ * Reset tracker (for testing or cleanup)
162
+ */
163
+ @Suppress("UNUSED")
164
+ fun reset() {
165
+ lastScreenName = null
166
+ }
167
+ }
package/gradle.properties CHANGED
@@ -18,7 +18,7 @@ kotlin.apple.xcodeCompatibility.nowarn=true
18
18
  name="ComposeKits"
19
19
  group=vn.momo.kits
20
20
  artifact.id=kits
21
- version=0.157.7
21
+ version=0.157.8
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.157.7-debug",
3
+ "version": "0.157.8-debug",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},