zkit-ui 2.0.4 → 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.
- 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/GaleriaModule.kt +43 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GaleriaView.kt +305 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryLayoutSupport.java +450 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryMediaItem.java +36 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryPagerAdapter.java +140 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySharedElementGeometry.java +25 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySharedElementNames.java +39 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySharedElementState.java +25 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GallerySourceViewRegistry.java +115 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryTransitionSnapshotView.java +60 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryVideoPlayerView.java +338 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryViewerOverlayView.java +636 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryViewerPageView.java +357 -0
- package/android/src/main/java/cn/fontree/zkit/ui/preview/GalleryViewerTransitionController.java +223 -0
- package/android/src/main/res/layout/galeria_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 +3 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts.map +1 -0
- package/dist/ui/NativeImagePreview/NativeImagePreview.web.js +56 -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 +66 -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 +82 -0
- package/expo-module.config.json +11 -0
- package/ios/GaleriaModule.swift +46 -0
- package/ios/GaleriaView.swift +253 -0
- package/ios/GalleryViewer/GaleriaImageLoader.swift +73 -0
- package/ios/GalleryViewer/GaleriaImageViewInteraction.swift +245 -0
- package/ios/GalleryViewer/GaleriaLayoutSupport.swift +143 -0
- package/ios/GalleryViewer/GaleriaMedia.swift +28 -0
- package/ios/GalleryViewer/GaleriaSourceViewRegistry.swift +80 -0
- package/ios/GalleryViewer/GaleriaVideoPlayerView.swift +437 -0
- package/ios/GalleryViewer/GaleriaViewerConfiguration.swift +51 -0
- package/ios/GalleryViewer/GaleriaViewerController.swift +745 -0
- package/ios/GalleryViewer/GaleriaViewerPageView.swift +623 -0
- package/ios/GalleryViewer/GaleriaViewerTransitionCoordinator.swift +312 -0
- package/package.json +20 -12
- package/src/all.ts +18 -0
- package/src/services/ImagePreviewService/README.md +19 -0
- package/src/ui/NativeImagePreview/NativeImagePreview.tsx +146 -0
- package/src/ui/NativeImagePreview/NativeImagePreview.web.tsx +37 -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 +92 -0
- package/src/ui/NativeImagePreview/utils.ts +107 -0
- 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
|
+
}
|