@momo-kits/native-kits 0.160.1-scrolltotop.15-debug → 0.160.1-scrolltotop.17-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/compose.podspec +1 -1
- package/compose/src/androidMain/kotlin/vn/momo/kits/navigation/ScrollToTop.android.kt +0 -3
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/Navigator.kt +4 -0
- package/compose/src/commonMain/kotlin/vn/momo/kits/navigation/ScrollToTop.kt +0 -12
- package/compose/src/iosMain/kotlin/vn/momo/kits/navigation/ScrollToTop.ios.kt +177 -83
- package/gradle.properties +1 -1
- package/package.json +1 -1
- package/ios/Extensions/ScrollToTop.swift +0 -94
package/compose/build.gradle.kts
CHANGED
package/compose/compose.podspec
CHANGED
|
@@ -6,6 +6,3 @@ import androidx.compose.runtime.Composable
|
|
|
6
6
|
internal actual fun RegisterScrollToTop(navigator: Navigator, callback: (() -> Unit)?) {
|
|
7
7
|
// No-op on Android: status bar tap doesn't trigger scroll-to-top on this platform.
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
actual fun onNavigatorEntered(navigator: Navigator) = Unit
|
|
11
|
-
actual fun onNavigatorExited(navigator: Navigator) = Unit
|
|
@@ -216,6 +216,10 @@ object DynamicScreenRegistry {
|
|
|
216
216
|
fun setOptions(id: Int, options: NavigationOptions){
|
|
217
217
|
screens[id]?.options = options
|
|
218
218
|
}
|
|
219
|
+
|
|
220
|
+
fun scrollToTop() {
|
|
221
|
+
getLatestScreen()?.options?.scrollData?.scrollToTopCallback?.invoke()
|
|
222
|
+
}
|
|
219
223
|
}
|
|
220
224
|
|
|
221
225
|
|
|
@@ -4,15 +4,3 @@ import androidx.compose.runtime.Composable
|
|
|
4
4
|
|
|
5
5
|
@Composable
|
|
6
6
|
internal expect fun RegisterScrollToTop(navigator: Navigator, callback: (() -> Unit)?)
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Call when a Navigator becomes the active (visually topmost) one — e.g. from
|
|
10
|
-
* `ComposeNavigatorManager.push`. Android: no-op.
|
|
11
|
-
*/
|
|
12
|
-
expect fun onNavigatorEntered(navigator: Navigator)
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Call when a Navigator is no longer active — e.g. from
|
|
16
|
-
* `ComposeNavigatorManager.removeLast`. Android: no-op.
|
|
17
|
-
*/
|
|
18
|
-
expect fun onNavigatorExited(navigator: Navigator)
|
|
@@ -3,9 +3,129 @@ package vn.momo.kits.navigation
|
|
|
3
3
|
import androidx.compose.runtime.Composable
|
|
4
4
|
import androidx.compose.runtime.DisposableEffect
|
|
5
5
|
import androidx.compose.runtime.remember
|
|
6
|
+
import kotlinx.cinterop.BetaInteropApi
|
|
7
|
+
import kotlinx.cinterop.CFunction
|
|
8
|
+
import kotlinx.cinterop.COpaquePointer
|
|
9
|
+
import kotlinx.cinterop.CPointer
|
|
10
|
+
import kotlinx.cinterop.ExperimentalForeignApi
|
|
11
|
+
import kotlinx.cinterop.ObjCClass
|
|
12
|
+
import kotlinx.cinterop.reinterpret
|
|
13
|
+
import kotlinx.cinterop.staticCFunction
|
|
14
|
+
import kotlinx.cinterop.toKString
|
|
15
|
+
import platform.Foundation.NSNotification
|
|
16
|
+
import platform.Foundation.NSNotificationCenter
|
|
17
|
+
import platform.Foundation.NSOperationQueue
|
|
18
|
+
import platform.darwin.NSObjectProtocol
|
|
19
|
+
import platform.objc.class_getInstanceMethod
|
|
20
|
+
import platform.objc.class_replaceMethod
|
|
21
|
+
import platform.objc.method_getImplementation
|
|
22
|
+
import platform.objc.method_getTypeEncoding
|
|
23
|
+
import platform.objc.method_setImplementation
|
|
24
|
+
import platform.objc.objc_getClass
|
|
25
|
+
import platform.objc.sel_registerName
|
|
6
26
|
import kotlin.time.Clock
|
|
7
27
|
import kotlin.time.ExperimentalTime
|
|
8
28
|
|
|
29
|
+
@OptIn(ExperimentalForeignApi::class)
|
|
30
|
+
private val replacementHandleTap = staticCFunction<COpaquePointer, COpaquePointer, COpaquePointer?, Unit> { _, _, _ ->
|
|
31
|
+
NSNotificationCenter.defaultCenter.postNotificationName("statusBarSelected", null)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@OptIn(ExperimentalForeignApi::class)
|
|
35
|
+
private var originalHandleTapImp: CPointer<CFunction<() -> Unit>>? = null
|
|
36
|
+
|
|
37
|
+
private var swizzleInstalled = false
|
|
38
|
+
|
|
39
|
+
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
|
|
40
|
+
private fun installStatusBarSwizzle() {
|
|
41
|
+
try {
|
|
42
|
+
if (swizzleInstalled) return
|
|
43
|
+
// objc_getClass returns Any? in the binding, but the underlying value is
|
|
44
|
+
// the ObjC Class metaobject — safe to cast to ObjCClass for the other
|
|
45
|
+
// runtime calls that require it.
|
|
46
|
+
val cls = objc_getClass("UIStatusBarManager") as? ObjCClass ?: run {
|
|
47
|
+
log("swizzle skipped: UIStatusBarManager class not found")
|
|
48
|
+
return
|
|
49
|
+
}
|
|
50
|
+
val selector = sel_registerName("handleTapAction:") ?: run {
|
|
51
|
+
log("swizzle skipped: handleTapAction: selector unavailable")
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
val method = class_getInstanceMethod(cls, selector) ?: run {
|
|
55
|
+
log("swizzle skipped: handleTapAction: method not found")
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
// Capture original IMP so we can restore it on uninstall.
|
|
59
|
+
originalHandleTapImp = method_getImplementation(method)
|
|
60
|
+
val typeEncoding = method_getTypeEncoding(method)?.toKString()
|
|
61
|
+
class_replaceMethod(cls, selector, replacementHandleTap.reinterpret(), typeEncoding)
|
|
62
|
+
swizzleInstalled = true
|
|
63
|
+
log("UIStatusBarManager handleTapAction: replaced (originalImp captured)")
|
|
64
|
+
} catch (e: Throwable) {
|
|
65
|
+
log("install error: ${e.message}")
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class)
|
|
70
|
+
private fun uninstallStatusBarSwizzle() {
|
|
71
|
+
try {
|
|
72
|
+
if (!swizzleInstalled) return
|
|
73
|
+
val original = originalHandleTapImp ?: run {
|
|
74
|
+
log("uninstall skipped: no original IMP captured")
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
val cls = objc_getClass("UIStatusBarManager") as? ObjCClass ?: return
|
|
78
|
+
val selector = sel_registerName("handleTapAction:") ?: return
|
|
79
|
+
val method = class_getInstanceMethod(cls, selector) ?: return
|
|
80
|
+
method_setImplementation(method, original)
|
|
81
|
+
originalHandleTapImp = null
|
|
82
|
+
swizzleInstalled = false
|
|
83
|
+
log("UIStatusBarManager handleTapAction: original IMP restored")
|
|
84
|
+
} catch (e: Throwable) {
|
|
85
|
+
log("uninstall error: ${e.message}")
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
private object StatusBarObserver {
|
|
91
|
+
private var token: NSObjectProtocol? = null
|
|
92
|
+
private var refCount = 0
|
|
93
|
+
|
|
94
|
+
fun acquire() {
|
|
95
|
+
refCount++
|
|
96
|
+
if (token != null) {
|
|
97
|
+
log("acquire (already installed) refCount=$refCount")
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
installStatusBarSwizzle()
|
|
101
|
+
token = NSNotificationCenter.defaultCenter.addObserverForName(
|
|
102
|
+
name = "statusBarSelected",
|
|
103
|
+
`object` = null,
|
|
104
|
+
queue = NSOperationQueue.mainQueue,
|
|
105
|
+
) { _: NSNotification? ->
|
|
106
|
+
log("statusBar tap notification → ScrollToTopRegistry.trigger()")
|
|
107
|
+
DynamicScreenRegistry.scrollToTop()
|
|
108
|
+
}
|
|
109
|
+
log("StatusBarObserver installed refCount=$refCount")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
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
|
+
}
|
|
122
|
+
token?.let { NSNotificationCenter.defaultCenter.removeObserver(it) }
|
|
123
|
+
token = null
|
|
124
|
+
uninstallStatusBarSwizzle()
|
|
125
|
+
log("StatusBarObserver uninstalled refCount=0")
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
9
129
|
private const val TAG = "[ScrollToTop]"
|
|
10
130
|
|
|
11
131
|
private fun Any.tag(): String = "${this::class.simpleName ?: "Any"}@${hashCode().toString(16)}"
|
|
@@ -23,102 +143,76 @@ private fun ts(): String {
|
|
|
23
143
|
|
|
24
144
|
private fun log(msg: String) = println("${ts()} $TAG $msg")
|
|
25
145
|
|
|
26
|
-
actual fun onNavigatorEntered(navigator: Navigator) {
|
|
27
|
-
log("onNavigatorEntered owner=${navigator.tag()}")
|
|
28
|
-
ScrollToTopRegistry.pushOwner(navigator)
|
|
29
|
-
}
|
|
30
146
|
|
|
31
|
-
actual fun onNavigatorExited(navigator: Navigator) {
|
|
32
|
-
log("onNavigatorExited owner=${navigator.tag()}")
|
|
33
|
-
ScrollToTopRegistry.removeOwner(navigator)
|
|
34
|
-
}
|
|
35
147
|
|
|
36
148
|
@Composable
|
|
37
149
|
internal actual fun RegisterScrollToTop(navigator: Navigator, callback: (() -> Unit)?) {
|
|
38
150
|
val token = remember { Any() }
|
|
39
151
|
DisposableEffect(navigator, callback) {
|
|
40
|
-
|
|
152
|
+
val acquired = callback != null
|
|
153
|
+
if (acquired) {
|
|
154
|
+
StatusBarObserver.acquire()
|
|
41
155
|
log("RegisterScrollToTop push owner=${navigator.tag()} token=${token.tag()}")
|
|
42
|
-
ScrollToTopRegistry.push(navigator, token, callback)
|
|
156
|
+
// ScrollToTopRegistry.push(navigator, token, callback)
|
|
43
157
|
} else {
|
|
44
158
|
log("RegisterScrollToTop remove(callback=null) owner=${navigator.tag()} token=${token.tag()}")
|
|
45
|
-
ScrollToTopRegistry.remove(navigator, token)
|
|
159
|
+
// ScrollToTopRegistry.remove(navigator, token)
|
|
46
160
|
}
|
|
47
161
|
onDispose {
|
|
48
162
|
log("RegisterScrollToTop dispose owner=${navigator.tag()} token=${token.tag()}")
|
|
49
|
-
ScrollToTopRegistry.remove(navigator, token)
|
|
163
|
+
// ScrollToTopRegistry.remove(navigator, token)
|
|
164
|
+
if (acquired) StatusBarObserver.release()
|
|
50
165
|
}
|
|
51
166
|
}
|
|
52
167
|
}
|
|
53
168
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
if (stacksByOwner.size == 1) {
|
|
105
|
-
val only = stacksByOwner.entries.first()
|
|
106
|
-
val cb = only.value.lastOrNull()?.callback
|
|
107
|
-
log("trigger(fallback singleOwner) owner=${only.key.tag()} hasCallback=${cb != null}")
|
|
108
|
-
cb?.invoke()
|
|
109
|
-
} else {
|
|
110
|
-
log("trigger NO-OP (no owner, owners=${stacksByOwner.size}) state=${dump()}")
|
|
111
|
-
}
|
|
112
|
-
return
|
|
113
|
-
}
|
|
114
|
-
val cb = stacksByOwner[owner]?.lastOrNull()?.callback
|
|
115
|
-
log("trigger owner=${owner.tag()} hasCallback=${cb != null} state=${dump()}")
|
|
116
|
-
cb?.invoke()
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
private fun dump(): String {
|
|
120
|
-
val owners = ownerStack.joinToString(",") { it.tag() }
|
|
121
|
-
val stacks = stacksByOwner.entries.joinToString(",") { (o, s) -> "${o.tag()}:${s.size}" }
|
|
122
|
-
return "ownerStack=[$owners] stacks={$stacks}"
|
|
123
|
-
}
|
|
124
|
-
}
|
|
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
|
+
//}
|
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.17
|
|
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,94 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// ScrollToTop.swift
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// Created by Thành Lê on 26/5/26.
|
|
6
|
-
//
|
|
7
|
-
import Foundation
|
|
8
|
-
import SwiftUI
|
|
9
|
-
import Combine
|
|
10
|
-
extension UIStatusBarManager {
|
|
11
|
-
public static var statusBarTappedNotification: Notification.Name = {
|
|
12
|
-
if let originalMethod = class_getInstanceMethod(UIStatusBarManager.self, Selector(("handleTapAction:"))),
|
|
13
|
-
let swizzledMethod = class_getInstanceMethod(UIStatusBarManager.self, #selector(_handleTapAction)) {
|
|
14
|
-
method_exchangeImplementations(originalMethod, swizzledMethod)
|
|
15
|
-
}
|
|
16
|
-
return .init("statusBarSelected")
|
|
17
|
-
}()
|
|
18
|
-
|
|
19
|
-
@objc private func _handleTapAction(_ action: Any?) {
|
|
20
|
-
_handleTapAction(action) // Call the original implementation
|
|
21
|
-
NotificationCenter.default.post(name: UIStatusBarManager.statusBarTappedNotification, object: nil)
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
public struct StatusBarTapModifier: ViewModifier {
|
|
26
|
-
|
|
27
|
-
let action: () -> Void
|
|
28
|
-
|
|
29
|
-
@State private var observer: AnyCancellable?
|
|
30
|
-
|
|
31
|
-
public init(action: @escaping () -> Void) {
|
|
32
|
-
self.action = action
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public func body(content: Content) -> some View {
|
|
36
|
-
content
|
|
37
|
-
.onAppear {
|
|
38
|
-
observer = NotificationCenter.default
|
|
39
|
-
.publisher(for: UIStatusBarManager.statusBarTappedNotification)
|
|
40
|
-
.sink { _ in
|
|
41
|
-
action()
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
.onDisappear {
|
|
45
|
-
observer = nil
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
extension View {
|
|
51
|
-
public func onStatusBarTap(_ action: @escaping () -> Void) -> some View {
|
|
52
|
-
modifier(StatusBarTapModifier(action: action))
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
extension UIViewController {
|
|
57
|
-
|
|
58
|
-
private static var statusBarTapActionKey: UInt8 = 0
|
|
59
|
-
private static var statusBarTapObserverKey: UInt8 = 0
|
|
60
|
-
|
|
61
|
-
private var statusBarTapAction: (() -> Void)? {
|
|
62
|
-
get { objc_getAssociatedObject(self, &Self.statusBarTapActionKey) as? () -> Void }
|
|
63
|
-
set { objc_setAssociatedObject(self, &Self.statusBarTapActionKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC) }
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
private var statusBarTapObserver: NSObjectProtocol? {
|
|
67
|
-
get { objc_getAssociatedObject(self, &Self.statusBarTapObserverKey) as? NSObjectProtocol }
|
|
68
|
-
set { objc_setAssociatedObject(self, &Self.statusBarTapObserverKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/// Register a callback fired when the user taps the status bar / notch.
|
|
72
|
-
/// Host app wires `action` to its scroll-to-top bridge (e.g. Navigation.scrollData.scrollToTopCallback).
|
|
73
|
-
public func registerStatusBarTap(_ action: @escaping () -> Void) {
|
|
74
|
-
unregisterStatusBarTap()
|
|
75
|
-
statusBarTapAction = action
|
|
76
|
-
statusBarTapObserver = NotificationCenter.default.addObserver(
|
|
77
|
-
forName: UIStatusBarManager.statusBarTappedNotification,
|
|
78
|
-
object: nil,
|
|
79
|
-
queue: .main
|
|
80
|
-
) { [weak self] _ in
|
|
81
|
-
self?.statusBarTapAction?()
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
public func unregisterStatusBarTap() {
|
|
86
|
-
if let observer = statusBarTapObserver {
|
|
87
|
-
NotificationCenter.default.removeObserver(observer)
|
|
88
|
-
}
|
|
89
|
-
statusBarTapObserver = nil
|
|
90
|
-
statusBarTapAction = nil
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|