From c2b93929e28f14ad4116452870e14f0feaae45c7 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:50:28 +0900 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=94=A8Chore:=20=EA=B8=B0=ED=83=80?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 21 +++++++++++++++++++++ lib/models/temp | 0 pubspec.yaml | 2 ++ 3 files changed, 23 insertions(+) delete mode 100644 lib/models/temp diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d95a33d..8c7380d 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -709,6 +709,13 @@ PODS: - nanopb (< 2.30910.0, >= 2.30908.0) - FirebaseSharedSwift (10.21.0) - Flutter (1.0.0) + - flutter_inappwebview (0.0.1): + - Flutter + - flutter_inappwebview/Core (= 0.0.1) + - OrderedSet (~> 5.0) + - flutter_inappwebview/Core (0.0.1): + - Flutter + - OrderedSet (~> 5.0) - flutter_localization (0.0.1): - Flutter - flutter_native_splash (0.0.1): @@ -813,6 +820,7 @@ PODS: - nanopb/encode (= 2.30909.1) - nanopb/decode (2.30909.1) - nanopb/encode (2.30909.1) + - OrderedSet (5.0.0) - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -827,6 +835,9 @@ PODS: - Flutter - Try - Try (2.1.1) + - video_player_avfoundation (0.0.1): + - Flutter + - FlutterMacOS DEPENDENCIES: - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) @@ -834,6 +845,7 @@ DEPENDENCIES: - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_database (from `.symlinks/plugins/firebase_database/ios`) - Flutter (from `Flutter`) + - flutter_inappwebview (from `.symlinks/plugins/flutter_inappwebview/ios`) - flutter_localization (from `.symlinks/plugins/flutter_localization/ios`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) @@ -843,6 +855,7 @@ DEPENDENCIES: - permission_handler (from `.symlinks/plugins/permission_handler/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - speech_to_text (from `.symlinks/plugins/speech_to_text/ios`) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) SPEC REPOS: trunk: @@ -868,6 +881,7 @@ SPEC REPOS: - GTMSessionFetcher - leveldb-library - nanopb + - OrderedSet - PromisesObjC - RecaptchaInterop - Try @@ -883,6 +897,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/firebase_database/ios" Flutter: :path: Flutter + flutter_inappwebview: + :path: ".symlinks/plugins/flutter_inappwebview/ios" flutter_localization: :path: ".symlinks/plugins/flutter_localization/ios" flutter_native_splash: @@ -901,6 +917,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" speech_to_text: :path: ".symlinks/plugins/speech_to_text/ios" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/darwin" SPEC CHECKSUMS: abseil: 926fb7a82dc6d2b8e1f2ed7f3a718bce691d1e46 @@ -921,6 +939,7 @@ SPEC CHECKSUMS: FirebaseFirestoreInternal: 7ac1e0c5b4e75aeb898dfe4b1d6d77abbac9eca3 FirebaseSharedSwift: 19b3f709993d6fa1d84941d41c01e3c4c11eab93 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + flutter_inappwebview: 3d32228f1304635e7c028b0d4252937730bbc6cf flutter_localization: f43b18844a2b3d2c71fd64f04ffd6b1e64dd54d4 flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be @@ -935,6 +954,7 @@ SPEC CHECKSUMS: GTMSessionFetcher: 8a1b34ad97ebe6f909fb8b9b77fba99943007556 leveldb-library: e74c27d8fbd22854db7cb467968a0b8aa1db7126 nanopb: d4d75c12cd1316f4a64e3c6963f879ecd4b5e0d5 + OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0 PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 @@ -942,6 +962,7 @@ SPEC CHECKSUMS: shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 speech_to_text: b43a7d99aef037bd758ed8e45d79bbac035d2dfe Try: 5ef669ae832617b3cee58cb2c6f99fb767a4ff96 + video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579 PODFILE CHECKSUM: 0b2c97823421f8b156b8e4753a469ac167670df8 diff --git a/lib/models/temp b/lib/models/temp deleted file mode 100644 index e69de29..0000000 diff --git a/pubspec.yaml b/pubspec.yaml index aa8eb84..5ae1361 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,8 @@ dependencies: http: ^1.2.0 flutter_localization: ^0.2.0 easy_localization: ^3.0.4 + video_player: ^2.8.2 + youtube_player_flutter: ^8.1.2 dev_dependencies: flutter_test: From df45dc3fbd021f457a8083e9c7c7b00295cb4c7d Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:53:04 +0900 Subject: [PATCH 2/6] =?UTF-8?q?=E2=9C=A8Feat:=20user/word=20Model=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/user_word_model.dart | 27 +++++++++++++++++++ lib/models/word_card_model.dart | 47 +++++++++++++++++++++++++++++++++ lib/models/word_data_model.dart | 12 +++++++++ 3 files changed, 86 insertions(+) create mode 100644 lib/models/user_word_model.dart create mode 100644 lib/models/word_card_model.dart create mode 100644 lib/models/word_data_model.dart diff --git a/lib/models/user_word_model.dart b/lib/models/user_word_model.dart new file mode 100644 index 0000000..b38abc8 --- /dev/null +++ b/lib/models/user_word_model.dart @@ -0,0 +1,27 @@ +class UserWord { + final int wordId; + final bool isDone; + final String? doneDate; + + UserWord({ + required this.wordId, + required this.isDone, + this.doneDate, + }); + + Map toMap() { + return { + 'wordId': wordId, + 'isDone': isDone, + 'doneDate': doneDate, + }; + } + + factory UserWord.fromDocument(Map doc) { + return UserWord( + wordId: doc['wordId'], + isDone: doc['isDone'], + doneDate: doc['doneDate'], + ); + } +} diff --git a/lib/models/word_card_model.dart b/lib/models/word_card_model.dart new file mode 100644 index 0000000..aaaf5eb --- /dev/null +++ b/lib/models/word_card_model.dart @@ -0,0 +1,47 @@ +class WordCard { + final int id; + final String word; + final String speaker; + final String video; + + WordCard({ + required this.id, + required this.word, + required this.speaker, + required this.video, + }); + + Map toMap() { + return { + 'id': id, + 'word': word, + 'speaker': speaker, + 'video': video, + }; + } + + factory WordCard.fromDocument(Map doc) { + return WordCard( + id: doc['id'], + word: doc['word'], + speaker: doc['speaker'], + video: doc['video'], + ); + } + + WordCard copyWith({ + int? id, + String? word, + String? speaker, + bool? isDone, + String? doneDate, + String? video, + }) { + return WordCard( + id: id ?? this.id, + word: word ?? this.word, + speaker: speaker ?? this.speaker, + video: video ?? this.video, + ); + } +} diff --git a/lib/models/word_data_model.dart b/lib/models/word_data_model.dart new file mode 100644 index 0000000..f6c606a --- /dev/null +++ b/lib/models/word_data_model.dart @@ -0,0 +1,12 @@ +import 'package:earlips/models/user_word_model.dart'; +import 'package:earlips/models/word_card_model.dart'; + +class WordData { + final WordCard wordCard; + final UserWord? userWord; + + WordData({ + required this.wordCard, + this.userWord, + }); +} From 83b35e5658e32b37beeb306ebfaebe6d4a63859c Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:53:25 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=E2=9C=A8Feat:=20uid=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/services/auth/auth_service.dart | 1 + lib/viewModels/auth/email_signup_viewmodel.dart | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/services/auth/auth_service.dart b/lib/services/auth/auth_service.dart index 84839e3..543ad26 100644 --- a/lib/services/auth/auth_service.dart +++ b/lib/services/auth/auth_service.dart @@ -54,6 +54,7 @@ class AuthService { // 사용자 데이터 final userData = { + 'uid': userCredential.user!.uid, 'systemLanguage': 'kr', 'learningLanguage': 'kr', 'nickname': extractNickname(googleUser!.email), // @ 앞까지만 추출 diff --git a/lib/viewModels/auth/email_signup_viewmodel.dart b/lib/viewModels/auth/email_signup_viewmodel.dart index d31ccbf..99e0bf5 100644 --- a/lib/viewModels/auth/email_signup_viewmodel.dart +++ b/lib/viewModels/auth/email_signup_viewmodel.dart @@ -2,14 +2,12 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:earlips/utilities/validators/auth_validators.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get/get.dart'; class EmailSignupViewModel extends GetxController { final formKey = GlobalKey(); final emailController = TextEditingController(); final passwordController = TextEditingController(); - final FlutterSecureStorage _storage = const FlutterSecureStorage(); // Email Validator String? emailValidator(String? value) { @@ -44,6 +42,7 @@ class EmailSignupViewModel extends GetxController { // 사용자 데이터 final userData = { + 'uid': userCredential.user!.uid, 'systemLanguage': 'kr', 'learningLanguage': 'kr', 'nickname': extractNickname(emailController.text.trim()), From dba822da31b41b3003e4db1c633bbc20322c0d21 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:53:51 +0900 Subject: [PATCH 4/6] =?UTF-8?q?=E2=9C=A8Feat:=20user,=20word=20viewmodel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/viewModels/user/user_viewmodel.dart | 43 +++++++++++ lib/viewModels/word/word_viewmodel.dart | 94 +++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 lib/viewModels/word/word_viewmodel.dart diff --git a/lib/viewModels/user/user_viewmodel.dart b/lib/viewModels/user/user_viewmodel.dart index f4f4ca5..0dfa5b6 100644 --- a/lib/viewModels/user/user_viewmodel.dart +++ b/lib/viewModels/user/user_viewmodel.dart @@ -9,6 +9,40 @@ class UserViewModel extends GetxController { final FirebaseFirestore _firestore = FirebaseFirestore.instance; final storage = const FlutterSecureStorage(); // Instance for secure storage + // final List wordList = [ + // WordCard( + // id: 1, + // word: "강", + // speaker: "가-앙", + // video: "https://www.youtube.com/watch?v=OzHrIz-wMLA"), + // WordCard( + // id: 2, + // word: "서", + // speaker: "가-앙", + // video: "https://www.youtube.com/watch?v=OzHrIz-wMLA"), + // WordCard( + // id: 3, + // word: "희", + // speaker: "가-앙", + // video: "https://www.youtube.com/watch?v=OzHrIz-wMLA"), + // WordCard( + // id: 4, + // word: "찬", + // speaker: "차-안", + // video: "https://www.youtube.com/watch?v=OzHrIz-wMLA"), + // WordCard( + // id: 5, + // word: "캬", + // speaker: "캬", + // video: "https://www.youtube.com/watch?v=OzHrIz-wMLA"), + // ]; + + // for (final word in wordList) { + // await FirebaseFirestore.instance + // .collection('words') + // .doc(word.id.toString()) + // .set(word.toMap()); + // } // User Profile Data final Rx _firebaseUser = Rx(null); String? get uid => _firebaseUser.value?.uid; @@ -31,6 +65,15 @@ class UserViewModel extends GetxController { Future getUserData() async { if (uid != null) { + // // users 컬렉션의 하위 컬렉션에 단어 완료 정보 삽입 + // for (final word in wordList) { + // await FirebaseFirestore.instance + // .collection('users') + // .doc(uid) + // .collection('words') + // .doc(word.id.toString()) + // .set(UserWord(wordId: word.id, isDone: false).toMap()); + // } final doc = await _firestore.collection('users').doc(uid).get(); userData.value = doc.data() ?? {}; nickname.value = userData.value['nickname'] ?? ''; diff --git a/lib/viewModels/word/word_viewmodel.dart b/lib/viewModels/word/word_viewmodel.dart new file mode 100644 index 0000000..17260c7 --- /dev/null +++ b/lib/viewModels/word/word_viewmodel.dart @@ -0,0 +1,94 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:earlips/models/user_word_model.dart'; +import 'package:earlips/models/word_card_model.dart'; +import 'package:earlips/models/word_data_model.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:get/get.dart'; +import 'package:intl/intl.dart'; + +class WordViewModel extends GetxController { + final FirebaseFirestore _firestore = FirebaseFirestore.instance; + final FirebaseAuth _auth = FirebaseAuth.instance; + + RxList wordList = RxList([]); + RxInt currentIndex = 0.obs; + + @override + void onInit() { + super.onInit(); + fetchWords(); + fetchWords(); + wordList.refresh(); + } + + void updateCurrentIndex(int index) { + currentIndex.value = index; + } + + // 단어 데이터 가져오기 + Future fetchWords() async { + final uid = _auth.currentUser?.uid; + if (uid != null) { + // 모든 단어 데이터 가져오기 + final wordsQuery = await _firestore.collection('words').get(); + final allWords = wordsQuery.docs + .map((doc) => WordCard.fromDocument(doc.data())) + .toList(); + + // 현재 사용자의 단어 데이터 가져오기 + final userWordsQuery = await _firestore + .collection('users') + .doc(uid) + .collection('words') + .get(); + final userWords = userWordsQuery.docs + .map((doc) => UserWord.fromDocument(doc.data())) + .toList(); + + // 모든 단어 데이터에 사용자의 단어 데이터를 추가 + wordList.value = allWords.map((word) { + final matchingUserWord = userWords + .firstWhereOrNull((userWord) => userWord.wordId == word.id); + + return WordData( + wordCard: word, + userWord: matchingUserWord, + ); + }).toList(); + update(); + } + } + + // 단어 완료 처리 + Future markWordAsDone(WordCard word) async { + final uid = _auth.currentUser?.uid; + if (uid != null) { + // Add or update data in the user's 'words' subcollection in Firestore + await _firestore + .collection('users') + .doc(uid) + .collection('words') + .doc(word.id.toString()) + .set({ + 'wordId': word.id, + 'isDone': true, + 'doneDate': DateFormat('yyyy/MM/dd').format(DateTime.now()), + }); + + final index = + wordList.indexWhere((element) => element.wordCard.id == word.id); + if (index != -1) { + // Handle the scenario where we don't find the card locally. + wordList[index] = WordData( + wordCard: word, + userWord: UserWord( + wordId: word.id, + isDone: true, + doneDate: DateFormat('yyyy/MM/dd').format(DateTime.now()), + ), + ); + wordList.refresh(); + } + } + } +} From 6e10aff3b277514e17f28ade781eded74990ead0 Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:53:58 +0900 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=A8Feat:=20word=20screen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/views/word/word_screen.dart | 230 +++++++++----------------------- 1 file changed, 61 insertions(+), 169 deletions(-) diff --git a/lib/views/word/word_screen.dart b/lib/views/word/word_screen.dart index 100ab51..d7167a8 100644 --- a/lib/views/word/word_screen.dart +++ b/lib/views/word/word_screen.dart @@ -1,62 +1,10 @@ import 'package:earlips/utilities/style/color_styles.dart'; +import 'package:earlips/viewModels/word/word_viewmodel.dart'; import 'package:earlips/views/word/widget/blue_back_appbar.dart'; +import 'package:earlips/views/word/widget/word_list_widget.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; - -// word dummy Daaa -final List wordList = [ - WordCard( - id: 1, - word: "강", - speaker: "가-앙", - isDone: false, - doneDate: "2021/10/10", - video: "https://www.youtube.com/watch?v=OzHrIz-wMLA"), - WordCard( - id: 2, - word: "서", - speaker: "가-앙", - isDone: false, - doneDate: "2022/10/10", - video: "https://www.youtube.com/watch?v=OzHrIz-wMLA"), - WordCard( - id: 3, - word: "희", - speaker: "가-앙", - isDone: false, - video: "https://www.youtube.com/watch?v=OzHrIz-wMLA"), - WordCard( - id: 4, - word: "찬", - speaker: "차-안", - isDone: false, - video: "https://www.youtube.com/watch?v=OzHrIz-wMLA"), - WordCard( - id: 5, - word: "캬", - speaker: "캬", - isDone: true, - doneDate: "2023/10/10", - video: "https://www.youtube.com/watch?v=OzHrIz-wMLA"), - // Add more WordCard instances as needed -]; - -class WordCard { - final int id; - final String word; - final String speaker; - final bool isDone; - final String? doneDate; - final String video; - - WordCard( - {required this.id, - required this.word, - required this.speaker, - required this.isDone, - this.doneDate, - required this.video}); -} +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; class WordScreen extends StatelessWidget { final String title; @@ -66,6 +14,11 @@ class WordScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final wordViewModel = Get.put(WordViewModel()); + + final PageController pageController = + PageController(initialPage: wordViewModel.currentIndex.value); + return Scaffold( appBar: PreferredSize( preferredSize: const Size.fromHeight(kToolbarHeight), @@ -85,9 +38,13 @@ class WordScreen extends StatelessWidget { child: Column( children: [ const SizedBox(height: 20), - Padding( - padding: const EdgeInsets.fromLTRB(0, 8, 0, 0), - child: WordList(wordList: wordList), + GetBuilder( + // Add GetBuilder here + builder: (controller) => WordList( + // viewmodel + wordDataList: controller.wordList, + pageController: pageController, + ), ), const SizedBox( height: 70, @@ -110,122 +67,57 @@ class WordScreen extends StatelessWidget { ], ), ), - const Spacer(), - ], - ), - ), - ); - } -} - -class WordList extends StatefulWidget { - final List wordList; - - const WordList({super.key, required this.wordList}); - - @override - _WordListState createState() => _WordListState(); -} - -class _WordListState extends State { - late PageController _pageController; - int currentIndex = 0; - - @override - void initState() { - super.initState(); - _pageController = PageController(initialPage: currentIndex); - } - @override - Widget build(BuildContext context) { - return SizedBox( - height: Get.height * 0.165, - child: PageView.builder( - controller: _pageController, - itemCount: widget.wordList.length, - onPageChanged: (index) { - setState(() { - currentIndex = index; - }); - }, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.fromLTRB(20, 8, 20, 0), - child: Container( - decoration: BoxDecoration( - color: ColorSystem.white, - border: Border.all(color: const Color(0xffB3CBE2), width: 4), - borderRadius: BorderRadius.circular(24), - ), - child: Column( - children: [ - Container( - color: Colors.transparent, - padding: const EdgeInsets.fromLTRB(20, 10, 20, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '${currentIndex + 1}/${widget.wordList.length}', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: ColorSystem.gray5, - ), - ), + const Spacer(), + // wordViewModel final String video로 영상 유튜브 링크를 바로 볼 수 있게 하기 + ElevatedButton( + onPressed: () { + Get.toNamed( + '/video', + arguments: wordViewModel + .wordList[wordViewModel.currentIndex.value] + .wordCard + .video, + ); + }, + child: const Text("영상 보기"), + ), + ElevatedButton( + onPressed: () async { + await wordViewModel.markWordAsDone(wordViewModel + .wordList[wordViewModel.currentIndex.value].wordCard); - // isDone 여부에 따라 다른 체크박스 아이콘을 표시합니다. - widget.wordList[index].isDone - ? Row( - children: [ - Text(widget.wordList[index].doneDate!, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: ColorSystem.gray5)), - const Icon( - Icons.check_circle_rounded, - color: ColorSystem.green, - ), - ], - ) - : const Icon( - Icons.check_circle_outline_rounded, - color: ColorSystem.green2, - ), - ], - ), - ), - ListTile( - tileColor: Colors.white, - title: Text(widget.wordList[index].word, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.w600, - color: ColorSystem.black, - ), - textAlign: TextAlign.center), - subtitle: Text(widget.wordList[index].speaker, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: ColorSystem.gray4, - ), - textAlign: TextAlign.center), + Get.dialog( + AlertDialog( + title: const Text('학습 완료'), + content: const Text('다음으로 넘어가려면 아래 버튼을 눌러주세요.'), + actions: [ + ElevatedButton( + onPressed: () { + Get.back(); + if (wordViewModel.currentIndex.value < + wordViewModel.wordList.length - 1) { + pageController.animateToPage( + wordViewModel.currentIndex.value + 1, + duration: const Duration(milliseconds: 300), + curve: Curves.ease, + ); + wordViewModel.currentIndex.value = + wordViewModel.currentIndex.value + 1; + } + }, + child: const Text('다음으로 넘어가기'), + ), + ], ), - ], - ), + ); + }, + child: const Text("학습 완료"), ), - ); - }, + const SizedBox(height: 80), + ], + ), ), ); } - - @override - void dispose() { - _pageController.dispose(); - super.dispose(); - } } From 3894defc010936f06ae427129daf2bd0ecb9b07c Mon Sep 17 00:00:00 2001 From: HuiChan Seo <78739194+seochan99@users.noreply.github.com> Date: Sun, 18 Feb 2024 17:54:06 +0900 Subject: [PATCH 6/6] =?UTF-8?q?=E2=9C=A8Feat:=20word=20list=20widget?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/views/word/widget/word_list_widget.dart | 136 ++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 lib/views/word/widget/word_list_widget.dart diff --git a/lib/views/word/widget/word_list_widget.dart b/lib/views/word/widget/word_list_widget.dart new file mode 100644 index 0000000..1aa7311 --- /dev/null +++ b/lib/views/word/widget/word_list_widget.dart @@ -0,0 +1,136 @@ +import 'package:earlips/models/word_card_model.dart'; +import 'package:earlips/models/word_data_model.dart'; +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'; + +class WordList extends StatefulWidget { + final List wordDataList; + + PageController pageController; + + WordList( + {super.key, required this.wordDataList, required this.pageController}); + + @override + _WordListState createState() => _WordListState(); +} + +class _WordListState extends State { + final wordViewModel = Get.find(); // Access your ViewModel! + + @override + void initState() { + super.initState(); + widget.pageController = + PageController(initialPage: wordViewModel.currentIndex.value); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: Get.height * 0.165, + child: PageView.builder( + controller: widget.pageController, + itemCount: widget.wordDataList.length, + onPageChanged: (index) { + setState(() { + wordViewModel.currentIndex.value = index; + }); + print('currentIndex: ${wordViewModel.currentIndex.value}'); + }, + itemBuilder: (context, index) { + final wordData = widget.wordDataList[index]; + final isDone = wordData.userWord?.isDone ?? false; + final doneDate = wordData.userWord?.doneDate ?? ''; + + return Padding( + padding: const EdgeInsets.fromLTRB(20, 8, 20, 0), + child: Container( + decoration: BoxDecoration( + color: ColorSystem.white, + border: Border.all( + color: const Color(0xffB3CBE2), + width: 4, + ), + borderRadius: BorderRadius.circular(24), + ), + child: Column( + children: [ + Container( + color: Colors.transparent, + padding: const EdgeInsets.fromLTRB(20, 10, 20, 0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${wordViewModel.currentIndex.value + 1}/${widget.wordDataList.length}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: ColorSystem.gray5, + ), + ), + + // isDone 여부에 따라 다른 체크박스 아이콘을 표시합니다. + isDone + ? Row( + children: [ + Text( + doneDate, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: ColorSystem.gray5, + ), + ), + const Icon( + Icons.check_circle_rounded, + color: ColorSystem.green, + ), + ], + ) + : const Icon( + Icons.check_circle_outline_rounded, + color: ColorSystem.green2, + ), + ], + ), + ), + ListTile( + tileColor: Colors.white, + title: Text( + wordData.wordCard.word, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.w600, + color: ColorSystem.black, + ), + textAlign: TextAlign.center, + ), + subtitle: Text( + wordData.wordCard.speaker, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: ColorSystem.gray4, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ); + }, + ), + ); + } + + @override + void dispose() { + widget.pageController.dispose(); + super.dispose(); + } +}