zkit-ui 2.0.3 → 2.0.5

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.
Files changed (63) hide show
  1. package/README.md +56 -0
  2. package/android/build.gradle +100 -0
  3. package/android/src/main/AndroidManifest.xml +1 -0
  4. package/android/src/main/java/cn/fontree/zkit/ui/preview/GaleriaModule.kt +43 -0
  5. package/android/src/main/java/cn/fontree/zkit/ui/preview/GaleriaView.kt +305 -0
  6. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryLayoutSupport.java +450 -0
  7. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryMediaItem.java +36 -0
  8. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryPagerAdapter.java +140 -0
  9. package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySharedElementGeometry.java +25 -0
  10. package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySharedElementNames.java +39 -0
  11. package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySharedElementState.java +25 -0
  12. package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySourceViewRegistry.java +115 -0
  13. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryTransitionSnapshotView.java +60 -0
  14. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryVideoPlayerView.java +338 -0
  15. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryViewerOverlayView.java +636 -0
  16. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryViewerPageView.java +357 -0
  17. package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryViewerTransitionController.java +223 -0
  18. package/android/src/main/res/layout/galeria_player_view.xml +9 -0
  19. package/dist/all.d.ts +2 -0
  20. package/dist/all.d.ts.map +1 -1
  21. package/dist/all.js +24 -22
  22. package/dist/ui/NativeImagePreview/NativeImagePreview.d.ts +3 -0
  23. package/dist/ui/NativeImagePreview/NativeImagePreview.d.ts.map +1 -0
  24. package/dist/ui/NativeImagePreview/NativeImagePreview.js +99 -0
  25. package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts +3 -0
  26. package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts.map +1 -0
  27. package/dist/ui/NativeImagePreview/NativeImagePreview.web.js +56 -0
  28. package/dist/ui/NativeImagePreview/context.d.ts +14 -0
  29. package/dist/ui/NativeImagePreview/context.d.ts.map +1 -0
  30. package/dist/ui/NativeImagePreview/context.js +38 -0
  31. package/dist/ui/NativeImagePreview/index.d.ts +3 -0
  32. package/dist/ui/NativeImagePreview/index.d.ts.map +1 -0
  33. package/dist/ui/NativeImagePreview/index.js +5 -0
  34. package/dist/ui/NativeImagePreview/types.d.ts +66 -0
  35. package/dist/ui/NativeImagePreview/types.d.ts.map +1 -0
  36. package/dist/ui/NativeImagePreview/types.js +2 -0
  37. package/dist/ui/NativeImagePreview/utils.d.ts +8 -0
  38. package/dist/ui/NativeImagePreview/utils.d.ts.map +1 -0
  39. package/dist/ui/NativeImagePreview/utils.js +82 -0
  40. package/expo-module.config.json +11 -0
  41. package/ios/GaleriaModule.swift +46 -0
  42. package/ios/GaleriaView.swift +253 -0
  43. package/ios/GalleryViewer/GaleriaImageLoader.swift +73 -0
  44. package/ios/GalleryViewer/GaleriaImageViewInteraction.swift +245 -0
  45. package/ios/GalleryViewer/GaleriaLayoutSupport.swift +143 -0
  46. package/ios/GalleryViewer/GaleriaMedia.swift +28 -0
  47. package/ios/GalleryViewer/GaleriaSourceViewRegistry.swift +80 -0
  48. package/ios/GalleryViewer/GaleriaVideoPlayerView.swift +437 -0
  49. package/ios/GalleryViewer/GaleriaViewerConfiguration.swift +51 -0
  50. package/ios/GalleryViewer/GaleriaViewerController.swift +745 -0
  51. package/ios/GalleryViewer/GaleriaViewerPageView.swift +623 -0
  52. package/ios/GalleryViewer/GaleriaViewerTransitionCoordinator.swift +312 -0
  53. package/package.json +21 -13
  54. package/src/all.ts +18 -0
  55. package/src/services/ImagePreviewService/README.md +19 -0
  56. package/src/ui/NativeImagePreview/NativeImagePreview.tsx +146 -0
  57. package/src/ui/NativeImagePreview/NativeImagePreview.web.tsx +37 -0
  58. package/src/ui/NativeImagePreview/README.md +111 -0
  59. package/src/ui/NativeImagePreview/context.tsx +22 -0
  60. package/src/ui/NativeImagePreview/index.tsx +18 -0
  61. package/src/ui/NativeImagePreview/types.ts +92 -0
  62. package/src/ui/NativeImagePreview/utils.ts +107 -0
  63. package/zkit-ui.podspec +13 -1
