@momo-kits/native-kits 0.160.1-scrolltotop.18-debug → 0.160.1-scrolltotop.19-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.160.1-scrolltotop.18-debug"
43
+ version = "0.160.1-scrolltotop.19-debug"
44
44
  summary = "IOS Shared module"
45
45
  homepage = "https://momo.vn"
46
46
  ios.deploymentTarget = "15.0"
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |spec|
2
2
  spec.name = 'compose'
3
- spec.version = '0.160.1-scrolltotop.15'
3
+ spec.version = '0.160.1-scrolltotop.18'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
@@ -3,6 +3,9 @@ package vn.momo.kits.navigation
3
3
  import androidx.compose.runtime.Composable
4
4
 
5
5
  @Composable
6
- internal actual fun RegisterScrollToTop(navigator: Navigator, callback: (() -> Unit)?) {
6
+ internal actual fun RegisterScrollToTop(navigator: Navigation, callback: (() -> Unit)?) {
7
7
  // No-op on Android: status bar tap doesn't trigger scroll-to-top on this platform.
8
8
  }
9
+
10
+ internal actual fun onNavigatorEntered(navigator: Navigator) = Unit
11
+ internal actual fun onNavigatorExited(navigator: Navigator) = Unit
@@ -27,7 +27,7 @@ fun NavigationContainer(
27
27
  statusBarHeight: Dp? = null,
28
28
  config: KitConfig? = null,
29
29
  language: String? = null
30
- ){
30
+ ) {
31
31
  val navController = rememberNavController()
32
32
  val navigator = remember { Navigator(navController = navController, maxApi = maxApi) }
33
33
  val statusBarHeight = statusBarHeight ?: getAppStatusBarHeight()
@@ -39,6 +39,7 @@ fun NavigationContainer(
39
39
  val theme = remember { mutableStateOf(initialTheme) }
40
40
 
41
41
  LaunchedEffect(Unit) {
42
+ onNavigatorEntered(navigator)
42
43
  val headerBar = config?.headerBar
43
44
  if (headerBar != null && theme.value.assets.headerBackground == null) {
44
45
  theme.value = theme.value.copy(
@@ -47,6 +48,7 @@ fun NavigationContainer(
47
48
  )
48
49
  )
49
50
  }
51
+
50
52
  }
51
53
 
52
54
  val startDestination = DynamicScreenRegistry.register(initialScreenName, initialScreen, options)
@@ -131,6 +133,7 @@ fun NavigationContainer(
131
133
 
132
134
  DisposableEffect(Unit) {
133
135
  onDispose {
136
+ onNavigatorExited(navigator)
134
137
  navigator.dispose()
135
138
  }
136
139
  }
@@ -9,6 +9,7 @@ import androidx.compose.runtime.staticCompositionLocalOf
9
9
  import androidx.compose.ui.Alignment
10
10
  import androidx.compose.ui.Modifier
11
11
  import androidx.navigation.NavController
12
+ import androidx.navigation.toRoute
12
13
  import kotlinx.coroutines.CoroutineScope
13
14
  import kotlinx.coroutines.Dispatchers
14
15
  import kotlinx.coroutines.SupervisorJob
@@ -3,4 +3,9 @@ package vn.momo.kits.navigation
3
3
  import androidx.compose.runtime.Composable
4
4
 
5
5
  @Composable
6
- internal expect fun RegisterScrollToTop(navigator: Navigator, callback: (() -> Unit)?)
6
+ internal expect fun RegisterScrollToTop(navigator: Navigation, callback: (() -> Unit)?)
7
+
8
+
9
+ internal expect fun onNavigatorEntered(navigator: Navigator)
10
+
11
+ internal expect fun onNavigatorExited(navigator: Navigator)
@@ -150,9 +150,9 @@ internal fun StackScreen(
150
150
  val footerHeightPx = remember { mutableIntStateOf(0) }
151
151
  val headerRightWidthPx = remember { mutableIntStateOf(0) }
152
152
 
153
- options.scrollData.scrollToTopCallback?.let { cb ->
154
- RegisterScrollToTop(navigator, cb)
155
- }
153
+
154
+ RegisterScrollToTop(navigation, navigation.options?.scrollData?.scrollToTopCallback)
155
+
156
156
 
157
157
  BackHandler(true) { navigator.onBackSafe() }
158
158
 
@@ -164,15 +164,16 @@ internal fun StackScreen(
164
164
  LocalFooterHeightPx provides footerHeightPx,
165
165
  LocalHeaderRightWidthPx provides headerRightWidthPx
166
166
  ) {
167
- Box(Modifier
168
- .fillMaxSize()
169
- .background(options.backgroundColor ?: AppTheme.current.colors.background.default)
170
- .conditional(options.keyboardOptions.keyboardShouldPersistTaps) {
171
- hideKeyboardOnTap()
172
- }
173
- .conditional(options.keyboardOptions.useAvoidKeyboard && supportsImePadding()) {
174
- imePadding()
175
- }
167
+ Box(
168
+ Modifier
169
+ .fillMaxSize()
170
+ .background(options.backgroundColor ?: AppTheme.current.colors.background.default)
171
+ .conditional(options.keyboardOptions.keyboardShouldPersistTaps) {
172
+ hideKeyboardOnTap()
173
+ }
174
+ .conditional(options.keyboardOptions.useAvoidKeyboard && supportsImePadding()) {
175
+ imePadding()
176
+ }
176
177
  ) {
177
178
  Box(Modifier.zIndex(1f)) {
178
179
  HeaderBackground()
@@ -182,7 +183,7 @@ internal fun StackScreen(
182
183
  Header(onBackHandler)
183
184
  }
184
185
 
185
- Column (Modifier.zIndex(6f)) {
186
+ Column(Modifier.zIndex(6f)) {
186
187
  SearchAnimated(isScrollInProgress = scrollInProcess)
187
188
  }
188
189
 
@@ -194,7 +195,7 @@ internal fun StackScreen(
194
195
  FooterContent()
195
196
  }
196
197
 
197
- Box(Modifier.zIndex(7f)){
198
+ Box(Modifier.zIndex(7f)) {
198
199
  FloatingContent()
199
200
  }
200
201
 
@@ -204,15 +205,15 @@ internal fun StackScreen(
204
205
  }
205
206
 
206
207
  @Composable
207
- fun FloatingContent(){
208
+ fun FloatingContent() {
208
209
  val options = LocalOptions.current
209
210
  val density = LocalDensity.current
210
211
  val footerHeightPx = LocalFooterHeightPx.current
211
212
  val scrollState = LocalScrollState.current
212
213
 
213
214
  val fabProps = options.floatingButtonProps ?: return
214
- val bottomPadding = fabProps.bottom ?:
215
- (Spacing.M + if (options.footerComponent != null) with(density){ footerHeightPx.intValue.toDp() } else 0.dp)
215
+ val bottomPadding = fabProps.bottom
216
+ ?: (Spacing.M + if (options.footerComponent != null) with(density) { footerHeightPx.intValue.toDp() } else 0.dp)
216
217
 
217
218
  FloatingButton(
218
219
  scrollPosition = fabProps.scrollState?.value ?: scrollState.value,
@@ -228,17 +229,19 @@ fun FloatingContent(){
228
229
  }
229
230
 
230
231
  @Composable
231
- fun ColumnScope.MainContent(content: @Composable ()-> Unit){
232
+ fun ColumnScope.MainContent(content: @Composable () -> Unit) {
232
233
  val options = LocalOptions.current
233
234
  val inputSearchType = getInputSearchType(options)
234
235
  val density = LocalDensity.current
235
236
  val scrollState = LocalScrollState.current
236
237
 
237
- Spacer(Modifier.height(
238
- if (options.headerType is HeaderType.DefaultOrExtended || (options.headerType is HeaderType.Transparent && !options.headerType.isFullScreenContent))
239
- AppStatusBar.current + HEADER_HEIGHT.dp else 0.dp)
238
+ Spacer(
239
+ Modifier.height(
240
+ if (options.headerType is HeaderType.DefaultOrExtended || (options.headerType is HeaderType.Transparent && !options.headerType.isFullScreenContent))
241
+ AppStatusBar.current + HEADER_HEIGHT.dp else 0.dp
242
+ )
240
243
  )
241
- if (inputSearchType == InputSearchType.Animated){
244
+ if (inputSearchType == InputSearchType.Animated) {
242
245
  val scrollDp = with(density) { scrollState.value.toDp() }
243
246
 
244
247
  val animatedTopPadding by animateDpAsState(
@@ -247,17 +250,18 @@ fun ColumnScope.MainContent(content: @Composable ()-> Unit){
247
250
  )
248
251
  Spacer(Modifier.height(animatedTopPadding))
249
252
  }
250
- Column (Modifier
251
- .fillMaxWidth()
252
- .weight(1f)
253
- .conditional(options.scrollData.scrollable && options.scrollData.scrollState is ScrollState) {
254
- verticalScroll(scrollState)
255
- }
253
+ Column(
254
+ Modifier
255
+ .fillMaxWidth()
256
+ .weight(1f)
257
+ .conditional(options.scrollData.scrollable && options.scrollData.scrollState is ScrollState) {
258
+ verticalScroll(scrollState)
259
+ }
256
260
  ) {
257
261
  ScreenContent(content = content)
258
262
  }
259
263
 
260
- if (options.footerComponent != null){
264
+ if (options.footerComponent != null) {
261
265
  val footerHeight = LocalFooterHeightPx.current
262
266
  val footerHeightDp = with(density) { footerHeight.value.toDp() }
263
267
  Spacer(Modifier.height(footerHeightDp))
@@ -265,17 +269,17 @@ fun ColumnScope.MainContent(content: @Composable ()-> Unit){
265
269
  }
266
270
 
267
271
  @Composable
268
- fun ScreenContent(content: @Composable () -> Unit){
272
+ fun ScreenContent(content: @Composable () -> Unit) {
269
273
  val scrollState = LocalScrollState.current
270
274
  val options = LocalOptions.current
271
275
 
272
- if (options.headerType is HeaderType.Animated){
276
+ if (options.headerType is HeaderType.Animated) {
273
277
  val animatedHeader = options.headerType
274
278
  Box {
275
- Box(Modifier.fillMaxWidth().aspectRatio(animatedHeader.aspectRatio.value)){
279
+ Box(Modifier.fillMaxWidth().aspectRatio(animatedHeader.aspectRatio.value)) {
276
280
  animatedHeader.composable.invoke(scrollState.value)
277
281
  }
278
- Box(Modifier.offset(x = 0.dp, y = AppStatusBar.current + HEADER_HEIGHT.dp + animatedHeader.layoutOffSet)){
282
+ Box(Modifier.offset(x = 0.dp, y = AppStatusBar.current + HEADER_HEIGHT.dp + animatedHeader.layoutOffSet)) {
279
283
  content()
280
284
  }
281
285
  }
@@ -285,9 +289,9 @@ fun ScreenContent(content: @Composable () -> Unit){
285
289
  }
286
290
 
287
291
  @Composable
288
- fun FooterContent(){
292
+ fun FooterContent() {
289
293
  val options = LocalOptions.current
290
- if (options.footerComponent != null){
294
+ if (options.footerComponent != null) {
291
295
  val ime = WindowInsets.ime
292
296
  val density = LocalDensity.current
293
297
  val imeBottom = ime.getBottom(density)
@@ -299,11 +303,11 @@ fun FooterContent(){
299
303
  }
300
304
 
301
305
  @Composable
302
- fun OverplayView(bottomTabIndex: Int, id: Int){
306
+ fun OverplayView(bottomTabIndex: Int, id: Int) {
303
307
  val overplayType = OverplayComponentRegistry.getOverplayType()
304
308
 
305
309
  if (overplayType != null) {
306
- Box(Modifier.zIndex(if (overplayType == OverplayComponentType.SNACK_BAR) 3f else 8f).fillMaxSize()){
310
+ Box(Modifier.zIndex(if (overplayType == OverplayComponentType.SNACK_BAR) 3f else 8f).fillMaxSize()) {
307
311
  if (bottomTabIndex != -1) return@Box
308
312
  if (OverplayComponentRegistry.currentRootId() != id) return@Box
309
313
  OverplayComponentRegistry.OverlayComponent()
@@ -326,22 +330,24 @@ fun Footer(footerComponent: @Composable (() -> Unit)?, bottomPadding: Dp) {
326
330
  Box(Modifier.onGloballyPositioned {
327
331
  if (footerHeightPx.intValue != it.size.height) footerHeightPx.intValue = it.size.height
328
332
  }) {
329
- Box(Modifier
330
- .fillMaxWidth()
331
- .background(AppTheme.current.colors.background.surface)
332
- .conditional(IsShowBaseLineDebug) {
333
- border(1.dp, Colors.blue_03)
334
- }
335
- .padding(top = Spacing.S, start = Spacing.M, end = Spacing.M, bottom = bottomPadding + Spacing.S)
336
- ){
333
+ Box(
334
+ Modifier
335
+ .fillMaxWidth()
336
+ .background(AppTheme.current.colors.background.surface)
337
+ .conditional(IsShowBaseLineDebug) {
338
+ border(1.dp, Colors.blue_03)
339
+ }
340
+ .padding(top = Spacing.S, start = Spacing.M, end = Spacing.M, bottom = bottomPadding + Spacing.S)
341
+ ) {
337
342
  footerComponent.invoke()
338
343
  }
339
344
 
340
- Box(modifier = Modifier
341
- .fillMaxWidth()
342
- .height(6.dp)
343
- .offset(x = 0.dp, y = (-6).dp)
344
- .background(shadowBrush)
345
+ Box(
346
+ modifier = Modifier
347
+ .fillMaxWidth()
348
+ .height(6.dp)
349
+ .offset(x = 0.dp, y = (-6).dp)
350
+ .background(shadowBrush)
345
351
  )
346
352
  }
347
353
  }
@@ -351,6 +357,7 @@ data class InputSearchLayoutParams(
351
357
  val startPadding: Dp,
352
358
  val endPadding: Dp
353
359
  )
360
+
354
361
  @Composable
355
362
  fun SearchAnimated(
356
363
  isScrollInProgress: Boolean
@@ -465,6 +472,7 @@ internal fun isKeyboardVisible(): Boolean {
465
472
  val density = LocalDensity.current
466
473
  return ime.getBottom(density) > 0
467
474
  }
475
+
468
476
  private fun quantize(value: Int, stepPx: Int): Int {
469
477
  if (stepPx <= 1) return value
470
478
  return (value / stepPx) * stepPx
@@ -544,13 +552,14 @@ internal val LocalOptions = staticCompositionLocalOf<NavigationOptions> { error(
544
552
 
545
553
  val StackScreenScrollableState = staticCompositionLocalOf<ScrollableState?> { null }
546
554
 
547
- internal fun getInputSearchType(options: NavigationOptions): InputSearchType{
555
+ internal fun getInputSearchType(options: NavigationOptions): InputSearchType {
548
556
  return when (val headerType = options.headerType) {
549
557
  is HeaderType.DefaultOrExtended -> when {
550
558
  headerType.inputSearchProps == null -> InputSearchType.None
551
559
  headerType.useAnimated -> InputSearchType.Animated
552
560
  else -> InputSearchType.Header
553
561
  }
562
+
554
563
  else -> InputSearchType.None
555
564
  }
556
565
  }
@@ -87,42 +87,27 @@ private fun uninstallStatusBarSwizzle() {
87
87
  }
88
88
 
89
89
 
90
- private object StatusBarObserver {
90
+ private class StatusBarObserver {
91
91
  private var token: NSObjectProtocol? = null
92
- private var refCount = 0
93
-
94
- fun acquire() {
95
- refCount++
92
+ fun acquire(callback: (() -> Unit)?) {
96
93
  if (token != null) {
97
- log("acquire (already installed) refCount=$refCount")
98
94
  return
99
95
  }
100
- installStatusBarSwizzle()
101
96
  token = NSNotificationCenter.defaultCenter.addObserverForName(
102
97
  name = "statusBarSelected",
103
98
  `object` = null,
104
99
  queue = NSOperationQueue.mainQueue,
105
100
  ) { _: NSNotification? ->
106
101
  log("statusBar tap notification → ScrollToTopRegistry.trigger()")
107
- DynamicScreenRegistry.scrollToTop()
102
+ callback?.invoke()
108
103
  }
109
- log("StatusBarObserver installed refCount=$refCount")
104
+
110
105
  }
111
106
 
112
107
  fun release() {
113
- if (refCount <= 0) {
114
- log("release ignored (refCount already 0)")
115
- return
116
- }
117
- refCount--
118
- if (refCount > 0) {
119
- log("release refCount=$refCount")
120
- return
121
- }
108
+ if (token == null) return
122
109
  token?.let { NSNotificationCenter.defaultCenter.removeObserver(it) }
123
110
  token = null
124
- uninstallStatusBarSwizzle()
125
- log("StatusBarObserver uninstalled refCount=0")
126
111
  }
127
112
  }
128
113
 
@@ -144,75 +129,26 @@ private fun ts(): String {
144
129
  private fun log(msg: String) = println("${ts()} $TAG $msg")
145
130
 
146
131
 
147
-
148
132
  @Composable
149
- internal actual fun RegisterScrollToTop(navigator: Navigator, callback: (() -> Unit)?) {
133
+ internal actual fun RegisterScrollToTop(navigator: Navigation, callback: (() -> Unit)?) {
150
134
  val token = remember { Any() }
151
- DisposableEffect(navigator, callback) {
135
+
136
+ DisposableEffect(navigator, callback, token) {
152
137
  val acquired = callback != null
138
+ val statusBarObserver = StatusBarObserver()
153
139
  if (acquired) {
154
- StatusBarObserver.acquire()
155
- log("RegisterScrollToTop push owner=${navigator.tag()} token=${token.tag()}")
156
- // ScrollToTopRegistry.push(navigator, token, callback)
157
- } else {
158
- log("RegisterScrollToTop remove(callback=null) owner=${navigator.tag()} token=${token.tag()}")
159
- // ScrollToTopRegistry.remove(navigator, token)
140
+ statusBarObserver.acquire { callback?.invoke() }
160
141
  }
161
142
  onDispose {
162
- log("RegisterScrollToTop dispose owner=${navigator.tag()} token=${token.tag()}")
163
- // ScrollToTopRegistry.remove(navigator, token)
164
- if (acquired) StatusBarObserver.release()
143
+ if (acquired) statusBarObserver.release()
165
144
  }
166
145
  }
167
146
  }
168
147
 
169
- //object ScrollToTopRegistry {
170
- // private data class Entry(val token: Any, val callback: () -> Unit)
171
- //
172
- // private val stacksByOwner = mutableMapOf<Any, MutableList<Entry>>()
173
- // private val ownerStack = mutableListOf<Any>()
174
- //
175
- //
176
- // internal fun push(owner: Any, token: Any, callback: () -> Unit) {
177
- // val stack = stacksByOwner.getOrPut(owner) { mutableListOf() }
178
- // val idx = stack.indexOfFirst { it.token === token }
179
- // if (idx >= 0) {
180
- // stack[idx] = Entry(token, callback)
181
- // log("push(update) owner=${owner.tag()} token=${token.tag()} idx=$idx size=${stack.size} state=${dump()}")
182
- // } else {
183
- // stack.add(Entry(token, callback))
184
- // log("push(new) owner=${owner.tag()} token=${token.tag()} size=${stack.size} state=${dump()}")
185
- // }
186
- // }
187
- //
188
- // internal fun remove(owner: Any, token: Any) {
189
- // val stack = stacksByOwner[owner]
190
- // val before = stack?.size ?: 0
191
- // val removed = stack?.removeAll { it.token === token } ?: false
192
- // log("remove owner=${owner.tag()} token=${token.tag()} removed=$removed size=${before}->${stack?.size ?: 0} state=${dump()}")
193
- // }
194
- //
195
- // fun trigger() {
196
- // val owner = ownerStack.lastOrNull()
197
- // if (owner == null) {
198
- // if (stacksByOwner.size == 1) {
199
- // val only = stacksByOwner.entries.first()
200
- // val cb = only.value.lastOrNull()?.callback
201
- // log("trigger(fallback singleOwner) owner=${only.key.tag()} hasCallback=${cb != null}")
202
- // cb?.invoke()
203
- // } else {
204
- // log("trigger NO-OP (no owner, owners=${stacksByOwner.size}) state=${dump()}")
205
- // }
206
- // return
207
- // }
208
- // val cb = stacksByOwner[owner]?.lastOrNull()?.callback
209
- // log("trigger owner=${owner.tag()} hasCallback=${cb != null} state=${dump()}")
210
- // cb?.invoke()
211
- // }
212
- //
213
- // private fun dump(): String {
214
- // val owners = ownerStack.joinToString(",") { it.tag() }
215
- // val stacks = stacksByOwner.entries.joinToString(",") { (o, s) -> "${o.tag()}:${s.size}" }
216
- // return "ownerStack=[$owners] stacks={$stacks}"
217
- // }
218
- //}
148
+ internal actual fun onNavigatorEntered(navigator: Navigator) {
149
+ installStatusBarSwizzle()
150
+ }
151
+
152
+ internal actual fun onNavigatorExited(navigator: Navigator) {
153
+ uninstallStatusBarSwizzle()
154
+ }
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.160.1-scrolltotop.18
21
+ version=0.160.1-scrolltotop.19
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.160.1-scrolltotop.18-debug",
3
+ "version": "0.160.1-scrolltotop.19-debug",
4
4
  "private": false,
5
5
  "dependencies": {},
6
6
  "devDependencies": {},