diff --git a/lib/core/router/bindings/community_binding.dart b/lib/core/router/bindings/community_binding.dart index b984b24a..a0997900 100644 --- a/lib/core/router/bindings/community_binding.dart +++ b/lib/core/router/bindings/community_binding.dart @@ -6,6 +6,10 @@ 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_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'; @@ -21,6 +25,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(), postSearchUseCase: 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 2a7c89e1..1b772910 100644 --- a/lib/core/router/bindings/post_binding.dart +++ b/lib/core/router/bindings/post_binding.dart @@ -4,6 +4,7 @@ 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/create_post_usecase.dart'; import 'package:ondo/domain/usecases/post/like_post_usecase.dart'; import 'package:ondo/domain/usecases/post/update_post_usecase.dart'; @@ -11,6 +12,7 @@ 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'; @@ -62,6 +64,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 b2333bda..3651f8c5 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'; @@ -40,6 +42,10 @@ class PostViewBinding extends Bindings { () => SavePostLikeLocalUseCase(Get.find()), ); + Get.lazyPut( + () => SavePostBookmarkLocalUseCase(Get.find()), + ); + // Comment DataSource Get.lazyPut( () => CommentRemoteDataSourceImpl(Get.find()), @@ -81,6 +87,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 0f6fce95..ae8c6b8e 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/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 9e20180d..cc265a21 100644 --- a/lib/presentation/community/controllers/community_controller.dart +++ b/lib/presentation/community/controllers/community_controller.dart @@ -9,6 +9,10 @@ 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/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'; @@ -19,6 +23,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; final PostSearchUseCase _postSearchUseCase; CommunityController({ @@ -27,12 +35,20 @@ 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, required PostSearchUseCase postSearchUseCase, }) : _likeUseCase = likeUseCase, _unlikeUseCase = unlikeUseCase, _getRecommendPostsUseCase = getRecommendPostsUseCase, _savePostLikeLocalUseCase = savePostLikeLocalUseCase, _likedPostUseCase = likedPostUseCase, + _bookmarkUseCase = bookmarkUseCase, + _unbookmarkUseCase = unbookmarkUseCase, + _savePostBookmarkLocalUseCase = savePostBookmarkLocalUseCase, + _bookmarkedPostUseCase = bookmarkedPostUseCase, _postSearchUseCase = postSearchUseCase; final RxSet viewTagList = {}.obs; @@ -60,6 +76,15 @@ 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); + }, + ); + ever( Get.find().lastDeleteEvent, (postId) { @@ -93,6 +118,25 @@ 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, + ); + } + } + @override void onReady() { Get.put(CommunityResultController()); @@ -122,6 +166,7 @@ class CommunityController extends GetxController { result.content.map((post) async { return post.copyWith( isFavorite: await _likedPostUseCase(post.postId), + isBookmark: await _bookmarkedPostUseCase(post.postId), ); }), ); @@ -192,6 +237,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 273d62bf..2fc0e49b 100644 --- a/lib/presentation/community/widgets/community_post_list.dart +++ b/lib/presentation/community/widgets/community_post_list.dart @@ -37,6 +37,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 cc38f415..9015e646 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, }); @@ -72,6 +84,15 @@ 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); + }, + ); + ever( Get.find().lastDeleteEvent, (postId) { @@ -108,6 +129,17 @@ class HomeController extends GetxController with BaseHomeController { _updateRankPostLikeInList(postId, isLiked, likeCount: likeCount); } + 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 { isLoadingRanks.value = true; rankErrorMessage.value = ''; @@ -152,6 +184,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), ); }), ); @@ -220,6 +253,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); + } + void _updateRankPostLikeInList(int postId, bool isFavorite, {int? likeCount}) { final index = recentPopularPostList.indexWhere((p) => p.postId == postId); if (index == -1) return; diff --git a/lib/presentation/home/screens/home_screen.dart b/lib/presentation/home/screens/home_screen.dart index 16a93530..f209d332 100644 --- a/lib/presentation/home/screens/home_screen.dart +++ b/lib/presentation/home/screens/home_screen.dart @@ -79,7 +79,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 15db8e9d..2735c090 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'; import 'package:ondo/domain/entities/post/post_updated_event.dart'; @@ -8,6 +9,10 @@ class PostController extends GetxController { /// 상세 화면에서 변경된 좋아요 상태를 게시물 목록 화면들에 전파 final Rx lastLikeEvent = Rx(null); + /// 좋아요와 동일하게, 상세 화면에서 변경된 북마크 상태를 목록 화면들에 전파한다. + final Rx lastBookmarkEvent = + Rx(null); + /// 게시물 삭제 이벤트 — postId final Rx lastDeleteEvent = Rx(null); @@ -22,6 +27,14 @@ class PostController extends GetxController { ); } + void updateBookmarkState(int postId, bool isBookmarked, int bookmarkCount) { + lastBookmarkEvent.value = BookmarkChangedEvent( + postId: postId, + isBookmarked: isBookmarked, + bookmarkCount: bookmarkCount, + ); + } + void notifyPostDeleted(int postId) { lastDeleteEvent.value = postId; } diff --git a/lib/presentation/post/controllers/post_view_controller.dart b/lib/presentation/post/controllers/post_view_controller.dart index cab1e199..d55eea2f 100644 --- a/lib/presentation/post/controllers/post_view_controller.dart +++ b/lib/presentation/post/controllers/post_view_controller.dart @@ -11,7 +11,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'; @@ -42,6 +44,8 @@ class PostViewController extends GetxController { final DeleteCommentUseCase _deleteCommentUseCase; final LikedPostUseCase _likedPostUseCase; final SavePostLikeLocalUseCase _savePostLikeLocalUseCase; + final BookmarkedPostUseCase _bookmarkedPostUseCase; + final SavePostBookmarkLocalUseCase _savePostBookmarkLocalUseCase; PostViewController({ required this.postId, @@ -58,6 +62,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, @@ -69,7 +75,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; @@ -115,13 +123,17 @@ class PostViewController extends GetxController { } Future _initPostState() async { - // 좋아요 여부(로컬 캐시)와 상세 정보(서버)를 함께 받은 뒤, - // 상세에서 조회한 최신 좋아요 수를 게시물 목록 화면들에 동기화한다. + // 좋아요·북마크 여부(로컬 캐시)와 상세 정보(서버)를 함께 받은 뒤, + // 상세에서 조회한 최신 좋아요/북마크 수를 게시물 목록 화면들에 동기화한다. await Future.wait([ _initIsFavorite(), + _initIsBookmark(), fetchPostDetail(postId), ]); - _syncLikeToList(); + if (post.value != null) { + _syncLikeToList(); + _syncBookmarkToList(); + } } Future _initIsFavorite() async { @@ -130,6 +142,10 @@ class PostViewController extends GetxController { initialHeartState = liked; } + Future _initIsBookmark() async { + selectBookMark.value = await _bookmarkedPostUseCase(postId); + } + /// 상세에서 조회한 최신 좋아요 상태/수를 목록 화면 캐시에 반영한다. /// (토글하지 않고 단순 조회만 한 경우에도 미리보기 count가 상세와 일치하도록) void _syncLikeToList() { @@ -140,6 +156,15 @@ class PostViewController extends GetxController { ); } + /// 좋아요와 동일하게, 상세에서의 북마크 상태/수를 목록 화면 캐시에 반영한다. + void _syncBookmarkToList() { + Get.find().updateBookmarkState( + postId, + selectBookMark.value, + bookMarkTotal.value, + ); + } + Future fetchPostDetail(int postId) async { isLoading.value = true; @@ -254,6 +279,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(