package/README.md CHANGED
@@ -121,6 +121,7 @@ function Demo() {
121
121
  - Sheet:通用方向弹层(H5 bottom/top/left/right 自绘,iOS/Android bottom 内部走原生 TrueSheet)
122
122
  - LoadingService:全局加载 HUD 服务(handle 状态模型、Promise 绑定、Android 无 elevation 残影路径)
123
123
  - ImagePreview:全屏图片预览(声明式 open/value 状态模型 + 全局 imagePreview.open 服务)
124
+ - NativeImagePreview:原生共享转场图片预览(真实缩略图锚点 + 原生 viewer 手势链路)
124
125
  - Picker:底部滚轮选择器(单列/级联、确认提交、iOS 原生 wheel)
125
126
  - AddressCascader:省市区级联选择(内置中国数据)
126
127
  - DatePicker:年/月/日日期选择(空值、范围、精度与确认提交语义)
@@ -209,6 +210,61 @@ imagePreview.open({ images, index: 0 });
209
210
 
210
211
  `ImagePreview` 使用 `open/defaultOpen/onOpenChange` 管理显隐,使用 `value/defaultValue/onChange` 管理当前图片索引。全局 `imagePreview.open()` 返回 handle,可通过 `handle.result` 获取关闭原因和最终索引;`ZKitProvider` 已内置 `ImagePreviewProvider`。
211
212
 
213
+ ### NativeImagePreview 用法
214
+
215
+ ```tsx
216
+ import { Image, StyleSheet, View, type ImageSourcePropType } from 'react-native';
217
+ import { NativeImagePreview, type NativeImagePreviewItemDescriptor } from 'zkit-ui/native-image-preview';
218
+
219
+ const items: NativeImagePreviewItemDescriptor[] = [
220
+ { id: 'cover', source: require('./cover.png'), width: 1200, height: 900 },
221
+ { id: 'detail', source: 'https://example.com/detail.jpg' },
222
+ { id: 'clip', type: 'video', url: videoUrl, poster: require('./poster.png') },
223
+ ];
224
+
225
+ function getThumbnailSource(item: NativeImagePreviewItemDescriptor): ImageSourcePropType | undefined {
226
+ const source = item.poster ?? item.source;
227
+ return typeof source === 'string' ? { uri: source } : source;
228
+ }
229
+
230
+ export function DemoNativeImagePreview() {
231
+ return (
232
+ <NativeImagePreview items={items} colorScheme="dark" edgeToEdge>
233
+ <View style={styles.grid}>
234
+ {items.map((item, index) => {
235
+ const thumbnailSource = getThumbnailSource(item);
236
+ if (!thumbnailSource) return null;
237
+
238
+ return (
239
+ <View key={item.id ?? index} style={styles.tile}>
240
+ <NativeImagePreview.Item index={index} style={StyleSheet.absoluteFill}>
241
+ <Image source={thumbnailSource} style={styles.thumbnail} />
242
+ </NativeImagePreview.Item>
243
+ </View>
244
+ );
245
+ })}
246
+ </View>
247
+ </NativeImagePreview>
248
+ );
249
+ }
250
+
251
+ const styles = StyleSheet.create({
252
+ grid: {
253
+ flexDirection: 'row',
254
+ flexWrap: 'wrap',
255
+ },
256
+ tile: {
257
+ aspectRatio: 1,
258
+ width: '33.333%',
259
+ },
260
+ thumbnail: {
261
+ ...StyleSheet.absoluteFillObject,
262
+ },
263
+ });
264
+ ```
265
+
266
+ `NativeImagePreview` 不是 `ImagePreview` 的命令式替代品,而是面向真实缩略图锚点的原生共享转场组件。`items` 顺序必须和 `NativeImagePreview.Item index` 对齐,`Item` 内必须渲染真实图片节点;iOS / Android 走原生 viewer,原生模块未注册时不会降级到普通 `ImagePreview`,需要重建 native app。Web 仅渲染子节点,不提供预览能力。
267
+
212
268
  ### 目录与导出
213
269
 
214
270
  - 组件放在 `src/ui/`(一组件一文件为主;复杂组件可拆同名子文件)
@@ -0,0 +1,100 @@
1
+ apply plugin: 'com.android.library'
2
+ apply plugin: 'kotlin-android'
3
+ apply plugin: 'maven-publish'
4
+
5
+ group = 'cn.fontree.zkit.ui'
6
+ version = '1.0.0'
7
+
8
+ buildscript {
9
+ def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
10
+ if (expoModulesCorePlugin.exists()) {
11
+ apply from: expoModulesCorePlugin
12
+ applyKotlinExpoModulesCorePlugin()
13
+ }
14
+
15
+ // Simple helper that allows the root project to override versions declared by this library.
16
+ ext.safeExtGet = { prop, fallback ->
17
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
18
+ }
19
+
20
+ // Ensures backward compatibility
21
+ ext.getKotlinVersion = {
22
+ if (ext.has("kotlinVersion")) {
23
+ ext.kotlinVersion()
24
+ } else {
25
+ ext.safeExtGet("kotlinVersion", "1.8.10")
26
+ }
27
+ }
28
+
29
+ repositories {
30
+ mavenCentral()
31
+ maven { url 'https://jitpack.io' }
32
+ }
33
+
34
+ dependencies {
35
+ classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${getKotlinVersion()}")
36
+ }
37
+ }
38
+
39
+ afterEvaluate {
40
+ publishing {
41
+ publications {
42
+ release(MavenPublication) {
43
+ from components.release
44
+ }
45
+ }
46
+ repositories {
47
+ maven {
48
+ url = mavenLocal().url
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ android {
55
+ compileSdkVersion safeExtGet("compileSdkVersion", 33)
56
+
57
+ compileOptions {
58
+ sourceCompatibility JavaVersion.VERSION_17
59
+ targetCompatibility JavaVersion.VERSION_17
60
+ }
61
+
62
+ kotlinOptions {
63
+ jvmTarget = JavaVersion.VERSION_17.majorVersion
64
+ }
65
+
66
+ namespace "cn.fontree.zkit.ui.preview"
67
+ defaultConfig {
68
+ minSdkVersion safeExtGet("minSdkVersion", 21)
69
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
70
+ versionCode 1
71
+ versionName "1.0.0"
72
+ }
73
+ lintOptions {
74
+ abortOnError false
75
+ }
76
+ publishing {
77
+ singleVariant("release") {
78
+ withSourcesJar()
79
+ }
80
+ }
81
+ }
82
+
83
+ repositories {
84
+ mavenCentral()
85
+ }
86
+
87
+ dependencies {
88
+ def GLIDE_VERSION = "4.16.0"
89
+ implementation 'com.facebook.react:react-android'
90
+ implementation 'androidx.appcompat:appcompat:1.7.0'
91
+
92
+ implementation project(':expo-modules-core')
93
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
94
+ implementation "com.github.bumptech.glide:glide:${GLIDE_VERSION}"
95
+ implementation 'androidx.viewpager2:viewpager2:1.1.0'
96
+ implementation 'androidx.recyclerview:recyclerview:1.4.0'
97
+ implementation 'com.github.chrisbanes:PhotoView:2.3.0'
98
+ implementation 'androidx.media3:media3-exoplayer:1.5.1'
99
+ implementation 'androidx.media3:media3-ui:1.5.1'
100
+ }
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,43 @@
1
+ package cn.fontree.zkit.ui.preview
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+ import expo.modules.kotlin.modules.ModuleDefinition
5
+
6
+ class GaleriaModule : Module() {
7
+ override fun definition() = ModuleDefinition {
8
+ Name("ZKitNativeImagePreview")
9
+
10
+ View(GaleriaView::class) {
11
+ Events(
12
+ "onIndexChange"
13
+ )
14
+ Prop("theme") { view: GaleriaView, theme: Theme ->
15
+ view.theme = theme
16
+ }
17
+ Prop("items") { view: GaleriaView, items: Array<Map<String, Any?>> ->
18
+ view.items = items.toList()
19
+ }
20
+ Prop("urls") { view: GaleriaView, urls: Array<String> ->
21
+ view.urls = urls
22
+ }
23
+ Prop("index") { view: GaleriaView, index: Int ->
24
+ view.initialIndex = index
25
+ }
26
+ Prop("galleryId") { view: GaleriaView, galleryId: String? ->
27
+ view.galleryId = galleryId
28
+ }
29
+ Prop("disableHiddenOriginalImage") { view: GaleriaView, disableHiddenOriginalImage: Boolean ->
30
+ view.disableHiddenOriginalImage = disableHiddenOriginalImage
31
+ }
32
+ Prop("edgeToEdge") { view: GaleriaView, edgeToEdge: Boolean ->
33
+ view.edgeToEdge = edgeToEdge
34
+ }
35
+ Prop("transitionOffsetY") { view: GaleriaView, transitionOffsetY: Int? ->
36
+ view.transitionOffsetY = transitionOffsetY
37
+ }
38
+ Prop("transitionOffsetX") { view: GaleriaView, transitionOffsetX: Int? ->
39
+ view.transitionOffsetX = transitionOffsetX
40
+ }
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,305 @@
1
+ package cn.fontree.zkit.ui.preview
2
+
3
+ import android.app.Activity
4
+ import android.content.Context
5
+ import android.content.ContextWrapper
6
+ import android.view.View
7
+ import android.view.ViewGroup
8
+ import android.widget.ImageView
9
+ import androidx.activity.ComponentActivity
10
+ import androidx.activity.OnBackPressedCallback
11
+ import expo.modules.kotlin.viewevent.EventDispatcher
12
+
13
+ enum class Theme(val value: String) {
14
+ Dark("dark"),
15
+ Light("light")
16
+ }
17
+
18
+ class GaleriaView(context: Context) : ViewGroup(context) {
19
+ var urls: Array<String>? = null
20
+ var items: List<Map<String, Any?>> = emptyList()
21
+ var theme: Theme = Theme.Dark
22
+ var initialIndex: Int = 0
23
+ set(value) {
24
+ field = value
25
+ bindSourceImageView()
26
+ }
27
+ var galleryId: String? = null
28
+ set(value) {
29
+ field = value
30
+ bindSourceImageView()
31
+ }
32
+ var disableHiddenOriginalImage = false
33
+ var edgeToEdge = false
34
+ var transitionOffsetY: Int? = null
35
+ var transitionOffsetX: Int? = 0
36
+ val onIndexChange by EventDispatcher()
37
+
38
+ private var sourceImageView: ImageView? = null
39
+ private var observedSourceImageView: ImageView? = null
40
+ private var sourceImageLayoutListener: View.OnLayoutChangeListener? = null
41
+ private var sourceImageAttachListener: View.OnAttachStateChangeListener? = null
42
+ private var overlayView: GalleryViewerOverlayView? = null
43
+ private var overlayBackCallback: OnBackPressedCallback? = null
44
+
45
+ override fun onAttachedToWindow() {
46
+ super.onAttachedToWindow()
47
+ bindSourceImageView()
48
+ }
49
+
50
+ override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
51
+ for (i in 0 until childCount) {
52
+ val child = getChildAt(i)
53
+ child.layout(0, 0, width, height)
54
+ }
55
+ bindSourceImageView()
56
+ }
57
+
58
+ override fun onDetachedFromWindow() {
59
+ overlayView?.dismissImmediately()
60
+ overlayView = null
61
+ overlayBackCallback?.remove()
62
+ overlayBackCallback = null
63
+ super.onDetachedFromWindow()
64
+ unregisterSourceImageView()
65
+ }
66
+
67
+ private fun scopedGalleryId(): String? = galleryId?.takeIf { it.isNotBlank() }
68
+
69
+ private fun transitionNameForIndex(index: Int): String {
70
+ return GallerySharedElementNames.forIndex(scopedGalleryId(), index)
71
+ }
72
+
73
+ private fun transitionNameForItem(item: GalleryMediaItem): String {
74
+ return GallerySharedElementNames.forItem(scopedGalleryId(), item)
75
+ }
76
+
77
+ private fun bindSourceImageView() {
78
+ val imageView = findImageView(this)
79
+ if (imageView == null) {
80
+ unregisterSourceImageView()
81
+ return
82
+ }
83
+
84
+ val previousSourceImageView = sourceImageView
85
+ if (previousSourceImageView != null && previousSourceImageView !== imageView) {
86
+ clearSourceImageObserver()
87
+ if (!isSourceImageViewUsable(previousSourceImageView)) {
88
+ GallerySourceViewRegistry.unregisterView(previousSourceImageView)
89
+ }
90
+ }
91
+
92
+ sourceImageView = imageView
93
+ imageView.setOnClickListener {
94
+ openViewer(imageView)
95
+ }
96
+ registerSourceImageViewWhenReady(imageView)
97
+ }
98
+
99
+ private fun openViewer(sourceView: ImageView) {
100
+ val mediaItems = buildMediaItems()
101
+ if (mediaItems.isEmpty()) {
102
+ return
103
+ }
104
+ if (overlayView != null) {
105
+ return
106
+ }
107
+ val safeIndex = initialIndex.coerceIn(0, mediaItems.lastIndex)
108
+ val activity = getActivity(context) ?: return
109
+ val overlayHost = activity.window.decorView as? ViewGroup
110
+ ?: activity.findViewById<ViewGroup>(android.R.id.content)
111
+ ?: return
112
+ val sourceKey = transitionNameForItem(mediaItems[safeIndex])
113
+ GallerySourceViewRegistry.register(sourceKey, sourceView)
114
+ val overlay = GalleryViewerOverlayView(
115
+ activity,
116
+ mediaItems,
117
+ safeIndex,
118
+ theme == Theme.Light,
119
+ scopedGalleryId(),
120
+ object : GalleryViewerOverlayView.Listener {
121
+ override fun onOverlayDismissed() {
122
+ if (overlayView != null) {
123
+ overlayView = null
124
+ }
125
+ overlayBackCallback?.remove()
126
+ overlayBackCallback = null
127
+ }
128
+
129
+ override fun onOverlayIndexChange(index: Int) {
130
+ onIndexChange(mapOf("currentIndex" to index))
131
+ }
132
+ }
133
+ )
134
+ overlayView = overlay
135
+ overlayHost.addView(overlay)
136
+ if (activity is ComponentActivity) {
137
+ overlayBackCallback?.remove()
138
+ overlayBackCallback = object : OnBackPressedCallback(true) {
139
+ override fun handleOnBackPressed() {
140
+ overlayView?.requestClose()
141
+ }
142
+ }.also { callback ->
143
+ activity.onBackPressedDispatcher.addCallback(callback)
144
+ }
145
+ }
146
+ }
147
+
148
+ private fun buildMediaItems(): List<GalleryMediaItem> {
149
+ if (items.isNotEmpty()) {
150
+ return items.mapIndexedNotNull { index, data ->
151
+ val type = data["type"] as? String ?: return@mapIndexedNotNull null
152
+ val url = data["url"] as? String ?: return@mapIndexedNotNull null
153
+ if (type.lowercase() == "video") {
154
+ val poster = data["poster"] as? String ?: url
155
+ GalleryMediaItem(index.toString(), GalleryMediaItem.MediaType.VIDEO, url, poster)
156
+ } else {
157
+ GalleryMediaItem(index.toString(), GalleryMediaItem.MediaType.IMAGE, url, url)
158
+ }
159
+ }
160
+ }
161
+ val rawUrls = urls ?: return emptyList()
162
+ return rawUrls.mapIndexed { index, url ->
163
+ GalleryMediaItem(index.toString(), GalleryMediaItem.MediaType.IMAGE, url, url)
164
+ }
165
+ }
166
+
167
+ private fun findImageView(view: View): ImageView? {
168
+ var bestImageView: ImageView? = null
169
+ var bestScore = Int.MIN_VALUE
170
+
171
+ fun visit(candidate: View) {
172
+ if (candidate is ImageView) {
173
+ val score = sourceImageViewScore(candidate)
174
+ if (score > bestScore) {
175
+ bestImageView = candidate
176
+ bestScore = score
177
+ }
178
+ }
179
+ if (candidate is ViewGroup) {
180
+ for (i in 0 until candidate.childCount) {
181
+ visit(candidate.getChildAt(i))
182
+ }
183
+ }
184
+ }
185
+
186
+ visit(view)
187
+ return bestImageView
188
+ }
189
+
190
+ private fun registerSourceImageViewWhenReady(imageView: ImageView) {
191
+ val transitionName = transitionNameForIndex(initialIndex)
192
+ if (isSourceImageViewUsable(imageView)) {
193
+ clearSourceImageObserver(imageView)
194
+ GallerySourceViewRegistry.register(transitionName, imageView)
195
+ return
196
+ }
197
+
198
+ observeSourceImageView(imageView)
199
+ }
200
+
201
+ private fun observeSourceImageView(imageView: ImageView) {
202
+ if (observedSourceImageView === imageView && sourceImageLayoutListener != null) {
203
+ return
204
+ }
205
+
206
+ clearSourceImageObserver()
207
+ observedSourceImageView = imageView
208
+ val layoutListener = View.OnLayoutChangeListener { view, _, _, _, _, _, _, _, _ ->
209
+ val observed = view as? ImageView ?: return@OnLayoutChangeListener
210
+ if (sourceImageView !== observed) {
211
+ clearSourceImageObserver(observed)
212
+ return@OnLayoutChangeListener
213
+ }
214
+ if (isSourceImageViewUsable(observed)) {
215
+ registerSourceImageViewWhenReady(observed)
216
+ }
217
+ }
218
+ val attachListener = object : View.OnAttachStateChangeListener {
219
+ override fun onViewAttachedToWindow(view: View) {
220
+ val observed = view as? ImageView ?: return
221
+ if (sourceImageView !== observed) {
222
+ clearSourceImageObserver(observed)
223
+ return
224
+ }
225
+ registerSourceImageViewWhenReady(observed)
226
+ }
227
+
228
+ override fun onViewDetachedFromWindow(view: View) = Unit
229
+ }
230
+ sourceImageLayoutListener = layoutListener
231
+ sourceImageAttachListener = attachListener
232
+ imageView.addOnLayoutChangeListener(layoutListener)
233
+ imageView.addOnAttachStateChangeListener(attachListener)
234
+ imageView.post {
235
+ if (sourceImageView !== imageView) {
236
+ return@post
237
+ }
238
+ if (isSourceImageViewUsable(imageView)) {
239
+ registerSourceImageViewWhenReady(imageView)
240
+ }
241
+ }
242
+ }
243
+
244
+ private fun isSourceImageViewUsable(imageView: ImageView): Boolean {
245
+ return imageView.isAttachedToWindow && imageView.width > 0 && imageView.height > 0
246
+ }
247
+
248
+ private fun sourceImageViewScore(imageView: ImageView): Int {
249
+ var score = 0
250
+ if (imageView.isAttachedToWindow) {
251
+ score += 16
252
+ }
253
+ if (imageView.width > 0 && imageView.height > 0) {
254
+ score += 16
255
+ }
256
+ if (imageView.visibility == View.VISIBLE) {
257
+ score += 16
258
+ }
259
+ if (imageView.isShown) {
260
+ score += 16
261
+ }
262
+ if (imageView.drawable != null) {
263
+ score += 8
264
+ }
265
+ if (imageView === sourceImageView) {
266
+ score += 1
267
+ }
268
+ return score
269
+ }
270
+
271
+ private fun clearSourceImageObserver(imageView: ImageView? = null) {
272
+ val observed = observedSourceImageView ?: return
273
+ if (imageView != null && observed !== imageView) {
274
+ return
275
+ }
276
+ sourceImageLayoutListener?.let { listener ->
277
+ observed.removeOnLayoutChangeListener(listener)
278
+ }
279
+ sourceImageAttachListener?.let { listener ->
280
+ observed.removeOnAttachStateChangeListener(listener)
281
+ }
282
+ sourceImageLayoutListener = null
283
+ sourceImageAttachListener = null
284
+ observedSourceImageView = null
285
+ }
286
+
287
+ private fun getActivity(context: Context): Activity? {
288
+ var current = context
289
+ while (current is ContextWrapper) {
290
+ if (current is Activity) {
291
+ return current
292
+ }
293
+ current = current.baseContext
294
+ }
295
+ return null
296
+ }
297
+
298
+ private fun unregisterSourceImageView() {
299
+ clearSourceImageObserver()
300
+ sourceImageView?.let { imageView ->
301
+ GallerySourceViewRegistry.unregisterView(imageView)
302
+ }
303
+ sourceImageView = null
304
+ }
305
+ }