From 753c08e777fe74cb0111f5d9598b42fdc8b5e4ba Mon Sep 17 00:00:00 2001 From: bunju20 <85238126+bunju20@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:40:15 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix:=EC=A7=84=ED=96=89=EC=A4=91=EC=9D=B8?= =?UTF-8?q?=EB=8D=B0=20=EC=9E=A0=EA=B9=90=20=EC=A0=80=EC=9E=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../learning_session_screen_viewmodel.dart | 42 ++++++++++ lib/views/paragraph/analyze_screen.dart | 84 +++++++++++-------- lib/views/paragraph/create_script_screen.dart | 24 ++---- .../paragraph/learning_session_screen.dart | 75 +++++++++++++++++ .../study/widget/study_main_body_widget.dart | 4 +- lib/views/word/word_screen.dart | 2 +- 6 files changed, 179 insertions(+), 52 deletions(-) create mode 100644 lib/viewModels/Paragraph/learning_session_screen_viewmodel.dart create mode 100644 lib/views/paragraph/learning_session_screen.dart diff --git a/lib/viewModels/Paragraph/learning_session_screen_viewmodel.dart b/lib/viewModels/Paragraph/learning_session_screen_viewmodel.dart new file mode 100644 index 0000000..b1671d5 --- /dev/null +++ b/lib/viewModels/Paragraph/learning_session_screen_viewmodel.dart @@ -0,0 +1,42 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:get/get_rx/src/rx_types/rx_types.dart'; +import 'package:get/get_state_manager/src/simple/get_controllers.dart'; +final FirebaseFirestore _firestore = FirebaseFirestore.instance; + + +class LearningSessionScreenViewModel extends GetxController { + final RxBool isLoading = false.obs; // 로딩 상태 관리 + final RxList paragraphs = [].obs; // Paragraph 객체 리스트 + + // Firestore에서 paragraphs 컬렉션의 데이터를 가져오는 함수 + Future fetchParagraphs() async { + try { + isLoading(true); // 로딩 시작 + final QuerySnapshot paragraphSnapshot = await _firestore + .collection('paragraphs') + .limit(5) // 예제로 5개의 문서만 가져오기 + .get(); + + final List fetchedParagraphs = paragraphSnapshot.docs + .map((doc) => Paragraph( + title: doc['title'], // 문서의 title 필드 + text: doc['text'], // 문서의 text 필드 + )) + .toList(); + + paragraphs.value = fetchedParagraphs; // 상태 업데이트 + } catch (e) { + print("Error fetching paragraphs: $e"); // 오류 처리 + } finally { + isLoading(false); // 로딩 종료 + } + } +} + +// Firestore 문서로부터 생성되는 Paragraph 모델 +class Paragraph { + final String title; + final String text; + + Paragraph({required this.title, required this.text}); +} diff --git a/lib/views/paragraph/analyze_screen.dart b/lib/views/paragraph/analyze_screen.dart index d53608a..f806d8e 100644 --- a/lib/views/paragraph/analyze_screen.dart +++ b/lib/views/paragraph/analyze_screen.dart @@ -8,14 +8,21 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:earlips/viewModels/script/analyze_viewmodel.dart'; import '../../utilities/app_routes.dart'; - - class AnalyzeScreen extends StatefulWidget { + AnalyzeScreen({Key? key}) : super(key: key); @override _AnalyzeScreenState createState() => _AnalyzeScreenState(); } class _AnalyzeScreenState extends State { + late AnalyzeViewModel viewModel; + + @override + void initState() { + super.initState(); + viewModel = Get.put(AnalyzeViewModel()); // 여기서 viewModel을 등록 + print(viewModel.userSenten); + } @override Widget build(BuildContext context) { @@ -36,13 +43,13 @@ class _AnalyzeScreenState extends State { width: Get.width - 40, height: Get.height * 0.2, margin: EdgeInsets.all(20.0), - padding: EdgeInsets.all(10.0), // 내부 여백을 추가합니다. + padding: EdgeInsets.all(10.0), decoration: BoxDecoration( - color: Colors.white, // 배경색을 지정합니다. - borderRadius: BorderRadius.circular(15.0), // 테두리 둥글기를 지정합니다. - border: Border.all(color: Colors.white), // 테두리 색상을 지정합니다. 필요에 따라 변경 가능합니다. + color: Colors.white, + borderRadius: BorderRadius.circular(15.0), + border: Border.all(color: Colors.white), ), - child: _TopText(), + child: _TopText(), // 이 부분은 상태를 표시하지 않으므로 그대로 유지합니다. ), Stack( children: [ @@ -51,15 +58,14 @@ class _AnalyzeScreenState extends State { width: Get.width - 40, height: Get.height * 0.5, margin: EdgeInsets.all(20.0), - padding: EdgeInsets.all(10.0), // 내부 여백을 추가합니다. + padding: EdgeInsets.all(10.0), decoration: BoxDecoration( - color: Colors.white, // 배경색을 지정합니다. - borderRadius: BorderRadius.circular(15.0), // 테두리 둥글기를 지정합니다. - border: Border.all(color: Colors.white), // 테두리 색상을 지정합니다. 필요에 따라 변경 가능합니다. + color: Colors.white, + borderRadius: BorderRadius.circular(15.0), + border: Border.all(color: Colors.white), ), - child: TextStylingWidget(), + child: TextStylingWidget(viewModel: viewModel), // viewModel을 전달합니다. ), - ], ), ], @@ -68,13 +74,12 @@ class _AnalyzeScreenState extends State { floatingActionButton: Container( width: Get.width, alignment: Alignment.bottomCenter, - child: FloatingActionButton( onPressed: () { Get.toNamed(Routes.HOME); }, - child: Icon(Icons.home), // 홈 아이콘 사용 - tooltip: '홈으로', // 롱 프레스 시 표시되는 텍스트 + child: Icon(Icons.home), + tooltip: '홈으로', ), ), ), @@ -83,7 +88,9 @@ class _AnalyzeScreenState extends State { } class TextStylingWidget extends StatelessWidget { - final AnalyzeViewModel model = Get.put(AnalyzeViewModel()); + final AnalyzeViewModel viewModel; // viewModel을 받기 위한 생성자 파라미터를 추가합니다. + + TextStylingWidget({required this.viewModel}); // 생성자를 통해 viewModel을 초기화합니다. @override Widget build(BuildContext context) { @@ -98,32 +105,46 @@ class TextStylingWidget extends StatelessWidget { ); } + List _buildTextSpans() { List spans = []; - int globalWordIndex = 0; // 전체 단어에 대한 인덱스를 추적합니다. + int globalWordIndex = 0; - for (int i = 0; i < model.userSenten.length; i++) { - final List words = model.userSenten[i].split(' '); + for (int i = 0; i < viewModel.userSenten.length; i++) { + final List words = viewModel.userSenten[i].split(' '); List wordSpans = []; for (String word in words) { - final bool isWrongWord = model.wrongWordIndexes.contains(globalWordIndex); + final bool isWrongWord = viewModel.wrongWordIndexes.contains(globalWordIndex); wordSpans.add(TextSpan( text: "$word ", style: TextStyle( color: isWrongWord ? Colors.red : Colors.black, ), )); - globalWordIndex++; // 각 단어를 처리할 때마다 전체 단어 인덱스를 증가시킵니다. + globalWordIndex++; + } + + // `wrongFastIndexes`의 값에 따라 밑줄 색상을 결정합니다. + Color underlineColor = Colors.white; // 기본값은 투명색입니다. + if (viewModel.wrongFastIndexes[i] < 1) { + underlineColor = Colors.purple; // 1미만인 경우 보라색 밑줄 + } else if (viewModel.wrongFastIndexes[i] > 1) { + underlineColor = Colors.red; // 1초과인 경우 빨간색 밑줄 } spans.add(TextSpan( children: wordSpans, style: TextStyle( - decoration: model.wrongFastIndexes.contains(i) ? TextDecoration.underline : TextDecoration.none, + decoration: viewModel.wrongFastIndexes[i] != 0 ? TextDecoration.underline : TextDecoration.none, + decorationColor: underlineColor, // 밑줄 색상을 지정합니다. + decorationStyle: TextDecorationStyle.solid, + decorationThickness: 3.0, + //밑줄을 밑으로 내리기 위한 값 + ), )); - spans.add(TextSpan(text: "\n")); // 문장 사이에 줄바꿈 추가 + spans.add(TextSpan(text: "\n")); } return spans; @@ -166,16 +187,9 @@ class _TopText extends StatelessWidget { text: TextSpan( style: TextStyle(fontSize: 16, color: Colors.black), children: [ - TextSpan(text: '문장의 빠르기가 빠르거나 느리면 밑줄이 표시됩니다. ex)'), + TextSpan(text: '문장이 빠르면 빨강, 느리면 보라색 밑줄로 표시됩니다.'), // 예시에 적용할 스타일 - TextSpan( - text: '강아지는 ', - style: TextStyle(decoration: TextDecoration.underline), - ), - TextSpan( - text: '뛴다', - style: TextStyle(decoration: TextDecoration.underline), - ), + //들여쓰기 ], ), ), @@ -183,4 +197,6 @@ class _TopText extends StatelessWidget { ), ); } -} \ No newline at end of file + +} + diff --git a/lib/views/paragraph/create_script_screen.dart b/lib/views/paragraph/create_script_screen.dart index ae0c745..0c41b1a 100644 --- a/lib/views/paragraph/create_script_screen.dart +++ b/lib/views/paragraph/create_script_screen.dart @@ -5,7 +5,10 @@ import 'package:earlips/viewModels/script/create_script_viewmodel.dart'; import 'package:get/get.dart'; class CreateScriptPage extends StatelessWidget { - const CreateScriptPage({super.key}); + final String title; + final String text; + + const CreateScriptPage({super.key, required this.title, required this.text}); @override Widget build(BuildContext context) { @@ -14,7 +17,7 @@ class CreateScriptPage extends StatelessWidget { child: Consumer( builder: (context, model, child) => Scaffold( appBar: AppBar( - title: const Text('문단교정'), + title: Text(title), centerTitle: true, actions: [ TextButton( @@ -39,20 +42,9 @@ class CreateScriptPage extends StatelessWidget { flex: 1, child: Padding( padding: const EdgeInsets.all(10.0), - child: TextField( - controller: model.writedTextController, - expands: true, - maxLines: null, - decoration: InputDecoration( - hintText: '대본을 입력하세요...', - fillColor: Colors.white, - filled: true, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(15.0), - borderSide: BorderSide.none, - ), - ), - textAlignVertical: TextAlignVertical.top, + child: Text( + text, + style: const TextStyle(fontSize: 16), ), ), ), diff --git a/lib/views/paragraph/learning_session_screen.dart b/lib/views/paragraph/learning_session_screen.dart new file mode 100644 index 0000000..6930020 --- /dev/null +++ b/lib/views/paragraph/learning_session_screen.dart @@ -0,0 +1,75 @@ +import 'package:earlips/viewModels/Paragraph/learning_session_screen_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:earlips/views/Paragraph/create_script_screen.dart'; + +class LearningSessionScreen extends StatefulWidget { + LearningSessionScreen({Key? key}) : super(key: key); + + @override + State createState() => _LearningSessionScreenState(); +} + +class _LearningSessionScreenState extends State { + final viewModel = Get.put(LearningSessionScreenViewModel()); // ViewModel 인스턴스 생성 + + @override + void initState() { + super.initState(); + viewModel.fetchParagraphs(); // 화면이 로드될 때 Firestore에서 데이터 가져오기 + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('대본으로 학습하기'), + centerTitle: true, + ), + body: Obx(() { // GetX의 Obx()를 사용하여 반응형 UI 구성 + if (viewModel.isLoading.value) { + return Center(child: CircularProgressIndicator()); + } else { + return ListView.separated( + padding: const EdgeInsets.fromLTRB(25, 20, 25, 20), + itemCount: viewModel.paragraphs.length, + itemBuilder: (context, index) { + var paragraph = viewModel.paragraphs[index]; + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(15.0), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.15), + spreadRadius: 0.1, + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: ListTile( + contentPadding: const EdgeInsets.all(20), + title: Text( + paragraph.title, + style: const TextStyle( + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + onTap: () { + // 다음 페이지로 이동하며 파라미터 전달 + Get.to(() => CreateScriptPage(title: paragraph.title, text: paragraph.text)); + }, + ), + ); + }, + separatorBuilder: (context, index) => const SizedBox(height: 20), + ); + } + }), + + ); + } +} + diff --git a/lib/views/study/widget/study_main_body_widget.dart b/lib/views/study/widget/study_main_body_widget.dart index 9aaa1b9..8f301dc 100644 --- a/lib/views/study/widget/study_main_body_widget.dart +++ b/lib/views/study/widget/study_main_body_widget.dart @@ -4,6 +4,7 @@ import 'package:earlips/views/word/word_screen.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 StudyNainBodyWidget extends StatelessWidget { const StudyNainBodyWidget({ @@ -77,7 +78,8 @@ class StudyNainBodyWidget extends StatelessWidget { subtitle: "대본 입력 및 발음 테스트", imagePath: "assets/images/study/4.png", onTap: () { - Get.to(() => const CreateScriptPage()); + Get.to(() => LearningSessionScreen( + )); }, imgSize: 85, ), diff --git a/lib/views/word/word_screen.dart b/lib/views/word/word_screen.dart index 65c6674..96af7e3 100644 --- a/lib/views/word/word_screen.dart +++ b/lib/views/word/word_screen.dart @@ -86,7 +86,7 @@ class WordScreen extends StatelessWidget { ); } else { return Container( - child: const CreateScriptPage(), + // child: const CreateScriptPage(), ); } }, From 8e1c2e5af905527cdbe3f15d3486385165113720 Mon Sep 17 00:00:00 2001 From: bunju20 <85238126+bunju20@users.noreply.github.com> Date: Tue, 20 Feb 2024 17:51:21 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EB=AC=B8=EB=8B=A8=20=ED=95=99?= =?UTF-8?q?=EC=8A=B5=20=EB=8C=80=EB=B3=B8=20DB=20=EC=97=B0=EB=8F=99=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../learning_session_screen_viewmodel.dart | 22 ++++++++++++++----- lib/views/paragraph/create_script_screen.dart | 2 +- .../paragraph/learning_session_screen.dart | 14 +++++++----- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/viewModels/Paragraph/learning_session_screen_viewmodel.dart b/lib/viewModels/Paragraph/learning_session_screen_viewmodel.dart index b1671d5..377e418 100644 --- a/lib/viewModels/Paragraph/learning_session_screen_viewmodel.dart +++ b/lib/viewModels/Paragraph/learning_session_screen_viewmodel.dart @@ -1,29 +1,39 @@ import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:get/get_rx/src/rx_types/rx_types.dart'; import 'package:get/get_state_manager/src/simple/get_controllers.dart'; -final FirebaseFirestore _firestore = FirebaseFirestore.instance; + class LearningSessionScreenViewModel extends GetxController { + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final FirebaseAuth _auth = FirebaseAuth.instance; + final RxBool isLoading = false.obs; // 로딩 상태 관리 final RxList paragraphs = [].obs; // Paragraph 객체 리스트 // Firestore에서 paragraphs 컬렉션의 데이터를 가져오는 함수 Future fetchParagraphs() async { + final uid = _auth.currentUser?.uid; // 사용자 UID 가져오기 try { isLoading(true); // 로딩 시작 final QuerySnapshot paragraphSnapshot = await _firestore - .collection('paragraphs') + .collection('paragraph') // 사용자의 paragraph 컬렉션에 접근 .limit(5) // 예제로 5개의 문서만 가져오기 .get(); final List fetchedParagraphs = paragraphSnapshot.docs - .map((doc) => Paragraph( - title: doc['title'], // 문서의 title 필드 - text: doc['text'], // 문서의 text 필드 - )) + .map((doc) { + // doc.data() 호출 결과를 Map으로 타입 캐스팅 + final data = doc.data() as Map?; + // Null-safety를 고려하여, 필드에 접근하기 전에 null 체크 + final title = data?['title'] as String? ?? ''; // title이 없으면 빈 문자열 할당 + final text = data?['text'] as String? ?? ''; // text가 없으면 빈 문자열 할당 + return Paragraph(title: title, text: text); + }) .toList(); + paragraphs.value = fetchedParagraphs; // 상태 업데이트 } catch (e) { print("Error fetching paragraphs: $e"); // 오류 처리 diff --git a/lib/views/paragraph/create_script_screen.dart b/lib/views/paragraph/create_script_screen.dart index 0c41b1a..dd0926e 100644 --- a/lib/views/paragraph/create_script_screen.dart +++ b/lib/views/paragraph/create_script_screen.dart @@ -7,8 +7,8 @@ import 'package:get/get.dart'; class CreateScriptPage extends StatelessWidget { final String title; final String text; + const CreateScriptPage({Key? key, required this.title, required this.text}) : super(key: key); - const CreateScriptPage({super.key, required this.title, required this.text}); @override Widget build(BuildContext context) { diff --git a/lib/views/paragraph/learning_session_screen.dart b/lib/views/paragraph/learning_session_screen.dart index 6930020..72e5d96 100644 --- a/lib/views/paragraph/learning_session_screen.dart +++ b/lib/views/paragraph/learning_session_screen.dart @@ -1,7 +1,7 @@ import 'package:earlips/viewModels/Paragraph/learning_session_screen_viewmodel.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/create_script_screen.dart'; class LearningSessionScreen extends StatefulWidget { LearningSessionScreen({Key? key}) : super(key: key); @@ -11,7 +11,8 @@ class LearningSessionScreen extends StatefulWidget { } class _LearningSessionScreenState extends State { - final viewModel = Get.put(LearningSessionScreenViewModel()); // ViewModel 인스턴스 생성 + final viewModel = Get.put( + LearningSessionScreenViewModel()); // ViewModel 인스턴스 생성 @override void initState() { @@ -26,7 +27,7 @@ class _LearningSessionScreenState extends State { title: Text('대본으로 학습하기'), centerTitle: true, ), - body: Obx(() { // GetX의 Obx()를 사용하여 반응형 UI 구성 + body: Obx(() { if (viewModel.isLoading.value) { return Center(child: CircularProgressIndicator()); } else { @@ -58,8 +59,10 @@ class _LearningSessionScreenState extends State { ), ), onTap: () { - // 다음 페이지로 이동하며 파라미터 전달 - Get.to(() => CreateScriptPage(title: paragraph.title, text: paragraph.text)); + // title과 text만 다음 페이지로 전달 + Get.to(() => + CreateScriptPage( + title: paragraph.title, text: paragraph.text)); }, ), ); @@ -68,7 +71,6 @@ class _LearningSessionScreenState extends State { ); } }), - ); } } From 3475ba49eb69cff46772ad510fbb0d61a07134dd Mon Sep 17 00:00:00 2001 From: bunju20 <85238126+bunju20@users.noreply.github.com> Date: Tue, 20 Feb 2024 19:36:33 +0900 Subject: [PATCH 3/3] =?UTF-8?q?feat:=20=EB=AC=B8=EB=8B=A8=EA=B5=90?= =?UTF-8?q?=EC=A0=95=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20DB=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/views/paragraph/create_script_screen.dart | 29 +++++++++++++++---- .../paragraph/learning_session_screen.dart | 9 +++--- lib/views/word/word_screen.dart | 3 +- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/lib/views/paragraph/create_script_screen.dart b/lib/views/paragraph/create_script_screen.dart index dd0926e..00f90c9 100644 --- a/lib/views/paragraph/create_script_screen.dart +++ b/lib/views/paragraph/create_script_screen.dart @@ -40,11 +40,30 @@ class CreateScriptPage extends StatelessWidget { children: [ Expanded( flex: 1, - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Text( - text, - style: const TextStyle(fontSize: 16), + child: SingleChildScrollView( + child: Container( + child: Padding( + padding: const EdgeInsets.all(0.0), + child: Container( + margin: const EdgeInsets.all(10.0), + padding: const EdgeInsets.all(20.0), + //가장자리 둥글게 + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(15.0), + + color: Colors.white, + + ), + + child: Text( + text, + style: const TextStyle(fontSize: 16, + fontFamily: 'Pretendard', + fontWeight: FontWeight.bold + ), + ), + ), + ), ), ), ), diff --git a/lib/views/paragraph/learning_session_screen.dart b/lib/views/paragraph/learning_session_screen.dart index 72e5d96..d1e74fd 100644 --- a/lib/views/paragraph/learning_session_screen.dart +++ b/lib/views/paragraph/learning_session_screen.dart @@ -1,4 +1,5 @@ import 'package:earlips/viewModels/Paragraph/learning_session_screen_viewmodel.dart'; +import 'package:earlips/views/word/widget/blue_back_appbar.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:earlips/views/paragraph/create_script_screen.dart'; @@ -23,9 +24,9 @@ class _LearningSessionScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text('대본으로 학습하기'), - centerTitle: true, + appBar: PreferredSize( + preferredSize: const Size.fromHeight(kToolbarHeight), + child: BlueBackAppbar(title: "문단교정"), ), body: Obx(() { if (viewModel.isLoading.value) { @@ -54,7 +55,7 @@ class _LearningSessionScreenState extends State { title: Text( paragraph.title, style: const TextStyle( - fontSize: 24.0, + fontSize: 20.0, fontWeight: FontWeight.bold, ), ), diff --git a/lib/views/word/word_screen.dart b/lib/views/word/word_screen.dart index a7c18fa..ba565fd 100644 --- a/lib/views/word/word_screen.dart +++ b/lib/views/word/word_screen.dart @@ -7,6 +7,7 @@ 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 { final String title; @@ -85,7 +86,7 @@ class WordScreen extends StatelessWidget { wordDataList: controller.wordList, ); } else { - return const CreateScriptPage(); + return LearningSessionScreen(); } }, ),