From d4fd641920308963e150bf61a25e4e14a8955696 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:16:16 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8Feat=20:=20=ED=95=99=EC=8A=B5?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=AF=B8=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=9C=A0=EC=A0=80=20=EB=A7=89=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/views/study/study_main.dart | 91 +++++++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 15 deletions(-) diff --git a/lib/views/study/study_main.dart b/lib/views/study/study_main.dart index 4675810..1ebdec8 100644 --- a/lib/views/study/study_main.dart +++ b/lib/views/study/study_main.dart @@ -1,12 +1,19 @@ +import 'dart:ui'; + import 'package:earlips/utilities/style/color_styles.dart'; +import 'package:earlips/views/auth/login_screen.dart'; import 'package:earlips/views/study/widget/study_main_body_widget.dart'; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:earlips/views/base/base_screen.dart'; import 'package:earlips/viewModels/study/study_viewmodel.dart'; import 'package:earlips/views/study/widget/contribution.dart'; +import 'package:get/get.dart'; class StudyMain extends BaseScreen { - const StudyMain({super.key}); + final User? user; + + const StudyMain({super.key, this.user}); @override Widget buildBody(BuildContext context) { return Scaffold( @@ -14,20 +21,74 @@ class StudyMain extends BaseScreen { color: ColorSystem.white, child: SafeArea( top: true, - child: SingleChildScrollView( - child: Column( - children: [ - const SizedBox( - height: 40, - ), - Container( - color: const Color(0x000000ff), - child: const Contribute(), - ), - const StudyNainBodyWidget(), - ], - ), - ), + child: StreamBuilder( + stream: FirebaseAuth.instance.authStateChanges(), + builder: (context, snapshot) { + final bool isLoggedIn = snapshot.hasData; + + return SingleChildScrollView( + child: Column( + children: [ + Container( + color: const Color(0x000000ff), + child: const Contribute(), + ), + Stack( + children: [ + const StudyNainBodyWidget(), + if (!isLoggedIn) + Positioned.fill( + child: ClipRRect( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3, sigmaY: 3), + child: Container( + alignment: Alignment.center, + color: Colors.grey.withOpacity(0.1), + child: Container( + padding: const EdgeInsets.all(20), + height: 150, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.grey.withOpacity(0.5), + ), + ), + child: Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + const Text( + "로그인이 필요합니다.", + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: ColorSystem.black, + ), + ), + IconButton( + onPressed: () { + Get.to(() => LoginScreen()); + }, + icon: const Icon( + Icons.login, + size: 50, + color: ColorSystem.main2, + ), + ), + ], + ), + ), + ), + ), + ), + ), + ], + ), + ], + ), + ); + }), ), )); } From 028cebf3df47ced975feb42e6e5c80fd7048efe4 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:20:19 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=E2=9C=A8Feat=20:=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=EC=97=AC=EB=B6=80=EC=97=90=20=EB=94=B0=EB=A5=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=84=B0=EB=94=94=20=ED=8E=98=EC=9D=B4=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/views/study/study_main.dart | 10 +- lib/views/study/widget/contribution.dart | 131 ++++++++++++----------- 2 files changed, 74 insertions(+), 67 deletions(-) diff --git a/lib/views/study/study_main.dart b/lib/views/study/study_main.dart index 1ebdec8..4222b02 100644 --- a/lib/views/study/study_main.dart +++ b/lib/views/study/study_main.dart @@ -11,9 +11,9 @@ import 'package:earlips/views/study/widget/contribution.dart'; import 'package:get/get.dart'; class StudyMain extends BaseScreen { - final User? user; - - const StudyMain({super.key, this.user}); + const StudyMain({ + super.key, + }); @override Widget buildBody(BuildContext context) { return Scaffold( @@ -31,7 +31,9 @@ class StudyMain extends BaseScreen { children: [ Container( color: const Color(0x000000ff), - child: const Contribute(), + child: Contribute( + isLogin: isLoggedIn, + ), ), Stack( children: [ diff --git a/lib/views/study/widget/contribution.dart b/lib/views/study/widget/contribution.dart index e691fc1..675e8e7 100644 --- a/lib/views/study/widget/contribution.dart +++ b/lib/views/study/widget/contribution.dart @@ -3,12 +3,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_calendar_week/flutter_calendar_week.dart'; import 'package:intl/intl.dart'; import 'package:get/get.dart'; -import 'package:earlips/views/base/base_widget.dart'; -import 'package:earlips/viewModels/study/study_viewmodel.dart'; import 'package:earlips/views/study/date_study_screen.dart'; class Contribute extends StatefulWidget { - const Contribute({super.key}); + final bool isLogin; + const Contribute({super.key, required this.isLogin}); @override _ContributeState createState() => _ContributeState(); @@ -25,70 +24,76 @@ class _ContributeState extends State { @override Widget build(BuildContext context) { - return Container( - width: Get.width * 0.9, - color: Colors.white, - height: Get.height * 0.175, - child: CalendarWeek( - controller: _controller, // Use initialized controller - showMonth: true, - minDate: DateTime.now().add(const Duration(days: -365)), - maxDate: DateTime.now().add(const Duration(days: 365)), - pressedDateBackgroundColor: Colors.transparent, - todayDateStyle: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - color: ColorSystem.white, + return Column( + children: [ + const SizedBox( + height: 30, ), - todayBackgroundColor: ColorSystem.main, - pressedDateStyle: const TextStyle( - fontSize: 16, - color: ColorSystem.black, - fontWeight: FontWeight.w400, - ), - dateStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w400, - color: ColorSystem.black, - ), - onDatePressed: (DateTime datetime) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => DateStudyScreen(date: datetime), + Container( + width: Get.width * 0.9, + color: Colors.white, + height: Get.height * 0.175, + child: CalendarWeek( + controller: _controller, // Use initialized controller + showMonth: true, + minDate: DateTime.now().add(const Duration(days: -365)), + maxDate: DateTime.now().add(const Duration(days: 365)), + pressedDateBackgroundColor: Colors.transparent, + todayDateStyle: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: ColorSystem.white, + ), + todayBackgroundColor: ColorSystem.main, + pressedDateStyle: const TextStyle( + fontSize: 16, + color: ColorSystem.black, + fontWeight: FontWeight.w400, + ), + dateStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w400, + color: ColorSystem.black, ), - ); - }, - onDateLongPressed: (DateTime datetime) {}, - onWeekChanged: () {}, - monthViewBuilder: (DateTime time) => Align( - alignment: FractionalOffset.center, - child: Column( - children: [ - Container( - margin: const EdgeInsets.only(bottom: 15.0), - child: Text( - DateFormat.yMMMM().format(time), - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontSize: 18.0, - color: ColorSystem.black, - fontWeight: FontWeight.w600), - )), - ], + onDatePressed: (DateTime datetime) { + // no user noting action + if (!widget.isLogin) { + return; + } + Get.to(() => DateStudyScreen(date: datetime)); + }, + onDateLongPressed: (DateTime datetime) {}, + onWeekChanged: () {}, + monthViewBuilder: (DateTime time) => Align( + alignment: FractionalOffset.center, + child: Column( + children: [ + Container( + margin: const EdgeInsets.only(bottom: 15.0), + child: Text( + DateFormat.yMMMM().format(time), + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 18.0, + color: ColorSystem.black, + fontWeight: FontWeight.w600), + )), + ], + ), + ), + + // DecorationItem( + // date: DateTime.now().add(const Duration(days: 3)), + // decoration: const Text( + // 'Holiday', + // style: TextStyle( + // color: Colors.brown, + // fontWeight: FontWeight.w600, + // ), + // )), ), ), - - // DecorationItem( - // date: DateTime.now().add(const Duration(days: 3)), - // decoration: const Text( - // 'Holiday', - // style: TextStyle( - // color: Colors.brown, - // fontWeight: FontWeight.w600, - // ), - // )), - ), + ], ); } } From 12416c93f44f54517c679eaf36f5fef373ffe1e7 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:21:12 +0900 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=94=A8Chore=20:=20=EB=A0=88=EC=9D=B4?= =?UTF-8?q?=EC=95=84=EC=9B=83=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 4 +- lib/views/home/home_screen.dart | 25 ++- lib/views/profile/profile_header_widget.dart | 2 +- lib/views/root/root_screen.dart | 5 +- .../study/widget/study_main_body_widget.dart | 147 ++++++++++-------- 5 files changed, 97 insertions(+), 86 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 969ba14..8c7380d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -938,7 +938,7 @@ SPEC CHECKSUMS: FirebaseFirestore: 21be9ea244830f6cac15464550c2975c43f9dffc FirebaseFirestoreInternal: 7ac1e0c5b4e75aeb898dfe4b1d6d77abbac9eca3 FirebaseSharedSwift: 19b3f709993d6fa1d84941d41c01e3c4c11eab93 - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_inappwebview: 3d32228f1304635e7c028b0d4252937730bbc6cf flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4 flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef @@ -966,4 +966,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 0b2c97823421f8b156b8e4753a469ac167670df8 -COCOAPODS: 1.14.3 +COCOAPODS: 1.15.2 diff --git a/lib/views/home/home_screen.dart b/lib/views/home/home_screen.dart index c12be16..525e0f2 100644 --- a/lib/views/home/home_screen.dart +++ b/lib/views/home/home_screen.dart @@ -13,9 +13,6 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:earlips/views/script/learning_session_screen.dart'; import '../realtime/real_create_script_screen.dart'; -import 'package:earlips/viewModels/user/user_viewmodel.dart'; - - class HomeScreen extends BaseScreen { final User? user = FirebaseAuth.instance.currentUser; // FirebaseFirestore 인스턴스 생성 @@ -40,7 +37,10 @@ class HomeScreen extends BaseScreen { child: Column( children: [ HomeHeaderWidget(isLoggedIn: isLoggedIn, vm: viewModel), - _Top(isLoggedIn: isLoggedIn, vm: viewModel,), + _Top( + isLoggedIn: isLoggedIn, + vm: viewModel, + ), const _Middle(), // 로그인 상태에 따라 _Bottom 클래스의 컨테이너 색상을 변경 _Bottom(isLoggedIn: isLoggedIn), @@ -59,8 +59,6 @@ class _Top extends StatelessWidget { final UserViewModel vm; const _Top({required this.isLoggedIn, required this.vm}); - - @override Widget build(BuildContext context) { return Center( @@ -75,8 +73,8 @@ class _Top extends StatelessWidget { height: Get.height * 0.19, child: Stack( children: [ - Row(children: [ - _Circle(vm : vm), + Row(children: [ + _Circle(vm: vm), _SpeakingAbility(vm: vm), ]), if (!isLoggedIn) // 로그인 안 됐을 때만 블러 효과와 자물쇠 아이콘 표시 @@ -228,10 +226,10 @@ class _Bottom extends StatelessWidget { ), ), ), - Container( - alignment: Alignment.centerLeft, + Container( + alignment: Alignment.centerLeft, margin: const EdgeInsets.only(left: 15.0), - child: LineChartSample2()), + child: LineChartSample2()), ], ), ), @@ -261,7 +259,7 @@ class _Bottom extends StatelessWidget { class _Circle extends StatelessWidget { final UserViewModel vm; - const _Circle({super.key, required this.vm}); + const _Circle({super.key, required this.vm}); @override Widget build(BuildContext context) { @@ -296,7 +294,7 @@ class _Circle extends StatelessWidget { class _SpeakingAbility extends StatelessWidget { final UserViewModel vm; - const _SpeakingAbility( {super.key, required this.vm}); + const _SpeakingAbility({super.key, required this.vm}); @override Widget build(BuildContext context) { @@ -351,4 +349,3 @@ class _SpeakingAbility extends StatelessWidget { ); } } - diff --git a/lib/views/profile/profile_header_widget.dart b/lib/views/profile/profile_header_widget.dart index ee54518..8e274dd 100644 --- a/lib/views/profile/profile_header_widget.dart +++ b/lib/views/profile/profile_header_widget.dart @@ -21,7 +21,7 @@ class ProfileHeader extends StatelessWidget { child: Container( color: ColorSystem.background, child: Padding( - padding: const EdgeInsets.fromLTRB(20, 48, 20, 24), + padding: const EdgeInsets.fromLTRB(20, 30, 20, 24), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/views/root/root_screen.dart b/lib/views/root/root_screen.dart index 042c219..64d5375 100644 --- a/lib/views/root/root_screen.dart +++ b/lib/views/root/root_screen.dart @@ -17,7 +17,6 @@ class RootScreen extends BaseScreen { @override Color? get screenBackgroundColor => const Color(0xFFAAA4F8); - @override Widget buildBody(BuildContext context) { return Obx( @@ -25,8 +24,8 @@ class RootScreen extends BaseScreen { index: viewModel.selectedIndex, children: [ HomeScreen(), - StudyMain(), - ProfileScreen(), + const StudyMain(), + const ProfileScreen(), ], ), ); diff --git a/lib/views/study/widget/study_main_body_widget.dart b/lib/views/study/widget/study_main_body_widget.dart index bdb3d10..9aaa1b9 100644 --- a/lib/views/study/widget/study_main_body_widget.dart +++ b/lib/views/study/widget/study_main_body_widget.dart @@ -6,77 +6,92 @@ import 'package:get/get.dart'; import 'package:earlips/views/paragraph/create_script_screen.dart'; class StudyNainBodyWidget extends StatelessWidget { - const StudyNainBodyWidget({super.key}); + const StudyNainBodyWidget({ + super.key, + }); @override Widget build(BuildContext context) { return Container( color: ColorSystem.background, - child: Column(children: [ - const SizedBox( - height: 32, - ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - StudyCardWidget( - title: "음소 교정", - subtitle: "옴소 교정 및 발음 테스트", - imagePath: "assets/images/study/1.png", - onTap: () { - Get.to(() => const WordScreen( - title: "음소 교정", - type: 0, - )); - }, - imgSize: 85, - ), - StudyCardWidget( - title: "단어 교정", - subtitle: "단어 교정 및 발음 테스트", - imagePath: "assets/images/study/2.png", - onTap: () { - Get.to(() => const WordScreen( - title: "단어 교정", - type: 1, - )); - }, - imgSize: 150, - ), - ], - ), - const SizedBox( - height: 20, - ), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - StudyCardWidget( - title: "문장 교정", - subtitle: "문장 교정 및 발음 테스트", - imagePath: "assets/images/study/3.png", - onTap: () { - Get.to(() => const WordScreen( - title: "문장 교정", - type: 2, - )); - }, - imgSize: 85, - ), - StudyCardWidget( - title: "문단 교정", - subtitle: "대본 입력 및 발음 테스트", - imagePath: "assets/images/study/4.png", - onTap: () { - Get.to(() => CreateScriptPage()); - }, - imgSize: 85, - ), - ], - ), - ]), + child: Column( + children: [ + Stack( + children: [ + Column( + children: [ + const SizedBox( + height: 60, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + StudyCardWidget( + title: "음소 교정", + subtitle: "옴소 교정 및 발음 테스트", + imagePath: "assets/images/study/1.png", + onTap: () { + Get.to(() => const WordScreen( + title: "음소 교정", + type: 0, + )); + }, + imgSize: 85, + ), + StudyCardWidget( + title: "단어 교정", + subtitle: "단어 교정 및 발음 테스트", + imagePath: "assets/images/study/2.png", + onTap: () { + Get.to(() => const WordScreen( + title: "단어 교정", + type: 1, + )); + }, + imgSize: 150, + ), + ], + ), + const SizedBox( + height: 20, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + StudyCardWidget( + title: "문장 교정", + subtitle: "문장 교정 및 발음 테스트", + imagePath: "assets/images/study/3.png", + onTap: () { + Get.to(() => const WordScreen( + title: "문장 교정", + type: 2, + )); + }, + imgSize: 85, + ), + StudyCardWidget( + title: "문단 교정", + subtitle: "대본 입력 및 발음 테스트", + imagePath: "assets/images/study/4.png", + onTap: () { + Get.to(() => const CreateScriptPage()); + }, + imgSize: 85, + ), + ], + ), + const SizedBox( + height: 90, + ), + ], + ), + ], + ), + ], + ), ); } } From eda6bcc08326432c96c81ffd3e92b2cf338da001 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Mon, 19 Feb 2024 20:39:05 +0900 Subject: [PATCH 4/5] =?UTF-8?q?=E2=9C=A8Feat:=20dailyWords=20=EC=97=B0?= =?UTF-8?q?=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/viewModels/word/word_viewmodel.dart | 51 ++++++++++++++++++- .../study/widget/study_row_card_widget.dart | 28 ++++++++++ 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 lib/views/study/widget/study_row_card_widget.dart diff --git a/lib/viewModels/word/word_viewmodel.dart b/lib/viewModels/word/word_viewmodel.dart index 65d8553..cc26767 100644 --- a/lib/viewModels/word/word_viewmodel.dart +++ b/lib/viewModels/word/word_viewmodel.dart @@ -86,6 +86,55 @@ class WordViewModel extends GetxController { 'isDone': true, 'doneDate': currentDate, }); + // daily record 업데이트 + DocumentReference dailyRecordRef = _firestore + .collection('users') + .doc(uid) + .collection('daily_records') + .doc(DateFormat('yyyyMMdd').format(DateTime.now())); + + try { + await _firestore.runTransaction((transaction) async { + // 현재 daily record 가져오기 + DocumentSnapshot recordSnapshot = + await transaction.get(dailyRecordRef); + + // 만약 단어가 이미 있는지 확인 + if (recordSnapshot.exists) { + // 기존 단어 리스트 가져오기 + List> existingWordsList = + List>.from( + recordSnapshot.get('wordsList') ?? []); + + // 이미 있는 단어인지 확인 + if (existingWordsList + .any((element) => element['word'] == word.word)) { + // 만약 이미 있는 단어라면 return + return; + } else { + // 만약 없는 단어라면 추가 + existingWordsList.add({ + 'word': word.word, + 'type': word.type, + }); + + // 업데이트 + transaction + .update(dailyRecordRef, {'wordsList': existingWordsList}); + } + } else { + // If the document doesn't exist, create a new one with the initial word + transaction.set(dailyRecordRef, { + 'wordsList': [ + { + 'word': word.word, + 'type': word.type, + }, + ], + }); + } + }); + } catch (_) {} // 유저 record 업데이트 DocumentReference recordRef = _firestore @@ -93,7 +142,6 @@ class WordViewModel extends GetxController { .doc(uid) .collection('records') .doc(DateFormat('yyyyMMdd').format(DateTime.now())); - try { await _firestore.runTransaction((transaction) async { // Get the current record @@ -107,7 +155,6 @@ class WordViewModel extends GetxController { }); } } else { - print('different date!!!!!!!!!!!!!'); transaction.set(recordRef, { 'cnt': 1, 'date': currentDate, diff --git a/lib/views/study/widget/study_row_card_widget.dart b/lib/views/study/widget/study_row_card_widget.dart new file mode 100644 index 0000000..c21cfc9 --- /dev/null +++ b/lib/views/study/widget/study_row_card_widget.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class SmallCard extends StatelessWidget { + final String name; + + const SmallCard({super.key, required this.name}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4.0), + color: const Color(0xFF1FA9DC), + ), + alignment: Alignment.center, + width: 50, + height: 24, + child: Text( + name, + style: const TextStyle( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ); + } +} From d38bb98424d67e498f03be3c1c21ffe7602eda9d Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Mon, 19 Feb 2024 21:25:38 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=E2=9C=A8feat=20:=20=EB=8D=B0=EC=9D=BC?= =?UTF-8?q?=EB=A6=AC=EA=B8=B0=EB=A1=9D=20=EB=B3=B4=EA=B8=B0=20=EC=99=84?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main_app.dart | 1 + lib/utilities/app_routes.dart | 5 + .../study/date_study_screen_viewmodel.dart | 61 ++++-- lib/viewModels/word/word_viewmodel.dart | 4 +- lib/views/study/date_study_screen.dart | 178 +++++++++++------- .../study/widget/study_row_card_widget.dart | 20 +- lib/views/word/widget/word_list_widget.dart | 3 + lib/views/word/word_screen.dart | 42 ++++- 8 files changed, 219 insertions(+), 95 deletions(-) diff --git a/lib/main_app.dart b/lib/main_app.dart index dc4b989..e60d734 100644 --- a/lib/main_app.dart +++ b/lib/main_app.dart @@ -43,6 +43,7 @@ class MainApp extends StatelessWidget { Routes.routes[1], Routes.routes[2], Routes.routes[3], + Routes.routes[4], ], ); } diff --git a/lib/utilities/app_routes.dart b/lib/utilities/app_routes.dart index e448225..931474d 100644 --- a/lib/utilities/app_routes.dart +++ b/lib/utilities/app_routes.dart @@ -3,6 +3,7 @@ import 'package:earlips/views/home/home_screen.dart'; import 'package:earlips/views/profile/profile_account/profile_account_screen.dart'; import 'package:earlips/views/profile/profile_language_setting/profile_language_setting.dart'; import 'package:earlips/views/profile/profile_screen.dart'; +import 'package:earlips/views/study/study_main.dart'; import 'package:get/get.dart'; abstract class Routes { @@ -20,6 +21,10 @@ abstract class Routes { name: HOME, page: () => HomeScreen(), ), + GetPage( + name: STUDY, + page: () => const StudyMain(), + ), GetPage( name: PROFILE, page: () => const ProfileScreen(), diff --git a/lib/viewModels/study/date_study_screen_viewmodel.dart b/lib/viewModels/study/date_study_screen_viewmodel.dart index b110d71..68df46a 100644 --- a/lib/viewModels/study/date_study_screen_viewmodel.dart +++ b/lib/viewModels/study/date_study_screen_viewmodel.dart @@ -1,24 +1,61 @@ -import 'package:flutter/material.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:intl/intl.dart'; class LearningSession { - final String type; // 세션 유형 (음소, 단어, 문장) - final DateTime createdDate; // 세션 생성 날짜 + final int type; // 세션 유형 (음소, 단어, 문장) + final String createdDate; // 세션 생성 날짜 final String text; // 세션과 관련된 텍스트 + final int index; // 세션 인덱스 - LearningSession({required this.type, required this.createdDate, required this.text}); + LearningSession( + {required this.type, + required this.createdDate, + required this.text, + required this.index}); } class DateStudyViewModel { final DateTime date; - + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final FirebaseAuth _auth = FirebaseAuth.instance; DateStudyViewModel({required this.date}); - List getSessions() { - // 실제 애플리케이션에서는 여기서 날짜에 따라 데이터를 조회하거나 필터링하는 로직을 구현할 수 있습니다. - return [ - LearningSession(type: '음소', createdDate: DateTime(2024, 2, 12), text: "가"), - LearningSession(type: '단어', createdDate: DateTime(2024, 2, 13), text: "가다"), - LearningSession(type: '문장', createdDate: DateTime(2024, 2, 14), text: "가다 보면 길이 있다."), - ]; + Future> getSessions() async { + // 파이어스토어에 저장된 날짜 형식에 맞게 날짜를 변환 + final uid = _auth.currentUser?.uid; + + String formattedDate = DateFormat('yyyyMMdd').format(date); + try { + // 해당 날짜의 daily record 문서를 가져옴 + DocumentSnapshot dailyRecordSnapshot = await _firestore + .collection('users') + .doc(uid) + .collection('daily_records') + .doc(formattedDate) + .get(); + + if (dailyRecordSnapshot.exists) { + // 해당 날짜의 세션 데이터를 가져옴 + List> sessionsData = + List>.from( + dailyRecordSnapshot.get('wordsList') ?? []); + // 세션 데이터를 LearningSession 객체로 변환 + List sessions = sessionsData.map((session) { + return LearningSession( + type: session['type'], + createdDate: formattedDate, + text: session['word'], + index: session['index'], + ); + }).toList(); + + return sessions; + } else { + return []; + } + } catch (error) { + return []; + } } } diff --git a/lib/viewModels/word/word_viewmodel.dart b/lib/viewModels/word/word_viewmodel.dart index cc26767..b9adef8 100644 --- a/lib/viewModels/word/word_viewmodel.dart +++ b/lib/viewModels/word/word_viewmodel.dart @@ -116,6 +116,7 @@ class WordViewModel extends GetxController { existingWordsList.add({ 'word': word.word, 'type': word.type, + 'index': currentIndex.value }); // 업데이트 @@ -123,12 +124,13 @@ class WordViewModel extends GetxController { .update(dailyRecordRef, {'wordsList': existingWordsList}); } } else { - // If the document doesn't exist, create a new one with the initial word + // 만약 daily record가 없다면 새로 생성 transaction.set(dailyRecordRef, { 'wordsList': [ { 'word': word.word, 'type': word.type, + 'index': currentIndex.value }, ], }); diff --git a/lib/views/study/date_study_screen.dart b/lib/views/study/date_study_screen.dart index 3efe831..b99466b 100644 --- a/lib/views/study/date_study_screen.dart +++ b/lib/views/study/date_study_screen.dart @@ -1,18 +1,19 @@ +import 'package:earlips/utilities/style/color_styles.dart'; +import 'package:earlips/viewModels/study/date_study_screen_viewmodel.dart'; import 'package:earlips/views/base/default_back_appbar.dart'; +import 'package:earlips/views/study/widget/study_row_card_widget.dart'; +import 'package:earlips/views/word/word_screen.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:intl/intl.dart'; -// ViewModel import 경로는 실제 프로젝트 구조에 따라 달라질 수 있습니다. -import 'package:earlips/viewModels/study/date_study_screen_viewmodel.dart'; class DateStudyScreen extends StatelessWidget { final DateTime date; + const DateStudyScreen({super.key, required this.date}); @override Widget build(BuildContext context) { - final viewModel = DateStudyViewModel(date: date); - final sessions = viewModel.getSessions(); - return Scaffold( appBar: PreferredSize( preferredSize: const Size.fromHeight(kToolbarHeight), @@ -20,77 +21,110 @@ class DateStudyScreen extends StatelessWidget { title: DateFormat('yyyy/MM/dd').format(date), ), ), - body: ListView.separated( - padding: const EdgeInsets.fromLTRB(25, 20, 25, 20), - itemCount: sessions.length, - itemBuilder: (context, index) { - var session = sessions[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: Column( - children: [ - Container( - alignment: Alignment.centerLeft, - margin: const EdgeInsets.only(left: 20.0, top: 16.0), - child: _SmallCard(name: session.type)), // 세션 유형을 표시 - ListTile( - contentPadding: - const EdgeInsets.only(left: 20, right: 20, bottom: 30), - title: Container( - alignment: Alignment.center, - child: Text( - session.text, // 세션과 관련된 텍스트를 표시 - style: const TextStyle( - fontSize: 24.0, - fontWeight: FontWeight.bold, + body: FutureBuilder>( + future: DateStudyViewModel(date: date).getSessions(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator(), + ); + } else if (snapshot.hasError) { + return Center( + child: Text('Error: ${snapshot.error}'), + ); + } else if (!snapshot.hasData || snapshot.data!.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text('등록된 학습 기록이 없습니다.'), + const SizedBox(height: 20), + // 학습하러가기 + ElevatedButton( + style: ButtonStyle( + padding: MaterialStateProperty.all( + const EdgeInsets.fromLTRB(20, 10, 20, 10), + ), + backgroundColor: + MaterialStateProperty.all(ColorSystem.main2), + ), + onPressed: () { + Get.back(); + }, + child: const Text( + '학습하러가기', + style: TextStyle( + color: Colors.white, + fontSize: 16.0, ), ), ), - onTap: () {}, - ), - ], - ), - ); + ], + ), + ); + } else { + return ListView.separated( + padding: const EdgeInsets.fromLTRB(25, 20, 25, 20), + itemCount: snapshot.data!.length, + itemBuilder: (context, index) { + var session = snapshot.data![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: Column( + children: [ + Container( + alignment: Alignment.centerLeft, + margin: const EdgeInsets.only(left: 20.0, top: 16.0), + child: SmallCard(type: session.type), + ), + ListTile( + contentPadding: const EdgeInsets.only( + left: 20, right: 20, bottom: 30), + title: Container( + alignment: Alignment.center, + child: Text( + session.text, + style: const TextStyle( + fontSize: 24.0, + fontWeight: FontWeight.bold, + ), + ), + ), + onTap: () { + Get.to( + () => WordScreen( + // session type에 따라 다른 tttle + title: session.type == 0 + ? '음소' + : (session.type == 1 + ? '단어' + : (session.type == 2 + ? '문장' + : '문단')), + type: 0, + ), + arguments: session.index); + }, + ), + ], + ), + ); + }, + separatorBuilder: (context, index) => const SizedBox(height: 20), + ); + } }, - separatorBuilder: (context, index) => const SizedBox(height: 20), - ), - ); - } -} - -class _SmallCard extends StatelessWidget { - final String name; - - const _SmallCard({super.key, required this.name}); - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4.0), - color: const Color(0xFF1FA9DC), - ), - alignment: Alignment.center, - width: 50, - height: 24, - child: Text( - name, - style: const TextStyle( - color: Colors.white, - fontSize: 14, - fontWeight: FontWeight.w600, - ), ), ); } diff --git a/lib/views/study/widget/study_row_card_widget.dart b/lib/views/study/widget/study_row_card_widget.dart index c21cfc9..267bf29 100644 --- a/lib/views/study/widget/study_row_card_widget.dart +++ b/lib/views/study/widget/study_row_card_widget.dart @@ -1,9 +1,25 @@ import 'package:flutter/material.dart'; class SmallCard extends StatelessWidget { - final String name; + final int type; - const SmallCard({super.key, required this.name}); + const SmallCard({super.key, required this.type}); + + // type에 따라 name을 반환 + String get name { + switch (type) { + case 0: + return '음소'; + case 1: + return '단어'; + case 2: + return '문장'; + case 3: + return '문단'; + default: + return '음소'; + } + } @override Widget build(BuildContext context) { diff --git a/lib/views/word/widget/word_list_widget.dart b/lib/views/word/widget/word_list_widget.dart index 5c20c8d..d665fba 100644 --- a/lib/views/word/widget/word_list_widget.dart +++ b/lib/views/word/widget/word_list_widget.dart @@ -85,6 +85,9 @@ class _WordListState extends State { color: ColorSystem.gray5, ), ), + const SizedBox( + width: 5, + ), const Icon( Icons.check_circle_rounded, color: ColorSystem.green, diff --git a/lib/views/word/word_screen.dart b/lib/views/word/word_screen.dart index b2ad2ac..65c6674 100644 --- a/lib/views/word/word_screen.dart +++ b/lib/views/word/word_screen.dart @@ -13,11 +13,15 @@ class WordScreen extends StatelessWidget { const WordScreen({super.key, required this.title, required this.type}); + // argument로 받은 type에 따라 다른 wordViewModel을 사용 + @override Widget build(BuildContext context) { final wordViewModel = Get.put(WordViewModel( type: type, )); + var arg = Get.arguments; + wordViewModel.currentIndex.value = arg; final PageController pageController = PageController(initialPage: wordViewModel.currentIndex.value); @@ -82,7 +86,7 @@ class WordScreen extends StatelessWidget { ); } else { return Container( - child: CreateScriptPage(), + child: const CreateScriptPage(), ); } }, @@ -90,18 +94,29 @@ class WordScreen extends StatelessWidget { const Spacer(), // wordViewModel final String video로 영상 유튜브 링크를 바로 볼 수 있게 하기 ElevatedButton( + style: ButtonStyle( + padding: MaterialStateProperty.all( + const EdgeInsets.fromLTRB(30, 10, 30, 10), + ), + backgroundColor: MaterialStateProperty.all(ColorSystem.main2), + ), onPressed: () async { - await wordViewModel.markWordAsDone(wordViewModel - .wordList[wordViewModel.currentIndex.value].wordCard); - + // isLast Get.dialog( AlertDialog( title: const Text('학습 완료'), content: const Text('다음으로 넘어가려면 아래 버튼을 눌러주세요.'), actions: [ ElevatedButton( - onPressed: () { - Get.back(); + // 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( @@ -113,13 +128,24 @@ class WordScreen extends StatelessWidget { wordViewModel.currentIndex.value + 1; } }, - child: const Text('다음으로 넘어가기'), + // 마지막 단어일 경우 홈으로 이동 + + child: Text(wordViewModel.currentIndex.value < + wordViewModel.wordList.length - 1 + ? '다음 단어' + : '홈으로 이동'), ), ], ), ); }, - child: const Text("학습 완료"), + child: const Text( + "학습 완료", + style: TextStyle( + color: ColorSystem.white, + fontSize: 16.0, + ), + ), ), const SizedBox(height: 80), ],