Add video support

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-08-01 14:35:10 +03:00
parent f1f969c8ea
commit b83407f1f4
13 changed files with 358 additions and 158 deletions

View File

@@ -1,13 +1,20 @@
import React, { useState } from 'react';
import React, { useMemo } from 'react';
import { HelperText, Text, TextInput, useTheme } from 'react-native-paper';
import { Image, LayoutAnimation } from 'react-native';
import { useSafeAreaFrame } from 'react-native-safe-area-context';
import { LoadingView, MemeFail, MemeTagSelector } from '..';
import { getFilenameFromUri, validateMemeTitle } from '../../utilities';
import { Dimensions, StagingMeme } from '../../types';
import {
getFilenameFromUri,
getMemeTypeFromMimeType,
validateMemeTitle,
} from '../../utilities';
import { StagingMeme } from '../../types';
import { useMemeDimensions } from '../../hooks';
import { MEME_TYPE } from '../../database';
import Video from 'react-native-video';
const memeEditorStyles = {
image: {
media: {
marginBottom: 15,
borderRadius: 5,
},
@@ -26,6 +33,7 @@ const memeEditorStyles = {
const MemeEditor = ({
uri,
mimeType,
loading,
setLoading,
error,
setError,
@@ -44,7 +52,59 @@ const MemeEditor = ({
const { width } = useSafeAreaFrame();
const { colors } = useTheme();
const [dimensions, setDimensions] = useState<Dimensions>();
const { dimensions } = useMemeDimensions(
uri,
mimeType,
useMemo(
() => () => {
setLoading(false);
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
},
[setLoading],
),
useMemo(() => (errorIn: Error) => setError(errorIn), [setError]),
);
const mediaComponent = useMemo(() => {
if (!mimeType || !dimensions) return <></>;
const dimensionStyles = {
width: width * 0.92,
height: Math.max(
Math.min((width * 0.92) / dimensions.aspectRatio, 500),
100,
),
};
const memeType = getMemeTypeFromMimeType(mimeType);
if (!memeType) return <></>;
switch (memeType) {
case MEME_TYPE.IMAGE:
case MEME_TYPE.GIF: {
return (
<Image
source={{ uri }}
style={[memeEditorStyles.media, dimensionStyles]}
resizeMode="contain"
/>
);
}
case MEME_TYPE.VIDEO: {
return (
<Video
source={{ uri }}
style={[memeEditorStyles.media, dimensionStyles]}
resizeMode="contain"
controls
/>
);
}
default: {
return <></>;
}
}
}, [dimensions, mimeType, uri, width]);
if (!uri || !mimeType || !staging) return <LoadingView />;
@@ -70,51 +130,15 @@ const MemeEditor = ({
width: width * 0.92,
height: width * 0.92,
},
memeEditorStyles.image,
memeEditorStyles.media,
]}
iconSize={50}
/>
) : // eslint-disable-next-line unicorn/no-nested-ternary
loading || !dimensions ? (
<></>
) : (
<Image
source={{ uri }}
style={[
dimensions
? {
width: width * 0.92,
height: Math.max(
Math.min(
((width * 0.92) / dimensions.width) * dimensions.height,
500,
),
100,
),
}
: // eslint-disable-next-line react-native/no-inline-styles
{
width: width * 0.92,
height: 1,
},
memeEditorStyles.image,
]}
resizeMode="contain"
onLoad={event => {
setDimensions({
width: event.nativeEvent.source.width,
height: event.nativeEvent.source.height,
});
setLoading(false);
LayoutAnimation.configureNext(
LayoutAnimation.Presets.easeInEaseOut,
);
}}
onError={() =>
setError(
new Error(
'The URI for this meme appears to be broken. This may have been caused by the file being moved or deleted.',
),
)
}
/>
mediaComponent
)}
<Text
variant="bodySmall"

View File

@@ -1,12 +1,13 @@
import React from 'react';
import React, { useMemo } from 'react';
import { StyleSheet, View } from 'react-native';
import { useSafeAreaFrame } from 'react-native-safe-area-context';
import { useImageDimensions } from '@react-native-community/hooks';
import { AndroidScoped } from 'react-native-file-access';
import { useSelector } from 'react-redux';
import { Meme } from '../../database';
import { MEME_TYPE, Meme } from '../../database';
import { RootState } from '../../state';
import { AnimatedImage, LoadingView, MemeFail } from '..';
import { useMemeDimensions } from '../../hooks';
import Video from 'react-native-video';
const memeViewItemStyles = StyleSheet.create({
view: {
@@ -24,7 +25,34 @@ const MemeViewItem = ({ meme }: { meme: Meme }) => {
const uri = AndroidScoped.appendPath(storageUri, meme.filename);
const { dimensions, loading, error } = useImageDimensions({ uri });
const { dimensions, loading, error } = useMemeDimensions(uri, meme.mimeType);
const mediaComponent = useMemo(() => {
if (!dimensions) return <></>;
const dimensionStyles =
dimensions.aspectRatio > width / (height - 128)
? {
width,
height: width / (dimensions.width / dimensions.height),
}
: {
width: (height - 128) * (dimensions.width / dimensions.height),
height: height - 128,
};
switch (meme.memeType) {
case MEME_TYPE.IMAGE:
case MEME_TYPE.GIF: {
return <AnimatedImage source={{ uri }} style={dimensionStyles} />;
}
default: {
return (
<Video source={{ uri }} style={dimensionStyles} paused controls />
);
}
}
}, [dimensions, height, meme.memeType, uri, width]);
if (!error && (loading || !dimensions)) {
return <LoadingView style={{ width, height }} />;
@@ -41,21 +69,7 @@ const MemeViewItem = ({ meme }: { meme: Meme }) => {
iconSize={50}
/>
) : (
<AnimatedImage
source={{ uri }}
style={
dimensions.aspectRatio > width / (height - 128)
? {
width,
height: width / (dimensions.width / dimensions.height),
}
: {
width:
(height - 128) * (dimensions.width / dimensions.height),
height: height - 128,
}
}
/>
mediaComponent
)}
</View>
);

View File

@@ -1,13 +1,13 @@
import React from 'react';
import React, { useMemo } from 'react';
import { Image, TouchableHighlight } from 'react-native';
import { useSelector } from 'react-redux';
import { useSafeAreaFrame } from 'react-native-safe-area-context';
import { useImageDimensions } from '@react-native-community/hooks';
import { AndroidScoped } from 'react-native-file-access';
import { Meme } from '../../../database';
import { MEME_TYPE, Meme } from '../../../database';
import { RootState } from '../../../state';
import { MemeFail } from '..';
import { getFontAwesome5IconSize } from '../../../utilities';
import { useMemeDimensions } from '../../../hooks';
const MemesGridItem = ({
meme,
@@ -29,7 +29,32 @@ const MemesGridItem = ({
const uri = AndroidScoped.appendPath(storageUri, meme.filename);
const { dimensions, loading, error } = useImageDimensions({ uri });
const { dimensions, loading, error } = useMemeDimensions(uri, meme.mimeType);
const itemWidth = (width * 0.92 - 5) / gridColumns;
const mediaComponent = useMemo(() => {
switch (meme.memeType) {
case MEME_TYPE.IMAGE:
case MEME_TYPE.GIF:
case MEME_TYPE.VIDEO: {
return (
<Image
source={{ uri }}
style={[
{
width: itemWidth,
height: itemWidth,
},
]}
/>
);
}
default: {
return <></>;
}
}
}, [itemWidth, meme.memeType, uri]);
if (!error && (loading || !dimensions)) return <></>;
@@ -44,15 +69,7 @@ const MemesGridItem = ({
iconSize={getFontAwesome5IconSize(gridColumns)}
/>
) : (
<Image
source={{ uri }}
style={[
{
width: (width * 0.92 - 5) / gridColumns,
height: (width * 0.92 - 5) / gridColumns,
},
]}
/>
mediaComponent
)}
</TouchableHighlight>
);

View File

@@ -1,13 +1,13 @@
import React from 'react';
import React, { useMemo } from 'react';
import { Image, StyleSheet, View } from 'react-native';
import { Text, TouchableRipple } from 'react-native-paper';
import { useSafeAreaFrame } from 'react-native-safe-area-context';
import { useImageDimensions } from '@react-native-community/hooks';
import { AndroidScoped } from 'react-native-file-access';
import { useSelector } from 'react-redux';
import { Meme } from '../../../database';
import { MEME_TYPE, Meme } from '../../../database';
import { MemeFail } from '..';
import { RootState } from '../../../state';
import { useMemeDimensions } from '../../../hooks';
const memesListItemStyles = StyleSheet.create({
view: {
@@ -50,7 +50,20 @@ const MemesListItem = ({
const uri = AndroidScoped.appendPath(storageUri, meme.filename);
const { dimensions, loading, error } = useImageDimensions({ uri });
const { dimensions, loading, error } = useMemeDimensions(uri, meme.mimeType);
const mediaComponent = useMemo(() => {
switch (meme.memeType) {
case MEME_TYPE.IMAGE:
case MEME_TYPE.GIF:
case MEME_TYPE.VIDEO: {
return <Image source={{ uri }} style={[memesListItemStyles.image]} />;
}
default: {
return <></>;
}
}
}, [meme.memeType, uri]);
if (!error && (loading || !dimensions)) return <></>;
@@ -62,7 +75,7 @@ const MemesListItem = ({
{error ? (
<MemeFail style={memesListItemStyles.image} />
) : (
<Image source={{ uri }} style={memesListItemStyles.image} />
mediaComponent
)}
<View
style={[

View File

@@ -1,13 +1,13 @@
import React from 'react';
import React, { useMemo } from 'react';
import { Image, StyleSheet, TouchableHighlight } from 'react-native';
import { useSelector } from 'react-redux';
import { useSafeAreaFrame } from 'react-native-safe-area-context';
import { AndroidScoped } from 'react-native-file-access';
import { useImageDimensions } from '@react-native-community/hooks';
import { Meme } from '../../../database';
import { MEME_TYPE, Meme } from '../../../database';
import { RootState } from '../../../state';
import { MemeFail } from '..';
import { getFontAwesome5IconSize } from '../../../utilities';
import { useMemeDimensions } from '../../../hooks';
const memeMasonryItemStyles = StyleSheet.create({
view: {
@@ -39,7 +39,35 @@ const MemesMasonryItem = ({
const uri = AndroidScoped.appendPath(storageUri, meme.filename);
const { dimensions, loading, error } = useImageDimensions({ uri });
const { dimensions, loading, error } = useMemeDimensions(uri, meme.mimeType);
const itemWidth = (width * 0.92 - 5) / masonryColumns - 5;
const itemHeight =
((width * 0.92) / masonryColumns - 5) / (dimensions?.aspectRatio ?? 1);
const mediaComponent = useMemo(() => {
switch (meme.memeType) {
case MEME_TYPE.IMAGE:
case MEME_TYPE.GIF:
case MEME_TYPE.VIDEO: {
return (
<Image
source={{ uri }}
style={[
memeMasonryItemStyles.image,
{
width: itemWidth,
height: itemHeight,
},
]}
/>
);
}
default: {
return <></>;
}
}
}, [itemHeight, itemWidth, meme.memeType, uri]);
if (!error && (loading || !dimensions)) return <></>;
@@ -51,25 +79,12 @@ const MemesMasonryItem = ({
<MemeFail
style={[
memeMasonryItemStyles.image,
{
width: (width * 0.92) / masonryColumns - 5,
height: (width * 0.92) / masonryColumns - 5,
},
{ width: itemWidth, height: itemHeight },
]}
iconSize={getFontAwesome5IconSize(masonryColumns)}
/>
) : (
<Image
source={{ uri }}
style={[
memeMasonryItemStyles.image,
{
width: (width * 0.92) / masonryColumns - 5,
height:
((width * 0.92) / masonryColumns - 5) / dimensions.aspectRatio,
},
]}
/>
mediaComponent
)}
</TouchableHighlight>
);