Improve performance

Signed-off-by: Nikolaos Karaolidis <nick@karaolidis.com>
This commit is contained in:
2023-08-04 13:39:55 +03:00
parent 5958cf57ee
commit 1b09b058e4
7 changed files with 95 additions and 85 deletions

16
package-lock.json generated
View File

@@ -23,6 +23,7 @@
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.72.2", "react-native": "0.72.2",
"react-native-document-picker": "^9.0.1", "react-native-document-picker": "^9.0.1",
"react-native-fast-image": "^8.6.3",
"react-native-file-access": "^3.0.4", "react-native-file-access": "^3.0.4",
"react-native-gesture-handler": "^2.12.0", "react-native-gesture-handler": "^2.12.0",
"react-native-get-random-values": "^1.9.0", "react-native-get-random-values": "^1.9.0",
@@ -13434,6 +13435,15 @@
} }
} }
}, },
"node_modules/react-native-fast-image": {
"version": "8.6.3",
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-8.6.3.tgz",
"integrity": "sha512-Sdw4ESidXCXOmQ9EcYguNY2swyoWmx53kym2zRsvi+VeFCHEdkO+WG1DK+6W81juot40bbfLNhkc63QnWtesNg==",
"peerDependencies": {
"react": "^17 || ^18",
"react-native": ">=0.60.0"
}
},
"node_modules/react-native-file-access": { "node_modules/react-native-file-access": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-native-file-access/-/react-native-file-access-3.0.4.tgz", "resolved": "https://registry.npmjs.org/react-native-file-access/-/react-native-file-access-3.0.4.tgz",
@@ -25861,6 +25871,12 @@
"invariant": "^2.2.4" "invariant": "^2.2.4"
} }
}, },
"react-native-fast-image": {
"version": "8.6.3",
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-8.6.3.tgz",
"integrity": "sha512-Sdw4ESidXCXOmQ9EcYguNY2swyoWmx53kym2zRsvi+VeFCHEdkO+WG1DK+6W81juot40bbfLNhkc63QnWtesNg==",
"requires": {}
},
"react-native-file-access": { "react-native-file-access": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-native-file-access/-/react-native-file-access-3.0.4.tgz", "resolved": "https://registry.npmjs.org/react-native-file-access/-/react-native-file-access-3.0.4.tgz",

View File

@@ -28,6 +28,7 @@
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.72.2", "react-native": "0.72.2",
"react-native-document-picker": "^9.0.1", "react-native-document-picker": "^9.0.1",
"react-native-fast-image": "^8.6.3",
"react-native-file-access": "^3.0.4", "react-native-file-access": "^3.0.4",
"react-native-gesture-handler": "^2.12.0", "react-native-gesture-handler": "^2.12.0",
"react-native-get-random-values": "^1.9.0", "react-native-get-random-values": "^1.9.0",

View File

@@ -1,8 +1,12 @@
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { HelperText, Text, TextInput, useTheme } from 'react-native-paper'; import { HelperText, Text, TextInput, useTheme } from 'react-native-paper';
import { Image, LayoutAnimation, View } from 'react-native'; import { LayoutAnimation, View } from 'react-native';
import { useSafeAreaFrame } from 'react-native-safe-area-context'; import { useSafeAreaFrame } from 'react-native-safe-area-context';
import Video from 'react-native-video'; import Video from 'react-native-video';
import TextRecognition, {
TextRecognitionResult,
} from '@react-native-ml-kit/text-recognition';
import FastImage from 'react-native-fast-image';
import { LoadingView, MemeFail, TextOverlay } from '../../../components'; import { LoadingView, MemeFail, TextOverlay } from '../../../components';
import { import {
getFilenameFromUri, getFilenameFromUri,
@@ -13,9 +17,6 @@ import { StagingMeme } from '../../../types';
import { useMemeDimensions } from '../../../hooks'; import { useMemeDimensions } from '../../../hooks';
import { MEME_TYPE } from '../../../database'; import { MEME_TYPE } from '../../../database';
import MemeTagSelector from './memeTagSelector/memeTagSelector'; import MemeTagSelector from './memeTagSelector/memeTagSelector';
import TextRecognition, {
TextRecognitionResult,
} from '@react-native-ml-kit/text-recognition';
const memeEditorStyles = { const memeEditorStyles = {
media: { media: {
@@ -95,7 +96,7 @@ const MemeEditor = ({
case MEME_TYPE.GIF: { case MEME_TYPE.GIF: {
return ( return (
<View> <View>
<Image <FastImage
source={{ uri }} source={{ uri }}
style={[memeEditorStyles.media, dimensionStyles]} style={[memeEditorStyles.media, dimensionStyles]}
resizeMode="contain" resizeMode="contain"

View File

@@ -1,52 +1,41 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Image, TouchableHighlight } from 'react-native'; import { TouchableHighlight } from 'react-native';
import { useSelector } from 'react-redux';
import { useSafeAreaFrame } from 'react-native-safe-area-context'; import { useSafeAreaFrame } from 'react-native-safe-area-context';
import { AndroidScoped } from 'react-native-file-access'; import FastImage from 'react-native-fast-image';
import { MEME_TYPE, Meme } from '../../../database'; import { MEME_TYPE, Meme } from '../../../database';
import { RootState } from '../../../state';
import { MemeFail } from '../../../components'; import { MemeFail } from '../../../components';
import { getFontAwesome5IconSize } from '../../../utilities'; import { getFontAwesome5IconSize } from '../../../utilities';
import { useMemeDimensions } from '../../../hooks'; import { useMemeDimensions } from '../../../hooks';
const MemesGridItem = ({ const MemesGridItem = ({
meme, meme,
index,
focusMeme, focusMeme,
uri,
columns,
}: { }: {
meme: Meme; meme: Meme;
index: number; focusMeme: () => void;
focusMeme: (index: number) => void; uri: string;
columns: number;
}) => { }) => {
const { width } = useSafeAreaFrame(); const { width } = useSafeAreaFrame();
const gridColumns = useSelector(
(state: RootState) => state.settings.gridColumns,
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const storageUri = useSelector(
(state: RootState) => state.settings.storageUri,
)!;
const uri = AndroidScoped.appendPath(storageUri, meme.filename); const itemWidth = useMemo(
() => (width * 0.92 - 5) / columns,
[columns, width],
);
const { dimensions, loading, error } = useMemeDimensions(uri, meme.mimeType); const { dimensions, loading, error } = useMemeDimensions(uri, meme.mimeType);
const itemWidth = (width * 0.92 - 5) / gridColumns;
const mediaComponent = useMemo(() => { const mediaComponent = useMemo(() => {
switch (meme.memeType) { switch (meme.memeType) {
case MEME_TYPE.IMAGE: case MEME_TYPE.IMAGE:
case MEME_TYPE.GIF: case MEME_TYPE.GIF:
case MEME_TYPE.VIDEO: { case MEME_TYPE.VIDEO: {
return ( return (
<Image <FastImage
source={{ uri }} source={{ uri }}
style={[ style={[{ width: itemWidth, height: itemWidth }]}
{
width: itemWidth,
height: itemWidth,
},
]}
/> />
); );
} }
@@ -59,14 +48,11 @@ const MemesGridItem = ({
if (!error && (loading || !dimensions)) return <></>; if (!error && (loading || !dimensions)) return <></>;
return ( return (
<TouchableHighlight onPress={() => focusMeme(index)}> <TouchableHighlight onPress={focusMeme}>
{error ? ( {error ? (
<MemeFail <MemeFail
style={{ style={{ width: itemWidth, height: itemWidth }}
width: (width * 0.92 - 5) / gridColumns, iconSize={getFontAwesome5IconSize(columns)}
height: (width * 0.92 - 5) / gridColumns,
}}
iconSize={getFontAwesome5IconSize(gridColumns)}
/> />
) : ( ) : (
mediaComponent mediaComponent

View File

@@ -15,6 +15,7 @@ import { getFlashListItemHeight } from '../../../utilities';
import MemesMasonryItem from './memesMasonryItem'; import MemesMasonryItem from './memesMasonryItem';
import MemesGridItem from './memesGridItem'; import MemesGridItem from './memesGridItem';
import MemesListItem from './memesListItem'; import MemesListItem from './memesListItem';
import { AndroidScoped } from 'react-native-file-access';
const sharedMemesListStyles = StyleSheet.create({ const sharedMemesListStyles = StyleSheet.create({
flashList: { flashList: {
@@ -66,6 +67,10 @@ const MemesList = ({
const gridColumns = useSelector( const gridColumns = useSelector(
(state: RootState) => state.settings.gridColumns, (state: RootState) => state.settings.gridColumns,
); );
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const storageUri = useSelector(
(state: RootState) => state.settings.storageUri,
)!;
return ( return (
<> <>
@@ -82,7 +87,12 @@ const MemesList = ({
numColumns={masonryColumns} numColumns={masonryColumns}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
renderItem={({ item: meme, index }) => ( renderItem={({ item: meme, index }) => (
<MemesMasonryItem meme={meme} index={index} focusMeme={focusMeme} /> <MemesMasonryItem
meme={meme}
focusMeme={() => focusMeme(index)}
uri={AndroidScoped.appendPath(storageUri, meme.filename)}
columns={masonryColumns}
/>
)} )}
contentContainerStyle={{ contentContainerStyle={{
paddingTop: flashListPadding, paddingTop: flashListPadding,
@@ -96,6 +106,7 @@ const MemesList = ({
)} )}
onScroll={handleScroll} onScroll={handleScroll}
fadingEdgeLength={100} fadingEdgeLength={100}
overScrollMode="never"
/> />
)} )}
{view === VIEW.GRID && ( {view === VIEW.GRID && (
@@ -111,7 +122,12 @@ const MemesList = ({
numColumns={gridColumns} numColumns={gridColumns}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
renderItem={({ item: meme, index }) => ( renderItem={({ item: meme, index }) => (
<MemesGridItem meme={meme} index={index} focusMeme={focusMeme} /> <MemesGridItem
meme={meme}
focusMeme={() => focusMeme(index)}
uri={AndroidScoped.appendPath(storageUri, meme.filename)}
columns={gridColumns}
/>
)} )}
contentContainerStyle={{ contentContainerStyle={{
paddingTop: flashListPadding, paddingTop: flashListPadding,
@@ -125,20 +141,25 @@ const MemesList = ({
)} )}
onScroll={handleScroll} onScroll={handleScroll}
fadingEdgeLength={100} fadingEdgeLength={100}
overScrollMode="never"
/> />
)} )}
{view === VIEW.LIST && ( {view === VIEW.LIST && (
<FlashList <FlashList
ref={flashListRef} ref={flashListRef}
data={memes} data={memes}
estimatedItemSize={50} estimatedItemSize={90}
estimatedListSize={{ estimatedListSize={{
height: height, height: height,
width: width * 0.92, width: width * 0.92,
}} }}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
renderItem={({ item: meme, index }) => ( renderItem={({ item: meme, index }) => (
<MemesListItem meme={meme} index={index} focusMeme={focusMeme} /> <MemesListItem
meme={meme}
focusMeme={() => focusMeme(index)}
uri={AndroidScoped.appendPath(storageUri, meme.filename)}
/>
)} )}
ItemSeparatorComponent={() => <Divider />} ItemSeparatorComponent={() => <Divider />}
contentContainerStyle={{ contentContainerStyle={{
@@ -153,6 +174,7 @@ const MemesList = ({
)} )}
onScroll={handleScroll} onScroll={handleScroll}
fadingEdgeLength={100} fadingEdgeLength={100}
overScrollMode="never"
/> />
)} )}
</> </>

View File

@@ -1,12 +1,10 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Image, StyleSheet, View } from 'react-native'; import { StyleSheet, View } from 'react-native';
import { Text, TouchableRipple } from 'react-native-paper'; import { Text, TouchableRipple } from 'react-native-paper';
import { useSafeAreaFrame } from 'react-native-safe-area-context'; import { useSafeAreaFrame } from 'react-native-safe-area-context';
import { AndroidScoped } from 'react-native-file-access'; import FastImage from 'react-native-fast-image';
import { useSelector } from 'react-redux';
import { MEME_TYPE, Meme } from '../../../database'; import { MEME_TYPE, Meme } from '../../../database';
import { MemeFail } from '../../../components'; import { MemeFail } from '../../../components';
import { RootState } from '../../../state';
import { useMemeDimensions } from '../../../hooks'; import { useMemeDimensions } from '../../../hooks';
const memesListItemStyles = StyleSheet.create({ const memesListItemStyles = StyleSheet.create({
@@ -35,20 +33,19 @@ const memesListItemStyles = StyleSheet.create({
const MemesListItem = ({ const MemesListItem = ({
meme, meme,
index,
focusMeme, focusMeme,
uri,
}: { }: {
meme: Meme; meme: Meme;
index: number; focusMeme: () => void;
focusMeme: (index: number) => void; uri: string;
}) => { }) => {
const { width } = useSafeAreaFrame(); const { width } = useSafeAreaFrame();
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const storageUri = useSelector(
(state: RootState) => state.settings.storageUri,
)!;
const uri = AndroidScoped.appendPath(storageUri, meme.filename); const listItemWidth = useMemo(
() => ({ width: width * 0.92 - 75 - 10 }),
[width],
);
const { dimensions, loading, error } = useMemeDimensions(uri, meme.mimeType); const { dimensions, loading, error } = useMemeDimensions(uri, meme.mimeType);
@@ -57,7 +54,7 @@ const MemesListItem = ({
case MEME_TYPE.IMAGE: case MEME_TYPE.IMAGE:
case MEME_TYPE.GIF: case MEME_TYPE.GIF:
case MEME_TYPE.VIDEO: { case MEME_TYPE.VIDEO: {
return <Image source={{ uri }} style={[memesListItemStyles.image]} />; return <FastImage source={{ uri }} style={[memesListItemStyles.image]} />;
} }
default: { default: {
return <></>; return <></>;
@@ -68,22 +65,14 @@ const MemesListItem = ({
if (!error && (loading || !dimensions)) return <></>; if (!error && (loading || !dimensions)) return <></>;
return ( return (
<TouchableRipple <TouchableRipple onPress={focusMeme} style={memesListItemStyles.view}>
onPress={() => focusMeme(index)}
style={memesListItemStyles.view}>
<> <>
{error ? ( {error ? (
<MemeFail style={memesListItemStyles.image} /> <MemeFail style={memesListItemStyles.image} />
) : ( ) : (
mediaComponent mediaComponent
)} )}
<View <View style={[memesListItemStyles.detailsView, listItemWidth]}>
style={[
memesListItemStyles.detailsView,
{
width: width * 0.92 - 75 - 10,
},
]}>
<Text variant="titleMedium" style={memesListItemStyles.text}> <Text variant="titleMedium" style={memesListItemStyles.text}>
{meme.title} {meme.title}
</Text> </Text>

View File

@@ -1,10 +1,8 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Image, StyleSheet, TouchableHighlight } from 'react-native'; import { StyleSheet, TouchableHighlight } from 'react-native';
import { useSelector } from 'react-redux';
import { useSafeAreaFrame } from 'react-native-safe-area-context'; import { useSafeAreaFrame } from 'react-native-safe-area-context';
import { AndroidScoped } from 'react-native-file-access'; import FastImage from 'react-native-fast-image';
import { MEME_TYPE, Meme } from '../../../database'; import { MEME_TYPE, Meme } from '../../../database';
import { RootState } from '../../../state';
import { MemeFail } from '../../../components'; import { MemeFail } from '../../../components';
import { getFontAwesome5IconSize } from '../../../utilities'; import { getFontAwesome5IconSize } from '../../../utilities';
import { useMemeDimensions } from '../../../hooks'; import { useMemeDimensions } from '../../../hooks';
@@ -21,29 +19,28 @@ const memeMasonryItemStyles = StyleSheet.create({
const MemesMasonryItem = ({ const MemesMasonryItem = ({
meme, meme,
index,
focusMeme, focusMeme,
uri,
columns,
}: { }: {
meme: Meme; meme: Meme;
index: number; focusMeme: () => void;
focusMeme: (index: number) => void; uri: string;
columns: number;
}) => { }) => {
const { width } = useSafeAreaFrame(); const { width } = useSafeAreaFrame();
const masonryColumns = useSelector(
(state: RootState) => state.settings.masonryColumns,
);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const storageUri = useSelector(
(state: RootState) => state.settings.storageUri,
)!;
const uri = AndroidScoped.appendPath(storageUri, meme.filename); const itemWidth = useMemo(
() => (width * 0.92 - 5) / columns - 5,
[columns, width],
);
const { dimensions, loading, error } = useMemeDimensions(uri, meme.mimeType); const { dimensions, loading, error } = useMemeDimensions(uri, meme.mimeType);
const itemWidth = (width * 0.92 - 5) / masonryColumns - 5; const itemHeight = useMemo(
const itemHeight = () => ((width * 0.92) / columns - 5) / (dimensions?.aspectRatio ?? 1),
((width * 0.92) / masonryColumns - 5) / (dimensions?.aspectRatio ?? 1); [columns, dimensions?.aspectRatio, width],
);
const mediaComponent = useMemo(() => { const mediaComponent = useMemo(() => {
switch (meme.memeType) { switch (meme.memeType) {
@@ -51,7 +48,7 @@ const MemesMasonryItem = ({
case MEME_TYPE.GIF: case MEME_TYPE.GIF:
case MEME_TYPE.VIDEO: { case MEME_TYPE.VIDEO: {
return ( return (
<Image <FastImage
source={{ uri }} source={{ uri }}
style={[ style={[
memeMasonryItemStyles.image, memeMasonryItemStyles.image,
@@ -72,16 +69,14 @@ const MemesMasonryItem = ({
if (!error && (loading || !dimensions)) return <></>; if (!error && (loading || !dimensions)) return <></>;
return ( return (
<TouchableHighlight <TouchableHighlight onPress={focusMeme} style={memeMasonryItemStyles.view}>
onPress={() => focusMeme(index)}
style={memeMasonryItemStyles.view}>
{error || !dimensions ? ( {error || !dimensions ? (
<MemeFail <MemeFail
style={[ style={[
memeMasonryItemStyles.image, memeMasonryItemStyles.image,
{ width: itemWidth, height: itemHeight }, { width: itemWidth, height: itemHeight },
]} ]}
iconSize={getFontAwesome5IconSize(masonryColumns)} iconSize={getFontAwesome5IconSize(columns)}
/> />
) : ( ) : (
mediaComponent mediaComponent