@momo-kits/native-kits 0.164.1-beta.10-debug → 0.164.1-beta.11-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.164.1-beta.10-debug"
43
+ version = "0.164.1-beta.11-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.164.1-beta.10-debug'
3
+ spec.version = '0.164.1-beta.11-debug'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
@@ -163,8 +163,7 @@ fun NavigationContainer(
163
163
  id = route.id,
164
164
  name = screen.name,
165
165
  content = screen.content,
166
- navigationOptions = screen.options,
167
- isDialogRoute = true
166
+ navigationOptions = screen.options
168
167
  )
169
168
  }
170
169
  }
@@ -4,15 +4,11 @@ import androidx.compose.foundation.layout.Box
4
4
  import androidx.compose.foundation.layout.fillMaxSize
5
5
  import androidx.compose.runtime.Composable
6
6
  import androidx.compose.runtime.MutableState
7
- import androidx.compose.runtime.collectAsState
8
- import androidx.compose.runtime.getValue
9
7
  import androidx.compose.runtime.mutableStateOf
10
8
  import androidx.compose.runtime.staticCompositionLocalOf
11
9
  import androidx.compose.ui.Alignment
12
10
  import androidx.compose.ui.Modifier
13
- import androidx.navigation.NavBackStackEntry
14
11
  import androidx.navigation.NavController
15
- import androidx.navigation.NavGraph
16
12
  import androidx.navigation.toRoute
17
13
  import kotlinx.coroutines.CoroutineScope
18
14
  import kotlinx.coroutines.Dispatchers
@@ -83,21 +79,9 @@ class Navigator(
83
79
  }
84
80
 
85
81
  // The swipe-back opt-out predicate in StackScreen needs this: the root screen must keep custom
86
- // back handling so it still reaches maxApi.dismiss. It has to read currentBackStack rather than
87
- // previousBackStackEntry — the latter walks a plain ArrayDeque, so a composable reading it never
88
- // recomposes and the predicate would freeze at the value it had on first composition.
89
- @Composable
90
- internal fun rememberCanPop(): Boolean {
91
- val backStack by navController.currentBackStack.collectAsState()
92
- return backStack.hasPoppableEntry()
93
- }
94
-
95
- /**
96
- * Non-composable twin of [rememberCanPop] for the iOS host: its own back check is mount-based
97
- * and so answers `true` even on the mini-app root, where a swipe must dismiss rather than pop.
98
- * Reads the StateFlow value directly — correct here precisely because nothing recomposes on it.
99
- */
100
- fun canPop(): Boolean = navController.currentBackStack.value.hasPoppableEntry()
82
+ // back handling so it still reaches maxApi.dismiss.
83
+ // Public: the iOS host asks this to decide whether the kit owns the swipe on this screen.
84
+ fun canPop(): Boolean = navController.previousBackStackEntry != null
101
85
 
