From 785d5a3a707b9d2aedb95135df297be79e80d456 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:32:54 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=E2=9C=A8=20Feat:=20language?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/languages.dart | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 lib/languages.dart diff --git a/lib/languages.dart b/lib/languages.dart new file mode 100644 index 0000000..9c874b3 --- /dev/null +++ b/lib/languages.dart @@ -0,0 +1,11 @@ +import 'package:get/get.dart'; + +class Languages extends Translations { + @override + Map> get keys => { + 'ko_KR': {'title': '제목'}, + 'en_US': {'title': 'title'}, + 'ja_JP': {'title': '題名'}, + 'vi_VN': {'title': 'Tiêu đề'}, + }; +} From 83b49a8aa8e45ec4b6134eb258afc7e011bd3c64 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:33:15 +0900 Subject: [PATCH 2/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20fail=20wor?= =?UTF-8?q?d=20dialog=20widget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../word/widget/fail_word_dialog_widget.dart | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 lib/views/word/widget/fail_word_dialog_widget.dart diff --git a/lib/views/word/widget/fail_word_dialog_widget.dart b/lib/views/word/widget/fail_word_dialog_widget.dart new file mode 100644 index 0000000..321221c --- /dev/null +++ b/lib/views/word/widget/fail_word_dialog_widget.dart @@ -0,0 +1,46 @@ +import 'package:earlips/utilities/style/color_styles.dart'; +import 'package:earlips/viewModels/word/word_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +Future FailWordDialogWidget( + WordViewModel wordViewModel, PageController pageController) { + return Get.dialog( + AlertDialog( + title: const Text('학습 실패'), + content: const Text('다시 한번 녹음해주세요.'), + backgroundColor: ColorSystem.white, + surfaceTintColor: ColorSystem.white, + actions: [ + ElevatedButton( + onPressed: () async { + Get.back(); + }, + child: const Text('다시하기'), + ), + ElevatedButton( + onPressed: () async { + // 마지막 단어가 아닐 경우 뒤로가기, 마지막 단어일 경우 홈으로 이동 + wordViewModel.currentIndex.value < wordViewModel.wordList.length - 1 + ? Get.back() + : Get.offAllNamed('/'); + // 다음 단어로 넘어가기 + if (wordViewModel.currentIndex.value < + wordViewModel.wordList.length - 1) { + pageController.animateToPage( + wordViewModel.currentIndex.value + 1, + duration: const Duration(milliseconds: 300), + curve: Curves.ease, + ); + + // currentIndex 증가 + wordViewModel.currentIndex.value = + wordViewModel.currentIndex.value + 1; + } + }, + child: const Text('다음 단어'), + ), + ], + ), + ); +} From 10696404aa877cc4f800e04a5ad85efb4c19f4e8 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:33:24 +0900 Subject: [PATCH 3/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20highlift?= =?UTF-8?q?=20widget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widget/highlight_mistake_text_widget.dart | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 lib/views/word/widget/highlight_mistake_text_widget.dart diff --git a/lib/views/word/widget/highlight_mistake_text_widget.dart b/lib/views/word/widget/highlight_mistake_text_widget.dart new file mode 100644 index 0000000..d48fe59 --- /dev/null +++ b/lib/views/word/widget/highlight_mistake_text_widget.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +class HighlightMistakesTextWidget extends StatelessWidget { + final List userWords; // 사용자가 발음한 단어 리스트 + final List wrongIndices; // 잘못 발음한 단어의 인덱스 리스트 + + const HighlightMistakesTextWidget({ + super.key, + required this.userWords, + required this.wrongIndices, + }); + + @override + Widget build(BuildContext context) { + return wrongIndices[0] == -1 + ? const Text("틀린단어 없음") + : RichText( + text: TextSpan( + children: userWords.asMap().entries.map((entry) { + int idx = entry.key; + String word = entry.value; + bool isWrong = wrongIndices.contains(idx); + return TextSpan( + text: "$word ", // 단어와 공백을 포함시킵니다. + style: TextStyle( + color: isWrong + ? Colors.red + : Colors.black, // 잘못된 부분은 빨간색으로, 그 외는 검정색으로 표시 + ), + ); + }).toList(), + ), + ); + } +} From 123107b05724ab431bf5a5c8f99ed6e7eec4ba05 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:33:33 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20sentence?= =?UTF-8?q?=20alrt=20widget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../word/widget/sentence_alert_widget.dart | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 lib/views/word/widget/sentence_alert_widget.dart diff --git a/lib/views/word/widget/sentence_alert_widget.dart b/lib/views/word/widget/sentence_alert_widget.dart new file mode 100644 index 0000000..28f6dd9 --- /dev/null +++ b/lib/views/word/widget/sentence_alert_widget.dart @@ -0,0 +1,185 @@ +import 'package:earlips/utilities/style/color_styles.dart'; +import 'package:earlips/viewModels/record/record_viewmodel.dart'; +import 'package:earlips/viewModels/word/word_viewmodel.dart'; +import 'package:earlips/views/word/widget/highlight_mistake_text_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +Future SentenceAlertWidget(RecordViewModel model, + WordViewModel wordViewModel, PageController pageController) { + return Get.dialog( + AlertDialog( + title: const Text('문장 테스트 결과', + style: TextStyle( + color: ColorSystem.black, + fontSize: 20, + )), + surfaceTintColor: ColorSystem.white, + backgroundColor: ColorSystem.white, + content: SingleChildScrollView( + child: ListBody( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '문장', + style: TextStyle( + color: ColorSystem.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + '${model.response['sentence_word'].join(" ")}', + style: const TextStyle( + color: ColorSystem.black, + fontSize: 14, + ), + ), + ], + ), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '사용자 발음', + style: TextStyle( + color: ColorSystem.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + HighlightMistakesTextWidget( + userWords: model.response['user_word'], + wrongIndices: model.response['wrong'], + ), + ], + ), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '틀린 부분', + style: TextStyle( + color: ColorSystem.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + '${model.response['wrong'][0] == -1 ? "없음" : model.response['wrong'].join(", ")} 번째 단어'), + ], + ), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '볼륨', + style: TextStyle( + color: ColorSystem.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text('${model.response['loudness']}'), + ], + ), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '변동성', + style: TextStyle( + color: ColorSystem.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text(model.response['variance'] == 1 ? "일정함" : "변동 폭 큼", + style: const TextStyle( + color: ColorSystem.black, + fontSize: 14, + )), + ], + ), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '속도', + style: TextStyle( + color: ColorSystem.black, + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + Text( + _getSpeedDescription(model.response['speed']), + style: const TextStyle( + color: ColorSystem.black, + fontSize: 14, + ), + ), + ], + ) + ], + ), + ), + actions: [ + TextButton( + child: const Text( + '다음', + style: TextStyle( + color: ColorSystem.black, + fontSize: 16, + ), + ), + onPressed: () async { + // 다음으로 넘어가는 코드 + await wordViewModel.markWordAsDone(wordViewModel + .wordList[wordViewModel.currentIndex.value].wordCard); + wordViewModel.currentIndex.value < wordViewModel.wordList.length - 1 + ? Get.back() + : Get.offAllNamed('/'); + + // 다음 단어로 넘어가기 + if (wordViewModel.currentIndex.value < + wordViewModel.wordList.length - 1) { + pageController.animateToPage( + wordViewModel.currentIndex.value + 1, + duration: const Duration(milliseconds: 300), + curve: Curves.ease, + ); + // currentIndex 증가 + wordViewModel.currentIndex.value = + wordViewModel.currentIndex.value + 1; + } + }, + ), + ], + ), + ); +} + +String _getSpeedDescription(double speed) { + switch (speed) { + case 0: + return '엄청 느림'; + case 0.5: + return '느림'; + case 1: + return '평범'; + case 1.5: + return '약간 빠름'; + case 2: + return '완전 빠름'; + default: + return '알 수 없음'; // 속도 값이 주어진 범위에 없는 경우 + } +} From c51a3d21294965f8117e3624780245bf8aab86b8 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:33:41 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20suceess=20?= =?UTF-8?q?widget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widget/sucess_word_dialog_widget.dart | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 lib/views/word/widget/sucess_word_dialog_widget.dart diff --git a/lib/views/word/widget/sucess_word_dialog_widget.dart b/lib/views/word/widget/sucess_word_dialog_widget.dart new file mode 100644 index 0000000..bac5266 --- /dev/null +++ b/lib/views/word/widget/sucess_word_dialog_widget.dart @@ -0,0 +1,39 @@ +import 'package:earlips/viewModels/word/word_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +Future SucessWordDialogWidget( + WordViewModel wordViewModel, PageController pageController) { + return Get.dialog( + AlertDialog( + title: const Text('학습 완료'), + content: const Text('다음으로 넘어가려면 아래 버튼을 눌러주세요.'), + actions: [ + ElevatedButton( + onPressed: () async { + await wordViewModel.markWordAsDone(wordViewModel + .wordList[wordViewModel.currentIndex.value].wordCard); + wordViewModel.currentIndex.value < wordViewModel.wordList.length - 1 + ? Get.back() + : Get.offAllNamed('/'); + + // 다음 단어로 넘어가기 + if (wordViewModel.currentIndex.value < + wordViewModel.wordList.length - 1) { + pageController.animateToPage( + wordViewModel.currentIndex.value + 1, + duration: const Duration(milliseconds: 300), + curve: Curves.ease, + ); + + // currentIndex 증가 + wordViewModel.currentIndex.value = + wordViewModel.currentIndex.value + 1; + } + }, + child: const Text('다음 단어'), + ), + ], + ), + ); +} From 49806d2c458ff592dc601fede934e1e8e7826289 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:33:46 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20word=20res?= =?UTF-8?q?ult=20widget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widget/word_result_dialog_widget.dart | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 lib/views/word/widget/word_result_dialog_widget.dart diff --git a/lib/views/word/widget/word_result_dialog_widget.dart b/lib/views/word/widget/word_result_dialog_widget.dart new file mode 100644 index 0000000..ecb602b --- /dev/null +++ b/lib/views/word/widget/word_result_dialog_widget.dart @@ -0,0 +1,87 @@ +import 'package:earlips/utilities/style/color_styles.dart'; +import 'package:earlips/viewModels/record/record_viewmodel.dart'; +import 'package:earlips/viewModels/word/word_viewmodel.dart'; +import 'package:earlips/views/word/widget/fail_word_dialog_widget.dart'; +import 'package:earlips/views/word/widget/sucess_word_dialog_widget.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +Future WordResultDialogWidget(RecordViewModel model, + WordViewModel wordViewModel, PageController pageController) { + return Get.dialog( + AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + insetPadding: const EdgeInsets.all(20), + surfaceTintColor: ColorSystem.white, + title: const Text( + '결과', + style: TextStyle( + color: ColorSystem.black, + fontSize: 20, + ), + textAlign: TextAlign.center, + ), + content: GetBuilder( + builder: (model) => SizedBox( + height: 100, + child: Column( + textBaseline: TextBaseline.alphabetic, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + model.response['pronunciation'] != null + ? '발음: ${model.response['pronunciation']}' + : '너무 빠르거나 느립니다. 다시 녹음해주세요', + style: const TextStyle( + color: ColorSystem.black, + fontSize: 16, + ), + ), + Text( + model.response['similarity'] != null + ? '정확도: ${model.response['similarity']}' + : "", + style: const TextStyle( + color: ColorSystem.black, + fontSize: 16, + )), + ], + ), + ), + ), + actions: [ + TextButton( + onPressed: () { + Get.back(); + if (model.response['similarity'] >= 70) { + SucessWordDialogWidget(wordViewModel, pageController); + } else { + // 80 미만일 경우 다른 AlertDialog를 표시 다시하기 혹은 다음으로 + FailWordDialogWidget(wordViewModel, pageController); + } + }, + child: Center( + child: Container( + padding: const EdgeInsets.fromLTRB(30, 10, 30, 10), + decoration: BoxDecoration( + color: ColorSystem.white, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: ColorSystem.black), + ), + child: const Text( + '확인', + textAlign: TextAlign.center, + style: TextStyle( + color: ColorSystem.black, + ), + ), + ), + ), + ), + ], + ), + ); +} From 20999fc0a7b2f5c3e74d67a7a9e714998c9bdc2c Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Wed, 21 Feb 2024 21:33:51 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20word=20sen?= =?UTF-8?q?tence=20widget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../word/widget/word_sentence_widget.dart | 407 +----------------- 1 file changed, 6 insertions(+), 401 deletions(-) diff --git a/lib/views/word/widget/word_sentence_widget.dart b/lib/views/word/widget/word_sentence_widget.dart index e0f9130..d637b34 100644 --- a/lib/views/word/widget/word_sentence_widget.dart +++ b/lib/views/word/widget/word_sentence_widget.dart @@ -1,7 +1,8 @@ import 'package:earlips/models/word_data_model.dart'; -import 'package:earlips/utilities/style/color_styles.dart'; import 'package:earlips/viewModels/record/record_viewmodel.dart'; import 'package:earlips/viewModels/word/word_viewmodel.dart'; +import 'package:earlips/views/word/widget/sentence_alert_widget.dart'; +import 'package:earlips/views/word/widget/word_result_dialog_widget.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; // Import GetX library @@ -43,358 +44,13 @@ class WordSentenceWidget extends StatelessWidget { .wordCard .word, type); - - // Handle the response here, e.g., show it in a dialog if (type == 2) { // 타입이 2일 경우, 문장 교정 정보를 보여주는 대화상자를 표시 - Get.dialog( - AlertDialog( - title: const Text('문장 테스트 결과', - style: TextStyle( - color: ColorSystem.black, - fontSize: 20, - )), - surfaceTintColor: ColorSystem.white, - backgroundColor: ColorSystem.white, - content: SingleChildScrollView( - child: ListBody( - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const Text( - '문장', - style: TextStyle( - color: ColorSystem.black, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - Text( - '${model.response['sentence_word'].join(" ")}', - style: const TextStyle( - color: ColorSystem.black, - fontSize: 14, - ), - ), - ], - ), - SizedBox(height: 10), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const Text( - '사용자 발음', - style: TextStyle( - color: ColorSystem.black, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - HighlightMistakesTextWidget( - userWords: - model.response['user_word'], - wrongIndices: model.response['wrong'], - ), - ], - ), - SizedBox(height: 10), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const Text( - '틀린 부분', - style: TextStyle( - color: ColorSystem.black, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - Text( - '${model.response['wrong'][0] == -1 ? "없음" : model.response['wrong'].join(", ")} 번째 단어'), - ], - ), - SizedBox(height: 10), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const Text( - '볼륨', - style: TextStyle( - color: ColorSystem.black, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - Text('${model.response['loudness']}'), - ], - ), - SizedBox(height: 10), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const Text( - '변동성', - style: TextStyle( - color: ColorSystem.black, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - Text( - '${model.response['variance'] == 1 ? "일정함" : "변동 폭 큼"}', - style: const TextStyle( - color: ColorSystem.black, - fontSize: 14, - )), - ], - ), - SizedBox(height: 10), - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - const Text( - '속도', - style: TextStyle( - color: ColorSystem.black, - fontSize: 16, - fontWeight: FontWeight.w600, - ), - ), - Text( - _getSpeedDescription( - model.response['speed']), - style: const TextStyle( - color: ColorSystem.black, - fontSize: 14, - ), - ), - ], - ) - ], - ), - ), - actions: [ - TextButton( - child: const Text( - '다음', - style: TextStyle( - color: ColorSystem.black, - fontSize: 16, - ), - ), - onPressed: () async { - // 다음으로 넘어가는 코드 - await wordViewModel.markWordAsDone( - wordViewModel - .wordList[wordViewModel - .currentIndex.value] - .wordCard); - wordViewModel.currentIndex.value < - wordViewModel.wordList.length - 1 - ? Get.back() - : Get.offAllNamed('/'); - - // 다음 단어로 넘어가기 - if (wordViewModel.currentIndex.value < - wordViewModel.wordList.length - 1) { - pageController.animateToPage( - wordViewModel.currentIndex.value + 1, - duration: - const Duration(milliseconds: 300), - curve: Curves.ease, - ); - // currentIndex 증가 - wordViewModel.currentIndex.value = - wordViewModel.currentIndex.value + 1; - } - }, - ), - ], - ), - ); + SentenceAlertWidget( + model, wordViewModel, pageController); } else { - Get.dialog( - AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(20), - ), - insetPadding: const EdgeInsets.all(20), - surfaceTintColor: ColorSystem.white, - title: const Text( - '결과', - style: TextStyle( - color: ColorSystem.black, - fontSize: 20, - ), - textAlign: TextAlign.center, - ), - content: GetBuilder( - builder: (model) => SizedBox( - height: 100, - child: Column( - textBaseline: TextBaseline.alphabetic, - crossAxisAlignment: - CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - model.response['pronunciation'] != null - ? '발음: ${model.response['pronunciation']}' - : '너무 빠르거나 느립니다. 다시 녹음해주세요', - style: const TextStyle( - color: ColorSystem.black, - fontSize: 16, - ), - ), - Text( - model.response['similarity'] != null - ? '정확도: ${model.response['similarity']}' - : "", - style: const TextStyle( - color: ColorSystem.black, - fontSize: 16, - )), - ], - ), - ), - ), - actions: [ - TextButton( - onPressed: () { - Get.back(); - if (model.response['similarity'] >= 70) { - Get.dialog( - AlertDialog( - title: const Text('학습 완료'), - content: const Text( - '다음으로 넘어가려면 아래 버튼을 눌러주세요.'), - actions: [ - ElevatedButton( - onPressed: () async { - await wordViewModel - .markWordAsDone(wordViewModel - .wordList[wordViewModel - .currentIndex.value] - .wordCard); - wordViewModel.currentIndex - .value < - wordViewModel.wordList - .length - - 1 - ? Get.back() - : Get.offAllNamed('/'); - - // 다음 단어로 넘어가기 - if (wordViewModel - .currentIndex.value < - wordViewModel - .wordList.length - - 1) { - pageController.animateToPage( - wordViewModel.currentIndex - .value + - 1, - duration: const Duration( - milliseconds: 300), - curve: Curves.ease, - ); - - // currentIndex 증가 - wordViewModel.currentIndex - .value = wordViewModel - .currentIndex.value + - 1; - } - }, - child: const Text('다음 단어'), - ), - ], - ), - ); - } else { - // 80 미만일 경우 다른 AlertDialog를 표시 다시하기 혹은 다음으로 - Get.dialog( - AlertDialog( - title: const Text('학습 실패'), - content: const Text('다시 한번 녹음해주세요.'), - backgroundColor: ColorSystem.white, - surfaceTintColor: ColorSystem.white, - actions: [ - ElevatedButton( - onPressed: () async { - Get.back(); - }, - child: const Text('다시하기'), - ), - ElevatedButton( - onPressed: () async { - // 마지막 단어가 아닐 경우 뒤로가기, 마지막 단어일 경우 홈으로 이동 - wordViewModel.currentIndex - .value < - wordViewModel.wordList - .length - - 1 - ? Get.back() - : Get.offAllNamed('/'); - // 다음 단어로 넘어가기 - if (wordViewModel - .currentIndex.value < - wordViewModel - .wordList.length - - 1) { - pageController.animateToPage( - wordViewModel.currentIndex - .value + - 1, - duration: const Duration( - milliseconds: 300), - curve: Curves.ease, - ); - - // currentIndex 증가 - wordViewModel.currentIndex - .value = wordViewModel - .currentIndex.value + - 1; - } - }, - child: const Text('다음 단어'), - ), - ], - ), - ); - } - }, - child: Center( - child: Container( - padding: const EdgeInsets.fromLTRB( - 30, 10, 30, 10), - decoration: BoxDecoration( - color: ColorSystem.white, - borderRadius: BorderRadius.circular(20), - border: Border.all( - color: ColorSystem.black), - ), - child: const Text( - '확인', - textAlign: TextAlign.center, - style: TextStyle( - color: ColorSystem.black, - ), - ), - ), - ), - ), - ], - ), - ); + WordResultDialogWidget( + model, wordViewModel, pageController); } } else { // 녹음 시작 @@ -418,55 +74,4 @@ class WordSentenceWidget extends StatelessWidget { }, ); } - - String _getSpeedDescription(double speed) { - switch (speed) { - case 0: - return '엄청 느림'; - case 0.5: - return '느림'; - case 1: - return '평범'; - case 1.5: - return '약간 빠름'; - case 2: - return '완전 빠름'; - default: - return '알 수 없음'; // 속도 값이 주어진 범위에 없는 경우 - } - } -} - -class HighlightMistakesTextWidget extends StatelessWidget { - final List userWords; // 사용자가 발음한 단어 리스트 - final List wrongIndices; // 잘못 발음한 단어의 인덱스 리스트 - - HighlightMistakesTextWidget({ - Key? key, - required this.userWords, - required this.wrongIndices, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return wrongIndices[0] == -1 - ? Text("틀린단어 없음") - : RichText( - text: TextSpan( - children: userWords.asMap().entries.map((entry) { - int idx = entry.key; - String word = entry.value; - bool isWrong = wrongIndices.contains(idx); - return TextSpan( - text: "$word ", // 단어와 공백을 포함시킵니다. - style: TextStyle( - color: isWrong - ? Colors.red - : Colors.black, // 잘못된 부분은 빨간색으로, 그 외는 검정색으로 표시 - ), - ); - }).toList(), - ), - ); - } } From 46a3e248529d7bd68754e6d7fb2c756f585ec85c Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Wed, 21 Feb 2024 22:33:56 +0900 Subject: [PATCH 8/8] =?UTF-8?q?=E2=9C=A8Feat=20:=20=EC=9D=8C=EC=A0=95=20?= =?UTF-8?q?=EC=B0=A8=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/viewModels/record/record_viewmodel.dart | 23 +- .../word/widget/sentence_alert_widget.dart | 33 ++- .../word/widget/sentence_guide_widget.dart | 206 ++++++++++++++++++ .../word/widget/word_sentence_widget.dart | 16 ++ lib/views/word/word_screen.dart | 74 +------ 5 files changed, 259 insertions(+), 93 deletions(-) create mode 100644 lib/views/word/widget/sentence_guide_widget.dart diff --git a/lib/viewModels/record/record_viewmodel.dart b/lib/viewModels/record/record_viewmodel.dart index 07e8dc9..025b373 100644 --- a/lib/viewModels/record/record_viewmodel.dart +++ b/lib/viewModels/record/record_viewmodel.dart @@ -8,15 +8,21 @@ import 'package:path_provider/path_provider.dart'; class RecordViewModel extends GetxController { final FlutterSoundRecorder _audioRecorder = FlutterSoundRecorder(); - bool _isRecorderInitialized = false; // 녹음기 초기화 여부 : 파일 + bool _isRecorderInitialized = false; final RxBool isRecording = false.obs; RxString audioFilePath = ''.obs; + RxDouble loudness = 0.0.obs; // Reactive variable for loudness + final RxMap response = {}.obs; // Specify the types @override void onInit() { _requestPermission(); + _audioRecorder.openRecorder(); // Initialize recorder + _audioRecorder.onProgress!.listen((e) { + loudness.value = e.decibels! / 160; // Example: Normalize decibels + }); super.onInit(); } @@ -65,18 +71,13 @@ class RecordViewModel extends GetxController { } Future sendTextAndAudio(String content, int type) async { - isRecording.value ? await _stopRecording() : await _startRecording(); update(); - if(isRecording.value == true) { + if (isRecording.value == true) { return; } - print("들어오긴 함?"); String url = '${dotenv.env['API_URL']!}/study/${type == 0 ? 'syllable' : (type == 1 ? 'word' : 'sentence')}'; - - - print(audioFilePath.value); if (audioFilePath.value.isEmpty) { return; } @@ -93,14 +94,10 @@ class RecordViewModel extends GetxController { final jsonResponse = json.decode(respStr); // this.response.value = jsonResponse; - print(jsonResponse); - } else { - print('Failed to upload'); - } - } catch (e) {} + } else {} + } catch (_) {} } - @override void onClose() { _audioRecorder.closeRecorder(); diff --git a/lib/views/word/widget/sentence_alert_widget.dart b/lib/views/word/widget/sentence_alert_widget.dart index 28f6dd9..6731837 100644 --- a/lib/views/word/widget/sentence_alert_widget.dart +++ b/lib/views/word/widget/sentence_alert_widget.dart @@ -31,12 +31,12 @@ Future SentenceAlertWidget(RecordViewModel model, ), ), Text( - '${model.response['sentence_word'].join(" ")}', + '${model.response['sentence_word'] != null ? model.response['sentence_word'].join(" ") : "다시 녹음해주세요"}', style: const TextStyle( color: ColorSystem.black, fontSize: 14, ), - ), + ) ], ), const SizedBox(height: 10), @@ -52,8 +52,8 @@ Future SentenceAlertWidget(RecordViewModel model, ), ), HighlightMistakesTextWidget( - userWords: model.response['user_word'], - wrongIndices: model.response['wrong'], + userWords: model.response['user_word'] ?? [], + wrongIndices: model.response['wrong'] ?? [-1], ), ], ), @@ -70,7 +70,12 @@ Future SentenceAlertWidget(RecordViewModel model, ), ), Text( - '${model.response['wrong'][0] == -1 ? "없음" : model.response['wrong'].join(", ")} 번째 단어'), + '${model.response['wrong'] != null && model.response['wrong'][0] != -1 ? model.response['wrong'].join(", ") : "알 수 없음"} 번째 단어', + style: const TextStyle( + color: ColorSystem.black, + fontSize: 14, + ), + ) ], ), const SizedBox(height: 10), @@ -85,7 +90,7 @@ Future SentenceAlertWidget(RecordViewModel model, fontWeight: FontWeight.w600, ), ), - Text('${model.response['loudness']}'), + Text('${model.response['loudness'] ?? '알 수 없음'}'), ], ), const SizedBox(height: 10), @@ -100,11 +105,15 @@ Future SentenceAlertWidget(RecordViewModel model, fontWeight: FontWeight.w600, ), ), - Text(model.response['variance'] == 1 ? "일정함" : "변동 폭 큼", - style: const TextStyle( - color: ColorSystem.black, - fontSize: 14, - )), + Text( + model.response['variance'] == null + ? "알 수 없음" + : (model.response['variance'] == 1 ? "일정함" : "변동 폭 큼"), + style: const TextStyle( + color: ColorSystem.black, + fontSize: 14, + ), + ) ], ), const SizedBox(height: 10), @@ -120,7 +129,7 @@ Future SentenceAlertWidget(RecordViewModel model, ), ), Text( - _getSpeedDescription(model.response['speed']), + _getSpeedDescription(model.response['speed'] ?? 1), style: const TextStyle( color: ColorSystem.black, fontSize: 14, diff --git a/lib/views/word/widget/sentence_guide_widget.dart b/lib/views/word/widget/sentence_guide_widget.dart new file mode 100644 index 0000000..2116dde --- /dev/null +++ b/lib/views/word/widget/sentence_guide_widget.dart @@ -0,0 +1,206 @@ +import 'dart:ffi'; + +import 'package:earlips/utilities/style/color_styles.dart'; +import 'package:earlips/viewModels/record/record_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:fl_chart/fl_chart.dart'; +import 'package:get/get.dart'; + +class PronunciationGuidelinesWidget extends StatelessWidget { + final double loudness; // Loudness value (0 ~ 100) + final int variance; // Variance value (1 or -1) + + const PronunciationGuidelinesWidget({ + super.key, + required this.loudness, + required this.variance, + }); + + @override + Widget build(BuildContext context) { + return GetBuilder(builder: (model) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _buildVarianceChart(variance), + const SizedBox( + height: 30, + ), + _buildLoudnessChart(model.loudness.value), + ], + ); + }); + } + + // 음량 차트 + Widget _buildLoudnessChart(double loudness) { + return SizedBox( + height: 100, + child: BarChart( + BarChartData( + barGroups: [ + BarChartGroupData( + x: 100, + barRods: [ + BarChartRodData( + toY: 100, + color: ColorSystem.main, + width: 40, + borderRadius: BorderRadius.circular(5), // Rounded edges + ), + BarChartRodData( + toY: loudness, + color: ColorSystem.gray4, + width: 40, // Thicker bars for better visibility + borderRadius: BorderRadius.circular(5), // Rounded edges + ), + ], + ), + ], + titlesData: FlTitlesData( + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) => const Text('목소리 높이', + style: + TextStyle(fontSize: 14, fontWeight: FontWeight.w600)), + ), + ), + ), + borderData: FlBorderData( + border: const Border( + bottom: BorderSide(), + left: BorderSide(), + ), + ), + ), + ), + ); + } + + Widget _buildVarianceChart(int variance) { + return SizedBox( + width: 100, + height: 150, + child: PieChart( + PieChartData( + sectionsSpace: 10, // Add some space between sections + centerSpaceRadius: 40, // Make the center hole larger + startDegreeOffset: -90, // Rotate chart to start at top + sections: [ + PieChartSectionData( + value: 100, + color: ColorSystem.main, + title: '변동 일정', + titleStyle: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + badgePositionPercentageOffset: 10, // Center badge text + ), + PieChartSectionData( + value: variance == -1 ? 100 : 0, + color: ColorSystem.gray4, + title: '변동 일정', + titleStyle: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + badgePositionPercentageOffset: 10, // Center badge text + ), + PieChartSectionData( + value: variance == -1 ? 100 : 0, + color: Colors.red.shade400, + title: '변동', + titleStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + badgePositionPercentageOffset: 50, // Center badge text + ), + ], + ), + ), + ); + } + + // 속도 차트 + Widget _buildSpeedChart(double speed) { + return SizedBox( + height: 100, // Adjust height as needed + child: LineChart( + LineChartData( + lineBarsData: [ + LineChartBarData( + spots: [ + const FlSpot(0, 0), + FlSpot(1, speed), + ], + color: Colors.purple.withOpacity(0.8), + gradient: LinearGradient( + colors: [ + Colors.purple.shade700, + Colors.purple.shade300, + ], + begin: Alignment.bottomLeft, + end: Alignment.topRight, + ), + barWidth: 3, // Increase line thickness + isStrokeCapRound: true, // Round edges of line + dotData: const FlDotData( + show: true, // Show data point markers + ), + ), + ], + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + interval: 1, // Show labels for every speed value + reservedSize: 30, + getTitlesWidget: (value, meta) => Text( + '${value.toInt()}배', + style: const TextStyle(fontSize: 14), + ), + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) => const Text('속도'), + ), + ), + ), + gridData: const FlGridData( + show: true, + drawVerticalLine: false, // Remove vertical grid lines + horizontalInterval: 1, // Show grid lines for every speed value + ), + borderData: FlBorderData( + border: const Border( + bottom: BorderSide(), + left: BorderSide(), + ), + ), + minX: 0, + maxX: 1, + minY: 0, + maxY: speed + 0.5, // Set max Y based on speed value + extraLinesData: ExtraLinesData( + // Remove square brackets here + horizontalLines: [ + HorizontalLine( + y: speed, + color: Colors.purple.shade500, + strokeWidth: 2, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/views/word/widget/word_sentence_widget.dart b/lib/views/word/widget/word_sentence_widget.dart index d637b34..97f4a8b 100644 --- a/lib/views/word/widget/word_sentence_widget.dart +++ b/lib/views/word/widget/word_sentence_widget.dart @@ -2,6 +2,7 @@ import 'package:earlips/models/word_data_model.dart'; import 'package:earlips/viewModels/record/record_viewmodel.dart'; import 'package:earlips/viewModels/word/word_viewmodel.dart'; import 'package:earlips/views/word/widget/sentence_alert_widget.dart'; +import 'package:earlips/views/word/widget/sentence_guide_widget.dart'; import 'package:earlips/views/word/widget/word_result_dialog_widget.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; // Import GetX library @@ -28,6 +29,21 @@ class WordSentenceWidget extends StatelessWidget { return Center( child: Column( children: [ + type == 2 + ? const Column( + children: [ + PronunciationGuidelinesWidget( + loudness: 50, + variance: 1, + ), + SizedBox( + height: 60, + ), + ], + ) + : const SizedBox( + height: 20, + ), Align( alignment: Alignment.bottomCenter, child: Ink( diff --git a/lib/views/word/word_screen.dart b/lib/views/word/word_screen.dart index 350d657..6f69dfc 100644 --- a/lib/views/word/word_screen.dart +++ b/lib/views/word/word_screen.dart @@ -6,7 +6,6 @@ import 'package:earlips/views/word/widget/word_sentence_widget.dart'; import 'package:earlips/views/word/widget/word_youtube_widget.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:earlips/views/paragraph/create_script_screen.dart'; import 'package:earlips/views/paragraph/learning_session_screen.dart'; class WordScreen extends StatelessWidget { @@ -51,16 +50,18 @@ class WordScreen extends StatelessWidget { pageController: pageController, ), ), - const SizedBox( + SizedBox( height: 70, child: Column( children: [ - SizedBox( + const SizedBox( height: 15, ), Text( - "아래의 혀, 입술 모양을 따라 말해보세요!", - style: TextStyle( + type == 2 + ? "문장의 높낮이를 참고하세요" + : "아래의 혀, 입술 모양을 따라 말해보세요!", + style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: ColorSystem.white, @@ -114,69 +115,6 @@ class WordScreen extends StatelessWidget { } }, ), - // const Spacer(), - // // final String video로 영상 유튜브 링크를 바로 볼 수 있게 하기 - // ElevatedButton( - // style: ButtonStyle( - // padding: MaterialStateProperty.all( - // const EdgeInsets.fromLTRB(30, 10, 30, 10), - // ), - // backgroundColor: MaterialStateProperty.all(ColorSystem.main2), - // ), - // onPressed: () async { - // // isLast - // Get.dialog( - // AlertDialog( - // title: const Text('학습 완료'), - // content: const Text('다음으로 넘어가려면 아래 버튼을 눌러주세요.'), - // actions: [ - // ElevatedButton( - // // button style - // onPressed: () async { - // // 단어 학습 완료 처리 => - // await wordViewModel.markWordAsDone(wordViewModel - // .wordList[wordViewModel.currentIndex.value] - // .wordCard); - - // // 마지막 단어가 아닐 경우 뒤로가기, 마지막 단어일 경우 홈으로 이동 - // wordViewModel.currentIndex.value < - // wordViewModel.wordList.length - 1 - // ? Get.back() - // : Get.offAllNamed('/'); - - // // 다음 단어로 넘어가기 - // if (wordViewModel.currentIndex.value < - // wordViewModel.wordList.length - 1) { - // pageController.animateToPage( - // wordViewModel.currentIndex.value + 1, - // duration: const Duration(milliseconds: 300), - // curve: Curves.ease, - // ); - - // // currentIndex 증가 - // wordViewModel.currentIndex.value = - // wordViewModel.currentIndex.value + 1; - // } - // }, - // // 마지막 단어일 경우 홈으로 이동 - // child: Text(wordViewModel.currentIndex.value < - // wordViewModel.wordList.length - 1 - // ? '다음 단어' - // : '홈으로 이동'), - // ), - // ], - // ), - // ); - // }, - // child: const Text( - // "학습 완료", - // style: TextStyle( - // color: ColorSystem.white, - // fontSize: 16.0, - // ), - // ), - // ), - // const SizedBox(height: 80), ], ), ),