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.
Files changed (86) 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/NativeImagePreviewLayoutSupport.java +450 -0
  5. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewMediaItem.java +36 -0
  6. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewModule.kt +43 -0
  7. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewPagerAdapter.java +140 -0
  8. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSharedElementGeometry.java +25 -0
  9. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSharedElementNames.java +39 -0
  10. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSharedElementState.java +25 -0
  11. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewSourceViewRegistry.java +115 -0
  12. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewTransitionSnapshotView.java +60 -0
  13. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewVideoPlayerView.java +338 -0
  14. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewView.kt +305 -0
  15. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewViewerOverlayView.java +636 -0
  16. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewViewerPageView.java +357 -0
  17. package/android/src/main/java/cn/fontree/zkit/ui/preview/NativeImagePreviewViewerTransitionController.java +223 -0
  18. package/android/src/main/res/layout/native_image_preview_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 +4 -0
  26. package/dist/ui/NativeImagePreview/NativeImagePreview.web.d.ts.map +1 -0
  27. package/dist/ui/NativeImagePreview/NativeImagePreview.web.js +150 -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 +68 -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 +83 -0
  40. package/dist/ui/NativeImagePreview/web/geometry.d.ts +20 -0
  41. package/dist/ui/NativeImagePreview/web/geometry.d.ts.map +1 -0
  42. package/dist/ui/NativeImagePreview/web/geometry.js +121 -0
  43. package/dist/ui/NativeImagePreview/web/media.d.ts +6 -0
  44. package/dist/ui/NativeImagePreview/web/media.d.ts.map +1 -0
  45. package/dist/ui/NativeImagePreview/web/media.js +90 -0
  46. package/dist/ui/NativeImagePreview/web/styles.css +137 -0
  47. package/dist/ui/NativeImagePreview/web/transition.d.ts +15 -0
  48. package/dist/ui/NativeImagePreview/web/transition.d.ts.map +1 -0
  49. package/dist/ui/NativeImagePreview/web/transition.js +77 -0
  50. package/dist/ui/NativeImagePreview/web/types.d.ts +74 -0
  51. package/dist/ui/NativeImagePreview/web/types.d.ts.map +1 -0
  52. package/dist/ui/NativeImagePreview/web/types.js +3 -0
  53. package/dist/ui/NativeImagePreview/web/viewer.d.ts +104 -0
  54. package/dist/ui/NativeImagePreview/web/viewer.d.ts.map +1 -0
  55. package/dist/ui/NativeImagePreview/web/viewer.js +894 -0
  56. package/expo-module.config.json +11 -0
  57. package/ios/NativeImagePreview/NativeImagePreviewImageLoader.swift +73 -0
  58. package/ios/NativeImagePreview/NativeImagePreviewImageViewInteraction.swift +245 -0
  59. package/ios/NativeImagePreview/NativeImagePreviewLayoutSupport.swift +143 -0
  60. package/ios/NativeImagePreview/NativeImagePreviewMedia.swift +28 -0
  61. package/ios/NativeImagePreview/NativeImagePreviewSourceViewRegistry.swift +80 -0
  62. package/ios/NativeImagePreview/NativeImagePreviewVideoPlayerView.swift +437 -0
  63. package/ios/NativeImagePreview/NativeImagePreviewViewerConfiguration.swift +51 -0
  64. package/ios/NativeImagePreview/NativeImagePreviewViewerController.swift +745 -0
  65. package/ios/NativeImagePreview/NativeImagePreviewViewerPageView.swift +623 -0
  66. package/ios/NativeImagePreview/NativeImagePreviewViewerTransitionCoordinator.swift +312 -0
  67. package/ios/NativeImagePreviewModule.swift +46 -0
  68. package/ios/NativeImagePreviewView.swift +252 -0
  69. package/package.json +16 -2
  70. package/src/all.ts +18 -0
  71. package/src/services/ImagePreviewService/README.md +19 -0
  72. package/src/types.d.ts +1 -0
  73. package/src/ui/NativeImagePreview/NativeImagePreview.tsx +146 -0
  74. package/src/ui/NativeImagePreview/NativeImagePreview.web.tsx +201 -0
  75. package/src/ui/NativeImagePreview/README.md +111 -0
  76. package/src/ui/NativeImagePreview/context.tsx +22 -0
  77. package/src/ui/NativeImagePreview/index.tsx +18 -0
  78. package/src/ui/NativeImagePreview/types.ts +94 -0
  79. package/src/ui/NativeImagePreview/utils.ts +108 -0
  80. package/src/ui/NativeImagePreview/web/geometry.ts +134 -0
  81. package/src/ui/NativeImagePreview/web/media.ts +91 -0
  82. package/src/ui/NativeImagePreview/web/styles.css +137 -0
  83. package/src/ui/NativeImagePreview/web/transition.ts +111 -0
  84. package/src/ui/NativeImagePreview/web/types.ts +88 -0
  85. package/src/ui/NativeImagePreview/web/viewer.ts +1024 -0
  86. package/zkit-ui.podspec +13 -1
