From 040cbe3bb759ca6db50d7a45b213949c88b5f296 Mon Sep 17 00:00:00 2001 From: 0hee0 Date: Sun, 14 Aug 2022 23:38:23 +0900 Subject: [PATCH] [#11] feat: implement clipboard and enable scroll --- react-native/components/BottomDrawer.tsx | 19 +- react-native/locales/en.js | 1 + react-native/locales/ja.js | 3 +- react-native/locales/km.js | 3 +- react-native/locales/ko.js | 3 +- react-native/locales/th.js | 3 +- react-native/locales/vn.js | 3 +- react-native/locales/zh.js | 3 +- react-native/screens/TranslateScreen.tsx | 304 ++++++++++++----------- react-native/types.ts | 1 + react-native/yarn.lock | 5 + 11 files changed, 194 insertions(+), 154 deletions(-) diff --git a/react-native/components/BottomDrawer.tsx b/react-native/components/BottomDrawer.tsx index 35b0625..34523ef 100644 --- a/react-native/components/BottomDrawer.tsx +++ b/react-native/components/BottomDrawer.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect } from 'react'; -import { StyleSheet, Dimensions, View, TouchableOpacity, TouchableHighlight, ScrollView, Alert, Linking } from 'react-native'; +import { StyleSheet, Dimensions, View, TouchableOpacity, TouchableHighlight, ScrollView, Alert, Linking, TouchableWithoutFeedback } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; -import { Popover, Button, Text, Modal, FormControl, Input, VStack, CheckIcon, AlertDialog } from 'native-base'; +import { Popover, Button, Text, Modal, FormControl, Input, VStack, HStack, AlertDialog } from 'native-base'; import { theme } from '../core/theme'; import type { BottomDrawerProps, EventForm, ResultsForm, UserData } from '../types'; import { useAuth } from '../contexts/Auth'; @@ -152,15 +152,20 @@ function BottomDrawer(props: BottomDrawerProps) { - {props.showKorean ? i18n.t('korean') : i18n.t('translation')} - + {props.showKorean ? i18n.t('korean') : i18n.t('translation')} + - + + + + - + {/* */} + + {!props.showKorean ? ( props.results?.fullText?.map((item, index) => @@ -304,7 +309,9 @@ function BottomDrawer(props: BottomDrawerProps) { {props.results?.korean} )} + + {/* */} {props.isTranslateScreen && diff --git a/react-native/locales/en.js b/react-native/locales/en.js index 012f8a3..36b44e1 100644 --- a/react-native/locales/en.js +++ b/react-native/locales/en.js @@ -41,6 +41,7 @@ export default { translateMessage_1: "You can add a schedule to the calendar by clicking the highlighted text.", translateMessage_2: "You must enter at least one character for the title.", saveAlarm: 'Results have been saved', + copiedToClipboard: "Copied to Clipboard", /* BottomDrawer */ sessionExpired: "The session has expired. Please log in again.", helpertext: "Give your results a title.", diff --git a/react-native/locales/ja.js b/react-native/locales/ja.js index 702583c..04a14e7 100644 --- a/react-native/locales/ja.js +++ b/react-native/locales/ja.js @@ -70,5 +70,6 @@ export default { saveFirst: "保存ボタンを押して結果を保存してください!", eventNotFound: "イベントが見つかりませんでした", noResults: "まだ結果はありません。", - translateFirst: "結果を翻訳して保存する。" + translateFirst: "結果を翻訳して保存する。", + copiedToClipboard: "クリップボードにコピーしました", } \ No newline at end of file diff --git a/react-native/locales/km.js b/react-native/locales/km.js index 83da9d0..9a05009 100644 --- a/react-native/locales/km.js +++ b/react-native/locales/km.js @@ -70,5 +70,6 @@ export default { saveFirst: "ចុចប៊ូតុង Save ដើម្បីរក្សាទុកលទ្ធផលជាមុន!", eventNotFound: "រកមិនឃើញព្រឹត្តិការណ៍ទេ។", noResults: "មិនទាន់មានលទ្ធផលនៅឡើយទេ។", - translateFirst: "បកប្រែ និងរក្សាទុកលទ្ធផល។" + translateFirst: "បកប្រែ និងរក្សាទុកលទ្ធផល។", + copiedToClipboard: "បានចម្លងទៅក្ដារតម្បៀតខ្ទាស់", } \ No newline at end of file diff --git a/react-native/locales/ko.js b/react-native/locales/ko.js index bb4cd97..fb58986 100644 --- a/react-native/locales/ko.js +++ b/react-native/locales/ko.js @@ -69,5 +69,6 @@ export default { saveFirst: "저장 버튼을 눌러서 결과 저장을 먼저 해주세요!", eventNotFound: "이벤트를 찾지 못했어요", noResults: "아직 결과가 없습니다.", - translateFirst: "번역을 한 후에 결과를 저장해주세요." + translateFirst: "번역을 한 후에 결과를 저장해주세요.", + copiedToClipboard: "클립보드에 복사되었습니다", } \ No newline at end of file diff --git a/react-native/locales/th.js b/react-native/locales/th.js index b942c0f..45c1216 100644 --- a/react-native/locales/th.js +++ b/react-native/locales/th.js @@ -70,5 +70,6 @@ export default { saveFirst: "คลิกปุ่มบันทึกเพื่อบันทึกผลลัพธ์ก่อน!", eventNotFound: "ไม่พบกิจกรรม", noResults: "ยังไม่มีผลลัพธ", - translateFirst: "แปลและบันทึกผลลัพธ์" + translateFirst: "แปลและบันทึกผลลัพธ์", + copiedToClipboard: "คัดลอกไปยังคลิปบอร์ดแล้ว", } \ No newline at end of file diff --git a/react-native/locales/vn.js b/react-native/locales/vn.js index f73ea7e..b86a648 100644 --- a/react-native/locales/vn.js +++ b/react-native/locales/vn.js @@ -69,5 +69,6 @@ export default { saveFirst: "Nhấp vào nút Lưu để lưu kết quả trước!", eventNotFound: "Không tìm thấy sự kiện nào", noResults: "Chưa có kết quả.", - translateFirst: "Dịch và lưu kết quả." + translateFirst: "Dịch và lưu kết quả.", + copiedToClipboard: "Sao chép vào clipboard", } \ No newline at end of file diff --git a/react-native/locales/zh.js b/react-native/locales/zh.js index e94b073..fa5794c 100644 --- a/react-native/locales/zh.js +++ b/react-native/locales/zh.js @@ -69,5 +69,6 @@ export default { saveFirst: "单击保存按钮首先保存结果!", eventNotFound: "未找到任何事件", noResults: "目前还没有结果。", - translateFirst: "翻译并保存结果。" + translateFirst: "翻译并保存结果。", + copiedToClipboard: "已复制到剪贴板", } \ No newline at end of file diff --git a/react-native/screens/TranslateScreen.tsx b/react-native/screens/TranslateScreen.tsx index 825c86a..22d4716 100644 --- a/react-native/screens/TranslateScreen.tsx +++ b/react-native/screens/TranslateScreen.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { StyleSheet, View, TouchableOpacity, ImageBackground, Dimensions, Alert, Image } from 'react-native'; -import { useToast, Button, HStack, Text, Divider, Modal, VStack } from 'native-base'; +import { useToast, Button, HStack, Text, Divider, Modal, VStack, Box } from 'native-base'; import axios, { AxiosRequestConfig } from "axios"; import { Camera } from 'expo-camera'; import { Ionicons, SimpleLineIcons } from '@expo/vector-icons'; @@ -14,26 +14,23 @@ import { useAuth } from '../contexts/Auth'; import Loading from '../components/Loading'; import i18n from 'i18n-js'; import '../locales/i18n'; +import * as Clipboard from 'expo-clipboard'; -/* TODO: - - 스크롤 내려가게 하기 (지금은 ScrollView의 스크롤이 안 먹음) -*/ - const date = new Date(); export default function TranslateScreen({ navigation }: Navigation) { - const [hasPermission, setHasPermission] = useState(false); - const [type, setType] = useState(Camera.Constants.Type.back); - const [camera, setCamera] = useState(null); - const [imageUri, setImageUri] = useState(''); - const [results, setResults] = useState(); - const [showKorean, setShowKorean] = useState(false); - const [isFullDrawer, setFullDrawer] = useState(false); - const [loading, setLoading] = useState(false); - const [openSaveForm, setOpenSaveForm] = useState(false); - const [openInitialEventForm, setOpenInitialEventForm] = useState(true); + const [hasPermission, setHasPermission] = useState(false); + const [type, setType] = useState(Camera.Constants.Type.back); + const [camera, setCamera] = useState(null); + const [imageUri, setImageUri] = useState(''); + const [results, setResults] = useState(); + const [showKorean, setShowKorean] = useState(false); + const [isFullDrawer, setFullDrawer] = useState(false); + const [loading, setLoading] = useState(false); + const [openSaveForm, setOpenSaveForm] = useState(false); + const [openInitialEventForm, setOpenInitialEventForm] = useState(true); const toast = useToast(); const auth = useAuth(); @@ -81,62 +78,61 @@ export default function TranslateScreen({ navigation }: Navigation) { }; const extractText = async (): Promise => { - if (imageUri) { - // console.log(imageUri); - let FormData = require("form-data"); - const formdata = new FormData(); - formdata.append("uploadfile", { - uri: imageUri, - type: mime.getType(imageUri), - name: imageUri.split("/").pop(), - }); + if (imageUri) { + // console.log(imageUri); + let FormData = require("form-data"); + const formdata = new FormData(); + formdata.append("uploadfile", { + uri: imageUri, + type: mime.getType(imageUri), + name: imageUri.split("/").pop(), + }); - console.log("ocr", formdata); + console.log("ocr", formdata); - setLoading(true); + // setLoading(true); - if (auth?.authData?.access_token) { - const axiosInstance = axios.create({ - baseURL: 'http://localhost:8080', - timeout: 30000, - headers: { - "X-Platform": 'iOS', - "X-App-Build-Number": '1.0.0', - 'ACCESS-TOKEN': auth.authData.access_token - }, - }); - - const formData = new FormData(); - formData.append("uploadfile", { - uri : imageUri, - type: mime.getType(imageUri), - name: imageUri.split("/").pop() - }); - - const config: AxiosRequestConfig = { - method: "post", - url: "/notice/ocr", - headers: { - 'Content-Type': 'multipart/form-data', - }, - transformRequest: (data, headers) => { - return formData; - }, - onUploadProgress: (progressEvent) => { - }, - data: formData, - }; - - // send post request and get response - const response = await axiosInstance.request(config); - - // console.log('response',response.data); - if (response.data) { - setResults(response.data); - setLoading(false); - } - } - } + if (auth?.authData?.access_token) { + const axiosInstance = axios.create({ + baseURL: 'http://localhost:8080', + timeout: 30000, + headers: { + "X-Platform": 'iOS', + "X-App-Build-Number": '1.0.0', + 'ACCESS-TOKEN': auth.authData.access_token + }, + }); + + const formData = new FormData(); + formData.append("uploadfile", { + uri : imageUri, + type: mime.getType(imageUri), + name: imageUri.split("/").pop() + }); + + const config: AxiosRequestConfig = { + method: "post", + url: "/notice/ocr", + headers: { + 'Content-Type': 'multipart/form-data', + }, + transformRequest: (data, headers) => { + return formData; + }, + onUploadProgress: (progressEvent) => { + }, + data: formData, + }; + + // send post request and get response + const response = await axiosInstance.request(config); + + if (response.data) { + setResults(response.data); + setLoading(false); + } + } + } // TEST: mockup data // setResults({ @@ -146,8 +142,9 @@ export default function TranslateScreen({ navigation }: Navigation) { // {id: 3, eid: -1, content: ": 1st and 2nd graders, each classroom, 9:00-10:50 (no meals)\n2) ", date: "", highlight: false, registered: false}, // {id: 4, eid: -1, content: "Diploma representation ceremony", date: "2022-01-04", highlight: true, registered: true}, // {id: 5, eid: -1, content: ": 3rd grade, multi-purpose auditorium (2nd floor), 10:30-12:20\n2. School opening and entrance ceremony for new students: March 4th (Mon), 2019 at 9 o'clock for students to go to school.", date: "", highlight: false, registered: false}, + // {id: 6, eid: -1, content: ": 3rd grade, multi-purpose auditorium (2nd floor), 10:30-12:20\n2. School opening and entrance ceremony for new students: March 4th (Mon), 2019 at 9 o'clock for students to go to school.", date: "", highlight: false, registered: false}, // ], - // korean: "가정통신문\n예당중학교\n8053-8388\n꿈은 크게. 마음은 넘게·\n행동은 바르게\n학부모님께\n희망찬 새해를 맞이하며 학부모님 가정에 건강과 행운이 함께 하시기를 기원 드립니다.\n드릴 말씀은, 2018학년도 종업식 및 졸업장 수여식과 2019학년도 개학 및 신입생 입학식을 다음과 같이 안내드리오니, 이후 3월 개학 때까지 학생들이 자기주도 학습 능력을 배양하고 다양한 체험 활동을 통하여 심신이 건강해지며 각종 유해 환경에 노출되지 않고 안전하고 줄거운 시간이 되도록 지도해 주시기 바랍니다.\n", + // korean: "가정통신문\n예당중학교\n8053-8388\n꿈은 크게. 마음은 넘게·\n행동은 바르게\n학부모님께\n희망찬 새해를 맞이하며 학부모님 가정에 건강과 행운이 함께 하시기를 기원 드립니다.\n드릴 말씀은, 2018학년도 종업식 및 졸업장 수여식과 2019학년도 개학 및 신입생 입학식을 다음과 같이 안내드리오니, 이후 3월 개학 때까지 학생들이 자기주도 학습 능력을 배양하고 다양한 체험 활동을 통하여 심신이 건강해지며 각종 유해 환경에 노출되지 않고 안전하고 줄거운 시간이 되도록 지도해 주시기 바랍니다.\n드릴 말씀은, 2018학년도 종업식 및 졸업장 수여식과 2019학년도 개학 및 신입생 입학식을 다음과 같이 안내드리오니, 이후 3월 개학 때까지 학생들이 자기주도 학습 능력을 배양하고 다양한 체험 활동을 통하여 심신이 건강해지며 각종 유해 환경에 노출되지 않고 안전하고 줄거운 시간이 되도록 지도해 주시기 바랍니다.", // trans_full: "hello", // event_num: 2, // events: [ @@ -168,86 +165,107 @@ export default function TranslateScreen({ navigation }: Navigation) { setShowKorean(!showKorean); }; + const copyToClipboard = () => { + if (showKorean) { + if (results?.korean) { + Clipboard.setString(results.korean); + } + } + else { + if (results?.trans_full) { + Clipboard.setString(results?.trans_full); + } + } + toast.show({ // Design according to mui toast guidelines (https://material.io/components/snackbars#anatomy) + placement: "top", + render: () => { + return + {i18n.t('copiedToClipboard')} + ; + } + }); + }; + const handleOpenSaveForm = () => { setOpenSaveForm(!openSaveForm); }; - const saveResults = async(form: ResultsForm): Promise => { - // data 보내고, success 라면, 서버에 저장된 제목 받아와서 보여주기! - if (!form?.title) { - Alert.alert("You must enter at least one character for the title."); - return; - } - - if (imageUri) { - let FormData = require('form-data'); - const formdata = new FormData(); - formdata.append('uploadfile', { - uri : imageUri, - type: mime.getType(imageUri), - name: imageUri.split("/").pop() - }); - - if (auth?.authData?.access_token) { - const axiosInstance = axios.create({ - baseURL: 'http://localhost:8080', - timeout: 30000, - headers: { - "X-Platform": 'iOS', - "X-App-Build-Number": '1.0.0', - 'ACCESS-TOKEN': auth.authData.access_token - }, - }); + const saveResults = async(form: ResultsForm): Promise => { + // data 보내고, success 라면, 서버에 저장된 제목 받아와서 보여주기! + if (!form?.title) { + Alert.alert("You must enter at least one character for the title."); + return; + } + + if (imageUri) { + let FormData = require('form-data'); + const formdata = new FormData(); + formdata.append('uploadfile', { + uri : imageUri, + type: mime.getType(imageUri), + name: imageUri.split("/").pop() + }); + + if (auth?.authData?.access_token) { + const axiosInstance = axios.create({ + baseURL: 'http://localhost:8080', + timeout: 30000, + headers: { + "X-Platform": 'iOS', + "X-App-Build-Number": '1.0.0', + 'ACCESS-TOKEN': auth.authData.access_token + }, + }); - axiosInstance({ - method: "post", - url: "/notice/image", - transformRequest: (data, headers) => { - return formdata; - }, - onUploadProgress: (progressEvent) => { - }, - data: formdata, - }) - .then(function (response) { - console.log('image response',response.data); - if (response.data && auth?.authData?.access_token) { - const imageUrl = response.data.imageUrl; + axiosInstance({ + method: "post", + url: "/notice/image", + transformRequest: (data, headers) => { + return formdata; + }, + onUploadProgress: (progressEvent) => { + }, + data: formdata, + }) + .then(function (response) { + console.log('image response',response.data); + if (response.data && auth?.authData?.access_token) { + const imageUrl = response.data.imageUrl; - let data = { - imageUrl: imageUrl, - cid: form?.cid, - title: form?.title, - date: new Date().toISOString().slice(0, 10), - korean: results?.korean, - fullText: results?.trans_full - } + let data = { + imageUrl: imageUrl, + cid: form?.cid, + title: form?.title, + date: new Date().toISOString().slice(0, 10), + korean: results?.korean, + fullText: results?.trans_full + } - axios({ - method: "post", - url: 'http://localhost:8080/notice/save', - headers: {'ACCESS-TOKEN': auth.authData.access_token }, - data: data - }) - .then(response => { - if (response.data) { - console.log('success', response.data); - Alert.alert(`The result was saved in Search as [${response.data?.title}]`); - setResults(response.data); - handleOpenSaveForm(); - } - }) - .catch(err => { - console.log('save err', err); - }) - } - }) - .catch(function (error) { - console.log('error',error.response); - }); - } - } - } + axios({ + method: "post", + url: 'http://localhost:8080/notice/save', + headers: {'ACCESS-TOKEN': auth.authData.access_token }, + data: data + }) + .then(response => { + if (response.data) { + console.log('success', response.data); + Alert.alert(`The result was saved in Search as [${response.data?.title}]`); + setResults(response.data); + handleOpenSaveForm(); + } + }) + .catch(err => { + console.log('save err', err); + }) + } + }) + .catch(function (error) { + console.log('error',error.response); + }); + } + } + } const retakePicture = (): void => { setImageUri(""); @@ -277,6 +295,7 @@ export default function TranslateScreen({ navigation }: Navigation) { isTranslateScreen={true} openSaveForm={openSaveForm} handleKorean={handleKorean} + copyToClipboard={copyToClipboard} saveResults={saveResults} retakePicture={retakePicture} handleOpenSaveForm={handleOpenSaveForm} @@ -290,6 +309,7 @@ export default function TranslateScreen({ navigation }: Navigation) { isTranslateScreen={true} openSaveForm={openSaveForm} handleKorean={handleKorean} + copyToClipboard={copyToClipboard} saveResults={saveResults} retakePicture={retakePicture} handleOpenSaveForm={handleOpenSaveForm} diff --git a/react-native/types.ts b/react-native/types.ts index 3e5aeb5..4b69779 100644 --- a/react-native/types.ts +++ b/react-native/types.ts @@ -100,6 +100,7 @@ interface BottomDrawerProps { isTranslateScreen?: boolean, openSaveForm?: boolean, handleKorean?: () => void, + copyToClipboard?: () => void, saveResults?: (form: ResultsForm) => void, closeResults?: () => void, retakePicture?: () => void, diff --git a/react-native/yarn.lock b/react-native/yarn.lock index 6a1def2..7a9d803 100644 --- a/react-native/yarn.lock +++ b/react-native/yarn.lock @@ -3637,6 +3637,11 @@ expo-camera@^12.1.2: "@koale/useworker" "^4.0.2" invariant "^2.2.4" +expo-clipboard@~2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/expo-clipboard/-/expo-clipboard-2.1.1.tgz#77a506eb70918e6a4708760e6cfb0fafdc18192d" + integrity sha512-ay4zv7JGIWT/lqFsonWK8BQ4FuMLaSNFg5gaYJQcIZgZoCyF9f7GRh1HrV3tA0rCPA3i9T6Kt8/8vMMrraH9RQ== + expo-constants@~13.0.0, expo-constants@~13.0.2: version "13.0.2" resolved "https://registry.npmjs.org/expo-constants/-/expo-constants-13.0.2.tgz"