diff --git a/lib/core/design_system/components/custom_icon_button.dart b/lib/core/design_system/components/custom_icon_button.dart index d9255a3f..b615825e 100644 --- a/lib/core/design_system/components/custom_icon_button.dart +++ b/lib/core/design_system/components/custom_icon_button.dart @@ -2,8 +2,15 @@ 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 { const CustomIconButton({ @@ -23,62 +30,65 @@ class CustomIconButton extends StatefulWidget { final int total; final bool initialIsSelected; final Color activeColor; - final void Function(bool isSelect, int total)? action; + final void Function( + bool isSelect, + int total, + )? action; @override State createState() => _CustomIconButtonState(); } class _CustomIconButtonState extends State { - late int total; - late bool isSelect; + late bool _isSelected; + late int _total; @override void initState() { super.initState(); - total = widget.total; - isSelect = widget.initialIsSelected; + _isSelected = widget.initialIsSelected; + _total = widget.total; } @override void didUpdateWidget(CustomIconButton oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.total != widget.total) { - total = widget.total; - } + // 외부(컨트롤러)에서 갱신된 값이 들어오면 내부 상태에 반영한다. if (oldWidget.initialIsSelected != widget.initialIsSelected) { - isSelect = 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: () { - setState(() { - isSelect = !isSelect; - isSelect ? total += 1 : total -= 1; - }); - widget.action?.call(isSelect, total); - }, + onTap: _onTap, child: Image.asset( widget.imagePath, - color: isSelect ? widget.activeColor : AppColors.gray50, + color: _isSelected ? widget.activeColor : AppColors.gray50, height: widget.iconSize, width: widget.iconSize, ), ), AppGap.h4, Text( - "$total", - style: TextStyle( - fontSize: widget.totalStyle.fontSize, - fontWeight: widget.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/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 bfa7f6c6..21e1418f 100644 --- a/lib/core/router/bindings/navigation_binding.dart +++ b/lib/core/router/bindings/navigation_binding.dart @@ -15,21 +15,23 @@ import 'package:ondo/data/datasource/base/auth_local_datasource.dart'; import 'package:ondo/data/network/clients/auth_client.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), @@ -38,7 +40,11 @@ class NavigationBinding extends Bindings { ); /// 전 화면 공통 controller 등록 - Get.lazyPut(() => NavigationController()); + Get.lazyPut( + () => NavigationController(), + ); + + NotificationBinding().dependencies(); NotificationBinding().dependencies(); ReportBinding().dependencies(); @@ -51,4 +57,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..db20b677 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,57 @@ 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(), - ); + /// 좋아요 상태 동기화 이벤트를 전역으로 유지해야 하므로 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 ce74e1a6..6aa0acd0 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'; @@ -21,76 +23,60 @@ 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() { // 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(), @@ -102,7 +88,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/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/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 b9c81299..b1c64dd2 100644 --- a/lib/presentation/community/controllers/community_controller.dart +++ b/lib/presentation/community/controllers/community_controller.dart @@ -2,37 +2,38 @@ 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/screens/community_post_create_screen.dart'; -import 'package:ondo/presentation/home/controllers/home_controller.dart'; +import 'package:ondo/presentation/post/controllers/post_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, - }) : _likeUseCase = likeUseCase, - _unlikeUseCase = unlikeUseCase, - _getRecommendPostsUseCase = getRecommendPostsUseCase, - _savePostLikeLocalUseCase = savePostLikeLocalUseCase, - _getCachedLikedPostIdsUseCase = getCachedLikedPostIdsUseCase; - - final Set viewTagList = {}.obs; + required LikedPostUseCase likedPostUseCase, + }) : _likeUseCase = likeUseCase, + _unlikeUseCase = unlikeUseCase, + _getRecommendPostsUseCase = getRecommendPostsUseCase, + _savePostLikeLocalUseCase = savePostLikeLocalUseCase, + _likedPostUseCase = likedPostUseCase; + + final RxSet viewTagList = {}.obs; final RxSet selectTagList = {}.obs; + final List _cachePostList = []; final RxList viewPostList = [].obs; @@ -40,18 +41,31 @@ class CommunityController extends GetxController { final RxBool isLastPage = false.obs; int _currentPage = 0; - Set _cachedLikedIds = {}; @override void onInit() { super.onInit(); viewTagList.addAll(_getTags()); - _loadCacheAndFetch(); + loadRecommendPostList(); + + ever( + Get.find().lastLikeEvent, + (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 @@ -69,6 +83,7 @@ class CommunityController extends GetxController { } if (isLastPage.value) return; + isLoading.value = true; try { @@ -77,56 +92,45 @@ class CommunityController extends GetxController { size: 20, ); - // 로컬 캐시 기반으로 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); - } - 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); isLastPage.value = result.last ?? true; - _currentPage++; } catch (e) { - debugPrint( - '[CommunityController] 게시물 조회 실패 - error: $e', - ); + debugPrint('[CommunityController] 게시물 조회 실패 - error: $e'); } finally { isLoading.value = false; } } + @override + Future refresh() async { + await loadRecommendPostList(refresh: true); + } + Future refreshPosts() => loadRecommendPostList(refresh: true); void enterPostCreate() { - Get.delete( - force: true, - ); - + Get.delete(force: true); Get.lazyPut( - () => CommunityPostCreateController( + () => CommunityPostCreateController( createUseCase: Get.find(), updateUseCase: Get.find(), ), ); - Get.to(() => CommunityPostCreateScreen()); } - Future toggleLike( - int postId, - bool isLiked, - ) async { - // 옵티미스틱 업데이트: API 결과 전에 즉시 UI 반영 + Future toggleLike(int postId, bool isLiked) async { _updatePostLikeInList(postId, isLiked ? 1 : -1, isLiked); try { @@ -135,54 +139,24 @@ class CommunityController extends GetxController { } else { await _unlikeUseCase(postId); } - - // 로컬 캐시 갱신 및 영속화 - if (isLiked) { - _cachedLikedIds.add(postId); - } else { - _cachedLikedIds.remove(postId); - } await _savePostLikeLocalUseCase(postId, isLiked); - - // 홈 목록과 cross-sync - if (Get.isRegistered()) { - final post = viewPostList.firstWhereOrNull((p) => p.postId == postId); - if (post != null) { - Get.find().updatePostLike( - postId, - post.likeCount, - isLiked, - ); - } - } } catch (e) { - debugPrint( - '[CommunityController] 좋아요 토글 실패 - error: $e', - ); - - // API 실패 시 롤백 + debugPrint('[CommunityController] 좋아요 토글 실패 - error: $e'); _updatePostLikeInList(postId, isLiked ? -1 : 1, !isLiked); } } - void _updatePostLikeInList( - int postId, - int delta, - bool isFavorite, - ) { + 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, @@ -191,78 +165,28 @@ class CommunityController extends GetxController { } } - Future updatePostLike( - int postId, - int likeCount, - bool isFavorite, - ) async { - // 로컬 캐시 동기화 (PostViewController → CommunityController 방향) - if (isFavorite) { - _cachedLikedIds.add(postId); - } else { - _cachedLikedIds.remove(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.refresh(); - } - - final cacheIndex = _cachePostList.indexWhere((p) => p.postId == postId); - - if (cacheIndex != -1) { - _cachePostList[cacheIndex] = _cachePostList[cacheIndex].copyWith( - likeCount: likeCount, - 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) { final Set result = {}; - result.addAllIf( searchList.isNotEmpty, _cachePostList.where( - (post) => + (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().updateResult(result); } - void filterPostTag( - String tag, - bool isSelect, - ) { + void filterPostTag(String tag, bool isSelect) { isSelect ? selectTagList.add(tag) : selectTagList.remove(tag); if (selectTagList.isEmpty) { @@ -273,18 +197,12 @@ class CommunityController extends GetxController { final result = _cachePostList .where( (post) => - selectTagList.any( - (t) => post.title.contains(t), - ) || - selectTagList.any( - (t) => post.authorName.contains(t), - ) || - selectTagList.any( - (t) => post.tags.any( - (skill) => skill.contains(t), - ), - ), - ) + selectTagList.any((t) => post.title.contains(t)) || + selectTagList.any((t) => post.authorName.contains(t)) || + selectTagList.any( + (t) => post.tags.any((skill) => skill.contains(t)), + ), + ) .toList(); viewPostList.assignAll(result); @@ -294,9 +212,7 @@ class CommunityController extends GetxController { class CommunityResultController extends GetxController { final RxList viewPosts = [].obs; - void updateResult( - Iterable results, - ) { + void updateResult(Iterable results) { viewPosts.assignAll(results); } } @@ -307,4 +223,4 @@ List _getTags() => [ "UIUX", "공부잘하는법", "FrontEnd", -]; +]; \ 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..7d20757d 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: 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 8069fece..4e60af4f 100644 --- a/lib/presentation/home/controllers/home_controller.dart +++ b/lib/presentation/home/controllers/home_controller.dart @@ -5,22 +5,21 @@ 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/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; 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().lastLikeEvent, + (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,26 +98,25 @@ 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); } + @override + Future refresh() async { + await _loadRecommendPostList(refresh: true); + } + Future toggleLike(int postId, bool isLiked) async { - // 옵티미스틱 업데이트 - _updatePostLikeInList(postId, isLiked ? 1 : -1, isLiked); + _updatePostLikeInList(postId, isLiked); try { if (isLiked) { @@ -111,60 +124,24 @@ class HomeController extends GetxController with BaseHomeController { } else { await unlikePostUseCase(postId); } - - if (isLiked) { - _cachedLikedIds.add(postId); - } else { - _cachedLikedIds.remove(postId); - } await savePostLikeLocalUseCase(postId, isLiked); - - // 커뮤니티 목록과 cross-sync - 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); + _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, ); } viewPostList.assignAll(_cachePostList); } - void updatePostLike(int postId, int likeCount, bool isFavorite) { - final index = viewPostList.indexWhere((p) => p.postId == postId); - if (index != -1) { - viewPostList[index] = viewPostList[index].copyWith( - likeCount: likeCount, - isFavorite: isFavorite, - ); - 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()); @@ -174,8 +151,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), ); @@ -185,7 +160,6 @@ class HomeController extends GetxController with BaseHomeController { sort: "latest", ); - ///홈 검색 결과 표시 controller searchResultController.updateResult( postResult.content, userRes, @@ -195,12 +169,11 @@ class HomeController extends GetxController with BaseHomeController { class HomeSearchResultController extends GetxController with BaseHomeController { - ///홈 검색 결과 업데이트 void updateResult( - Iterable posts, - Iterable profiles, - ) { + Iterable posts, + Iterable profiles, + ) { 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 6f6a53cd..2a5e9ae4 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,29 +38,34 @@ class HomeScreen extends GetView { children: [ HomePostRankList(), AppGap.v16, - HomeProfileList(title: "커피챗 추천", controller: controller), + + HomeProfileList( + title: "커피챗 추천", + controller: controller, + ), + AppGap.v16, + Obx( - () => PostGridList( + () => PostGridList( title: "추천 게시물", list: controller.viewPostList .map( (post) => PostItem( - post: post, - bookmarkAction: (isBookmark, total) { - //TODO : 북마크 api 개발 이후 구현 - }, - heartAction: (isFavorite, total) { - controller.toggleLike(post.postId, isFavorite); - }, - isMy: true, - ), - ) + 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_controller.dart b/lib/presentation/post/controllers/post_controller.dart index 18386710..8bcb6f62 100644 --- a/lib/presentation/post/controllers/post_controller.dart +++ b/lib/presentation/post/controllers/post_controller.dart @@ -1,14 +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 { - void enterPostDetail( + /// 상세 화면에서 변경된 좋아요 상태를 게시물 목록 화면들에 전파하기 위한 이벤트. + /// 게시물 목록 화면 컨트롤러(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, - }) { - context.push('/post/$postId', extra: isFavorite); + int postId, + ) async { + final result = await context.push>( + '/post/$postId', + ); + return result; } } diff --git a/lib/presentation/post/controllers/post_view_controller.dart b/lib/presentation/post/controllers/post_view_controller.dart index 954dccfc..ea6de5b1 100644 --- a/lib/presentation/post/controllers/post_view_controller.dart +++ b/lib/presentation/post/controllers/post_view_controller.dart @@ -10,9 +10,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/post/controllers/post_controller.dart'; import '../../../data/models/post/request/post_update_request_model.dart'; import '../../../domain/usecases/post/create_post_usecase.dart'; @@ -38,6 +39,8 @@ class PostViewController extends GetxController { final GetCommentsUseCase _getCommentsUseCase; final CreateCommentUseCase _createCommentUseCase; final DeleteCommentUseCase _deleteCommentUseCase; + final LikedPostUseCase _likedPostUseCase; + final SavePostLikeLocalUseCase _savePostLikeLocalUseCase; PostViewController({ required this.postId, @@ -52,16 +55,20 @@ class PostViewController extends GetxController { required GetCommentsUseCase getCommentsUseCase, 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; + required LikedPostUseCase likedPostUseCase, + required SavePostLikeLocalUseCase savePostLikeLocalUseCase, + }) : _getPostDetailUseCase = getPostDetailUseCase, + _updatePostUseCase = updatePostUseCase, + _deletePostUseCase = deletePostUseCase, + _likePostUseCase = likePostUseCase, + _unlikePostUseCase = unlikePostUseCase, + _bookmarkPostUseCase = bookmarkPostUseCase, + _unbookmarkPostUseCase = unbookmarkPostUseCase, + _getCommentsUseCase = getCommentsUseCase, + _createCommentUseCase = createCommentUseCase, + _deleteCommentUseCase = deleteCommentUseCase, + _likedPostUseCase = likedPostUseCase, + _savePostLikeLocalUseCase = savePostLikeLocalUseCase; final Rx post = Rx(null); final RxList relatedPostList = [].obs; @@ -87,112 +94,87 @@ 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}', - ); - - fetchPostDetail(postId); + _initPostState(); fetchComments(); } - bool _resolveIsFavorite() { - if (Get.isRegistered()) { - final post = Get.find().viewPostList - .firstWhereOrNull( - (p) => p.postId == postId, - ); - - if (post != null) return post.isFavorite; - } - - if (Get.isRegistered()) { - final post = Get.find().viewPostList.firstWhereOrNull( - (p) => p.postId == postId, - ); + Future _initPostState() async { + // 좋아요 여부(로컬 캐시)와 상세 정보(서버)를 함께 받은 뒤, + // 상세에서 조회한 최신 좋아요 수를 게시물 목록 화면들에 동기화한다. + await Future.wait([ + _initIsFavorite(), + fetchPostDetail(postId), + ]); + _syncLikeToList(); + } - if (post != null) return post.isFavorite; - } + Future _initIsFavorite() async { + final liked = await _likedPostUseCase(postId); + selectHeart.value = liked; + initialHeartState = liked; + } - return initialIsFavorite; + /// 상세에서 조회한 최신 좋아요 상태/수를 목록 화면 캐시에 반영한다. + /// (토글하지 않고 단순 조회만 한 경우에도 미리보기 count가 상세와 일치하도록) + void _syncLikeToList() { + Get.find().updateLikeState( + postId, + selectHeart.value, + heartTotal.value, + ); } + 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; postTags.assignAll(result.tags); heartTotal.value = result.likeCount; + bookMarkTotal.value = result.bookmarkCount; commentCount.value = result.commentCount; - selectHeart.value = result.isLike; } 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 { selectHeart.value = isLiked; - heartTotal.value = isLiked ? heartTotal.value + 1 : heartTotal.value - 1; try { @@ -201,29 +183,17 @@ class PostViewController extends GetxController { } else { await _unlikePostUseCase(postId); } + await _savePostLikeLocalUseCase(postId, isLiked); - if (Get.isRegistered()) { - await Get.find().updatePostLike( - postId, - heartTotal.value, - isLiked, - ); - } - if (Get.isRegistered()) { - 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; } } @@ -240,15 +210,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 @@ -256,6 +219,7 @@ class PostViewController extends GetxController { } } + Future updatePost({ required String title, required String content, @@ -265,10 +229,6 @@ class PostViewController extends GetxController { errorMessage.value = ''; try { - debugPrint( - '[PostViewController] 게시물 수정 요청 - postId: $postId', - ); - await _updatePostUseCase( postId, PostUpdateRequestModel( @@ -277,48 +237,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; @@ -329,53 +273,31 @@ 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); - - comments.removeWhere((c) => c.id == comment.id); - + await fetchComments(); commentCount.value = comments.length; - - debugPrint('[PostViewController] 댓글 삭제 성공'); } catch (e) { - debugPrint( - '[PostViewController] 댓글 삭제 실패 - error: $e', - ); + debugPrint('[PostViewController] 댓글 삭제 실패 - error: $e'); } } + void reportPost(BuildContext context) { showDialog( context: context, @@ -396,10 +318,11 @@ class PostViewController extends GetxController { ); } + void editPost() { Get.delete(force: true); Get.lazyPut( - () => CommunityPostCreateController( + () => CommunityPostCreateController( createUseCase: Get.find(), updateUseCase: Get.find(), isEditMode: true, @@ -411,4 +334,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 138d022a..076e659f 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, + onPopInvokedWithResult: (didPop, result) { + if (!didPop) _onBack(); + }, + child: BaseScaffold( + body: SingleChildScrollView( + child: Column( + children: [ + ..._top(), + AppGap.v16, + _body(), + RelatedPostList(), + ], + ), ), ), ); @@ -44,6 +63,7 @@ class _PostDetailScreenState extends State { List _top() => [ //TODO : controller에서 publicId를 받아오면, 분기 처리 CustomBackButton( + backAction: _onBack, moreOptions: true, itemBuilder: (context) => [ _topPopupItem("게시물 수정하기", controller.editPost), @@ -71,4 +91,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_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/lib/presentation/post/widgets/post_item.dart b/lib/presentation/post/widgets/post_item.dart index 3925c986..c206fbc3 100644 --- a/lib/presentation/post/widgets/post_item.dart +++ b/lib/presentation/post/widgets/post_item.dart @@ -26,12 +26,11 @@ class PostItem extends GetView { @override Widget build(BuildContext context) { return GestureDetector( - onTap: () { - controller.enterPostDetail( + onTap: () async { + await controller.enterPostDetail( context, isMy, post.postId, - isFavorite: post.isFavorite, ); }, child: Container( @@ -125,4 +124,4 @@ class PostItem extends GetView { ], ); } -} +} \ No newline at end of file