@@ -0,0 +1,636 @@
1
+ package cn.fontree.zkit.ui.preview;
2
+
3
+ import android.content.Context;
4
+ import android.graphics.RectF;
5
+ import android.graphics.drawable.Drawable;
6
+ import android.view.KeyEvent;
7
+ import android.view.MotionEvent;
8
+ import android.view.View;
9
+ import android.view.ViewConfiguration;
10
+ import android.view.ViewGroup;
11
+ import android.view.VelocityTracker;
12
+ import android.view.ViewParent;
13
+ import android.widget.FrameLayout;
14
+ import android.widget.ImageView;
15
+
16
+ import androidx.annotation.NonNull;
17
+ import androidx.annotation.Nullable;
18
+ import androidx.viewpager2.widget.ViewPager2;
19
+
20
+ import java.util.ArrayList;
21
+ import java.util.List;
22
+
23
+ public final class NativeImagePreviewViewerOverlayView extends FrameLayout implements NativeImagePreviewViewerPageView.Listener {
24
+ public interface Listener {
25
+ void onOverlayDismissed();
26
+
27
+ void onOverlayIndexChange(int index);
28
+ }
29
+
30
+ private final List<NativeImagePreviewMediaItem> items;
31
+ @Nullable
32
+ private final String previewGroupId;
33
+ @Nullable
34
+ private final Listener listener;
35
+ private final boolean lightTheme;
36
+ private final int touchSlop;
37
+ private final int minFlingVelocity;
38
+
39
+ private View backgroundView;
40
+ private FrameLayout contentView;
41
+ private ViewPager2 viewPager;
42
+ private NativeImagePreviewPagerAdapter pagerAdapter;
43
+ private NativeImagePreviewViewerTransitionController transitionController;
44
+ private int currentIndex;
45
+ private float downX;
46
+ private float downY;
47
+ private boolean dragging;
48
+ private boolean multiTouchLock;
49
+ private boolean needReanchorAfterMultiTouch;
50
+ private float dragStartRawY;
51
+ private float dragStartTranslationY;
52
+ @Nullable
53
+ private VelocityTracker velocityTracker;
54
+ @Nullable
55
+ private View dragTarget;
56
+ @Nullable
57
+ private NativeImagePreviewViewerPageView dragPageView;
58
+ @Nullable
59
+ private ImageView hiddenActiveSourceView;
60
+ private float hiddenActiveSourcePreviousAlpha = 1f;
61
+ private boolean closing;
62
+ private boolean openingTransitionStarted;
63
+ private boolean contentPresented;
64
+ private boolean finished;
65
+
66
+ private final Runnable openTransitionReadyWatcher = new Runnable() {
67
+ @Override
68
+ public void run() {
69
+ if (openingTransitionStarted || closing || finished) {
70
+ return;
71
+ }
72
+ NativeImagePreviewViewerPageView pageView = resolveCurrentPageView();
73
+ if (pageView == null || getWidth() <= 0 || getHeight() <= 0) {
74
+ postOnAnimation(this);
75
+ return;
76
+ }
77
+ startOpenTransition(pageView, pageView.sharedElementState());
78
+ }
79
+ };
80
+
81
+ public NativeImagePreviewViewerOverlayView(
82
+ @NonNull Context context,
83
+ @NonNull List<NativeImagePreviewMediaItem> sourceItems,
84
+ int startIndex,
85
+ boolean lightTheme,
86
+ @Nullable String previewGroupId,
87
+ @Nullable Listener listener
88
+ ) {
89
+ super(context);
90
+ this.items = new ArrayList<>(sourceItems);
91
+ this.currentIndex = Math.max(0, Math.min(startIndex, items.size() - 1));
92
+ this.lightTheme = lightTheme;
93
+ this.previewGroupId = previewGroupId;
94
+ this.listener = listener;
95
+ ViewConfiguration viewConfiguration = ViewConfiguration.get(context);
96
+ touchSlop = viewConfiguration.getScaledTouchSlop();
97
+ minFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity();
98
+ setLayoutParams(new LayoutParams(
99
+ ViewGroup.LayoutParams.MATCH_PARENT,
100
+ ViewGroup.LayoutParams.MATCH_PARENT
101
+ ));
102
+ setClickable(true);
103
+ setFocusable(true);
104
+ setFocusableInTouchMode(true);
105
+ setupUi();
106
+ post(openTransitionReadyWatcher);
107
+ requestFocus();
108
+ }
109
+
110
+ public void requestClose() {
111
+ closeViewer();
112
+ }
113
+
114
+ public void dismissImmediately() {
115
+ finishWithoutAnimation();
116
+ }
117
+
118
+ @Override
119
+ protected void onDetachedFromWindow() {
120
+ if (!finished) {
121
+ cleanup();
122
+ }
123
+ super.onDetachedFromWindow();
124
+ }
125
+
126
+ @Override
127
+ public boolean dispatchKeyEvent(KeyEvent event) {
128
+ if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
129
+ requestClose();
130
+ return true;
131
+ }
132
+ return super.dispatchKeyEvent(event);
133
+ }
134
+
135
+ @Override
136
+ public boolean dispatchTouchEvent(MotionEvent event) {
137
+ if (closing || !contentPresented) {
138
+ super.dispatchTouchEvent(event);
139
+ return true;
140
+ }
141
+
142
+ int action = event.getActionMasked();
143
+ if (action == MotionEvent.ACTION_POINTER_DOWN) {
144
+ enterMultiTouchLock();
145
+ super.dispatchTouchEvent(event);
146
+ return true;
147
+ }
148
+ if (action == MotionEvent.ACTION_POINTER_UP) {
149
+ if (multiTouchLock && event.getPointerCount() - 1 <= 1) {
150
+ exitMultiTouchLockAndWaitReanchor();
151
+ }
152
+ super.dispatchTouchEvent(event);
153
+ return true;
154
+ }
155
+ if (multiTouchLock && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) {
156
+ clearMultiTouchLock();
157
+ super.dispatchTouchEvent(event);
158
+ return true;
159
+ }
160
+ if (multiTouchLock) {
161
+ super.dispatchTouchEvent(event);
162
+ return true;
163
+ }
164
+
165
+ switch (action) {
166
+ case MotionEvent.ACTION_DOWN:
167
+ if (needReanchorAfterMultiTouch) {
168
+ needReanchorAfterMultiTouch = false;
169
+ }
170
+ syncPagerGesturePolicy();
171
+ downX = event.getRawX();
172
+ downY = event.getRawY();
173
+ dragging = false;
174
+ dragTarget = null;
175
+ dragPageView = null;
176
+ ensureVelocityTracker();
177
+ velocityTracker.clear();
178
+ velocityTracker.addMovement(event);
179
+ break;
180
+ case MotionEvent.ACTION_MOVE:
181
+ if (needReanchorAfterMultiTouch) {
182
+ super.dispatchTouchEvent(event);
183
+ return true;
184
+ }
185
+ ensureVelocityTracker();
186
+ velocityTracker.addMovement(event);
187
+ if (!dragging) {
188
+ float dx = event.getRawX() - downX;
189
+ float dy = event.getRawY() - downY;
190
+ NativeImagePreviewViewerPageView pageView = resolveCurrentPageView();
191
+ if (pageView != null
192
+ && Math.abs(dy) > touchSlop
193
+ && pageView.canBeginVerticalDismiss(dx, dy, event.getRawX(), event.getRawY())) {
194
+ startDragging(pageView, event.getRawY());
195
+ }
196
+ }
197
+ if (dragging) {
198
+ applyDragTransform(event.getRawY());
199
+ return true;
200
+ }
201
+ syncPagerGesturePolicy();
202
+ break;
203
+ case MotionEvent.ACTION_UP:
204
+ case MotionEvent.ACTION_CANCEL:
205
+ if (needReanchorAfterMultiTouch) {
206
+ if (action == MotionEvent.ACTION_CANCEL) {
207
+ needReanchorAfterMultiTouch = false;
208
+ syncPagerGesturePolicy();
209
+ }
210
+ releaseVelocityTracker();
211
+ super.dispatchTouchEvent(event);
212
+ return true;
213
+ }
214
+ if (dragging) {
215
+ ensureVelocityTracker();
216
+ velocityTracker.addMovement(event);
217
+ float velocityY = consumeYVelocity();
218
+ View target = getActiveDragTarget();
219
+ float height = Math.max(1f, getHeight());
220
+ float dismissDistance = height * 0.16f;
221
+ float quickFlingDistance = height * 0.06f;
222
+ float dismissVelocity = Math.max(1300f, minFlingVelocity * 1.6f);
223
+ boolean dismissByDistance = Math.abs(target.getTranslationY()) > dismissDistance;
224
+ boolean dismissByFling = Math.abs(target.getTranslationY()) > quickFlingDistance
225
+ && Math.abs(velocityY) > dismissVelocity;
226
+ dragging = false;
227
+ syncPagerGesturePolicy();
228
+ releaseVelocityTracker();
229
+ if (dismissByDistance || dismissByFling) {
230
+ closeViewer();
231
+ } else {
232
+ animateBackToCenter();
233
+ }
234
+ return true;
235
+ }
236
+ releaseVelocityTracker();
237
+ break;
238
+ default:
239
+ break;
240
+ }
241
+
242
+ super.dispatchTouchEvent(event);
243
+ return true;
244
+ }
245
+
246
+ @Override
247
+ public void onDismissRequested(@NonNull NativeImagePreviewViewerPageView pageView) {
248
+ if (pageView == resolveCurrentPageView()) {
249
+ closeViewer();
250
+ }
251
+ }
252
+
253
+ @Override
254
+ public void onVideoCloseRequested(@NonNull NativeImagePreviewViewerPageView pageView) {
255
+ if (pageView == resolveCurrentPageView()) {
256
+ closeViewer();
257
+ }
258
+ }
259
+
260
+ private void setupUi() {
261
+ backgroundView = new View(getContext());
262
+ backgroundView.setLayoutParams(new FrameLayout.LayoutParams(
263
+ ViewGroup.LayoutParams.MATCH_PARENT,
264
+ ViewGroup.LayoutParams.MATCH_PARENT
265
+ ));
266
+ backgroundView.setBackgroundColor(lightTheme ? 0xFFFFFFFF : 0xFF000000);
267
+ backgroundView.setAlpha(0f);
268
+ addView(backgroundView);
269
+
270
+ contentView = new FrameLayout(getContext());
271
+ contentView.setLayoutParams(new FrameLayout.LayoutParams(
272
+ ViewGroup.LayoutParams.MATCH_PARENT,
273
+ ViewGroup.LayoutParams.MATCH_PARENT
274
+ ));
275
+ contentView.setAlpha(0f);
276
+
277
+ viewPager = new ViewPager2(getContext());
278
+ viewPager.setLayoutParams(new FrameLayout.LayoutParams(
279
+ ViewGroup.LayoutParams.MATCH_PARENT,
280
+ ViewGroup.LayoutParams.MATCH_PARENT
281
+ ));
282
+ viewPager.setOffscreenPageLimit(1);
283
+ viewPager.setUserInputEnabled(false);
284
+ pagerAdapter = new NativeImagePreviewPagerAdapter(this, this::applySourcePlaceholderIfNeeded);
285
+ viewPager.setAdapter(pagerAdapter);
286
+ pagerAdapter.setItems(items);
287
+ viewPager.setCurrentItem(currentIndex, false);
288
+ viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
289
+ @Override
290
+ public void onPageSelected(int position) {
291
+ currentIndex = position;
292
+ if (contentPresented) {
293
+ pagerAdapter.setActiveIndex(position);
294
+ syncPagerGesturePolicy();
295
+ hideActiveSourceViewForCurrentIndex();
296
+ if (listener != null) {
297
+ listener.onOverlayIndexChange(position);
298
+ }
299
+ }
300
+ }
301
+ });
302
+ contentView.addView(viewPager);
303
+ addView(contentView);
304
+
305
+ transitionController = new NativeImagePreviewViewerTransitionController(this, backgroundView, contentView);
306
+ }
307
+
308
+ private void startOpenTransition(
309
+ @NonNull NativeImagePreviewViewerPageView pageView,
310
+ @Nullable NativeImagePreviewSharedElementState resolvedPageState
311
+ ) {
312
+ if (openingTransitionStarted) {
313
+ return;
314
+ }
315
+ openingTransitionStarted = true;
316
+ NativeImagePreviewMediaItem item = items.get(currentIndex);
317
+ String anchorKey = NativeImagePreviewSharedElementNames.forItem(previewGroupId, item);
318
+ ImageView sourceView = NativeImagePreviewSourceViewRegistry.find(anchorKey);
319
+ NativeImagePreviewSharedElementState sourceState = NativeImagePreviewLayoutSupport.captureImageViewState(sourceView);
320
+ applySourcePlaceholderIfNeeded(pageView, currentIndex);
321
+ pageView.prepareForOpenTransition();
322
+ pageView.setMediaHidden(true);
323
+
324
+ Drawable fallbackDrawable = sourceState != null ? sourceState.getDrawable() : null;
325
+ RectF overlayBounds = NativeImagePreviewLayoutSupport.viewBoundsOnScreen(this);
326
+ NativeImagePreviewSharedElementState effectivePageState = usableSharedElementState(resolvedPageState)
327
+ ? resolvedPageState
328
+ : pageView.sharedElementState();
329
+ NativeImagePreviewSharedElementGeometry targetGeometry = usableSharedElementState(effectivePageState)
330
+ ? effectivePageState.getGeometry()
331
+ : defaultOpenGeometryFromSource(sourceState, overlayBounds, pageView, fallbackDrawable);
332
+
333
+ transitionController.performOpenTransition(
334
+ sourceView,
335
+ sourceState,
336
+ targetGeometry,
337
+ () -> finishOpenTransition(pageView)
338
+ );
339
+ }
340
+
341
+ private void finishOpenTransition(@NonNull NativeImagePreviewViewerPageView pageView) {
342
+ if (contentPresented) {
343
+ return;
344
+ }
345
+ pageView.setMediaHidden(false);
346
+ transitionController.completeOpenTransition(() -> {
347
+ contentPresented = true;
348
+ pagerAdapter.setActiveIndex(currentIndex);
349
+ syncPagerGesturePolicy();
350
+ hideActiveSourceViewForCurrentIndex();
351
+ if (listener != null) {
352
+ listener.onOverlayIndexChange(currentIndex);
353
+ }
354
+ });
355
+ }
356
+
357
+ private void closeViewer() {
358
+ if (closing || finished) {
359
+ return;
360
+ }
361
+ syncCurrentIndexFromPager();
362
+ closing = true;
363
+ dragging = false;
364
+ viewPager.setUserInputEnabled(false);
365
+ viewPager.requestDisallowInterceptTouchEvent(false);
366
+ releaseVelocityTracker();
367
+
368
+ NativeImagePreviewViewerPageView pageView = resolveCurrentPageView();
369
+ if (pageView == null) {
370
+ finishWithoutAnimation();
371
+ return;
372
+ }
373
+
374
+ pageView.prepareForReturnTransition();
375
+ NativeImagePreviewSharedElementState pageState = pageView.sharedElementState();
376
+ pageView.setMediaHidden(true);
377
+
378
+ NativeImagePreviewMediaItem item = items.get(currentIndex);
379
+ String anchorKey = NativeImagePreviewSharedElementNames.forItem(previewGroupId, item);
380
+ ImageView targetView = NativeImagePreviewSourceViewRegistry.find(anchorKey);
381
+ NativeImagePreviewSharedElementState targetState = NativeImagePreviewLayoutSupport.captureImageViewState(targetView);
382
+ NativeImagePreviewSharedElementGeometry targetGeometry = targetState != null
383
+ ? targetState.getGeometry()
384
+ : NativeImagePreviewLayoutSupport.captureImageViewGeometry(
385
+ targetView,
386
+ pageState != null ? pageState.getDrawable() : null
387
+ );
388
+
389
+ transitionController.performCloseTransition(
390
+ pageState,
391
+ targetGeometry,
392
+ targetView,
393
+ () -> {
394
+ restoreHiddenActiveSourceView();
395
+ finishWithoutAnimation();
396
+ }
397
+ );
398
+ }
399
+
400
+ @Nullable
401
+ private NativeImagePreviewViewerPageView resolveCurrentPageView() {
402
+ NativeImagePreviewViewerPageView pageView = pagerAdapter.findAttachedView(currentIndex);
403
+ if (pageView != null) {
404
+ return pageView;
405
+ }
406
+ return pagerAdapter.findActiveView();
407
+ }
408
+
409
+ @NonNull
410
+ private NativeImagePreviewSharedElementGeometry defaultOpenGeometryFromSource(
411
+ @Nullable NativeImagePreviewSharedElementState sourceState,
412
+ @NonNull RectF overlayBounds,
413
+ @NonNull NativeImagePreviewViewerPageView pageView,
414
+ @Nullable Drawable fallbackDrawable
415
+ ) {
416
+ if (sourceState != null) {
417
+ RectF sourceContent = sourceState.getGeometry().getContentFrameInVisibleBounds();
418
+ if (sourceContent.width() > 1f && sourceContent.height() > 1f) {
419
+ return NativeImagePreviewLayoutSupport.defaultGeometryForAspectRatio(
420
+ sourceContent.width(),
421
+ sourceContent.height(),
422
+ overlayBounds
423
+ );
424
+ }
425
+ }
426
+ return pageView.defaultTransitionGeometry(overlayBounds, fallbackDrawable);
427
+ }
428
+
429
+ private boolean usableSharedElementState(@Nullable NativeImagePreviewSharedElementState state) {
430
+ if (state == null) {
431
+ return false;
432
+ }
433
+ RectF visibleFrame = state.getGeometry().getVisibleFrameInWindow();
434
+ RectF contentFrame = state.getGeometry().getContentFrameInVisibleBounds();
435
+ return visibleFrame.width() > 1f
436
+ && visibleFrame.height() > 1f
437
+ && contentFrame.width() > 1f
438
+ && contentFrame.height() > 1f;
439
+ }
440
+
441
+ private void syncCurrentIndexFromPager() {
442
+ if (viewPager == null || items.isEmpty()) {
443
+ return;
444
+ }
445
+ int pagerIndex = viewPager.getCurrentItem();
446
+ currentIndex = Math.max(0, Math.min(pagerIndex, items.size() - 1));
447
+ }
448
+
449
+ private void startDragging(@NonNull NativeImagePreviewViewerPageView pageView, float rawY) {
450
+ dragging = true;
451
+ dragPageView = pageView;
452
+ dragPageView.prepareForDismissDrag();
453
+ dragTarget = pageView.getDragTarget();
454
+ dragTarget.animate().cancel();
455
+ backgroundView.animate().cancel();
456
+ dragStartRawY = rawY;
457
+ dragStartTranslationY = dragTarget.getTranslationY();
458
+ viewPager.setUserInputEnabled(false);
459
+ viewPager.requestDisallowInterceptTouchEvent(true);
460
+ }
461
+
462
+ private void enterMultiTouchLock() {
463
+ multiTouchLock = true;
464
+ needReanchorAfterMultiTouch = false;
465
+ if (dragging) {
466
+ dragging = false;
467
+ animateBackToCenter();
468
+ }
469
+ dragTarget = null;
470
+ releaseVelocityTracker();
471
+ viewPager.setUserInputEnabled(false);
472
+ viewPager.requestDisallowInterceptTouchEvent(true);
473
+ }
474
+
475
+ private void exitMultiTouchLockAndWaitReanchor() {
476
+ multiTouchLock = false;
477
+ needReanchorAfterMultiTouch = true;
478
+ dragging = false;
479
+ dragTarget = null;
480
+ releaseVelocityTracker();
481
+ viewPager.setUserInputEnabled(false);
482
+ viewPager.requestDisallowInterceptTouchEvent(true);
483
+ }
484
+
485
+ private void clearMultiTouchLock() {
486
+ multiTouchLock = false;
487
+ needReanchorAfterMultiTouch = false;
488
+ dragging = false;
489
+ dragTarget = null;
490
+ dragPageView = null;
491
+ releaseVelocityTracker();
492
+ syncPagerGesturePolicy();
493
+ }
494
+
495
+ private void syncPagerGesturePolicy() {
496
+ NativeImagePreviewViewerPageView pageView = resolveCurrentPageView();
497
+ boolean canPage = items.size() > 1 && pageView != null && pageView.canPageHorizontally();
498
+ viewPager.setUserInputEnabled(canPage);
499
+ viewPager.requestDisallowInterceptTouchEvent(!canPage);
500
+ if (pageView != null) {
501
+ pageView.setAllowParentInterceptOnImageEdge(canPage);
502
+ }
503
+ }
504
+
505
+ @NonNull
506
+ private View getActiveDragTarget() {
507
+ if (dragTarget != null) {
508
+ return dragTarget;
509
+ }
510
+ return viewPager;
511
+ }
512
+
513
+ private void applyDragTransform(float rawY) {
514
+ View target = getActiveDragTarget();
515
+ float dy = dragStartTranslationY + rawY - dragStartRawY;
516
+ float abs = Math.abs(dy);
517
+ float height = Math.max(1, getHeight());
518
+ float progress = Math.min(1f, abs / height);
519
+ float scale = Math.max(0.84f, 1f - progress * 0.16f);
520
+ target.setTranslationY(dy);
521
+ target.setScaleX(scale);
522
+ target.setScaleY(scale);
523
+ backgroundView.setAlpha(1f - Math.min(0.9f, progress * 1.25f));
524
+ }
525
+
526
+ private void animateBackToCenter() {
527
+ View target = getActiveDragTarget();
528
+ NativeImagePreviewViewerPageView restorePageView = dragPageView;
529
+ dragPageView = null;
530
+ target.animate()
531
+ .translationY(0f)
532
+ .scaleX(1f)
533
+ .scaleY(1f)
534
+ .setDuration(200L)
535
+ .withEndAction(() -> {
536
+ backgroundView.setAlpha(1f);
537
+ if (restorePageView != null) {
538
+ restorePageView.restoreAfterDismissCancelled();
539
+ }
540
+ })
541
+ .start();
542
+ backgroundView.animate()
543
+ .alpha(1f)
544
+ .setDuration(200L)
545
+ .start();
546
+ }
547
+
548
+ @Nullable
549
+ private ImageView sourceViewForIndex(int index) {
550
+ if (index < 0 || index >= items.size()) {
551
+ return null;
552
+ }
553
+ NativeImagePreviewMediaItem item = items.get(index);
554
+ String anchorKey = NativeImagePreviewSharedElementNames.forItem(previewGroupId, item);
555
+ return NativeImagePreviewSourceViewRegistry.find(anchorKey);
556
+ }
557
+
558
+ private void applySourcePlaceholderIfNeeded(@NonNull NativeImagePreviewViewerPageView pageView, int index) {
559
+ ImageView sourceView = sourceViewForIndex(index);
560
+ if (sourceView == null) {
561
+ return;
562
+ }
563
+ Drawable sourceDrawable = NativeImagePreviewLayoutSupport.transitionDrawableForImageView(sourceView);
564
+ pageView.setImagePlaceholderIfEmpty(sourceDrawable);
565
+ }
566
+
567
+ private void hideActiveSourceViewForCurrentIndex() {
568
+ if (!contentPresented || closing) {
569
+ return;
570
+ }
571
+ ImageView sourceView = sourceViewForIndex(currentIndex);
572
+ if (hiddenActiveSourceView != sourceView) {
573
+ restoreHiddenActiveSourceView();
574
+ hiddenActiveSourcePreviousAlpha = sourceView != null ? sourceView.getAlpha() : 1f;
575
+ hiddenActiveSourceView = sourceView;
576
+ }
577
+ if (sourceView != null) {
578
+ sourceView.setAlpha(0f);
579
+ }
580
+ }
581
+
582
+ private void restoreHiddenActiveSourceView() {
583
+ if (hiddenActiveSourceView != null) {
584
+ hiddenActiveSourceView.setAlpha(hiddenActiveSourcePreviousAlpha);
585
+ }
586
+ hiddenActiveSourceView = null;
587
+ hiddenActiveSourcePreviousAlpha = 1f;
588
+ }
589
+
590
+ private void ensureVelocityTracker() {
591
+ if (velocityTracker == null) {
592
+ velocityTracker = VelocityTracker.obtain();
593
+ }
594
+ }
595
+
596
+ private float consumeYVelocity() {
597
+ if (velocityTracker == null) {
598
+ return 0f;
599
+ }
600
+ velocityTracker.computeCurrentVelocity(1000);
601
+ return velocityTracker.getYVelocity();
602
+ }
603
+
604
+ private void releaseVelocityTracker() {
605
+ if (velocityTracker == null) {
606
+ return;
607
+ }
608
+ velocityTracker.recycle();
609
+ velocityTracker = null;
610
+ }
611
+
612
+ private void finishWithoutAnimation() {
613
+ if (finished) {
614
+ return;
615
+ }
616
+ finished = true;
617
+ closing = true;
618
+ cleanup();
619
+ ViewParent parent = getParent();
620
+ if (parent instanceof ViewGroup) {
621
+ ((ViewGroup) parent).removeView(this);
622
+ }
623
+ if (listener != null) {
624
+ listener.onOverlayDismissed();
625
+ }
626
+ }
627
+
628
+ private void cleanup() {
629
+ restoreHiddenActiveSourceView();
630
+ removeCallbacks(openTransitionReadyWatcher);
631
+ if (pagerAdapter != null) {
632
+ pagerAdapter.releaseAll();
633
+ }
634
+ releaseVelocityTracker();
635
+ }
636
+ }