@momo-kits/native-kits 0.164.1-beta.11-debug → 0.164.1-beta.12-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.11-debug"
43
+ version = "0.164.1-beta.12-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.11-debug'
3
+ spec.version = '0.164.1-beta.12-debug'
4
4
  spec.homepage = 'https://momo.vn'
5
5
  spec.source = { :http=> ''}
6
6
  spec.authors = ''
@@ -0,0 +1,125 @@
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.NavigationEvent
7
+ import androidx.navigationevent.NavigationEventDispatcher
8
+ import androidx.navigationevent.NavigationEventInput
9
+ import kotlinx.cinterop.ExperimentalForeignApi
10
+ import platform.Foundation.NSThread
11
+ import platform.darwin.dispatch_async
12
+ import platform.darwin.dispatch_get_main_queue
13
+
14
+ /**
15
+ * Kit end of the host's edge-pan recogniser.
16
+ *
17
+ * iOS keeps exactly one back-gesture recogniser and it belongs to the host app — the kit never
18
+ * installs its own, so the `.failed -> completed` conflict between two competing recognisers is
19
+ * unreachable by construction. The host replays its drag here; the events are fed into the
20
+ * `NavigationEventDispatcher` that [NavigationContainer] provides, which drives NavHost's
21
+ * `PredictiveBackHandler` and therefore `SeekableTransitionState.seekTo`. Parallax, scrim and edge
22
+ * shadow are children of that same transition, so they follow the finger for free.
23
+ *
24
+ * Only primitives cross the Swift boundary: `androidx.navigationevent` is a private transitive
25
+ * dependency and is absent from the generated framework header.
26
+ *
27
+ * Call every method from the main thread; anything else is re-posted to the main queue.
28
+ */
29
+ object HostBackGestureBridge {
30
+ private val attachedInputs = mutableListOf<HostNavigationEventInput>()
31
+ private var gestureInput: HostNavigationEventInput? = null
32
+
33
+ /**
34
+ * Whether the top-most mounted container has an enabled back handler, i.e. the authoritative
35
+ * answer to "will Compose consume this back?". `false` means no container is mounted, or the
36
+ * mounted one deliberately opts out (kill switch, overlay showing, custom back handler), and
37
+ * the host should fall back to its own navigation.
38
+ */
39
+ val hasEnabledHandlers: Boolean
40
+ get() = attachedInputs.lastOrNull()?.hasEnabledHandlers == true
41
+
42
+ /** Begins a drag. Pairs with exactly one [completed] or [cancelled]. */
43
+ fun started() = onMain {
44
+ val input = attachedInputs.lastOrNull() ?: return@onMain
45
+ gestureInput = input
46
+ input.backStarted()
47
+ }
48
+
49
+ /** Drag progress in `0f..1f` — the fraction of the screen width travelled. */
50
+ fun progressed(progress: Float) = onMain {
51
+ gestureInput?.backProgressed(progress.coerceIn(0f, 1f))
52
+ }
53
+
54
+ /**
55
+ * Released past the commit threshold: the screen pops.
56
+ *
57
+ * Falls back to the attached input when no drag was started, so a host that commits without
58
+ * ever seeking still gets a back. Dispatching a bare completion is the documented
59
+ * non-predictive path — the same shape as a back button press — and dropping it here is what
60
+ * silently stranded the mini-app root, where the host never seeks.
61
+ */
62
+ fun completed() = onMain {
63
+ (gestureInput ?: attachedInputs.lastOrNull())?.backCompleted()
64
+ gestureInput = null
65
+ }
66
+
67
+ /** Released below the commit threshold: the screen springs back. */
68
+ fun cancelled() = onMain {
69
+ gestureInput?.backCancelled()
70
+ gestureInput = null
71
+ }
72
+
73
+ internal fun attach(input: HostNavigationEventInput) {
74
+ attachedInputs += input
75
+ }
76
+
77
+ internal fun detach(input: HostNavigationEventInput) {
78
+ attachedInputs -= input
79
+ // A container unmounted mid-drag must not hand its pending terminal event to whichever
80
+ // container is on top now: NavigationEventInput fabricates a non-predictive start for a
81
+ // completed/cancelled it never saw begin, which would pop a screen out of nowhere.
82
+ if (gestureInput === input) gestureInput = null
83
+ }
84
+ }
85
+
86
+ /** Wires one [NavigationContainer]'s dispatcher to [HostBackGestureBridge] while it is composed. */
87
+ @Composable
88
+ internal fun AttachHostBackGesture(dispatcher: NavigationEventDispatcher) {
89
+ val input = remember(dispatcher) { HostNavigationEventInput() }
90
+
91
+ DisposableEffect(dispatcher, input) {
92
+ dispatcher.addInput(input)
93
+ HostBackGestureBridge.attach(input)
94
+ onDispose {
95
+ HostBackGestureBridge.detach(input)
96
+ dispatcher.removeInput(input)
97
+ }
98
+ }
99
+ }
100
+
101
+ internal class HostNavigationEventInput : NavigationEventInput() {
102
+ var hasEnabledHandlers: Boolean = false
103
+ private set
104
+
105
+ override fun onHasEnabledHandlersChanged(hasEnabledHandlers: Boolean) {
106
+ this.hasEnabledHandlers = hasEnabledHandlers
107
+ }
108
+
109
+ // Touch coordinates are not forwarded — the stack transition is seeked by progress alone, and
110
+ // leaving them out keeps the bridge's Swift-facing signatures primitive.
111
+ fun backStarted() = dispatchOnBackStarted(NavigationEvent(swipeEdge = NavigationEvent.EDGE_LEFT))
112
+
113
+ fun backProgressed(progress: Float) = dispatchOnBackProgressed(
114
+ NavigationEvent(swipeEdge = NavigationEvent.EDGE_LEFT, progress = progress)
115
+ )
116
+
117
+ fun backCancelled() = dispatchOnBackCancelled()
118
+
119
+ fun backCompleted() = dispatchOnBackCompleted()
120
+ }
121
+
122
+ @OptIn(ExperimentalForeignApi::class)
123
+ private fun onMain(block: () -> Unit) {
124
+ if (NSThread.isMainThread()) block() else dispatch_async(dispatch_get_main_queue()) { block() }
125
+ }
@@ -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.SwipeBackConfig
42
+ import vn.momo.kits.navigation.AttachHostBackGesture
43
43
 
