Add custom AnimatedImage component
This also fixes the white flashing when loading images Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
18
package-lock.json
generated
18
package-lock.json
generated
@@ -10,7 +10,6 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bankify/redux-persist-realm": "^0.1.3",
|
"@bankify/redux-persist-realm": "^0.1.3",
|
||||||
"@likashefqet/react-native-image-zoom": "^1.3.0",
|
|
||||||
"@react-native-clipboard/clipboard": "^1.11.2",
|
"@react-native-clipboard/clipboard": "^1.11.2",
|
||||||
"@react-native-community/hooks": "^3.0.0",
|
"@react-native-community/hooks": "^3.0.0",
|
||||||
"@react-navigation/bottom-tabs": "^6.5.8",
|
"@react-navigation/bottom-tabs": "^6.5.8",
|
||||||
@@ -2991,17 +2990,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
|
||||||
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
|
"integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
|
||||||
},
|
},
|
||||||
"node_modules/@likashefqet/react-native-image-zoom": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@likashefqet/react-native-image-zoom/-/react-native-image-zoom-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-PLRd1hNMHe9LUn8b4rmLt86282geuaqP4Qd2rFWIloxMS2ePNTIaNlEUu3T3LaO8Pg9vhVV97TxfFeU8F+tcYQ==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": ">=16.x.x",
|
|
||||||
"react-native": ">=0.62.x",
|
|
||||||
"react-native-gesture-handler": ">=2.x.x",
|
|
||||||
"react-native-reanimated": ">=2.x.x"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
|
"node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
|
||||||
"version": "5.1.1-v1",
|
"version": "5.1.1-v1",
|
||||||
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
||||||
@@ -17940,12 +17928,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@likashefqet/react-native-image-zoom": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@likashefqet/react-native-image-zoom/-/react-native-image-zoom-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-PLRd1hNMHe9LUn8b4rmLt86282geuaqP4Qd2rFWIloxMS2ePNTIaNlEUu3T3LaO8Pg9vhVV97TxfFeU8F+tcYQ==",
|
|
||||||
"requires": {}
|
|
||||||
},
|
|
||||||
"@nicolo-ribaudo/eslint-scope-5-internals": {
|
"@nicolo-ribaudo/eslint-scope-5-internals": {
|
||||||
"version": "5.1.1-v1",
|
"version": "5.1.1-v1",
|
||||||
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
|
||||||
|
@@ -15,7 +15,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bankify/redux-persist-realm": "^0.1.3",
|
"@bankify/redux-persist-realm": "^0.1.3",
|
||||||
"@likashefqet/react-native-image-zoom": "^1.3.0",
|
|
||||||
"@react-native-clipboard/clipboard": "^1.11.2",
|
"@react-native-clipboard/clipboard": "^1.11.2",
|
||||||
"@react-native-community/hooks": "^3.0.0",
|
"@react-native-community/hooks": "^3.0.0",
|
||||||
"@react-navigation/bottom-tabs": "^6.5.8",
|
"@react-navigation/bottom-tabs": "^6.5.8",
|
||||||
|
56
src/components/animatedImage.tsx
Normal file
56
src/components/animatedImage.tsx
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import React, { ComponentProps, useMemo } from 'react';
|
||||||
|
import { Image } from 'react-native';
|
||||||
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
||||||
|
import Animated, {
|
||||||
|
useAnimatedStyle,
|
||||||
|
useSharedValue,
|
||||||
|
withSpring,
|
||||||
|
} from 'react-native-reanimated';
|
||||||
|
|
||||||
|
const AnimatedImage = ({ ...props }: ComponentProps<typeof Image>) => {
|
||||||
|
const scale = useSharedValue(1);
|
||||||
|
const translateX = useSharedValue(0);
|
||||||
|
const translateY = useSharedValue(0);
|
||||||
|
|
||||||
|
const animatedStyles = useAnimatedStyle(() => {
|
||||||
|
return {
|
||||||
|
transform: [
|
||||||
|
{ translateX: translateX.value },
|
||||||
|
{ translateY: translateY.value },
|
||||||
|
{ scale: scale.value },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const gesture = useMemo(
|
||||||
|
() =>
|
||||||
|
Gesture.Simultaneous(
|
||||||
|
Gesture.Pinch()
|
||||||
|
.onUpdate(event => {
|
||||||
|
scale.value = event.scale;
|
||||||
|
})
|
||||||
|
.onFinalize(() => {
|
||||||
|
scale.value = withSpring(1);
|
||||||
|
}),
|
||||||
|
Gesture.Pan()
|
||||||
|
.minPointers(2)
|
||||||
|
.onUpdate(event => {
|
||||||
|
translateX.value = event.translationX;
|
||||||
|
translateY.value = event.translationY;
|
||||||
|
})
|
||||||
|
.onFinalize(() => {
|
||||||
|
translateX.value = withSpring(0);
|
||||||
|
translateY.value = withSpring(0);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
[scale, translateX, translateY],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GestureDetector gesture={gesture}>
|
||||||
|
<Animated.Image {...props} style={[props.style, animatedStyles]} />
|
||||||
|
</GestureDetector>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AnimatedImage;
|
@@ -1,11 +1,13 @@
|
|||||||
export {
|
export {
|
||||||
MemesList,
|
MemesList,
|
||||||
MemeEditor,
|
MemeEditor,
|
||||||
|
MemeFail,
|
||||||
MemesHeader,
|
MemesHeader,
|
||||||
MemeTagSelector,
|
MemeTagSelector,
|
||||||
MemeViewItem,
|
MemeViewItem,
|
||||||
} from './memes';
|
} from './memes';
|
||||||
export { TagChip, TagEditor, TagPreview, TagRow, TagsHeader } from './tags';
|
export { TagChip, TagEditor, TagPreview, TagRow, TagsHeader } from './tags';
|
||||||
|
export { default as AnimatedImage } from './animatedImage';
|
||||||
export { default as FloatingActionButton } from './floatingActionButton';
|
export { default as FloatingActionButton } from './floatingActionButton';
|
||||||
export { default as HideableBottomNavigationBar } from './hideableBottomNavigationBar';
|
export { default as HideableBottomNavigationBar } from './hideableBottomNavigationBar';
|
||||||
export { default as HideableHeader } from './hideableHeader';
|
export { default as HideableHeader } from './hideableHeader';
|
||||||
|
@@ -3,8 +3,7 @@ import { HelperText, Text, TextInput, useTheme } from 'react-native-paper';
|
|||||||
import { Image } from 'react-native';
|
import { Image } from 'react-native';
|
||||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||||
import { useImageDimensions } from '@react-native-community/hooks/lib/useImageDimensions';
|
import { useImageDimensions } from '@react-native-community/hooks/lib/useImageDimensions';
|
||||||
import LoadingView from '../loadingView';
|
import { MemeFail, MemeTagSelector, LoadingView } from '..';
|
||||||
import { MemeFail, MemeTagSelector } from '.';
|
|
||||||
import { Tag } from '../../database';
|
import { Tag } from '../../database';
|
||||||
import { StringValidationResult, validateMemeTitle } from '../../utilities';
|
import { StringValidationResult, validateMemeTitle } from '../../utilities';
|
||||||
|
|
||||||
|
@@ -1,14 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ImageZoom } from '@likashefqet/react-native-image-zoom';
|
|
||||||
import { StyleSheet, View } from 'react-native';
|
import { StyleSheet, View } from 'react-native';
|
||||||
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
import { useSafeAreaFrame } from 'react-native-safe-area-context';
|
||||||
import { useImageDimensions } from '@react-native-community/hooks';
|
import { useImageDimensions } from '@react-native-community/hooks';
|
||||||
import { AndroidScoped } from 'react-native-file-access';
|
import { AndroidScoped } from 'react-native-file-access';
|
||||||
import LoadingView from '../loadingView';
|
|
||||||
import { Meme } from '../../database';
|
|
||||||
import MemeFail from './memeFail';
|
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
import { Meme } from '../../database';
|
||||||
import { RootState } from '../../state';
|
import { RootState } from '../../state';
|
||||||
|
import { AnimatedImage, LoadingView, MemeFail } from '..';
|
||||||
|
|
||||||
const memeViewItemStyles = StyleSheet.create({
|
const memeViewItemStyles = StyleSheet.create({
|
||||||
view: {
|
view: {
|
||||||
@@ -47,7 +45,7 @@ const MemeViewItem = ({ meme }: { meme: Meme }) => {
|
|||||||
iconSize={50}
|
iconSize={50}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ImageZoom
|
<AnimatedImage
|
||||||
source={{ uri }}
|
source={{ uri }}
|
||||||
style={
|
style={
|
||||||
dimensions.aspectRatio > width / (height - 128)
|
dimensions.aspectRatio > width / (height - 128)
|
||||||
@@ -61,7 +59,6 @@ const MemeViewItem = ({ meme }: { meme: Meme }) => {
|
|||||||
height: height - 128,
|
height: height - 128,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
minScale={0.5}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
Reference in New Issue
Block a user