diff --git a/app/src/main/java/com/teamwss/websoso/ui/activityDetail/ActivityDetailActivity.kt b/app/src/main/java/com/teamwss/websoso/ui/activityDetail/ActivityDetailActivity.kt index b0001f340..f1be40ec6 100644 --- a/app/src/main/java/com/teamwss/websoso/ui/activityDetail/ActivityDetailActivity.kt +++ b/app/src/main/java/com/teamwss/websoso/ui/activityDetail/ActivityDetailActivity.kt @@ -3,16 +3,29 @@ package com.teamwss.websoso.ui.activityDetail import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.LayoutInflater import android.view.View +import android.view.WindowManager +import android.widget.PopupWindow import android.widget.TextView import androidx.activity.viewModels +import androidx.databinding.ViewDataBinding import com.teamwss.websoso.R import com.teamwss.websoso.R.string.my_activity_detail_title import com.teamwss.websoso.R.string.other_user_page_activity import com.teamwss.websoso.common.ui.base.BaseActivity import com.teamwss.websoso.databinding.ActivityActivityDetailBinding +import com.teamwss.websoso.databinding.MenuMyActivityPopupBinding +import com.teamwss.websoso.databinding.MenuOtherUserActivityPopupBinding import com.teamwss.websoso.ui.activityDetail.adapter.ActivityDetailAdapter +import com.teamwss.websoso.ui.createFeed.CreateFeedActivity import com.teamwss.websoso.ui.feedDetail.FeedDetailActivity +import com.teamwss.websoso.ui.feedDetail.model.EditFeedModel +import com.teamwss.websoso.ui.main.feed.dialog.FeedRemoveDialogFragment +import com.teamwss.websoso.ui.main.feed.dialog.FeedReportDialogFragment +import com.teamwss.websoso.ui.main.feed.dialog.FeedReportDoneDialogFragment +import com.teamwss.websoso.ui.main.feed.dialog.RemoveMenuType +import com.teamwss.websoso.ui.main.feed.dialog.ReportMenuType import com.teamwss.websoso.ui.main.myPage.MyPageViewModel import com.teamwss.websoso.ui.main.myPage.myActivity.ActivityItemClickListener import com.teamwss.websoso.ui.main.myPage.myActivity.MyActivityFragment @@ -38,6 +51,7 @@ class ActivityDetailActivity : intent.getStringExtra(MyActivityFragment.EXTRA_SOURCE) ?: "" } private val userId: Long by lazy { intent.getLongExtra(USER_ID_KEY, DEFAULT_USER_ID) } + private var _popupWindow: PopupWindow? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -74,9 +88,9 @@ class ActivityDetailActivity : } private fun setupObserver() { - activityDetailViewModel.userActivity.observe(this) { activities -> + activityDetailViewModel.activityDetailUiState.observe(this) { uiState -> val userProfile = getUserProfile() - updateAdapterWithActivitiesAndProfile(activities, userProfile) + updateAdapterWithActivitiesAndProfile(uiState.activities, userProfile) } when (activityDetailViewModel.source) { @@ -85,7 +99,7 @@ class ActivityDetailActivity : uiState.myProfile?.let { myProfile -> val userProfile = myProfile.toUserProfileModel() updateAdapterWithActivitiesAndProfile( - activityDetailViewModel.userActivity.value, + activityDetailViewModel.activityDetailUiState.value?.activities, userProfile ) } @@ -97,8 +111,8 @@ class ActivityDetailActivity : otherUserProfile?.let { val userProfile = otherUserProfile.toUserProfileModel() updateAdapterWithActivitiesAndProfile( - activityDetailViewModel.userActivity.value, - userProfile + activityDetailViewModel.activityDetailUiState.value?.activities, + userProfile, ) } } @@ -162,18 +176,113 @@ class ActivityDetailActivity : likeCountTextView.text = updatedLikeCount.toString() view.isSelected = !view.isSelected - activityDetailViewModel.updateActivityLike( - view.isSelected, - feedId, - updatedLikeCount, - ) + activityDetailViewModel.updateActivityLike(view.isSelected, feedId, updatedLikeCount) } override fun onMoreButtonClick(view: View, feedId: Long) { - // TODO 팝업메뉴 수정 및 차단 + showPopupMenu(view, feedId) + } + } + + private fun showPopupMenu(view: View, feedId: Long) { + val inflater = LayoutInflater.from(this) + val binding = when (source) { + SOURCE_MY_ACTIVITY -> MenuMyActivityPopupBinding.inflate(inflater) + SOURCE_OTHER_USER_ACTIVITY -> MenuOtherUserActivityPopupBinding.inflate(inflater) + else -> return + } + + _popupWindow?.dismiss() + _popupWindow = PopupWindow( + binding.root, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + true + ).apply { + elevation = 2f + showAsDropDown(view) + } + setupPopupMenuClickListeners(binding, feedId) + } + + private fun setupPopupMenuClickListeners(binding: ViewDataBinding, feedId: Long) { + when (binding) { + is MenuMyActivityPopupBinding -> { + binding.tvMyActivityModification.setOnClickListener { + navigateToFeedEdit(feedId) + _popupWindow?.dismiss() + } + binding.tvMyActivityPopupDeletion.setOnClickListener { + showRemoveDialog(feedId) + _popupWindow?.dismiss() + } + } + + is MenuOtherUserActivityPopupBinding -> { + binding.tvOtherUserActivityReportSpoiler.setOnClickListener { + showReportDialog(feedId, ReportMenuType.SPOILER_FEED.name) + _popupWindow?.dismiss() + } + binding.tvOtherUserActivityReportExpression.setOnClickListener { + showReportDialog(feedId, ReportMenuType.IMPERTINENCE_FEED.name) + _popupWindow?.dismiss() + } + } } } + private fun navigateToFeedEdit(feedId: Long) { + val activityModel = + activityDetailViewModel.activityDetailUiState.value?.activities?.find { it.feedId == feedId } + activityModel?.let { feed -> + val editFeedModel = EditFeedModel( + feedId = feed.feedId, + novelId = feed.novelId ?: 0L, + novelTitle = feed.title ?: "", + feedContent = feed.feedContent, + feedCategory = feed.relevantCategories?.split(", ") ?: emptyList() + ) + startActivity(CreateFeedActivity.getIntent(this, editFeedModel)) + } ?: throw IllegalArgumentException("Feed not found") + } + + private fun showRemoveDialog(feedId: Long) { + val dialogFragment = FeedRemoveDialogFragment.newInstance( + menuType = RemoveMenuType.REMOVE_FEED.name, + event = { + activityDetailViewModel.updateRemovedFeed(feedId) + } + ) + dialogFragment.show(supportFragmentManager, FeedRemoveDialogFragment.TAG) + } + + private fun showReportDialog(feedId: Long, menuType: String) { + val dialogFragment = FeedReportDialogFragment.newInstance( + menuType = menuType, + event = { + when (menuType) { + ReportMenuType.SPOILER_FEED.name -> activityDetailViewModel.updateReportedSpoilerFeed( + feedId + ) + + ReportMenuType.IMPERTINENCE_FEED.name -> activityDetailViewModel.updateReportedImpertinenceFeed( + feedId + ) + } + showReportDoneDialog(menuType) + } + ) + dialogFragment.show(supportFragmentManager, FeedReportDialogFragment.TAG) + } + + private fun showReportDoneDialog(menuType: String) { + val doneDialogFragment = FeedReportDoneDialogFragment.newInstance( + menuType = menuType, + event = {} + ) + doneDialogFragment.show(supportFragmentManager, FeedReportDoneDialogFragment.TAG) + } + companion object { const val USER_ID_KEY = "userId" const val DEFAULT_USER_ID = -1L diff --git a/app/src/main/java/com/teamwss/websoso/ui/activityDetail/ActivityDetailViewModel.kt b/app/src/main/java/com/teamwss/websoso/ui/activityDetail/ActivityDetailViewModel.kt index 432554326..590cff247 100644 --- a/app/src/main/java/com/teamwss/websoso/ui/activityDetail/ActivityDetailViewModel.kt +++ b/app/src/main/java/com/teamwss/websoso/ui/activityDetail/ActivityDetailViewModel.kt @@ -8,7 +8,7 @@ import androidx.lifecycle.viewModelScope import com.teamwss.websoso.data.repository.FeedRepository import com.teamwss.websoso.data.repository.UserRepository import com.teamwss.websoso.ui.activityDetail.ActivityDetailActivity.Companion.SOURCE_MY_ACTIVITY -import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivitiesModel.ActivityModel +import com.teamwss.websoso.ui.activityDetail.model.ActivityDetailUiState import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivityLikeState import com.teamwss.websoso.ui.mapper.toUi import dagger.hilt.android.lifecycle.HiltViewModel @@ -22,15 +22,12 @@ class ActivityDetailViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, ) : ViewModel() { - private val _userActivity = MutableLiveData>() - val userActivity: LiveData> get() = _userActivity + private val _activityDetailUiState = MutableLiveData() + val activityDetailUiState: LiveData get() = _activityDetailUiState private val _likeState = MutableLiveData() val likeState: LiveData get() = _likeState - private val _lastFeedId: MutableLiveData = MutableLiveData(0L) - val lastFeedId: LiveData get() = _lastFeedId - private val size: Int = ACTIVITY_LOAD_SIZE var source: String? @@ -45,6 +42,10 @@ class ActivityDetailViewModel @Inject constructor( savedStateHandle["userId"] = value } + init { + _activityDetailUiState.value = ActivityDetailUiState() + } + fun updateUserActivities(userId: Long) { this.userId = userId if (source == SOURCE_MY_ACTIVITY) { @@ -55,32 +56,50 @@ class ActivityDetailViewModel @Inject constructor( } private fun updateMyActivities() { + _activityDetailUiState.value = _activityDetailUiState.value?.copy(isLoading = true) viewModelScope.launch { runCatching { userRepository.fetchMyActivities( - lastFeedId.value ?: 0L, + _activityDetailUiState.value?.lastFeedId ?: 0L, size, ) }.onSuccess { response -> - _userActivity.value = response.feeds.map { it.toUi() } - - _lastFeedId.value = response.feeds.lastOrNull()?.feedId?.toLong() ?: 0L + _activityDetailUiState.value = _activityDetailUiState.value?.copy( + isLoading = false, + activities = response.feeds.map { it.toUi() }, + lastFeedId = response.feeds.lastOrNull()?.feedId?.toLong() ?: 0L, + error = false, + ) + }.onFailure { exception -> + _activityDetailUiState.value = _activityDetailUiState.value?.copy( + isLoading = false, + error = true, + ) } } } private fun updateOtherUserActivities(userId: Long) { + _activityDetailUiState.value = _activityDetailUiState.value?.copy(isLoading = true) viewModelScope.launch { runCatching { userRepository.fetchUserFeeds( userId = userId, - lastFeedId = lastFeedId.value ?: 0L, + lastFeedId = _activityDetailUiState.value?.lastFeedId ?: 0L, size = size, ) }.onSuccess { response -> - _userActivity.value = response.feeds.map { it.toUi() } - _lastFeedId.value = response.feeds.lastOrNull()?.feedId?.toLong() ?: 0L - }.onFailure { + _activityDetailUiState.value = _activityDetailUiState.value?.copy( + isLoading = false, + activities = response.feeds.map { it.toUi() }, + lastFeedId = response.feeds.lastOrNull()?.feedId?.toLong() ?: 0L, + error = false, + ) + }.onFailure { exception -> + _activityDetailUiState.value = _activityDetailUiState.value?.copy( + isLoading = false, + error = true, + ) } } } @@ -96,22 +115,80 @@ class ActivityDetailViewModel @Inject constructor( }.onSuccess { val newLikeCount = if (isLiked) currentLikeCount - 1 else currentLikeCount + 1 _likeState.value = ActivityLikeState(feedId, !isLiked, newLikeCount) - - saveActivityLikeState(feedId, !isLiked, newLikeCount) + updateLikeStateInUi(feedId, !isLiked, newLikeCount) }.onFailure { + } } } - private fun saveActivityLikeState(feedId: Long, isLiked: Boolean, likeCount: Int) { - _userActivity.value = _userActivity.value?.map { activity -> - if (activity.feedId == feedId) { - activity.copy( - isLiked = isLiked, - likeCount = likeCount, + private fun updateLikeStateInUi(feedId: Long, isLiked: Boolean, likeCount: Int) { + _activityDetailUiState.value = _activityDetailUiState.value?.copy( + activities = _activityDetailUiState.value?.activities?.map { activity -> + if (activity.feedId == feedId) { + activity.copy( + isLiked = isLiked, + likeCount = likeCount, + ) + } else { + activity + } + } ?: emptyList() + ) + } + + fun updateRemovedFeed(feedId: Long) { + viewModelScope.launch { + _activityDetailUiState.value = _activityDetailUiState.value?.copy(isLoading = true) + runCatching { + feedRepository.saveRemovedFeed(feedId) + }.onSuccess { + _activityDetailUiState.value = _activityDetailUiState.value?.copy( + isLoading = false, + activities = _activityDetailUiState.value?.activities?.filter { it.feedId != feedId } + ?: emptyList(), + ) + }.onFailure { + _activityDetailUiState.value = _activityDetailUiState.value?.copy( + isLoading = false, + error = true, ) - } else { - activity + } + } + } + + fun updateReportedSpoilerFeed(feedId: Long) { + activityDetailUiState.value?.let { feedUiState -> + viewModelScope.launch { + _activityDetailUiState.value = feedUiState.copy(isLoading = true) + runCatching { + feedRepository.saveSpoilerFeed(feedId) + }.onSuccess { + _activityDetailUiState.value = feedUiState.copy(isLoading = false) + }.onFailure { + _activityDetailUiState.value = feedUiState.copy( + isLoading = false, + error = true, + ) + } + } + } + } + + fun updateReportedImpertinenceFeed(feedId: Long) { + activityDetailUiState.value?.let { feedUiState -> + viewModelScope.launch { + _activityDetailUiState.value = feedUiState.copy(isLoading = true) + runCatching { + feedRepository.saveImpertinenceFeed(feedId) + }.onSuccess { + _activityDetailUiState.value = feedUiState.copy(isLoading = false) + }.onFailure { + _activityDetailUiState.value = feedUiState.copy( + isLoading = false, + error = true, + ) + } } } } @@ -120,4 +197,4 @@ class ActivityDetailViewModel @Inject constructor( const val ACTIVITY_LOAD_SIZE = 10 const val DEFAULT_USER_ID = -1L } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/teamwss/websoso/ui/activityDetail/model/ActivityDetailUiState.kt b/app/src/main/java/com/teamwss/websoso/ui/activityDetail/model/ActivityDetailUiState.kt new file mode 100644 index 000000000..8edc960ed --- /dev/null +++ b/app/src/main/java/com/teamwss/websoso/ui/activityDetail/model/ActivityDetailUiState.kt @@ -0,0 +1,10 @@ +package com.teamwss.websoso.ui.activityDetail.model + +import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivitiesModel + +data class ActivityDetailUiState( + val isLoading: Boolean = false, + val activities: List = emptyList(), + val lastFeedId: Long = 0L, + val error: Boolean = false, +) \ No newline at end of file diff --git a/app/src/main/java/com/teamwss/websoso/ui/main/myPage/myActivity/MyActivityFragment.kt b/app/src/main/java/com/teamwss/websoso/ui/main/myPage/myActivity/MyActivityFragment.kt index c6c9e5ef6..10bdc1a81 100644 --- a/app/src/main/java/com/teamwss/websoso/ui/main/myPage/myActivity/MyActivityFragment.kt +++ b/app/src/main/java/com/teamwss/websoso/ui/main/myPage/myActivity/MyActivityFragment.kt @@ -1,18 +1,26 @@ package com.teamwss.websoso.ui.main.myPage.myActivity import android.os.Bundle +import android.view.LayoutInflater import android.view.View +import android.view.WindowManager +import android.widget.PopupWindow import android.widget.TextView import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import com.teamwss.websoso.R import com.teamwss.websoso.common.ui.base.BaseFragment import com.teamwss.websoso.databinding.FragmentMyActivityBinding +import com.teamwss.websoso.databinding.MenuMyActivityPopupBinding import com.teamwss.websoso.ui.activityDetail.ActivityDetailActivity +import com.teamwss.websoso.ui.createFeed.CreateFeedActivity import com.teamwss.websoso.ui.feedDetail.FeedDetailActivity +import com.teamwss.websoso.ui.feedDetail.model.EditFeedModel +import com.teamwss.websoso.ui.main.feed.dialog.FeedRemoveDialogFragment +import com.teamwss.websoso.ui.main.feed.dialog.RemoveMenuType import com.teamwss.websoso.ui.main.myPage.MyPageViewModel import com.teamwss.websoso.ui.main.myPage.myActivity.adapter.MyActivityAdapter -import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivitiesModel.ActivityModel +import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivitiesModel import com.teamwss.websoso.ui.main.myPage.myActivity.model.UserActivityModel import com.teamwss.websoso.ui.main.myPage.myActivity.model.UserProfileModel import com.teamwss.websoso.ui.novelDetail.NovelDetailActivity @@ -26,6 +34,7 @@ class MyActivityFragment : private val myActivityAdapter: MyActivityAdapter by lazy { MyActivityAdapter(onClickFeedItem()) } + private var _popupWindow: PopupWindow? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -39,9 +48,9 @@ class MyActivityFragment : } private fun setupObserver() { - myActivityViewModel.myActivity.observe(viewLifecycleOwner) { activities -> + myActivityViewModel.myActivityUiState.observe(viewLifecycleOwner) { uiState -> val userProfile = getUserProfile() - updateAdapterWithActivitiesAndProfile(activities, userProfile) + updateAdapterWithActivitiesAndProfile(uiState.activities, userProfile) } myPageViewModel.myPageUiState.observe(viewLifecycleOwner) { uiState -> @@ -51,15 +60,15 @@ class MyActivityFragment : avatarImage = myProfileEntity.avatarImage ) updateAdapterWithActivitiesAndProfile( - myActivityViewModel.myActivity.value, - userProfile + myActivityViewModel.myActivityUiState.value?.activities, + userProfile, ) } } } private fun updateAdapterWithActivitiesAndProfile( - activities: List?, + activities: List?, userProfile: UserProfileModel? ) { if (activities != null && userProfile != null) { @@ -100,8 +109,7 @@ class MyActivityFragment : } override fun onLikeButtonClick(view: View, feedId: Long) { - val likeCountTextView: TextView = - view.findViewById(R.id.tv_my_activity_thumb_up_count) + val likeCountTextView: TextView = view.findViewById(R.id.tv_my_activity_thumb_up_count) val currentLikeCount = likeCountTextView.text.toString().toInt() val updatedLikeCount: Int = if (view.isSelected) { @@ -113,20 +121,68 @@ class MyActivityFragment : likeCountTextView.text = updatedLikeCount.toString() view.isSelected = !view.isSelected - myActivityViewModel.updateActivityLike( - view.isSelected, - feedId, - updatedLikeCount, - ) + myActivityViewModel.updateActivityLike(view.isSelected, feedId, updatedLikeCount) } override fun onMoreButtonClick(view: View, feedId: Long) { - // TODO 팝업메뉴 수정 and 차단 + showPopupMenu(view, feedId) + } + } + + private fun showPopupMenu(view: View, feedId: Long) { + val inflater = LayoutInflater.from(requireContext()) + val binding = MenuMyActivityPopupBinding.inflate(inflater) + + _popupWindow?.dismiss() + _popupWindow = PopupWindow( + binding.root, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + true + ).apply { + elevation = POPUP_ELEVATION + showAsDropDown(view) + } + + binding.tvMyActivityModification.setOnClickListener { + navigateToFeedEdit(feedId) + _popupWindow?.dismiss() + } + + binding.tvMyActivityPopupDeletion.setOnClickListener { + showRemovedDialog(feedId) + _popupWindow?.dismiss() } } + fun navigateToFeedEdit(feedId: Long) { + val activityModel = + myActivityViewModel.myActivityUiState.value?.activities?.find { it.feedId == feedId } + activityModel?.let { feed -> + val editFeedModel = EditFeedModel( + feedId = feed.feedId, + novelId = feed.novelId ?: 0L, + novelTitle = feed.title ?: "", + feedContent = feed.feedContent, + feedCategory = feed.relevantCategories?.split(", ") ?: emptyList(), + ) + startActivity(CreateFeedActivity.getIntent(requireContext(), editFeedModel)) + } ?: throw IllegalArgumentException("Feed not found") + } + + private fun showRemovedDialog(feedId: Long) { + val dialogFragment = FeedRemoveDialogFragment.newInstance( + menuType = RemoveMenuType.REMOVE_FEED.name, + event = { + myActivityViewModel.updateRemovedFeed(feedId) + } + ) + dialogFragment.show(parentFragmentManager, FeedRemoveDialogFragment.TAG) + } + companion object { const val EXTRA_SOURCE = "source" const val SOURCE_MY_ACTIVITY = "myActivity" + const val POPUP_ELEVATION = 2f } } \ No newline at end of file diff --git a/app/src/main/java/com/teamwss/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt b/app/src/main/java/com/teamwss/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt index b6c87bc83..c4afce96e 100644 --- a/app/src/main/java/com/teamwss/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt +++ b/app/src/main/java/com/teamwss/websoso/ui/main/myPage/myActivity/MyActivityViewModel.kt @@ -6,8 +6,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.teamwss.websoso.data.repository.FeedRepository import com.teamwss.websoso.data.repository.UserRepository -import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivitiesModel.ActivityModel import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivityLikeState +import com.teamwss.websoso.ui.main.myPage.myActivity.model.MyActivityUiState import com.teamwss.websoso.ui.main.myPage.myActivity.model.UserProfileModel import com.teamwss.websoso.ui.mapper.toUi import dagger.hilt.android.lifecycle.HiltViewModel @@ -18,13 +18,10 @@ import javax.inject.Inject class MyActivityViewModel @Inject constructor( private val userRepository: UserRepository, private val feedRepository: FeedRepository, - ) : ViewModel() { +) : ViewModel() { - private val _myActivity = MutableLiveData>() - val myActivity: LiveData> get() = _myActivity - - private val _likeState = MutableLiveData() - val likeState: LiveData get() = _likeState + private val _myActivityUiState: MutableLiveData = MutableLiveData(MyActivityUiState()) + val myActivityUiState: LiveData get() = _myActivityUiState private val _lastFeedId: MutableLiveData = MutableLiveData(0L) val lastFeedId: LiveData get() = _lastFeedId @@ -40,15 +37,23 @@ class MyActivityViewModel @Inject constructor( private fun updateMyActivities() { viewModelScope.launch { + _myActivityUiState.value = _myActivityUiState.value?.copy(isLoading = true) runCatching { userRepository.fetchMyActivities( lastFeedId.value ?: 0L, size, ) }.onSuccess { response -> - _myActivity.value = response.feeds.map { it.toUi() }.take(5) - + _myActivityUiState.value = _myActivityUiState.value?.copy( + isLoading = false, + activities = response.feeds.map { it.toUi() }.take(ACTIVITY_LIMIT_COUNT), + ) _lastFeedId.value = response.feeds.lastOrNull()?.feedId?.toLong() ?: 0L + }.onFailure { + _myActivityUiState.value = _myActivityUiState.value?.copy( + isLoading = false, + error = true, + ) } } } @@ -63,28 +68,53 @@ class MyActivityViewModel @Inject constructor( } }.onSuccess { val newLikeCount = if (isLiked) currentLikeCount - 1 else currentLikeCount + 1 - _likeState.value = ActivityLikeState(feedId, !isLiked, newLikeCount) + _myActivityUiState.value = _myActivityUiState.value?.copy( + likeState = ActivityLikeState(feedId, !isLiked, newLikeCount), + ) saveActivityLikeState(feedId, !isLiked, newLikeCount) }.onFailure { + } } } private fun saveActivityLikeState(feedId: Long, isLiked: Boolean, likeCount: Int) { - _myActivity.value = _myActivity.value?.map { activity -> - if (activity.feedId == feedId) { - activity.copy( - isLiked = isLiked, - likeCount = likeCount, + _myActivityUiState.value = _myActivityUiState.value?.copy( + activities = _myActivityUiState.value?.activities?.map { activity -> + if (activity.feedId == feedId) { + activity.copy( + isLiked = isLiked, + likeCount = likeCount, + ) + } else { + activity + } + } ?: emptyList() + ) + } + + fun updateRemovedFeed(feedId: Long) { + viewModelScope.launch { + _myActivityUiState.value = _myActivityUiState.value?.copy(isLoading = true) + runCatching { + feedRepository.saveRemovedFeed(feedId) + }.onSuccess { + _myActivityUiState.value = _myActivityUiState.value?.copy( + isLoading = false, + activities = _myActivityUiState.value?.activities?.filter { it.feedId != feedId } ?: emptyList(), + ) + }.onFailure { + _myActivityUiState.value = _myActivityUiState.value?.copy( + isLoading = false, + error = true, ) - } else { - activity } } } - companion object{ + companion object { const val ACTIVITY_LOAD_SIZE = 10 + const val ACTIVITY_LIMIT_COUNT = 5 } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/teamwss/websoso/ui/main/myPage/myActivity/model/MyActivityUiState.kt b/app/src/main/java/com/teamwss/websoso/ui/main/myPage/myActivity/model/MyActivityUiState.kt new file mode 100644 index 000000000..7279baa75 --- /dev/null +++ b/app/src/main/java/com/teamwss/websoso/ui/main/myPage/myActivity/model/MyActivityUiState.kt @@ -0,0 +1,9 @@ +package com.teamwss.websoso.ui.main.myPage.myActivity.model + +import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivitiesModel.ActivityModel +data class MyActivityUiState( + val isLoading: Boolean = false, + val activities: List = emptyList(), + val likeState: ActivityLikeState? = null, + val error: Boolean = false, +) \ No newline at end of file diff --git a/app/src/main/java/com/teamwss/websoso/ui/otherUserPage/otherUserActivity/OtherUserActivityFragment.kt b/app/src/main/java/com/teamwss/websoso/ui/otherUserPage/otherUserActivity/OtherUserActivityFragment.kt index 9dd660795..f47070464 100644 --- a/app/src/main/java/com/teamwss/websoso/ui/otherUserPage/otherUserActivity/OtherUserActivityFragment.kt +++ b/app/src/main/java/com/teamwss/websoso/ui/otherUserPage/otherUserActivity/OtherUserActivityFragment.kt @@ -1,17 +1,23 @@ package com.teamwss.websoso.ui.otherUserPage.otherUserActivity import android.os.Bundle +import android.view.LayoutInflater import android.view.View +import android.view.WindowManager +import android.widget.PopupWindow import android.widget.TextView import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels import com.teamwss.websoso.R import com.teamwss.websoso.common.ui.base.BaseFragment import com.teamwss.websoso.databinding.FragmentOtherUserActivityBinding +import com.teamwss.websoso.databinding.MenuOtherUserActivityPopupBinding import com.teamwss.websoso.ui.activityDetail.ActivityDetailActivity import com.teamwss.websoso.ui.feedDetail.FeedDetailActivity +import com.teamwss.websoso.ui.main.feed.dialog.FeedReportDialogFragment +import com.teamwss.websoso.ui.main.feed.dialog.FeedReportDoneDialogFragment import com.teamwss.websoso.ui.main.myPage.myActivity.ActivityItemClickListener -import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivitiesModel.ActivityModel +import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivitiesModel import com.teamwss.websoso.ui.main.myPage.myActivity.model.UserActivityModel import com.teamwss.websoso.ui.main.myPage.myActivity.model.UserProfileModel import com.teamwss.websoso.ui.novelDetail.NovelDetailActivity @@ -27,6 +33,7 @@ class OtherUserActivityFragment : private val otherUserActivityAdapter: OtherUserActivityAdapter by lazy { OtherUserActivityAdapter(onClickFeedItem()) } + private var _popupWindow: PopupWindow? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -46,9 +53,9 @@ class OtherUserActivityFragment : } private fun setupObserver() { - otherUserActivityViewModel.otherUserActivity.observe(viewLifecycleOwner) { activities -> + otherUserActivityViewModel.otherUserActivityUiState.observe(viewLifecycleOwner) { uiState -> val userProfile = getUserProfile() - updateAdapterWithActivitiesAndProfile(activities, userProfile) + updateAdapterWithActivitiesAndProfile(uiState.activities, userProfile) } otherUserPageViewModel.otherUserProfile.observe(viewLifecycleOwner) { otherUserProfile -> @@ -58,15 +65,15 @@ class OtherUserActivityFragment : avatarImage = it.avatarImage ) updateAdapterWithActivitiesAndProfile( - otherUserActivityViewModel.otherUserActivity.value, - userProfile + otherUserActivityViewModel.otherUserActivityUiState.value?.activities, + userProfile, ) } } } private fun updateAdapterWithActivitiesAndProfile( - activities: List?, + activities: List?, userProfile: UserProfileModel? ) { if (activities != null && userProfile != null) { @@ -108,8 +115,7 @@ class OtherUserActivityFragment : } override fun onLikeButtonClick(view: View, feedId: Long) { - val likeCountTextView: TextView = - view.findViewById(R.id.tv_my_activity_thumb_up_count) + val likeCountTextView: TextView = view.findViewById(R.id.tv_my_activity_thumb_up_count) val currentLikeCount = likeCountTextView.text.toString().toInt() val updatedLikeCount: Int = if (view.isSelected) { @@ -121,22 +127,77 @@ class OtherUserActivityFragment : likeCountTextView.text = updatedLikeCount.toString() view.isSelected = !view.isSelected - otherUserActivityViewModel.updateActivityLike( - view.isSelected, - feedId, - updatedLikeCount, - ) + otherUserActivityViewModel.updateActivityLike(view.isSelected, feedId, updatedLikeCount) } override fun onMoreButtonClick(view: View, feedId: Long) { - // TODO 팝업메뉴 수정 and 차단 + showPopupMenu(view, feedId) + } + } + + private fun showPopupMenu(view: View, feedId: Long) { + val inflater = LayoutInflater.from(requireContext()) + val binding = MenuOtherUserActivityPopupBinding.inflate(inflater) + + _popupWindow?.dismiss() + _popupWindow = PopupWindow( + binding.root, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + true + ).apply { + elevation = POPUP_ELEVATION + showAsDropDown(view) } + + binding.tvOtherUserActivityReportSpoiler.setOnClickListener { + showReportDialog(feedId, "SPOILER_FEED") + _popupWindow?.dismiss() + } + + binding.tvOtherUserActivityReportExpression.setOnClickListener { + showReportDialog(feedId, "IMPERTINENCE_FEED") + _popupWindow?.dismiss() + } + } + + fun showReportDialog(feedId: Long, menuType: String) { + _popupWindow?.dismiss() + _popupWindow = null + + val dialogFragment = FeedReportDialogFragment.newInstance( + menuType = menuType, + event = { + when (menuType) { + "SPOILER_FEED" -> otherUserActivityViewModel.updateReportedSpoilerFeed(feedId) + "IMPERTINENCE_FEED" -> otherUserActivityViewModel.updateReportedImpertinenceFeed( + feedId + ) + } + + parentFragmentManager.findFragmentByTag(FeedReportDialogFragment.TAG)?.let { + (it as? FeedReportDialogFragment)?.dismiss() + } + + showReportDoneDialog(menuType) + } + ) + dialogFragment.show(parentFragmentManager, FeedReportDialogFragment.TAG) + } + + private fun showReportDoneDialog(menuType: String) { + val doneDialogFragment = FeedReportDoneDialogFragment.newInstance( + menuType = menuType, + event = {} + ) + doneDialogFragment.show(parentFragmentManager, FeedReportDoneDialogFragment.TAG) } companion object { const val EXTRA_SOURCE = "source" const val SOURCE_OTHER_USER_ACTIVITY = "otherUserActivity" const val USER_ID_KEY = "userId" + const val POPUP_ELEVATION = 2f fun newInstance(userId: Long) = OtherUserActivityFragment().apply { arguments = Bundle().apply { @@ -144,4 +205,4 @@ class OtherUserActivityFragment : } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/teamwss/websoso/ui/otherUserPage/otherUserActivity/OtherUserActivityViewModel.kt b/app/src/main/java/com/teamwss/websoso/ui/otherUserPage/otherUserActivity/OtherUserActivityViewModel.kt index a1bbb4236..4d68d8188 100644 --- a/app/src/main/java/com/teamwss/websoso/ui/otherUserPage/otherUserActivity/OtherUserActivityViewModel.kt +++ b/app/src/main/java/com/teamwss/websoso/ui/otherUserPage/otherUserActivity/OtherUserActivityViewModel.kt @@ -6,9 +6,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.teamwss.websoso.data.repository.FeedRepository import com.teamwss.websoso.data.repository.UserRepository -import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivitiesModel.ActivityModel import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivityLikeState import com.teamwss.websoso.ui.mapper.toUi +import com.teamwss.websoso.ui.otherUserPage.otherUserActivity.model.OtherUserActivityUiState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.launch import javax.inject.Inject @@ -18,11 +18,10 @@ class OtherUserActivityViewModel @Inject constructor( private val otherUserActivityRepository: UserRepository, private val feedRepository: FeedRepository, ) : ViewModel() { - private val _otherUserActivity = MutableLiveData>() - val otherUserActivity: LiveData> get() = _otherUserActivity - private val _likeState = MutableLiveData() - val likeState: LiveData get() = _likeState + private val _otherUserActivityUiState: MutableLiveData = + MutableLiveData(OtherUserActivityUiState()) + val otherUserActivityUiState: LiveData get() = _otherUserActivityUiState private val _lastFeedId: MutableLiveData = MutableLiveData(0L) val lastFeedId: LiveData get() = _lastFeedId @@ -39,8 +38,10 @@ class OtherUserActivityViewModel @Inject constructor( } } - fun updateOtherUserActivities(userId: Long) { + private fun updateOtherUserActivities(userId: Long) { viewModelScope.launch { + _otherUserActivityUiState.value = + _otherUserActivityUiState.value?.copy(isLoading = true) runCatching { otherUserActivityRepository.fetchUserFeeds( userId = userId, @@ -48,10 +49,16 @@ class OtherUserActivityViewModel @Inject constructor( size = size, ) }.onSuccess { response -> - _otherUserActivity.value = response.feeds.map { it.toUi() }.take(ACTIVITY_COUNT) + _otherUserActivityUiState.value = _otherUserActivityUiState.value?.copy( + isLoading = false, + activities = response.feeds.map { it.toUi() }.take(ACTIVITY_COUNT), + ) _lastFeedId.value = response.feeds.lastOrNull()?.feedId?.toLong() ?: 0L }.onFailure { - + _otherUserActivityUiState.value = _otherUserActivityUiState.value?.copy( + isLoading = false, + error = true, + ) } } } @@ -66,7 +73,9 @@ class OtherUserActivityViewModel @Inject constructor( } }.onSuccess { val newLikeCount = if (isLiked) currentLikeCount - 1 else currentLikeCount + 1 - _likeState.value = ActivityLikeState(feedId, !isLiked, newLikeCount) + _otherUserActivityUiState.value = _otherUserActivityUiState.value?.copy( + likeState = ActivityLikeState(feedId, !isLiked, newLikeCount), + ) saveActivityLikeState(feedId, !isLiked, newLikeCount) }.onFailure { @@ -75,14 +84,52 @@ class OtherUserActivityViewModel @Inject constructor( } private fun saveActivityLikeState(feedId: Long, isLiked: Boolean, likeCount: Int) { - _otherUserActivity.value = _otherUserActivity.value?.map { activity -> - if (activity.feedId == feedId) { - activity.copy( - isLiked = isLiked, - likeCount = likeCount, - ) - } else { - activity + _otherUserActivityUiState.value = _otherUserActivityUiState.value?.copy( + activities = _otherUserActivityUiState.value?.activities?.map { activity -> + if (activity.feedId == feedId) { + activity.copy( + isLiked = isLiked, + likeCount = likeCount, + ) + } else { + activity + } + } ?: emptyList() + ) + } + + fun updateReportedSpoilerFeed(feedId: Long) { + otherUserActivityUiState.value?.let { feedUiState -> + viewModelScope.launch { + _otherUserActivityUiState.value = feedUiState.copy(isLoading = true) + runCatching { + feedRepository.saveSpoilerFeed(feedId) + }.onSuccess { + _otherUserActivityUiState.value = feedUiState.copy(isLoading = false) + }.onFailure { + _otherUserActivityUiState.value = feedUiState.copy( + isLoading = false, + error = true, + ) + } + } + } + } + + fun updateReportedImpertinenceFeed(feedId: Long) { + otherUserActivityUiState.value?.let { feedUiState -> + viewModelScope.launch { + _otherUserActivityUiState.value = feedUiState.copy(isLoading = true) + runCatching { + feedRepository.saveImpertinenceFeed(feedId) + }.onSuccess { + _otherUserActivityUiState.value = feedUiState.copy(isLoading = false) + }.onFailure { + _otherUserActivityUiState.value = feedUiState.copy( + isLoading = false, + error = true, + ) + } } } } diff --git a/app/src/main/java/com/teamwss/websoso/ui/otherUserPage/otherUserActivity/model/OtherUserActivityUiState.kt b/app/src/main/java/com/teamwss/websoso/ui/otherUserPage/otherUserActivity/model/OtherUserActivityUiState.kt new file mode 100644 index 000000000..b4a7230bf --- /dev/null +++ b/app/src/main/java/com/teamwss/websoso/ui/otherUserPage/otherUserActivity/model/OtherUserActivityUiState.kt @@ -0,0 +1,11 @@ +package com.teamwss.websoso.ui.otherUserPage.otherUserActivity.model + +import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivitiesModel.ActivityModel +import com.teamwss.websoso.ui.main.myPage.myActivity.model.ActivityLikeState + +data class OtherUserActivityUiState( + val isLoading: Boolean = false, + val activities: List = emptyList(), + val likeState: ActivityLikeState? = null, + val error: Boolean = false, +) \ No newline at end of file diff --git a/app/src/main/res/layout/item_my_activity.xml b/app/src/main/res/layout/item_my_activity.xml index 4d8513e2c..0ffc2ce11 100644 --- a/app/src/main/res/layout/item_my_activity.xml +++ b/app/src/main/res/layout/item_my_activity.xml @@ -101,6 +101,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="20dp" + android:onClick="@{(iv_my_activity_more) -> onClick.onMoreButtonClick(iv_my_activity_more,activity.feedId)}" android:padding="12dp" android:src="@drawable/ic_more" app:layout_constraintBottom_toBottomOf="@+id/cl_my_activity_profile" diff --git a/app/src/main/res/layout/menu_my_activity_popup.xml b/app/src/main/res/layout/menu_my_activity_popup.xml new file mode 100644 index 000000000..6cd9b8b69 --- /dev/null +++ b/app/src/main/res/layout/menu_my_activity_popup.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/menu_other_user_activity_popup.xml b/app/src/main/res/layout/menu_other_user_activity_popup.xml new file mode 100644 index 000000000..e40ecb7da --- /dev/null +++ b/app/src/main/res/layout/menu_other_user_activity_popup.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 23421de3b..af0796227 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -333,6 +333,8 @@ %.1f (%d) · 활동기록 더보기 + 수정하기 + 삭제하기 내 활동 @@ -386,4 +388,8 @@ 오래된 순 최신 순 %d개 + + + 스포일러 신고 + 부적절한 표현 신고