@momo-kits/native-kits 0.160.1-scrolltotop.22-debug → 0.160.1-scrolltotop.23-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.
- package/compose/build.gradle.kts +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +1 -4
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -3
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +16 -130
- package/gradle.properties +1 -1
- package/ios/StatusBarTap/StatusBarTap.swift +42 -0
- package/package.json +1 -1
package/compose/build.gradle.kts
CHANGED
|
@@ -3,7 +3,4 @@ package vn.momo.kits.navigation
|
|
|
3
3
|
import androidx.compose.runtime.Composable
|
|
4
4
|
|
|
5
5
|
@Composable
|
|
6
|
-
internal actual fun RegisterScrollToTop(callback: (() -> Unit)?) = Unit
|
|
7
|
-
|
|
8
|
-
internal actual fun onNavigatorEntered(navigator: Navigator) = Unit
|
|
9
|
-
internal actual fun onNavigatorExited(navigator: Navigator) = Unit
|
|
6
|
+
internal actual fun RegisterScrollToTop(callback: (() -> Unit)?) = Unit
|
|
@@ -2,146 +2,32 @@ package vn.momo.kits.navigation
|
|
|
2
2
|
|
|
3
3
|
import androidx.compose.runtime.Composable
|
|
4
4
|
import androidx.compose.runtime.DisposableEffect
|
|
5
|
-
import kotlinx.cinterop.*
|
|
6
5
|
import platform.Foundation.NSNotification
|
|
7
6
|
import platform.Foundation.NSNotificationCenter
|
|
8
7
|
import platform.Foundation.NSOperationQueue
|
|
9
8
|
import platform.darwin.NSObjectProtocol
|
|
10
|
-
import platform.objc.*
|
|
11
|
-
import kotlin.time.Clock
|
|
12
|
-
import kotlin.time.ExperimentalTime
|
|
13
|
-
|
|
14
|
-
@OptIn(ExperimentalForeignApi::class)
|
|
15
|
-
private val replacementHandleTap = staticCFunction<COpaquePointer, COpaquePointer, COpaquePointer?, Unit> { _, _, _ ->
|
|
16
|
-
NSNotificationCenter.defaultCenter.postNotificationName("statusBarSelected", null)
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
|
|
20
|
-
private object StatusBarSwizzleManager {
|
|
21
|
-
var originalHandleTapImp: CPointer<CFunction<() -> Unit>>? = null
|
|
22
|
-
var swizzleInstalled = false
|
|
23
|
-
var observerCount = 0
|
|
24
|
-
|
|
25
|
-
fun install() {
|
|
26
|
-
try {
|
|
27
|
-
if (swizzleInstalled) return
|
|
28
|
-
// objc_getClass returns Any? in the binding, but the underlying value is
|
|
29
|
-
// the ObjC Class metaobject — safe to cast to ObjCClass for the other
|
|
30
|
-
// runtime calls that require it.
|
|
31
|
-
val cls = objc_getClass("UIStatusBarManager") as? ObjCClass ?: run {
|
|
32
|
-
log("swizzle skipped: UIStatusBarManager class not found")
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
val selector = sel_registerName("handleTapAction:") ?: run {
|
|
36
|
-
log("swizzle skipped: handleTapAction: selector unavailable")
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
val method = class_getInstanceMethod(cls, selector) ?: run {
|
|
40
|
-
log("swizzle skipped: handleTapAction: method not found")
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
// Capture original IMP so we can restore it on uninstall.
|
|
44
|
-
originalHandleTapImp = method_getImplementation(method)
|
|
45
|
-
val typeEncoding = method_getTypeEncoding(method)?.toKString()
|
|
46
|
-
class_replaceMethod(cls, selector, replacementHandleTap.reinterpret(), typeEncoding)
|
|
47
|
-
swizzleInstalled = true
|
|
48
|
-
log("UIStatusBarManager handleTapAction: replaced (originalImp captured)")
|
|
49
|
-
} catch (e: Throwable) {
|
|
50
|
-
log("install error: ${e.message}")
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
fun uninstall() {
|
|
55
|
-
try {
|
|
56
|
-
if (!swizzleInstalled) return
|
|
57
|
-
val original = originalHandleTapImp ?: run {
|
|
58
|
-
log("uninstall skipped: no original IMP captured")
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
val cls = objc_getClass("UIStatusBarManager") as? ObjCClass ?: return
|
|
62
|
-
val selector = sel_registerName("handleTapAction:") ?: return
|
|
63
|
-
val method = class_getInstanceMethod(cls, selector) ?: return
|
|
64
|
-
method_setImplementation(method, original)
|
|
65
|
-
originalHandleTapImp = null
|
|
66
|
-
swizzleInstalled = false
|
|
67
|
-
log("UIStatusBarManager handleTapAction: original IMP restored")
|
|
68
|
-
} catch (e: Throwable) {
|
|
69
|
-
log("uninstall error: ${e.message}")
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
private class StatusBarObserver {
|
|
76
|
-
private var token: NSObjectProtocol? = null
|
|
77
|
-
fun acquire(callback: (() -> Unit)?) {
|
|
78
|
-
if (token != null) {
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
if (StatusBarSwizzleManager.observerCount == 0) {
|
|
82
|
-
StatusBarSwizzleManager.install()
|
|
83
|
-
}
|
|
84
|
-
token = NSNotificationCenter.defaultCenter.addObserverForName(
|
|
85
|
-
name = "statusBarSelected",
|
|
86
|
-
`object` = null,
|
|
87
|
-
queue = NSOperationQueue.mainQueue,
|
|
88
|
-
) { _: NSNotification? ->
|
|
89
|
-
log("statusBar tap notification → ScrollToTopRegistry.trigger()")
|
|
90
|
-
callback?.invoke()
|
|
91
|
-
}
|
|
92
|
-
StatusBarSwizzleManager.observerCount++
|
|
93
|
-
log("observerCount acquire ${StatusBarSwizzleManager.observerCount}")
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
fun release() {
|
|
98
|
-
if (token == null) return
|
|
99
|
-
token?.let { NSNotificationCenter.defaultCenter.removeObserver(it) }
|
|
100
|
-
token = null
|
|
101
|
-
StatusBarSwizzleManager.observerCount--
|
|
102
|
-
if (StatusBarSwizzleManager.observerCount == 0) {
|
|
103
|
-
StatusBarSwizzleManager.uninstall()
|
|
104
|
-
}
|
|
105
|
-
log("observerCount release ${StatusBarSwizzleManager.observerCount}")
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
private const val TAG = "[ScrollToTop]"
|
|
110
|
-
|
|
111
|
-
private fun Any.tag(): String = "${this::class.simpleName ?: "Any"}@${hashCode().toString(16)}"
|
|
112
|
-
|
|
113
|
-
@OptIn(ExperimentalTime::class)
|
|
114
|
-
private fun ts(): String {
|
|
115
|
-
val ms = Clock.System.now().toEpochMilliseconds()
|
|
116
|
-
val s = ms / 1000
|
|
117
|
-
val hh = ((s / 3600) % 24).toString().padStart(2, '0')
|
|
118
|
-
val mm = ((s / 60) % 60).toString().padStart(2, '0')
|
|
119
|
-
val ss = (s % 60).toString().padStart(2, '0')
|
|
120
|
-
val mmm = (ms % 1000).toString().padStart(3, '0')
|
|
121
|
-
return "$hh:$mm:$ss.$mmm"
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private fun log(msg: String) = println("${ts()} $TAG $msg")
|
|
125
9
|
|
|
10
|
+
private const val STATUS_BAR_TAPPED_NOTIFICATION = "statusBarSelected"
|
|
126
11
|
|
|
127
12
|
@Composable
|
|
128
13
|
internal actual fun RegisterScrollToTop(callback: (() -> Unit)?) {
|
|
14
|
+
val maxApi = LocalMaxApi.current
|
|
129
15
|
DisposableEffect(callback) {
|
|
130
|
-
val
|
|
131
|
-
val
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
16
|
+
val cb = callback
|
|
17
|
+
val token: NSObjectProtocol? = if (cb != null) {
|
|
18
|
+
NSNotificationCenter.defaultCenter.addObserverForName(
|
|
19
|
+
name = STATUS_BAR_TAPPED_NOTIFICATION,
|
|
20
|
+
`object` = null,
|
|
21
|
+
queue = NSOperationQueue.mainQueue,
|
|
22
|
+
) { _: NSNotification? ->
|
|
23
|
+
runCatching { cb() }.onFailure {
|
|
24
|
+
maxApi?.logFile("[ScrollToTop] callback threw: ${it.message}") {}
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} else null
|
|
135
29
|
onDispose {
|
|
136
|
-
|
|
30
|
+
token?.let { NSNotificationCenter.defaultCenter.removeObserver(it) }
|
|
137
31
|
}
|
|
138
32
|
}
|
|
139
33
|
}
|
|
140
|
-
|
|
141
|
-
internal actual fun onNavigatorEntered(navigator: Navigator) {
|
|
142
|
-
StatusBarSwizzleManager.install()
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
internal actual fun onNavigatorExited(navigator: Navigator) {
|
|
146
|
-
StatusBarSwizzleManager.uninstall()
|
|
147
|
-
}
|
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.
|
|
21
|
+
version=0.160.1-scrolltotop.23
|
|
22
22
|
|
|
23
23
|
repo=GitLab
|
|
24
24
|
url=https://gitlab.mservice.com.vn/api/v4/projects/5400/packages/maven
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
import ObjectiveC.runtime
|
|
3
|
+
|
|
4
|
+
extension UIStatusBarManager {
|
|
5
|
+
public static var statusBarTappedNotification: Notification.Name = {
|
|
6
|
+
let name = Notification.Name("statusBarSelected")
|
|
7
|
+
guard let original = class_getInstanceMethod(UIStatusBarManager.self, Selector(("handleTapAction:"))) else {
|
|
8
|
+
NSLog("[MoMoStatusBarTap] original handleTapAction: not found — skip")
|
|
9
|
+
return name
|
|
10
|
+
}
|
|
11
|
+
// Verify the method takes (self, _cmd, id) and returns void.
|
|
12
|
+
// Encoding may include offsets (e.g. "v32@0:8@16") so strip digits before comparing.
|
|
13
|
+
if let encPtr = method_getTypeEncoding(original) {
|
|
14
|
+
let enc = String(cString: encPtr).filter { !$0.isNumber }
|
|
15
|
+
guard enc == "v@:@" else {
|
|
16
|
+
NSLog("[MoMoStatusBarTap] unexpected encoding \(enc) — skip")
|
|
17
|
+
return name
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
guard let swizzled = class_getInstanceMethod(UIStatusBarManager.self, #selector(_mm_handleTapAction)) else {
|
|
21
|
+
NSLog("[MoMoStatusBarTap] replacement _mm_handleTapAction not found — skip")
|
|
22
|
+
return name
|
|
23
|
+
}
|
|
24
|
+
method_exchangeImplementations(original, swizzled)
|
|
25
|
+
NSLog("[MoMoStatusBarTap] swizzle installed")
|
|
26
|
+
return name
|
|
27
|
+
}()
|
|
28
|
+
|
|
29
|
+
@objc dynamic private func _mm_handleTapAction(_ action: Any?) {
|
|
30
|
+
_mm_handleTapAction(action)
|
|
31
|
+
NotificationCenter.default.post(name: UIStatusBarManager.statusBarTappedNotification, object: nil)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@objc(MoMoStatusBarTap)
|
|
36
|
+
public final class MoMoStatusBarTap: NSObject {
|
|
37
|
+
@objc public static let notificationName: String = "statusBarSelected"
|
|
38
|
+
|
|
39
|
+
@objc public static func install() {
|
|
40
|
+
_ = UIStatusBarManager.statusBarTappedNotification
|
|
41
|
+
}
|
|
42
|
+
}
|