diff --git a/react-native/package.json b/react-native/package.json index c098499..66c06c6 100644 --- a/react-native/package.json +++ b/react-native/package.json @@ -16,7 +16,7 @@ "expo": "~44.0.0", "expo-app-loading": "~1.3.0", "expo-camera": "^12.1.2", - "expo-font": "^10.0.5", + "expo-font": "~10.0.4", "expo-status-bar": "~1.2.0", "react": "17.0.1", "react-dom": "17.0.1", @@ -29,7 +29,8 @@ "react-navigation": "^4.4.4", "react-navigation-stack": "^2.10.4", "react-redux": "^7.2.6", - "redux": "^4.1.2" + "redux": "^4.1.2", + "rn-swipeable-panel": "^1.2.7" }, "devDependencies": { "@babel/core": "^7.12.9", diff --git a/react-native/screens/TranslateScreen.tsx b/react-native/screens/TranslateScreen.tsx index f024ec1..fdc99aa 100644 --- a/react-native/screens/TranslateScreen.tsx +++ b/react-native/screens/TranslateScreen.tsx @@ -1,14 +1,45 @@ -import React, { useState, useEffect } from 'react'; -import { StyleSheet, Text, View, TouchableOpacity, ImageBackground } from 'react-native'; +import React, { useState, useEffect, useCallback } from 'react'; +import { StyleSheet, Text, View, ScrollView, TouchableOpacity, TouchableHighlight, ImageBackground, Dimensions } from 'react-native'; import { Camera } from 'expo-camera'; -import { Ionicons } from '@expo/vector-icons'; +import { Ionicons, Entypo, FontAwesome, MaterialIcons } from '@expo/vector-icons'; +import { SwipeablePanel } from 'rn-swipeable-panel'; import { theme } from '../core/theme'; +import type { Navigation } from '../types'; +import AppLoading from 'expo-app-loading'; +import useFonts from '../hooks/useFonts' -export default function TranslateScreen() { + +const NO_WIDTH_SPACE = '​'; +const highlight = (text: string) => + text.split(' ').map((word, i) => ( + + {word} + {NO_WIDTH_SPACE} + + )); + +export default function TranslateScreen({ navigation }: Navigation) { const [hasPermission, setHasPermission] = useState(false); + const [fontsLoaded, SetFontsLoaded] = useState(false); const [type, setType] = useState(Camera.Constants.Type.back); const [camera, setCamera] = useState(null); const [imageUri, setImageUri] = useState(""); + const [results, setResults] = useState<{"content": string; "underline": boolean}[]>(); + const [fullText, setFullText] = useState<{"translated": string; "korean": string}>(); + const [showFullText, setShowFullText] = useState(false); + const [showTranslated, setShowTranslated] = useState(true); + const [panelProps, setPanelProps] = useState({ + fullWidth: true, + openLarge: false, + onlyLarge: false, + smallPanelHeight: Dimensions.get('window').height*0.6, + onClose: () => {} + }) + const [loaded, setLoaded] = useState(0); + + const LoadFontsAndRestoreToken = async () => { + await useFonts(); + }; useEffect(() => { (async () => { @@ -24,6 +55,16 @@ export default function TranslateScreen() { return No access to camera! } + if (!fontsLoaded) { + return ( + SetFontsLoaded(true)} + onError={() => {}} + /> + ); + } + const takePicture = async () => { if (camera) { const data = await camera.takePictureAsync(null); @@ -32,47 +73,152 @@ export default function TranslateScreen() { } }; + const extractText = () => { + // TODO: api + // TEST + setResults([ + {"content": "Buy Suyeon a delicious meal.", "underline": false}, + {"content": "The graduation ceremony will be held in the auditorium at 2 p.m. on February 14th.", "underline": true}, + ]); + setFullText({ + "translated": "You have to buy Suyeon a delicious meal. Hee is writing ... The graduation ceremony will be held in the auditorium at 2 p.m. on February 14th. We look forward to your involvement! You have to buy Suyeon a delicious meal. Hee is writing ... The graduation ceremony will be held in the auditorium at 2 p.m. on February 14th. We look forward to your involvement! You have to buy Suyeon a delicious meal. Hee is writing ... The graduation ceremony will be held in the auditorium at 2 p.m. on February 14th. We look forward to your involvement!", + "korean": "수연이에게 맛있는 밥을 사야합니다. 희가 쓰는 중... 졸업식은 2월 14일에 강당에서 열릴 예정입니다. 많은 참여 부탁드립니다!" + }); + } + + const handleFullText = () => { + setShowFullText(!showFullText); + setPanelProps({...panelProps, openLarge: !panelProps.openLarge, onlyLarge: !panelProps.onlyLarge}); + // setPanelProps({...panelProps, onlyLarge: !panelProps.onlyLarge}); + } + + const saveResults = () => { + // TODO: api + console.log("save"); + alert('saved'); + } + + const handleTranslatedText = () => { + setShowTranslated(!showTranslated); + } + + const closeResults = () => { + navigation.navigate('Home'); + } + const retakePicture = () => { - setImageUri(""); + setImageUri(''); + setResults([]); + setFullText({ "translated": "", "korean": "" }); + setShowFullText(false); + setPanelProps({...panelProps, openLarge: false}); } return ( + {/* After taking a picture */} {imageUri ? ( - <> - - - - - - - + /* After taking a picture and press the check button */ + results && results.length > 0 ? ( + + + + + + {showFullText ? "Full Text" : "Results"} + {!showFullText ? ( + + + + + + + + + ) : ( + + + + )} + + + + {!showFullText ? ( + results.map((result, index) => + + {index+1}.  + {result.underline ? ( + highlight(result.content) + ) : ( + result.content + )} + + ) + ) : ( + showTranslated ? ( + {fullText?.translated} + ) : ( + {fullText?.korean} + ) + )} + + + + {!showFullText ? ( + + Close + + ) : ( + + Back + + )} + + + Try again + + + + + + ) : ( + /* After taking a picture, before OCR(pressing the check button) */ + <> + + + + + + + + ) ) : ( + /* Before taking a picture, Camera ready */ <> setCamera(ref)}> - { - setType( - type === Camera.Constants.Type.back - ? Camera.Constants.Type.front - : Camera.Constants.Type.back - ); - }}> - + { + setType( + type === Camera.Constants.Type.back + ? Camera.Constants.Type.front + : Camera.Constants.Type.back + ); + }}> + - - + + )} - ); + ); } const styles = StyleSheet.create({ @@ -88,7 +234,7 @@ const styles = StyleSheet.create({ flexDirection: 'row', margin: 20, }, - button: { + reverseButton: { flex: 0.1, alignSelf: 'flex-end', alignItems: 'center', @@ -98,10 +244,79 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', + backgroundColor: '#000' }, - captureButton: { - backgroundColor: theme.colors.primary, + circleButton: { borderRadius: 48, - padding: 8 + height: 64, + width: 64, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + }, + primaryBackground: { + backgroundColor: theme.colors.primary + }, + grayBackground: { + backgroundColor: theme.colors.gray + }, + whiteText: { + color: '#fff', + fontSize: 16 + }, + whiteBackground: { + backgroundColor: '#fff', + }, + regularButton: { + paddingVertical: 16, + flex: 0.9, + marginTop: 16, + alignItems: 'center', + borderRadius: 16 + }, + gap: { + flex: 0.1 + }, + innerCircle: { + borderRadius: 48, + padding: 8, + height: 56, + width: 56, + borderWidth: 2 + }, + bottomDrawer: { + flex: 1, + height: Dimensions.get('window').height*0.5, + flexDirection: 'column', + alignContent: 'space-between', + backgroundColor: theme.colors.background, + borderTopLeftRadius: 48, + borderTopRightRadius: 48, + padding: 32 + }, + title: { + fontFamily: 'Lora_700Bold', + fontSize: 24, + fontWeight: '700', + color: theme.colors.primary, + }, + content: { + fontSize: 16, + paddingBottom: 8 + }, + spaceBetween: { + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingBottom: 24 + }, + alignRow: { + flexDirection: 'row' + }, + rightSpace: { + paddingRight: 8 + }, + highlighted: { + backgroundColor: theme.colors.skyblue } }); \ No newline at end of file diff --git a/react-native/yarn.lock b/react-native/yarn.lock index e7828dc..3d0098d 100644 --- a/react-native/yarn.lock +++ b/react-native/yarn.lock @@ -2726,7 +2726,7 @@ expo-file-system@~13.1.3: "@expo/config-plugins" "^4.0.2" uuid "^3.4.0" -expo-font@^10.0.5, expo-font@~10.0.5: +expo-font@~10.0.4, expo-font@~10.0.5: version "10.0.5" resolved "https://registry.npmjs.org/expo-font/-/expo-font-10.0.5.tgz" integrity sha512-x9YwM0xLkDdSvFjeNbyuh33Q1Hk3uc2jbMuuAN5W2ZVcUZqG0M8GCX/KV/D/7rYqdXKbliQA5r44MyDwZe/XRw== @@ -5044,6 +5044,13 @@ rimraf@~2.2.6: resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" integrity sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI= +rn-swipeable-panel@^1.2.5, rn-swipeable-panel@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/rn-swipeable-panel/-/rn-swipeable-panel-1.2.7.tgz#a72f65c91d51715e84cdb9e912db8d94c150b1be" + integrity sha512-ibaU2+4J/VliCud8neQLzuLLNv3DP3t3+ySeG6Nn4mibhLQp/lvU47lCzvPiz4WhO6FTzre2k7Q5LqsuH5ThXQ== + dependencies: + rn-swipeable-panel "^1.2.5" + rsvp@^4.8.4: version "4.8.5" resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz"