From cdcf193a1de5f99059b0f48ab7f0b83667fb0d80 Mon Sep 17 00:00:00 2001 From: ryusuye0n Date: Mon, 22 Jun 2026 14:24:47 +0900 Subject: [PATCH 1/3] =?UTF-8?q?fix=20:=20"=EB=B6=81=EB=A7=88=ED=81=AC=20?= =?UTF-8?q?=EC=83=81=ED=83=9C/=EA=B0=9C=EC=88=98=EB=A5=BC=20=EB=A1=9C?= =?UTF-8?q?=EC=BB=AC=20=EC=BA=90=EC=8B=9C=20=EA=B8=B0=EB=B0=98=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=B2=98=EB=A6=AC=ED=95=98=EA=B3=A0=20=EC=83=81?= =?UTF-8?q?=EC=84=B8-=EB=AA=A9=EB=A1=9D=20=ED=99=94=EB=A9=B4=20=EA=B0=84?= =?UTF-8?q?=20=EB=8F=99=EA=B8=B0=ED=99=94=ED=95=98=EC=97=AC=20=EB=AF=B8?= =?UTF-8?q?=EB=A6=AC=EB=B3=B4=EA=B8=B0=20=EB=B6=88=EC=9D=BC=EC=B9=98=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 좋아요(하트)와 동일한 패턴으로 북마크 기능 전체 배선 - PostLocalDatasource에 북마크 상태 로컬 캐시(saveBookmarkState/bookmarkedPost) 추가 - BookmarkedPostUseCase, SavePostBookmarkLocalUseCase, BookmarkChangedEvent 신규 - PostViewController: 진입 시 로컬 캐시로 북마크 상태 복원 및 목록 동기화 - Home/CommunityController: 목록 로드 시 isBookmark 복원, lastBookmarkEvent 구독, toggleBookmark 추가 - home_screen 빈 콜백 연결 및 community_post_list bookmarkAction 추가 - PostEntity.copyWith가 isBookmark/bookmarkCount를 보존하도록 수정(좋아요 토글 시 북마크 표시 사라지던 문제 해결) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../router/bindings/community_binding.dart | 8 ++ lib/core/router/bindings/home_binding.dart | 8 ++ lib/core/router/bindings/post_binding.dart | 10 +++ .../router/bindings/post_view_binding.dart | 8 ++ .../post/post_local_datasource.dart | 35 +++++++++ .../post/post_repository_impl.dart | 10 +++ .../entities/post/bookmark_changed_event.dart | 11 +++ lib/domain/entities/post/post_entity.dart | 31 +++++--- .../repositories/post/post_repository.dart | 4 + .../post/bookmarked_post_use_case.dart | 11 +++ .../save_post_bookmark_local_use_case.dart | 11 +++ .../controllers/community_controller.dart | 73 ++++++++++++++++++- .../widgets/community_post_list.dart | 3 + .../home/controllers/home_controller.dart | 60 +++++++++++++++ .../home/screens/home_screen.dart | 4 +- .../post/controllers/post_controller.dart | 13 ++++ .../controllers/post_view_controller.dart | 36 ++++++++- lib/presentation/post/widgets/post_item.dart | 1 + 18 files changed, 320 insertions(+), 17 deletions(-) create mode 100644 lib/domain/entities/post/bookmark_changed_event.dart create mode 100644 lib/domain/usecases/post/bookmarked_post_use_case.dart create mode 100644 lib/domain/usecases/post/save_post_bookmark_local_use_case.dart diff --git a/lib/core/router/bindings/community_binding.dart b/lib/core/router/bindings/community_binding.dart index 21bce934..c25021e9 100644 --- a/lib/core/router/bindings/community_binding.dart +++ b/lib/core/router/bindings/community_binding.dart @@ -5,6 +5,10 @@ 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'; +import 'package:ondo/domain/usecases/post/bookmark_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/unbookmark_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/bookmarked_post_use_case.dart'; +import 'package:ondo/domain/usecases/post/save_post_bookmark_local_use_case.dart'; import 'package:ondo/presentation/community/controllers/community_controller.dart'; import 'package:ondo/presentation/search/states/search_page_state.dart'; @@ -20,6 +24,10 @@ class CommunityBinding extends Bindings { getRecommendPostsUseCase: Get.find(), savePostLikeLocalUseCase: Get.find(), likedPostUseCase: Get.find(), + bookmarkUseCase: Get.find(), + unbookmarkUseCase: Get.find(), + savePostBookmarkLocalUseCase: Get.find(), + bookmarkedPostUseCase: Get.find(), ), ); } diff --git a/lib/core/router/bindings/home_binding.dart b/lib/core/router/bindings/home_binding.dart index cd8a46d8..ab836da7 100644 --- a/lib/core/router/bindings/home_binding.dart +++ b/lib/core/router/bindings/home_binding.dart @@ -8,6 +8,10 @@ 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'; +import 'package:ondo/domain/usecases/post/bookmark_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/unbookmark_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/bookmarked_post_use_case.dart'; +import 'package:ondo/domain/usecases/post/save_post_bookmark_local_use_case.dart'; 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'; @@ -29,6 +33,10 @@ class HomeBinding extends Bindings { unlikePostUseCase: Get.find(), savePostLikeLocalUseCase: Get.find(), likedPostUseCase: Get.find(), + bookmarkPostUseCase: Get.find(), + unbookmarkPostUseCase: Get.find(), + savePostBookmarkLocalUseCase: Get.find(), + bookmarkedPostUseCase: Get.find(), ), ); } diff --git a/lib/core/router/bindings/post_binding.dart b/lib/core/router/bindings/post_binding.dart index db20b677..795e1356 100644 --- a/lib/core/router/bindings/post_binding.dart +++ b/lib/core/router/bindings/post_binding.dart @@ -4,11 +4,13 @@ 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/bookmarked_post_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'; +import 'package:ondo/domain/usecases/post/save_post_bookmark_local_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/domain/usecases/post/unlike_post_usecase.dart'; @@ -52,6 +54,14 @@ class PostBinding extends Bindings { () => UnbookmarkPostUseCase(Get.find()), ); + Get.lazyPut( // ← 로컬 캐시 확인(북마크) + () => BookmarkedPostUseCase(Get.find()), + ); + + Get.lazyPut( + () => SavePostBookmarkLocalUseCase(Get.find()), + ); + Get.lazyPut( () => PostSearchUseCase(Get.find()), ); diff --git a/lib/core/router/bindings/post_view_binding.dart b/lib/core/router/bindings/post_view_binding.dart index 6aa0acd0..bf71b330 100644 --- a/lib/core/router/bindings/post_view_binding.dart +++ b/lib/core/router/bindings/post_view_binding.dart @@ -2,7 +2,9 @@ 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/bookmarked_post_use_case.dart'; import 'package:ondo/domain/usecases/post/liked_post_use_case.dart'; +import 'package:ondo/domain/usecases/post/save_post_bookmark_local_use_case.dart'; import 'package:ondo/domain/usecases/post/save_post_like_local_use_case.dart'; import '../../../data/datasource/comment/comment_remote_datasource.dart'; @@ -49,6 +51,10 @@ class PostViewBinding extends Bindings { () => SavePostLikeLocalUseCase(Get.find()), ); + Get.lazyPut( + () => SavePostBookmarkLocalUseCase(Get.find()), + ); + // Comment DataSource Get.lazyPut( () => CommentRemoteDataSourceImpl(Get.find()), @@ -90,6 +96,8 @@ class PostViewBinding extends Bindings { deleteCommentUseCase: Get.find(), likedPostUseCase: Get.find(), savePostLikeLocalUseCase: Get.find(), + bookmarkedPostUseCase: Get.find(), + savePostBookmarkLocalUseCase: Get.find(), ), ); } diff --git a/lib/data/datasource/post/post_local_datasource.dart b/lib/data/datasource/post/post_local_datasource.dart index ae25a130..16189fac 100644 --- a/lib/data/datasource/post/post_local_datasource.dart +++ b/lib/data/datasource/post/post_local_datasource.dart @@ -2,9 +2,11 @@ import 'package:shared_preferences/shared_preferences.dart'; class PostLocalDatasource { static const String _likedPostsKey = 'liked_post_ids'; + static const String _bookmarkedPostsKey = 'bookmarked_post_ids'; SharedPreferences? _prefs; Set? _cachedLikedIds; + Set? _cachedBookmarkedIds; Future _getPrefs() async { return _prefs ??= await SharedPreferences.getInstance(); @@ -42,4 +44,37 @@ class PostLocalDatasource { final ids = await getLikedPostIds(); return ids.contains(postId); } + + Future saveBookmarkState(int postId, bool isBookmarked) async { + final prefs = await _getPrefs(); + final bookmarkedIds = await getBookmarkedPostIds(); + + if (isBookmarked) { + bookmarkedIds.add(postId); + } else { + bookmarkedIds.remove(postId); + } + + _cachedBookmarkedIds = bookmarkedIds; + + await prefs.setStringList( + _bookmarkedPostsKey, + bookmarkedIds.map((id) => id.toString()).toList(), + ); + } + + Future> getBookmarkedPostIds() async { + if (_cachedBookmarkedIds != null) return _cachedBookmarkedIds!; + + final prefs = await _getPrefs(); + final ids = prefs.getStringList(_bookmarkedPostsKey); + _cachedBookmarkedIds = + (ids ?? []).map((id) => int.tryParse(id)).whereType().toSet(); + return _cachedBookmarkedIds!; + } + + Future bookmarkedPost(int postId) async { + final ids = await getBookmarkedPostIds(); + 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 89c3bdf1..c02f6352 100644 --- a/lib/data/repositories/post/post_repository_impl.dart +++ b/lib/data/repositories/post/post_repository_impl.dart @@ -103,6 +103,16 @@ class PostRepositoryImpl implements PostRepository { return _localDatasource.likedPost(postId); } + @override + Future saveBookmarkState(int postId, bool isBookmarked) { + return _localDatasource.saveBookmarkState(postId, isBookmarked); + } + + @override + Future bookmarkedPost(int postId) { + return _localDatasource.bookmarkedPost(postId); + } + @override Future> search( String keyword, diff --git a/lib/domain/entities/post/bookmark_changed_event.dart b/lib/domain/entities/post/bookmark_changed_event.dart new file mode 100644 index 00000000..348848fd --- /dev/null +++ b/lib/domain/entities/post/bookmark_changed_event.dart @@ -0,0 +1,11 @@ +class BookmarkChangedEvent { + final int postId; + final bool isBookmarked; + final int bookmarkCount; + + BookmarkChangedEvent({ + required this.postId, + required this.isBookmarked, + required this.bookmarkCount, + }); +} diff --git a/lib/domain/entities/post/post_entity.dart b/lib/domain/entities/post/post_entity.dart index ed3676d9..792f073a 100644 --- a/lib/domain/entities/post/post_entity.dart +++ b/lib/domain/entities/post/post_entity.dart @@ -28,18 +28,25 @@ class PostEntity { this.isBookmark = false, }); - PostEntity copyWith({int? likeCount, bool? isFavorite}) => PostEntity( - postId: postId, - title: title, - authorName: authorName, - tags: tags, - viewCount: viewCount, - likeCount: likeCount ?? this.likeCount, - commentCount: commentCount, - bookmarkCount: bookmarkCount, - createAt: createAt, - isFavorite: isFavorite ?? this.isFavorite, - ); + PostEntity copyWith({ + int? likeCount, + bool? isFavorite, + int? bookmarkCount, + bool? isBookmark, + }) => + PostEntity( + postId: postId, + title: title, + authorName: authorName, + tags: tags, + viewCount: viewCount, + likeCount: likeCount ?? this.likeCount, + commentCount: commentCount, + bookmarkCount: bookmarkCount ?? this.bookmarkCount, + createAt: createAt, + isFavorite: isFavorite ?? this.isFavorite, + isBookmark: isBookmark ?? this.isBookmark, + ); PostEntity.fromPostModel(PostModel model) : postId = model.postId, diff --git a/lib/domain/repositories/post/post_repository.dart b/lib/domain/repositories/post/post_repository.dart index 45c9d176..7f6d7b3c 100644 --- a/lib/domain/repositories/post/post_repository.dart +++ b/lib/domain/repositories/post/post_repository.dart @@ -33,6 +33,10 @@ abstract class PostRepository { Future likedPost(int postId); + Future saveBookmarkState(int postId, bool isBookmarked); + + Future bookmarkedPost(int postId); + Future> search( String keyword, List? tags, diff --git a/lib/domain/usecases/post/bookmarked_post_use_case.dart b/lib/domain/usecases/post/bookmarked_post_use_case.dart new file mode 100644 index 00000000..4bfe578c --- /dev/null +++ b/lib/domain/usecases/post/bookmarked_post_use_case.dart @@ -0,0 +1,11 @@ +import 'package:ondo/domain/repositories/post/post_repository.dart'; + +class BookmarkedPostUseCase { + final PostRepository _repository; + + BookmarkedPostUseCase(this._repository); + + Future call(int postId) { + return _repository.bookmarkedPost(postId); + } +} diff --git a/lib/domain/usecases/post/save_post_bookmark_local_use_case.dart b/lib/domain/usecases/post/save_post_bookmark_local_use_case.dart new file mode 100644 index 00000000..83bf9f1f --- /dev/null +++ b/lib/domain/usecases/post/save_post_bookmark_local_use_case.dart @@ -0,0 +1,11 @@ +import 'package:ondo/domain/repositories/post/post_repository.dart'; + +class SavePostBookmarkLocalUseCase { + final PostRepository _repository; + + SavePostBookmarkLocalUseCase(this._repository); + + Future call(int postId, bool isBookmarked) { + return _repository.saveBookmarkState(postId, isBookmarked); + } +} diff --git a/lib/presentation/community/controllers/community_controller.dart b/lib/presentation/community/controllers/community_controller.dart index b1c64dd2..6211fb31 100644 --- a/lib/presentation/community/controllers/community_controller.dart +++ b/lib/presentation/community/controllers/community_controller.dart @@ -8,6 +8,10 @@ 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/domain/usecases/post/bookmark_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/unbookmark_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/bookmarked_post_use_case.dart'; +import 'package:ondo/domain/usecases/post/save_post_bookmark_local_use_case.dart'; import 'package:ondo/presentation/community/controllers/community_post_create_screen_controller.dart'; import 'package:ondo/presentation/community/screens/community_post_create_screen.dart'; import 'package:ondo/presentation/post/controllers/post_controller.dart'; @@ -18,6 +22,10 @@ class CommunityController extends GetxController { final LoadRecommendPostListUseCase _getRecommendPostsUseCase; final SavePostLikeLocalUseCase _savePostLikeLocalUseCase; final LikedPostUseCase _likedPostUseCase; + final BookmarkPostUseCase _bookmarkUseCase; + final UnbookmarkPostUseCase _unbookmarkUseCase; + final SavePostBookmarkLocalUseCase _savePostBookmarkLocalUseCase; + final BookmarkedPostUseCase _bookmarkedPostUseCase; CommunityController({ required LikePostUseCase likeUseCase, @@ -25,11 +33,19 @@ class CommunityController extends GetxController { required LoadRecommendPostListUseCase getRecommendPostsUseCase, required SavePostLikeLocalUseCase savePostLikeLocalUseCase, required LikedPostUseCase likedPostUseCase, + required BookmarkPostUseCase bookmarkUseCase, + required UnbookmarkPostUseCase unbookmarkUseCase, + required SavePostBookmarkLocalUseCase savePostBookmarkLocalUseCase, + required BookmarkedPostUseCase bookmarkedPostUseCase, }) : _likeUseCase = likeUseCase, _unlikeUseCase = unlikeUseCase, _getRecommendPostsUseCase = getRecommendPostsUseCase, _savePostLikeLocalUseCase = savePostLikeLocalUseCase, - _likedPostUseCase = likedPostUseCase; + _likedPostUseCase = likedPostUseCase, + _bookmarkUseCase = bookmarkUseCase, + _unbookmarkUseCase = unbookmarkUseCase, + _savePostBookmarkLocalUseCase = savePostBookmarkLocalUseCase, + _bookmarkedPostUseCase = bookmarkedPostUseCase; final RxSet viewTagList = {}.obs; final RxSet selectTagList = {}.obs; @@ -55,6 +71,14 @@ class CommunityController extends GetxController { _syncLike(event.postId, event.isLiked, event.likeCount); }, ); + + ever( + Get.find().lastBookmarkEvent, + (event) { + if (event == null) return; + _syncBookmark(event.postId, event.isBookmarked, event.bookmarkCount); + }, + ); } void _syncLike(int postId, bool isLiked, int likeCount) { @@ -68,6 +92,17 @@ class CommunityController extends GetxController { } } + void _syncBookmark(int postId, bool isBookmarked, int bookmarkCount) { + final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); + if (cacheIndex != -1) { + _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( + bookmarkCount: bookmarkCount, + isBookmark: isBookmarked, + ); + viewPostList.assignAll(_cachePostList); + } + } + @override void onReady() { Get.put(CommunityResultController()); @@ -96,6 +131,7 @@ class CommunityController extends GetxController { result.content.map((post) async { return post.copyWith( isFavorite: await _likedPostUseCase(post.postId), + isBookmark: await _bookmarkedPostUseCase(post.postId), ); }), ); @@ -165,6 +201,41 @@ class CommunityController extends GetxController { } } + Future toggleBookmark(int postId, bool isBookmarked) async { + _updatePostBookmarkInList(postId, isBookmarked ? 1 : -1, isBookmarked); + + try { + if (isBookmarked) { + await _bookmarkUseCase(postId); + } else { + await _unbookmarkUseCase(postId); + } + await _savePostBookmarkLocalUseCase(postId, isBookmarked); + } catch (e) { + debugPrint('[CommunityController] 북마크 토글 실패 - error: $e'); + _updatePostBookmarkInList(postId, isBookmarked ? -1 : 1, !isBookmarked); + } + } + + void _updatePostBookmarkInList(int postId, int delta, bool isBookmark) { + final index = viewPostList.indexWhere((p) => p.postId == postId); + if (index != -1) { + viewPostList[index] = viewPostList[index].copyWith( + bookmarkCount: viewPostList[index].bookmarkCount + delta, + isBookmark: isBookmark, + ); + viewPostList.refresh(); + } + + final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); + if (cacheIndex != -1) { + _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( + bookmarkCount: _cachePostList[cacheIndex].bookmarkCount + delta, + isBookmark: isBookmark, + ); + } + } + void removePost(int postId) { viewPostList.removeWhere((p) => p.postId == postId); _cachePostList.removeWhere((p) => p.postId == postId); diff --git a/lib/presentation/community/widgets/community_post_list.dart b/lib/presentation/community/widgets/community_post_list.dart index 7d20757d..733fde34 100644 --- a/lib/presentation/community/widgets/community_post_list.dart +++ b/lib/presentation/community/widgets/community_post_list.dart @@ -23,6 +23,9 @@ class CommunityPostList extends GetView { heartAction: (isLiked, total) { controller.toggleLike(post.postId, isLiked); }, + bookmarkAction: (isBookmark, total) { + controller.toggleBookmark(post.postId, isBookmark); + }, ); }, ), diff --git a/lib/presentation/home/controllers/home_controller.dart b/lib/presentation/home/controllers/home_controller.dart index 4e60af4f..e5e9cdea 100644 --- a/lib/presentation/home/controllers/home_controller.dart +++ b/lib/presentation/home/controllers/home_controller.dart @@ -11,6 +11,10 @@ import 'package:ondo/domain/usecases/post/load_recent_popular_post_list_use_case 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/post/bookmark_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/unbookmark_post_usecase.dart'; +import 'package:ondo/domain/usecases/post/bookmarked_post_use_case.dart'; +import 'package:ondo/domain/usecases/post/save_post_bookmark_local_use_case.dart'; import 'package:ondo/domain/usecases/user/user_search_use_case.dart'; import 'package:ondo/presentation/home/controllers/base_home_controller.dart'; import 'package:ondo/presentation/post/controllers/post_controller.dart'; @@ -28,6 +32,10 @@ class HomeController extends GetxController with BaseHomeController { final UnlikePostUseCase unlikePostUseCase; final SavePostLikeLocalUseCase savePostLikeLocalUseCase; final LikedPostUseCase likedPostUseCase; + final BookmarkPostUseCase bookmarkPostUseCase; + final UnbookmarkPostUseCase unbookmarkPostUseCase; + final SavePostBookmarkLocalUseCase savePostBookmarkLocalUseCase; + final BookmarkedPostUseCase bookmarkedPostUseCase; final LoadRecentPopularPostListUseCase loadRecentPopularPostListUseCase; final searchResultController = HomeSearchResultController(); @@ -40,6 +48,10 @@ class HomeController extends GetxController with BaseHomeController { required this.unlikePostUseCase, required this.savePostLikeLocalUseCase, required this.likedPostUseCase, + required this.bookmarkPostUseCase, + required this.unbookmarkPostUseCase, + required this.savePostBookmarkLocalUseCase, + required this.bookmarkedPostUseCase, required this.postSearchUseCase, required this.loadRecentPopularPostListUseCase, }); @@ -61,6 +73,14 @@ class HomeController extends GetxController with BaseHomeController { _syncLike(event.postId, event.isLiked, event.likeCount); }, ); + + ever( + Get.find().lastBookmarkEvent, + (event) { + if (event == null) return; + _syncBookmark(event.postId, event.isBookmarked, event.bookmarkCount); + }, + ); } void _syncLike(int postId, bool isLiked, int likeCount) { @@ -74,6 +94,17 @@ class HomeController extends GetxController with BaseHomeController { } } + void _syncBookmark(int postId, bool isBookmarked, int bookmarkCount) { + final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); + if (cacheIndex != -1) { + _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( + bookmarkCount: bookmarkCount, + isBookmark: isBookmarked, + ); + viewPostList.assignAll(_cachePostList); + } + } + Future _loadRecentPopularPostList() async { final result = await loadRecentPopularPostListUseCase(); if (result.isEmpty) return; @@ -102,6 +133,7 @@ class HomeController extends GetxController with BaseHomeController { result.content.map((post) async { return post.copyWith( isFavorite: await likedPostUseCase(post.postId), + isBookmark: await bookmarkedPostUseCase(post.postId), ); }), ); @@ -142,6 +174,34 @@ class HomeController extends GetxController with BaseHomeController { viewPostList.assignAll(_cachePostList); } + Future toggleBookmark(int postId, bool isBookmarked) async { + _updatePostBookmarkInList(postId, isBookmarked); + + try { + if (isBookmarked) { + await bookmarkPostUseCase(postId); + } else { + await unbookmarkPostUseCase(postId); + } + await savePostBookmarkLocalUseCase(postId, isBookmarked); + } catch (e) { + debugPrint('[HomeController] 북마크 토글 실패 - error: $e'); + _updatePostBookmarkInList(postId, !isBookmarked); + } + } + + void _updatePostBookmarkInList(int postId, bool isBookmark) { + final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); + if (cacheIndex != -1) { + _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( + bookmarkCount: + _cachePostList[cacheIndex].bookmarkCount + (isBookmark ? 1 : -1), + isBookmark: isBookmark, + ); + } + viewPostList.assignAll(_cachePostList); + } + Future loadRecommendUsers() async { _cacheProfileList.clear(); _cacheProfileList.addAll(await loadRecommendUsersUseCase.call()); diff --git a/lib/presentation/home/screens/home_screen.dart b/lib/presentation/home/screens/home_screen.dart index 2a5e9ae4..88cee739 100644 --- a/lib/presentation/home/screens/home_screen.dart +++ b/lib/presentation/home/screens/home_screen.dart @@ -57,7 +57,9 @@ class HomeScreen extends GetView { heartAction: (isFavorite, total) { controller.toggleLike(post.postId, isFavorite); }, - bookmarkAction: (isBookmark, total) {}, + bookmarkAction: (isBookmark, total) { + controller.toggleBookmark(post.postId, isBookmark); + }, ), ) .toList(), diff --git a/lib/presentation/post/controllers/post_controller.dart b/lib/presentation/post/controllers/post_controller.dart index 8bcb6f62..3bd1e63b 100644 --- a/lib/presentation/post/controllers/post_controller.dart +++ b/lib/presentation/post/controllers/post_controller.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:go_router/go_router.dart'; +import 'package:ondo/domain/entities/post/bookmark_changed_event.dart'; import 'package:ondo/domain/entities/post/like_changed_event.dart'; class PostController extends GetxController { @@ -9,6 +10,10 @@ class PostController extends GetxController { /// 화면으로 진입하므로, 좋아요 동기화 이벤트도 PostController에서 관리한다. final Rx lastLikeEvent = Rx(null); + /// 좋아요와 동일하게, 상세 화면에서 변경된 북마크 상태를 목록 화면들에 전파한다. + final Rx lastBookmarkEvent = + Rx(null); + void updateLikeState(int postId, bool isLiked, int likeCount) { lastLikeEvent.value = LikeChangedEvent( postId: postId, @@ -17,6 +22,14 @@ class PostController extends GetxController { ); } + void updateBookmarkState(int postId, bool isBookmarked, int bookmarkCount) { + lastBookmarkEvent.value = BookmarkChangedEvent( + postId: postId, + isBookmarked: isBookmarked, + bookmarkCount: bookmarkCount, + ); + } + Future?> enterPostDetail( BuildContext context, bool isMy, diff --git a/lib/presentation/post/controllers/post_view_controller.dart b/lib/presentation/post/controllers/post_view_controller.dart index ea6de5b1..82fdfdf6 100644 --- a/lib/presentation/post/controllers/post_view_controller.dart +++ b/lib/presentation/post/controllers/post_view_controller.dart @@ -10,7 +10,9 @@ 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/bookmarked_post_use_case.dart'; import 'package:ondo/domain/usecases/post/liked_post_use_case.dart'; +import 'package:ondo/domain/usecases/post/save_post_bookmark_local_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/post/controllers/post_controller.dart'; @@ -41,6 +43,8 @@ class PostViewController extends GetxController { final DeleteCommentUseCase _deleteCommentUseCase; final LikedPostUseCase _likedPostUseCase; final SavePostLikeLocalUseCase _savePostLikeLocalUseCase; + final BookmarkedPostUseCase _bookmarkedPostUseCase; + final SavePostBookmarkLocalUseCase _savePostBookmarkLocalUseCase; PostViewController({ required this.postId, @@ -57,6 +61,8 @@ class PostViewController extends GetxController { required DeleteCommentUseCase deleteCommentUseCase, required LikedPostUseCase likedPostUseCase, required SavePostLikeLocalUseCase savePostLikeLocalUseCase, + required BookmarkedPostUseCase bookmarkedPostUseCase, + required SavePostBookmarkLocalUseCase savePostBookmarkLocalUseCase, }) : _getPostDetailUseCase = getPostDetailUseCase, _updatePostUseCase = updatePostUseCase, _deletePostUseCase = deletePostUseCase, @@ -68,7 +74,9 @@ class PostViewController extends GetxController { _createCommentUseCase = createCommentUseCase, _deleteCommentUseCase = deleteCommentUseCase, _likedPostUseCase = likedPostUseCase, - _savePostLikeLocalUseCase = savePostLikeLocalUseCase; + _savePostLikeLocalUseCase = savePostLikeLocalUseCase, + _bookmarkedPostUseCase = bookmarkedPostUseCase, + _savePostBookmarkLocalUseCase = savePostBookmarkLocalUseCase; final Rx post = Rx(null); final RxList relatedPostList = [].obs; @@ -105,13 +113,15 @@ class PostViewController extends GetxController { } Future _initPostState() async { - // 좋아요 여부(로컬 캐시)와 상세 정보(서버)를 함께 받은 뒤, - // 상세에서 조회한 최신 좋아요 수를 게시물 목록 화면들에 동기화한다. + // 좋아요·북마크 여부(로컬 캐시)와 상세 정보(서버)를 함께 받은 뒤, + // 상세에서 조회한 최신 좋아요/북마크 수를 게시물 목록 화면들에 동기화한다. await Future.wait([ _initIsFavorite(), + _initIsBookmark(), fetchPostDetail(postId), ]); _syncLikeToList(); + _syncBookmarkToList(); } Future _initIsFavorite() async { @@ -120,6 +130,10 @@ class PostViewController extends GetxController { initialHeartState = liked; } + Future _initIsBookmark() async { + selectBookMark.value = await _bookmarkedPostUseCase(postId); + } + /// 상세에서 조회한 최신 좋아요 상태/수를 목록 화면 캐시에 반영한다. /// (토글하지 않고 단순 조회만 한 경우에도 미리보기 count가 상세와 일치하도록) void _syncLikeToList() { @@ -130,6 +144,15 @@ class PostViewController extends GetxController { ); } + /// 좋아요와 동일하게, 상세에서의 북마크 상태/수를 목록 화면 캐시에 반영한다. + void _syncBookmarkToList() { + Get.find().updateBookmarkState( + postId, + selectBookMark.value, + bookMarkTotal.value, + ); + } + Future fetchPostDetail(int postId) async { isLoading.value = true; @@ -210,6 +233,13 @@ class PostViewController extends GetxController { } else { await _unbookmarkPostUseCase(postId); } + await _savePostBookmarkLocalUseCase(postId, isBookmarked); + + Get.find().updateBookmarkState( + postId, + isBookmarked, + bookMarkTotal.value, + ); } catch (e) { debugPrint('[PostViewController] 북마크 토글 실패 - error: $e'); selectBookMark.value = !isBookmarked; diff --git a/lib/presentation/post/widgets/post_item.dart b/lib/presentation/post/widgets/post_item.dart index c206fbc3..922ba101 100644 --- a/lib/presentation/post/widgets/post_item.dart +++ b/lib/presentation/post/widgets/post_item.dart @@ -58,6 +58,7 @@ class PostItem extends GetView { final visibleSkills = post.tags.take(2).toList(); final remainCount = post.tags.length - 2; + return Row( children: [ Expanded( From a8486e88d38f4d376aa2b43621305f938bc0e6d7 Mon Sep 17 00:00:00 2001 From: ryusuye0n Date: Tue, 23 Jun 2026 11:28:12 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=EC=A4=91=EB=B3=B5=20=ED=98=B8?= =?UTF-8?q?=EC=B6=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../community/controllers/community_controller.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/presentation/community/controllers/community_controller.dart b/lib/presentation/community/controllers/community_controller.dart index 6211fb31..d511978d 100644 --- a/lib/presentation/community/controllers/community_controller.dart +++ b/lib/presentation/community/controllers/community_controller.dart @@ -93,13 +93,21 @@ class CommunityController extends GetxController { } void _syncBookmark(int postId, bool isBookmarked, int bookmarkCount) { + final index = viewPostList.indexWhere((p) => p.postId == postId); + if (index != -1) { + viewPostList[index] = viewPostList[index].copyWith( + bookmarkCount: bookmarkCount, + isBookmark: isBookmarked, + ); + viewPostList.refresh(); + } + final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); if (cacheIndex != -1) { _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( bookmarkCount: bookmarkCount, isBookmark: isBookmarked, ); - viewPostList.assignAll(_cachePostList); } } From e8c8b82cc1e4dfc57b3eeb52b8c0e7278691d39b Mon Sep 17 00:00:00 2001 From: ryusuye0n Date: Tue, 23 Jun 2026 11:30:04 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20=EB=8F=99=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EB=B0=A9=EC=96=B4=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/presentation/post/controllers/post_view_controller.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/presentation/post/controllers/post_view_controller.dart b/lib/presentation/post/controllers/post_view_controller.dart index 82fdfdf6..39de746e 100644 --- a/lib/presentation/post/controllers/post_view_controller.dart +++ b/lib/presentation/post/controllers/post_view_controller.dart @@ -120,8 +120,10 @@ class PostViewController extends GetxController { _initIsBookmark(), fetchPostDetail(postId), ]); - _syncLikeToList(); - _syncBookmarkToList(); + if (post.value != null) { + _syncLikeToList(); + _syncBookmarkToList(); + } } Future _initIsFavorite() async {