102
86
  fun pop(count: Int = 1, callBack: (() -> Unit)? = null) {
103
87
  scope.launch {
@@ -196,10 +180,6 @@ class Navigator(
196
180
  }
197
181
  }
198
182
 
199
- // The NavGraph itself sits in the back stack as an entry; only real destinations can be popped.
200
- private fun List<NavBackStackEntry>.hasPoppableEntry(): Boolean =
201
- count { it.destination !is NavGraph } > 1
202
-
203
183
  val LocalNavigator = staticCompositionLocalOf<Navigator> {
204
184
  error("No NavigationStack provided")
205
185
  }
@@ -93,7 +93,6 @@ internal fun StackScreen(
93
93
  id: Int = -1,
94
94
  name: String = "",
95
95
  bottomTabIndex: Int = -1,
96
- isDialogRoute: Boolean = false,
97
96
  onBackHandler: (() -> Unit)? = null,
98
97
  ) {
99
98
  val navigator = LocalNavigator.current
@@ -170,29 +169,17 @@ internal fun StackScreen(
170
169
  RegisterScrollToTop(navigation.options.scrollData.scrollToTopCallback)
171
170
  }
172
171
 
173
- // Hoisted out of the `||` chain below: short-circuiting would drop the back-stack subscription
174
- // whenever an earlier term is true.
175
- val canPop = navigator.rememberCanPop()
176
-
177
172
  // Port of RN's `hasCustomBackHandling`: this handler is registered inside the NavHost
178
173
  // destination, so while it is enabled it beats NavHost's own predictive-back handler and
179
174
  // swallows the swipe. Enable it only when a plain pop is the wrong answer.
180
- //
181
- // A presented route is one of those cases: NavHost arms predictive back on back-stack size
182
- // alone, so a horizontal edge drag over a dialog would seek its *vertical* slide.
183
175
  val hasCustomBackHandling = !SwipeBackConfig.enabled ||
184
176
  isBottomTabChild ||
185
- isDialogRoute ||
186
177
  options.onBackHandler != null ||
187
178
  options.headerBackProps.preventBack != null ||
188
179
  OverplayComponentRegistry.isOverplayShowing() ||
189
- !canPop
190
-
191
- // BottomTab supplies a tab-aware handler; falling through to onBackSafe() there would dismiss
192
- // the whole mini-app instead of returning to tab 0.
193
- val onBack = onBackHandler ?: { navigator.onBackSafe() }
180
+ !navigator.canPop()
194
181
 
195
- BackHandler(hasCustomBackHandling, onBack)
182
+ BackHandler(hasCustomBackHandling) { navigator.onBackSafe() }
196
183
 
197
184
  CompositionLocalProvider(
198
185
  StackScreenScrollableState provides options.scrollData.scrollState,
@@ -18,9 +18,7 @@ import androidx.compose.runtime.Stable
18
18
  import androidx.compose.runtime.derivedStateOf
19
19
  import androidx.compose.runtime.getValue
20
20
  import androidx.compose.runtime.mutableStateMapOf
21
- import androidx.compose.runtime.mutableStateOf
22
21
  import androidx.compose.runtime.remember
23
- import androidx.compose.runtime.setValue
24
22
  import androidx.compose.runtime.staticCompositionLocalOf
25
23
  import androidx.compose.ui.Modifier
26
24
  import androidx.compose.ui.draw.drawBehind
@@ -45,15 +43,12 @@ private const val EDGE_SHADOW_MAX_ALPHA = 0.3f
45
43
  private val EDGE_SHADOW_WIDTH = 8.dp
46
44
 
47
45
  /**
48
- * Kill switch for the swipe-back gesture. Set to `false` to make kit back handling always win,
49
- * which disables the predictive-back drag on both platforms — the gesture then pops without
50
- * following the finger, and fires no haptic. Screen transitions stay on the parallax curve either
51
- * way.
46
+ * Kill switch for the swipe-back gesture. Set to `false` to restore pre-DS-760 behaviour (kit
47
+ * back handling always wins, iOS edge pan inert) without a kit release. Screen transitions stay
48
+ * on the new parallax curve either way.
52
49
  */
53
50
  object SwipeBackConfig {
54
- // Snapshot-backed so a host toggle takes effect on the next frame instead of waiting for an
55
- // unrelated recomposition of StackScreen.
56
- var enabled: Boolean by mutableStateOf(true)
51
+ var enabled: Boolean = true
57
52
  }
58
53
 
59
54
  // LinearEasing is mandatory, not a style choice: SeekableTransitionState.seekTo() maps the drag
@@ -111,10 +106,8 @@ internal fun AnimatedVisibilityScope.StackTransitionDecor(
111
106
  content: @Composable () -> Unit,
112
107
  ) {
113
108
  val coordinator = LocalStackTransition.current
114
- // attach() writes state that derivedStateOf { topStackId } reads in the same pass, so it has to
115
- // stay out of the composable body.
116
- DisposableEffect(coordinator, id, isStackRoute) {
117
- coordinator.attach(id, isStackRoute)
109
+ coordinator.attach(id, isStackRoute)
110
+ DisposableEffect(coordinator, id) {
118
111
  onDispose { coordinator.detach(id) }
119
112
  }
120
113
 
@@ -17,9 +17,7 @@ private const val SWIPE_BACK_HAPTIC_THRESHOLD_DP = 50f
17
17
  */
18
18
  @Composable
19
19
  internal fun SwipeBackHaptic(maxApi: IMaxApi?) {
20
- // With the kill switch off the kit's own BackHandler swallows the gesture, so the card never
21
- // moves — a tick there would be a phantom.
22
- if (maxApi == null || !SwipeBackConfig.enabled) return
20
+ if (maxApi == null) return
23
21
 
24
22
  val progress = rememberBackGestureProgress()
25
23
  val screenWidth = getScreenDimensions().width.toFloat()
@@ -39,7 +39,7 @@ import platform.Foundation.NSBundle
39
39
  import platform.UIKit.UIColor
40
40
  import platform.UIKit.UIDevice
41
41
  import platform.UIKit.UIScreen
42
- import vn.momo.kits.navigation.ObserveBackGestureOwnership
42
+ import vn.momo.kits.navigation.SwipeBackConfig
43
43
 
44
44
  actual fun getPlatformName(): String = "iOS"
45
45
 
@@ -62,9 +62,8 @@ actual fun getStatusBarHeight(): Dp {
62
62
  return WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
63
63
  }
64
64
 
65
- // An enabled kit BackHandler is registered inside the NavHost destination, so it out-ranks
66
- // NavHost's own predictive-back handler and swallows the gesture — that is how a screen opts out
67
- // of swipe-back (the port of RN's `hasCustomBackHandling`).
65
+ // An enabled kit BackHandler sits innermost and therefore consumes the edge-pan gesture — that is
66
+ // how a screen opts out of swipe-back (the port of RN's `hasCustomBackHandling`).
68
67
  @Suppress("DEPRECATION")
69
68
  @OptIn(ExperimentalComposeUiApi::class)
70
69
  @Composable
@@ -76,18 +75,16 @@ actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {
76
75
  @Composable
77
76
  actual fun ProvideNavigationEventDispatcherOwner(content: @Composable () -> Unit) {
78
77
  val platformOwner = LocalCompatNavigationEventDispatcherOwner.current
79
- if (platformOwner != null) {
80
- // UIKitNavigationEventInput is registered on the scene's dispatcher, and that dispatcher
81
- // having enabled handlers is what arms Compose's UIKitBackGestureRecognizer. Providing a
82
- // fresh NavigationEventDispatcher() here would shadow it with an input-less one that never
83
- // sees a single edge-pan event — no recogniser, and no finger-following seek.
84
- ObserveBackGestureOwnership(platformOwner.navigationEventDispatcher)
78
+ if (platformOwner != null && SwipeBackConfig.enabled) {
79
+ // UIKitNavigationEventInput is registered on the scene's dispatcher. Providing a fresh
80
+ // NavigationEventDispatcher() here would shadow it with an input-less one that never sees
81
+ // a single edge-pan event, enabled or not — so pass the platform owner straight through.
85
82
  content()
86
83
  return
87
84
  }
88
85
 
89
86
  // Bare UIView hosts have no scene dispatcher, and BackHandler/PredictiveBackHandler error() on
90
- // a null owner. Disabled: nothing can feed it, so it exists only to keep those from throwing.
87
+ // a null owner. This detached fallback is also what the kill switch falls back to.
91
88
  val fallbackOwner = remember {
92
89
  object : NavigationEventDispatcherOwner {
93
90
  override val navigationEventDispatcher = NavigationEventDispatcher().apply {
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.164.1-beta.10
21
+ version=0.164.1-beta.11
22
22
 
23
23
  repo=GitLab
24
24
  url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKits'
3
- s.version = '0.164.1-beta.10'
3
+ s.version = '0.164.1-beta.11'
4
4
  s.summary = 'MoMoUIKits for iOS'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'MoMoUIKitsDemo'
3
- s.version = '0.164.1-beta.10'
3
+ s.version = '0.164.1-beta.11'
4
4
  s.summary = 'Demo browser for MoMoUIKits SwiftUI components'
5
5
  s.homepage = 'https://momo.vn'
6
6
  s.license = { :type => 'MIT' }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momo-kits/native-kits",
3
- "version": "0.164.1-beta.10-debug",
3
+ "version": "0.164.1-beta.11-debug",
4
4
  "private": false,
5
5
  "dependeNavigationncies": {},
6
6
  "devDependencies": {},
@@ -1,65 +0,0 @@
1
- package vn.momo.kits.navigation
2
-
3
- import androidx.compose.runtime.Composable
4
- import androidx.compose.runtime.DisposableEffect
5
- import androidx.compose.runtime.remember
6
- import androidx.navigationevent.NavigationEventDispatcher
7
- import androidx.navigationevent.NavigationEventInput
8
-
9
- /**
10
- * Read-only answer to "will Compose consume this back?", for the iOS host.
11
- *
12
- * Compose owns the edge-pan gesture through its own `UIKitBackGestureRecognizer`; the host reads
13
- * this to decide whether to stand down or keep driving its own recogniser, instead of guessing.
14
- * Nothing here dispatches — [ObserveBackGestureOwnership] only listens.
15
- *
16
- * Pair it with [Navigator.canPop]: the mini-app root reports an enabled handler too (it swallows
17
- * back to reach `maxApi.dismiss`), yet there the host still owns the gesture.
18
- */
19
- object ComposeBackGesture {
20
- private val observers = mutableListOf<BackHandlerObserver>()
21
-
22
- /** State of the top-most mounted container; `false` when none is mounted. */
23
- val hasEnabledHandlers: Boolean
24
- get() = observers.lastOrNull()?.hasEnabledHandlers == true
25
-
26
- internal fun attach(observer: BackHandlerObserver) {
27
- observers += observer
28
- }
29
-
30
- internal fun detach(observer: BackHandlerObserver) {
31
- observers -= observer
32
- }
33
- }
34
-
35
- /**
36
- * Publishes one [NavigationContainer]'s back-handling state to [ComposeBackGesture] while composed.
37
- *
38
- * Only called for a container whose dispatcher is the scene's — a bare-`UIView` host gets the
39
- * detached fallback dispatcher, which can never receive an edge pan, so reporting its handlers
40
- * would tell the host to stand down in favour of a gesture that does not exist.
41
- */
42
- @Composable
43
- internal fun ObserveBackGestureOwnership(dispatcher: NavigationEventDispatcher) {
44
- val observer = remember(dispatcher) { BackHandlerObserver() }
45
-
46
- DisposableEffect(dispatcher, observer) {
47
- dispatcher.addInput(observer)
48
- ComposeBackGesture.attach(observer)
49
- onDispose {
50
- ComposeBackGesture.detach(observer)
51
- dispatcher.removeInput(observer)
52
- }
53
- }
54
- }
55
-
56
- // A NavigationEventInput that never dispatches: adding an input is the only way to observe the
57
- // dispatcher's enabled-handler state without registering a handler, which would consume the gesture.
58
- internal class BackHandlerObserver : NavigationEventInput() {
59
- var hasEnabledHandlers: Boolean = false
60
- private set
61
-
62
- override fun onHasEnabledHandlersChanged(hasEnabledHandlers: Boolean) {
63
- this.hasEnabledHandlers = hasEnabledHandlers
64
- }
65
- }