diff --git a/package-lock.json b/package-lock.json
index 590a1a9..b9dd6a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"@bankify/redux-persist-realm": "^0.1.3",
"@react-native-clipboard/clipboard": "^1.11.2",
"@react-native-community/hooks": "^3.0.0",
+ "@react-native-ml-kit/text-recognition": "^1.2.1",
"@react-navigation/bottom-tabs": "^6.5.8",
"@react-navigation/native": "^6.1.7",
"@react-navigation/native-stack": "^6.9.13",
@@ -4113,6 +4114,15 @@
"react-native": ">=0.65"
}
},
+ "node_modules/@react-native-ml-kit/text-recognition": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@react-native-ml-kit/text-recognition/-/text-recognition-1.2.1.tgz",
+ "integrity": "sha512-pJrnf8AvihzYdPAZoZZEeKbOUOMjdsetDjHlleXOoVcoPo6qjfh6Il/Q0ey3boIQuO3HglvNjcMPGEPThF3sPA==",
+ "peerDependencies": {
+ "react": ">=16.8.1",
+ "react-native": ">=0.60.0-rc.0 <1.0.x"
+ }
+ },
"node_modules/@react-native/assets-registry": {
"version": "0.72.0",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.72.0.tgz",
@@ -18799,6 +18809,12 @@
"integrity": "sha512-g2OyxXHfwIytXUJitBR6Z/ISoOfp0WKx5FOv+NqJ/CrWjRDcTw6zXE5I1C9axfuh30kJqzWchVfCDrkzZYTxqg==",
"requires": {}
},
+ "@react-native-ml-kit/text-recognition": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@react-native-ml-kit/text-recognition/-/text-recognition-1.2.1.tgz",
+ "integrity": "sha512-pJrnf8AvihzYdPAZoZZEeKbOUOMjdsetDjHlleXOoVcoPo6qjfh6Il/Q0ey3boIQuO3HglvNjcMPGEPThF3sPA==",
+ "requires": {}
+ },
"@react-native/assets-registry": {
"version": "0.72.0",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.72.0.tgz",
diff --git a/package.json b/package.json
index 3581801..d12a95f 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"@bankify/redux-persist-realm": "^0.1.3",
"@react-native-clipboard/clipboard": "^1.11.2",
"@react-native-community/hooks": "^3.0.0",
+ "@react-native-ml-kit/text-recognition": "^1.2.1",
"@react-navigation/bottom-tabs": "^6.5.8",
"@react-navigation/native": "^6.1.7",
"@react-navigation/native-stack": "^6.9.13",
diff --git a/src/components/index.ts b/src/components/index.ts
index 2bb13d1..265ad48 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -5,3 +5,4 @@ export { default as HideableHeader } from './hideableHeader';
export { default as LoadingView } from './loadingView';
export { default as MemeFail } from './memeFail';
export { default as TagChip } from './tagChip';
+export { default as TextOverlay } from './textOverlay';
diff --git a/src/components/textOverlay.tsx b/src/components/textOverlay.tsx
new file mode 100644
index 0000000..3514e9e
--- /dev/null
+++ b/src/components/textOverlay.tsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import { TextBlock } from '@react-native-ml-kit/text-recognition';
+import { TouchableRipple, useTheme } from 'react-native-paper';
+import { StyleSheet } from 'react-native';
+import { Dimensions } from '../types';
+
+const textOverlayStyles = StyleSheet.create({
+ touchable: {
+ position: 'absolute',
+ borderWidth: 1,
+ zIndex: 1,
+ },
+});
+
+const TextOverlay = ({
+ blocks,
+ onTextPress,
+ onTextLongPress,
+ imageDimensions,
+ frameDimensions,
+}: {
+ blocks: TextBlock[];
+ onTextPress: (text: string) => void;
+ onTextLongPress: (text: string) => void;
+ imageDimensions: Dimensions;
+ frameDimensions: Dimensions;
+}) => {
+ const { colors } = useTheme();
+
+ const widthScale = frameDimensions.width / imageDimensions.width;
+ const heightScale = frameDimensions.height / imageDimensions.height;
+
+ return (
+ <>
+ {blocks.map(
+ (block, index) =>
+ block.frame && (
+
+ onTextPress(block.text.replaceAll('\n', ' ').trim())
+ }
+ onLongPress={() =>
+ onTextLongPress(block.text.replaceAll('\n', ' ').trim())
+ }>
+ <>>
+
+ ),
+ )}
+ >
+ );
+};
+
+export default TextOverlay;
diff --git a/src/screens/editors/meme/memeEditor.tsx b/src/screens/editors/meme/memeEditor.tsx
index a21b39f..919a63a 100644
--- a/src/screens/editors/meme/memeEditor.tsx
+++ b/src/screens/editors/meme/memeEditor.tsx
@@ -1,9 +1,9 @@
-import React, { useMemo } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import { HelperText, Text, TextInput, useTheme } from 'react-native-paper';
-import { Image, LayoutAnimation } from 'react-native';
+import { Image, LayoutAnimation, View } from 'react-native';
import { useSafeAreaFrame } from 'react-native-safe-area-context';
import Video from 'react-native-video';
-import { LoadingView, MemeFail } from '../../../components';
+import { LoadingView, MemeFail, TextOverlay } from '../../../components';
import {
getFilenameFromUri,
getMemeTypeFromMimeType,
@@ -13,6 +13,9 @@ import { StagingMeme } from '../../../types';
import { useMemeDimensions } from '../../../hooks';
import { MEME_TYPE } from '../../../database';
import MemeTagSelector from './memeTagSelector/memeTagSelector';
+import TextRecognition, {
+ TextRecognitionResult,
+} from '@react-native-ml-kit/text-recognition';
const memeEditorStyles = {
media: {
@@ -66,8 +69,15 @@ const MemeEditor = ({
useMemo(() => (errorIn: Error) => setError(errorIn), [setError]),
);
+ const [recognizedText, setRecognizedText] = useState();
+
+ useEffect(() => {
+ if (!uri || !mimeType || !mimeType.startsWith('image')) return;
+ void TextRecognition.recognize(uri).then(setRecognizedText);
+ }, [mimeType, uri]);
+
const mediaComponent = useMemo(() => {
- if (!mimeType || !dimensions) return <>>;
+ if (!mimeType || !dimensions || !staging) return <>>;
const dimensionStyles = {
width: width * 0.92,
@@ -84,11 +94,35 @@ const MemeEditor = ({
case MEME_TYPE.IMAGE:
case MEME_TYPE.GIF: {
return (
-
+
+
+ {recognizedText && (
+
+ setStaging({
+ ...staging,
+ title: validateMemeTitle(text),
+ })
+ }
+ onTextLongPress={text =>
+ setStaging({
+ ...staging,
+ title: validateMemeTitle(`${staging.title.parsed} ${text}`),
+ })
+ }
+ imageDimensions={dimensions}
+ frameDimensions={{
+ ...dimensionStyles,
+ aspectRatio: dimensionStyles.width / dimensionStyles.height,
+ }}
+ />
+ )}
+
);
}
case MEME_TYPE.VIDEO: {
@@ -105,7 +139,7 @@ const MemeEditor = ({
return <>>;
}
}
- }, [dimensions, mimeType, uri, width]);
+ }, [dimensions, mimeType, recognizedText, setStaging, staging, uri, width]);
if (!uri || !mimeType || !staging) return ;