From 0ee524dc87e56fceb8a4e52434be750fae6a742d Mon Sep 17 00:00:00 2001 From: ryusuye0n Date: Thu, 4 Jun 2026 10:31:08 +0900 Subject: [PATCH 1/8] =?UTF-8?q?fix=20:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=ED=95=98=ED=8A=B8=20=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/community_controller.dart | 22 +-- .../home/controllers/home_controller.dart | 120 +++++++++++------ .../home/screens/home_screen.dart | 60 +++++---- .../controllers/post_view_controller.dart | 126 +++++++++++++----- lib/presentation/post/widgets/post_body.dart | 11 +- pubspec.lock | 2 +- 6 files changed, 222 insertions(+), 119 deletions(-) diff --git a/lib/presentation/community/controllers/community_controller.dart b/lib/presentation/community/controllers/community_controller.dart index b40de1f4..0a3d003f 100644 --- a/lib/presentation/community/controllers/community_controller.dart +++ b/lib/presentation/community/controllers/community_controller.dart @@ -60,6 +60,11 @@ class CommunityController extends GetxController { super.onReady(); } + + bool isPostLiked(int postId) { + return _cachedLikedIds.contains(postId); + } + Future fetchRecommendPosts({bool refresh = false}) async { if (refresh) { _currentPage = 0; @@ -77,12 +82,12 @@ class CommunityController extends GetxController { page: _currentPage, ); - // 로컬 캐시 기반으로 isFavorite 덮어쓰기 (앱 재시작 후에도 유지) + final applied = result.content.map((post) { if (_cachedLikedIds.contains(post.postId)) { return post.copyWith(isFavorite: true); } - // API가 이미 좋아요 상태를 반환한 경우 캐시에도 반영 + if (post.isFavorite) { _cachedLikedIds.add(post.postId); } @@ -127,7 +132,7 @@ class CommunityController extends GetxController { int postId, bool isLiked, ) async { - // 옵티미스틱 업데이트: API 결과 전에 즉시 UI 반영 + _updatePostLikeInList(postId, isLiked ? 1 : -1, isLiked); try { @@ -137,7 +142,7 @@ class CommunityController extends GetxController { await _unlikeUseCase(postId); } - // 로컬 캐시 갱신 및 영속화 + if (isLiked) { _cachedLikedIds.add(postId); } else { @@ -145,7 +150,7 @@ class CommunityController extends GetxController { } await _savePostLikeLocalUseCase(postId, isLiked); - // 홈 목록과 cross-sync + if (Get.isRegistered()) { final post = viewPosts.firstWhereOrNull((p) => p.postId == postId); if (post != null) { @@ -161,7 +166,7 @@ class CommunityController extends GetxController { '[CommunityController] 좋아요 토글 실패 - error: $e', ); - // API 실패 시 롤백 + _updatePostLikeInList(postId, isLiked ? -1 : 1, !isLiked); } } @@ -201,7 +206,7 @@ class CommunityController extends GetxController { int likeCount, bool isFavorite, ) async { - // 로컬 캐시 동기화 (PostViewController → CommunityController 방향) + if (isFavorite) { _cachedLikedIds.add(postId); } else { @@ -303,8 +308,7 @@ class CommunityController extends GetxController { } } -class CommunityResultController - extends GetxController { +class CommunityResultController extends GetxController { final RxList viewPosts = [].obs; diff --git a/lib/presentation/home/controllers/home_controller.dart b/lib/presentation/home/controllers/home_controller.dart index e6fcfdba..2fc23fd1 100644 --- a/lib/presentation/home/controllers/home_controller.dart +++ b/lib/presentation/home/controllers/home_controller.dart @@ -76,8 +76,13 @@ class HomeController extends GetxController with BaseHomeController { viewPostList.assignAll(_cachePostList); } + + bool isPostLiked(int postId) { + return _cachedLikedIds.contains(postId); + } + Future toggleLike(int postId, bool isLiked) async { - // 옵티미스틱 업데이트 + _updatePostLikeInList(postId, isLiked ? 1 : -1, isLiked); try { @@ -94,7 +99,7 @@ class HomeController extends GetxController with BaseHomeController { } await savePostLikeLocalUseCase(postId, isLiked); - // 커뮤니티 목록과 cross-sync + if (Get.isRegistered()) { final post = viewPostList.firstWhereOrNull((p) => p.postId == postId); if (post != null) { @@ -129,21 +134,48 @@ class HomeController extends GetxController with BaseHomeController { } } - void updatePostLike(int postId, int likeCount, bool isFavorite) { - final index = viewPostList.indexWhere((p) => p.postId == postId); + void updatePostLike( + int postId, + int likeCount, + bool isFavorite, + ) { + + if (isFavorite) { + _cachedLikedIds.add(postId); + } else { + _cachedLikedIds.remove(postId); + } + + final index = viewPostList.indexWhere( + (p) => p.postId == postId, + ); + if (index != -1) { - viewPostList[index] = viewPostList[index].copyWith( + final updatedPost = + viewPostList[index].copyWith( likeCount: likeCount, isFavorite: isFavorite, ); - viewPostList.refresh(); + + viewPostList[index] = updatedPost; + + + viewPostList.assignAll( + List.from(viewPostList), + ); } - final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); + + final cacheIndex = + _cachePostList.indexWhere( + (p) => p.postId == postId, + ); + if (cacheIndex != -1) { - _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( - likeCount: likeCount, - isFavorite: isFavorite, - ); + _cachePostList[cacheIndex] = + _cachePostList[cacheIndex].copyWith( + likeCount: likeCount, + isFavorite: isFavorite, + ); } } @@ -183,9 +215,9 @@ class HomeSearchResultController extends GetxController with BaseHomeController { ///홈 검색 결과 업데이트 void updateResult( - Iterable posts, - Iterable profiles, - ) { + Iterable posts, + Iterable profiles, + ) { viewUserList.assignAll(profiles); viewPostList.assignAll(posts); } @@ -193,47 +225,47 @@ class HomeSearchResultController extends GetxController //TODO : 임시 데이터 삭제 typedef HomeRecentPopularPostInfo = ({ - int postId, - String title, - Duration creatAt, - int favorites, - bool isFavorite, +int postId, +String title, +Duration creatAt, +int favorites, +bool isFavorite, }); typedef HomeProfileInfo = ({String name, String skill, int rating}); typedef PostInfo = ({ - int postId, - List skills, - String title, - String name, - int favoites, - int bookmarks, - DateTime createAt, - bool isBookmark, - bool isFavorite, +int postId, +List skills, +String title, +String name, +int favoites, +int bookmarks, +DateTime createAt, +bool isBookmark, +bool isFavorite, }); List _getRanks() => [ for (int i = 1; i < 5; i++) ...{ ( - postId: i + 1, - title: "요즘 공부 어케 하시나요 다들", - creatAt: Duration(days: 3), - favorites: 160 * Random().nextInt(i), - isFavorite: i % 2 == 0, + postId: i + 1, + title: "요즘 공부 어케 하시나요 다들", + creatAt: Duration(days: 3), + favorites: 160 * Random().nextInt(i), + isFavorite: i % 2 == 0, ), ( - postId: i + 10, - title: "10년차 개발자는 무슨 공부할까", - creatAt: Duration(days: 5), - favorites: 121 * Random().nextInt(i), - isFavorite: i % 2 == 0, + postId: i + 10, + title: "10년차 개발자는 무슨 공부할까", + creatAt: Duration(days: 5), + favorites: 121 * Random().nextInt(i), + isFavorite: i % 2 == 0, ), ( - postId: i + 120, - title: "팀장 퇴사해서 디자인빵꾸남", - creatAt: Duration(days: 2), - favorites: 73 * Random().nextInt(i), - isFavorite: i % 2 == 0, + postId: i + 120, + title: "팀장 퇴사해서 디자인빵꾸남", + creatAt: Duration(days: 2), + favorites: 73 * Random().nextInt(i), + isFavorite: i % 2 == 0, ), }, -]; +]; \ No newline at end of file diff --git a/lib/presentation/home/screens/home_screen.dart b/lib/presentation/home/screens/home_screen.dart index e8b3318c..78933814 100644 --- a/lib/presentation/home/screens/home_screen.dart +++ b/lib/presentation/home/screens/home_screen.dart @@ -38,30 +38,42 @@ class HomeScreen extends GetView { AppGap.v16, HomeProfileList(title: "커피챗 추천", controller: controller), AppGap.v16, - PostGridList( - title: "추천 게시물", - list: controller.viewPostList - .map( - (post) => PostItem( - postId: post.postId, - skills: post.tags, - title: post.title, - author: post.authorName, - bookmarks: post.bookmarkCount, - favorites: post.likeCount, - createAt: post.createAt, - bookmarkAction: (isBookmark, total) { - //TODO : 북마크 api 개발 이후 구현 - }, - heartAction: (isFavorite, total) { - controller.toggleLike(post.postId, isFavorite); - }, - initialBookmark: false, - initialFavorite: post.isFavorite, - isMy: true, - ), - ) - .toList(), + Obx( + () => PostGridList( + title: "추천 게시물", + list: controller.viewPostList + .map( + (post) => PostItem( + postId: post.postId, + skills: post.tags, + title: post.title, + author: post.authorName, + bookmarks: post.bookmarkCount, + favorites: post.likeCount, + createAt: post.createAt, + bookmarkAction: ( + isBookmark, + total, + ) { + // TODO : 북마크 api 개발 이후 구현 + }, + heartAction: ( + isFavorite, + total, + ) { + controller.toggleLike( + post.postId, + isFavorite, + ); + }, + initialBookmark: false, + initialFavorite: + post.isFavorite, + isMy: true, + ), + ) + .toList(), + ), ), AppGap.v16, ], diff --git a/lib/presentation/post/controllers/post_view_controller.dart b/lib/presentation/post/controllers/post_view_controller.dart index a16f2e21..3956ce07 100644 --- a/lib/presentation/post/controllers/post_view_controller.dart +++ b/lib/presentation/post/controllers/post_view_controller.dart @@ -51,15 +51,15 @@ class PostViewController extends GetxController { required CreateCommentUseCase createCommentUseCase, required DeleteCommentUseCase deleteCommentUseCase, }) : _getPostDetailUseCase = getPostDetailUseCase, - _updatePostUseCase = updatePostUseCase, - _deletePostUseCase = deletePostUseCase, - _likePostUseCase = likePostUseCase, - _unlikePostUseCase = unlikePostUseCase, - _bookmarkPostUseCase = bookmarkPostUseCase, - _unbookmarkPostUseCase = unbookmarkPostUseCase, - _getCommentsUseCase = getCommentsUseCase, - _createCommentUseCase = createCommentUseCase, - _deleteCommentUseCase = deleteCommentUseCase; + _updatePostUseCase = updatePostUseCase, + _deletePostUseCase = deletePostUseCase, + _likePostUseCase = likePostUseCase, + _unlikePostUseCase = unlikePostUseCase, + _bookmarkPostUseCase = bookmarkPostUseCase, + _unbookmarkPostUseCase = unbookmarkPostUseCase, + _getCommentsUseCase = getCommentsUseCase, + _createCommentUseCase = createCommentUseCase, + _deleteCommentUseCase = deleteCommentUseCase; final Rx post = Rx(null); final RxList postList = [].obs; @@ -102,23 +102,36 @@ class PostViewController extends GetxController { } bool _resolveIsFavorite() { - if (Get.isRegistered()) { - final post = Get.find().viewPosts.firstWhereOrNull( - (p) => p.postId == postId, - ); + bool homeLiked = false; + bool communityLiked = false; - if (post != null) return post.isFavorite; - } if (Get.isRegistered()) { - final post = Get.find().viewPostList.firstWhereOrNull( - (p) => p.postId == postId, - ); + homeLiked = + Get.find() + .isPostLiked(postId); + } - if (post != null) return post.isFavorite; + + if (Get.isRegistered()) { + communityLiked = + Get.find() + .isPostLiked(postId); } - return initialIsFavorite; + debugPrint( + '[PostViewController] ' + 'resolveIsFavorite - ' + 'postId: $postId, ' + 'homeLiked: $homeLiked, ' + 'communityLiked: $communityLiked, ' + 'initialIsFavorite: $initialIsFavorite', + ); + + + return homeLiked || + communityLiked || + initialIsFavorite; } Future fetchPostDetail(int postId) async { @@ -145,8 +158,11 @@ class PostViewController extends GetxController { postTags.assignAll(result.tags); heartTotal.value = result.likeCount; + bookMarkTotal.value = result.bookmarkCount; commentCount.value = result.commentCount; - selectHeart.value = result.isLike; + + + selectHeart.value = _resolveIsFavorite(); } catch (e) { debugPrint( '[PostViewController] API 요청 실패 - error: $e', @@ -170,7 +186,7 @@ class PostViewController extends GetxController { comments.assignAll( result.map( - (e) => e.toEntity(currentUserId: null), + (e) => e.toEntity(currentUserId: null), ), ); @@ -189,9 +205,29 @@ class PostViewController extends GetxController { } Future toggleLike(bool isLiked) async { + debugPrint( + '[PostViewController] toggleLike 시작 ' + 'postId: $postId, ' + 'isLiked: $isLiked', + ); + + debugPrint( + '[PostViewController] ' + 'HomeController registered: ' + '${Get.isRegistered()}', + ); + + debugPrint( + '[PostViewController] ' + 'CommunityController registered: ' + '${Get.isRegistered()}', + ); + selectHeart.value = isLiked; - heartTotal.value = isLiked ? heartTotal.value + 1 : heartTotal.value - 1; + heartTotal.value = isLiked + ? heartTotal.value + 1 + : heartTotal.value - 1; try { if (isLiked) { @@ -200,16 +236,38 @@ class PostViewController extends GetxController { await _unlikePostUseCase(postId); } - if (Get.isRegistered()) { - await Get.find().updatePostLike( + // 홈 목록 강제 업데이트 + if (Get.isRegistered()) { + final homeController = + Get.find(); + + debugPrint( + '[PostViewController] ' + 'Home updatePostLike 호출', + ); + + homeController.updatePostLike( postId, heartTotal.value, isLiked, ); + + debugPrint( + '[PostViewController] ' + 'home liked after update: ' + '${homeController.isPostLiked(postId)}', + ); } - if (Get.isRegistered()) { - Get.find().updatePostLike( + // 커뮤니티 목록 업데이트 + if (Get.isRegistered()) { + debugPrint( + '[PostViewController] ' + 'Community updatePostLike 호출', + ); + + await Get.find() + .updatePostLike( postId, heartTotal.value, isLiked, @@ -217,15 +275,17 @@ class PostViewController extends GetxController { } } catch (e) { debugPrint( - '[PostViewController] 좋아요 토글 실패 - error: $e', + '[PostViewController] ' + '좋아요 토글 실패 - error: $e', ); selectHeart.value = !isLiked; - heartTotal.value = isLiked ? heartTotal.value - 1 : heartTotal.value + 1; + heartTotal.value = isLiked + ? heartTotal.value - 1 + : heartTotal.value + 1; } } - Future toggleBookmark(bool isBookmarked) async { selectBookMark.value = isBookmarked; bookMarkTotal.value = isBookmarked @@ -307,7 +367,7 @@ class PostViewController extends GetxController { '[PostViewController] 게시물 삭제 요청 - postId: $postId', ); - await _deletePostUseCase(postId); + await _deletePostUseCase(postId); debugPrint('[PostViewController] 게시물 삭제 성공'); Get.find().removePost(postId); @@ -383,7 +443,7 @@ class PostViewController extends GetxController { void editPost() { Get.delete(force: true); Get.lazyPut( - () => CommunityPostCreateController( + () => CommunityPostCreateController( createUseCase: Get.find(), updateUseCase: Get.find(), isEditMode: true, @@ -395,4 +455,4 @@ class PostViewController extends GetxController { ); Get.to(() => CommunityPostCreateScreen()); } -} +} \ No newline at end of file diff --git a/lib/presentation/post/widgets/post_body.dart b/lib/presentation/post/widgets/post_body.dart index 5cf66e4c..9a28321c 100644 --- a/lib/presentation/post/widgets/post_body.dart +++ b/lib/presentation/post/widgets/post_body.dart @@ -14,7 +14,7 @@ class PostBody extends GetView { @override Widget build(BuildContext context) { return Obx( - () => Column( + () => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _top(), @@ -77,12 +77,7 @@ class PostBody extends GetView { ); Widget _bookmarkButton() => GestureDetector( - onTap: () { - controller.selectBookMark.value = !controller.selectBookMark.value; - controller.selectBookMark.value - ? controller.bookMarkTotal.value += 1 - : controller.bookMarkTotal.value -= 1; - }, + onTap: () => controller.toggleBookmark(!controller.selectBookMark.value), // 수정 child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -102,4 +97,4 @@ class PostBody extends GetView { ], ), ); -} +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 4952096d..7fffb99f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -788,7 +788,7 @@ packages: sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" url: "https://pub.dev" source: hosted - version: "0.7.9" + version: "0.7.10" timezone: dependency: transitive description: From 4079b3a2d98a0cf205458d6322180c39dd795dde Mon Sep 17 00:00:00 2001 From: ryusuye0n Date: Thu, 4 Jun 2026 11:21:28 +0900 Subject: [PATCH 2/8] =?UTF-8?q?fix=20:=20=EC=BD=94=EB=93=9C=20=EC=97=90?= =?UTF-8?q?=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/community_controller.dart | 285 ++++++++++++------ .../home/screens/home_screen.dart | 27 +- .../controllers/post_view_controller.dart | 3 + 3 files changed, 220 insertions(+), 95 deletions(-) diff --git a/lib/presentation/community/controllers/community_controller.dart b/lib/presentation/community/controllers/community_controller.dart index 77725647..8608b604 100644 --- a/lib/presentation/community/controllers/community_controller.dart +++ b/lib/presentation/community/controllers/community_controller.dart @@ -25,16 +25,21 @@ class CommunityController extends GetxController { required LoadRecommendPostListUseCase getRecommendPostsUseCase, required SavePostLikeLocalUseCase savePostLikeLocalUseCase, required GetCachedLikedPostIdsUseCase getCachedLikedPostIdsUseCase, - }) : _likeUseCase = likeUseCase, - _unlikeUseCase = unlikeUseCase, - _getRecommendPostsUseCase = getRecommendPostsUseCase, - _savePostLikeLocalUseCase = savePostLikeLocalUseCase, - _getCachedLikedPostIdsUseCase = getCachedLikedPostIdsUseCase; + }) : _likeUseCase = likeUseCase, + _unlikeUseCase = unlikeUseCase, + _getRecommendPostsUseCase = getRecommendPostsUseCase, + _savePostLikeLocalUseCase = savePostLikeLocalUseCase, + _getCachedLikedPostIdsUseCase = + getCachedLikedPostIdsUseCase; final Set viewTagList = {}.obs; final RxSet selectTagList = {}.obs; - final List _cachePostList = []; - final RxList viewPostList = [].obs; + + final List _cachePostList = + []; + + final RxList viewPostList = + [].obs; final RxBool isLoading = false.obs; final RxBool isLastPage = false.obs; @@ -50,7 +55,9 @@ class CommunityController extends GetxController { } Future _loadCacheAndFetch() async { - _cachedLikedIds = await _getCachedLikedPostIdsUseCase(); + _cachedLikedIds = + await _getCachedLikedPostIdsUseCase(); + await loadRecommendPostList(); } @@ -60,6 +67,13 @@ class CommunityController extends GetxController { super.onReady(); } + bool isPostLiked(int postId) { + return _cachedLikedIds.contains(postId); + } + + Future loadRecommendPostList({ + bool refresh = false, + }) async { if (refresh) { _currentPage = 0; _cachePostList.clear(); @@ -68,62 +82,88 @@ class CommunityController extends GetxController { } if (isLastPage.value) return; + isLoading.value = true; try { - final result = await _getRecommendPostsUseCase( + final result = + await _getRecommendPostsUseCase( page: _currentPage, size: 20, ); - - final applied = result.content.map((post) { - if (_cachedLikedIds.contains(post.postId)) { - return post.copyWith(isFavorite: true); + final applied = + result.content.map((post) { + if (_cachedLikedIds + .contains(post.postId)) { + return post.copyWith( + isFavorite: true, + ); } if (post.isFavorite) { - _cachedLikedIds.add(post.postId); + _cachedLikedIds.add( + post.postId, + ); } + return post; }).toList(); _cachePostList.addAll(applied); - viewPostList.assignAll(_cachePostList); + viewPostList.assignAll( + _cachePostList, + ); - isLastPage.value = result.last ?? true; + isLastPage.value = + result.last ?? true; _currentPage++; } catch (e) { debugPrint( - '[CommunityController] 게시물 조회 실패 - error: $e', + '[CommunityController] ' + '게시물 조회 실패 - error: $e', ); } finally { isLoading.value = false; } } - Future refreshPosts() => loadRecommendPostList(refresh: true); + Future refreshPosts() => + loadRecommendPostList( + refresh: true, + ); void enterPostCreate() { - Get.delete( + Get.delete< + CommunityPostCreateController>( force: true, ); Get.lazyPut( - () => CommunityPostCreateController( - createUseCase: Get.find(), - updateUseCase: Get.find(), + () => CommunityPostCreateController( + createUseCase: + Get.find(), + updateUseCase: + Get.find(), ), ); - Get.to(() => CommunityPostCreateScreen()); + Get.to( + () => CommunityPostCreateScreen(), + ); } Future toggleLike( - - _updatePostLikeInList(postId, isLiked ? 1 : -1, isLiked); + int postId, + bool isLiked, + ) async { + _updatePostLikeInList( + postId, + isLiked ? 1 : -1, + isLiked, + ); try { if (isLiked) { @@ -132,19 +172,27 @@ class CommunityController extends GetxController { await _unlikeUseCase(postId); } - if (isLiked) { _cachedLikedIds.add(postId); } else { _cachedLikedIds.remove(postId); } - await _savePostLikeLocalUseCase(postId, isLiked); + await _savePostLikeLocalUseCase( + postId, + isLiked, + ); + + if (Get.isRegistered< + HomeController>()) { + final post = + viewPostList.firstWhereOrNull( + (p) => p.postId == postId, + ); - if (Get.isRegistered()) { - final post = viewPostList.firstWhereOrNull((p) => p.postId == postId); if (post != null) { - Get.find().updatePostLike( + Get.find() + .updatePostLike( postId, post.likeCount, isLiked, @@ -153,142 +201,197 @@ class CommunityController extends GetxController { } } catch (e) { debugPrint( - '[CommunityController] 좋아요 토글 실패 - error: $e', + '[CommunityController] ' + '좋아요 토글 실패 - error: $e', ); - - _updatePostLikeInList(postId, isLiked ? -1 : 1, !isLiked); + _updatePostLikeInList( + postId, + isLiked ? -1 : 1, + !isLiked, + ); } } void _updatePostLikeInList( - int postId, - int delta, - bool isFavorite, - ) { - final index = viewPostList.indexWhere((p) => p.postId == postId); + int postId, + int delta, + bool isFavorite, + ) { + final index = + viewPostList.indexWhere( + (p) => p.postId == postId, + ); if (index != -1) { - viewPostList[index] = viewPostList[index].copyWith( - likeCount: viewPostList[index].likeCount + delta, - isFavorite: isFavorite, - ); + viewPostList[index] = + viewPostList[index].copyWith( + likeCount: + viewPostList[index] + .likeCount + + delta, + isFavorite: isFavorite, + ); viewPostList.refresh(); } - final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); + final cacheIndex = + _cachePostList.indexWhere( + (p) => p.postId == postId, + ); if (cacheIndex != -1) { - _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( - likeCount: _cachePostList[cacheIndex].likeCount + delta, - isFavorite: isFavorite, - ); + _cachePostList[cacheIndex] = + _cachePostList[cacheIndex] + .copyWith( + likeCount: + _cachePostList[ + cacheIndex] + .likeCount + + delta, + isFavorite: isFavorite, + ); } } Future updatePostLike( - + int postId, + int likeCount, + bool isFavorite, + ) async { if (isFavorite) { _cachedLikedIds.add(postId); } else { _cachedLikedIds.remove(postId); } - await _savePostLikeLocalUseCase(postId, isFavorite); - final index = viewPostList.indexWhere((p) => p.postId == postId); + await _savePostLikeLocalUseCase( + postId, + isFavorite, + ); + + final index = + viewPostList.indexWhere( + (p) => p.postId == postId, + ); if (index != -1) { - viewPostList[index] = viewPostList[index].copyWith( - likeCount: likeCount, - isFavorite: isFavorite, - ); + viewPostList[index] = + viewPostList[index].copyWith( + likeCount: likeCount, + isFavorite: isFavorite, + ); viewPostList.refresh(); } - final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); + final cacheIndex = + _cachePostList.indexWhere( + (p) => p.postId == postId, + ); if (cacheIndex != -1) { - _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( - likeCount: likeCount, - isFavorite: isFavorite, - ); + _cachePostList[cacheIndex] = + _cachePostList[cacheIndex] + .copyWith( + likeCount: likeCount, + isFavorite: isFavorite, + ); } } void removePost(int postId) { viewPostList.removeWhere( - (p) => p.postId == postId, + (p) => p.postId == postId, ); _cachePostList.removeWhere( - (p) => p.postId == postId, + (p) => p.postId == postId, ); } - void searchPost(List searchList) { + void searchPost( + List searchList, + ) { final Set result = {}; result.addAllIf( searchList.isNotEmpty, _cachePostList.where( - (post) => + (post) => + searchList.any( + (search) => post.title + .contains(search), + ) || searchList.any( - (search) => post.title.contains(search), + (search) => post + .authorName + .contains(search), ) || searchList.any( - (search) => post.authorName.contains(search), - ) || - searchList.any( - (search) => post.tags.any( - (tag) => tag.contains(search), + (search) => post.tags.any( + (tag) => tag.contains( + search, + ), ), ), ), ); - Get.find().updateResult(result); + Get.find< + CommunityResultController>() + .updateResult(result); } void filterPostTag( - String tag, - bool isSelect, - ) { - isSelect ? selectTagList.add(tag) : selectTagList.remove(tag); + String tag, + bool isSelect, + ) { + isSelect + ? selectTagList.add(tag) + : selectTagList.remove(tag); if (selectTagList.isEmpty) { - viewPostList.assignAll(_cachePostList); + viewPostList.assignAll( + _cachePostList, + ); return; } final result = _cachePostList .where( (post) => - selectTagList.any( - (t) => post.title.contains(t), - ) || - selectTagList.any( - (t) => post.authorName.contains(t), - ) || - selectTagList.any( + selectTagList.any( + (t) => post.title + .contains(t), + ) || + selectTagList.any( + (t) => post + .authorName + .contains(t), + ) || + selectTagList.any( (t) => post.tags.any( - (skill) => skill.contains(t), - ), - ), - ) + (skill) => skill + .contains(t), + ), + ), + ) .toList(); viewPostList.assignAll(result); } } -class CommunityResultController extends GetxController { - +class CommunityResultController + extends GetxController { + final RxList viewPosts = + [].obs; void updateResult( - Iterable results, - ) { + Iterable results, + ) { viewPosts.assignAll(results); } } @@ -299,4 +402,4 @@ List _getTags() => [ "UIUX", "공부잘하는법", "FrontEnd", -]; +]; \ No newline at end of file diff --git a/lib/presentation/home/screens/home_screen.dart b/lib/presentation/home/screens/home_screen.dart index 63e95d1f..e3bcbc90 100644 --- a/lib/presentation/home/screens/home_screen.dart +++ b/lib/presentation/home/screens/home_screen.dart @@ -21,7 +21,9 @@ class HomeScreen extends GetView { backgroundColor: AppColors.background, body: MainTopSearchBar( pageState: SearchPageState.home, - mainPage: SingleChildScrollView(child: _body()), + mainPage: SingleChildScrollView( + child: _body(), + ), resultPageBuilder: (state) { if (state.keyword.isEmpty) return null; controller.search(state.keyword); @@ -36,20 +38,37 @@ class HomeScreen extends GetView { children: [ HomePostRankList(), AppGap.v16, - HomeProfileList(title: "커피챗 추천", controller: controller), + + HomeProfileList( + title: "커피챗 추천", + controller: controller, + ), + AppGap.v16, - Obx( + Obx( + () => PostGridList( title: "추천 게시물", list: controller.viewPostList .map( (post) => PostItem( + post: post, + isMy: true, + heartAction: (isFavorite, total) { + controller.toggleLike(post.postId, isFavorite); + }, + + bookmarkAction: (isBookmark, total) { + }, + ), + ) .toList(), ), ), + AppGap.v16, ], ); } -} +} \ No newline at end of file diff --git a/lib/presentation/post/controllers/post_view_controller.dart b/lib/presentation/post/controllers/post_view_controller.dart index 76a164c3..023200a8 100644 --- a/lib/presentation/post/controllers/post_view_controller.dart +++ b/lib/presentation/post/controllers/post_view_controller.dart @@ -105,6 +105,9 @@ class PostViewController extends GetxController { bool _resolveIsFavorite() { + bool homeLiked = false; + bool communityLiked = false; + if (Get.isRegistered()) { homeLiked = Get.find() From 28e7b171ddd0f10cdcc48dfa980c9b62dabcb3e4 Mon Sep 17 00:00:00 2001 From: ryusuye0n Date: Thu, 4 Jun 2026 17:31:20 +0900 Subject: [PATCH 3/8] =?UTF-8?q?refactor=20:=20refresh=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/presentation/home/controllers/home_controller.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/presentation/home/controllers/home_controller.dart b/lib/presentation/home/controllers/home_controller.dart index d355bc1b..7a63421e 100644 --- a/lib/presentation/home/controllers/home_controller.dart +++ b/lib/presentation/home/controllers/home_controller.dart @@ -178,9 +178,7 @@ class HomeController extends GetxController with BaseHomeController { viewPostList[index] = updatedPost; - viewPostList.assignAll( - List.from(viewPostList), - ); + viewPostList.refresh(); } final cacheIndex = From 20ad860fa927ef61d56b716fbc42ab1afb841123 Mon Sep 17 00:00:00 2001 From: ryusuye0n Date: Fri, 5 Jun 2026 09:56:43 +0900 Subject: [PATCH 4/8] =?UTF-8?q?refactor:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=83=81=ED=83=9C=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/custom_icon_button.dart | 80 ++--- .../router/bindings/community_binding.dart | 9 +- lib/core/router/bindings/home_binding.dart | 12 +- .../router/bindings/navigation_binding.dart | 19 +- lib/core/router/bindings/post_binding.dart | 50 ++- .../router/bindings/post_view_binding.dart | 52 ++- .../post/post_local_datasource.dart | 16 +- .../post/post_repository_impl.dart | 71 ++-- .../repositories/post/post_repository.dart | 29 +- .../usecases/post/liked_post_use_case.dart | 11 + .../controllers/community_controller.dart | 334 ++++-------------- .../controllers/like_state_controller.dart | 25 ++ .../widgets/community_post_list.dart | 7 +- .../home/controllers/home_controller.dart | 126 ++----- .../home/screens/home_screen.dart | 5 +- .../post/controllers/post_controller.dart | 12 +- .../controllers/post_view_controller.dart | 273 ++++---------- .../post/screens/post_detail_screen.dart | 40 ++- lib/presentation/post/widgets/post_item.dart | 6 +- 19 files changed, 414 insertions(+), 763 deletions(-) create mode 100644 lib/domain/usecases/post/liked_post_use_case.dart create mode 100644 lib/presentation/community/controllers/like_state_controller.dart diff --git a/lib/core/design_system/components/custom_icon_button.dart b/lib/core/design_system/components/custom_icon_button.dart index d9255a3f..7d7131b4 100644 --- a/lib/core/design_system/components/custom_icon_button.dart +++ b/lib/core/design_system/components/custom_icon_button.dart @@ -2,10 +2,17 @@ import 'package:flutter/material.dart'; import 'package:ondo/core/design_system/app_colors.dart'; import 'package:ondo/core/design_system/app_layout.dart'; -typedef FavoriteAction = void Function(bool isFavorite, int total); -typedef BookmarkAction = void Function(bool isBookmark, int total); +typedef FavoriteAction = void Function( + bool isFavorite, + int total, + ); + +typedef BookmarkAction = void Function( + bool isBookmark, + int total, + ); -class CustomIconButton extends StatefulWidget { +class CustomIconButton extends StatelessWidget { const CustomIconButton({ super.key, required this.imagePath, @@ -23,33 +30,10 @@ class CustomIconButton extends StatefulWidget { final int total; final bool initialIsSelected; final Color activeColor; - final void Function(bool isSelect, int total)? action; - - @override - State createState() => _CustomIconButtonState(); -} - -class _CustomIconButtonState extends State { - late int total; - late bool isSelect; - - @override - void initState() { - super.initState(); - total = widget.total; - isSelect = widget.initialIsSelected; - } - - @override - void didUpdateWidget(CustomIconButton oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.total != widget.total) { - total = widget.total; - } - if (oldWidget.initialIsSelected != widget.initialIsSelected) { - isSelect = widget.initialIsSelected; - } - } + final void Function( + bool isSelect, + int total, + )? action; @override Widget build(BuildContext context) { @@ -58,26 +42,38 @@ class _CustomIconButtonState extends State { children: [ GestureDetector( onTap: () { - setState(() { - isSelect = !isSelect; - isSelect ? total += 1 : total -= 1; - }); - widget.action?.call(isSelect, total); + final newIsSelected = + !initialIsSelected; + + final newTotal = + newIsSelected + ? total + 1 + : total - 1; + + action?.call( + newIsSelected, + newTotal, + ); }, child: Image.asset( - widget.imagePath, - color: isSelect ? widget.activeColor : AppColors.gray50, - height: widget.iconSize, - width: widget.iconSize, + imagePath, + color: initialIsSelected + ? activeColor + : AppColors.gray50, + height: iconSize, + width: iconSize, ), ), AppGap.h4, Text( "$total", style: TextStyle( - fontSize: widget.totalStyle.fontSize, - fontWeight: widget.totalStyle.fontWeight, - color: AppColors.gray60, + fontSize: + totalStyle.fontSize, + fontWeight: + totalStyle.fontWeight, + color: + AppColors.gray60, ), ), ], diff --git a/lib/core/router/bindings/community_binding.dart b/lib/core/router/bindings/community_binding.dart index 5957d133..21bce934 100644 --- a/lib/core/router/bindings/community_binding.dart +++ b/lib/core/router/bindings/community_binding.dart @@ -1,7 +1,7 @@ import 'package:get/get.dart'; import 'package:ondo/core/router/bindings/search_binding.dart'; -import 'package:ondo/domain/usecases/post/get_cached_liked_post_ids_use_case.dart'; import 'package:ondo/domain/usecases/post/like_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/liked_post_use_case.dart'; import 'package:ondo/domain/usecases/post/load_recommend_post_list_use_case.dart'; import 'package:ondo/domain/usecases/post/save_post_like_local_use_case.dart'; import 'package:ondo/domain/usecases/post/unlike_post_usecase.dart'; @@ -11,17 +11,16 @@ import 'package:ondo/presentation/search/states/search_page_state.dart'; class CommunityBinding extends Bindings { @override void dependencies() { - /// 검색 관련 의존성 SearchBinding(pageState: SearchPageState.community).dependencies(); Get.lazyPut( - () => CommunityController( + () => CommunityController( likeUseCase: Get.find(), unlikeUseCase: Get.find(), getRecommendPostsUseCase: Get.find(), savePostLikeLocalUseCase: Get.find(), - getCachedLikedPostIdsUseCase: Get.find(), + likedPostUseCase: Get.find(), ), ); } -} +} \ No newline at end of file diff --git a/lib/core/router/bindings/home_binding.dart b/lib/core/router/bindings/home_binding.dart index 20fe4d7a..cd8a46d8 100644 --- a/lib/core/router/bindings/home_binding.dart +++ b/lib/core/router/bindings/home_binding.dart @@ -3,8 +3,8 @@ import 'package:ondo/core/router/bindings/search_binding.dart'; import 'package:ondo/domain/usecases/post/load_recent_popular_post_list_use_case.dart'; import 'package:ondo/domain/usecases/post/load_recommend_post_list_use_case.dart'; import 'package:ondo/domain/usecases/user/load_recommend_users_use_case.dart'; -import 'package:ondo/domain/usecases/post/get_cached_liked_post_ids_use_case.dart'; import 'package:ondo/domain/usecases/post/like_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/liked_post_use_case.dart'; import 'package:ondo/domain/usecases/post/post_search_use_case.dart'; import 'package:ondo/domain/usecases/post/save_post_like_local_use_case.dart'; import 'package:ondo/domain/usecases/post/unlike_post_usecase.dart'; @@ -12,17 +12,15 @@ import 'package:ondo/domain/usecases/user/user_search_use_case.dart'; import 'package:ondo/presentation/home/controllers/home_controller.dart'; import 'package:ondo/presentation/search/states/search_page_state.dart'; - class HomeBinding extends Bindings { @override void dependencies() { SearchBinding(pageState: SearchPageState.home).dependencies(); - ///HomeController 등록 Get.lazyPut( - () => HomeController( + () => HomeController( loadRecentPopularPostListUseCase: - Get.find(), + Get.find(), postSearchUseCase: Get.find(), loadRecommendPostsUseCase: Get.find(), loadRecommendUsersUseCase: Get.find(), @@ -30,8 +28,8 @@ class HomeBinding extends Bindings { likePostUseCase: Get.find(), unlikePostUseCase: Get.find(), savePostLikeLocalUseCase: Get.find(), - getCachedLikedPostIdsUseCase: Get.find(), + likedPostUseCase: Get.find(), ), ); } -} +} \ No newline at end of file diff --git a/lib/core/router/bindings/navigation_binding.dart b/lib/core/router/bindings/navigation_binding.dart index e4d3eddc..ab697069 100644 --- a/lib/core/router/bindings/navigation_binding.dart +++ b/lib/core/router/bindings/navigation_binding.dart @@ -12,23 +12,26 @@ import 'package:ondo/data/datasource/auth/auth_local_datasource_impl.dart'; import 'package:ondo/data/datasource/auth/auth_remote_datasource.dart'; import 'package:ondo/data/datasource/base/auth_local_datasource.dart'; import 'package:ondo/data/network/clients/auth_client.dart'; +import 'package:ondo/presentation/community/controllers/like_state_controller.dart'; import 'package:ondo/presentation/navigation/controllers/navigation_controller.dart'; + class NavigationBinding extends Bindings { @override void dependencies() { Get.lazyPut( - () => AuthRemoteDatasource(Env.apiBaseUrl), + () => AuthRemoteDatasource(Env.apiBaseUrl), fenix: true, ); + Get.lazyPut( - () => AuthLocalDatasourceImpl(), + () => AuthLocalDatasourceImpl(), fenix: true, ); /// 인증 token을 포함하는 client 등록 Get.lazyPut( - () => AuthClient( + () => AuthClient( localDatasource: Get.find(), remoteDatasource: Get.find(), onAuthFailed: () => appRouter.go(RoutePaths.login), @@ -36,8 +39,14 @@ class NavigationBinding extends Bindings { fenix: true, ); + /// 전역 LikeStateController 등록 + Get.put(LikeStateController(), permanent: true); + /// 전 화면 공통 controller 등록 - Get.lazyPut(() => NavigationController()); + Get.lazyPut( + () => NavigationController(), + ); + NotificationBinding().dependencies(); UserBinding().dependencies(); @@ -49,4 +58,4 @@ class NavigationBinding extends Bindings { ChatBinding().dependencies(); ProfileBinding().dependencies(); } -} +} \ No newline at end of file diff --git a/lib/core/router/bindings/post_binding.dart b/lib/core/router/bindings/post_binding.dart index 865b63b2..7ca361c6 100644 --- a/lib/core/router/bindings/post_binding.dart +++ b/lib/core/router/bindings/post_binding.dart @@ -4,8 +4,8 @@ import 'package:ondo/data/datasource/post/post_remote_datasource.dart'; import 'package:ondo/data/network/clients/auth_client.dart'; import 'package:ondo/data/repositories/post/post_repository_impl.dart'; import 'package:ondo/domain/usecases/post/bookmark_post_usecase.dart'; -import 'package:ondo/domain/usecases/post/get_cached_liked_post_ids_use_case.dart'; import 'package:ondo/domain/usecases/post/like_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/liked_post_use_case.dart'; // ← 추가 import 'package:ondo/domain/usecases/post/load_recent_popular_post_list_use_case.dart'; import 'package:ondo/domain/usecases/post/load_recommend_post_list_use_case.dart'; import 'package:ondo/domain/usecases/post/post_search_use_case.dart'; @@ -18,68 +18,58 @@ class PostBinding extends Bindings { @override void dependencies() { Get.lazyPut( - () => PostRemoteDatasource( - Get.find(), - ), + () => PostRemoteDatasource(Get.find()), ); Get.lazyPut( - () => PostLocalDatasource(), + () => PostLocalDatasource(), ); - // Post Repository Get.lazyPut( - () => PostRepositoryImpl( + () => PostRepositoryImpl( Get.find(), Get.find(), ), ); - Get.lazyPut( - () => LikePostUseCase( - Get.find(), - ), + + Get.lazyPut( // ← 서버 좋아요 요청 + () => LikePostUseCase(Get.find()), + ); + + Get.lazyPut( // ← 로컬 캐시 확인 + () => LikedPostUseCase(Get.find()), ); Get.lazyPut( - () => UnlikePostUseCase( - Get.find(), - ), + () => UnlikePostUseCase(Get.find()), ); - // 추가된 북마크 UseCase Get.lazyPut( - () => BookmarkPostUseCase( - Get.find(), - ), + () => BookmarkPostUseCase(Get.find()), ); Get.lazyPut( - () => UnbookmarkPostUseCase( - Get.find(), - ), + () => UnbookmarkPostUseCase(Get.find()), ); Get.lazyPut( - () => PostSearchUseCase(Get.find()), + () => PostSearchUseCase(Get.find()), ); Get.lazyPut( - () => SavePostLikeLocalUseCase(Get.find()), - ); - Get.lazyPut( - () => GetCachedLikedPostIdsUseCase(Get.find()), + () => SavePostLikeLocalUseCase(Get.find()), ); Get.lazyPut( - () => LoadRecommendPostListUseCase(Get.find()), + () => LoadRecommendPostListUseCase(Get.find()), ); Get.lazyPut( - () => LoadRecentPopularPostListUseCase(Get.find()), + () => LoadRecentPopularPostListUseCase(Get.find()), ); Get.lazyPut( - () => PostController(), + () => PostController(), ); } -} +} \ No newline at end of file diff --git a/lib/core/router/bindings/post_view_binding.dart b/lib/core/router/bindings/post_view_binding.dart index f9cfe262..a072033c 100644 --- a/lib/core/router/bindings/post_view_binding.dart +++ b/lib/core/router/bindings/post_view_binding.dart @@ -2,6 +2,8 @@ import 'package:get/get.dart'; import 'package:ondo/domain/usecases/comment/create_comment_usecase.dart'; import 'package:ondo/domain/usecases/comment/delete_comment_usecase.dart'; import 'package:ondo/domain/usecases/comment/get_comments_usecase.dart'; +import 'package:ondo/domain/usecases/post/liked_post_use_case.dart'; +import 'package:ondo/domain/usecases/post/save_post_like_local_use_case.dart'; import '../../../data/datasource/comment/comment_remote_datasource.dart'; import '../../../data/network/clients/auth_client.dart'; @@ -24,75 +26,61 @@ class PostViewBinding extends Bindings { final bool isFavorite; PostViewBinding( - this.postId, [ - this.isFavorite = false, - ]); + this.postId, [ + this.isFavorite = false, + ]); @override void dependencies() { - // Post UseCases Get.lazyPut( - () => GetPostDetailUseCase( - Get.find(), - ), + () => GetPostDetailUseCase(Get.find()), ); Get.lazyPut( - () => CreatePostUseCase( - Get.find(), - ), + () => CreatePostUseCase(Get.find()), ); Get.lazyPut( - () => UpdatePostUseCase( - Get.find(), - ), + () => UpdatePostUseCase(Get.find()), ); Get.lazyPut( - () => DeletePostUseCase( - Get.find(), - ), + () => DeletePostUseCase(Get.find()), ); + Get.lazyPut( + () => SavePostLikeLocalUseCase(Get.find()), + ); // Comment DataSource Get.lazyPut( - () => CommentRemoteDataSourceImpl( - Get.find(), - ), + () => CommentRemoteDataSourceImpl(Get.find()), ); // Comment Repository Get.lazyPut( - () => CommentRepositoryImpl( + () => CommentRepositoryImpl( remoteDataSource: Get.find(), ), ); // Comment UseCases Get.lazyPut( - () => GetCommentsUseCase( - Get.find(), - ), + () => GetCommentsUseCase(Get.find()), ); Get.lazyPut( - () => CreateCommentUseCase( - Get.find(), - ), + () => CreateCommentUseCase(Get.find()), ); Get.lazyPut( - () => DeleteCommentUseCase( - Get.find(), - ), + () => DeleteCommentUseCase(Get.find()), ); // Controller Get.lazyPut( - () => PostViewController( + () => PostViewController( postId: postId, getPostDetailUseCase: Get.find(), updatePostUseCase: Get.find(), @@ -104,7 +92,9 @@ class PostViewBinding extends Bindings { getCommentsUseCase: Get.find(), createCommentUseCase: Get.find(), deleteCommentUseCase: Get.find(), + likedPostUseCase: Get.find(), + savePostLikeLocalUseCase: Get.find(), ), ); } -} +} \ No newline at end of file diff --git a/lib/data/datasource/post/post_local_datasource.dart b/lib/data/datasource/post/post_local_datasource.dart index eba8fc9a..ae25a130 100644 --- a/lib/data/datasource/post/post_local_datasource.dart +++ b/lib/data/datasource/post/post_local_datasource.dart @@ -4,6 +4,7 @@ class PostLocalDatasource { static const String _likedPostsKey = 'liked_post_ids'; SharedPreferences? _prefs; + Set? _cachedLikedIds; Future _getPrefs() async { return _prefs ??= await SharedPreferences.getInstance(); @@ -19,6 +20,8 @@ class PostLocalDatasource { likedIds.remove(postId); } + _cachedLikedIds = likedIds; + await prefs.setStringList( _likedPostsKey, likedIds.map((id) => id.toString()).toList(), @@ -26,8 +29,17 @@ class PostLocalDatasource { } Future> getLikedPostIds() async { + if (_cachedLikedIds != null) return _cachedLikedIds!; + final prefs = await _getPrefs(); final ids = prefs.getStringList(_likedPostsKey); - return (ids ?? []).map((id) => int.tryParse(id)).whereType().toSet(); + _cachedLikedIds = + (ids ?? []).map((id) => int.tryParse(id)).whereType().toSet(); + return _cachedLikedIds!; + } + + Future likedPost(int postId) async { + final ids = await getLikedPostIds(); + return ids.contains(postId); } -} +} \ No newline at end of file diff --git a/lib/data/repositories/post/post_repository_impl.dart b/lib/data/repositories/post/post_repository_impl.dart index 9c5d59a1..89c3bdf1 100644 --- a/lib/data/repositories/post/post_repository_impl.dart +++ b/lib/data/repositories/post/post_repository_impl.dart @@ -23,29 +23,17 @@ class PostRepositoryImpl implements PostRepository { @override Future> loadRecentPopularPostList() async { final json = await _remoteDatasource.getRecentPopularPostList(); - final data = - json - ?.map( - (e) => PostRankModel.fromJson(e), - ) - .toList() ?? - []; - - return data - .map( - (e) => PostRankEntity.fromPostRankModel(e), - ) - .toList(); + final data = json?.map((e) => PostRankModel.fromJson(e)).toList() ?? []; + return data.map((e) => PostRankEntity.fromPostRankModel(e)).toList(); } @override Future> loadRecommendPostList( - int page, - int size, - ) async { + int page, + int size, + ) async { final model = ListRequestModelBasePage(size: size, page: page); final json = await _remoteDatasource.getRecommendPostList(model); - final data = PostDataModel.fromJson(json); return ListableWrapper( @@ -54,11 +42,7 @@ class PostRepositoryImpl implements PostRepository { totalElements: data.totalElements, totalPages: data.totalPages, last: data.last, - content: data.content - .map( - (e) => PostEntity.fromPostModel(e), - ) - .toList(), + content: data.content.map((e) => PostEntity.fromPostModel(e)).toList(), ); } @@ -94,6 +78,16 @@ class PostRepositoryImpl implements PostRepository { return _remoteDatasource.unlikePost(postId); } + @override + Future bookmarkPost(int postId) { + return _remoteDatasource.bookmarkPost(postId); + } + + @override + Future unbookmarkPost(int postId) { + return _remoteDatasource.unbookmarkPost(postId); + } + @override Future> getCachedLikedPostIds() { return _localDatasource.getLikedPostIds(); @@ -105,24 +99,19 @@ class PostRepositoryImpl implements PostRepository { } @override - Future bookmarkPost(int postId) { - return _remoteDatasource.bookmarkPost(postId); - } - - @override - Future unbookmarkPost(int postId) { - return _remoteDatasource.unbookmarkPost(postId); + Future likedPost(int postId) { + return _localDatasource.likedPost(postId); } @override Future> search( - String keyword, - List? tags, - String? sort, - bool? latest, - int? page, - int? size, - ) async { + String keyword, + List? tags, + String? sort, + bool? latest, + int? page, + int? size, + ) async { final model = PostSearchRequestModel( keyword: keyword, tags: tags, @@ -132,22 +121,16 @@ class PostRepositoryImpl implements PostRepository { size: size, ); final json = await _remoteDatasource.search(model); - if (json == null) return ListableWrapper.none(); final data = PostDataModel.fromJson(json); - return ListableWrapper( page: data.page, size: data.size, totalElements: data.totalElements, totalPages: data.totalPages, last: data.last, - content: data.content - .map( - (e) => PostEntity.fromPostModel(e), - ) - .toList(), + content: data.content.map((e) => PostEntity.fromPostModel(e)).toList(), ); } -} +} \ No newline at end of file diff --git a/lib/domain/repositories/post/post_repository.dart b/lib/domain/repositories/post/post_repository.dart index 106ecebb..45c9d176 100644 --- a/lib/domain/repositories/post/post_repository.dart +++ b/lib/domain/repositories/post/post_repository.dart @@ -15,10 +15,7 @@ abstract class PostRepository { Future createPost(PostCreateRequestModel model); - Future updatePost( - int postId, - PostUpdateRequestModel model, - ); + Future updatePost(int postId, PostUpdateRequestModel model); Future deletePost(int postId); @@ -30,20 +27,18 @@ abstract class PostRepository { Future unbookmarkPost(int postId); - /// 로컬 좋아요 캐시 Future> getCachedLikedPostIds(); - Future saveLikeState( - int postId, - bool isLiked, - ); + Future saveLikeState(int postId, bool isLiked); + + Future likedPost(int postId); Future> search( - String keyword, - List? tags, - String? sort, - bool? latest, - int? page, - int? size, - ); -} + String keyword, + List? tags, + String? sort, + bool? latest, + int? page, + int? size, + ); +} \ No newline at end of file diff --git a/lib/domain/usecases/post/liked_post_use_case.dart b/lib/domain/usecases/post/liked_post_use_case.dart new file mode 100644 index 00000000..5c51739f --- /dev/null +++ b/lib/domain/usecases/post/liked_post_use_case.dart @@ -0,0 +1,11 @@ +import 'package:ondo/domain/repositories/post/post_repository.dart'; + +class LikedPostUseCase { + final PostRepository _repository; + + LikedPostUseCase(this._repository); + + Future call(int postId) { + return _repository.likedPost(postId); + } +} \ No newline at end of file diff --git a/lib/presentation/community/controllers/community_controller.dart b/lib/presentation/community/controllers/community_controller.dart index 8608b604..b8b62be3 100644 --- a/lib/presentation/community/controllers/community_controller.dart +++ b/lib/presentation/community/controllers/community_controller.dart @@ -2,63 +2,70 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:ondo/domain/entities/post/post_entity.dart'; import 'package:ondo/domain/usecases/post/create_post_usecase.dart'; -import 'package:ondo/domain/usecases/post/get_cached_liked_post_ids_use_case.dart'; import 'package:ondo/domain/usecases/post/load_recommend_post_list_use_case.dart'; import 'package:ondo/domain/usecases/post/like_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/liked_post_use_case.dart'; import 'package:ondo/domain/usecases/post/save_post_like_local_use_case.dart'; import 'package:ondo/domain/usecases/post/unlike_post_usecase.dart'; import 'package:ondo/domain/usecases/post/update_post_usecase.dart'; import 'package:ondo/presentation/community/controllers/community_post_create_screen_controller.dart'; +import 'package:ondo/presentation/community/controllers/like_state_controller.dart'; import 'package:ondo/presentation/community/screens/community_post_create_screen.dart'; -import 'package:ondo/presentation/home/controllers/home_controller.dart'; class CommunityController extends GetxController { final LikePostUseCase _likeUseCase; final UnlikePostUseCase _unlikeUseCase; final LoadRecommendPostListUseCase _getRecommendPostsUseCase; final SavePostLikeLocalUseCase _savePostLikeLocalUseCase; - final GetCachedLikedPostIdsUseCase _getCachedLikedPostIdsUseCase; + final LikedPostUseCase _likedPostUseCase; CommunityController({ required LikePostUseCase likeUseCase, required UnlikePostUseCase unlikeUseCase, required LoadRecommendPostListUseCase getRecommendPostsUseCase, required SavePostLikeLocalUseCase savePostLikeLocalUseCase, - required GetCachedLikedPostIdsUseCase getCachedLikedPostIdsUseCase, + required LikedPostUseCase likedPostUseCase, }) : _likeUseCase = likeUseCase, _unlikeUseCase = unlikeUseCase, _getRecommendPostsUseCase = getRecommendPostsUseCase, _savePostLikeLocalUseCase = savePostLikeLocalUseCase, - _getCachedLikedPostIdsUseCase = - getCachedLikedPostIdsUseCase; + _likedPostUseCase = likedPostUseCase; - final Set viewTagList = {}.obs; + final RxSet viewTagList = {}.obs; final RxSet selectTagList = {}.obs; - final List _cachePostList = - []; - - final RxList viewPostList = - [].obs; + final List _cachePostList = []; + final RxList viewPostList = [].obs; final RxBool isLoading = false.obs; final RxBool isLastPage = false.obs; int _currentPage = 0; - Set _cachedLikedIds = {}; @override void onInit() { super.onInit(); viewTagList.addAll(_getTags()); - _loadCacheAndFetch(); + loadRecommendPostList(); + + ever( + Get.find().lastEvent, + (event) { + if (event == null) return; + _syncLike(event.postId, event.isLiked, event.likeCount); + }, + ); } - Future _loadCacheAndFetch() async { - _cachedLikedIds = - await _getCachedLikedPostIdsUseCase(); - - await loadRecommendPostList(); + void _syncLike(int postId, bool isLiked, int likeCount) { + final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); + if (cacheIndex != -1) { + _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( + likeCount: likeCount, + isFavorite: isLiked, + ); + viewPostList.assignAll(_cachePostList); + } } @override @@ -67,13 +74,7 @@ class CommunityController extends GetxController { super.onReady(); } - bool isPostLiked(int postId) { - return _cachedLikedIds.contains(postId); - } - - Future loadRecommendPostList({ - bool refresh = false, - }) async { + Future loadRecommendPostList({bool refresh = false}) async { if (refresh) { _currentPage = 0; _cachePostList.clear(); @@ -86,84 +87,50 @@ class CommunityController extends GetxController { isLoading.value = true; try { - final result = - await _getRecommendPostsUseCase( + final result = await _getRecommendPostsUseCase( page: _currentPage, size: 20, ); - final applied = - result.content.map((post) { - if (_cachedLikedIds - .contains(post.postId)) { + final applied = await Future.wait( + result.content.map((post) async { return post.copyWith( - isFavorite: true, - ); - } - - if (post.isFavorite) { - _cachedLikedIds.add( - post.postId, + isFavorite: await _likedPostUseCase(post.postId), ); - } - - return post; - }).toList(); - - _cachePostList.addAll(applied); - - viewPostList.assignAll( - _cachePostList, + }), ); - isLastPage.value = - result.last ?? true; + _cachePostList.addAll(applied); + viewPostList.assignAll(_cachePostList); + isLastPage.value = result.last ?? true; _currentPage++; } catch (e) { - debugPrint( - '[CommunityController] ' - '게시물 조회 실패 - error: $e', - ); + debugPrint('[CommunityController] 게시물 조회 실패 - error: $e'); } finally { isLoading.value = false; } } - Future refreshPosts() => - loadRecommendPostList( - refresh: true, - ); + Future refresh() async { + await loadRecommendPostList(refresh: true); + } - void enterPostCreate() { - Get.delete< - CommunityPostCreateController>( - force: true, - ); + Future refreshPosts() => loadRecommendPostList(refresh: true); + void enterPostCreate() { + Get.delete(force: true); Get.lazyPut( () => CommunityPostCreateController( - createUseCase: - Get.find(), - updateUseCase: - Get.find(), + createUseCase: Get.find(), + updateUseCase: Get.find(), ), ); - - Get.to( - () => CommunityPostCreateScreen(), - ); + Get.to(() => CommunityPostCreateScreen()); } - Future toggleLike( - int postId, - bool isLiked, - ) async { - _updatePostLikeInList( - postId, - isLiked ? 1 : -1, - isLiked, - ); + Future toggleLike(int postId, bool isLiked) async { + _updatePostLikeInList(postId, isLiked ? 1 : -1, isLiked); try { if (isLiked) { @@ -171,211 +138,68 @@ class CommunityController extends GetxController { } else { await _unlikeUseCase(postId); } - - if (isLiked) { - _cachedLikedIds.add(postId); - } else { - _cachedLikedIds.remove(postId); - } - - await _savePostLikeLocalUseCase( - postId, - isLiked, - ); - - if (Get.isRegistered< - HomeController>()) { - final post = - viewPostList.firstWhereOrNull( - (p) => p.postId == postId, - ); - - if (post != null) { - Get.find() - .updatePostLike( - postId, - post.likeCount, - isLiked, - ); - } - } + await _savePostLikeLocalUseCase(postId, isLiked); } catch (e) { - debugPrint( - '[CommunityController] ' - '좋아요 토글 실패 - error: $e', - ); - - _updatePostLikeInList( - postId, - isLiked ? -1 : 1, - !isLiked, - ); + debugPrint('[CommunityController] 좋아요 토글 실패 - error: $e'); + _updatePostLikeInList(postId, isLiked ? -1 : 1, !isLiked); } } - void _updatePostLikeInList( - int postId, - int delta, - bool isFavorite, - ) { - final index = - viewPostList.indexWhere( - (p) => p.postId == postId, - ); - - if (index != -1) { - viewPostList[index] = - viewPostList[index].copyWith( - likeCount: - viewPostList[index] - .likeCount + - delta, - isFavorite: isFavorite, - ); - - viewPostList.refresh(); - } - - final cacheIndex = - _cachePostList.indexWhere( - (p) => p.postId == postId, - ); - - if (cacheIndex != -1) { - _cachePostList[cacheIndex] = - _cachePostList[cacheIndex] - .copyWith( - likeCount: - _cachePostList[ - cacheIndex] - .likeCount + - delta, - isFavorite: isFavorite, - ); - } - } - - Future updatePostLike( - int postId, - int likeCount, - bool isFavorite, - ) async { - if (isFavorite) { - _cachedLikedIds.add(postId); - } else { - _cachedLikedIds.remove(postId); - } - - await _savePostLikeLocalUseCase( - postId, - isFavorite, - ); - - final index = - viewPostList.indexWhere( - (p) => p.postId == postId, - ); - + void _updatePostLikeInList(int postId, int delta, bool isFavorite) { + final index = viewPostList.indexWhere((p) => p.postId == postId); if (index != -1) { - viewPostList[index] = - viewPostList[index].copyWith( - likeCount: likeCount, - isFavorite: isFavorite, - ); - + viewPostList[index] = viewPostList[index].copyWith( + likeCount: viewPostList[index].likeCount + delta, + isFavorite: isFavorite, + ); viewPostList.refresh(); } - final cacheIndex = - _cachePostList.indexWhere( - (p) => p.postId == postId, - ); - + final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); if (cacheIndex != -1) { - _cachePostList[cacheIndex] = - _cachePostList[cacheIndex] - .copyWith( - likeCount: likeCount, - isFavorite: isFavorite, - ); + _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( + likeCount: _cachePostList[cacheIndex].likeCount + delta, + isFavorite: isFavorite, + ); } } void removePost(int postId) { - viewPostList.removeWhere( - (p) => p.postId == postId, - ); - - _cachePostList.removeWhere( - (p) => p.postId == postId, - ); + viewPostList.removeWhere((p) => p.postId == postId); + _cachePostList.removeWhere((p) => p.postId == postId); } - void searchPost( - List searchList, - ) { + void searchPost(List searchList) { final Set result = {}; - result.addAllIf( searchList.isNotEmpty, _cachePostList.where( (post) => - searchList.any( - (search) => post.title - .contains(search), - ) || - searchList.any( - (search) => post - .authorName - .contains(search), - ) || + searchList.any((search) => post.title.contains(search)) || + searchList.any((search) => post.authorName.contains(search)) || searchList.any( - (search) => post.tags.any( - (tag) => tag.contains( - search, - ), - ), + (search) => post.tags.any((tag) => tag.contains(search)), ), ), ); - - Get.find< - CommunityResultController>() - .updateResult(result); + Get.find().updateResult(result); } - void filterPostTag( - String tag, - bool isSelect, - ) { - isSelect - ? selectTagList.add(tag) - : selectTagList.remove(tag); + void filterPostTag(String tag, bool isSelect) { + isSelect ? selectTagList.add(tag) : selectTagList.remove(tag); if (selectTagList.isEmpty) { - viewPostList.assignAll( - _cachePostList, - ); + viewPostList.assignAll(_cachePostList); return; } final result = _cachePostList .where( (post) => - selectTagList.any( - (t) => post.title - .contains(t), - ) || + selectTagList.any((t) => post.title.contains(t)) || + selectTagList.any((t) => post.authorName.contains(t)) || selectTagList.any( - (t) => post - .authorName - .contains(t), - ) || - selectTagList.any( - (t) => post.tags.any( - (skill) => skill - .contains(t), - ), + (t) => post.tags.any((skill) => skill.contains(t)), ), ) .toList(); @@ -384,14 +208,10 @@ class CommunityController extends GetxController { } } -class CommunityResultController - extends GetxController { - final RxList viewPosts = - [].obs; +class CommunityResultController extends GetxController { + final RxList viewPosts = [].obs; - void updateResult( - Iterable results, - ) { + void updateResult(Iterable results) { viewPosts.assignAll(results); } } diff --git a/lib/presentation/community/controllers/like_state_controller.dart b/lib/presentation/community/controllers/like_state_controller.dart new file mode 100644 index 00000000..c67f16f2 --- /dev/null +++ b/lib/presentation/community/controllers/like_state_controller.dart @@ -0,0 +1,25 @@ +import 'package:get/get.dart'; + +class LikeChangedEvent { + final int postId; + final bool isLiked; + final int likeCount; + + LikeChangedEvent({ + required this.postId, + required this.isLiked, + required this.likeCount, + }); +} + +class LikeStateController extends GetxController { + final Rx lastEvent = Rx(null); + + void updateLikeState(int postId, bool isLiked, int likeCount) { + lastEvent.value = LikeChangedEvent( + postId: postId, + isLiked: isLiked, + likeCount: likeCount, + ); + } +} \ No newline at end of file diff --git a/lib/presentation/community/widgets/community_post_list.dart b/lib/presentation/community/widgets/community_post_list.dart index 35c2db67..8e076892 100644 --- a/lib/presentation/community/widgets/community_post_list.dart +++ b/lib/presentation/community/widgets/community_post_list.dart @@ -10,13 +10,14 @@ class CommunityPostList extends GetView { @override Widget build(BuildContext context) { return Obx( - () => IndicatorPostPageList( + () => IndicatorPostPageList( title: controller.selectTagList.isEmpty ? "게시물 목록" : "태그 분류 결과", items: List.generate( controller.viewPostList.length, - (index) { + (index) { final post = controller.viewPostList[index]; return PostItem( + key: ValueKey('${post.postId}-${post.likeCount}-${post.isFavorite}'), post: post, isMy: true, heartAction: (isLiked, total) { @@ -29,4 +30,4 @@ class CommunityPostList extends GetView { ), ); } -} +} \ No newline at end of file diff --git a/lib/presentation/home/controllers/home_controller.dart b/lib/presentation/home/controllers/home_controller.dart index 7a63421e..8cbe2b0a 100644 --- a/lib/presentation/home/controllers/home_controller.dart +++ b/lib/presentation/home/controllers/home_controller.dart @@ -5,14 +5,14 @@ import 'package:ondo/domain/entities/post/post_rank_entity.dart'; import 'package:ondo/domain/entities/user/user_entity.dart'; import 'package:ondo/domain/usecases/post/load_recommend_post_list_use_case.dart'; import 'package:ondo/domain/usecases/user/load_recommend_users_use_case.dart'; -import 'package:ondo/domain/usecases/post/get_cached_liked_post_ids_use_case.dart'; +import 'package:ondo/domain/usecases/post/liked_post_use_case.dart'; import 'package:ondo/domain/usecases/post/like_post_usecase.dart'; import 'package:ondo/domain/usecases/post/load_recent_popular_post_list_use_case.dart'; import 'package:ondo/domain/usecases/post/save_post_like_local_use_case.dart'; import 'package:ondo/domain/usecases/post/post_search_use_case.dart'; import 'package:ondo/domain/usecases/post/unlike_post_usecase.dart'; import 'package:ondo/domain/usecases/user/user_search_use_case.dart'; -import 'package:ondo/presentation/community/controllers/community_controller.dart'; +import 'package:ondo/presentation/community/controllers/like_state_controller.dart'; import 'package:ondo/presentation/home/controllers/base_home_controller.dart'; class HomeController extends GetxController with BaseHomeController { @@ -20,7 +20,6 @@ class HomeController extends GetxController with BaseHomeController { final List _cachePostList = []; final List _cacheProfileList = []; - ///usecase 모음 final LoadRecommendPostListUseCase loadRecommendPostsUseCase; final LoadRecommendUsersUseCase loadRecommendUsersUseCase; final UserSearchUseCase userSearchUseCase; @@ -28,11 +27,9 @@ class HomeController extends GetxController with BaseHomeController { final LikePostUseCase likePostUseCase; final UnlikePostUseCase unlikePostUseCase; final SavePostLikeLocalUseCase savePostLikeLocalUseCase; - final GetCachedLikedPostIdsUseCase getCachedLikedPostIdsUseCase; + final LikedPostUseCase likedPostUseCase; final LoadRecentPopularPostListUseCase loadRecentPopularPostListUseCase; - Set _cachedLikedIds = {}; - final searchResultController = HomeSearchResultController(); HomeController({ @@ -42,7 +39,7 @@ class HomeController extends GetxController with BaseHomeController { required this.likePostUseCase, required this.unlikePostUseCase, required this.savePostLikeLocalUseCase, - required this.getCachedLikedPostIdsUseCase, + required this.likedPostUseCase, required this.postSearchUseCase, required this.loadRecentPopularPostListUseCase, }); @@ -56,15 +53,32 @@ class HomeController extends GetxController with BaseHomeController { _loadRecentPopularPostList(); _loadRecommendPostList(refresh: true); loadRecommendUsers(); + + ever( + Get.find().lastEvent, + (event) { + if (event == null) return; + _syncLike(event.postId, event.isLiked, event.likeCount); + }, + ); + } + + void _syncLike(int postId, bool isLiked, int likeCount) { + final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); + if (cacheIndex != -1) { + _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( + likeCount: likeCount, + isFavorite: isLiked, + ); + viewPostList.assignAll(_cachePostList); + } } Future _loadRecentPopularPostList() async { final result = await loadRecentPopularPostListUseCase(); if (result.isEmpty) return; recentPopularPostList.assignAll(result); - recentPopularPostList.sort( - (a, b) => a.rank - b.rank, - ); + recentPopularPostList.sort((a, b) => a.rank - b.rank); } Future _loadRecommendPostList({ @@ -84,30 +98,23 @@ class HomeController extends GetxController with BaseHomeController { ); isLast = result.last ?? true; - // 로컬 캐시에 저장된 좋아요 누른 게시물 id 불러오기 - if (_cachedLikedIds.isEmpty && _cachePostList.isEmpty) { - _cachedLikedIds = await getCachedLikedPostIdsUseCase(); - } - - // 로컬 캐시 기반으로 isFavorite 보정 (앱 재시작 후에도 유지) - final applied = result.content.map((post) { - if (_cachedLikedIds.contains(post.postId)) { - return post.copyWith(isFavorite: true); - } - return post; - }).toList(); + final applied = await Future.wait( + result.content.map((post) async { + return post.copyWith( + isFavorite: await likedPostUseCase(post.postId), + ); + }), + ); _cachePostList.addAll(applied); viewPostList.assignAll(_cachePostList); } - - bool isPostLiked(int postId) { - return _cachedLikedIds.contains(postId); + Future refresh() async { + await _loadRecommendPostList(refresh: true); } Future toggleLike(int postId, bool isLiked) async { - _updatePostLikeInList(postId, isLiked ? 1 : -1, isLiked); try { @@ -116,25 +123,7 @@ class HomeController extends GetxController with BaseHomeController { } else { await unlikePostUseCase(postId); } - - if (isLiked) { - _cachedLikedIds.add(postId); - } else { - _cachedLikedIds.remove(postId); - } await savePostLikeLocalUseCase(postId, isLiked); - - - if (Get.isRegistered()) { - final post = viewPostList.firstWhereOrNull((p) => p.postId == postId); - if (post != null) { - Get.find().updatePostLike( - postId, - post.likeCount, - isLiked, - ); - } - } } catch (e) { debugPrint('[HomeController] 좋아요 토글 실패 - error: $e'); _updatePostLikeInList(postId, isLiked ? -1 : 1, !isLiked); @@ -152,49 +141,6 @@ class HomeController extends GetxController with BaseHomeController { viewPostList.assignAll(_cachePostList); } - void updatePostLike( - int postId, - int likeCount, - bool isFavorite, - ) { - - if (isFavorite) { - _cachedLikedIds.add(postId); - } else { - _cachedLikedIds.remove(postId); - } - - final index = viewPostList.indexWhere( - (p) => p.postId == postId, - ); - - if (index != -1) { - final updatedPost = - viewPostList[index].copyWith( - likeCount: likeCount, - isFavorite: isFavorite, - ); - - viewPostList[index] = updatedPost; - - - viewPostList.refresh(); - } - - final cacheIndex = - _cachePostList.indexWhere( - (p) => p.postId == postId, - ); - - if (cacheIndex != -1) { - _cachePostList[cacheIndex] = - _cachePostList[cacheIndex].copyWith( - likeCount: likeCount, - isFavorite: isFavorite, - ); - } - } - Future loadRecommendUsers() async { _cacheProfileList.clear(); _cacheProfileList.addAll(await loadRecommendUsersUseCase.call()); @@ -204,8 +150,6 @@ class HomeController extends GetxController with BaseHomeController { void search(String query) async { final Set userRes = {}; - ///서버 유저 검색 api에서 user결과 실시간 표시 - // TODO 구조 변경 userRes.addAll( await userSearchUseCase.call(keyword: query), ); @@ -215,7 +159,6 @@ class HomeController extends GetxController with BaseHomeController { sort: "latest", ); - ///홈 검색 결과 표시 controller searchResultController.updateResult( postResult.content, userRes, @@ -225,7 +168,6 @@ class HomeController extends GetxController with BaseHomeController { class HomeSearchResultController extends GetxController with BaseHomeController { - ///홈 검색 결과 업데이트 void updateResult( Iterable posts, Iterable profiles, @@ -233,4 +175,4 @@ class HomeSearchResultController extends GetxController viewUserList.assignAll(profiles); viewPostList.assignAll(posts); } -} +} \ No newline at end of file diff --git a/lib/presentation/home/screens/home_screen.dart b/lib/presentation/home/screens/home_screen.dart index e3bcbc90..2a5e9ae4 100644 --- a/lib/presentation/home/screens/home_screen.dart +++ b/lib/presentation/home/screens/home_screen.dart @@ -54,13 +54,10 @@ class HomeScreen extends GetView { (post) => PostItem( post: post, isMy: true, - heartAction: (isFavorite, total) { controller.toggleLike(post.postId, isFavorite); }, - - bookmarkAction: (isBookmark, total) { - }, + bookmarkAction: (isBookmark, total) {}, ), ) .toList(), diff --git a/lib/presentation/post/controllers/post_controller.dart b/lib/presentation/post/controllers/post_controller.dart index 18386710..9fc38d8a 100644 --- a/lib/presentation/post/controllers/post_controller.dart +++ b/lib/presentation/post/controllers/post_controller.dart @@ -3,12 +3,16 @@ import 'package:get/get.dart'; import 'package:go_router/go_router.dart'; class PostController extends GetxController { - void enterPostDetail( + Future?> enterPostDetail( BuildContext context, bool isMy, int postId, { bool isFavorite = false, - }) { - context.push('/post/$postId', extra: isFavorite); + }) async { + final result = await context.push>( + '/post/$postId', + extra: isFavorite, + ); + return result; } -} +} \ No newline at end of file diff --git a/lib/presentation/post/controllers/post_view_controller.dart b/lib/presentation/post/controllers/post_view_controller.dart index 023200a8..10b7a4fc 100644 --- a/lib/presentation/post/controllers/post_view_controller.dart +++ b/lib/presentation/post/controllers/post_view_controller.dart @@ -8,9 +8,10 @@ import 'package:ondo/domain/usecases/comment/create_comment_usecase.dart'; import 'package:ondo/domain/usecases/comment/delete_comment_usecase.dart'; import 'package:ondo/domain/usecases/comment/get_comments_usecase.dart'; import 'package:ondo/domain/usecases/post/bookmark_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/liked_post_use_case.dart'; +import 'package:ondo/domain/usecases/post/save_post_like_local_use_case.dart'; import 'package:ondo/domain/usecases/post/unbookmark_post_usecase.dart'; -import 'package:ondo/presentation/community/controllers/community_controller.dart'; -import 'package:ondo/presentation/home/controllers/home_controller.dart'; +import 'package:ondo/presentation/community/controllers/like_state_controller.dart'; import '../../../data/models/post/request/post_update_request_model.dart'; import '../../../domain/usecases/post/create_post_usecase.dart'; @@ -37,6 +38,8 @@ class PostViewController extends GetxController { final GetCommentsUseCase _getCommentsUseCase; final CreateCommentUseCase _createCommentUseCase; final DeleteCommentUseCase _deleteCommentUseCase; + final LikedPostUseCase _likedPostUseCase; + final SavePostLikeLocalUseCase _savePostLikeLocalUseCase; PostViewController({ required this.postId, @@ -51,7 +54,9 @@ class PostViewController extends GetxController { required GetCommentsUseCase getCommentsUseCase, required CreateCommentUseCase createCommentUseCase, required DeleteCommentUseCase deleteCommentUseCase, - }) : _getPostDetailUseCase = getPostDetailUseCase, + required LikedPostUseCase likedPostUseCase, + required SavePostLikeLocalUseCase savePostLikeLocalUseCase, + }) : _getPostDetailUseCase = getPostDetailUseCase, _updatePostUseCase = updatePostUseCase, _deletePostUseCase = deletePostUseCase, _likePostUseCase = likePostUseCase, @@ -60,7 +65,9 @@ class PostViewController extends GetxController { _unbookmarkPostUseCase = unbookmarkPostUseCase, _getCommentsUseCase = getCommentsUseCase, _createCommentUseCase = createCommentUseCase, - _deleteCommentUseCase = deleteCommentUseCase; + _deleteCommentUseCase = deleteCommentUseCase, + _likedPostUseCase = likedPostUseCase, + _savePostLikeLocalUseCase = savePostLikeLocalUseCase; final Rx post = Rx(null); final RxList relatedPostList = [].obs; @@ -86,73 +93,32 @@ class PostViewController extends GetxController { late final TextEditingController commentController; + bool initialHeartState = false; + @override void onInit() { super.onInit(); - commentController = TextEditingController(); - - selectHeart.value = _resolveIsFavorite(); - - debugPrint( - '[PostViewController] onInit - postId: $postId, resolvedIsFavorite: ${selectHeart.value}', - ); - + _initIsFavorite(); fetchPostDetail(postId); fetchComments(); } - bool _resolveIsFavorite() { - - - bool homeLiked = false; - bool communityLiked = false; - - if (Get.isRegistered()) { - homeLiked = - Get.find() - .isPostLiked(postId); - } - - - if (Get.isRegistered()) { - communityLiked = - Get.find() - .isPostLiked(postId); - } - - debugPrint( - '[PostViewController] ' - 'resolveIsFavorite - ' - 'postId: $postId, ' - 'homeLiked: $homeLiked, ' - 'communityLiked: $communityLiked, ' - 'initialIsFavorite: $initialIsFavorite', - ); - - - return homeLiked || - communityLiked || - initialIsFavorite; + Future _initIsFavorite() async { + final liked = await _likedPostUseCase(postId); + selectHeart.value = liked; + initialHeartState = liked; } + Future fetchPostDetail(int postId) async { isLoading.value = true; errorMessage.value = ''; try { - debugPrint( - '[PostViewController] API 요청 시작 - postId: $postId', - ); - final result = await _getPostDetailUseCase(postId); post.value = result; - - debugPrint( - '[PostViewController] API 응답 성공 - title: ${result.title}', - ); - title.value = result.title; authorName.value = result.authorName; bodyText.value = result.content; @@ -161,74 +127,35 @@ class PostViewController extends GetxController { heartTotal.value = result.likeCount; bookMarkTotal.value = result.bookmarkCount; commentCount.value = result.commentCount; - - - selectHeart.value = _resolveIsFavorite(); } catch (e) { - debugPrint( - '[PostViewController] API 요청 실패 - error: $e', - ); - + debugPrint('[PostViewController] API 요청 실패 - error: $e'); errorMessage.value = e.toString(); } finally { isLoading.value = false; } } + Future fetchComments() async { isCommentsLoading.value = true; try { - debugPrint( - '[PostViewController] 댓글 조회 요청 - postId: $postId', - ); - final result = await _getCommentsUseCase(postId); - comments.assignAll( - result.map( - (e) => e.toEntity(currentUserId: null), - ), - ); - - debugPrint( - '[PostViewController] 댓글 조회 성공 - count: ${result.length}', + result.map((e) => e.toEntity(currentUserId: null)), ); } catch (e) { - debugPrint( - '[PostViewController] 댓글 조회 실패 - error: $e', - ); - + debugPrint('[PostViewController] 댓글 조회 실패 - error: $e'); errorMessage.value = e.toString(); } finally { isCommentsLoading.value = false; } } - Future toggleLike(bool isLiked) async { - debugPrint( - '[PostViewController] toggleLike 시작 ' - 'postId: $postId, ' - 'isLiked: $isLiked', - ); - - debugPrint( - '[PostViewController] ' - 'HomeController registered: ' - '${Get.isRegistered()}', - ); - - debugPrint( - '[PostViewController] ' - 'CommunityController registered: ' - '${Get.isRegistered()}', - ); + Future toggleLike(bool isLiked) async { selectHeart.value = isLiked; - - heartTotal.value = isLiked - ? heartTotal.value + 1 - : heartTotal.value - 1; + heartTotal.value = isLiked ? heartTotal.value + 1 : heartTotal.value - 1; try { if (isLiked) { @@ -236,57 +163,21 @@ class PostViewController extends GetxController { } else { await _unlikePostUseCase(postId); } + await _savePostLikeLocalUseCase(postId, isLiked); - // 홈 목록 강제 업데이트 - if (Get.isRegistered()) { - final homeController = - Get.find(); - - debugPrint( - '[PostViewController] ' - 'Home updatePostLike 호출', - ); - - homeController.updatePostLike( - postId, - heartTotal.value, - isLiked, - ); - - debugPrint( - '[PostViewController] ' - 'home liked after update: ' - '${homeController.isPostLiked(postId)}', - ); - } - // 커뮤니티 목록 업데이트 - if (Get.isRegistered()) { - debugPrint( - '[PostViewController] ' - 'Community updatePostLike 호출', - ); - - await Get.find() - .updatePostLike( - postId, - heartTotal.value, - isLiked, - ); - } - } catch (e) { - debugPrint( - '[PostViewController] ' - '좋아요 토글 실패 - error: $e', + Get.find().updateLikeState( + postId, + isLiked, + heartTotal.value, ); - + } catch (e) { + debugPrint('[PostViewController] 좋아요 토글 실패 - error: $e'); selectHeart.value = !isLiked; - - heartTotal.value = isLiked - ? heartTotal.value - 1 - : heartTotal.value + 1; + heartTotal.value = isLiked ? heartTotal.value - 1 : heartTotal.value + 1; } } + Future toggleBookmark(bool isBookmarked) async { selectBookMark.value = isBookmarked; bookMarkTotal.value = isBookmarked @@ -299,15 +190,8 @@ class PostViewController extends GetxController { } else { await _unbookmarkPostUseCase(postId); } - - debugPrint( - '[PostViewController] 북마크 토글 성공 - isBookmarked: $isBookmarked', - ); } catch (e) { - debugPrint( - '[PostViewController] 북마크 토글 실패 - error: $e', - ); - + debugPrint('[PostViewController] 북마크 토글 실패 - error: $e'); selectBookMark.value = !isBookmarked; bookMarkTotal.value = isBookmarked ? bookMarkTotal.value - 1 @@ -315,6 +199,7 @@ class PostViewController extends GetxController { } } + Future updatePost({ required String title, required String content, @@ -324,10 +209,6 @@ class PostViewController extends GetxController { errorMessage.value = ''; try { - debugPrint( - '[PostViewController] 게시물 수정 요청 - postId: $postId', - ); - await _updatePostUseCase( postId, PostUpdateRequestModel( @@ -336,48 +217,32 @@ class PostViewController extends GetxController { tags: tags, ), ); - - debugPrint('[PostViewController] 게시물 수정 성공'); - await fetchPostDetail(postId); } catch (e) { - debugPrint( - '[PostViewController] 게시물 수정 실패 - error: $e', - ); - + debugPrint('[PostViewController] 게시물 수정 실패 - error: $e'); errorMessage.value = e.toString(); } finally { isLoading.value = false; } } + void deletePost() { Get.dialog( CustomAlertDialog( title: "알림", comment: "정말 게시물 삭제하시겠어요?", - actionLeft: () { - Get.back(); - }, + actionLeft: () => Get.back(), actionRight: () async { isLoading.value = true; - errorMessage.value = ''; try { - debugPrint( - '[PostViewController] 게시물 삭제 요청 - postId: $postId', - ); - await _deletePostUseCase(postId); - debugPrint('[PostViewController] 게시물 삭제 성공'); - Get.find().removePost(postId); - Get.back(closeOverlays: true); + Get.back(); + Get.back(result: {'postId': postId, 'deleted': true}); } catch (e) { - debugPrint( - '[PostViewController] 게시물 삭제 실패 - error: $e', - ); - + debugPrint('[PostViewController] 게시물 삭제 실패 - error: $e'); errorMessage.value = e.toString(); } finally { isLoading.value = false; @@ -388,59 +253,36 @@ class PostViewController extends GetxController { ); } + Future createComment(String content) async { if (content.trim().isEmpty) return; try { - debugPrint( - '[PostViewController] 댓글 작성 요청 - content: $content', - ); - - await _createCommentUseCase( - postId: postId, - content: content, - ); - + await _createCommentUseCase(postId: postId, content: content); commentController.clear(); - await fetchComments(); - commentCount.value = comments.length; - - debugPrint('[PostViewController] 댓글 작성 성공'); } catch (e) { - debugPrint( - '[PostViewController] 댓글 작성 실패 - error: $e', - ); + debugPrint('[PostViewController] 댓글 작성 실패 - error: $e'); } } Future deleteComment(CommentEntity comment) async { try { - debugPrint( - '[PostViewController] 댓글 삭제 요청 - commentId: ${comment.id}', - ); - await _deleteCommentUseCase(comment.id); - await fetchComments(); - commentCount.value = comments.length; - - debugPrint('[PostViewController] 댓글 삭제 성공'); } catch (e) { - debugPrint( - '[PostViewController] 댓글 삭제 실패 - error: $e', - ); + debugPrint('[PostViewController] 댓글 삭제 실패 - error: $e'); } } + void reportPost() { - Get.dialog( - PostReportDialog(), - ); + Get.dialog(PostReportDialog()); } + void editPost() { Get.delete(force: true); Get.lazyPut( @@ -456,4 +298,21 @@ class PostViewController extends GetxController { ); Get.to(() => CommunityPostCreateScreen()); } + + + void goBack() { + Get.back(result: { + 'postId': postId, + 'isLiked': selectHeart.value, + 'likeCount': heartTotal.value, + 'changed': selectHeart.value != initialHeartState, + 'deleted': false, + }); + } + + @override + void onClose() { + commentController.dispose(); + super.onClose(); + } } \ No newline at end of file diff --git a/lib/presentation/post/screens/post_detail_screen.dart b/lib/presentation/post/screens/post_detail_screen.dart index 7f68f6e3..3c0b405d 100644 --- a/lib/presentation/post/screens/post_detail_screen.dart +++ b/lib/presentation/post/screens/post_detail_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:go_router/go_router.dart'; import 'package:ondo/core/design_system/app_layout.dart'; import 'package:ondo/core/design_system/components/custom_back_button.dart'; import 'package:ondo/core/ui/base/base_scaffold.dart'; @@ -25,17 +26,35 @@ class _PostDetailScreenState extends State { controller = Get.find(); } + void _onBack() { + final map = { + 'postId': controller.postId, + 'isLiked': controller.selectHeart.value, + 'likeCount': controller.heartTotal.value, + 'changed': controller.selectHeart.value != controller.initialHeartState, + 'deleted': false, + }; + debugPrint(' _onBack: $map'); + context.pop(map); + } + @override Widget build(BuildContext context) { - return BaseScaffold( - body: SingleChildScrollView( - child: Column( - children: [ - ..._top(), - AppGap.v16, - _body(), - RelatedPostList(), - ], + return PopScope( + canPop: false, + onPopInvoked: (didPop) { + if (!didPop) _onBack(); + }, + child: BaseScaffold( + body: SingleChildScrollView( + child: Column( + children: [ + ..._top(), + AppGap.v16, + _body(), + RelatedPostList(), + ], + ), ), ), ); @@ -43,6 +62,7 @@ class _PostDetailScreenState extends State { List _top() => [ CustomBackButton( + backAction: _onBack, moreOptions: true, itemBuilder: (context) => [ _topPopupItem("게시물 수정하기", controller.editPost), @@ -70,4 +90,4 @@ class _PostDetailScreenState extends State { onTap: onTap, child: Center(child: Text(title)), ); -} +} \ No newline at end of file diff --git a/lib/presentation/post/widgets/post_item.dart b/lib/presentation/post/widgets/post_item.dart index 3925c986..8c32846a 100644 --- a/lib/presentation/post/widgets/post_item.dart +++ b/lib/presentation/post/widgets/post_item.dart @@ -26,8 +26,8 @@ class PostItem extends GetView { @override Widget build(BuildContext context) { return GestureDetector( - onTap: () { - controller.enterPostDetail( + onTap: () async { + await controller.enterPostDetail( context, isMy, post.postId, @@ -125,4 +125,4 @@ class PostItem extends GetView { ], ); } -} +} \ No newline at end of file From 485a96625f8372fe143e0e698000f47731d9bd9b Mon Sep 17 00:00:00 2001 From: ryusuye0n Date: Fri, 5 Jun 2026 12:18:31 +0900 Subject: [PATCH 5/8] =?UTF-8?q?refactor:=20refresh=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EC=B6=A9=EB=8F=8C=20=EB=B0=8F=20PopScope=20?= =?UTF-8?q?=EC=BD=9C=EB=B0=B1=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/controllers/community_controller.dart | 1 + lib/presentation/home/controllers/home_controller.dart | 1 + lib/presentation/post/screens/post_detail_screen.dart | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/community/controllers/community_controller.dart b/lib/presentation/community/controllers/community_controller.dart index b8b62be3..b7218827 100644 --- a/lib/presentation/community/controllers/community_controller.dart +++ b/lib/presentation/community/controllers/community_controller.dart @@ -112,6 +112,7 @@ class CommunityController extends GetxController { } } + @override Future refresh() async { await loadRecommendPostList(refresh: true); } diff --git a/lib/presentation/home/controllers/home_controller.dart b/lib/presentation/home/controllers/home_controller.dart index 8cbe2b0a..ae9cfab7 100644 --- a/lib/presentation/home/controllers/home_controller.dart +++ b/lib/presentation/home/controllers/home_controller.dart @@ -110,6 +110,7 @@ class HomeController extends GetxController with BaseHomeController { viewPostList.assignAll(_cachePostList); } + @override Future refresh() async { await _loadRecommendPostList(refresh: true); } diff --git a/lib/presentation/post/screens/post_detail_screen.dart b/lib/presentation/post/screens/post_detail_screen.dart index 3c0b405d..4e0acb6a 100644 --- a/lib/presentation/post/screens/post_detail_screen.dart +++ b/lib/presentation/post/screens/post_detail_screen.dart @@ -42,7 +42,7 @@ class _PostDetailScreenState extends State { Widget build(BuildContext context) { return PopScope( canPop: false, - onPopInvoked: (didPop) { + onPopInvokedWithResult: (didPop, result) { if (!didPop) _onBack(); }, child: BaseScaffold( From 29ab3690ed36b57e7ef51caebf73254013691cea Mon Sep 17 00:00:00 2001 From: ryusuye0n Date: Sun, 21 Jun 2026 17:19:15 +0900 Subject: [PATCH 6/8] =?UTF-8?q?refactor:=20ci=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=98=A4=EB=A5=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/post_view_controller.dart | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/presentation/post/controllers/post_view_controller.dart b/lib/presentation/post/controllers/post_view_controller.dart index 6b10e60c..1e41f948 100644 --- a/lib/presentation/post/controllers/post_view_controller.dart +++ b/lib/presentation/post/controllers/post_view_controller.dart @@ -279,8 +279,24 @@ class PostViewController extends GetxController { } - void reportPost() { - Get.dialog(PostReportDialog()); + void reportPost(BuildContext context) { + showDialog( + context: context, + builder: (context) => CustomReportDialog( + type: ReportType.post, + targetId: postId.toString(), + ), + ); + } + + void reportComment(BuildContext context, CommentEntity comment) { + showDialog( + context: context, + builder: (context) => CustomReportDialog( + type: ReportType.comment, + targetId: comment.id.toString(), + ), + ); } From 98bf1597b988c12d1a1d5862250d41078c8b7d48 Mon Sep 17 00:00:00 2001 From: ryusuye0n Date: Sun, 21 Jun 2026 23:20:36 +0900 Subject: [PATCH 7/8] =?UTF-8?q?refactor:=20PR=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EB=B0=98=EC=98=81=20-=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EB=8F=99=EA=B8=B0=ED=99=94=20=EA=B5=AC=EC=A1=B0=20PostControll?= =?UTF-8?q?er=EB=A1=9C=20=EB=B3=91=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LikeStateController 제거 후 PostController로 좋아요 이벤트/동기화 로직 병합 - LikeChangedEvent를 domain/post/entity로 이동 - LikeStateController 전역 바인딩을 PostBinding(permanent)으로 이동 - HomeController _updatePostLikeInList의 delta 인자 제거 (내부에서 계산) - PostViewBinding의 미사용 isFavorite 인자 제거 및 호출부 정리 - CustomIconButton을 StatefulWidget으로 변경해 자체 상태 관리 (totalStyle.copyWith) - CommunityPostList PostItem key를 postId 기준으로 단순화 Co-Authored-By: Claude Opus 4.8 (1M context) --- .../components/custom_icon_button.dart | 74 +++++++++++-------- lib/core/router/app_router.dart | 2 +- .../router/bindings/navigation_binding.dart | 4 - lib/core/router/bindings/post_binding.dart | 5 +- .../router/bindings/post_view_binding.dart | 6 +- .../entities/post/like_changed_event.dart | 11 +++ .../controllers/community_controller.dart | 4 +- .../controllers/like_state_controller.dart | 25 ------- .../widgets/community_post_list.dart | 2 +- .../home/controllers/home_controller.dart | 12 +-- .../post/controllers/post_controller.dart | 22 ++++-- .../controllers/post_view_controller.dart | 4 +- lib/presentation/post/widgets/post_item.dart | 1 - 13 files changed, 87 insertions(+), 85 deletions(-) create mode 100644 lib/domain/entities/post/like_changed_event.dart delete mode 100644 lib/presentation/community/controllers/like_state_controller.dart diff --git a/lib/core/design_system/components/custom_icon_button.dart b/lib/core/design_system/components/custom_icon_button.dart index 7d7131b4..b615825e 100644 --- a/lib/core/design_system/components/custom_icon_button.dart +++ b/lib/core/design_system/components/custom_icon_button.dart @@ -12,7 +12,7 @@ typedef BookmarkAction = void Function( int total, ); -class CustomIconButton extends StatelessWidget { +class CustomIconButton extends StatefulWidget { const CustomIconButton({ super.key, required this.imagePath, @@ -35,46 +35,60 @@ class CustomIconButton extends StatelessWidget { int total, )? action; + @override + State createState() => _CustomIconButtonState(); +} + +class _CustomIconButtonState extends State { + late bool _isSelected; + late int _total; + + @override + void initState() { + super.initState(); + _isSelected = widget.initialIsSelected; + _total = widget.total; + } + + @override + void didUpdateWidget(CustomIconButton oldWidget) { + super.didUpdateWidget(oldWidget); + // 외부(컨트롤러)에서 갱신된 값이 들어오면 내부 상태에 반영한다. + if (oldWidget.initialIsSelected != widget.initialIsSelected) { + _isSelected = widget.initialIsSelected; + } + if (oldWidget.total != widget.total) { + _total = widget.total; + } + } + + void _onTap() { + setState(() { + _isSelected = !_isSelected; + _total += _isSelected ? 1 : -1; + }); + + widget.action?.call(_isSelected, _total); + } + @override Widget build(BuildContext context) { return Row( mainAxisSize: MainAxisSize.min, children: [ GestureDetector( - onTap: () { - final newIsSelected = - !initialIsSelected; - - final newTotal = - newIsSelected - ? total + 1 - : total - 1; - - action?.call( - newIsSelected, - newTotal, - ); - }, + onTap: _onTap, child: Image.asset( - imagePath, - color: initialIsSelected - ? activeColor - : AppColors.gray50, - height: iconSize, - width: iconSize, + widget.imagePath, + color: _isSelected ? widget.activeColor : AppColors.gray50, + height: widget.iconSize, + width: widget.iconSize, ), ), AppGap.h4, Text( - "$total", - style: TextStyle( - fontSize: - totalStyle.fontSize, - fontWeight: - totalStyle.fontWeight, - color: - AppColors.gray60, - ), + "$_total", + style: widget.totalStyle.copyWith(color: AppColors.gray60), ), ], ); diff --git a/lib/core/router/app_router.dart b/lib/core/router/app_router.dart index 0569cb6d..66360b55 100644 --- a/lib/core/router/app_router.dart +++ b/lib/core/router/app_router.dart @@ -137,7 +137,7 @@ final GoRouter appRouter = GoRouter( Get.delete(force: true); } - PostViewBinding(postId, state.extra as bool? ?? false).dependencies(); + PostViewBinding(postId).dependencies(); return PostDetailScreen(); }, diff --git a/lib/core/router/bindings/navigation_binding.dart b/lib/core/router/bindings/navigation_binding.dart index 6b52750a..21e1418f 100644 --- a/lib/core/router/bindings/navigation_binding.dart +++ b/lib/core/router/bindings/navigation_binding.dart @@ -13,7 +13,6 @@ import 'package:ondo/data/datasource/auth/auth_local_datasource_impl.dart'; import 'package:ondo/data/datasource/auth/auth_remote_datasource.dart'; import 'package:ondo/data/datasource/base/auth_local_datasource.dart'; import 'package:ondo/data/network/clients/auth_client.dart'; -import 'package:ondo/presentation/community/controllers/like_state_controller.dart'; import 'package:ondo/presentation/navigation/controllers/navigation_controller.dart'; @@ -40,9 +39,6 @@ class NavigationBinding extends Bindings { fenix: true, ); - /// 전역 LikeStateController 등록 - Get.put(LikeStateController(), permanent: true); - /// 전 화면 공통 controller 등록 Get.lazyPut( () => NavigationController(), diff --git a/lib/core/router/bindings/post_binding.dart b/lib/core/router/bindings/post_binding.dart index 7ca361c6..db20b677 100644 --- a/lib/core/router/bindings/post_binding.dart +++ b/lib/core/router/bindings/post_binding.dart @@ -68,8 +68,7 @@ class PostBinding extends Bindings { () => LoadRecentPopularPostListUseCase(Get.find()), ); - Get.lazyPut( - () => PostController(), - ); + /// 좋아요 상태 동기화 이벤트를 전역으로 유지해야 하므로 permanent 등록 + Get.put(PostController(), permanent: true); } } \ No newline at end of file diff --git a/lib/core/router/bindings/post_view_binding.dart b/lib/core/router/bindings/post_view_binding.dart index a072033c..6aa0acd0 100644 --- a/lib/core/router/bindings/post_view_binding.dart +++ b/lib/core/router/bindings/post_view_binding.dart @@ -23,12 +23,8 @@ import '../../../presentation/post/controllers/post_view_controller.dart'; class PostViewBinding extends Bindings { final int postId; - final bool isFavorite; - PostViewBinding( - this.postId, [ - this.isFavorite = false, - ]); + PostViewBinding(this.postId); @override void dependencies() { diff --git a/lib/domain/entities/post/like_changed_event.dart b/lib/domain/entities/post/like_changed_event.dart new file mode 100644 index 00000000..165691b8 --- /dev/null +++ b/lib/domain/entities/post/like_changed_event.dart @@ -0,0 +1,11 @@ +class LikeChangedEvent { + final int postId; + final bool isLiked; + final int likeCount; + + LikeChangedEvent({ + required this.postId, + required this.isLiked, + required this.likeCount, + }); +} diff --git a/lib/presentation/community/controllers/community_controller.dart b/lib/presentation/community/controllers/community_controller.dart index b7218827..b1c64dd2 100644 --- a/lib/presentation/community/controllers/community_controller.dart +++ b/lib/presentation/community/controllers/community_controller.dart @@ -9,8 +9,8 @@ import 'package:ondo/domain/usecases/post/save_post_like_local_use_case.dart'; import 'package:ondo/domain/usecases/post/unlike_post_usecase.dart'; import 'package:ondo/domain/usecases/post/update_post_usecase.dart'; import 'package:ondo/presentation/community/controllers/community_post_create_screen_controller.dart'; -import 'package:ondo/presentation/community/controllers/like_state_controller.dart'; import 'package:ondo/presentation/community/screens/community_post_create_screen.dart'; +import 'package:ondo/presentation/post/controllers/post_controller.dart'; class CommunityController extends GetxController { final LikePostUseCase _likeUseCase; @@ -49,7 +49,7 @@ class CommunityController extends GetxController { loadRecommendPostList(); ever( - Get.find().lastEvent, + Get.find().lastLikeEvent, (event) { if (event == null) return; _syncLike(event.postId, event.isLiked, event.likeCount); diff --git a/lib/presentation/community/controllers/like_state_controller.dart b/lib/presentation/community/controllers/like_state_controller.dart deleted file mode 100644 index c67f16f2..00000000 --- a/lib/presentation/community/controllers/like_state_controller.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:get/get.dart'; - -class LikeChangedEvent { - final int postId; - final bool isLiked; - final int likeCount; - - LikeChangedEvent({ - required this.postId, - required this.isLiked, - required this.likeCount, - }); -} - -class LikeStateController extends GetxController { - final Rx lastEvent = Rx(null); - - void updateLikeState(int postId, bool isLiked, int likeCount) { - lastEvent.value = LikeChangedEvent( - postId: postId, - isLiked: isLiked, - likeCount: likeCount, - ); - } -} \ No newline at end of file diff --git a/lib/presentation/community/widgets/community_post_list.dart b/lib/presentation/community/widgets/community_post_list.dart index 8e076892..7d20757d 100644 --- a/lib/presentation/community/widgets/community_post_list.dart +++ b/lib/presentation/community/widgets/community_post_list.dart @@ -17,7 +17,7 @@ class CommunityPostList extends GetView { (index) { final post = controller.viewPostList[index]; return PostItem( - key: ValueKey('${post.postId}-${post.likeCount}-${post.isFavorite}'), + key: ValueKey(post.postId), post: post, isMy: true, heartAction: (isLiked, total) { diff --git a/lib/presentation/home/controllers/home_controller.dart b/lib/presentation/home/controllers/home_controller.dart index ae9cfab7..4e60af4f 100644 --- a/lib/presentation/home/controllers/home_controller.dart +++ b/lib/presentation/home/controllers/home_controller.dart @@ -12,8 +12,8 @@ import 'package:ondo/domain/usecases/post/save_post_like_local_use_case.dart'; import 'package:ondo/domain/usecases/post/post_search_use_case.dart'; import 'package:ondo/domain/usecases/post/unlike_post_usecase.dart'; import 'package:ondo/domain/usecases/user/user_search_use_case.dart'; -import 'package:ondo/presentation/community/controllers/like_state_controller.dart'; import 'package:ondo/presentation/home/controllers/base_home_controller.dart'; +import 'package:ondo/presentation/post/controllers/post_controller.dart'; class HomeController extends GetxController with BaseHomeController { final RxList recentPopularPostList = [].obs; @@ -55,7 +55,7 @@ class HomeController extends GetxController with BaseHomeController { loadRecommendUsers(); ever( - Get.find().lastEvent, + Get.find().lastLikeEvent, (event) { if (event == null) return; _syncLike(event.postId, event.isLiked, event.likeCount); @@ -116,7 +116,7 @@ class HomeController extends GetxController with BaseHomeController { } Future toggleLike(int postId, bool isLiked) async { - _updatePostLikeInList(postId, isLiked ? 1 : -1, isLiked); + _updatePostLikeInList(postId, isLiked); try { if (isLiked) { @@ -127,15 +127,15 @@ class HomeController extends GetxController with BaseHomeController { await savePostLikeLocalUseCase(postId, isLiked); } catch (e) { debugPrint('[HomeController] 좋아요 토글 실패 - error: $e'); - _updatePostLikeInList(postId, isLiked ? -1 : 1, !isLiked); + _updatePostLikeInList(postId, !isLiked); } } - void _updatePostLikeInList(int postId, int delta, bool isFavorite) { + void _updatePostLikeInList(int postId, bool isFavorite) { final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); if (cacheIndex != -1) { _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( - likeCount: _cachePostList[cacheIndex].likeCount + delta, + likeCount: _cachePostList[cacheIndex].likeCount + (isFavorite ? 1 : -1), isFavorite: isFavorite, ); } diff --git a/lib/presentation/post/controllers/post_controller.dart b/lib/presentation/post/controllers/post_controller.dart index 9fc38d8a..8bcb6f62 100644 --- a/lib/presentation/post/controllers/post_controller.dart +++ b/lib/presentation/post/controllers/post_controller.dart @@ -1,18 +1,30 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:go_router/go_router.dart'; +import 'package:ondo/domain/entities/post/like_changed_event.dart'; class PostController extends GetxController { + /// 상세 화면에서 변경된 좋아요 상태를 게시물 목록 화면들에 전파하기 위한 이벤트. + /// 게시물 목록 화면 컨트롤러(Home/Community 등)는 PostController를 통해 상세 + /// 화면으로 진입하므로, 좋아요 동기화 이벤트도 PostController에서 관리한다. + final Rx lastLikeEvent = Rx(null); + + void updateLikeState(int postId, bool isLiked, int likeCount) { + lastLikeEvent.value = LikeChangedEvent( + postId: postId, + isLiked: isLiked, + likeCount: likeCount, + ); + } + Future?> enterPostDetail( BuildContext context, bool isMy, - int postId, { - bool isFavorite = false, - }) async { + int postId, + ) async { final result = await context.push>( '/post/$postId', - extra: isFavorite, ); return result; } -} \ No newline at end of file +} diff --git a/lib/presentation/post/controllers/post_view_controller.dart b/lib/presentation/post/controllers/post_view_controller.dart index 1e41f948..ae62db2e 100644 --- a/lib/presentation/post/controllers/post_view_controller.dart +++ b/lib/presentation/post/controllers/post_view_controller.dart @@ -13,7 +13,7 @@ import 'package:ondo/domain/usecases/post/bookmark_post_usecase.dart'; import 'package:ondo/domain/usecases/post/liked_post_use_case.dart'; import 'package:ondo/domain/usecases/post/save_post_like_local_use_case.dart'; import 'package:ondo/domain/usecases/post/unbookmark_post_usecase.dart'; -import 'package:ondo/presentation/community/controllers/like_state_controller.dart'; +import 'package:ondo/presentation/post/controllers/post_controller.dart'; import '../../../data/models/post/request/post_update_request_model.dart'; import '../../../domain/usecases/post/create_post_usecase.dart'; @@ -167,7 +167,7 @@ class PostViewController extends GetxController { await _savePostLikeLocalUseCase(postId, isLiked); - Get.find().updateLikeState( + Get.find().updateLikeState( postId, isLiked, heartTotal.value, diff --git a/lib/presentation/post/widgets/post_item.dart b/lib/presentation/post/widgets/post_item.dart index 8c32846a..c206fbc3 100644 --- a/lib/presentation/post/widgets/post_item.dart +++ b/lib/presentation/post/widgets/post_item.dart @@ -31,7 +31,6 @@ class PostItem extends GetView { context, isMy, post.postId, - isFavorite: post.isFavorite, ); }, child: Container( From 3ee3c7223756e5b4562aaa23e3133be3ef787420 Mon Sep 17 00:00:00 2001 From: ryusuye0n Date: Mon, 22 Jun 2026 00:02:58 +0900 Subject: [PATCH 8/8] =?UTF-8?q?fix=20:=20"=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20=EC=B5=9C?= =?UTF-8?q?=EC=8B=A0=20=EC=A2=8B=EC=95=84=EC=9A=94=20=EC=88=98=EB=A5=BC=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=ED=99=94=EB=A9=B4=EC=97=90=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=ED=99=94=ED=95=98=EC=97=AC=20=EB=AF=B8=EB=A6=AC?= =?UTF-8?q?=EB=B3=B4=EA=B8=B0=EC=99=80=20count=20=EB=B6=88=EC=9D=BC?= =?UTF-8?q?=EC=B9=98=20=ED=95=B4=EA=B2=B0"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/post_view_controller.dart | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/lib/presentation/post/controllers/post_view_controller.dart b/lib/presentation/post/controllers/post_view_controller.dart index ae62db2e..ea6de5b1 100644 --- a/lib/presentation/post/controllers/post_view_controller.dart +++ b/lib/presentation/post/controllers/post_view_controller.dart @@ -100,17 +100,36 @@ class PostViewController extends GetxController { void onInit() { super.onInit(); commentController = TextEditingController(); - _initIsFavorite(); - fetchPostDetail(postId); + _initPostState(); fetchComments(); } + Future _initPostState() async { + // 좋아요 여부(로컬 캐시)와 상세 정보(서버)를 함께 받은 뒤, + // 상세에서 조회한 최신 좋아요 수를 게시물 목록 화면들에 동기화한다. + await Future.wait([ + _initIsFavorite(), + fetchPostDetail(postId), + ]); + _syncLikeToList(); + } + Future _initIsFavorite() async { final liked = await _likedPostUseCase(postId); selectHeart.value = liked; initialHeartState = liked; } + /// 상세에서 조회한 최신 좋아요 상태/수를 목록 화면 캐시에 반영한다. + /// (토글하지 않고 단순 조회만 한 경우에도 미리보기 count가 상세와 일치하도록) + void _syncLikeToList() { + Get.find().updateLikeState( + postId, + selectHeart.value, + heartTotal.value, + ); + } + Future fetchPostDetail(int postId) async { isLoading.value = true;