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.
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 +20 -12
  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
@@ -0,0 +1,111 @@
1
+ # NativeImagePreview
2
+
3
+ `NativeImagePreview` 是强调原生共享转场体验的图片预览组件。它和普通 `ImagePreview` 服务分工不同:`ImagePreview` 适合命令式打开图片;`NativeImagePreview` 适合从真实缩略图自然过渡到全屏 viewer,并在 iOS / Android 上走原生手势、缩放、滑动和拖拽关闭路径。
4
+
5
+ ## 适用场景
6
+
7
+ - 相册九宫格、瀑布流、商品图集这类有真实缩略图锚点的页面。
8
+ - 需要从缩略图拾取到全屏 viewer,再从 viewer 顺滑回到原缩略图的位置。
9
+ - 需要原生 pinch / pan / swipe / drag-to-close 手势链路,而不是普通 Modal 图片预览。
10
+
11
+ 如果只是从富文本、消息流或按钮命令式打开图片,用 `ImagePreview` 或 `imagePreview.open()` 更合适。
12
+
13
+ ## 基础用法
14
+
15
+ ```tsx
16
+ import { Image, type ImageSourcePropType } from 'react-native';
17
+ import { NativeImagePreview, type NativeImagePreviewItemDescriptor } from 'zkit-ui/native-image-preview';
18
+
19
+ const items: NativeImagePreviewItemDescriptor[] = [
20
+ { id: 'photo-a', source: require('./photo-a.png') },
21
+ { id: 'photo-b', source: require('./photo-b.png'), width: 1200, height: 900 },
22
+ { id: 'clip', type: 'video', url: videoUrl, poster: require('./poster.png') },
23
+ ];
24
+
25
+ function getThumbnailSource(item: NativeImagePreviewItemDescriptor): ImageSourcePropType | undefined {
26
+ const source = item.poster ?? item.source;
27
+ return typeof source === 'string' ? { uri: source } : source;
28
+ }
29
+
30
+ export function Demo() {
31
+ return (
32
+ <NativeImagePreview items={items} colorScheme="dark">
33
+ {items.map((item, index) => {
34
+ const thumbnailSource = getThumbnailSource(item);
35
+ if (!thumbnailSource) return null;
36
+
37
+ return (
38
+ <NativeImagePreview.Item index={index} key={item.id ?? index}>
39
+ <Image source={thumbnailSource} />
40
+ </NativeImagePreview.Item>
41
+ );
42
+ })}
43
+ </NativeImagePreview>
44
+ );
45
+ }
46
+ ```
47
+
48
+ `NativeImagePreview.Item` 必须包住真实图片节点,原生层会从内部找到 `UIImageView` / `ImageView` 作为转场锚点。不要用普通 `View` 替代缩略图,否则共享转场会退化。
49
+
50
+ ## 推荐数据模型
51
+
52
+ ```ts
53
+ type NativeImagePreviewItem =
54
+ | string
55
+ | ImageSourcePropType
56
+ | {
57
+ id?: string;
58
+ type?: 'image' | 'video';
59
+ source?: string | ImageSourcePropType;
60
+ url?: string;
61
+ poster?: string | ImageSourcePropType;
62
+ width?: number;
63
+ height?: number;
64
+ };
65
+ ```
66
+
67
+ - 图片可以只传 `source`,也可以用字符串 URL。
68
+ - 视频必须传 `type: 'video'` 和 `url`,`poster` 用于缩略图和过渡封面。
69
+ - `id` 用于稳定媒体身份;不传时会回退为索引字符串。
70
+ - `width / height` 是媒体元信息,帮助 viewer 更稳定地计算布局。
71
+
72
+ ## Props
73
+
74
+ ### NativeImagePreview
75
+
76
+ - `items`:图片或媒体集合,顺序必须和 `NativeImagePreview.Item index` 对齐。
77
+ - `colorScheme`:viewer 明暗外观,支持 `dark / light / system`,默认 `dark`。
78
+ - `disabled`:禁用所有锚点预览。
79
+ - `edgeToEdge`:Android edge-to-edge 预览,默认关闭;系统已处于 edge-to-edge 时会自动启用。
80
+ - `nativeProps.iosCloseIconName`:iOS 关闭按钮 SF Symbol 名称。
81
+ - `onChange(value, meta)`:viewer 滑动到新索引时触发。
82
+
83
+ ### NativeImagePreview.Item
84
+
85
+ - `index`:当前缩略图在 `items` 中的索引。
86
+ - `disabled`:禁用当前锚点。
87
+ - `style`:锚点容器样式,常用 `StyleSheet.absoluteFill` 覆盖图片卡片。
88
+ - `onChange(value, meta)`:仅当前锚点打开的 viewer 发生索引变化时触发。
89
+
90
+ ## 与 ImagePreview 的区别
91
+
92
+ ```tsx
93
+ import { imagePreview } from 'zkit-ui/image-preview';
94
+
95
+ imagePreview.open({ images, index: 0 });
96
+ ```
97
+
98
+ `ImagePreview` 是普通全屏预览服务,适合没有稳定缩略图锚点的命令式场景。`NativeImagePreview` 是声明式共享转场组件,必须就地包住真实图片节点。两者不要混成同一个封装,否则很容易把原生共享转场退化成普通弹层。
99
+
100
+ ## 平台行为
101
+
102
+ - iOS / Android:使用原生 viewer 和真实图片锚点,主打共享转场、缩放、滑动和拖拽关闭。
103
+ - 首次接入或变更原生依赖后需要重新构建 native app;仅刷新 JS bundle 不会注册新的原生 ViewManager。
104
+ - 原生模块未注册时不会降级到 `ImagePreview`。这是 native 接入或构建错误,需要重新安装依赖并重建 native app。
105
+ - Web:仅保留子节点渲染,不提供预览能力,也不会模拟普通 `ImagePreview`。
106
+
107
+ ## 手动确认点
108
+
109
+ - iOS:缩略图拾取和回落位置、快速左右滑、视频封面、横竖屏、安全区、关闭按钮。
110
+ - Android:edge-to-edge、返回键、快速滑动、列表复用、视频页、普通机型掉帧情况。
111
+ - 列表场景:确保 `items` 顺序、`Item index` 和缩略图渲染顺序稳定一致。
@@ -0,0 +1,22 @@
1
+ import * as React from 'react';
2
+ import type {
3
+ NativeImagePreviewChangeMeta,
4
+ NativeImagePreviewNativeItem,
5
+ NativeImagePreviewNativeProps,
6
+ NativeImagePreviewResolvedItem,
7
+ NativeImagePreviewResolvedColorScheme,
8
+ } from './types';
9
+
10
+ export type NativeImagePreviewContextValue = {
11
+ galleryId: string;
12
+ items: readonly NativeImagePreviewResolvedItem[];
13
+ nativeItems: readonly NativeImagePreviewNativeItem[];
14
+ colorScheme: NativeImagePreviewResolvedColorScheme;
15
+ disabled: boolean;
16
+ edgeToEdge: boolean;
17
+ nativeProps?: NativeImagePreviewNativeProps;
18
+ onChange?: (value: number, meta: NativeImagePreviewChangeMeta) => void;
19
+ };
20
+
21
+ export const NativeImagePreviewContext =
22
+ React.createContext<NativeImagePreviewContextValue | null>(null);
@@ -0,0 +1,18 @@
1
+ export { NativeImagePreview } from './NativeImagePreview';
2
+ export type {
3
+ NativeImagePreviewChangeEvent,
4
+ NativeImagePreviewChangeEventPayload,
5
+ NativeImagePreviewChangeMeta,
6
+ NativeImagePreviewChangeReason,
7
+ NativeImagePreviewColorScheme,
8
+ NativeImagePreviewComponent,
9
+ NativeImagePreviewItem,
10
+ NativeImagePreviewItemDescriptor,
11
+ NativeImagePreviewItemProps,
12
+ NativeImagePreviewMediaType,
13
+ NativeImagePreviewNativeProps,
14
+ NativeImagePreviewProps,
15
+ NativeImagePreviewResolvedItem,
16
+ NativeImagePreviewResolvedColorScheme,
17
+ NativeImagePreviewSource,
18
+ } from './types';
@@ -0,0 +1,92 @@
1
+ import type * as React from 'react';
2
+ import type {
3
+ ImageSourcePropType,
4
+ NativeSyntheticEvent,
5
+ StyleProp,
6
+ ViewStyle,
7
+ } from 'react-native';
8
+
9
+ export type NativeImagePreviewResolvedColorScheme = 'dark' | 'light';
10
+ export type NativeImagePreviewColorScheme =
11
+ | NativeImagePreviewResolvedColorScheme
12
+ | 'system';
13
+ export type NativeImagePreviewMediaType = 'image' | 'video';
14
+ export type NativeImagePreviewSource = ImageSourcePropType | string;
15
+
16
+ export type NativeImagePreviewItemDescriptor = {
17
+ id?: string;
18
+ type?: NativeImagePreviewMediaType;
19
+ url?: string;
20
+ source?: NativeImagePreviewSource;
21
+ poster?: NativeImagePreviewSource;
22
+ width?: number;
23
+ height?: number;
24
+ };
25
+
26
+ export type NativeImagePreviewItem =
27
+ | NativeImagePreviewSource
28
+ | NativeImagePreviewItemDescriptor;
29
+
30
+ export type NativeImagePreviewResolvedItem = {
31
+ id: string;
32
+ type: NativeImagePreviewMediaType;
33
+ url: string;
34
+ poster?: string;
35
+ width?: number;
36
+ height?: number;
37
+ raw: NativeImagePreviewItem;
38
+ };
39
+
40
+ export type NativeImagePreviewNativeItem = {
41
+ id: string;
42
+ type: NativeImagePreviewMediaType;
43
+ url: string;
44
+ poster?: string;
45
+ width?: number;
46
+ height?: number;
47
+ };
48
+
49
+ export type NativeImagePreviewChangeReason = 'swipe';
50
+
51
+ export type NativeImagePreviewChangeEventPayload = {
52
+ currentIndex: number;
53
+ };
54
+
55
+ export type NativeImagePreviewChangeEvent =
56
+ NativeSyntheticEvent<NativeImagePreviewChangeEventPayload>;
57
+
58
+ export type NativeImagePreviewChangeMeta = {
59
+ reason: NativeImagePreviewChangeReason;
60
+ item: NativeImagePreviewResolvedItem | null;
61
+ nativeEvent?: NativeImagePreviewChangeEvent;
62
+ };
63
+
64
+ export type NativeImagePreviewNativeProps = {
65
+ iosCloseIconName?: string;
66
+ };
67
+
68
+ export type NativeImagePreviewProps = {
69
+ items: readonly NativeImagePreviewItem[];
70
+ children: React.ReactNode;
71
+ colorScheme?: NativeImagePreviewColorScheme;
72
+ disabled?: boolean;
73
+ edgeToEdge?: boolean;
74
+ nativeProps?: NativeImagePreviewNativeProps;
75
+ onChange?: (value: number, meta: NativeImagePreviewChangeMeta) => void;
76
+ };
77
+
78
+ export type NativeImagePreviewItemProps = {
79
+ index: number;
80
+ children: React.ReactElement;
81
+ disabled?: boolean;
82
+ style?: StyleProp<ViewStyle>;
83
+ onChange?: (value: number, meta: NativeImagePreviewChangeMeta) => void;
84
+ };
85
+
86
+ export type NativeImagePreviewComponent = React.MemoExoticComponent<
87
+ (props: NativeImagePreviewProps) => React.ReactElement
88
+ > & {
89
+ Item: React.MemoExoticComponent<
90
+ (props: NativeImagePreviewItemProps) => React.ReactElement
91
+ >;
92
+ };
@@ -0,0 +1,107 @@
1
+ import {
2
+ Image,
3
+ type ColorSchemeName,
4
+ type ImageSourcePropType,
5
+ } from 'react-native';
6
+ import type {
7
+ NativeImagePreviewColorScheme,
8
+ NativeImagePreviewItem,
9
+ NativeImagePreviewItemDescriptor,
10
+ NativeImagePreviewNativeItem,
11
+ NativeImagePreviewResolvedItem,
12
+ NativeImagePreviewResolvedColorScheme,
13
+ NativeImagePreviewSource,
14
+ } from './types';
15
+
16
+ function isDescriptor(
17
+ item: NativeImagePreviewItem
18
+ ): item is NativeImagePreviewItemDescriptor {
19
+ if (item == null || typeof item !== 'object' || Array.isArray(item)) return false;
20
+ return (
21
+ 'source' in item ||
22
+ 'url' in item ||
23
+ 'poster' in item ||
24
+ 'type' in item ||
25
+ 'id' in item
26
+ );
27
+ }
28
+
29
+ export function resolvePreviewSourceUri(
30
+ source: NativeImagePreviewSource | undefined
31
+ ) {
32
+ if (source == null) return undefined;
33
+ if (typeof source === 'string') return source;
34
+
35
+ const normalizedSource = Array.isArray(source) ? source[0] : source;
36
+ const resolved = Image.resolveAssetSource(normalizedSource as ImageSourcePropType);
37
+ return resolved?.uri;
38
+ }
39
+
40
+ function normalizePreviewItem(
41
+ item: NativeImagePreviewItem,
42
+ index: number
43
+ ): NativeImagePreviewResolvedItem | null {
44
+ if (isDescriptor(item)) {
45
+ const type = item.type ?? 'image';
46
+ const sourceUrl = resolvePreviewSourceUri(item.source);
47
+ const url = item.url ?? sourceUrl;
48
+ if (!url) return null;
49
+
50
+ const poster = resolvePreviewSourceUri(item.poster) ?? (type === 'video' ? sourceUrl : undefined);
51
+
52
+ return {
53
+ id: item.id ?? `${index}`,
54
+ type,
55
+ url,
56
+ poster,
57
+ width: item.width,
58
+ height: item.height,
59
+ raw: item,
60
+ };
61
+ }
62
+
63
+ const url = resolvePreviewSourceUri(item);
64
+ if (!url) return null;
65
+
66
+ return {
67
+ id: `${index}`,
68
+ type: 'image',
69
+ url,
70
+ raw: item,
71
+ };
72
+ }
73
+
74
+ export function normalizeNativeImagePreviewItems(
75
+ items: readonly NativeImagePreviewItem[]
76
+ ) {
77
+ return items
78
+ .map((item, index) => normalizePreviewItem(item, index))
79
+ .filter((item): item is NativeImagePreviewResolvedItem => item != null);
80
+ }
81
+
82
+ export function toNativePreviewItems(
83
+ items: readonly NativeImagePreviewResolvedItem[]
84
+ ): NativeImagePreviewNativeItem[] {
85
+ return items.map(({ id, type, url, poster, width, height }) => ({
86
+ id,
87
+ type,
88
+ url,
89
+ poster,
90
+ width,
91
+ height,
92
+ }));
93
+ }
94
+
95
+ export function clampPreviewIndex(index: number, length: number) {
96
+ if (!Number.isFinite(index)) return 0;
97
+ return Math.max(0, Math.min(Math.round(index), Math.max(0, length - 1)));
98
+ }
99
+
100
+ export function resolveNativeImagePreviewColorScheme(
101
+ colorScheme: NativeImagePreviewColorScheme | undefined,
102
+ systemColorScheme: ColorSchemeName
103
+ ): NativeImagePreviewResolvedColorScheme {
104
+ if (colorScheme === 'light' || colorScheme === 'dark') return colorScheme;
105
+ if (colorScheme === 'system') return systemColorScheme === 'light' ? 'light' : 'dark';
106
+ return 'dark';
107
+ }
package/zkit-ui.podspec CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'json'
2
2
 
