diff --git a/lib/presentation/screens/mypage/controller/chat_history_viewmodel.dart b/lib/presentation/screens/mypage/controller/chat_history_viewmodel.dart new file mode 100644 index 0000000..6172aa2 --- /dev/null +++ b/lib/presentation/screens/mypage/controller/chat_history_viewmodel.dart @@ -0,0 +1,45 @@ +import 'package:get/get.dart'; +import 'package:palink_v2/di/locator.dart'; +import 'package:palink_v2/domain/model/character/character.dart'; +import 'package:palink_v2/domain/model/chat/message.dart'; +import 'package:palink_v2/domain/usecase/fetch_chat_history_usecase.dart'; + +class ChatHistoryViewmodel extends GetxController { + final FetchChatHistoryUsecase getChatHistoryUsecase = Get.put(getIt()); + + List? messages; // 단일 피드백 정보 저장 + Character? character; // 캐릭터 정보 저장 + int chatroomId; // 채팅방 ID + RxBool conversationNotFound = true.obs; // 404 처리 플래그 + + + ChatHistoryViewmodel({ + required this.chatroomId, + }); + + @override + void onInit() { + super.onInit(); + conversationNotFound.value = true; + loadMessages(); + } + + // 메시지 로드 + void loadMessages() async { + try { + // 메시지 가져오기 + messages = await getChatHistoryUsecase.execute(chatroomId); + messages = messages!.reversed.toList(); + conversationNotFound.value = false; // 404 에러가 발생하지 않은 경우 플래그 설정; + update(); + } catch (e) { + // 404 에러 발생 시 처리 + if (e.toString().contains('404')) { + conversationNotFound.value = true; // 404 에러가 발생한 경우 플래그 설정 + } else { + Get.snackbar('Error', 'Failed to load feedback'); + } + update(); + } + } +} diff --git a/lib/presentation/screens/mypage/controller/myconversations_viewmodel.dart b/lib/presentation/screens/mypage/controller/myconversations_viewmodel.dart new file mode 100644 index 0000000..a47fa50 --- /dev/null +++ b/lib/presentation/screens/mypage/controller/myconversations_viewmodel.dart @@ -0,0 +1,42 @@ +import 'package:get/get.dart'; +import 'package:palink_v2/di/locator.dart'; +import 'package:palink_v2/domain/model/chat/conversation.dart'; +import 'package:palink_v2/domain/usecase/get_chatroom_by_user.dart'; +import 'package:palink_v2/domain/model/character/character.dart'; +import 'package:palink_v2/domain/repository/character_repository.dart'; + +class MyconversationsViewmodel extends GetxController { + final GetChatroomByUser getChatroomByUser = Get.put(getIt()); + final CharacterRepository characterRepository = Get.put(getIt()); + + List chatrooms = []; + Map characters = {}; // 캐릭터 정보 저장 + + MyconversationsViewmodel(); + + @override + void onInit() { + super.onInit(); + _loadChatRooms(); + } + + void _loadChatRooms() async { + try { + var fetchedData = await getChatroomByUser.execute(); + chatrooms = fetchedData; + + // 캐릭터 ID에 해당하는 캐릭터 데이터 불러오기 + for (var conversation in chatrooms) { + var characterId = conversation.characterId; + + var character = await characterRepository.getCharacterById(characterId); + characters[characterId] = character; // 캐릭터 정보 저장 + } + + update(); // UI 업데이트 + } catch (e) { + // 에러 발생 시 처리 + Get.snackbar('Error', 'Failed to load chatrooms'); + } + } +} diff --git a/lib/presentation/screens/mypage/view/chat_history_view.dart b/lib/presentation/screens/mypage/view/chat_history_view.dart new file mode 100644 index 0000000..6518fb9 --- /dev/null +++ b/lib/presentation/screens/mypage/view/chat_history_view.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:palink_v2/domain/model/character/character.dart'; +import 'package:palink_v2/presentation/screens/chatting/view/components/messages.dart'; +import 'package:palink_v2/presentation/screens/common/appbar_perferred_size.dart'; +import 'package:palink_v2/presentation/screens/main_screens.dart'; +import 'package:palink_v2/presentation/screens/mypage/controller/chat_history_viewmodel.dart'; +import 'package:sizing/sizing.dart'; + +import 'feedback_history_view.dart'; + +class ChatHistoryView extends StatelessWidget { + final int chatroomId; + final ChatHistoryViewmodel viewModel; + final Character character; + + ChatHistoryView({required this.chatroomId, required this.character}) + : viewModel = Get.put(ChatHistoryViewmodel(chatroomId: chatroomId)); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + backgroundColor: Colors.white, + title: const Text('대화 기록'), + bottom: appBarBottomLine(), + ), + body: Stack( + children: [ + // 메시지 섹션이 스크롤 가능하도록 설정 + Positioned.fill( + child: Obx(() { + // 대화 데이터가 로드되지 않은 경우 + if (viewModel.conversationNotFound.value) { + return const Center( + child: Text( + '대화가 저장되지 않았습니다.', + style: TextStyle(fontSize: 18, color: Colors.grey), + ), + ); + } + // 대화가 있는 경우 메시지 리스트를 표시 + return SingleChildScrollView( + child: SizedBox( + height: 0.8.sh, + child: Messages( + messages: viewModel.messages ?? [], + userId: chatroomId, + characterImg: character.image ?? '', + onReactionAdded: (message, reaction) {}, + ), + ), + ); + }), + ), + // 버튼을 하단에 고정 + Align( + alignment: Alignment.bottomCenter, + child: SafeArea( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // 피드백 보기 버튼 추가 + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.grey, + padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 30.0), // 버튼 크기 조절 + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), // 네모난 모서리 + ), + ), + onPressed: () { + Get.off(() => const MainScreens()); + }, + child: const Text('홈 화면으로'), + ), + const SizedBox(width: 20), // 버튼 사이의 간격 추가 + // 홈 화면으로 버튼 + ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.blueAccent, + padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 30.0), // 버튼 크기 조절 + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), // 네모난 모서리 + ), + ), + onPressed: () { + Get.to(() => FeedbackHistoryView( + chatroomId: chatroomId, + character: character, + )); + }, + child: const Text('피드백 보기'), + ), + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/screens/mypage/view/feedback_history_view.dart b/lib/presentation/screens/mypage/view/feedback_history_view.dart index f5c43f0..54585c9 100644 --- a/lib/presentation/screens/mypage/view/feedback_history_view.dart +++ b/lib/presentation/screens/mypage/view/feedback_history_view.dart @@ -4,7 +4,6 @@ import 'package:palink_v2/core/theme/app_colors.dart'; import 'package:palink_v2/domain/model/character/character.dart'; import 'package:palink_v2/presentation/screens/chatting/view/components/liking_bar.dart'; import 'package:palink_v2/presentation/screens/common/appbar_perferred_size.dart'; -import 'package:palink_v2/presentation/screens/common/custom_btn.dart'; import 'package:palink_v2/presentation/screens/main_screens.dart'; import 'package:palink_v2/presentation/screens/mypage/controller/feedback_history_viewmodel.dart'; import 'package:sizing/sizing.dart'; @@ -26,35 +25,34 @@ class FeedbackHistoryView extends StatelessWidget { title: const Text('대화 최종 피드백'), bottom: appBarBottomLine(), ), - body: Obx(() { - // 피드백이 없는 경우 - if (viewModel.feedbackNotFound.value) { - return const Center( - child: Text( - '피드백이 저장되지 않았습니다.', - style: TextStyle(fontSize: 18, color: Colors.grey), - ), - ); - } + body: Column( + children: [ + // Scrollable content + Expanded( + child: Obx(() { + // 피드백이 없는 경우 + if (viewModel.feedbackNotFound.value) { + return const Center( + child: Text( + '피드백이 저장되지 않았습니다.', + style: TextStyle(fontSize: 18, color: Colors.grey), + ), + ); + } - // 피드백 데이터가 로드되지 않은 경우 - if (viewModel.feedback == null) { - return const Center(child: CircularProgressIndicator()); - } + // 피드백 데이터가 로드되지 않은 경우 + if (viewModel.feedback == null) { + return const Center(child: CircularProgressIndicator()); + } - return Column( - children: [ - SizedBox( - height: 0.8.sh, // 화면 높이의 75% - child: SingleChildScrollView( + return SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ const Text( '평가', - style: - TextStyle(fontSize: 22, fontWeight: FontWeight.bold), + style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), ), _buildProfileImage(), SizedBox(height: 0.045.sh), @@ -70,56 +68,67 @@ class FeedbackHistoryView extends StatelessWidget { SizedBox(height: 0.03.sh), const Text( '최종 호감도', - style: - TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), LikingBar(viewModel.feedback!.finalLikingLevel), - Text( - '최종 호감도 ${viewModel.feedback!.finalLikingLevel}점'), + Text('최종 호감도 ${viewModel.feedback!.finalLikingLevel}점'), SizedBox(height: 0.05.sh), const Text( '최종 거절 점수', - style: - TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), Text('${viewModel.feedback!.totalRejectionScore}점'), SizedBox(height: 0.05.sh), - CustomButton( - label: '홈 화면으로', - onPressed: () { - Get.off(() => MainScreens()); - }) ], ), + ); + }), + ), + // Fixed button at the bottom + SafeArea( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.white, + backgroundColor: Colors.blueAccent, + padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 0.3.sw), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + onPressed: () { + Get.off(() => const MainScreens()); + }, + child: const Text('홈 화면으로'), ), ), - ], - ); - }), + ), + ], + ), ); } // 프로필 이미지 표시, character가 null일 경우 대체 이미지 보여주기 Widget _buildProfileImage() { - if (character == null || character!.image == null) { + if (character == null || character.image == null) { return Container( width: 120, height: 120, decoration: BoxDecoration( - color: Colors.grey.shade300, // 기본 회색 배경 + color: Colors.grey.shade300, borderRadius: BorderRadius.circular(10), ), child: const Icon( Icons.person, size: 80, color: Colors.white, - ), // 기본 아이콘 + ), ); } - // character가 null이 아니면 이미지 표시 return Container( padding: const EdgeInsets.all(10), width: 120, @@ -127,15 +136,4 @@ class FeedbackHistoryView extends StatelessWidget { child: Image.asset(character.image ?? ''), ); } - - // 쉼표로 구분된 문자열을 줄바꿈과 번호로 포맷하는 메서드 - String _formatAsList(String commaSeparatedString) { - final items = - commaSeparatedString.split(',').map((item) => item.trim()).toList(); - return items - .asMap() - .entries - .map((entry) => '${entry.key + 1}. ${entry.value}') - .join('\n'); - } } diff --git a/lib/presentation/screens/mypage/view/myconversations_view.dart b/lib/presentation/screens/mypage/view/myconversations_view.dart new file mode 100644 index 0000000..0b75948 --- /dev/null +++ b/lib/presentation/screens/mypage/view/myconversations_view.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:palink_v2/core/theme/app_fonts.dart'; +import 'package:palink_v2/presentation/screens/common/appbar_perferred_size.dart'; +import 'package:palink_v2/presentation/screens/mypage/controller/myfeedbacks_viewmodel.dart'; +import 'chat_history_view.dart'; + + +class MyconversationsView extends StatelessWidget { + final MyfeedbacksViewmodel viewModel = Get.put(MyfeedbacksViewmodel()); + final ScrollController _scrollController = ScrollController(); + + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xfff5f5f5), + appBar: AppBar( + backgroundColor: Colors.white, + title: Text('내 대화 기록', style: textTheme().titleMedium), + centerTitle: false, + bottom: appBarBottomLine(), + ), + body: GetBuilder( + builder: (viewModel) { + if (viewModel.chatrooms.isEmpty) { + return const Center(child: Text('피드백이 없습니다.')); + } + // 첫 번째 아이템으로 스크롤 + WidgetsBinding.instance.addPostFrameCallback((_) { + if (viewModel.chatrooms.isNotEmpty) { + _scrollController.jumpTo(_scrollController.position.maxScrollExtent); + } + }); + + return ListView.builder( + controller: _scrollController, + itemCount: viewModel.chatrooms.length, + reverse: true, + + itemBuilder: (context, index) { + var chatroom = viewModel.chatrooms[index]; + var character = viewModel.characters[chatroom.characterId]; + return Column( + children: [ + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 30.0, vertical: 15.0), + tileColor: Colors.white, // 배경을 하얀색으로 설정 + leading: ClipRRect( + borderRadius: BorderRadius.circular(10), + child: Image.asset(character!.image) + ), + title: Text(character != null ? character.name : '익명', style: textTheme().titleMedium), + subtitle: Text(_formatDate(chatroom.day)), + horizontalTitleGap: 30.0, + onTap: () { + // 클릭 시 chatroomId를 전달하여 FeedbackHistoryView로 이동 + Get.to(() => ChatHistoryView(chatroomId: chatroom.conversationId, character: character)); + }, + ), + const Divider( + height: 0, + thickness: 0.5, + color: Colors.grey, + ), + ], + ); + }, + ); + }, + ), + ); + } + + // 날짜 포맷팅 함수 + String _formatDate(DateTime date) { + return '${date.year}년 ${date.month}월 ${date.day}일 ${date.hour}시 ${date.minute}분'; + } +} diff --git a/lib/presentation/screens/mypage/view/mypage_view.dart b/lib/presentation/screens/mypage/view/mypage_view.dart index 543c483..f2323ec 100644 --- a/lib/presentation/screens/mypage/view/mypage_view.dart +++ b/lib/presentation/screens/mypage/view/mypage_view.dart @@ -7,6 +7,7 @@ import 'package:palink_v2/presentation/screens/mypage/view/component/profile_sec import 'package:palink_v2/presentation/screens/mypage/view/component/user_info_section.dart'; import 'package:palink_v2/presentation/screens/mypage/view/myfeedbacks_view.dart'; import '../../common/appbar_perferred_size.dart'; +import 'myconversations_view.dart'; class MypageView extends StatelessWidget { final MypageViewModel mypageViewmodel = Get.put(getIt()); @@ -64,14 +65,14 @@ class MypageView extends StatelessWidget { children: [ ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), - title: const Text('지난 피드백 보러가기'), - onTap: () => Get.to(() => MyfeedbacksView()), + title: const Text('대화 기록 보러가기'), + onTap: () => Get.to(() => MyconversationsView()), trailing: const Icon(Icons.arrow_forward_ios), ), ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5), - title: const Text('내 친구들 보러가기'), - onTap: () => _showComingSoonDialog(context), + title: const Text('지난 피드백 보러가기'), + onTap: () => Get.to(() => MyfeedbacksView()), trailing: const Icon(Icons.arrow_forward_ios), ), ListTile(