zkit-ui 2.0.4 → 2.0.6
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/README.md +56 -0
- package/android/build.gradle +100 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewLayoutSupport.java +450 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewMediaItem.java +36 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewModule.kt +43 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewPagerAdapter.java +140 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSharedElementGeometry.java +25 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSharedElementNames.java +39 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSharedElementState.java +25 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSourceViewRegistry.java +115 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewTransitionSnapshotView.java +60 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewVideoPlayerView.java +338 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewView.kt +305 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewViewerOverlayView.java +636 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewViewerPageView.java +357 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewViewerTransitionController.java +223 -0
- package/android/src/main/res/layout/native_image_preview_player_view.xml +9 -0
- package/dist/all.d.ts +2 -0
- package/dist/all.d.ts.map +1 -1
- package/dist/all.js +24 -22
- package/dist/ui/NativeImagePreview/NativeImagePreview.d.ts +3 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.js +99 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts +4 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.web.js +150 -0
- package/dist/ui/NativeImagePreview/context.d.ts +14 -0
- package/dist/ui/NativeImagePreview/context.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/context.js +38 -0
- package/dist/ui/NativeImagePreview/index.d.ts +3 -0
- package/dist/ui/NativeImagePreview/index.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/index.js +5 -0
- package/dist/ui/NativeImagePreview/types.d.ts +68 -0
- package/dist/ui/NativeImagePreview/types.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/types.js +2 -0
- package/dist/ui/NativeImagePreview/utils.d.ts +8 -0
- package/dist/ui/NativeImagePreview/utils.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/utils.js +83 -0
- package/dist/ui/NativeImagePreview/web/geometry.d.ts +20 -0
- package/dist/ui/NativeImagePreview/web/geometry.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/web/geometry.js +121 -0
- package/dist/ui/NativeImagePreview/web/media.d.ts +6 -0
- package/dist/ui/NativeImagePreview/web/media.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/web/media.js +90 -0
- package/dist/ui/NativeImagePreview/web/styles.css +137 -0
- package/dist/ui/NativeImagePreview/web/transition.d.ts +15 -0
- package/dist/ui/NativeImagePreview/web/transition.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/web/transition.js +77 -0
- package/dist/ui/NativeImagePreview/web/types.d.ts +74 -0
- package/dist/ui/NativeImagePreview/web/types.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/web/types.js +3 -0
- package/dist/ui/NativeImagePreview/web/viewer.d.ts +104 -0
- package/dist/ui/NativeImagePreview/web/viewer.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/web/viewer.js +894 -0
- package/expo-module.config.json +11 -0
- package/ios/NativeImagePreview/NativeImagePreviewImageLoader.swift +73 -0
- package/ios/NativeImagePreview/NativeImagePreviewImageViewInteraction.swift +245 -0
- package/ios/NativeImagePreview/NativeImagePreviewLayoutSupport.swift +143 -0
- package/ios/NativeImagePreview/NativeImagePreviewMedia.swift +28 -0
- package/ios/NativeImagePreview/NativeImagePreviewSourceViewRegistry.swift +80 -0
- package/ios/NativeImagePreview/NativeImagePreviewVideoPlayerView.swift +437 -0
- package/ios/NativeImagePreview/NativeImagePreviewViewerConfiguration.swift +51 -0
- package/ios/NativeImagePreview/NativeImagePreviewViewerController.swift +745 -0
- package/ios/NativeImagePreview/NativeImagePreviewViewerPageView.swift +623 -0
- package/ios/NativeImagePreview/NativeImagePreviewViewerTransitionCoordinator.swift +312 -0
- package/ios/NativeImagePreviewModule.swift +46 -0
- package/ios/NativeImagePreviewView.swift +252 -0
- package/package.json +16 -2
- package/src/all.ts +18 -0
- package/src/services/ImagePreviewService/README.md +19 -0
- package/src/types.d.ts +1 -0
- package/src/ui/NativeImagePreview/NativeImagePreview.tsx +146 -0
- package/src/ui/NativeImagePreview/NativeImagePreview.web.tsx +201 -0
- package/src/ui/NativeImagePreview/README.md +111 -0
- package/src/ui/NativeImagePreview/context.tsx +22 -0
- package/src/ui/NativeImagePreview/index.tsx +18 -0
- package/src/ui/NativeImagePreview/types.ts +94 -0
- package/src/ui/NativeImagePreview/utils.ts +108 -0
- package/src/ui/NativeImagePreview/web/geometry.ts +134 -0
- package/src/ui/NativeImagePreview/web/media.ts +91 -0
- package/src/ui/NativeImagePreview/web/styles.css +137 -0
- package/src/ui/NativeImagePreview/web/transition.ts +111 -0
- package/src/ui/NativeImagePreview/web/types.ts +88 -0
- package/src/ui/NativeImagePreview/web/viewer.ts +1024 -0
- package/zkit-ui.podspec +13 -1
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
private final class NativeImagePreviewTransitionSnapshotView: UIView {
|
|
4
|
+
private let imageView = UIImageView()
|
|
5
|
+
|
|
6
|
+
init(image: UIImage) {
|
|
7
|
+
super.init(frame: .zero)
|
|
8
|
+
backgroundColor = .clear
|
|
9
|
+
clipsToBounds = true
|
|
10
|
+
isUserInteractionEnabled = false
|
|
11
|
+
|
|
12
|
+
imageView.image = image
|
|
13
|
+
imageView.backgroundColor = .clear
|
|
14
|
+
imageView.contentMode = .scaleToFill
|
|
15
|
+
addSubview(imageView)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
required init?(coder: NSCoder) {
|
|
19
|
+
fatalError("init(coder:) has not been implemented")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func updateImage(_ image: UIImage) {
|
|
23
|
+
imageView.image = image
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func applyContentFrame(_ frame: CGRect) {
|
|
27
|
+
imageView.frame = frame
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
final class NativeImagePreviewViewerTransitionCoordinator {
|
|
32
|
+
private weak var containerView: UIView?
|
|
33
|
+
private weak var hiddenAnchorView: UIView?
|
|
34
|
+
private weak var hiddenPageView: NativeImagePreviewViewerPageView?
|
|
35
|
+
private var activeSnapshotView: UIView?
|
|
36
|
+
|
|
37
|
+
init(containerView: UIView) {
|
|
38
|
+
self.containerView = containerView
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static func aspectFitRect(for imageSize: CGSize, in bounds: CGRect) -> CGRect {
|
|
42
|
+
guard imageSize.width > 0, imageSize.height > 0 else { return bounds }
|
|
43
|
+
|
|
44
|
+
let widthRatio = bounds.width / imageSize.width
|
|
45
|
+
let heightRatio = bounds.height / imageSize.height
|
|
46
|
+
let scale = min(widthRatio, heightRatio)
|
|
47
|
+
let fittedSize = CGSize(
|
|
48
|
+
width: imageSize.width * scale,
|
|
49
|
+
height: imageSize.height * scale
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return CGRect(
|
|
53
|
+
x: bounds.midX - fittedSize.width * 0.5,
|
|
54
|
+
y: bounds.midY - fittedSize.height * 0.5,
|
|
55
|
+
width: fittedSize.width,
|
|
56
|
+
height: fittedSize.height
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
func performOpenTransition(
|
|
61
|
+
from sourceView: UIImageView?,
|
|
62
|
+
to pageView: NativeImagePreviewViewerPageView?,
|
|
63
|
+
backgroundView: UIView,
|
|
64
|
+
contentView: UIView,
|
|
65
|
+
completion: @escaping () -> Void
|
|
66
|
+
) {
|
|
67
|
+
guard let containerView = containerView else {
|
|
68
|
+
completion()
|
|
69
|
+
return
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
restoreHiddenViews()
|
|
73
|
+
activeSnapshotView?.removeFromSuperview()
|
|
74
|
+
activeSnapshotView = nil
|
|
75
|
+
|
|
76
|
+
backgroundView.alpha = 0
|
|
77
|
+
contentView.alpha = 0
|
|
78
|
+
|
|
79
|
+
flushLayoutForSharedElementSampling(of: sourceView)
|
|
80
|
+
guard let sourceView = sourceView, let sourceState = sourceView.nativeImagePreviewSharedElementState() else {
|
|
81
|
+
animateSimpleAppearance(
|
|
82
|
+
backgroundView: backgroundView,
|
|
83
|
+
contentView: contentView,
|
|
84
|
+
completion: completion
|
|
85
|
+
)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let snapshotView = makeSnapshot(from: sourceState)
|
|
90
|
+
apply(sourceState.geometry, to: snapshotView)
|
|
91
|
+
containerView.addSubview(snapshotView)
|
|
92
|
+
activeSnapshotView = snapshotView
|
|
93
|
+
|
|
94
|
+
hiddenAnchorView = sourceView
|
|
95
|
+
sourceView.alpha = 0
|
|
96
|
+
pageView?.setMediaHidden(true)
|
|
97
|
+
hiddenPageView = pageView
|
|
98
|
+
|
|
99
|
+
let animateIntoPage: (Bool) -> Void = { [weak self] isReady in
|
|
100
|
+
guard let self = self, let containerView = self.containerView else {
|
|
101
|
+
completion()
|
|
102
|
+
return
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let windowBounds = containerView.window?.bounds ?? UIScreen.main.bounds
|
|
106
|
+
let targetGeometry: NativeImagePreviewSharedElementGeometry
|
|
107
|
+
if isReady, let pageState = pageView?.sharedElementState() {
|
|
108
|
+
targetGeometry = pageState.geometry
|
|
109
|
+
} else if let pageView = pageView {
|
|
110
|
+
targetGeometry = pageView.defaultTransitionGeometry(in: windowBounds)
|
|
111
|
+
} else {
|
|
112
|
+
targetGeometry = self.defaultGeometry(for: sourceState.image.size, in: windowBounds)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
UIView.animate(
|
|
116
|
+
withDuration: 0.32,
|
|
117
|
+
delay: 0,
|
|
118
|
+
usingSpringWithDamping: 0.94,
|
|
119
|
+
initialSpringVelocity: 0.08,
|
|
120
|
+
options: [.curveEaseInOut, .allowUserInteraction]
|
|
121
|
+
) {
|
|
122
|
+
backgroundView.alpha = 1
|
|
123
|
+
self.apply(targetGeometry, to: snapshotView)
|
|
124
|
+
} completion: { _ in
|
|
125
|
+
self.restoreHiddenViews()
|
|
126
|
+
contentView.alpha = 1
|
|
127
|
+
snapshotView.removeFromSuperview()
|
|
128
|
+
self.activeSnapshotView = nil
|
|
129
|
+
completion()
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
guard let pageView = pageView else {
|
|
134
|
+
animateIntoPage(false)
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
waitUntilReady(pageView: pageView, timeout: 0.42) { ready in
|
|
139
|
+
animateIntoPage(ready)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
func performCloseTransition(
|
|
144
|
+
from pageView: NativeImagePreviewViewerPageView?,
|
|
145
|
+
to anchorView: UIImageView?,
|
|
146
|
+
backgroundView: UIView,
|
|
147
|
+
contentView: UIView,
|
|
148
|
+
completion: @escaping () -> Void
|
|
149
|
+
) {
|
|
150
|
+
guard let containerView = containerView else {
|
|
151
|
+
completion()
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
restoreHiddenViews()
|
|
156
|
+
activeSnapshotView?.removeFromSuperview()
|
|
157
|
+
activeSnapshotView = nil
|
|
158
|
+
|
|
159
|
+
guard
|
|
160
|
+
let pageView,
|
|
161
|
+
let pageState = pageView.sharedElementState()
|
|
162
|
+
else {
|
|
163
|
+
animateSimpleDismissal(
|
|
164
|
+
backgroundView: backgroundView,
|
|
165
|
+
contentView: contentView,
|
|
166
|
+
completion: completion
|
|
167
|
+
)
|
|
168
|
+
return
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let snapshotView = makeSnapshot(from: pageState)
|
|
172
|
+
apply(pageState.geometry, to: snapshotView)
|
|
173
|
+
containerView.addSubview(snapshotView)
|
|
174
|
+
activeSnapshotView = snapshotView
|
|
175
|
+
|
|
176
|
+
hiddenPageView = pageView
|
|
177
|
+
pageView.setMediaHidden(true)
|
|
178
|
+
|
|
179
|
+
var targetGeometry: NativeImagePreviewSharedElementGeometry?
|
|
180
|
+
flushLayoutForSharedElementSampling(of: anchorView)
|
|
181
|
+
if let anchorView = anchorView, let anchorState = anchorView.nativeImagePreviewSharedElementState() {
|
|
182
|
+
hiddenAnchorView = anchorView
|
|
183
|
+
anchorView.alpha = 0
|
|
184
|
+
targetGeometry = anchorState.geometry
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
UIView.animate(
|
|
188
|
+
withDuration: 0.24,
|
|
189
|
+
delay: 0,
|
|
190
|
+
options: [.curveEaseInOut, .allowUserInteraction]
|
|
191
|
+
) {
|
|
192
|
+
backgroundView.alpha = 0
|
|
193
|
+
contentView.alpha = 0
|
|
194
|
+
if let targetGeometry = targetGeometry {
|
|
195
|
+
self.apply(targetGeometry, to: snapshotView)
|
|
196
|
+
} else {
|
|
197
|
+
snapshotView.alpha = 0
|
|
198
|
+
snapshotView.transform = CGAffineTransform(scaleX: 0.92, y: 0.92)
|
|
199
|
+
}
|
|
200
|
+
} completion: { [weak self] _ in
|
|
201
|
+
self?.restoreHiddenViews()
|
|
202
|
+
snapshotView.removeFromSuperview()
|
|
203
|
+
self?.activeSnapshotView = nil
|
|
204
|
+
completion()
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private func animateSimpleAppearance(
|
|
209
|
+
backgroundView: UIView,
|
|
210
|
+
contentView: UIView,
|
|
211
|
+
completion: @escaping () -> Void
|
|
212
|
+
) {
|
|
213
|
+
UIView.animate(
|
|
214
|
+
withDuration: 0.18,
|
|
215
|
+
delay: 0,
|
|
216
|
+
options: [.curveEaseOut, .allowUserInteraction]
|
|
217
|
+
) {
|
|
218
|
+
backgroundView.alpha = 1
|
|
219
|
+
contentView.alpha = 1
|
|
220
|
+
} completion: { _ in
|
|
221
|
+
completion()
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private func animateSimpleDismissal(
|
|
226
|
+
backgroundView: UIView,
|
|
227
|
+
contentView: UIView,
|
|
228
|
+
completion: @escaping () -> Void
|
|
229
|
+
) {
|
|
230
|
+
UIView.animate(
|
|
231
|
+
withDuration: 0.18,
|
|
232
|
+
delay: 0,
|
|
233
|
+
options: [.curveEaseIn, .allowUserInteraction]
|
|
234
|
+
) {
|
|
235
|
+
backgroundView.alpha = 0
|
|
236
|
+
contentView.alpha = 0
|
|
237
|
+
} completion: { _ in
|
|
238
|
+
completion()
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
private func waitUntilReady(
|
|
243
|
+
pageView: NativeImagePreviewViewerPageView,
|
|
244
|
+
timeout: TimeInterval,
|
|
245
|
+
completion: @escaping (Bool) -> Void
|
|
246
|
+
) {
|
|
247
|
+
let deadline = CACurrentMediaTime() + timeout
|
|
248
|
+
var lastStableState: NativeImagePreviewSharedElementState?
|
|
249
|
+
var stableFrameCount = 0
|
|
250
|
+
|
|
251
|
+
func poll() {
|
|
252
|
+
pageView.layoutIfNeeded()
|
|
253
|
+
if let currentState = pageView.sharedElementState() {
|
|
254
|
+
if let lastStableState = lastStableState, lastStableState.approximatelyEquals(currentState) {
|
|
255
|
+
stableFrameCount += 1
|
|
256
|
+
} else {
|
|
257
|
+
lastStableState = currentState
|
|
258
|
+
stableFrameCount = 1
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if stableFrameCount >= 2 {
|
|
262
|
+
completion(true)
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if CACurrentMediaTime() >= deadline {
|
|
267
|
+
completion(false)
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 0.016, execute: poll)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
poll()
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private func frameInContainer(fromWindowFrame windowFrame: CGRect) -> CGRect {
|
|
277
|
+
guard let containerView = containerView else { return windowFrame }
|
|
278
|
+
return containerView.convert(windowFrame, from: nil)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
private func flushLayoutForSharedElementSampling(of imageView: UIImageView?) {
|
|
282
|
+
imageView?.superview?.layoutIfNeeded()
|
|
283
|
+
imageView?.layoutIfNeeded()
|
|
284
|
+
containerView?.layoutIfNeeded()
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private func makeSnapshot(from state: NativeImagePreviewSharedElementState) -> NativeImagePreviewTransitionSnapshotView {
|
|
288
|
+
NativeImagePreviewTransitionSnapshotView(image: state.image)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private func apply(_ geometry: NativeImagePreviewSharedElementGeometry, to snapshotView: NativeImagePreviewTransitionSnapshotView) {
|
|
292
|
+
snapshotView.frame = frameInContainer(fromWindowFrame: geometry.visibleFrameInWindow)
|
|
293
|
+
snapshotView.applyContentFrame(geometry.contentFrameInVisibleBounds)
|
|
294
|
+
snapshotView.layer.cornerRadius = geometry.cornerRadius
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
private func defaultGeometry(for imageSize: CGSize, in windowBounds: CGRect) -> NativeImagePreviewSharedElementGeometry {
|
|
298
|
+
let visibleFrame = Self.aspectFitRect(for: imageSize, in: windowBounds)
|
|
299
|
+
return NativeImagePreviewSharedElementGeometry(
|
|
300
|
+
visibleFrameInWindow: visibleFrame,
|
|
301
|
+
contentFrameInVisibleBounds: CGRect(origin: .zero, size: visibleFrame.size),
|
|
302
|
+
cornerRadius: 0
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private func restoreHiddenViews() {
|
|
307
|
+
hiddenAnchorView?.alpha = 1
|
|
308
|
+
hiddenAnchorView = nil
|
|
309
|
+
hiddenPageView?.setMediaHidden(false)
|
|
310
|
+
hiddenPageView = nil
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
|
|
3
|
+
public class NativeImagePreviewModule: Module {
|
|
4
|
+
public func definition() -> ModuleDefinition {
|
|
5
|
+
Name("ZKitNativeImagePreview")
|
|
6
|
+
|
|
7
|
+
View(NativeImagePreviewView.self) {
|
|
8
|
+
Events("onIndexChange")
|
|
9
|
+
|
|
10
|
+
OnViewDidUpdateProps { (view) in
|
|
11
|
+
view.setupImageView()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
Prop("urls") { (view, urls: [String]?) in
|
|
15
|
+
view.urls = urls
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Prop("items") { (view, items: [[String: Any]]?) in
|
|
19
|
+
view.items = items
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
Prop("index") { (view, index: Int?) in
|
|
23
|
+
view.initialIndex = index
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Prop("previewGroupId") { (view, previewGroupId: String?) in
|
|
27
|
+
view.previewGroupId = previewGroupId
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
Prop("theme") { (view, theme: Theme?) in
|
|
31
|
+
view.theme = theme ?? .dark
|
|
32
|
+
}
|
|
33
|
+
Prop("closeIconName") { (view, closeIconName: String?) in
|
|
34
|
+
view.closeIconName = closeIconName
|
|
35
|
+
}
|
|
36
|
+
Prop("rightNavItemIconName") { (view, rightNavItemIconName: String) in
|
|
37
|
+
view.rightNavItemIconName = rightNavItemIconName
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
func onIndexChange(index: Int) {
|
|
44
|
+
sendEvent("onIndexChange", ["currentIndex": index])
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import ExpoModulesCore
|
|
2
|
+
import UIKit
|
|
3
|
+
|
|
4
|
+
class NativeImagePreviewView: ExpoView {
|
|
5
|
+
private var childImageView: UIImageView?
|
|
6
|
+
private weak var registeredAnchorImageView: UIImageView?
|
|
7
|
+
private var registeredPreviewGroupId: String?
|
|
8
|
+
private var registeredIndex: Int?
|
|
9
|
+
|
|
10
|
+
deinit {
|
|
11
|
+
unregisterCurrentAnchor()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
override func didMoveToWindow() {
|
|
15
|
+
super.didMoveToWindow()
|
|
16
|
+
if window == nil {
|
|
17
|
+
unregisterCurrentAnchor()
|
|
18
|
+
} else {
|
|
19
|
+
registerCurrentAnchorIfNeeded()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
func getChildImageView() -> UIImageView? {
|
|
24
|
+
var reactSubviews: [UIView]? = nil
|
|
25
|
+
if RCTIsNewArchEnabled() {
|
|
26
|
+
reactSubviews = self.subviews
|
|
27
|
+
} else {
|
|
28
|
+
reactSubviews = self.reactSubviews()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
guard let reactSubviews = reactSubviews else { return nil }
|
|
32
|
+
|
|
33
|
+
for reactSubview in reactSubviews {
|
|
34
|
+
if let iv = findImageView(in: reactSubview) {
|
|
35
|
+
childImageView = iv
|
|
36
|
+
return iv
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return nil
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private func findImageView(in view: UIView) -> UIImageView? {
|
|
44
|
+
if let imageView = view as? UIImageView { return imageView }
|
|
45
|
+
for subview in view.subviews {
|
|
46
|
+
if let imageView = subview as? UIImageView { return imageView }
|
|
47
|
+
if let nested = findImageView(in: subview) { return nested }
|
|
48
|
+
}
|
|
49
|
+
return nil
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#if !RCT_NEW_ARCH_ENABLED
|
|
53
|
+
override func insertReactSubview(_ subview: UIView!, at atIndex: Int) {
|
|
54
|
+
super.insertReactSubview(subview, at: atIndex)
|
|
55
|
+
setupImageView()
|
|
56
|
+
}
|
|
57
|
+
#endif
|
|
58
|
+
|
|
59
|
+
#if RCT_NEW_ARCH_ENABLED
|
|
60
|
+
// Cleanup gesture recognizers from the image view to work with Fabric view recycling.
|
|
61
|
+
override func unmountChildComponentView(_ childComponentView: UIView, index: Int) {
|
|
62
|
+
unregisterCurrentAnchor()
|
|
63
|
+
childImageView?.removeNativeImagePreviewViewerInteraction()
|
|
64
|
+
childImageView = nil
|
|
65
|
+
super.unmountChildComponentView(childComponentView, index: index)
|
|
66
|
+
}
|
|
67
|
+
#endif
|
|
68
|
+
|
|
69
|
+
var theme: Theme = .dark
|
|
70
|
+
var urls: [String]?
|
|
71
|
+
var items: [[String: Any]]?
|
|
72
|
+
var initialIndex: Int?
|
|
73
|
+
var previewGroupId: String?
|
|
74
|
+
var closeIconName: String?
|
|
75
|
+
var rightNavItemIconName: String?
|
|
76
|
+
let onPressRightNavItemIcon = EventDispatcher()
|
|
77
|
+
let onIndexChange = EventDispatcher()
|
|
78
|
+
|
|
79
|
+
public func setupImageView() {
|
|
80
|
+
registerCurrentAnchorIfNeeded()
|
|
81
|
+
guard let childImage = getChildImageView() else {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if let items = self.items, let initialIndex = self.initialIndex {
|
|
86
|
+
setupViewerWithItems(childImage, items: items, initialIndex: initialIndex)
|
|
87
|
+
} else if let urls = self.urls, let initialIndex = self.initialIndex {
|
|
88
|
+
setupViewerWithUrls(childImage, urls: urls, initialIndex: initialIndex)
|
|
89
|
+
} else {
|
|
90
|
+
setupViewerWithSingleImage(childImage)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private func setupViewerWithItems(
|
|
95
|
+
_ childImage: UIImageView,
|
|
96
|
+
items: [[String: Any]],
|
|
97
|
+
initialIndex: Int
|
|
98
|
+
) {
|
|
99
|
+
let configuration = buildViewerConfiguration()
|
|
100
|
+
|
|
101
|
+
let imageItems: [NativeImagePreviewMediaItem] = items.compactMap { item in
|
|
102
|
+
guard let type = item["type"] as? String else { return nil }
|
|
103
|
+
|
|
104
|
+
if type == "video", let urlStr = item["url"] as? String, let url = URL(string: urlStr) {
|
|
105
|
+
var posterUrl: URL? = nil
|
|
106
|
+
if let posterStr = item["poster"] as? String {
|
|
107
|
+
posterUrl = URL(string: posterStr)
|
|
108
|
+
}
|
|
109
|
+
return NativeImagePreviewMediaItem.video(url: url, poster: posterUrl)
|
|
110
|
+
} else if type == "image", let urlStr = item["url"] as? String {
|
|
111
|
+
if urlStr.hasPrefix("http://") || urlStr.hasPrefix("https://") || urlStr.hasPrefix("file://") {
|
|
112
|
+
if let url = URL(string: urlStr) {
|
|
113
|
+
return NativeImagePreviewMediaItem.imageURL(url, placeholder: nil)
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
return NativeImagePreviewMediaItem.imageURL(URL(fileURLWithPath: urlStr), placeholder: nil)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return nil
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
let datasource = NativeImagePreviewArrayDataSource(items: imageItems)
|
|
123
|
+
childImage.setupNativeImagePreviewViewer(
|
|
124
|
+
dataSource: datasource,
|
|
125
|
+
initialIndex: initialIndex,
|
|
126
|
+
configuration: configuration,
|
|
127
|
+
previewGroupId: previewGroupId)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
private func setupViewerWithUrls(
|
|
132
|
+
_ childImage: UIImageView,
|
|
133
|
+
urls: [String],
|
|
134
|
+
initialIndex: Int
|
|
135
|
+
) {
|
|
136
|
+
let configuration = buildViewerConfiguration()
|
|
137
|
+
|
|
138
|
+
let urlObjects: [URL] = urls.compactMap { string in
|
|
139
|
+
if string.hasPrefix("http://") || string.hasPrefix("https://") || string.hasPrefix("file://") {
|
|
140
|
+
return URL(string: string)
|
|
141
|
+
}
|
|
142
|
+
return URL(fileURLWithPath: string)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
childImage.setupNativeImagePreviewViewer(
|
|
146
|
+
urls: urlObjects,
|
|
147
|
+
initialIndex: initialIndex,
|
|
148
|
+
configuration: configuration,
|
|
149
|
+
previewGroupId: previewGroupId)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
private func setupViewerWithSingleImage(_ childImage: UIImageView) {
|
|
153
|
+
guard let img = childImage.image else {
|
|
154
|
+
print("Missing image in childImage: \(childImage)")
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
let configuration = buildViewerConfiguration()
|
|
158
|
+
|
|
159
|
+
childImage.setupNativeImagePreviewViewer(images: [img], configuration: configuration, previewGroupId: previewGroupId)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private func buildViewerConfiguration() -> NativeImagePreviewViewerConfiguration {
|
|
163
|
+
var configuration = NativeImagePreviewViewerConfiguration(theme: theme.toViewerTheme())
|
|
164
|
+
let iconColor = theme.iconColor()
|
|
165
|
+
|
|
166
|
+
if let closeIconName = closeIconName,
|
|
167
|
+
let closeIconImage = UIImage(systemName: closeIconName)?.withTintColor(
|
|
168
|
+
iconColor, renderingMode: .alwaysOriginal)
|
|
169
|
+
{
|
|
170
|
+
configuration.closeIcon = closeIconImage
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if let rightIconName = rightNavItemIconName,
|
|
174
|
+
let rightIconImage = UIImage(systemName: rightIconName)?.withTintColor(
|
|
175
|
+
iconColor, renderingMode: .alwaysOriginal)
|
|
176
|
+
{
|
|
177
|
+
configuration.rightBarButton = .icon(
|
|
178
|
+
rightIconImage,
|
|
179
|
+
onTap: { index in
|
|
180
|
+
self.onPressRightNavItemIcon(["index": index])
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
configuration.onIndexChange = { [weak self] index in
|
|
185
|
+
self?.onIndexChange(["currentIndex": index])
|
|
186
|
+
}
|
|
187
|
+
return configuration
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private func registerCurrentAnchorIfNeeded() {
|
|
191
|
+
guard
|
|
192
|
+
window != nil,
|
|
193
|
+
let previewGroupId,
|
|
194
|
+
let initialIndex,
|
|
195
|
+
let childImage = getChildImageView()
|
|
196
|
+
else {
|
|
197
|
+
unregisterCurrentAnchor()
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if
|
|
202
|
+
let registeredAnchorImageView,
|
|
203
|
+
(registeredAnchorImageView !== childImage
|
|
204
|
+
|| registeredPreviewGroupId != previewGroupId
|
|
205
|
+
|| registeredIndex != initialIndex)
|
|
206
|
+
{
|
|
207
|
+
unregisterCurrentAnchor()
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
NativeImagePreviewSourceViewRegistry.shared.register(
|
|
211
|
+
childImage,
|
|
212
|
+
previewGroupId: previewGroupId,
|
|
213
|
+
index: initialIndex)
|
|
214
|
+
registeredAnchorImageView = childImage
|
|
215
|
+
registeredPreviewGroupId = previewGroupId
|
|
216
|
+
registeredIndex = initialIndex
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private func unregisterCurrentAnchor() {
|
|
220
|
+
if let registeredAnchorImageView = registeredAnchorImageView {
|
|
221
|
+
NativeImagePreviewSourceViewRegistry.shared.unregister(
|
|
222
|
+
registeredAnchorImageView,
|
|
223
|
+
previewGroupId: registeredPreviewGroupId,
|
|
224
|
+
index: registeredIndex)
|
|
225
|
+
}
|
|
226
|
+
registeredAnchorImageView = nil
|
|
227
|
+
registeredPreviewGroupId = nil
|
|
228
|
+
registeredIndex = nil
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
enum Theme: String, Enumerable {
|
|
233
|
+
case dark
|
|
234
|
+
case light
|
|
235
|
+
|
|
236
|
+
func toViewerTheme() -> NativeImagePreviewViewerTheme {
|
|
237
|
+
switch self {
|
|
238
|
+
case .dark:
|
|
239
|
+
return .dark
|
|
240
|
+
case .light:
|
|
241
|
+
return .light
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
func iconColor() -> UIColor {
|
|
245
|
+
switch self {
|
|
246
|
+
case .dark:
|
|
247
|
+
return .white
|
|
248
|
+
case .light:
|
|
249
|
+
return .black
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zkit-ui",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -87,6 +87,11 @@
|
|
|
87
87
|
"react-native": "./dist/ui/LinkedScroll/index.js",
|
|
88
88
|
"default": "./dist/ui/LinkedScroll/index.js"
|
|
89
89
|
},
|
|
90
|
+
"./native-image-preview": {
|
|
91
|
+
"types": "./dist/ui/NativeImagePreview/index.d.ts",
|
|
92
|
+
"react-native": "./dist/ui/NativeImagePreview/index.js",
|
|
93
|
+
"default": "./dist/ui/NativeImagePreview/index.js"
|
|
94
|
+
},
|
|
90
95
|
"./loading-spinner": {
|
|
91
96
|
"types": "./dist/ui/LoadingSpinner/index.d.ts",
|
|
92
97
|
"react-native": "./dist/ui/LoadingSpinner/index.js",
|
|
@@ -165,7 +170,10 @@
|
|
|
165
170
|
"./assets/*": "./dist/assets/*",
|
|
166
171
|
"./package.json": "./package.json"
|
|
167
172
|
},
|
|
168
|
-
"sideEffects":
|
|
173
|
+
"sideEffects": [
|
|
174
|
+
"*.css",
|
|
175
|
+
"**/*.css"
|
|
176
|
+
],
|
|
169
177
|
"repository": {
|
|
170
178
|
"type": "git",
|
|
171
179
|
"url": "git+https://github.com/Concur-max/zkit.git",
|
|
@@ -176,7 +184,10 @@
|
|
|
176
184
|
"url": "https://github.com/Concur-max/zkit/issues"
|
|
177
185
|
},
|
|
178
186
|
"files": [
|
|
187
|
+
"android/build.gradle",
|
|
188
|
+
"android/src",
|
|
179
189
|
"dist",
|
|
190
|
+
"expo-module.config.json",
|
|
180
191
|
"src",
|
|
181
192
|
"ios",
|
|
182
193
|
"react-native.config.js",
|
|
@@ -197,6 +208,7 @@
|
|
|
197
208
|
"dependencies": {
|
|
198
209
|
"@vant/area-data": "^2.1.0",
|
|
199
210
|
"dayjs": "^1.11.13",
|
|
211
|
+
"react-native-is-edge-to-edge": "^1.1.6",
|
|
200
212
|
"react-native-toast-message": "^2.3.3",
|
|
201
213
|
"zkit-tools": "1.0.1"
|
|
202
214
|
},
|
|
@@ -204,6 +216,7 @@
|
|
|
204
216
|
"@expo/vector-icons": "*",
|
|
205
217
|
"@lodev09/react-native-true-sheet": "*",
|
|
206
218
|
"@shopify/flash-list": "*",
|
|
219
|
+
"expo": "*",
|
|
207
220
|
"expo-image": "*",
|
|
208
221
|
"expo-linear-gradient": "*",
|
|
209
222
|
"lottie-react-native": "*",
|
|
@@ -222,6 +235,7 @@
|
|
|
222
235
|
"@shopify/flash-list": "^2.3.1",
|
|
223
236
|
"@types/react": "~19.1.17",
|
|
224
237
|
"dayjs": "^1.11.13",
|
|
238
|
+
"expo": "~54.0.31",
|
|
225
239
|
"expo-image": "~3.0.11",
|
|
226
240
|
"expo-linear-gradient": "~15.0.8",
|
|
227
241
|
"lottie-react-native": "7.3.2",
|
package/src/all.ts
CHANGED
|
@@ -238,6 +238,24 @@ export type {
|
|
|
238
238
|
LinkedScrollSectionRenderContext,
|
|
239
239
|
LinkedScrollValue,
|
|
240
240
|
} from './ui/LinkedScroll/index';
|
|
241
|
+
export { NativeImagePreview } from './ui/NativeImagePreview/index';
|
|
242
|
+
export type {
|
|
243
|
+
NativeImagePreviewChangeEvent,
|
|
244
|
+
NativeImagePreviewChangeEventPayload,
|
|
245
|
+
NativeImagePreviewChangeMeta,
|
|
246
|
+
NativeImagePreviewChangeReason,
|
|
247
|
+
NativeImagePreviewColorScheme,
|
|
248
|
+
NativeImagePreviewComponent,
|
|
249
|
+
NativeImagePreviewItem,
|
|
250
|
+
NativeImagePreviewItemDescriptor,
|
|
251
|
+
NativeImagePreviewItemProps,
|
|
252
|
+
NativeImagePreviewMediaType,
|
|
253
|
+
NativeImagePreviewNativeProps,
|
|
254
|
+
NativeImagePreviewProps,
|
|
255
|
+
NativeImagePreviewResolvedItem,
|
|
256
|
+
NativeImagePreviewResolvedColorScheme,
|
|
257
|
+
NativeImagePreviewSource,
|
|
258
|
+
} from './ui/NativeImagePreview/index';
|
|
241
259
|
export { ThemeProvider } from './theme/ThemeProvider';
|
|
242
260
|
export { useTheme } from './theme/useTheme';
|
|
243
261
|
export type { Theme, ThemeOverride } from './theme/types';
|