From c91100315d76281467d14eb30db991727d5348bc Mon Sep 17 00:00:00 2001 From: Hyeonae Date: Mon, 23 May 2022 00:17:51 +0900 Subject: [PATCH 01/27] Fix: return eid & event title without the child's name --- .../main/java/com/answer/notinote/Event/domain/Event.java | 7 ++----- .../com/answer/notinote/Event/dto/EventResponseDto.java | 8 ++++++++ .../com/answer/notinote/Notice/dto/NoticeSentenceDto.java | 1 + .../com/answer/notinote/Notice/service/NoticeService.java | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/spring/notinote/src/main/java/com/answer/notinote/Event/domain/Event.java b/spring/notinote/src/main/java/com/answer/notinote/Event/domain/Event.java index 96ed521..6ee5a56 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Event/domain/Event.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Event/domain/Event.java @@ -77,11 +77,8 @@ public void register(EventRegisterDto requestDto) { @Override public int compareTo(Event e) { - if (this.index_start < e.index_start) { - return -1; - } else if (this.index_start > e.index_start) { - return 1; - } + if (this.index_start < e.index_start) return -1; + if (this.index_start > e.index_start) return 1; return 0; } } diff --git a/spring/notinote/src/main/java/com/answer/notinote/Event/dto/EventResponseDto.java b/spring/notinote/src/main/java/com/answer/notinote/Event/dto/EventResponseDto.java index 0dd4260..263f524 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Event/dto/EventResponseDto.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Event/dto/EventResponseDto.java @@ -17,6 +17,14 @@ public class EventResponseDto { public EventResponseDto(Event event) { this.eid = event.getEid(); this.title = event.getTitle(); + + // [이름] 제거 + if (this.title.charAt(0) == '[') { + while(this.title.charAt(0) != ']' || this.title.length() <= 1) + this.title = this.title.substring(1); + this.title = this.title.substring(1); + } + this.description = event.getDescription(); this.date = event.getDate(); } diff --git a/spring/notinote/src/main/java/com/answer/notinote/Notice/dto/NoticeSentenceDto.java b/spring/notinote/src/main/java/com/answer/notinote/Notice/dto/NoticeSentenceDto.java index 04a9b88..755402b 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Notice/dto/NoticeSentenceDto.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Notice/dto/NoticeSentenceDto.java @@ -11,6 +11,7 @@ @Builder public class NoticeSentenceDto { int id; + long eid = -1; String content; LocalDate date; boolean highlight; diff --git a/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java b/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java index 718242c..9a7f748 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java @@ -87,7 +87,7 @@ public String detectText(MultipartFile uploadfile) throws IOException{ public String transText(String korean, String targetLanguage) throws IOException { String text = korean; - String projectId = "notinote-341918"; + String projectId = "solution-challenge-342914"; ArrayList textlist = new ArrayList(); try (TranslationServiceClient client = TranslationServiceClient.create()) { @@ -260,7 +260,6 @@ public NoticeTitleListDto saveNotice(MultipartFile uploadfile, NoticeRequestDto noticeRepository.save(notice); List events = detectEvent(notice, user.getUlanguage()); - List sentences = extractSentenceFromEvent(notice.getTrans_full(), events); return new NoticeTitleListDto(notice, sentences); @@ -302,6 +301,7 @@ public List extractSentenceFromEvent(String text, List String sentence = text.substring(event.getIndex_start(), event.getIndex_end()); NoticeSentenceDto dto = NoticeSentenceDto.builder() .id(id++) + .eid(event.getEid()) .content(sentence) .date(event.getDate()) .highlight(true) From c7b859a7c1085df1547833cc892154cde49da4bf Mon Sep 17 00:00:00 2001 From: hellouz818 Date: Thu, 26 May 2022 16:42:11 +0900 Subject: [PATCH 02/27] [#25] feat: Add cid to Notice --- .../notinote/Notice/controller/NoticeController.java | 9 ++++----- .../answer/notinote/Notice/domain/entity/Notice.java | 10 ++++++++-- .../answer/notinote/Notice/dto/NoticeRequestDto.java | 4 +++- .../answer/notinote/Notice/service/NoticeService.java | 4 ++++ 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/spring/notinote/src/main/java/com/answer/notinote/Notice/controller/NoticeController.java b/spring/notinote/src/main/java/com/answer/notinote/Notice/controller/NoticeController.java index 68e3aed..0f47732 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Notice/controller/NoticeController.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Notice/controller/NoticeController.java @@ -55,12 +55,9 @@ public NoticeOCRDto executeOCR (@RequestPart MultipartFile uploadfile, HttpServl @RequestMapping(value = "/notice/save", method = RequestMethod.POST) public NoticeTitleListDto saveNotice( @RequestPart(value = "uploadfile") MultipartFile uploadfile, - @RequestPart(value = "title") String title, - @RequestPart(value = "date") String stringdate, - @RequestPart(value = "korean") String korean, - @RequestPart(value = "trans_full") String fullText, + @RequestPart(value = "noticeRequestDto") NoticeRequestDto noticeRequestDto, HttpServletRequest request) throws IOException { - + /* LocalDate date = LocalDate.parse(stringdate); NoticeRequestDto noticeRequestDto = NoticeRequestDto.builder() .title(title) @@ -68,6 +65,8 @@ public NoticeTitleListDto saveNotice( .korean(korean) .fullText(fullText) .build(); + + */ NoticeTitleListDto notice_title = noticeService.saveNotice(uploadfile, noticeRequestDto, request); //notice 저장 return notice_title; } diff --git a/spring/notinote/src/main/java/com/answer/notinote/Notice/domain/entity/Notice.java b/spring/notinote/src/main/java/com/answer/notinote/Notice/domain/entity/Notice.java index cf598eb..e082907 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Notice/domain/entity/Notice.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Notice/domain/entity/Notice.java @@ -1,5 +1,6 @@ package com.answer.notinote.Notice.domain.entity; +import com.answer.notinote.Child.domain.Child; import com.answer.notinote.Event.domain.Event; import com.answer.notinote.User.domain.entity.User; import lombok.Builder; @@ -33,9 +34,13 @@ public class Notice { private String trans_full; @ManyToOne - @JoinColumn + @JoinColumn(name = "uid") private User user; + @ManyToOne + @JoinColumn(name = "cid") + private Child child; + private String title; @DateTimeFormat(pattern = "yyyy-MM-dd") @@ -48,7 +53,7 @@ public class Notice { @Builder - public Notice(String nimagename, String nimageoriginal, String nimageurl, String origin_full, String trans_full, LocalDate ndate, String title, User user) { + public Notice(String nimagename, String nimageoriginal, String nimageurl, String origin_full, String trans_full, LocalDate ndate, String title, User user, Child child) { this.nimagename = nimagename; this.nimageoriginal = nimageoriginal; this.nimageurl = nimageurl; @@ -57,6 +62,7 @@ public Notice(String nimagename, String nimageoriginal, String nimageurl, String this.ndate = ndate; this.title = title; this.user = user; + this.child = child; } diff --git a/spring/notinote/src/main/java/com/answer/notinote/Notice/dto/NoticeRequestDto.java b/spring/notinote/src/main/java/com/answer/notinote/Notice/dto/NoticeRequestDto.java index 8303a93..fef55cc 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Notice/dto/NoticeRequestDto.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Notice/dto/NoticeRequestDto.java @@ -13,14 +13,16 @@ public class NoticeRequestDto { private LocalDate date; private String korean; private String fullText; + private Long cid; @Builder - public NoticeRequestDto (String title, LocalDate date, String korean, String fullText){ + public NoticeRequestDto (String title, LocalDate date, String korean, String fullText, Long cid){ this.title = title; this.date = date; this.korean = korean; this.fullText= fullText; + this.cid = cid; } } diff --git a/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java b/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java index 9a7f748..d464b91 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java @@ -1,6 +1,7 @@ package com.answer.notinote.Notice.service; import com.answer.notinote.Auth.token.provider.JwtTokenProvider; +import com.answer.notinote.Child.service.ChildService; import com.answer.notinote.Event.domain.Event; import com.answer.notinote.Notice.dto.NoticeResponseBody; import com.answer.notinote.Event.dto.EventRequestDto; @@ -48,6 +49,8 @@ public class NoticeService { JwtTokenProvider jwtTokenProvider; @Autowired EventService eventService; + @Autowired + ChildService childService; public String detectText(MultipartFile uploadfile) throws IOException{ List requests = new ArrayList<>(); @@ -256,6 +259,7 @@ public NoticeTitleListDto saveNotice(MultipartFile uploadfile, NoticeRequestDto .origin_full(noticeRequestDto.getKorean()) .trans_full(noticeRequestDto.getFullText()) .user(user) + .child(childService.findChildById(noticeRequestDto.getCid())) .build(); noticeRepository.save(notice); From 67916b14f470c7fff28a35f5a53b3ea1b8fd253a Mon Sep 17 00:00:00 2001 From: Suyeon Nam <51125960+mori8@users.noreply.github.com> Date: Sat, 28 May 2022 16:17:26 +0900 Subject: [PATCH 03/27] Sorry: add translated school events dictionary --- fastapi/translated_events.py | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 fastapi/translated_events.py diff --git a/fastapi/translated_events.py b/fastapi/translated_events.py new file mode 100644 index 0000000..7063853 --- /dev/null +++ b/fastapi/translated_events.py @@ -0,0 +1,37 @@ +event_list = ["졸업식", "종업식", "소풍", "입학식", "수학여행", "현장체험학습", "방학식", "겨울방학", "여름방학", "봄방학", "개학", "전시회", "실기대회", + "미술대회", "수학올림피아드", "과학올림피아드", "신체검사", "예방접종", "수련회", "운동회", "견학", "답사", "민방위", "소방훈련", "개교기념일", "축제", + "졸업장 수여식"] +event_list_en = ["Graduation Ceremony", "Closing Ceremony", "Excursion", "Entrance Ceremony", "School Trip", + "Field Experience Study", "Vacation", "Winter Vacation", "Summer Vacation", "Spring Break", + "Opening Ceremony", "Exhibition", "Practical Competition", "Art Competition", + "Mathematics Olympiad", "Science Olympiad", "Physical Examination", "Vaccination", "Retreat", + "Sports Day", "Field Trip", "Exploration", "Civil defense", "Fire drill", "Anniversary of school", + "Festival", "Diploma Award Ceremony"] +event_list_th = ["พิธีสำเร็จการศึกษา", "พิธีปิด", "ทัศนศึกษา", "พิธีรับเข้าเรียน", "ทัศนศึกษา", + "การศึกษาประสบการณ์ภาคสนาม", + "พิธีเช้า", "วันหยุดฤดูหนาว", "วันหยุดฤดูร้อน", "วันหยุดฤดูใบไม้ผลิ", "โรงเรียนเริ่มต้น", "นิทรรศการ", + "การแข่งขันภาคปฏิบัติ", "การประกวดศิลปะ", "คณิตศาสตร์โอลิมปิก", "วิทยาศาสตร์โอลิมปิก", + "การตรวจร่างกาย", + "การฉีดวัคซีน", "หนี", "วันกีฬาสี", "ทัศนศึกษา", "สำรวจ", "ป้องกันภัยพลเรือน", "ซ้อมดับเพลิง", + "ครบรอบโรงเรียน", + "เทศกาล", "พิธีรับปริญญา"] +event_list_km = ["ពិធីចែកសញ្ញាបត្រ", "ពិធីបិទ", "ដំណើរកំសាន្ត", "ពិធីចូលរៀន", "ការធ្វើដំណើរសាលា", "ការសិក្សាបទពិសោធន៍", + "ពិធីអាហារពេលព្រឹក", "វិស្សមកាលរដូវរងារ", "វិស្សមកាលរដូវក្តៅ", "សម្រាកនិទាឃរដូវ", "សាលាចាប់ផ្តើម", + "ការតាំងពិព័រណ៍", + "ការប្រកួតប្រជែងជាក់ស្តែង", "ការប្រកួតសិល្បៈ", "គណិតវិទ្យាអូឡាំពិក", "វិទ្យាសាស្ត្រអូឡាំព្យាដ", + "ការប្រឡងរាងកាយ", + "ការចាក់វ៉ាក់សាំង", "សម្រាក", "ទិវាកីឡា", "ការធ្វើដំណើរវាល។ ", "ការរុករក", "ការការពារស៊ីវិល", + "សមយុទ្ធអគ្គីភ័យ", + "ខួបនៃសាលារៀន", "ពិធីបុណ្យ", "ពិធីបញ្ចប់ការសិក្សា"] +event_list_vi = ["Lễ tốt nghiệp", "Lễ tổng kết", "Chuyến tham quan", "Lễ nhập học", "Chuyến đi học", + "Nghiên cứu trải nghiệm thực tế", "Lễ ăn sáng", "Kỳ nghỉ đông", "Kỳ nghỉ hè", "Kỳ nghỉ xuân", + "Khai giảng", "Triển lãm", "Cuộc thi thực hành", "Cuộc thi nghệ thuật", "Olympic Toán học", + "Olympic Khoa học", "Kiểm tra thể chất", "Tiêm phòng", "Khóa tu", "Ngày hội thể thao", + "Chuyến dã ngoại ", " Khám phá ", " Phòng thủ dân sự ", " Diễn tập chữa cháy ", + " Lễ kỷ niệm thành lập trường ", " Lễ hội ", " Lễ tốt nghiệp "] +event_list_ja = ["卒業式", "従業式", "ピクニック", "入学式", "修学旅行", "現場体験学習", "方程式", "冬休み", "夏休み", "春休み", + "開校", "展示会", "実技大会", "美術大会", "数学オリンピアード", "科学オリンピアド", "身体検査", "予防接種", + "修練会", "運動会", "見学", "回答", "民防衛", "消防訓練", "開校記念日", "祭り", "卒業場授与式"] +event_list_zh = ["毕业典礼", "结业典礼", "远足", "入学典礼", "学校旅行", "实地体验学习", "早餐仪式", "寒假", "暑假", "春假", + "开学", "展览", "实践比赛", "艺术竞赛", "数学奥林匹克", "科学奥林匹克", "体格检查", "疫苗接种", "撤退", + "运动项目", "实地考察", "探索", "民防", "消防演习", "奠基日", "节日", "毕业典礼"] From 45cfcf0975f0052cd47de221530c2a07bedfe793 Mon Sep 17 00:00:00 2001 From: 0hee0 Date: Sat, 28 May 2022 16:30:07 +0900 Subject: [PATCH 04/27] [#11] update: change copy and button composition --- react-native/components/BottomDrawer.tsx | 118 +++++++++++------------ react-native/locales/en.js | 3 +- 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 +- 8 files changed, 70 insertions(+), 69 deletions(-) diff --git a/react-native/components/BottomDrawer.tsx b/react-native/components/BottomDrawer.tsx index 41d77e4..5a59f1e 100644 --- a/react-native/components/BottomDrawer.tsx +++ b/react-native/components/BottomDrawer.tsx @@ -128,63 +128,11 @@ function BottomDrawer(props: BottomDrawerProps) { - {props.showKorean ? i18n.t('korean') : i18n.t('results')} + {props.showKorean ? i18n.t('korean') : i18n.t('translation')} - + - {props.isTranslateScreen && props.handleOpenSaveForm && - <> - - - - - - - {i18n.t('saveResults')} - - - - {i18n.t('child')} - - - - Title - setResultsForm({...resultsForm, ['title']: text})} - /> - - {i18n.t('helpertext')} - - - - - - - - - - - - - - } @@ -331,13 +279,62 @@ function BottomDrawer(props: BottomDrawerProps) { {props.isTranslateScreen && - - {i18n.t('close')} + + {i18n.t('retake')} - - {i18n.t('tryAgain')} - + {props.handleOpenSaveForm && + <> + + {i18n.t('save')} + + + + + {i18n.t('saveResults')} + + + + {i18n.t('child')} + + + + Title + setResultsForm({...resultsForm, ['title']: text})} + /> + + {i18n.t('helpertext')} + + + + + + + + + + + + + + } } @@ -392,9 +389,6 @@ const styles = StyleSheet.create({ highlighted: { backgroundColor: theme.colors.skyblue }, - rightSpace: { - paddingRight: 8 - }, full: { paddingBottom: 96 }, diff --git a/react-native/locales/en.js b/react-native/locales/en.js index 57a26f0..9237d88 100644 --- a/react-native/locales/en.js +++ b/react-native/locales/en.js @@ -8,6 +8,7 @@ export default { start: 'Start NotiNote', /* Home */ translate: 'Translate', + translation: 'Translation', translateDesc: 'Translation and calendar registration are all possible just by taking a picture of the notice.', search: 'Search', searchDesc: 'You can find notices you have translated.', @@ -68,7 +69,7 @@ export default { tip_1: 'You can save the results and check them on the search screen!', registerFailed: 'Failed to add event. Please try again.', authFailed: "Authentication failed. Please try again.", - tryAgain: 'Try Again', + retake: 'Retake', join: 'Join', searchResult: "Search Result", noEvent: "There is no event today!" diff --git a/react-native/locales/ja.js b/react-native/locales/ja.js index 13bf367..4f1e25c 100644 --- a/react-native/locales/ja.js +++ b/react-native/locales/ja.js @@ -6,6 +6,7 @@ export default { swipe: 'スワイプして続行します ≫', start: 'NotiNoteを起動します', translate: '翻訳', + translation: '翻訳文', translateDesc: '通知の写真を撮るだけで、翻訳やカレンダー登録が可能です。', search: '検索', searchDesc: 'あなたが翻訳した通知を見つけることができます。', @@ -62,7 +63,7 @@ export default { gotIt: '了解しました', registerFailed: 'イベントの追加に失敗しました。 もう一度やり直してください。', authFailed: "認証に失敗しました。もう一度やり直してください。", - tryAgain: '再試行', + retake: '再撮影', join: '参加', searchResult: "検索結果", noEvent: "今日はイベントはありません!" diff --git a/react-native/locales/km.js b/react-native/locales/km.js index 04dd827..dcea50c 100644 --- a/react-native/locales/km.js +++ b/react-native/locales/km.js @@ -6,6 +6,7 @@ export default { swipe: 'អូសដើម្បីបន្ត ≫', start: 'ចាប់ផ្តើម NotiNote', translate: 'បកប្រែ', + translation: 'ការបកប្រែ', translateDesc: 'ការបកប្រែ និងការចុះឈ្មោះប្រតិទិនគឺអាចធ្វើទៅបានដោយគ្រាន់តែថតរូបការជូនដំណឹង។', search: 'ស្វែងរក', searchDesc: 'អ្នកអាចស្វែងរកការជូនដំណឹងដែលអ្នកបានបកប្រែ។', @@ -62,7 +63,7 @@ export default { gotIt: 'យល់ហើយ', registerFailed: 'បានបរាជ័យក្នុងការបន្ថែមព្រឹត្តិការណ៍។ សូម​ព្យាយាម​ម្តង​ទៀត។', authFailed: "ការផ្ទៀងផ្ទាត់បានបរាជ័យ។ សូមព្យាយាមម្តងទៀត", - tryAgain: 'ព្យាយាមម្តងទៀត', + retake: 'បាញ់ឡើងវិញ', join: 'ចូលរួម', searchResult: "លទ្ធផលស្វែងរក", noEvent: "មិនមានព្រឹត្តិការណ៍ថ្ងៃនេះទេ!" diff --git a/react-native/locales/ko.js b/react-native/locales/ko.js index 46cc752..477ca6e 100644 --- a/react-native/locales/ko.js +++ b/react-native/locales/ko.js @@ -6,6 +6,7 @@ export default { swipe: '계속하려면 넘겨주세요 ≫', start: 'NotiNote 시작하기', translate: '번역', + translation: '번역문', translateDesc: '가정통신문을 사진으로 찍기만 하면 번역부터 캘린더 등록까지 모두 가능합니다.', search: '검색', searchDesc: '이전에 번역한 가정통신문을 검색할 수 있습니다.', @@ -61,7 +62,7 @@ export default { gotIt: '확인', registerFailed: '이벤트를 추가하지 못했습니다. 다시 시도해 주세요.', authFailed: "인증에 실패했습니다. 다시 시도하십시오.", - tryAgain: '다시 시도', + retake: '다시 촬영', join: '가입', searchResult: "검색 결과", noEvent: "오늘은 이벤트가 없어요!" diff --git a/react-native/locales/th.js b/react-native/locales/th.js index 50e880f..e03c82a 100644 --- a/react-native/locales/th.js +++ b/react-native/locales/th.js @@ -6,6 +6,7 @@ export default { swipe: 'ปัดเพื่อดำเนินการต่อ ≫', start: 'เริ่ม NotiNote', translate: 'แปล', + translation: 'การแปล', translateDesc: 'การแปลและการลงทะเบียนปฏิทินทำได้เพียงแค่ถ่ายรูปประกาศ', ค้นหา: 'ค้นหา', searchDesc: 'คุณสามารถค้นหาประกาศที่คุณแปลได้', @@ -62,7 +63,7 @@ export default { gotIt: 'เข้าใจแล้ว', registerFailed: 'ไม่สามารถเพิ่มเหตุการณ์ได้ กรุณาลองอีกครั้ง.', authFailed: "การตรวจสอบสิทธิ์ล้มเหลว โปรดลองอีกครั้ง", - tryAgain: 'ลองอีกครั้ง', + retake: 'ถ่ายใหม่', join: 'เข้าร่วม', searchResult: "ผลการค้นหา", noEvent: "วันนี้ไม่มีกิจกรรม!" diff --git a/react-native/locales/vn.js b/react-native/locales/vn.js index f650bbd..9772815 100644 --- a/react-native/locales/vn.js +++ b/react-native/locales/vn.js @@ -6,6 +6,7 @@ export default { swipe: 'Vuốt để tiếp tục ≫', start: 'Bắt ​​đầu NotiNote', translate: 'dịch', + translation: 'dịch', translateDesc: 'Bạn có thể làm mọi thứ từ dịch thuật đến đăng ký lịch chỉ bằng cách chụp ảnh thư từ tại nhà.', search: 'tìm kiếm', searchDesc: 'Bạn có thể tìm kiếm thư từ trong gia đình đã được dịch trước đây.', @@ -61,7 +62,7 @@ export default { gotIt: 'Đã hiểu', registerFailed: 'Không thêm được sự kiện. Vui lòng thử lại.', authFailed: "Xác thực không thành công. Vui lòng thử lại.", - tryAgain: 'Thử lại', + retake: 'chụp lại', join: 'Tham gia', searchResult: "Kết quả tìm kiếm", noEvent: "Không có sự kiện hôm nay!" diff --git a/react-native/locales/zh.js b/react-native/locales/zh.js index 66a7b99..f4b8e96 100644 --- a/react-native/locales/zh.js +++ b/react-native/locales/zh.js @@ -6,6 +6,7 @@ export default { swipe: '滑动继续≫', start: '启动 NotiNote', translate: '翻译', + translation: '翻译', translateDesc: '您只需拍摄家庭信件的照片即可完成从翻译到日历登记的所有工作。', search: '搜索', searchDesc: '您可以搜索以前翻译的家庭信件。', @@ -61,7 +62,7 @@ export default { gotIt: '得到它', registerFailed: '添加事件失败。 请再试一次。', authFailed: "验证失败,请重试。", - tryAgain: '再试一次', + retake: '重拍', join: '加入', searchResult: "搜索结果", noEvent: "今天没有活动!" From 4bda8dd672a6ebdf354bdb2a82a4926ab27a9e48 Mon Sep 17 00:00:00 2001 From: mori8 Date: Sat, 28 May 2022 17:13:14 +0900 Subject: [PATCH 05/27] Style: Move Start button to bottom of the swipe view --- react-native/screens/IntroductionScreen.tsx | 250 +++++++++++--------- 1 file changed, 137 insertions(+), 113 deletions(-) diff --git a/react-native/screens/IntroductionScreen.tsx b/react-native/screens/IntroductionScreen.tsx index e04d9bb..1102f54 100644 --- a/react-native/screens/IntroductionScreen.tsx +++ b/react-native/screens/IntroductionScreen.tsx @@ -1,126 +1,150 @@ -import React, { useEffect } from 'react'; -import type { Navigation } from '../types'; -import { StyleSheet, View, Image, SafeAreaView, TouchableHighlight, Alert } from 'react-native'; -import { Text } from 'native-base' -import { theme } from '../core/theme'; -import Swiper from 'react-native-swiper'; -import i18n from 'i18n-js' -import * as WebBrowser from 'expo-web-browser'; -import * as Google from 'expo-auth-session/providers/google'; +import React, { useEffect } from "react"; +import type { Navigation } from "../types"; +import { + StyleSheet, + View, + Image, + SafeAreaView, + TouchableHighlight, + Alert, +} from "react-native"; +import { Text } from "native-base"; +import { theme } from "../core/theme"; +import Swiper from "react-native-swiper"; +import i18n from "i18n-js"; +import * as WebBrowser from "expo-web-browser"; +import * as Google from "expo-auth-session/providers/google"; // import { GOOGLE_CLIENT_ID_WEB } from '@env'; -import { useAuth } from '../contexts/Auth'; -import '../locales/i18n'; +import { useAuth } from "../contexts/Auth"; +import "../locales/i18n"; WebBrowser.maybeCompleteAuthSession(); -const GOOGLE_CLIENT_ID_WEB='1044354965352-6qsilpb0i9ntmhbmktld5h68fphi85v5.apps.googleusercontent.com'; -const GOOGLE_CLIENT_ID_IOS='044354965352-j2jr4f1s04m3jhr2fikacbn5euhh8ndh.apps.googleusercontent.com'; +const GOOGLE_CLIENT_ID_WEB = + "1044354965352-6qsilpb0i9ntmhbmktld5h68fphi85v5.apps.googleusercontent.com"; +const GOOGLE_CLIENT_ID_IOS = + "044354965352-j2jr4f1s04m3jhr2fikacbn5euhh8ndh.apps.googleusercontent.com"; export default function HomeScreen({ navigation }: Navigation) { - const [request, response, promptAsync] = Google.useAuthRequest({ - expoClientId: GOOGLE_CLIENT_ID_WEB, - webClientId: GOOGLE_CLIENT_ID_WEB, - }) - const auth = useAuth(); + const [request, response, promptAsync] = Google.useAuthRequest({ + expoClientId: GOOGLE_CLIENT_ID_WEB, + webClientId: GOOGLE_CLIENT_ID_WEB, + }); + const auth = useAuth(); - useEffect(() => { - if (response?.type === 'success') { - const { authentication } = response; + useEffect(() => { + if (response?.type === "success") { + const { authentication } = response; - if (authentication) { - auth.signIn(authentication?.accessToken); - } - else { - Alert.alert("Authentication failed. Please try again."); - } - } - else { - console.log('fail',response) - } - }, [response]); + if (authentication) { + auth.signIn(authentication?.accessToken); + } else { + Alert.alert("Authentication failed. Please try again."); + } + } else { + console.log("fail", response); + } + }, [response]); - useEffect(() => { - if (auth?.userData?.uroleType === 'GUEST') { - navigation.navigate('Join'); - } else if (auth?.userData?.uroleType === 'USER') { - navigation.navigate('Home'); - } - }, [auth?.userData]); + useEffect(() => { + if (auth?.userData?.uroleType === "GUEST") { + navigation.navigate("Join"); + } else if (auth?.userData?.uroleType === "USER") { + navigation.navigate("Home"); + } + }, [auth?.userData]); - return ( - - - - - - - {i18n.t('first_1')} - - Notinote - - {i18n.t('first_2')} - - - {i18n.t('swipe')} - - - - - {i18n.t('second')} - - {i18n.t('swipe')} - - - - - {i18n.t('third')} - - { - promptAsync(); - }} - > - - {i18n.t('start')} - - - - - - ) + return ( + + + + + + {i18n.t("first_1")} + Notinote + {i18n.t("first_2")} + + + {i18n.t("swipe")} + + + + + + {i18n.t("second")} + + + {i18n.t("swipe")} + + + + + + {i18n.t("third")} + + + + { + promptAsync(); + }} + > + + {i18n.t("start")} + + + + ); } const styles = StyleSheet.create({ - container: { - flex: 1, - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center' - }, - imageStyle: { - width: 320, - height: 320 - }, - highlight: { - color: theme.colors.primary, - }, - swipe: { - marginTop: 100 - }, - description: { - width: "80%", - height: 100 - }, - startButton: { - width: "90%", - backgroundColor: theme.colors.primary, - padding: 10, - borderRadius: 8, - marginTop: 42 - }, - buttonStyle: { - textAlign: "center", - color: "white", - } -}) + container: { + flex: 1, + flexDirection: "column", + justifyContent: "center", + alignItems: "center", + }, + imageStyle: { + width: 320, + height: 320, + }, + highlight: { + color: theme.colors.primary, + }, + swipe: { + marginTop: 100, + }, + description: { + width: "80%", + height: 100, + }, + startButton: { + width: "90%", + backgroundColor: theme.colors.primary, + padding: 10, + borderRadius: 8, + marginTop: 20, + }, + buttonStyle: { + textAlign: "center", + color: "white", + }, + skipButton: { + width: "90%", + marginTop: 28, + }, + skipText: { + textAlign: "center", + color: theme.colors.primary, + }, +}); From 29784ffb4edeed053e322a365d2c823b34019290 Mon Sep 17 00:00:00 2001 From: mori8 Date: Sat, 28 May 2022 17:23:04 +0900 Subject: [PATCH 06/27] Style: move the Start button a little higher --- react-native/screens/IntroductionScreen.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/react-native/screens/IntroductionScreen.tsx b/react-native/screens/IntroductionScreen.tsx index 1102f54..157d665 100644 --- a/react-native/screens/IntroductionScreen.tsx +++ b/react-native/screens/IntroductionScreen.tsx @@ -134,6 +134,7 @@ const styles = StyleSheet.create({ padding: 10, borderRadius: 8, marginTop: 20, + marginBottom: 32, }, buttonStyle: { textAlign: "center", From a2868340c899f7e1ad22aeb450342d1145af76fc Mon Sep 17 00:00:00 2001 From: mori8 Date: Sat, 28 May 2022 17:23:29 +0900 Subject: [PATCH 07/27] Delete swipe message --- react-native/screens/IntroductionScreen.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/react-native/screens/IntroductionScreen.tsx b/react-native/screens/IntroductionScreen.tsx index 157d665..5a3dd20 100644 --- a/react-native/screens/IntroductionScreen.tsx +++ b/react-native/screens/IntroductionScreen.tsx @@ -67,9 +67,6 @@ export default function HomeScreen({ navigation }: Navigation) { Notinote {i18n.t("first_2")} - - {i18n.t("swipe")} - {i18n.t("second")} - - {i18n.t("swipe")} - Date: Sat, 28 May 2022 18:17:09 +0900 Subject: [PATCH 08/27] [#25] feat: Add profile image to Child --- .../src/main/java/com/answer/notinote/Child/domain/Child.java | 4 ++++ .../src/main/java/com/answer/notinote/Child/dto/ChildDto.java | 2 ++ 2 files changed, 6 insertions(+) diff --git a/spring/notinote/src/main/java/com/answer/notinote/Child/domain/Child.java b/spring/notinote/src/main/java/com/answer/notinote/Child/domain/Child.java index 2056bcd..2421bb6 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Child/domain/Child.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Child/domain/Child.java @@ -32,6 +32,9 @@ public class Child extends Timestamped { @Column(length = 20) String cname; + @Column + private Long cprofileImg; + @Column() Long color; @@ -41,6 +44,7 @@ public class Child extends Timestamped { public Child (ChildDto requestDto) { this.cname = requestDto.getCname(); this.color = requestDto.getColor(); + this.cprofileImg = requestDto.getCprofileImg(); } public void setUser(User user) { diff --git a/spring/notinote/src/main/java/com/answer/notinote/Child/dto/ChildDto.java b/spring/notinote/src/main/java/com/answer/notinote/Child/dto/ChildDto.java index 831dc72..5ed8031 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Child/dto/ChildDto.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Child/dto/ChildDto.java @@ -23,12 +23,14 @@ public class ChildDto { Long cid; String cname; Long color; + Long cprofileImg; List events = new ArrayList<>(); public ChildDto(Child child) { this.cid = child.getCid(); this.cname = child.getCname(); this.color = child.getColor(); + this.cprofileImg = child.getCprofileImg(); for(Event event : child.getEvents()) { if (event.isRegistered() && event.getDate().isEqual(LocalDate.now())) From eecd325bc0e06a6eb01bae142326a690fcf242fb Mon Sep 17 00:00:00 2001 From: Hyeonae Date: Sat, 28 May 2022 21:24:28 +0900 Subject: [PATCH 09/27] [#5] fix: add AuthController and refreshToken api MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - userController에서 authController 분리 - jwtToken -> accessToken으로 명칭 변경 - /refresh api 추가 --- .../Auth/controller/AuthController.java | 54 ++++++++ .../Auth/filter/JwtAuthenticationFilter.java | 23 +--- .../notinote/Auth/service/AuthService.java | 125 ++++++++++++++++++ .../{OAuthService.java => OAuth2Service.java} | 4 +- .../Auth/service/RefreshTokenService.java | 26 ---- .../Auth/token/provider/JwtTokenProvider.java | 52 +++++--- .../Config/security/WebSecurityConfig.java | 3 +- .../answer/notinote/Exception/ErrorCode.java | 4 +- .../Notice/controller/NoticeController.java | 3 +- .../Notice/service/NoticeService.java | 2 +- .../Search/service/SearchService.java | 4 +- .../User/controller/UserController.java | 71 +--------- .../notinote/User/service/UserService.java | 74 ++--------- spring/notinote/tokens/StoredCredential | Bin 0 -> 1071 bytes 14 files changed, 234 insertions(+), 211 deletions(-) create mode 100644 spring/notinote/src/main/java/com/answer/notinote/Auth/controller/AuthController.java create mode 100644 spring/notinote/src/main/java/com/answer/notinote/Auth/service/AuthService.java rename spring/notinote/src/main/java/com/answer/notinote/Auth/service/{OAuthService.java => OAuth2Service.java} (94%) delete mode 100644 spring/notinote/src/main/java/com/answer/notinote/Auth/service/RefreshTokenService.java create mode 100644 spring/notinote/tokens/StoredCredential diff --git a/spring/notinote/src/main/java/com/answer/notinote/Auth/controller/AuthController.java b/spring/notinote/src/main/java/com/answer/notinote/Auth/controller/AuthController.java new file mode 100644 index 0000000..936f069 --- /dev/null +++ b/spring/notinote/src/main/java/com/answer/notinote/Auth/controller/AuthController.java @@ -0,0 +1,54 @@ +package com.answer.notinote.Auth.controller; + +import com.answer.notinote.Auth.service.AuthService; +import com.answer.notinote.User.dto.JoinRequestDto; +import com.answer.notinote.User.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@RestController +@RequiredArgsConstructor +public class AuthController { + + private final AuthService authService; + + /** + * oauth2 로그인을 진행합니다. + * @param token + * @return + */ + @GetMapping("/login/oauth2") + public ResponseEntity oauthLogin(HttpServletResponse response, @RequestHeader("Authorization") String token) { + return ResponseEntity.ok(authService.oauthLogin(response, token)); + } + + /** + * 회원가입 폼 정보를 받아 유저의 권한을 USER로 바꾸고 로그인을 진행합니다. + * @param requestDto + * @return + */ + @PostMapping("/join") + public ResponseEntity join(HttpServletResponse response, @RequestBody JoinRequestDto requestDto) { + return ResponseEntity.ok(authService.join(response, requestDto)); + } + + @PostMapping("/refresh") + public ResponseEntity refresh(HttpServletRequest request, HttpServletResponse response) { + + return ResponseEntity.ok(authService.refreshToken(request, response)); + } + + /** + * 회원을 로그아웃합니다. + * @param request + * @return + */ + @DeleteMapping("/logout") + public ResponseEntity logout(HttpServletRequest request) { + return ResponseEntity.ok(authService.logout(request)); + } +} diff --git a/spring/notinote/src/main/java/com/answer/notinote/Auth/filter/JwtAuthenticationFilter.java b/spring/notinote/src/main/java/com/answer/notinote/Auth/filter/JwtAuthenticationFilter.java index 04cf14a..9df0cf5 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Auth/filter/JwtAuthenticationFilter.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Auth/filter/JwtAuthenticationFilter.java @@ -21,27 +21,10 @@ public class JwtAuthenticationFilter extends GenericFilterBean { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - String jwtToken = jwtTokenProvider.resolveToken((HttpServletRequest) request); - String refreshToken = jwtTokenProvider.resolveRefreshToken((HttpServletRequest) request); + String accessToken = jwtTokenProvider.resolveAccessToken((HttpServletRequest) request); + if (accessToken != null && jwtTokenProvider.validateToken(accessToken)) + setAuthentication(accessToken); - if (jwtToken != null) { - if (jwtTokenProvider.validateToken(jwtToken)) { - // jwt token이 유효한 경우 - setAuthentication(jwtToken); - } - else { - if (refreshToken != null) { - if (jwtTokenProvider.validateToken(refreshToken) && jwtTokenProvider.existsRefreshToken(refreshToken)) { - // jwt token이 만료되고, refresh token이 유효한 경우 - String email = jwtTokenProvider.getUserEmail(refreshToken); - String newToken = jwtTokenProvider.createToken(email); - jwtTokenProvider.setHeaderToken((HttpServletResponse) response, newToken); - - setAuthentication(newToken); - } - } - } - } chain.doFilter(request, response); } diff --git a/spring/notinote/src/main/java/com/answer/notinote/Auth/service/AuthService.java b/spring/notinote/src/main/java/com/answer/notinote/Auth/service/AuthService.java new file mode 100644 index 0000000..ff7ad05 --- /dev/null +++ b/spring/notinote/src/main/java/com/answer/notinote/Auth/service/AuthService.java @@ -0,0 +1,125 @@ +package com.answer.notinote.Auth.service; + +import com.answer.notinote.Auth.data.ProviderType; +import com.answer.notinote.Auth.data.RoleType; +import com.answer.notinote.Auth.repository.RefreshTokenRepository; +import com.answer.notinote.Auth.token.RefreshToken; +import com.answer.notinote.Auth.token.provider.JwtTokenProvider; +import com.answer.notinote.Auth.userdetails.GoogleUser; +import com.answer.notinote.Child.service.ChildService; +import com.answer.notinote.Exception.CustomException; +import com.answer.notinote.Exception.ErrorCode; +import com.answer.notinote.User.domain.entity.User; +import com.answer.notinote.User.domain.repository.UserRepository; +import com.answer.notinote.User.dto.JoinRequestDto; +import com.answer.notinote.User.dto.UserResponseDto; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +@Service +@RequiredArgsConstructor +public class AuthService { + private final OAuth2Service oAuthService; + private final ChildService childService; + private final UserRepository userRepository; + private final RefreshTokenRepository refreshTokenRepository; + private final JwtTokenProvider jwtTokenProvider; + + public UserResponseDto oauthLogin(HttpServletResponse response, String token) { + ResponseEntity userInfoResponse = oAuthService.createGetRequest(token); + GoogleUser googleUser = oAuthService.getUserInfo(userInfoResponse); + + User user = userRepository.findByUemail(googleUser.getEmail()).orElse(null); + if (user == null) { + user = User.builder() + .uemail(googleUser.getEmail()) + .username(googleUser.getName()) + .uroleType(RoleType.GUEST) + .uproviderType(ProviderType.GOOGLE) + .build(); + userRepository.save(user); + } + else { + issueToken(response, user); + } + + return new UserResponseDto(user); + } + + @Transactional + public UserResponseDto join(HttpServletResponse response, JoinRequestDto requestDto) { + User user = userRepository.findById(requestDto.getUid()).orElseThrow( + () -> new CustomException(ErrorCode.USER_NOT_FOUND) + ); + + requestDto.getUchildren().forEach( childDto -> childService.create(childDto, user)); + + if (user.getUroleType() == RoleType.GUEST) { + user.setUsername(requestDto.getUsername()); + user.setUlanguage(requestDto.getUlanguage()); + user.setUroleType(RoleType.USER); + user.setUprofileImg(requestDto.getUprofileImg()); + userRepository.save(user); + + issueToken(response, user); + return new UserResponseDto(user); + } + else { + throw new CustomException(ErrorCode.USER_DUPLICATED); + } + } + + private void issueToken(HttpServletResponse response, User user) { + String accessToken = jwtTokenProvider.createToken(user); + String refreshToken = jwtTokenProvider.createRefreshToken(user); + + jwtTokenProvider.setAccessToken(response, accessToken); + jwtTokenProvider.setRefreshToken(response, refreshToken); + } + + @Transactional + public String refreshToken(HttpServletRequest request, HttpServletResponse response) { + String accessToken = jwtTokenProvider.resolveAccessToken(request); + String refreshToken = jwtTokenProvider.resolveRefreshToken(request); + + boolean validateRefreshToken = jwtTokenProvider.validateToken(refreshToken) && jwtTokenProvider.existsRefreshToken(refreshToken); + if (jwtTokenProvider.validateTokenExpired(accessToken) && validateRefreshToken) { + String email = jwtTokenProvider.getUserEmail(refreshToken); + User user = findUserByEmail(email); + + String newAccessToken = jwtTokenProvider.createToken(user); + jwtTokenProvider.setAccessToken(response, newAccessToken); + + return "ok"; + } else { + throw new CustomException(ErrorCode.TOKEN_NOT_EXPIRED); + } + } + + @Transactional + public Long logout(HttpServletRequest request) { + String token = jwtTokenProvider.resolveAccessToken(request); + String email = jwtTokenProvider.getUserEmail(token); + + User user = findUserByEmail(email); + + RefreshToken refreshToken = refreshTokenRepository.findByUser(user).orElseThrow( + () -> new CustomException(ErrorCode.TOKEN_NOT_FOUND) + ); + refreshTokenRepository.delete(refreshToken); + + return user.getUid(); + } + + private User findUserByEmail(String email) { + return userRepository.findByUemail(email).orElseThrow( + () -> new CustomException(ErrorCode.USER_NOT_FOUND) + ); + } + +} diff --git a/spring/notinote/src/main/java/com/answer/notinote/Auth/service/OAuthService.java b/spring/notinote/src/main/java/com/answer/notinote/Auth/service/OAuth2Service.java similarity index 94% rename from spring/notinote/src/main/java/com/answer/notinote/Auth/service/OAuthService.java rename to spring/notinote/src/main/java/com/answer/notinote/Auth/service/OAuth2Service.java index 55ba6fa..fdf73df 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Auth/service/OAuthService.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Auth/service/OAuth2Service.java @@ -15,7 +15,7 @@ @Service @RequiredArgsConstructor -public class OAuthService { +public class OAuth2Service { private final ObjectMapper objectMapper; private final RestTemplate restTemplate = new RestTemplate(); @@ -32,7 +32,7 @@ public ResponseEntity createGetRequest(String oAuthToken) { return restTemplate.exchange(url, HttpMethod.GET, request, String.class); } catch (Exception e) { - throw new CustomException(ErrorCode.TOKEN_INVALID); + throw new CustomException(ErrorCode.TOKEN_EXPIRED); } } diff --git a/spring/notinote/src/main/java/com/answer/notinote/Auth/service/RefreshTokenService.java b/spring/notinote/src/main/java/com/answer/notinote/Auth/service/RefreshTokenService.java deleted file mode 100644 index 0581805..0000000 --- a/spring/notinote/src/main/java/com/answer/notinote/Auth/service/RefreshTokenService.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.answer.notinote.Auth.service; - -import com.answer.notinote.Auth.repository.RefreshTokenRepository; -import com.answer.notinote.Auth.token.RefreshToken; -import com.answer.notinote.Exception.CustomException; -import com.answer.notinote.Exception.ErrorCode; -import com.answer.notinote.User.domain.entity.User; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -public class RefreshTokenService { - - private final RefreshTokenRepository refreshTokenRepository; - - @Transactional - public void delete(User user) { - RefreshToken token = refreshTokenRepository.findByUser(user).orElseThrow( - () -> new CustomException(ErrorCode.TOKEN_NOT_FOUND) - ); - - refreshTokenRepository.delete(token); - } -} diff --git a/spring/notinote/src/main/java/com/answer/notinote/Auth/token/provider/JwtTokenProvider.java b/spring/notinote/src/main/java/com/answer/notinote/Auth/token/provider/JwtTokenProvider.java index c6739a8..4a9dbbf 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Auth/token/provider/JwtTokenProvider.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Auth/token/provider/JwtTokenProvider.java @@ -1,7 +1,7 @@ package com.answer.notinote.Auth.token.provider; -import com.answer.notinote.Auth.data.RoleType; import com.answer.notinote.Auth.repository.RefreshTokenRepository; +import com.answer.notinote.Auth.token.RefreshToken; import com.answer.notinote.Exception.CustomException; import com.answer.notinote.Exception.ErrorCode; import com.answer.notinote.User.domain.entity.User; @@ -33,14 +33,12 @@ public class JwtTokenProvider { @Value("${jwt.secret}") private String secretKey; - @Value("${jwt.refresh}") - private String refreshKey; + private long accessTokenValidTime = 60 * 60 * 1000L; + private long refreshTokenValidTime = 300 * 60 * 1000L; - private long tokenValidTime = 60 * 60 * 1000L; - private long refreshValidTime = 300 * 60 * 1000L; + private String accessTokenHeader = "Access-Token"; - private String tokenHeader = "JWT_TOKEN"; - private String refreshHeader = "REFRESH_TOKEN"; + private String refreshTokenHeader = "Refresh-Token"; private final UserDetailsService userDetailsService; private final RefreshTokenRepository refreshTokenRepository; @@ -51,15 +49,17 @@ public class JwtTokenProvider { @PostConstruct protected void init() { secretKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); - refreshKey = Base64.getEncoder().encodeToString(refreshKey.getBytes()); } - public String createToken(String email) { - return convertToToken(email, tokenValidTime, secretKey); + public String createToken(User user) { + return convertToToken(user.getUemail(), accessTokenValidTime, secretKey); } - public String createRefreshToken(String email) { - return convertToToken(email, refreshValidTime, refreshKey); + public String createRefreshToken(User user) { + String refreshToken = convertToToken(user.getUemail(), refreshTokenValidTime, secretKey); + refreshTokenRepository.save(new RefreshToken(user, refreshToken)); + + return refreshToken; } // 토큰에서 인증 정보 조회 @@ -68,25 +68,24 @@ public Authentication getAuthentication(String token) { return new UsernamePasswordAuthenticationToken(userDetails, "", userDetails.getAuthorities()); } - // 토큰에서 회원 정보 추출 public String getUserEmail(String token) { return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject(); } - public String resolveToken(HttpServletRequest request) { - return request.getHeader(tokenHeader); + public String resolveAccessToken(HttpServletRequest request) { + return request.getHeader(accessTokenHeader); } - public String resolveRefreshToken(HttpServletRequest request) { - return request.getHeader(refreshHeader); + public void setAccessToken(HttpServletResponse response, String token) { + response.setHeader(accessTokenHeader, token); } - public void setHeaderToken(HttpServletResponse response, String token) { - response.setHeader(tokenHeader, token); + public String resolveRefreshToken(HttpServletRequest request) { + return request.getHeader(refreshTokenHeader); } - public void setHeaderRefreshToken(HttpServletResponse response, String token) { - response.setHeader(refreshHeader, token); + public void setRefreshToken(HttpServletResponse response, String token) { + response.setHeader(refreshTokenHeader, token); } // 토큰의 유효성 & 만료일자 확인 @@ -108,6 +107,17 @@ public boolean validateToken(String token) { return false; } + public boolean validateTokenExpired(String token) { + try { + Jws claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token); + return claims.getBody().getExpiration().before(new Date()); + } catch(ExpiredJwtException e) { + return true; + } catch (Exception e) { + return false; + } + } + private String convertToToken(String email, Long validTime, String key) { User user = userRepository.findByUemail(email).orElseThrow( () -> new CustomException(ErrorCode.USER_NOT_FOUND) diff --git a/spring/notinote/src/main/java/com/answer/notinote/Config/security/WebSecurityConfig.java b/spring/notinote/src/main/java/com/answer/notinote/Config/security/WebSecurityConfig.java index 24d7211..309d30b 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Config/security/WebSecurityConfig.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Config/security/WebSecurityConfig.java @@ -21,7 +21,6 @@ @RequiredArgsConstructor @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { - private final JwtTokenProvider jwtTokenProvider; @Override @@ -35,7 +34,7 @@ protected void configure(HttpSecurity http) throws Exception { .and() // 모두 접근 가능한 URL .authorizeRequests() - .antMatchers("/","/login/oauth2","/login", "/join", + .antMatchers("/","/login/oauth2","/login", "/join", "/refresh", "/swagger-ui.html", "/swagger/**", "/swagger-resources/**", "/webjars/**", "/v2/api-docs").permitAll() .and() // USER만 접근 가능한 URL diff --git a/spring/notinote/src/main/java/com/answer/notinote/Exception/ErrorCode.java b/spring/notinote/src/main/java/com/answer/notinote/Exception/ErrorCode.java index c8b28de..057082d 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Exception/ErrorCode.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Exception/ErrorCode.java @@ -10,8 +10,8 @@ public enum ErrorCode { USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 계정이 존재하지 않습니다."), USER_DUPLICATED(HttpStatus.CONFLICT, "이미 해당 계정의 유저가 존재합니다."), TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "토큰이 존재하지 않습니다."), - TOKEN_INVALID(HttpStatus.BAD_REQUEST, "액세스 토큰이 만료되었습니다."), - NOT_SUPPORTED_TYPE(HttpStatus.CONFLICT, "지원하지 않는 로그인 형식입니다"), + TOKEN_EXPIRED(HttpStatus.BAD_REQUEST, "액세스 토큰이 만료되었습니다."), + TOKEN_NOT_EXPIRED(HttpStatus.BAD_REQUEST, "액세스 토큰이 만료되지 않았습니다."), NOT_FOUND(HttpStatus.NOT_FOUND, "해당 객체가 존재하지 않습니다."), ; diff --git a/spring/notinote/src/main/java/com/answer/notinote/Notice/controller/NoticeController.java b/spring/notinote/src/main/java/com/answer/notinote/Notice/controller/NoticeController.java index 0f47732..71a0d64 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Notice/controller/NoticeController.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Notice/controller/NoticeController.java @@ -17,7 +17,6 @@ import javax.servlet.http.HttpServletRequest; import java.io.IOException; -import java.time.LocalDate; import java.util.List; @@ -39,7 +38,7 @@ public NoticeController(NoticeService noticeService) { @RequestMapping(value = "/notice/ocr", method = RequestMethod.POST) public NoticeOCRDto executeOCR (@RequestPart MultipartFile uploadfile, HttpServletRequest userrequest) throws IOException { - String token = jwtTokenProvider.resolveToken(userrequest); + String token = jwtTokenProvider.resolveAccessToken(userrequest); String email = jwtTokenProvider.getUserEmail(token); User user = userService.findUserByEmail(email); String targetLanguage = user.getUlanguage(); diff --git a/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java b/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java index d464b91..8830c94 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Notice/service/NoticeService.java @@ -237,7 +237,7 @@ public List detectEvent(Notice notice, String language) throws JsonProces public NoticeTitleListDto saveNotice(MultipartFile uploadfile, NoticeRequestDto noticeRequestDto, HttpServletRequest request) throws IOException{ //요청한 사용자 확인 - String token = jwtTokenProvider.resolveToken(request); + String token = jwtTokenProvider.resolveAccessToken(request); String useremail = jwtTokenProvider.getUserEmail(token); User user = userRepository.findByUemail(useremail).orElseThrow(IllegalArgumentException::new); diff --git a/spring/notinote/src/main/java/com/answer/notinote/Search/service/SearchService.java b/spring/notinote/src/main/java/com/answer/notinote/Search/service/SearchService.java index 4d640eb..d08f51f 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/Search/service/SearchService.java +++ b/spring/notinote/src/main/java/com/answer/notinote/Search/service/SearchService.java @@ -38,7 +38,7 @@ public class SearchService { public List searchList(HttpServletRequest request){ - String token = jwtTokenProvider.resolveToken(request); + String token = jwtTokenProvider.resolveAccessToken(request); String useremail = jwtTokenProvider.getUserEmail(token); User user = userRepository.findByUemail(useremail).orElseThrow(IllegalArgumentException::new); @@ -74,7 +74,7 @@ public List searchList(HttpServletRequest request){ public SearchDetailDto searchDetailList(String date, HttpServletRequest request) { - String token = jwtTokenProvider.resolveToken(request); + String token = jwtTokenProvider.resolveAccessToken(request); String useremail = jwtTokenProvider.getUserEmail(token); User user = userRepository.findByUemail(useremail).orElseThrow(IllegalArgumentException::new); LocalDate trans_date = LocalDate.parse(date, DateTimeFormatter.ISO_DATE); diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/controller/UserController.java b/spring/notinote/src/main/java/com/answer/notinote/User/controller/UserController.java index e46b7c0..98e7b5f 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/User/controller/UserController.java +++ b/spring/notinote/src/main/java/com/answer/notinote/User/controller/UserController.java @@ -1,12 +1,9 @@ package com.answer.notinote.User.controller; -import com.answer.notinote.Auth.data.RoleType; import com.answer.notinote.Auth.repository.RefreshTokenRepository; -import com.answer.notinote.Auth.token.RefreshToken; import com.answer.notinote.Auth.token.provider.JwtTokenProvider; import com.answer.notinote.User.dto.JoinRequestDto; import com.answer.notinote.User.domain.entity.User; -import com.answer.notinote.User.dto.UserResponseDto; import com.answer.notinote.User.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -18,68 +15,18 @@ @CrossOrigin @RestController @RequiredArgsConstructor -@RequestMapping("") +@RequestMapping("/user") public class UserController { private final UserService userService; private final JwtTokenProvider jwtTokenProvider; - private final RefreshTokenRepository refreshTokenRepository; - - /** - * oauth2 로그인을 진행합니다. - * @param response - * @param token - * @return - */ - @GetMapping("/login/oauth2") - public ResponseEntity oauthLogin(HttpServletResponse response, @RequestHeader("Authorization") String token) { - User user = userService.oauthLogin(token); - - issueJwtToken(response, user); - return ResponseEntity.ok(new UserResponseDto(user)); - } - - /** - * 회원가입 폼 정보를 받아 유저의 권한을 USER로 바꾸고 로그인을 진행합니다. - * @param requestDto - * @return - */ - @PostMapping("/join") - public ResponseEntity join(HttpServletResponse response, @RequestBody JoinRequestDto requestDto) { - User user = userService.join(requestDto); - issueJwtToken(response, user); - - return ResponseEntity.ok(new UserResponseDto(user)); - } - - /** - * 로그인한 유저의 ID를 받아 유저 정보와 JWT Token을 반환합니다. - * @param id - * @return - */ - @GetMapping("/login/{id}") - public ResponseEntity login(HttpServletResponse response, @PathVariable("id") long id) { - User user = userService.findUserById(id); - issueJwtToken(response, user); - return ResponseEntity.ok(new UserResponseDto(user)); - } - - /** - * 회원을 로그아웃합니다. - * @param request - * @return - */ - @DeleteMapping("/logout") - public ResponseEntity logout(HttpServletRequest request) { - return ResponseEntity.ok(userService.logout(request)); - } /** * 회원을 탙퇴 처리 합니다. * @param id * @return */ - @DeleteMapping("/user") + @DeleteMapping public Long delete(@RequestParam Long id) { return userService.delete(id); } @@ -89,22 +36,12 @@ public Long delete(@RequestParam Long id) { * @param request * @return */ - @GetMapping("/user/children") + @GetMapping("/children") public ResponseEntity getChildren(HttpServletRequest request) { - String token = jwtTokenProvider.resolveToken(request); + String token = jwtTokenProvider.resolveAccessToken(request); String email = jwtTokenProvider.getUserEmail(token); User user = userService.findUserByEmail(email); return ResponseEntity.ok(userService.findChildrenByUserId(user.getUid())); } - - private void issueJwtToken(HttpServletResponse response, User user) { - if(user.getUroleType() == RoleType.USER) { - String jwtToken = jwtTokenProvider.createToken(user.getUemail()); - String refreshToken = jwtTokenProvider.createRefreshToken(user.getUemail()); - jwtTokenProvider.setHeaderToken(response, jwtToken); - jwtTokenProvider.setHeaderRefreshToken(response, refreshToken); - refreshTokenRepository.save(new RefreshToken(user, refreshToken)); - } - } } diff --git a/spring/notinote/src/main/java/com/answer/notinote/User/service/UserService.java b/spring/notinote/src/main/java/com/answer/notinote/User/service/UserService.java index 428c814..7705bef 100644 --- a/spring/notinote/src/main/java/com/answer/notinote/User/service/UserService.java +++ b/spring/notinote/src/main/java/com/answer/notinote/User/service/UserService.java @@ -1,8 +1,9 @@ package com.answer.notinote.User.service; import com.answer.notinote.Auth.data.ProviderType; -import com.answer.notinote.Auth.service.RefreshTokenService; -import com.answer.notinote.Auth.service.OAuthService; +import com.answer.notinote.Auth.repository.RefreshTokenRepository; +import com.answer.notinote.Auth.service.OAuth2Service; +import com.answer.notinote.Auth.token.RefreshToken; import com.answer.notinote.Auth.token.provider.JwtTokenProvider; import com.answer.notinote.Auth.userdetails.GoogleUser; import com.answer.notinote.Child.domain.Child; @@ -15,75 +16,20 @@ import com.answer.notinote.Auth.data.RoleType; import com.answer.notinote.User.domain.entity.User; import com.answer.notinote.User.domain.repository.UserRepository; +import com.answer.notinote.User.dto.UserResponseDto; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.util.List; @Service @RequiredArgsConstructor public class UserService { - - private final OAuthService oAuthService; - private final RefreshTokenService refreshTokenService; - private final ChildService childService; - private final UserRepository userRepository; - private final JwtTokenProvider jwtTokenProvider; - - public User oauthLogin(String token) { - ResponseEntity userInfoResponse = oAuthService.createGetRequest(token); - GoogleUser googleUser = oAuthService.getUserInfo(userInfoResponse); - - User user = userRepository.findByUemail(googleUser.getEmail()).orElse(null); - if (user == null) { - user = User.builder() - .uemail(googleUser.getEmail()) - .username(googleUser.getName()) - .uroleType(RoleType.GUEST) - .uproviderType(ProviderType.GOOGLE) - .build(); - userRepository.save(user); - } - - return user; - } - - @Transactional - public User join(JoinRequestDto requestDto) { - User user = findUserById(requestDto.getUid()); - - requestDto.getUchildren().forEach( childDto -> { - childService.create(childDto, user); - }); - - if (user.getUroleType() == RoleType.GUEST) { - user.setUsername(requestDto.getUsername()); - user.setUlanguage(requestDto.getUlanguage()); - user.setUroleType(RoleType.USER); - user.setUprofileImg(requestDto.getUprofileImg()); - userRepository.save(user); - - return user; - } - else { - throw new CustomException(ErrorCode.USER_DUPLICATED); - } - } - - @Transactional - public Long logout(HttpServletRequest request) { - String token = jwtTokenProvider.resolveToken(request); - String email = jwtTokenProvider.getUserEmail(token); - - User user = findUserByEmail(email); - refreshTokenService.delete(user); - - return user.getUid(); - } @Transactional public Long delete(Long id) { @@ -95,12 +41,6 @@ public Long delete(Long id) { return id; } - public User findUserById(Long id) { - return userRepository.findById(id).orElseThrow( - () -> new CustomException(ErrorCode.USER_NOT_FOUND) - ); - } - public User findUserByEmail(String email) { return userRepository.findByUemail(email).orElseThrow( () -> new CustomException(ErrorCode.USER_NOT_FOUND) @@ -108,7 +48,9 @@ public User findUserByEmail(String email) { } public ChildrenResponseDto findChildrenByUserId(Long id) { - User user = findUserById(id); + User user = userRepository.findById(id).orElseThrow( + () -> new CustomException(ErrorCode.USER_NOT_FOUND) + );; ChildrenResponseDto response = new ChildrenResponseDto(); for (Child child : user.getUchildren()) { diff --git a/spring/notinote/tokens/StoredCredential b/spring/notinote/tokens/StoredCredential new file mode 100644 index 0000000000000000000000000000000000000000..409ed97ea9678bbb53cead4817109a42370291bd GIT binary patch literal 1071 zcma)5zi-n(6uzV_lop}F01^^N2nL46`O&7W)uFg94sPv)#A!;y02k+CJF$Jv`JC7V zA+aDN1{lge01KcZR+v!5!ob4H(n=sPASM<@&M8vMf_l=u<-PCw-uvF&)=x0v6L7K( zdyosLWpme{Z>rE6Ir?S$!|B_fhd@#SV>X6O3F;IR862Y~LB5IYX6P+k0r5HBA7ycZ z-2?^0fsaT)K=S6|)~_?akBnA#lK_Ms07LJ$_T+Lpb~poL!$utREKavA)Z}m+ zbDYMsP*Y@=Wh`jNGVCOhU|0d;P}h;~S8<0W!6_x~K5awSNNbc>t}(BGvncc|0;z@F zs^uWnvTe&pI(C~r1rrAl3LexC+gR^VaA7YKJCSC^dO!%1NXJP(t+2?bj}z329T>=W z!99=&Jqn&jFvoK+BUDHes;Oee#Me7DY5i&~tSoiiS}j|W=9+3G&#pKtEyLt<^;?-B z(w3*zq`K12nbyp*4hxOawVdu18>?7W^Vuw#t`(frTF{Yt{7TmgoX}qFLF{!_^hj8o z>CV}GRTn#QhVNFeszh`7s8Gq4TKwE}_PSPH;&B@~g0Z$3S+ZDXn?5nvbnF#3nEcVV zC!Zc)+)0AOG8pMWJ3t`;6Z=KQz-b`zVC(6*@y*>we;1j!Ki1iPz}PPDznguxxPe<- z1u^FcbZKl`YxTwCn(*N`NAM_=S0p>%GDNY|&@lp}HEohM#iXP^^ zYj@s{$L$l4Iy6O$eS7}d=BuUp-92pnKUksRvw3w|4p4xaG1es5wKfo8(~H4E Date: Sun, 29 May 2022 00:40:02 +0900 Subject: [PATCH 10/27] Feat: add 'All' button to show all childrens schedules --- react-native/screens/HomeScreen.tsx | 49 +++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/react-native/screens/HomeScreen.tsx b/react-native/screens/HomeScreen.tsx index f0676bb..b81ecdb 100644 --- a/react-native/screens/HomeScreen.tsx +++ b/react-native/screens/HomeScreen.tsx @@ -27,12 +27,22 @@ export default function HomeScreen({ navigation }: Navigation) { ] } ); - const [nowSelectedChildId, setNowSelectedChildId] = useState(1); + const SHOW_ALL = -1; + const [nowSelectedChildId, setNowSelectedChildId] = useState(SHOW_ALL); const [user, setUser] = useState(); const auth = useAuth(); + useEffect(()=> { setUser(auth?.userData); + // setUser({ + // uid: 1, + // username: "Soo", + // uemail: "kaithape@gmail.com", + // uprofileImg: 1, + // ulanguage: "english", + // uchildren:[{cid: 1, cname:"Soo"}, {cid: 2, cname:"Hee"}] + // }) if (auth?.authData?.jwt_token) { fetch('http://localhost:8080/user/children', { @@ -84,6 +94,13 @@ export default function HomeScreen({ navigation }: Navigation) { Today's Events + handleNowSelectedChildId(-1)}> + All + {events.children?.map((notice, index) => - {events.children.filter(child => child.cid === nowSelectedChildId).length > 0 && events.children.filter(child => child.cid === nowSelectedChildId)[0].events?.length ? ( - events.children?.filter(child => child.cid === nowSelectedChildId)[0].events?.map((item, index) => - - {/* {item.time} */} - {index+1 + '. ' + item} + {nowSelectedChildId === SHOW_ALL ? events.children.map((notice, index) => + + {notice.events.map((event, index) => { + return ( + {`[${notice.cname}] ` + event} + ) + })} + + ) : events.children.filter(notice => notice.cid === nowSelectedChildId).map((notice, index) => { + return ( + + {notice.events.map((event, index) => { + return ( + {event} + ) + })} ) - ) : ( - - - There is no event today! - - ) - } + } + )} From 349c886211bc101d6f9a31fd20f7077d53217ddb Mon Sep 17 00:00:00 2001 From: mori8 Date: Sun, 29 May 2022 01:04:21 +0900 Subject: [PATCH 11/27] Style: Set screen names more obvious --- react-native/App.tsx | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/react-native/App.tsx b/react-native/App.tsx index e6b06ca..04fdba0 100644 --- a/react-native/App.tsx +++ b/react-native/App.tsx @@ -76,10 +76,9 @@ export default function App() { component={HomeScreen} options={{ headerStyle: { backgroundColor: theme.colors.primary }, - title: "NotiNote", + title: "Home", headerBackVisible: false, headerRight: () => , - headerTitle: (props) => ( // App Logo ( // App Logo + + ), }} /> ( // App Logo + + ), }} /> Date: Sun, 29 May 2022 01:43:57 +0900 Subject: [PATCH 12/27] Delete Logout button & Place profile button --- react-native/screens/HomeScreen.tsx | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/react-native/screens/HomeScreen.tsx b/react-native/screens/HomeScreen.tsx index b81ecdb..f45f202 100644 --- a/react-native/screens/HomeScreen.tsx +++ b/react-native/screens/HomeScreen.tsx @@ -34,15 +34,25 @@ export default function HomeScreen({ navigation }: Navigation) { useEffect(()=> { - setUser(auth?.userData); - // setUser({ - // uid: 1, - // username: "Soo", - // uemail: "kaithape@gmail.com", - // uprofileImg: 1, - // ulanguage: "english", - // uchildren:[{cid: 1, cname:"Soo"}, {cid: 2, cname:"Hee"}] - // }) + // setUser(auth?.userData); + setUser({ + uid: 1, + username: "Soo", + uemail: "kaithape@gmail.com", + uprofileImg: 1, + ulanguage: "english", + uchildren:[{cid: 1, cname:"Soo"}, {cid: 2, cname:"Hee"}] + }) + + navigation.setOptions({ + headerRight: () => ( + { + + }}> + + + ) + }); if (auth?.authData?.jwt_token) { fetch('http://localhost:8080/user/children', { @@ -77,6 +87,7 @@ export default function HomeScreen({ navigation }: Navigation) { const handleNowSelectedChildId = (cid: number) => { setNowSelectedChildId(cid); } + return ( <>{ From 2ad472d41e03a1f61edf459c45c0be485b201b85 Mon Sep 17 00:00:00 2001 From: 0hee0 Date: Sat, 28 May 2022 22:53:28 +0900 Subject: [PATCH 13/27] [#11] style: add backdrop to image background --- react-native/components/BottomDrawer.tsx | 4 +- react-native/screens/TranslateScreen.tsx | 127 ++++++++++++----------- 2 files changed, 66 insertions(+), 65 deletions(-) diff --git a/react-native/components/BottomDrawer.tsx b/react-native/components/BottomDrawer.tsx index 5a59f1e..7bc113a 100644 --- a/react-native/components/BottomDrawer.tsx +++ b/react-native/components/BottomDrawer.tsx @@ -1,6 +1,6 @@ -import React, { useState, useEffect, useRef } from 'react'; +import React, { useState, useEffect } from 'react'; import { StyleSheet, Dimensions, View, TouchableOpacity, TouchableHighlight, ScrollView, Alert, Linking } from 'react-native'; -import { MaterialIcons, FontAwesome } from '@expo/vector-icons'; +import { MaterialIcons } from '@expo/vector-icons'; import { Popover, Button, Text, Modal, FormControl, Input, VStack, Select, CheckIcon, AlertDialog } from 'native-base'; import { theme } from '../core/theme'; import type { BottomDrawerProps, EventForm, ResultsForm, UserData } from '../types'; diff --git a/react-native/screens/TranslateScreen.tsx b/react-native/screens/TranslateScreen.tsx index 7d79b05..d84e173 100644 --- a/react-native/screens/TranslateScreen.tsx +++ b/react-native/screens/TranslateScreen.tsx @@ -111,6 +111,7 @@ 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", { @@ -119,6 +120,8 @@ export default function TranslateScreen({ navigation }: Navigation) { name: imageUri.split("/").pop() }); + console.log('ocr',formdata); + setLoading(true); if (auth?.authData?.jwt_token) { @@ -147,25 +150,6 @@ export default function TranslateScreen({ navigation }: Navigation) { } }); } - // TEST: mockup data -// setResults({ -// fullText: [ -// {id: 1, content: "1. Schedule of the closing ceremony and diploma presentation ceremony: Friday, January 4, 2019 at 9 o'clock for students to go to school.\n1) ", date: "", highlight: false, registered: false}, -// {id: 2, content: "Closing ceremony", date: "2022-01-04", highlight: true, registered: false}, -// {id: 3, content: ": 1st and 2nd graders, each classroom, 9:00-10:50 (no meals)\n2) ", date: "", highlight: false, registered: false}, -// {id: 4, content: "Diploma representation ceremony", date: "2022-01-04", highlight: true, registered: true}, -// {id: 5, 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\ -// 1. 종업식 및 졸업장 수여식 일정 : 2019년 1월 4일(금), 학생 등교 9시\n\ -// 1) 종업식 : 1· 2학년, 각 교실, 9:00-10:50 (급식 없음)\n\ -// 2) 졸업장 수여식 : 3학년, 다목적 강당(2층), 10:30~12:20\n\ -// 2. 개학 및 신입생 입학식 : 2019년 3월 4일(월), 학생 등교 9시\n\ -// 1) 3월 4일 일정 : 월요일 정상수업 (급식 실시)\n\ -// (준비물: 교과서, 노트, 필기도구. 학생용 실내화(흰색) 등)\n\ -// 2) 신입생 입학식 : 다목적 강당(2층) 10시 30분, 신입생 등교 9시(신반 교실로 입장)\n", -// trans_full: '' -// }) } } @@ -193,14 +177,23 @@ export default function TranslateScreen({ navigation }: Navigation) { type: mime.getType(imageUri), name: imageUri.split("/").pop() }); - // formdata.append('noticeRequestDTO', new Blob([JSON.stringify(data)], {type: 'application/json'})); - formdata.append('cid', form?.cid); - formdata.append('title', form?.title); - formdata.append('date', new Date().toISOString().slice(0, 10)); - formdata.append('korean', results?.korean); - formdata.append('trans_full', results?.trans_full); + let data = { + cid: form?.cid, + title: form?.title, + date: new Date().toISOString().slice(0, 10), + korean: results?.korean, + fullText: results?.trans_full + } + formdata.append("noticeRequestDto", JSON.stringify(data)); + // formdata.append('noticeRequestDto', new Blob([JSON.stringify(data)], {type: 'application/json'})); + + // formdata.append('cid', form?.cid); + // formdata.append('title', form?.title); + // formdata.append('date', new Date().toISOString().slice(0, 10)); + // formdata.append('korean', results?.korean); + // formdata.append('trans_full', results?.trans_full); - console.log(formdata); + // console.log(formdata); if (auth?.authData?.jwt_token) { fetch('http://localhost:8080/notice/save', { @@ -247,43 +240,46 @@ export default function TranslateScreen({ navigation }: Navigation) { {imageUri ? ( /* After taking a picture and press the check button */ results?.fullText && results?.korean ? ( - - - } - itemFull={ - - } - onShowMini={() => setFullDrawer(false)} - onShowFull={() => setFullDrawer(true)} - animation="easeInEaseOut" - disableSwipeIcon - extraMarginTop={10} - swipeHeight={Dimensions.get('window').height*0.5} - /> + + + + } + itemFull={ + + } + onShowMini={() => setFullDrawer(false)} + onShowFull={() => setFullDrawer(true)} + animation="easeInEaseOut" + disableSwipeIcon + extraMarginTop={10} + swipeHeight={Dimensions.get('window').height*0.65} + /> + + ) : ( /* After taking a picture, before OCR(pressing the check button) */ @@ -324,6 +320,7 @@ export default function TranslateScreen({ navigation }: Navigation) { + )} @@ -373,5 +370,9 @@ const styles = StyleSheet.create({ height: 56, width: 56, borderWidth: 2 + }, + backdrop: { + flex: 1, + backgroundColor: 'rgba(0,0,0, 0.60)' } }); From 3d032ba2183b7452cfb22d0e7f48ddbcbb5d68f3 Mon Sep 17 00:00:00 2001 From: 0hee0 Date: Sun, 29 May 2022 15:02:29 +0900 Subject: [PATCH 14/27] [#1] feat: add children profile image --- .../images/cprofile-images/profile-1.png | Bin 0 -> 6267 bytes .../images/cprofile-images/profile-2.png | Bin 0 -> 5643 bytes .../images/cprofile-images/profile-3.png | Bin 0 -> 5728 bytes .../images/cprofile-images/profile-4.png | Bin 0 -> 5539 bytes .../images/cprofile-images/profile-5.png | Bin 0 -> 6410 bytes .../images/cprofile-images/profile-6.png | Bin 0 -> 5645 bytes .../images/cprofile-images/profile-7.png | Bin 0 -> 5426 bytes .../images/cprofile-images/profile-8.png | Bin 0 -> 5814 bytes .../images/cprofile-images/profile-9.png | Bin 0 -> 5131 bytes react-native/screens/JoinScreen.tsx | 89 +++++++++++++++--- react-native/types.ts | 3 +- 11 files changed, 77 insertions(+), 15 deletions(-) create mode 100644 react-native/assets/images/cprofile-images/profile-1.png create mode 100644 react-native/assets/images/cprofile-images/profile-2.png create mode 100644 react-native/assets/images/cprofile-images/profile-3.png create mode 100644 react-native/assets/images/cprofile-images/profile-4.png create mode 100644 react-native/assets/images/cprofile-images/profile-5.png create mode 100644 react-native/assets/images/cprofile-images/profile-6.png create mode 100644 react-native/assets/images/cprofile-images/profile-7.png create mode 100644 react-native/assets/images/cprofile-images/profile-8.png create mode 100644 react-native/assets/images/cprofile-images/profile-9.png diff --git a/react-native/assets/images/cprofile-images/profile-1.png b/react-native/assets/images/cprofile-images/profile-1.png new file mode 100644 index 0000000000000000000000000000000000000000..5c3369a5238ee5bf9ceae5553b5535c3d34d2ec2 GIT binary patch literal 6267 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 WICPm>1@0)19Z}ED31JnOsIRaCQ;`+8G{s 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

rW3V(D5G6pC|Q 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 literal 0 HcmV?d00001 diff --git a/react-native/assets/images/cprofile-images/profile-4.png b/react-native/assets/images/cprofile-images/profile-4.png new file mode 100644 index 0000000000000000000000000000000000000000..22d2dc30121662dd50182ba5e59ffb1445d72101 GIT binary patch literal 5539 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 literal 0 HcmV?d00001 diff --git a/react-native/assets/images/cprofile-images/profile-5.png b/react-native/assets/images/cprofile-images/profile-5.png new file mode 100644 index 0000000000000000000000000000000000000000..6bddc3f4e49addb4b0fb15bfa203bc8d8e0fcb26 GIT binary patch literal 6410 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 A6aWAK literal 0 HcmV?d00001 diff --git a/react-native/assets/images/cprofile-images/profile-9.png b/react-native/assets/images/cprofile-images/profile-9.png new file mode 100644 index 0000000000000000000000000000000000000000..06c34a4ab529e04a9ba185c1e71408f64fc7a938 GIT binary patch literal 5131 zcmV+m6!hzfP)GFWJ@y2h?+rn zb<~+;My;IzU4pY)1XJKTr-d zNdL-#Ch1fiFeihe_DeszfI;bJx3|*OrHG*h!c+)C@bw3p5WnmO>8D$R$L>cIMhDR0 zt#l1)@J!XnOCk9B19dZ0LkU3=z@ySX zEdoBh*owgfVF)37g#-|fVFaPO5PbcCR-lc-sRFX$7l?w;Er!rd2)_P617Z+$n=S?z z4?pmzx6-vmkB93b1Yduk4R~7f2j>U-fmW@n3r&R}d4Vk$KM$ZcntKP$gy8EBJc<|# ze3gR1Txm!24ywlvstZAC5o`ojsQRG7r3YwHJ40z!vv4Ub+`(lL1~yAUwkoJ11WCOeT#g5n=4oGlV2c_BD;^_ABG{~Gp_hqgtGCkCs$_wR z3gNN{G)2)Cgz>@ezI}%jDbL|3&1j2YdG( zWZjb+7#bSZ^ttor&t<-M9^U!$CaV^mdB$7mYRmTKtc4(D47<>=`?$S*H&3m5L9fS8 z!Q-i>x%sY1FQOAGv+bYAMF_eki;N_Kc$PnSA8XdE&_fV@pedVlbXG#}^#?Wrk7RtK zLKs;DQ5=pk)<63=OP4Ou`q4)a#gI|6GGie~ql$l<(M<~C^L@g@EB{*am>^qLuU^6G zHJW#|WZbOGNC;9~(2b74;RE;oB_~dNs@LPQ;=9dvvVQ&JT0b_Ucp=lih8^fAf|oaK z%{vhQyw$#k_V(RcKX#+k`zs6CLXeV1x{WUk28Vd{)!*p#_?&ra-3$0qWI0v9jW`-i z7P5p8Gq^-IUVC-BW^sY5*rH0gFKqagD&?l&HYuh{i(HSe5gn@pExm!uZejPsjV!2f zDqi;C!$Ko0-h-NXgI%qSe7)MquGU6omzOABe*2E>`ayEQ zjW}^AC2~AMTh6BB$ZM}|*W@{yS{6_*c>=fH!W%zmz-3pqnmBy;h}N+Jhyz;cQeBWz z2$DC@bplFn?^#V+3Y%IMu)M)Dm%*c|?PW6`; zNF;(NYKTFLzbOGLQh9}>LXe_O-RC2Ab{oCO3BqiPFf*L0~TNm*}(_BT$Ti)Q|(5l5OsjJB9 zejPo6n*=X>Ndik4klUc%1SMxMKCJ`P)vqGhX0Yy5#kLNG*- zs07QTw^k`Hkc>4q&EfcCw^3|Sbt#AwKu9|9xjn4;1 z_<{obq&)+ql+7x{V@A&#yyZZ#E;zSTz#4_klON+$#YTsqha3h^>OTM zAZ-F^^-Xh9ma(ntG;X^!VGbhCgaG1;-vv{v2E%!SWKFD{t&P);)z>&DkNZc%Z2F*w zoxVPnHOyjpgGUlT`b5yZF#_<9hIy{z6FwgtWtmil+)QMi(1Mg;iWNeW9+qTEx@_RF zvq66NX4j;9KiEBh+ipo%r>A4>ZXesEs${3Hk0o^#2}PL4EcEpU%Dt7Y!Ppp~NiSvE04F;AmI@V|1b1jJQE&-k?_7UQpmm^pECk71NM+9syKx!4t{z335r6xXK6e zW+X){M-BRc=dlnP@~&zCIN1HAvIUw=m`YNouH|q*X}h$;Z6Ob{ zh}D%pe}hG1R}G$7H3^S@J~+Zbg~Xr~n?C5#+k~7jv=C-_5(@!fTi0m~ElEv48nlwn zsIxSP2E5+Y%M(p=W#{+Jp08z8z7QNh2+H$1n?C5t>uLcEE`)RoRzBD9>69+#R9F|? zj@fgy>}QAG+e!Fn`swQdX*5*6W9X`3zBl!Jgx9-zWycDk!~Y@sxk7M|i4(t>k=~Px?@PZ9lQ2HY_?}l0#zxa#d$4<;AiMz>TnHXd z4Gu?H%4?3D4YGE(PgZ#oCq88Ct$!xc`­ufIU#{F$_61xCXxd-H!}JvSe)ATG0S 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 literal 0 HcmV?d00001 diff --git a/react-native/screens/JoinScreen.tsx b/react-native/screens/JoinScreen.tsx index 0ea78b7..bdf29bb 100644 --- a/react-native/screens/JoinScreen.tsx +++ b/react-native/screens/JoinScreen.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; -import { StyleSheet, View, KeyboardAvoidingView, Alert, Platform, ScrollView, Image, GestureResponderEvent } from 'react-native'; -import { FormControl, Input, Button, VStack, Select, CheckIcon } from 'native-base'; +import { StyleSheet, KeyboardAvoidingView, Alert, Platform, ScrollView, Image, GestureResponderEvent, View } from 'react-native'; +import { FormControl, Input, Button, VStack, Select, CheckIcon, Popover } from 'native-base'; import { nameValidator } from '../core/utils'; import type { Navigation, UserData, JoinData } from '../types'; import { theme } from '../core/theme'; @@ -11,8 +11,10 @@ import '../locales/i18n'; export default function JoinScreen({ navigation }: Navigation) { const [childrenNumber, setChildrenNumber] = useState('1'); - const imgSource = [require(`../assets/images/profile-images/profile-1.png`), require(`../assets/images/profile-images/profile-2.png`), require(`../assets/images/profile-images/profile-3.png`), + const uProfileImgSource = [require(`../assets/images/profile-images/profile-1.png`), require(`../assets/images/profile-images/profile-2.png`), require(`../assets/images/profile-images/profile-3.png`), require(`../assets/images/profile-images/profile-4.png`), require(`../assets/images/profile-images/profile-5.png`), require(`../assets/images/profile-images/profile-6.png`), require(`../assets/images/profile-images/profile-7.png`)]; + const cProfileImgSource = [require(`../assets/images/cprofile-images/profile-1.png`), require(`../assets/images/cprofile-images/profile-2.png`), require(`../assets/images/cprofile-images/profile-3.png`), + require(`../assets/images/cprofile-images/profile-4.png`), require(`../assets/images/cprofile-images/profile-5.png`), require(`../assets/images/cprofile-images/profile-6.png`), require(`../assets/images/cprofile-images/profile-7.png`), require(`../assets/images/cprofile-images/profile-8.png`), require(`../assets/images/cprofile-images/profile-9.png`)]; const colors = [{id: 1, hex: '#7986cb'}, {id: 3, hex: '#8e24aa'}, {id: 4, hex: '#e67c73'}, {id: 5, hex: '#f6bf26'}, {id: 7, hex: '#039be5'}, {id: 10, hex: '#0b8043'}] // 1 3 4 5 7 10 const [joinForm, setJoinForm] = useState({ @@ -20,8 +22,9 @@ export default function JoinScreen({ navigation }: Navigation) { uprofileImg: 1, username: '', ulanguage: '', - uchildren: colors.map(color => ({'cname': '', 'color': color?.id})) + uchildren: colors.map(color => ({'cname': '', 'cprofileImg': 1, 'color': color?.id})) }) + const [open, setOpen] = useState(-1); const [user, setUser] = useState(); const auth = useAuth(); @@ -45,6 +48,10 @@ export default function JoinScreen({ navigation }: Navigation) { } }, [user]); + useEffect(() => { + //console.log(joinForm); + }, [joinForm]) + const errorAlert = (error: string) => Alert.alert( i18n.t('joinFailed'), @@ -58,9 +65,21 @@ export default function JoinScreen({ navigation }: Navigation) { setJoinForm({ ...joinForm, ['uprofileImg']: profileType }); } - const handleChildren = (childNum: number, text: string) => { + const handleChildrenName = (childNum: number, value: string) => { + let array = joinForm?.uchildren; + if (array) { + array[childNum].cname = value; + } + setJoinForm({ ...joinForm, ['uchildren']: array }); + } + + const handleChildrenProfileImg = (childNum: number,value: number) => (event: GestureResponderEvent) => { let array = joinForm?.uchildren; - if (array) array[childNum].cname = text; + console.log(array); + if (array) { + array[childNum].cprofileImg = value; + setOpen(-1); + } setJoinForm({ ...joinForm, ['uchildren']: array }); } @@ -70,16 +89,17 @@ export default function JoinScreen({ navigation }: Navigation) { let childrenArr = joinForm?.uchildren; childrenArr = childrenArr?.slice(0, Number(childrenNumber)); - joinForm.uchildren = childrenArr; const usernameError = nameValidator(joinForm.username); - const childrenNameError = joinForm.uchildren?.some(child => child.cname === ''); + const childrenNameError = childrenArr?.some(child => child.cname === ''); if (usernameError || childrenNameError || !joinForm.ulanguage) { errorAlert(i18n.t('fillAlarm')); return; } + joinForm.uchildren = childrenArr; + auth.signUp(joinForm); } }; @@ -93,7 +113,7 @@ export default function JoinScreen({ navigation }: Navigation) { {Array(7).fill(1).map((num, index) => )} @@ -152,13 +172,41 @@ export default function JoinScreen({ navigation }: Navigation) { size="md" variant="underlined" value={joinForm?.uchildren && joinForm.uchildren[index]?.cname} - onChangeText={(text) => handleChildren(index, text)} + onChangeText={(text) => handleChildrenName(index, text)} autoCapitalize="none" mb={2} InputRightElement={ - + setOpen(index)} + onClose={() => setOpen(-1)} + trigger={triggerProps => { + return + }} + > + + + + + {i18n.t('profileImage')} + + {Array(7).fill(1).map((num, i) => + + )} + + + + + + } /> )} @@ -182,12 +230,25 @@ const styles = StyleSheet.create({ flexDirection: 'column', justifyContent: 'center' }, - profileImage: { + uprofileImage: { width: 52, height: 52, }, + cprofileImage: { + width: 32, + height: 32, + }, disabled: { opacity: 0.3 + }, + shadow: { + shadowColor: "#999999", + shadowOpacity: 0.5, + shadowRadius: 8, + shadowOffset: { + height: 0, + width: 0, + }, } }); diff --git a/react-native/types.ts b/react-native/types.ts index 4f2be18..e4c5e39 100644 --- a/react-native/types.ts +++ b/react-native/types.ts @@ -22,6 +22,7 @@ export type TextInput = { interface Children { cid: number, cname?: string, + cProfileImg?: number, color?: number, } @@ -30,7 +31,7 @@ interface JoinData { uprofileImg?: number, username?: string, ulanguage?: string, - uchildren?: Children[] + uchildren?: { cname: string, cprofileImg: number, color: number }[] } interface UserData extends JoinData { From 76395ba44da4dffd0a8d9997fe4f91e74e75225a Mon Sep 17 00:00:00 2001 From: mori8 Date: Sun, 29 May 2022 16:49:46 +0900 Subject: [PATCH 15/27] Feat: create Popover menu @ HomeScreen bar --- react-native/components/HomeMenu.tsx | 40 ++++++++++++++++++++++++++++ react-native/package.json | 3 +++ react-native/screens/HomeScreen.tsx | 9 +++---- react-native/yarn.lock | 18 +++++++++++++ 4 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 react-native/components/HomeMenu.tsx diff --git a/react-native/components/HomeMenu.tsx b/react-native/components/HomeMenu.tsx new file mode 100644 index 0000000..99c66cf --- /dev/null +++ b/react-native/components/HomeMenu.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import { View, Text, Image, TouchableOpacity } from "react-native"; +import { useAuth } from "../contexts/Auth"; +import { useNavigation, StackActions } from '@react-navigation/native'; +import { Menu, MenuItem } from "react-native-material-menu"; + +export default function HomeMenu() { + const auth = useAuth(); + const navigation = useNavigation(); + const [isMenuVisible, setMenuVisibility] = React.useState(false); + + const showMenu = () => { + setMenuVisibility(true); + }; + const hideMenu = () => { + setMenuVisibility(false); + }; + + const logout = () => { + auth.signOut(); + navigation.dispatch(StackActions.popToTop()) + } + + return ( +

+ + + } + onRequestClose={hideMenu} + > + Logout + + ); +} diff --git a/react-native/package.json b/react-native/package.json index 948689c..418a7fc 100644 --- a/react-native/package.json +++ b/react-native/package.json @@ -16,6 +16,7 @@ "@react-navigation/native": "^6.0.8", "@react-navigation/native-stack": "^6.5.0", "@types/i18n-js": "^3.8.2", + "@types/react-native-material-menu": "^1.0.6", "axios": "^0.26.1", "expo": "~44.0.0", "expo-app-loading": "~1.3.0", @@ -39,7 +40,9 @@ "react-native": "0.64.3", "react-native-dotenv": "^3.3.1", "react-native-elements": "^3.4.2", + "react-native-material-menu": "^2.0.0", "react-native-modal-datetime-picker": "^13.1.0", + "react-native-popup-menu": "^0.15.12", "react-native-safe-area-context": "3.3.2", "react-native-screens": "~3.10.1", "react-native-splash-screen": "^3.3.0", diff --git a/react-native/screens/HomeScreen.tsx b/react-native/screens/HomeScreen.tsx index f45f202..5c45798 100644 --- a/react-native/screens/HomeScreen.tsx +++ b/react-native/screens/HomeScreen.tsx @@ -6,6 +6,7 @@ import { theme } from '../core/theme'; import type { Navigation, UserData } from '../types'; import { useAuth } from '../contexts/Auth'; import { StackActions } from '@react-navigation/native'; +import HomeMenu from '../components/HomeMenu'; export default function HomeScreen({ navigation }: Navigation) { @@ -46,11 +47,7 @@ export default function HomeScreen({ navigation }: Navigation) { navigation.setOptions({ headerRight: () => ( - { - - }}> - - + ) }); @@ -68,7 +65,7 @@ export default function HomeScreen({ navigation }: Navigation) { }) // console.log(data) .catch((error) => { console.log(error) - if(error?.response?.status==401) { + if (error?.response?.status==401) { //redirect to login Alert.alert("The session has expired. Please log in again."); auth.signOut(); diff --git a/react-native/yarn.lock b/react-native/yarn.lock index d818553..b05e180 100644 --- a/react-native/yarn.lock +++ b/react-native/yarn.lock @@ -2413,6 +2413,14 @@ resolved "https://registry.yarnpkg.com/@types/react-native-dotenv/-/react-native-dotenv-0.2.0.tgz#32c58422a422c1adf68acce363ed791314d5a8e7" integrity sha512-ZxX+dU/yoQc0jTk+/NWttkiuXceJyN5FpOSqDl0WynN5GDzxwH7OMruQ47qcY8llo2RD3irjvzJ9BwC8gDiq0A== +"@types/react-native-material-menu@^1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/react-native-material-menu/-/react-native-material-menu-1.0.6.tgz#953dd6cfce18baee9930290e7926b12f5b6676e7" + integrity sha512-qGqqRo5a74n3jB+9wvtW7m+qriqFWpg0JnbpHIir9g39FxRFxe71b7M0WOMdsC2ue5I/OeyJLaCmwXKUFk3zug== + dependencies: + "@types/react" "*" + "@types/react-native" "*" + "@types/react-native-vector-icons@^6.4.6": version "6.4.10" resolved "https://registry.yarnpkg.com/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.10.tgz#9bfd6e64dd37b8119425496b5e53ff91d034efa9" @@ -5972,6 +5980,11 @@ react-native-keyboard-aware-scroll-view@^0.9.5: prop-types "^15.6.2" react-native-iphone-x-helper "^1.0.3" +react-native-material-menu@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-native-material-menu/-/react-native-material-menu-2.0.0.tgz#139f407a52fbc0d47e5351d50ac769e05812da5c" + integrity sha512-SmO9PLE3E469EPbVWZqvdu6JGPPZIm7YjqDcWs2PPoY0k7w2V9tFo3BmmLXNzNZDCVCAi+PPSsL7h/5WkfHcSg== + react-native-modal-datetime-picker@^13.1.0: version "13.1.0" resolved "https://registry.yarnpkg.com/react-native-modal-datetime-picker/-/react-native-modal-datetime-picker-13.1.0.tgz#9aeb99d83f30e8ea1168d5c17cb84e21630278df" @@ -5979,6 +5992,11 @@ react-native-modal-datetime-picker@^13.1.0: dependencies: prop-types "^15.7.2" +react-native-popup-menu@^0.15.12: + version "0.15.12" + resolved "https://registry.yarnpkg.com/react-native-popup-menu/-/react-native-popup-menu-0.15.12.tgz#386852f4245f8d661a5003776989b9b55c9ce381" + integrity sha512-33zMqHW+7rt5m2Bgg7xM8jy1guIdUCE8qJfznYYNRlIt4+fJX/rDcwmJIrkSGrvfhlhhEaG3otW6h25uFZLFfg== + react-native-ratings@8.0.4: version "8.0.4" resolved "https://registry.yarnpkg.com/react-native-ratings/-/react-native-ratings-8.0.4.tgz#efd5ebad8acc08bf98d34d39b18fb7a6813ef991" From edf1c3dfd13f20c877216615bab92e63ce931c75 Mon Sep 17 00:00:00 2001 From: mori8 Date: Sun, 29 May 2022 16:50:01 +0900 Subject: [PATCH 16/27] Chore: delete unused modules --- react-native/screens/HomeScreen.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/react-native/screens/HomeScreen.tsx b/react-native/screens/HomeScreen.tsx index 5c45798..556f221 100644 --- a/react-native/screens/HomeScreen.tsx +++ b/react-native/screens/HomeScreen.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState } from 'react'; import { StyleSheet, View, SafeAreaView, TouchableOpacity, Image, ImageBackground, Alert } from 'react-native'; -import { Text, Box } from 'native-base' -import { Ionicons } from '@expo/vector-icons'; +import { Text } from 'native-base' import { theme } from '../core/theme'; import type { Navigation, UserData } from '../types'; import { useAuth } from '../contexts/Auth'; From a28a6d5f288a802cb71a54ce9809aa79daf63172 Mon Sep 17 00:00:00 2001 From: 0hee0 Date: Sun, 29 May 2022 19:27:39 +0900 Subject: [PATCH 17/27] [#1] style: update select box and sign up button --- react-native/package.json | 3 +- react-native/screens/JoinScreen.tsx | 199 ++++++++++++++++++++-------- react-native/yarn.lock | 28 ++-- 3 files changed, 162 insertions(+), 68 deletions(-) diff --git a/react-native/package.json b/react-native/package.json index 948689c..2cbb5e1 100644 --- a/react-native/package.json +++ b/react-native/package.json @@ -16,7 +16,7 @@ "@react-navigation/native": "^6.0.8", "@react-navigation/native-stack": "^6.5.0", "@types/i18n-js": "^3.8.2", - "axios": "^0.26.1", + "axios": "^0.27.2", "expo": "~44.0.0", "expo-app-loading": "~1.3.0", "expo-application": "^4.0.2", @@ -38,6 +38,7 @@ "react-dom": "17.0.1", "react-native": "0.64.3", "react-native-dotenv": "^3.3.1", + "react-native-element-dropdown": "^1.8.11", "react-native-elements": "^3.4.2", "react-native-modal-datetime-picker": "^13.1.0", "react-native-safe-area-context": "3.3.2", diff --git a/react-native/screens/JoinScreen.tsx b/react-native/screens/JoinScreen.tsx index bdf29bb..377d1bb 100644 --- a/react-native/screens/JoinScreen.tsx +++ b/react-native/screens/JoinScreen.tsx @@ -1,12 +1,15 @@ import React, { useState, useEffect } from 'react'; -import { StyleSheet, KeyboardAvoidingView, Alert, Platform, ScrollView, Image, GestureResponderEvent, View } from 'react-native'; -import { FormControl, Input, Button, VStack, Select, CheckIcon, Popover } from 'native-base'; +import { StyleSheet, KeyboardAvoidingView, Alert, Platform, ScrollView, Image, GestureResponderEvent, View, TouchableHighlight } from 'react-native'; +import { FormControl, Input, Button, VStack, Popover, Text } from 'native-base'; +import { Dropdown } from 'react-native-element-dropdown'; import { nameValidator } from '../core/utils'; import type { Navigation, UserData, JoinData } from '../types'; import { theme } from '../core/theme'; import { useAuth } from '../contexts/Auth'; import i18n from 'i18n-js' import '../locales/i18n'; +import useFonts from '../hooks/useFonts'; +import AppLoading from 'expo-app-loading'; export default function JoinScreen({ navigation }: Navigation) { @@ -29,6 +32,11 @@ export default function JoinScreen({ navigation }: Navigation) { const [user, setUser] = useState(); const auth = useAuth(); + const [fontsLoaded, SetFontsLoaded] = useState(false); + const LoadFontsAndRestoreToken = async () => { + await useFonts(); + }; + useEffect(() => { if (auth?.userData?.uroleType==='USER') { Alert.alert( @@ -49,8 +57,8 @@ export default function JoinScreen({ navigation }: Navigation) { }, [user]); useEffect(() => { - //console.log(joinForm); - }, [joinForm]) + // console.log(joinForm); + }, [joinForm]); const errorAlert = (error: string) => Alert.alert( @@ -104,15 +112,25 @@ export default function JoinScreen({ navigation }: Navigation) { } }; + if (!fontsLoaded) { + return ( + SetFontsLoaded(true)} + onError={() => {}} + /> + ); + } + return ( - + - + {i18n.t('profileImage')} - {Array(7).fill(1).map((num, index) => - )} @@ -131,73 +149,94 @@ export default function JoinScreen({ navigation }: Navigation) { {i18n.t('selectLang')} - + { + setJoinForm({ ...joinForm, ['ulanguage']: item.value }) + }} + /> {i18n.t('childrenNum')} - + { + setChildrenNumber(item.value) + }} + /> - + {i18n.t('childrenName')} - - {Array(Number(childrenNumber)).fill(1).map((child, index) => + + {Array.from(Array(Number(childrenNumber)).keys()).map((child, index) => handleChildrenName(index, text)} + value={joinForm?.uchildren && joinForm.uchildren[child]?.cname} + onChangeText={(text) => handleChildrenName(child, text)} autoCapitalize="none" mb={2} InputRightElement={ setOpen(index)} + key={'p_'+child} + isOpen={open===child} + onOpen={() => setOpen(child)} onClose={() => setOpen(-1)} trigger={triggerProps => { - return }} > - + {i18n.t('profileImage')} - {Array(7).fill(1).map((num, i) => - )} @@ -213,9 +252,11 @@ export default function JoinScreen({ navigation }: Navigation) { - + + + {i18n.t("signUp")} + + ); @@ -223,12 +264,13 @@ export default function JoinScreen({ navigation }: Navigation) { const styles = StyleSheet.create({ container: { - paddingHorizontal: 25, - paddingVertical: 40, + paddingHorizontal: "5%", + paddingVertical: 30, backgroundColor: theme.colors.background, flex: 1, flexDirection: 'column', - justifyContent: 'center' + justifyContent: 'center', + alignContent: 'space-between' }, uprofileImage: { width: 52, @@ -249,6 +291,49 @@ const styles = StyleSheet.create({ height: 0, width: 0, }, - } + }, + dropdown: { + height: 38, + borderColor: '#e5e5e5', + borderWidth: 0.6, + borderRadius: 5, + paddingHorizontal: 8, + marginTop: 1 + }, + label: { + position: 'absolute', + backgroundColor: 'white', + left: 22, + top: 8, + zIndex: 999, + paddingHorizontal: 8, + fontSize: 14, + fontFamily: 'Lora_400Regular', + }, + placeholderStyle: { + fontSize: 14, + fontFamily: 'Lora_400Regular', + color: '#a3a3a3' + }, + selectedTextStyle: { + fontSize: 14, + fontFamily: 'Lora_400Regular', + }, + inputSearchStyle: { + height: 36, + fontSize: 14, + fontFamily: 'Lora_400Regular', + }, + startButton: { + backgroundColor: theme.colors.primary, + padding: 10, + borderRadius: 8, + marginTop: 20, + marginBottom: 40, + }, + buttonStyle: { + textAlign: "center", + color: "white", + }, }); diff --git a/react-native/yarn.lock b/react-native/yarn.lock index d818553..8237453 100644 --- a/react-native/yarn.lock +++ b/react-native/yarn.lock @@ -2646,12 +2646,13 @@ atob@^2.1.2: resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== -axios@^0.26.1: - version "0.26.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" - integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== dependencies: - follow-redirects "^1.14.8" + follow-redirects "^1.14.9" + form-data "^4.0.0" babel-core@^7.0.0-bridge.0: version "7.0.0-bridge.0" @@ -3940,10 +3941,10 @@ flow-parser@0.*, flow-parser@^0.121.0: resolved "https://registry.npmjs.org/flow-parser/-/flow-parser-0.121.0.tgz" integrity sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg== -follow-redirects@^1.14.8: - version "1.14.9" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7" - integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w== +follow-redirects@^1.14.9: + version "1.15.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== fontfaceobserver@^2.1.0: version "2.1.0" @@ -4818,7 +4819,7 @@ lodash.throttle@^4.1.1: resolved "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz" integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ= -lodash@^4.17.14, lodash@^4.17.15: +lodash@*, lodash@^4.17.14, lodash@^4.17.15: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -5945,6 +5946,13 @@ react-native-dotenv@^3.3.1: dependencies: dotenv "^10.0.0" +react-native-element-dropdown@^1.8.11: + version "1.8.11" + resolved "https://registry.yarnpkg.com/react-native-element-dropdown/-/react-native-element-dropdown-1.8.11.tgz#5c66945555b657348e0330a5b9f5e8a354a8ab50" + integrity sha512-7IdQr/XP/t8ErAk5XU2yE8evB5EbYgLhhLafbGMeJEMb3LjRYQDYkm/fr/IhWI0SHgnneEUzj49PhUK/XpcOAA== + dependencies: + lodash "*" + react-native-elements@^3.4.2: version "3.4.2" resolved "https://registry.yarnpkg.com/react-native-elements/-/react-native-elements-3.4.2.tgz#66602be9c5e0e0a2a831913adec80ff6518d1ee2" From d3edf21d7d18442631243495d41dd8b614e622c4 Mon Sep 17 00:00:00 2001 From: mori8 Date: Sun, 29 May 2022 20:06:10 +0900 Subject: [PATCH 18/27] Design: Move function buttons to the top & make buttons more prettier --- .../assets/images/home-button-background.png | Bin 0 -> 4936 bytes react-native/screens/HomeScreen.tsx | 77 +++++++++--------- react-native/screens/IntroductionScreen.tsx | 4 + 3 files changed, 41 insertions(+), 40 deletions(-) create mode 100644 react-native/assets/images/home-button-background.png diff --git a/react-native/assets/images/home-button-background.png b/react-native/assets/images/home-button-background.png new file mode 100644 index 0000000000000000000000000000000000000000..fd6c7f135d66b65a2fd36e089e06ccce76bb2925 GIT binary patch literal 4936 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 L+SaoC6gKXkQDZHA literal 0 HcmV?d00001 diff --git a/react-native/screens/HomeScreen.tsx b/react-native/screens/HomeScreen.tsx index 556f221..1df9c8f 100644 --- a/react-native/screens/HomeScreen.tsx +++ b/react-native/screens/HomeScreen.tsx @@ -5,6 +5,7 @@ import { theme } from '../core/theme'; import type { Navigation, UserData } from '../types'; import { useAuth } from '../contexts/Auth'; import { StackActions } from '@react-navigation/native'; +import { MaterialIcons, FontAwesome } from '@expo/vector-icons'; import HomeMenu from '../components/HomeMenu'; @@ -89,15 +90,26 @@ export default function HomeScreen({ navigation }: Navigation) { <>{ user && events && events.children?.length > 0 && ( - - - - - {"Hi, " + user.username + "!"} - You've got {events.event_num} events today. - - - + + + navigation.navigate('Translate')}> + + + Translate + + + + + navigation.navigate('Search')}> + + + Search + + + + + + Today's Events @@ -141,26 +153,6 @@ export default function HomeScreen({ navigation }: Navigation) { )} - - Functions - - navigation.navigate('Translate')}> - - - Translate - Translation, summarization, and calendar registration are all possible just by taking a picture of the notice. - - - - navigation.navigate('Search')}> - - - Search - You can find notices you have translated. - - - - )} ) @@ -232,10 +224,16 @@ const styles = StyleSheet.create({ profielTextWrapper: { paddingRight: 30, }, + functionButtonImageBackground: { + flex: 1.16, + flexDirection: "row", + alignItems: "center", + }, functionButtonWrapper: { - flex: 1.5, - width: '88%', + flex: 1, paddingBottom: 30, + marginHorizontal: 20, + marginTop: -36 }, smallTitle: { marginBottom: 8, @@ -245,18 +243,17 @@ const styles = StyleSheet.create({ }, bigButton: { padding: 26, - marginBottom: 18, + marginBottom: 22, borderRadius: 16, - shadowColor: "#999999", - shadowOpacity: 0.5, - shadowRadius: 8, - shadowOffset: { - height: 0, - width: 0, - }, + height: 86 + }, + bigButtonContentWrapper: { + flex:1, + flexDirection:'row', + justifyContent:'space-between' }, deepBlue: { - color: theme.colors.secondary, + color: "#333333", }, lightPink: { color: theme.colors.primary, diff --git a/react-native/screens/IntroductionScreen.tsx b/react-native/screens/IntroductionScreen.tsx index 5a3dd20..a03442d 100644 --- a/react-native/screens/IntroductionScreen.tsx +++ b/react-native/screens/IntroductionScreen.tsx @@ -32,6 +32,10 @@ export default function HomeScreen({ navigation }: Navigation) { }); const auth = useAuth(); + useEffect(() => { + navigation.navigate("Home"); + }) + useEffect(() => { if (response?.type === "success") { const { authentication } = response; From 4eec0f4de894537eba64918f63f0e4b5c37405f0 Mon Sep 17 00:00:00 2001 From: 0hee0 Date: Sun, 29 May 2022 20:19:37 +0900 Subject: [PATCH 19/27] [#1] style: change success alert to toast --- react-native/screens/JoinScreen.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/react-native/screens/JoinScreen.tsx b/react-native/screens/JoinScreen.tsx index 377d1bb..9815b84 100644 --- a/react-native/screens/JoinScreen.tsx +++ b/react-native/screens/JoinScreen.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { StyleSheet, KeyboardAvoidingView, Alert, Platform, ScrollView, Image, GestureResponderEvent, View, TouchableHighlight } from 'react-native'; -import { FormControl, Input, Button, VStack, Popover, Text } from 'native-base'; +import { FormControl, Input, Button, VStack, Popover, Text, useToast, Box } from 'native-base'; import { Dropdown } from 'react-native-element-dropdown'; import { nameValidator } from '../core/utils'; import type { Navigation, UserData, JoinData } from '../types'; @@ -31,6 +31,7 @@ export default function JoinScreen({ navigation }: Navigation) { const [user, setUser] = useState(); const auth = useAuth(); + const toast = useToast(); const [fontsLoaded, SetFontsLoaded] = useState(false); const LoadFontsAndRestoreToken = async () => { @@ -39,10 +40,15 @@ export default function JoinScreen({ navigation }: Navigation) { useEffect(() => { if (auth?.userData?.uroleType==='USER') { - Alert.alert( - i18n.t('loginSuccess'), - i18n.t('loginSuccessText') - ) + toast.show({ // Design according to mui toast guidelines (https://material.io/components/snackbars#anatomy) + placement: "bottom", + render: () => { + return + 🎉  {i18n.t('loginSuccess')}  🎉 + {i18n.t('loginSuccessText')} + ; + } + }); navigation.navigate('Home'); } else if (auth?.userData?.uroleType==='GUEST') { From 510c4e9a3910c5d28848f88f5a6622903755a83f Mon Sep 17 00:00:00 2001 From: mori8 Date: Sun, 29 May 2022 21:22:55 +0900 Subject: [PATCH 20/27] Delete LogoutButton --- react-native/App.tsx | 2 -- react-native/components/LogoutButton.tsx | 38 ------------------------ 2 files changed, 40 deletions(-) delete mode 100644 react-native/components/LogoutButton.tsx diff --git a/react-native/App.tsx b/react-native/App.tsx index 04fdba0..05b1e12 100644 --- a/react-native/App.tsx +++ b/react-native/App.tsx @@ -14,7 +14,6 @@ import JoinScreen from './screens/JoinScreen'; import HomeScreen from './screens/HomeScreen'; import TranslateScreen from './screens/TranslateScreen'; import SearchScreen from './screens/SearchScreen'; -import LogoutButton from './components/LogoutButton'; import SearchResultScreen from './screens/SearchResultScreen'; import IntrodcutionScreen from './screens/IntroductionScreen' @@ -78,7 +77,6 @@ export default function App() { headerStyle: { backgroundColor: theme.colors.primary }, title: "Home", headerBackVisible: false, - headerRight: () => , headerTitle: (props) => ( // App Logo { - const navigation = useNavigation(); - const auth = useAuth(); - const [showBox, setShowBox] = useState(true); - - const LogoutConfirm = () => { - return Alert.alert( - "Logout", - "Are you sure you want to log out?", [ - { - text: "Yes", - onPress: () => { - setShowBox(false); - auth.signOut(); - navigation.dispatch(StackActions.popToTop()) - }, - }, { - text: "No", - } - ] - ); - } - - return ( - - - - ); -}; -export default LogoutButton; From f3945e5dc4f64eea2861fad17d9fa72604a2d30c Mon Sep 17 00:00:00 2001 From: mori8 Date: Sun, 29 May 2022 21:38:50 +0900 Subject: [PATCH 21/27] Chore: create /Home directory in /components --- react-native/components/{ => Home}/HomeMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename react-native/components/{ => Home}/HomeMenu.tsx (88%) diff --git a/react-native/components/HomeMenu.tsx b/react-native/components/Home/HomeMenu.tsx similarity index 88% rename from react-native/components/HomeMenu.tsx rename to react-native/components/Home/HomeMenu.tsx index 99c66cf..91fb934 100644 --- a/react-native/components/HomeMenu.tsx +++ b/react-native/components/Home/HomeMenu.tsx @@ -1,6 +1,6 @@ import React from "react"; import { View, Text, Image, TouchableOpacity } from "react-native"; -import { useAuth } from "../contexts/Auth"; +import { useAuth } from "../../contexts/Auth"; import { useNavigation, StackActions } from '@react-navigation/native'; import { Menu, MenuItem } from "react-native-material-menu"; @@ -28,7 +28,7 @@ export default function HomeMenu() { } From 2f369d5640611971c57999c01d5f523d0a18c727 Mon Sep 17 00:00:00 2001 From: mori8 Date: Sun, 29 May 2022 21:39:18 +0900 Subject: [PATCH 22/27] Feat: NoEventBox --- react-native/assets/images/no-event.png | Bin 0 -> 43850 bytes react-native/components/Home/NoEventBox.tsx | 25 ++++++++ react-native/screens/HomeScreen.tsx | 64 +++++++++----------- 3 files changed, 53 insertions(+), 36 deletions(-) create mode 100644 react-native/assets/images/no-event.png create mode 100644 react-native/components/Home/NoEventBox.tsx diff --git a/react-native/assets/images/no-event.png b/react-native/assets/images/no-event.png new file mode 100644 index 0000000000000000000000000000000000000000..5dd1516d19abc1cdafc0be9515bce4c49893e4f9 GIT binary patch literal 43850 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 literal 0 HcmV?d00001 diff --git a/react-native/components/Home/NoEventBox.tsx b/react-native/components/Home/NoEventBox.tsx new file mode 100644 index 0000000..848c1d1 --- /dev/null +++ b/react-native/components/Home/NoEventBox.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import { View, Image, StyleSheet } from "react-native"; +import { Text } from 'native-base' + +export default function NoEventBox() { + return ( + + + There's no events today! + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + flexDirection: "column", + justifyContent: "center", + alignItems: "center", + }, + imageStyle: { + width: 180, + height: 180, + } +}); diff --git a/react-native/screens/HomeScreen.tsx b/react-native/screens/HomeScreen.tsx index 1df9c8f..f457977 100644 --- a/react-native/screens/HomeScreen.tsx +++ b/react-native/screens/HomeScreen.tsx @@ -6,8 +6,8 @@ import type { Navigation, UserData } from '../types'; import { useAuth } from '../contexts/Auth'; import { StackActions } from '@react-navigation/native'; import { MaterialIcons, FontAwesome } from '@expo/vector-icons'; -import HomeMenu from '../components/HomeMenu'; - +import HomeMenu from '../components/Home/HomeMenu'; +import NoEventBox from '../components/Home/NoEventBox'; export default function HomeScreen({ navigation }: Navigation) { const [events, setEvents] = useState<{event_num: number, children: { cid: number, cname: string, events: string[] }[]}>( @@ -75,16 +75,9 @@ export default function HomeScreen({ navigation }: Navigation) { } }, [auth]); - useEffect(() => { - if (events && events?.children?.length > 0) { - setNowSelectedChildId(events.children[0].cid); - } - }, [events]); - const handleNowSelectedChildId = (cid: number) => { setNowSelectedChildId(cid); } - return ( <>{ @@ -111,7 +104,7 @@ export default function HomeScreen({ navigation }: Navigation) {
- Today's Events + Today's Events All - {events.children?.map((notice, index) => + {events.children?.map((notice, index) => handleNowSelectedChildId(notice.cid)}> @@ -131,26 +124,25 @@ export default function HomeScreen({ navigation }: Navigation) { )} - {nowSelectedChildId === SHOW_ALL ? events.children.map((notice, index) => - - {notice.events.map((event, index) => { - return ( - {`[${notice.cname}] ` + event} - ) - })} - - ) : events.children.filter(notice => notice.cid === nowSelectedChildId).map((notice, index) => { - return ( - - {notice.events.map((event, index) => { - return ( - {event} - ) - })} - - ) - } - )} + {nowSelectedChildId === SHOW_ALL ? ( + events.children.reduce((prevValue, child) => prevValue + child.events.length, 0) > 0 ? ( + events.children.map((notice, index) => + + {notice.events.map((event, index) => { + return ( + {`[${notice.cname}] ` + event} + ) + })} + )) + : + ) : events.children.filter(child => child.cid === nowSelectedChildId)[0].events?.length ? ( + events.children?.filter(child => child.cid === nowSelectedChildId)[0].events?.map((item, index) => + + {index+1 + '. ' + item} + + ) + ) : + } )} @@ -233,19 +225,19 @@ const styles = StyleSheet.create({ flex: 1, paddingBottom: 30, marginHorizontal: 20, - marginTop: -36 + marginTop: -28 }, smallTitle: { - marginBottom: 8, + marginBottom: 0, }, buttonName: { fontSize: 24, }, bigButton: { - padding: 26, - marginBottom: 22, + padding: 34, + marginBottom: 20, borderRadius: 16, - height: 86 + height: 100 }, bigButtonContentWrapper: { flex:1, From 9bf0d5cbf478b79929854da460785d31cba376bf Mon Sep 17 00:00:00 2001 From: 0hee0 Date: Sun, 29 May 2022 22:17:39 +0900 Subject: [PATCH 23/27] [#1] refactor: rename jwt_token to access_token --- react-native/components/BottomDrawer.tsx | 4 ++-- react-native/screens/HomeScreen.tsx | 4 ++-- react-native/screens/SearchResultScreen.tsx | 4 ++-- react-native/screens/SearchScreen.tsx | 4 ++-- react-native/screens/TranslateScreen.tsx | 8 ++++---- react-native/services/authService.ts | 9 +++++---- react-native/types.ts | 2 +- 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/react-native/components/BottomDrawer.tsx b/react-native/components/BottomDrawer.tsx index 7bc113a..409af03 100644 --- a/react-native/components/BottomDrawer.tsx +++ b/react-native/components/BottomDrawer.tsx @@ -79,12 +79,12 @@ function BottomDrawer(props: BottomDrawerProps) { } const addEvent = () => { - if (auth?.authData?.jwt_token && eventForm) { + if (auth?.authData?.access_token && eventForm) { console.log(eventForm, currentEvent); fetch(`http://localhost:8080/event/register?id=${currentEvent}`, { method: 'PUT', headers: { - 'JWT_TOKEN': auth.authData.jwt_token, + 'ACCESS-TOKEN': auth.authData.access_token, 'Content-Type': 'application/json;charset=UTF-8' }, body: JSON.stringify(eventForm), diff --git a/react-native/screens/HomeScreen.tsx b/react-native/screens/HomeScreen.tsx index f457977..7003f8e 100644 --- a/react-native/screens/HomeScreen.tsx +++ b/react-native/screens/HomeScreen.tsx @@ -51,11 +51,11 @@ export default function HomeScreen({ navigation }: Navigation) { ) }); - if (auth?.authData?.jwt_token) { + if (auth?.authData?.access_token) { fetch('http://localhost:8080/user/children', { method: 'GET', headers: { - 'JWT_TOKEN': auth.authData.jwt_token + 'ACCESS-TOKEN': auth.authData.access_token }, redirect: 'follow' }) diff --git a/react-native/screens/SearchResultScreen.tsx b/react-native/screens/SearchResultScreen.tsx index a59a2b3..ede36c3 100644 --- a/react-native/screens/SearchResultScreen.tsx +++ b/react-native/screens/SearchResultScreen.tsx @@ -53,11 +53,11 @@ export default function SearchResultScreen(props: SearchResultScreenProps) { }] }) - if (auth?.authData?.jwt_token) { + if (auth?.authData?.access_token) { fetch(`http://localhost:8080/search/detail?date=${props.route.params.date}`, { method: 'GET', headers: { - 'JWT_TOKEN': auth.authData.jwt_token + 'ACCESS-TOKEN': auth.authData.access_token }, redirect: 'follow' }) diff --git a/react-native/screens/SearchScreen.tsx b/react-native/screens/SearchScreen.tsx index 630a71d..2759218 100644 --- a/react-native/screens/SearchScreen.tsx +++ b/react-native/screens/SearchScreen.tsx @@ -53,11 +53,11 @@ export default function SearchScreen({ navigation }: Navigation) { const [searchDate, setSearchDate] = useState(i18n.t('searchByDateDefault')); useEffect(() => { - if (auth?.authData?.jwt_token) { + if (auth?.authData?.access_token) { fetch('http://localhost:8080/search', { method: 'GET', headers: { - 'JWT_TOKEN': auth.authData.jwt_token + 'ACCESS-TOKEN': auth.authData.access_token }, redirect: 'follow' }) diff --git a/react-native/screens/TranslateScreen.tsx b/react-native/screens/TranslateScreen.tsx index d84e173..ff93801 100644 --- a/react-native/screens/TranslateScreen.tsx +++ b/react-native/screens/TranslateScreen.tsx @@ -124,11 +124,11 @@ export default function TranslateScreen({ navigation }: Navigation) { setLoading(true); - if (auth?.authData?.jwt_token) { + if (auth?.authData?.access_token) { await fetch("http://localhost:8080/notice/ocr", { method: 'POST', headers: { - 'JWT_TOKEN': auth.authData.jwt_token + 'ACCESS-TOKEN': auth.authData.access_token }, body: formdata, redirect: 'follow' @@ -195,11 +195,11 @@ export default function TranslateScreen({ navigation }: Navigation) { // console.log(formdata); - if (auth?.authData?.jwt_token) { + if (auth?.authData?.access_token) { fetch('http://localhost:8080/notice/save', { method: 'POST', headers: { - 'JWT_TOKEN': auth.authData.jwt_token, + 'ACCESS-TOKEN': auth.authData.access_token, }, body: formdata, redirect: 'follow' diff --git a/react-native/services/authService.ts b/react-native/services/authService.ts index ca999b3..19f4b2e 100644 --- a/react-native/services/authService.ts +++ b/react-native/services/authService.ts @@ -10,10 +10,11 @@ const signIn = (accessToken: string): Promise => { } }) .then(response => { + // console.log('response headers',response.headers); let data = { header: { - jwt_token: response.headers.jwt_token, - refresh_token: response.headers.refresh_token + access_token: response.headers["access-token"], + refresh_token: response.headers["refresh-token"] }, body: response.data } @@ -32,8 +33,8 @@ const signUp = (data: JoinData): Promise => { .then(response => { let data = { header: { - jwt_token: response.headers.jwt_token, - refresh_token: response.headers.refresh_token + access_token: response.headers["access-token"], + refresh_token: response.headers["refresh-token"] }, body: response.data } diff --git a/react-native/types.ts b/react-native/types.ts index e4c5e39..fc7836d 100644 --- a/react-native/types.ts +++ b/react-native/types.ts @@ -41,7 +41,7 @@ interface UserData extends JoinData { } interface AuthData { - jwt_token?: string, + access_token?: string, refresh_token?: string, } From 0964b32df96f8ce5c8321367607d588ab6833303 Mon Sep 17 00:00:00 2001 From: 0hee0 Date: Sun, 29 May 2022 22:21:03 +0900 Subject: [PATCH 24/27] [#1} fix: add missing parts --- react-native/screens/JoinScreen.tsx | 8 +- react-native/types.ts | 124 ++++++++++++++-------------- 2 files changed, 68 insertions(+), 64 deletions(-) diff --git a/react-native/screens/JoinScreen.tsx b/react-native/screens/JoinScreen.tsx index 9815b84..e917c99 100644 --- a/react-native/screens/JoinScreen.tsx +++ b/react-native/screens/JoinScreen.tsx @@ -25,7 +25,7 @@ export default function JoinScreen({ navigation }: Navigation) { uprofileImg: 1, username: '', ulanguage: '', - uchildren: colors.map(color => ({'cname': '', 'cprofileImg': 1, 'color': color?.id})) + uchildren: colors.map(color => ({ cname: '', cprofileImg: 1, color: color?.id })) }) const [open, setOpen] = useState(-1); @@ -89,7 +89,7 @@ export default function JoinScreen({ navigation }: Navigation) { const handleChildrenProfileImg = (childNum: number,value: number) => (event: GestureResponderEvent) => { let array = joinForm?.uchildren; - console.log(array); + // console.log(array); if (array) { array[childNum].cprofileImg = value; setOpen(-1); @@ -227,8 +227,8 @@ export default function JoinScreen({ navigation }: Navigation) { onClose={() => setOpen(-1)} trigger={triggerProps => { return }} diff --git a/react-native/types.ts b/react-native/types.ts index fc7836d..5be067b 100644 --- a/react-native/types.ts +++ b/react-native/types.ts @@ -1,117 +1,121 @@ import type { NativeStackScreenProps } from '@react-navigation/native-stack'; export type RootStackParamList = { - Login: undefined; - Join: undefined; - Introduction: undefined; - Home: undefined; - Translate: undefined; - Search: undefined; - Calendar: undefined; - FullText: undefined; - SearchResult: undefined; + Login: undefined; + Join: undefined; + Introduction: undefined; + Home: undefined; + Translate: undefined; + Search: undefined; + Calendar: undefined; + FullText: undefined; + SearchResult: undefined; }; export type Navigation = NativeStackScreenProps; export type TextInput = { - errorText: string; - description: string; + errorText: string; + description: string; } interface Children { - cid: number, - cname?: string, - cProfileImg?: number, - color?: number, + cid: number, + cname?: string, + cprofileImg: number, + color?: number, } -interface JoinData { - uid?: number, - uprofileImg?: number, - username?: string, - ulanguage?: string, - uchildren?: { cname: string, cprofileImg: number, color: number }[] +interface UserInfo { + uid?: number, + uprofileImg?: number, + username?: string, + ulanguage?: string, } -interface UserData extends JoinData { +interface JoinData extends UserInfo { + uchildren?: { cname?: string, cprofileImg: number, color?: number }[] +} + +interface UserData extends UserInfo { + uchildren?: Children[], uemail?: string | undefined, uproviderType?: string | undefined, uroleType?: string | undefined, } interface AuthData { - access_token?: string, - refresh_token?: string, + access_token?: string, + refresh_token?: string, } interface AuthResponse { - header: AuthData, - body: UserData + header: AuthData, + body: UserData } interface AuthContextData { authData?: AuthData; userData?: UserData; loading: boolean; - update: boolean; + update: boolean; signUp(data: JoinData): Promise; signIn(accessToken: string): Promise; signOut(): void; - handleUpdate(): void; + handleUpdate(): void; }; interface Event { - id: number, - content: string, - date?: string, - highlight: boolean, - registered: boolean + id: number, + content: string, + date?: string, + highlight: boolean, + registered: boolean } interface Result { - id?: number, - imageUri?: string, - fullText: Event[], - korean: string, - trans_full?: string + id?: number, + imageUri?: string, + fullText: Event[], + korean: string, + trans_full?: string } interface Notice { - date: string, - results: Result[] + date: string, + results: Result[] } interface Notices { - date: string, - saved_titles: string[] + date: string, + saved_titles: string[] } interface BottomDrawerProps { - results: Result, - showKorean?: boolean, - isFullDrawer?: boolean, - isTranslateScreen?: boolean, - openSaveForm?: boolean, - handleKorean?: () => void, - saveResults?: (form: ResultsForm) => void, - closeResults?: () => void, - retakePicture?: () => void, - handleOpenSaveForm?: () => void + results: Result, + showKorean?: boolean, + isFullDrawer?: boolean, + isTranslateScreen?: boolean, + openSaveForm?: boolean, + handleKorean?: () => void, + saveResults?: (form: ResultsForm) => void, + closeResults?: () => void, + retakePicture?: () => void, + handleOpenSaveForm?: () => void } interface EventForm { - title: string, - date: string, - cid: number, - description: string + title: string, + date: string, + cid: number, + description: string } interface ResultsForm { - cid: number, - title: string + cid: number, + title: string } export type { - UserData, JoinData, AuthData, AuthResponse, AuthContextData, Children, - Event, Result, Notice, Notices, BottomDrawerProps, EventForm, ResultsForm + UserData, JoinData, AuthData, AuthResponse, AuthContextData, Children, + Event, Result, Notice, Notices, BottomDrawerProps, EventForm, ResultsForm } From 3258068765f197e0191e1dcd4f8dd09be2808b18 Mon Sep 17 00:00:00 2001 From: 0hee0 Date: Sun, 29 May 2022 23:21:32 +0900 Subject: [PATCH 25/27] [#3] fix: add missing parts --- react-native/components/Home/NoEventBox.tsx | 7 ++- react-native/screens/HomeScreen.tsx | 47 +++++++++++++-------- react-native/screens/IntroductionScreen.tsx | 6 +-- 3 files changed, 37 insertions(+), 23 deletions(-) diff --git a/react-native/components/Home/NoEventBox.tsx b/react-native/components/Home/NoEventBox.tsx index 848c1d1..c10747a 100644 --- a/react-native/components/Home/NoEventBox.tsx +++ b/react-native/components/Home/NoEventBox.tsx @@ -1,12 +1,15 @@ import React from "react"; import { View, Image, StyleSheet } from "react-native"; -import { Text } from 'native-base' +import { Text } from 'native-base'; +import i18n from 'i18n-js' +import '../../locales/i18n'; + export default function NoEventBox() { return ( - There's no events today! + {i18n.t('noEvent')} ); } diff --git a/react-native/screens/HomeScreen.tsx b/react-native/screens/HomeScreen.tsx index 7003f8e..a19d947 100644 --- a/react-native/screens/HomeScreen.tsx +++ b/react-native/screens/HomeScreen.tsx @@ -1,13 +1,15 @@ import React, { useEffect, useState } from 'react'; -import { StyleSheet, View, SafeAreaView, TouchableOpacity, Image, ImageBackground, Alert } from 'react-native'; +import { StyleSheet, View, SafeAreaView, TouchableOpacity, ImageBackground, Alert } from 'react-native'; import { Text } from 'native-base' import { theme } from '../core/theme'; import type { Navigation, UserData } from '../types'; import { useAuth } from '../contexts/Auth'; import { StackActions } from '@react-navigation/native'; -import { MaterialIcons, FontAwesome } from '@expo/vector-icons'; +import { MaterialIcons } from '@expo/vector-icons'; import HomeMenu from '../components/Home/HomeMenu'; import NoEventBox from '../components/Home/NoEventBox'; +import i18n from 'i18n-js' +import '../locales/i18n'; export default function HomeScreen({ navigation }: Navigation) { const [events, setEvents] = useState<{event_num: number, children: { cid: number, cname: string, events: string[] }[]}>( @@ -30,20 +32,21 @@ export default function HomeScreen({ navigation }: Navigation) { ); const SHOW_ALL = -1; const [nowSelectedChildId, setNowSelectedChildId] = useState(SHOW_ALL); - const [user, setUser] = useState(); + const [user, setUser] = useState({ + uid: 1, + username: "Soo", + uemail: "kaithape@gmail.com", + uprofileImg: 1, + ulanguage: "english", + uchildren:[{ cid: 1, cname:"Soo", cprofileImg: 1 }, { cid: 2, cname:"Hee", cprofileImg: 4 }] + }); const auth = useAuth(); useEffect(()=> { - // setUser(auth?.userData); - setUser({ - uid: 1, - username: "Soo", - uemail: "kaithape@gmail.com", - uprofileImg: 1, - ulanguage: "english", - uchildren:[{cid: 1, cname:"Soo"}, {cid: 2, cname:"Hee"}] - }) + if (auth?.userData) { + setUser(auth?.userData); + } navigation.setOptions({ headerRight: () => ( @@ -62,12 +65,13 @@ export default function HomeScreen({ navigation }: Navigation) { .then(response => response.json()) .then(data => { setEvents(data); - }) // console.log(data) + // console.log(data); + }) .catch((error) => { console.log(error) if (error?.response?.status==401) { //redirect to login - Alert.alert("The session has expired. Please log in again."); + Alert.alert(i18n.t('sessionExpired')); auth.signOut(); navigation.dispatch(StackActions.popToTop()) } @@ -75,6 +79,12 @@ export default function HomeScreen({ navigation }: Navigation) { } }, [auth]); + useEffect(() => { + if (events && events?.children?.length > 0) { + setNowSelectedChildId(events.children[0].cid); + } + }, [events]); + const handleNowSelectedChildId = (cid: number) => { setNowSelectedChildId(cid); } @@ -88,7 +98,7 @@ export default function HomeScreen({ navigation }: Navigation) { navigation.navigate('Translate')}> - Translate + {i18n.t('translate')} @@ -96,7 +106,7 @@ export default function HomeScreen({ navigation }: Navigation) { navigation.navigate('Search')}> - Search + {i18n.t('search')} @@ -104,7 +114,7 @@ export default function HomeScreen({ navigation }: Navigation) { - Today's Events + {i18n.t('todayEvent')} {notice.events.map((event, index) => { return ( - {`[${notice.cname}] ` + event} + {`[${notice.cname}] ` + event} ) })} )) @@ -257,3 +267,4 @@ const styles = StyleSheet.create({ alignItems: 'center', } }) + diff --git a/react-native/screens/IntroductionScreen.tsx b/react-native/screens/IntroductionScreen.tsx index a03442d..863daf9 100644 --- a/react-native/screens/IntroductionScreen.tsx +++ b/react-native/screens/IntroductionScreen.tsx @@ -32,9 +32,9 @@ export default function HomeScreen({ navigation }: Navigation) { }); const auth = useAuth(); - useEffect(() => { - navigation.navigate("Home"); - }) + // useEffect(() => { + // navigation.navigate("Home"); + // }) useEffect(() => { if (response?.type === "success") { From 8f385581fbd850c9fba7605bb3b8bf618cdc55dd Mon Sep 17 00:00:00 2001 From: 0hee0 Date: Mon, 30 May 2022 00:51:23 +0900 Subject: [PATCH 26/27] [#11] feat: add extracted events modal --- react-native/screens/TranslateScreen.tsx | 114 +++++++++++++++-------- react-native/types.ts | 5 +- 2 files changed, 79 insertions(+), 40 deletions(-) diff --git a/react-native/screens/TranslateScreen.tsx b/react-native/screens/TranslateScreen.tsx index ff93801..f99bc43 100644 --- a/react-native/screens/TranslateScreen.tsx +++ b/react-native/screens/TranslateScreen.tsx @@ -1,14 +1,12 @@ import React, { useState, useEffect } from 'react'; import { StyleSheet, View, TouchableOpacity, ImageBackground, Dimensions, Alert } from 'react-native'; +import { useToast, Box, Modal, Button, HStack, Text, Divider } from 'native-base'; import { Camera } from 'expo-camera'; import { Ionicons } from '@expo/vector-icons'; import { theme } from '../core/theme'; import type { Navigation, Result, ResultsForm } from '../types'; -import AppLoading from 'expo-app-loading'; -import useFonts from '../hooks/useFonts' import SwipeUpDown from 'react-native-swipe-up-down'; import BottomDrawer from '../components/BottomDrawer'; -import { useToast, Box } from 'native-base'; import mime from "mime"; import * as ImagePicker from 'expo-image-picker'; import { useAuth } from '../contexts/Auth'; @@ -27,23 +25,19 @@ const date = new Date(); 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({fullText: [], korean: '', trans_full: ''}); + 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(false); const toast = useToast(); const auth = useAuth(); - const LoadFontsAndRestoreToken = async () => { - await useFonts(); - }; - useEffect(() => { (async () => { const { status } = await Camera.requestCameraPermissionsAsync(); @@ -55,20 +49,34 @@ export default function TranslateScreen({ navigation }: Navigation) { if (imageUri) { extractText } - }, [imageUri]) + }, [imageUri]); useEffect(() => { if (results?.fullText && results.fullText.filter(item => item.highlight === true).length > 0) { - const message = i18n.t('translateMessage_1') - toast.show({ // Design according to mui toast guidelines (https://material.io/components/snackbars#anatomy) - render: () => { - return - {message} - ; - } - }); + // const message = i18n.t('translateMessage_1') + // toast.show({ // Design according to mui toast guidelines (https://material.io/components/snackbars#anatomy) + // render: () => { + // return + // {message} + // ; + // } + // }); + if (results?.event_num) { + setOpenInitialEventForm(true); + } + else { + const message = "There are no extracted events!" + toast.show({ // Design according to mui toast guidelines (https://material.io/components/snackbars#anatomy) + placement: "top", + render: () => { + return + {message} + ; + } + }); + } } - }, [results]) + }, [results]); // DEV TEST // if (hasPermission === null) { @@ -78,16 +86,6 @@ export default function TranslateScreen({ navigation }: Navigation) { // return No access to camera! // } - if (!fontsLoaded) { - return ( - SetFontsLoaded(true)} - onError={() => {}} - /> - ); - } - const takePicture = async () => { if (camera) { const data = await camera.takePictureAsync(null); @@ -135,9 +133,9 @@ export default function TranslateScreen({ navigation }: Navigation) { }) .then(response => response.json()) .then(data => { - console.log(data) - setResults(data) - setLoading(false) + console.log(data); + setResults(data); + setLoading(false); }) .catch(function (error) { console.log(error?.response?.status) // 401 @@ -151,6 +149,30 @@ export default function TranslateScreen({ navigation }: Navigation) { }); } } + + // TEST: mockup data + // setResults({ + // fullText: [ + // {id: 1, content: "1. Schedule of the closing ceremony and diploma presentation ceremony: Friday, January 4, 2019 at 9 o'clock for students to go to school.\n1) ", date: "", highlight: false, registered: false}, + // {id: 2, content: "Closing ceremony", date: "2022-01-04", highlight: true, registered: false}, + // {id: 3, content: ": 1st and 2nd graders, each classroom, 9:00-10:50 (no meals)\n2) ", date: "", highlight: false, registered: false}, + // {id: 4, content: "Diploma representation ceremony", date: "2022-01-04", highlight: true, registered: true}, + // {id: 5, 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", + // trans_full: "", + // event_num: 2, + // events: [ + // { + // title: "opening ceremony", + // date: "2022-03-24" + // }, + // { + // title: "closing ceremony", + // date: "2022-03-24" + // } + // ] + // }) } const handleKorean = (): void => { @@ -223,10 +245,6 @@ export default function TranslateScreen({ navigation }: Navigation) { } } - const closeResults = (): void => { - navigation.navigate('Home'); - } - const retakePicture = (): void => { setImageUri(''); setResults({id: 0, fullText: [], korean: ''}); @@ -252,7 +270,6 @@ export default function TranslateScreen({ navigation }: Navigation) { openSaveForm={openSaveForm} handleKorean={handleKorean} saveResults={saveResults} - closeResults={closeResults} retakePicture={retakePicture} handleOpenSaveForm={handleOpenSaveForm} /> @@ -266,7 +283,6 @@ export default function TranslateScreen({ navigation }: Navigation) { openSaveForm={openSaveForm} handleKorean={handleKorean} saveResults={saveResults} - closeResults={closeResults} retakePicture={retakePicture} handleOpenSaveForm={handleOpenSaveForm} /> @@ -278,8 +294,28 @@ export default function TranslateScreen({ navigation }: Navigation) { extraMarginTop={10} swipeHeight={Dimensions.get('window').height*0.65} /> + + + + {results?.event_num} Events Extracted + + + {results?.events?.map((item, index) => + + {item.date}  + {item.title} + + )} + + + + + + + - ) : ( /* After taking a picture, before OCR(pressing the check button) */ diff --git a/react-native/types.ts b/react-native/types.ts index 5be067b..2c9db0a 100644 --- a/react-native/types.ts +++ b/react-native/types.ts @@ -77,7 +77,10 @@ interface Result { imageUri?: string, fullText: Event[], korean: string, - trans_full?: string + trans_full?: string, + + event_num?: number, + events?: { title: string, date: string }[] } interface Notice { From 50d8f06b7a21bad5087a83aa13ded1c2d8ff0d92 Mon Sep 17 00:00:00 2001 From: 0hee0 Date: Mon, 30 May 2022 10:46:00 +0900 Subject: [PATCH 27/27] [#3] feat: add child profile image --- react-native/screens/HomeScreen.tsx | 38 ++++++++++++++++++----------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/react-native/screens/HomeScreen.tsx b/react-native/screens/HomeScreen.tsx index a19d947..52062bd 100644 --- a/react-native/screens/HomeScreen.tsx +++ b/react-native/screens/HomeScreen.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { StyleSheet, View, SafeAreaView, TouchableOpacity, ImageBackground, Alert } from 'react-native'; +import { StyleSheet, View, SafeAreaView, TouchableOpacity, ImageBackground, Alert, Image } from 'react-native'; import { Text } from 'native-base' import { theme } from '../core/theme'; import type { Navigation, UserData } from '../types'; @@ -12,20 +12,24 @@ import i18n from 'i18n-js' import '../locales/i18n'; export default function HomeScreen({ navigation }: Navigation) { - const [events, setEvents] = useState<{event_num: number, children: { cid: number, cname: string, events: string[] }[]}>( + const cProfileImgSource = [require(`../assets/images/cprofile-images/profile-1.png`), require(`../assets/images/cprofile-images/profile-2.png`), require(`../assets/images/cprofile-images/profile-3.png`), + require(`../assets/images/cprofile-images/profile-4.png`), require(`../assets/images/cprofile-images/profile-5.png`), require(`../assets/images/cprofile-images/profile-6.png`), require(`../assets/images/cprofile-images/profile-7.png`), require(`../assets/images/cprofile-images/profile-8.png`), require(`../assets/images/cprofile-images/profile-9.png`)]; + const [events, setEvents] = useState<{event_num: number, children: { cid: number, cname: string, cprofileImg: number, events: string[] }[]}>( {event_num: 4, children: [ { cid: 1, cname: "Soo", + cprofileImg: 2, events: [ "the 17th Graduate Seremony", "Do-Dream Festival" - ] + ], }, { cid: 2, cname: "Hee", - events: [] + events: [], + cprofileImg: 1, } ] } @@ -123,13 +127,14 @@ export default function HomeScreen({ navigation }: Navigation) { color: nowSelectedChildId !== SHOW_ALL ? theme.colors.primary : "#ffffff", }]}>All - {events.children?.map((notice, index) => + {events.children?.map((child, index) => handleNowSelectedChildId(notice.cid)}> + backgroundColor: nowSelectedChildId === child.cid ? theme.colors.primary : "#ffffff", + }]} onPress={() => handleNowSelectedChildId(child.cid)}> + {notice.cname} + color: nowSelectedChildId !== child.cid ? theme.colors.primary : "#ffffff", + }]}>{child.cname} )} @@ -203,13 +208,14 @@ const styles = StyleSheet.create({ childButton: { borderWidth: 1, borderColor: theme.colors.primary, - height: 30, - borderRadius: 16, - justifyContent: "center", + height: 40, + borderRadius: 32, + flexDirection: "row", + justifyContent: "space-between", alignItems: "center", paddingHorizontal: 16, alignSelf: 'flex-start', - marginRight: 8, + marginRight: 12, }, todayNoticeWrapper: { alignSelf: "flex-start", @@ -265,6 +271,10 @@ const styles = StyleSheet.create({ flexDirection: 'column', justifyContent: 'center', alignItems: 'center', + }, + cprofileImage: { + width: 20, + height: 20, + marginRight: 12 } }) -