44
44
  actual fun getPlatformName(): String = "iOS"
45
45
 
@@ -62,8 +62,9 @@ actual fun getStatusBarHeight(): Dp {
62
62
  return WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
63
63
  }
64
64
 
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`).
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`).
67
68
  @Suppress("DEPRECATION")
68
69
  @OptIn(ExperimentalComposeUiApi::class)
69
70
  @Composable
@@ -74,27 +75,21 @@ actual fun BackHandler(enabled: Boolean, onBack: () -> Unit) {
74
75
  @OptIn(InternalComposeUiApi::class)
75
76
  @Composable
76
77
  actual fun ProvideNavigationEventDispatcherOwner(content: @Composable () -> Unit) {
77
- val platformOwner = LocalCompatNavigationEventDispatcherOwner.current
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.
82
- content()
83
- return
84
- }
85
-
86
- // Bare UIView hosts have no scene dispatcher, and BackHandler/PredictiveBackHandler error() on
87
- // a null owner. This detached fallback is also what the kill switch falls back to.
88
- val fallbackOwner = remember {
78
+ // Detached on purpose: passing the scene's owner through would arm Compose's own
79
+ // UIKitBackGestureRecognizer alongside the host's, and UIKit settles that conflict by driving
80
+ // one to .failed, which UIKitNavigationEventInput turns into a fabricated completed back.
81
+ // Enabled on purpose: every NavigationEventDispatcher.dispatchOn* early-returns while
82
+ // isEnabled is false, which would mute the host-driven seek as well.
83
+ val navigationEventDispatcherOwner = remember {
89
84
  object : NavigationEventDispatcherOwner {
90
- override val navigationEventDispatcher = NavigationEventDispatcher().apply {
91
- isEnabled = false
92
- }
85
+ override val navigationEventDispatcher = NavigationEventDispatcher()
93
86
  }
94
87
  }
95
88
 
89
+ AttachHostBackGesture(navigationEventDispatcherOwner.navigationEventDispatcher)
90
+
96
91
  CompositionLocalProvider(
97
- LocalCompatNavigationEventDispatcherOwner provides fallbackOwner,
92
+ LocalCompatNavigationEventDispatcherOwner provides navigationEventDispatcherOwner,
98
93
  content = content
99
94
  )
100
95
  }
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.11
21
+ version=0.164.1-beta.12
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.11'
3
+ s.version = '0.164.1-beta.12'
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.11'
3
+ s.version = '0.164.1-beta.12'
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.11-debug",
3
+ "version": "0.164.1-beta.12-debug",
4
4
  "private": false,
5
5
  "dependeNavigationncies": {},
6
6
  "devDependencies": {},