diff --git a/docs/docs/.vitepress/config.mts b/docs/docs/.vitepress/config.mts
index 8f19a7f..f532fef 100644
--- a/docs/docs/.vitepress/config.mts
+++ b/docs/docs/.vitepress/config.mts
@@ -14,7 +14,7 @@ export default defineConfig({
nav: [
{ text: 'Home', link: '/' },
{
- text: '3.1.0',
+ text: '4.0.0',
items: [
{
text: 'Releases',
@@ -46,12 +46,12 @@ export default defineConfig({
text: 'Utilities',
collapsed: true,
items: [
+ { text: 'fitContainer', link: '/utilities/fitContainer' },
{ text: 'useImageResolution', link: '/utilities/useimageresolution' },
{
text: 'useTransformationState',
link: '/utilities/usetransformationstate',
},
- { text: 'getAspectRatioSize', link: '/utilities/getAspectRatioSize' },
],
},
{
diff --git a/docs/docs/components/gallery.md b/docs/docs/components/gallery.md
index 916312e..a999a92 100644
--- a/docs/docs/components/gallery.md
+++ b/docs/docs/components/gallery.md
@@ -14,7 +14,8 @@ A practical gallery component which mimics Telegram's gallery behavior, among it
- **Tap to Item:** Tap on the edges of an item to go to the previous or next item.
- **Custom Scroll Transition**: Customize scroll behavior with your own transitions.
-The next video footage is taken from the [Example app](https://github.com/Glazzes/react-native-zoom-toolkit/tree/main/example).
+The next video footage is taken from the [Example app](https://github.com/Glazzes/react-native-zoom-toolkit/tree/main/example)
+while using a custom transition.
@@ -165,6 +166,14 @@ Used to extract a unique key for a given item at the specified index.
Maximum number of items to be rendered at once.
+### gap
+
+| Type | Default |
+| -------- | ------- |
+| `number` | `0` |
+
+Blank space between items.
+
### initialIndex
| Type | Default |
@@ -468,11 +477,12 @@ Jump to the item at the given index.
### GalleryTransitionState
-| Property | Type | Description |
-| ------------- | -------------------- | ----------------------------------------------------- |
-| `index` | `number` | Index of an item rendered in the gallery. |
-| `activeIndex` | `number` | Index of the currently displayed item on the gallery. |
-| `vertical` | `boolean` | Whether the gallery is in vertical mode or not. |
-| `isScrolling` | `boolean` | Whether the gallery is actively being scrolled. |
-| `scroll` | `number` | Current scroll value. |
-| `gallerySize` | `SizeVector
` | Width and height of the gallery. |
+| Property | Type | Description |
+| ------------- | ------------------------ | ----------------------------------------------------- |
+| `index` | `number` | Index of an item rendered in the gallery. |
+| `activeIndex` | `number` | Index of the currently displayed item on the gallery. |
+| `gap` | `number` | Blank space between items. |
+| `direction` | `vertical \| horizontal` | Direction of the gallery. |
+| `isScrolling` | `boolean` | Whether the gallery is actively being scrolled. |
+| `scroll` | `number` | Current scroll value. |
+| `gallerySize` | `SizeVector` | Width and height of the gallery. |
diff --git a/docs/docs/components/resumablezoom.md b/docs/docs/components/resumablezoom.md
index d7c2f74..e3d3c32 100644
--- a/docs/docs/components/resumablezoom.md
+++ b/docs/docs/components/resumablezoom.md
@@ -31,10 +31,10 @@ This component is best utilized when at least one of the two dimensions of the w
```jsx
import React from 'react';
-import { Image, View, useWindowDimensions } from 'react-native';
+import { Image, useWindowDimensions } from 'react-native';
import {
+ fitContainer,
ResumableZoom,
- getAspectRatioSize,
useImageResolution,
} from 'react-native-zoom-toolkit';
@@ -42,26 +42,21 @@ const uri =
'https://assets-global.website-files.com/63634f4a7b868a399577cf37/64665685a870fadf4bb171c2_labrador%20americano.jpg';
const App = () => {
- const { width } = useWindowDimensions();
-
- // Gets the resolution of your image
+ const { width, height } = useWindowDimensions();
const { isFetching, resolution } = useImageResolution({ uri });
if (isFetching || resolution === undefined) {
return null;
}
- // An utility function to get the size without compromising the aspect ratio
- const imageSize = getAspectRatioSize({
- aspectRatio: resolution.width / resolution.height,
- width: width,
+ const size = fitContainer(resolution.width / resolution.height, {
+ width,
+ height,
});
return (
-
-
-
-
-
+
+
+
);
};
@@ -320,12 +315,19 @@ Programmatically zoom in or out to a xy position within the child component.
| multiplier | `number` | Value to multiply the current scale for, values greater than one zoom in and values less than one zoom out. |
| xy | `Vector \| undefined` | Position of the point to zoom in or out starting from the top left corner of your component, leaving this value as undefined will be infered as zooming in or out from the center of the child component's current visible area. |
+### getVisibleRect
+
+Get the coordinates of the current visible rectangle within ResumableZoom's frame.
+
+- type definition: `() => Rect`
+- return type: `{x: number, y: number, width: number, height: number}`
+
### requestState
Request internal transformation values of this component at the moment of the calling.
- type definition: `() => CommonZoomState`
-- return type: [CommonZoomState](#commonzoomstate)
+- return type: [CommonZoomState\](#commonzoomstate)
### assignState
diff --git a/docs/docs/components/snapbackzoom.md b/docs/docs/components/snapbackzoom.md
index aa0bacf..334a573 100644
--- a/docs/docs/components/snapbackzoom.md
+++ b/docs/docs/components/snapbackzoom.md
@@ -1,5 +1,5 @@
---
-title: Snapback Zoom
+title: SnapbackZoom
description: An ideal zoom component for preview handling
outline: deep
---
@@ -161,6 +161,28 @@ if you need to mirror the current state of the gesture to some other component,
Callback triggered once the snap back animation has finished.
+### scrollRef
+
+| Type | Default |
+| ------------------------------------------ | ----------- |
+| `React.RefObject>` | `undefined` |
+
+Improve gesture detection when SnapbackZoom is rendered within a vertical ScrollView, see the following example.
+
+```tsx
+const scrollViewRef = useRef(null);
+
+
+ {images.map((uri) => {
+ return (
+
+
+
+ );
+ })}
+;
+```
+
## About resizeConfig Property
Before you start reading, for a visual reference watch the video above and pay attention to the parrot image.
diff --git a/docs/docs/installation.md b/docs/docs/installation.md
index db02941..c4bfd17 100644
--- a/docs/docs/installation.md
+++ b/docs/docs/installation.md
@@ -5,46 +5,25 @@ outline: deep
# Installation
-Install `react-native-zoom-toolkit` in your project
-
-::: code-group
-
-```sh [npm]
-npm install react-native-zoom-toolkit
-```
-
-```sh [yarn]
-yarn add react-native-zoom-toolkit
-```
-
-:::
-
-### Dependencies
-
This library relies on both Reanimated and Gesture Handler making part of your project, if you do not have them installed already please refer to [Reanimated](https://docs.swmansion.com/react-native-reanimated/docs/fundamentals/getting-started/) and [Gesture Handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation) installation guides.
-::: tip Recommended Versions
-
-- Recommended versions for react-native-gesture-handler are `2.16.0` and above, previous versions should work
- well but it's not guaranteed.
- :::
+| React Native Version | React Native Zoom Toolkit Version | Gesture Handler version |
+| -------------------- | --------------------------------- | ----------------------- |
+| `<= 0.76` | `3.x.x` | 2.16.0 and beyond. |
+| `>= 0.76` | `4.x.x` | 2.19.0 and beyond. |
::: code-group
```sh [npm]
-npm install react-native-gesture-handler react-native-reanimated
+npm install react-native-zoom-toolkit react-native-gesture-handler react-native-reanimated
```
```sh [yarn]
-yarn add react-native-gesture-handler react-native-reanimated
+yarn add react-native-zoom-toolkit react-native-gesture-handler react-native-reanimated
```
```sh [expo]
-npx expo install react-native-gesture-handler react-native-reanimated
+npx expo install react-native-zoom-toolkit react-native-gesture-handler react-native-reanimated
```
:::
-
-### Additional Setup
-
-No additional setup is required.
diff --git a/docs/docs/utilities/fitContainer.md b/docs/docs/utilities/fitContainer.md
new file mode 100644
index 0000000..8297d37
--- /dev/null
+++ b/docs/docs/utilities/fitContainer.md
@@ -0,0 +1,28 @@
+---
+title: fitContainer
+description: Get width and height of an element to fit a container
+outline: deep
+---
+
+# fitContainer
+
+Get the width and height for an element based on its aspect ratio and the container to fit in a such a way the
+aspect ratio of the element is not compromised.
+
+## Type Definition
+
+| Name | Type | Description |
+| ----------- | -------------------- | ----------------------------------- |
+| aspectRatio | `number` | Aspect ratio of the element to fit. |
+| container | `SizeVector` | Width and height of the container. |
+
+## How to use
+
+```js
+const container = useWindowDimensions();
+const resolution = { width: 1920, height: 1080 };
+const size = fitContainer(resolution.width / resolution.height, {
+ width: container.width,
+ height: container.height,
+});
+```
diff --git a/docs/docs/utilities/getAspectRatioSize.md b/docs/docs/utilities/getAspectRatioSize.md
deleted file mode 100644
index 42490ab..0000000
--- a/docs/docs/utilities/getAspectRatioSize.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: getAspectRatioSize
-description: Gets width and height based on the aspect ratio
-outline: deep
----
-
-# getAspectRatioSize
-Gets width and height based on the aspect ratio.
-
-## How to use
-Let's assume you've got a HD image and you want to render this image with a max width of 200px, however you
-don't want to compromise its aspect ratio.
-
-```js
-import { getAspectRatioSize } from 'react-native-zoom-toolkit';
-
-const hdResolution = { width: 1920, height: 1080 };
-
-// width is 200 and height is 112.5
-const { width, height } = getAspectRatioSize({
- aspectRatio: hdResolution.width / hdResolution.height,
- width: 200
-});
-
-// Alternatively if you want to render your image with a height of 200
-// use height property, width is 355 here.
-const { width, height } = getAspectRatioSize({
- aspectRatio: hdResolution.width / hdResolution.height,
- height: 200
-});
-```
diff --git a/docs/docs/utilities/useimageresolution.md b/docs/docs/utilities/useimageresolution.md
index 25a5bab..5b3e55b 100644
--- a/docs/docs/utilities/useimageresolution.md
+++ b/docs/docs/utilities/useimageresolution.md
@@ -5,45 +5,39 @@ outline: deep
---
# useImageResolution hook
-Get the resolution of a bundle or network image.
+
+Get the resolution of a network, bundle or base64 image.
### How to use
+
```jsx
import { useImageResolution } from 'react-native-zoom-toolkit';
-// Get resolution of a bundle image
+// Network image
+const { isFetching, resolution, error } = useImageResolution({
+ uri: 'url to some network image',
+ headers: {
+ Authorization: 'some bearer token',
+ },
+});
+
+// Bundle image
const { isFetching, resolution, error } = useImageResolution(
require('path to your bundle image asset')
);
-// Get resolution of a network image
+// Base64 image
const { isFetching, resolution, error } = useImageResolution({
- uri: 'url to some network image',
- headers: {
- 'Authorization': 'some bearer token',
- }
-})
-
+ uri: 'your base64 string',
+});
```
-- parameter information
-
-| Property | Type |Description |
-|----------|------|------------|
-| `source` | `Source \| number` | An url pointing to a network image and headers or a require statement to a bundle image asset. |
-
-- returns [FetchImageResolutionResult](#fetchimageresolutionresult)
## Type Definitions
-### Source
-| Property | Type |Description |
-|----------|------|------------|
-| `uri` | `string` | An url pointing to a network image. |
-| `headers` | `Record \| undefined` | Optional headers, in case you are accesing network protected images. |
### FetchImageResolutionResult
-| Property | Type |Description |
-|----------|------|------------|
-| `isFetching` | `boolean` | Whether the hook is fetching or not. |
-| `resolution` | `SizeVector \| undefined` | Width and height of the image. |
-| `error` | `Error \| undefined` | An error in case the image fetching fails. |
+| Property | Type | Description |
+| ------------ | ------------------------- | ------------------------------------------ |
+| `isFetching` | `boolean` | Whether the hook is fetching or not. |
+| `resolution` | `SizeVector \| undefined` | Width and height of the image. |
+| `error` | `Error \| undefined` | An error in case the image fetching fails. |
diff --git a/example/app.json b/example/app.json
index ea1e869..4808f75 100644
--- a/example/app.json
+++ b/example/app.json
@@ -4,6 +4,7 @@
"slug": "example",
"scheme": "example",
"version": "1.0.0",
+ "newArchEnabled": true,
"orientation": "default",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
@@ -28,6 +29,6 @@
"favicon": "./assets/favicon.png",
"bundler": "metro"
},
- "plugins": ["expo-router"]
+ "plugins": ["expo-router", "expo-video"]
}
}
diff --git a/example/app/_layout.tsx b/example/app/_layout.tsx
index e4bb623..ffa75c4 100644
--- a/example/app/_layout.tsx
+++ b/example/app/_layout.tsx
@@ -1,4 +1,6 @@
import React from 'react';
+import { StyleSheet } from 'react-native';
+
import { Drawer } from 'expo-router/drawer';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import * as Orientation from 'expo-screen-orientation';
@@ -6,7 +8,6 @@ import * as Orientation from 'expo-screen-orientation';
Orientation.lockAsync(Orientation.OrientationLock.PORTRAIT_UP);
import { default as CustomDrawer } from '../src/navigation/Drawer';
-import { StyleSheet } from 'react-native';
const _layout = () => {
return (
diff --git a/example/app/snapback.tsx b/example/app/snapback.tsx
index 5abc77b..f2294fa 100644
--- a/example/app/snapback.tsx
+++ b/example/app/snapback.tsx
@@ -1,5 +1,5 @@
import React from 'react';
-import { SnapbackZoomExample } from '../src/snapback';
+import SnapbackZoomExample from '../src/snapback';
const snapback = () => {
return ;
diff --git a/example/package.json b/example/package.json
index 2928f81..526cd01 100644
--- a/example/package.json
+++ b/example/package.json
@@ -9,32 +9,32 @@
"web": "expo start --web"
},
"dependencies": {
- "@expo/metro-runtime": "~3.2.1",
+ "@expo/metro-runtime": "~4.0.0",
"@react-navigation/drawer": "^6.6.15",
- "@shopify/react-native-skia": "1.2.3",
- "expo": "^51.0.8",
- "expo-av": "~14.0.5",
- "expo-constants": "~16.0.1",
- "expo-image": "~1.12.9",
- "expo-image-manipulator": "~12.0.5",
- "expo-linking": "~6.3.1",
- "expo-media-library": "~16.0.3",
- "expo-router": "~3.5.14",
- "expo-screen-orientation": "~7.0.5",
- "expo-status-bar": "~1.12.1",
+ "@shopify/react-native-skia": "1.5.0",
+ "expo": "^52.0.4",
+ "expo-av": "~15.0.1",
+ "expo-constants": "~17.0.2",
+ "expo-image": "~2.0.0",
+ "expo-image-manipulator": "~13.0.5",
+ "expo-linking": "~7.0.2",
+ "expo-media-library": "~17.0.2",
+ "expo-router": "~4.0.2",
+ "expo-screen-orientation": "~8.0.0",
+ "expo-status-bar": "~2.0.0",
"fbemitter": "^3.0.0",
- "react": "18.2.0",
- "react-dom": "18.2.0",
- "react-native": "0.74.1",
- "react-native-gesture-handler": "~2.16.1",
+ "react": "18.3.1",
+ "react-dom": "18.3.1",
+ "react-native": "0.76.1",
+ "react-native-gesture-handler": "~2.20.2",
"react-native-image-viewing": "^0.2.2",
- "react-native-reanimated": "~3.10.1",
- "react-native-safe-area-context": "4.10.1",
- "react-native-screens": "3.31.1",
- "react-native-web": "~0.19.6"
+ "react-native-reanimated": "~3.16.1",
+ "react-native-safe-area-context": "4.12.0",
+ "react-native-screens": "~4.0.0",
+ "react-native-web": "~0.19.13"
},
"devDependencies": {
- "@babel/core": "^7.20.0",
+ "@babel/core": "^7.25.2",
"@expo/webpack-config": "~19.0.1",
"@types/fbemitter": "^2.0.35",
"babel-loader": "^8.1.0",
diff --git a/example/src/cropzoom/common-example/Controls.tsx b/example/src/cropzoom/common-example/Controls.tsx
index 7f3a060..af63516 100644
--- a/example/src/cropzoom/common-example/Controls.tsx
+++ b/example/src/cropzoom/common-example/Controls.tsx
@@ -1,11 +1,13 @@
import React, { useState } from 'react';
import { StyleSheet, View, Pressable, ActivityIndicator } from 'react-native';
-import { FlipType, type Action, manipulateAsync } from 'expo-image-manipulator';
+
+import { FlipType, SaveFormat, ImageManipulator } from 'expo-image-manipulator';
import Icon from '@expo/vector-icons/MaterialCommunityIcons';
import type { CropZoomType } from 'react-native-zoom-toolkit';
import { theme } from '../../constants';
import { activeColor, baseColor } from '../commons/contants';
+import { createAlbumAsync, createAssetAsync } from 'expo-media-library';
type ControlProps = {
uri: string;
@@ -44,32 +46,34 @@ const Controls: React.FC = ({ uri, cropRef, setCrop }) => {
}
setIsCropping(true);
- const cropResult = cropRef.current.crop(200);
+ const cropContext = cropRef.current.crop(300);
+ const manipulateContext = ImageManipulator.manipulate(uri);
- const actions: Action[] = [];
- if (cropResult.resize !== undefined) {
- actions.push({ resize: cropResult.resize });
- }
+ if (cropContext.resize !== undefined)
+ manipulateContext.resize(cropContext.resize);
- if (cropResult.context.flipHorizontal) {
- actions.push({ flip: FlipType.Horizontal });
- }
+ if (cropContext.context.flipHorizontal)
+ manipulateContext.flip(FlipType.Horizontal);
- if (cropResult.context.flipVertical) {
- actions.push({ flip: FlipType.Vertical });
- }
+ if (cropContext.context.flipVertical)
+ manipulateContext.flip(FlipType.Vertical);
- if (cropResult.context.rotationAngle !== 0) {
- actions.push({ rotate: cropResult.context.rotationAngle });
- }
+ if (cropContext.context.rotationAngle !== 0)
+ manipulateContext.rotate(cropContext.context.rotationAngle);
+
+ manipulateContext.crop(cropContext.crop);
+
+ const imageRef = await manipulateContext.renderAsync();
+ const result = await imageRef.saveAsync({
+ compress: 1,
+ format: SaveFormat.PNG,
+ });
- actions.push({ crop: cropResult.crop });
+ const asset = await createAssetAsync(result.uri);
+ await createAlbumAsync('cropping', asset);
- manipulateAsync(uri, actions)
- .then((manipulationResult) => {
- setCrop(manipulationResult.uri);
- })
- .finally(() => setIsCropping(false));
+ setCrop(result.uri);
+ setIsCropping(false);
};
return (
diff --git a/example/src/cropzoom/commons/SVGOverlay.tsx b/example/src/cropzoom/commons/SVGOverlay.tsx
index 59b3535..e5e0056 100644
--- a/example/src/cropzoom/commons/SVGOverlay.tsx
+++ b/example/src/cropzoom/commons/SVGOverlay.tsx
@@ -37,7 +37,7 @@ const SVGOverlay: React.FC = ({ cropSize }) => {
return (
);
};
diff --git a/example/src/gallery/GalleryExample.tsx b/example/src/gallery/GalleryExample.tsx
index 48f974d..b784f50 100644
--- a/example/src/gallery/GalleryExample.tsx
+++ b/example/src/gallery/GalleryExample.tsx
@@ -12,15 +12,12 @@ import {
MediaType,
type Asset,
} from 'expo-media-library';
-import {
- stackTransition,
- Gallery,
- type GalleryType,
-} from 'react-native-zoom-toolkit';
+import { Gallery, type GalleryType } from 'react-native-zoom-toolkit';
import GalleryImage from './GalleryImage';
import VideoControls from './controls/VideoControls';
import GalleryVideo from './GalleryVideo';
+import { StatusBar } from 'expo-status-bar';
type SizeVector = { width: number; height: number };
@@ -60,7 +57,6 @@ const GalleryExample = () => {
);
const keyExtractor = useCallback((item, index) => `${item.uri}-${index}`, []);
- const customTransition = useCallback(stackTransition, []);
// Toogle video controls opacity if the current item is a video
const onTap = useCallback(() => {
@@ -96,7 +92,7 @@ const GalleryExample = () => {
if (granted) {
const page = await getAssetsAsync({
first: 100,
- mediaType: ['photo', 'video'],
+ mediaType: ['photo'],
sortBy: 'creationTime',
});
@@ -124,6 +120,7 @@ const GalleryExample = () => {
data={assets}
keyExtractor={keyExtractor}
renderItem={renderItem}
+ gap={24}
maxScale={scales}
onIndexChange={(idx) => {
activeIndex.value = idx;
@@ -131,7 +128,6 @@ const GalleryExample = () => {
onTap={onTap}
pinchCenteringMode={'sync'}
onVerticalPull={onVerticalPulling}
- customTransition={customTransition}
/>
{
isSeeking={isSeeking}
opacity={opacityControls}
/>
+
+
);
};
diff --git a/example/src/gallery/GalleryImage.tsx b/example/src/gallery/GalleryImage.tsx
index 6a5d83b..0bc2654 100644
--- a/example/src/gallery/GalleryImage.tsx
+++ b/example/src/gallery/GalleryImage.tsx
@@ -8,7 +8,7 @@ import {
import { Image } from 'expo-image';
import { type Asset } from 'expo-media-library';
-import { calculateItemSize } from './utils/utils';
+import { fitContainer } from 'react-native-zoom-toolkit';
type GalleryImageProps = {
asset: Asset;
@@ -21,14 +21,10 @@ const GalleryImage: React.FC = ({
index,
activeIndex,
}) => {
- const [downScale, setDownScale] = useState(true);
const { width, height } = useWindowDimensions();
+ const size = fitContainer(asset.width / asset.height, { width, height });
- const size = calculateItemSize(
- { width: asset.width, height: asset.height },
- { width, height },
- width / height
- );
+ const [downScale, setDownScale] = useState(true);
const wrapper = (active: number) => {
if (index === active) setDownScale(false);
diff --git a/example/src/gallery/GalleryVideo.tsx b/example/src/gallery/GalleryVideo.tsx
index 640f574..aff3ccd 100644
--- a/example/src/gallery/GalleryVideo.tsx
+++ b/example/src/gallery/GalleryVideo.tsx
@@ -4,13 +4,13 @@ import { type SharedValue } from 'react-native-reanimated';
import { ResizeMode, Video, type AVPlaybackStatus } from 'expo-av';
import type { Asset } from 'expo-media-library';
-import { calculateItemSize } from './utils/utils';
import {
listenToPauseVideoEvent,
listenToPlayVideoEvent,
listenToSeekVideoEvent,
listenToStopVideoEvent,
} from './utils/emitter';
+import { fitContainer } from '../../../src/utils/fitContainer';
type GalleryVideoProps = {
asset: Asset;
@@ -29,13 +29,7 @@ const GalleryVideo: React.FC = ({
}) => {
const videoRef = useRef