3
3
  package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+ new_arch_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
5
+ new_arch_compiler_flags = '-DRCT_NEW_ARCH_ENABLED'
4
6
 
5
7
  Pod::Spec.new do |s|
6
8
  s.name = 'zkit-ui'
@@ -12,8 +14,18 @@ Pod::Spec.new do |s|
12
14
  s.platforms = { :ios => '15.1' }
13
15
  s.source = { :git => 'https://github.com/Concur-max/zkit.git', :tag => s.version.to_s }
14
16
  s.requires_arc = true
17
+ s.swift_version = '5.4'
18
+ s.static_framework = true
19
+ s.compiler_flags = new_arch_compiler_flags if new_arch_enabled
15
20
 
16
- s.source_files = 'ios/**/*.{h,m,mm}'
21
+ s.source_files = 'ios/**/*.{h,m,mm,swift}'
22
+ s.pod_target_xcconfig = {
23
+ 'DEFINES_MODULE' => 'YES',
24
+ 'SWIFT_COMPILATION_MODE' => 'wholemodule',
25
+ 'OTHER_SWIFT_FLAGS' => "$(inherited) #{new_arch_enabled ? new_arch_compiler_flags : ''}"
26
+ }
17
27
 
28
+ s.dependency 'ExpoModulesCore'
18
29
  s.dependency 'React-Core'
30
+ s.dependency 'SDWebImage'
19
31
  end