diff --git a/app/build.gradle b/app/build.gradle index 5452366..0a394f8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -35,8 +35,8 @@ android { applicationId "kr.co.vividnext.sodalive" minSdk 23 targetSdk 34 - versionCode 102 - versionName "1.16.2" + versionCode 104 + versionName "1.17.0" } buildTypes { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/comment/AudioContentCommentAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/comment/AudioContentCommentAdapter.kt index 6131e04..befedbb 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/comment/AudioContentCommentAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/comment/AudioContentCommentAdapter.kt @@ -9,6 +9,7 @@ import androidx.appcompat.widget.PopupMenu import androidx.recyclerview.widget.RecyclerView import coil.load import coil.transform.CircleCropTransformation +import com.orhanobut.logger.Logger import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.ItemAudioContentCommentBinding @@ -19,7 +20,8 @@ class AudioContentCommentAdapter( private val creatorId: Long, private val modifyComment: (Long, String) -> Unit, private val onClickDelete: (Long) -> Unit, - private val onItemClick: (GetAudioContentCommentListItem) -> Unit + private val onItemClick: (GetAudioContentCommentListItem) -> Unit, + private val onClickProfile: (Long) -> Unit ) : RecyclerView.Adapter() { var items = mutableSetOf() @@ -42,6 +44,12 @@ class AudioContentCommentAdapter( transformations(CircleCropTransformation()) } + binding.ivCommentProfile.setOnClickListener { + if (SharedPreferenceManager.userId != item.writerId) { + onClickProfile(item.writerId) + } + } + val tvCommentLayoutParams = binding.tvComment.layoutParams as LinearLayout.LayoutParams val can = item.donationCan if (can > 0) { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/comment/AudioContentCommentListFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/comment/AudioContentCommentListFragment.kt index 181d7b1..f7c7a5f 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/audio_content/comment/AudioContentCommentListFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/audio_content/comment/AudioContentCommentListFragment.kt @@ -20,6 +20,7 @@ import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.FragmentAudioContentCommentListBinding +import kr.co.vividnext.sodalive.dialog.MemberProfileDialog import kr.co.vividnext.sodalive.extensions.dpToPx import org.koin.android.ext.android.inject @@ -121,6 +122,14 @@ class AudioContentCommentListFragment : BaseFragment + if (it.isBlocked) { + viewModel.memberUnBlock(it.memberId) { dismiss() } + } else { + showMemberBlockDialog(it.memberId, it.nickname) + } + } + + dialogView.tvReportMember.setOnClickListener { _ -> + showMemberReportDialog(it.memberId) + } + + dialogView.tvReportMemberProfile.setOnClickListener { _ -> + showProfileReportDialog(it.memberId) + } + } + } + + private fun showMemberBlockDialog(memberId: Long, nickname: String) { + val message = if (SharedPreferenceManager.role == MemberRole.CREATOR.name) { + """ + ${nickname}님을 차단하시겠습니까? + + 사용자를 차단하면 사용자는 아래 기능이 제한됩니다. + - 내가 개설한 라이브 입장 불가 + - 나에게 메시지 보내기 불가 + - 내 채널의 팬Talk 작성불가 + """.trimIndent() + } else { + """ + ${nickname}님을 차단하시겠습니까? + + - 사용자를 차단하면 '차단한 사용자의 라이브 중 채팅'이 보이지 않습니다. + """.trimIndent() + } + val dialog = android.app.AlertDialog.Builder(activity) + dialog.setTitle("사용자 차단") + dialog.setMessage(message) + dialog.setPositiveButton("차단") { _, _ -> + viewModel.memberBlock(memberId) { dismiss() } + } + dialog.setNegativeButton("취소") { _, _ -> } + dialog.show() + } + + private fun showMemberReportDialog(memberId: Long) { + val dialog = UserReportDialog(activity, layoutInflater) { + viewModel.report( + type = ReportType.USER, + userId = memberId, + reason = it + ) { dismiss() } + } + + dialog.show(screenWidth) + } + + private fun showProfileReportDialog(memberId: Long) { + val dialog = ProfileReportDialog(activity, layoutInflater) { + viewModel.report( + type = ReportType.PROFILE, + userId = memberId + ) { dismiss() } + } + + dialog.show(screenWidth) + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt index 4a3bbd1..c3e971a 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt @@ -38,6 +38,7 @@ import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.ActivityUserProfileBinding import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityBinding +import kr.co.vividnext.sodalive.dialog.MemberProfileDialog import kr.co.vividnext.sodalive.explorer.profile.cheers.UserProfileCheersAdapter import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity @@ -382,6 +383,14 @@ class UserProfileActivity : BaseActivity( cancelButtonTitle = "취소", cancelButtonClick = {} ).show(screenWidth) + }, + onClickProfile = { + MemberProfileDialog( + activity = this@UserProfileActivity, + layoutInflater = layoutInflater, + memberId = it, + screenWidth = screenWidth + ).show() } ) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/cheers/UserProfileCheersAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/cheers/UserProfileCheersAdapter.kt index 0c3eae3..6a7d282 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/cheers/UserProfileCheersAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/cheers/UserProfileCheersAdapter.kt @@ -20,7 +20,8 @@ class UserProfileCheersAdapter( private val modifyReply: (Long, String?) -> Unit, private val modifyCheers: (Long, String) -> Unit, private val onClickReport: (Long) -> Unit, - private val onClickDelete: (Long) -> Unit + private val onClickDelete: (Long) -> Unit, + private val onClickProfile: (Long) -> Unit ) : RecyclerView.Adapter() { val items = mutableListOf() @@ -35,6 +36,12 @@ class UserProfileCheersAdapter( binding.llCheerReply.visibility = View.GONE binding.rlCheerReply.visibility = View.GONE + binding.ivProfile.setOnClickListener { + if (SharedPreferenceManager.userId != cheers.memberId) { + onClickProfile(cheers.memberId) + } + } + binding.ivProfile.load(cheers.profileUrl) { crossfade(true) placeholder(R.drawable.ic_place_holder) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentAdapter.kt index 3b0af50..96dc0b6 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentAdapter.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentAdapter.kt @@ -17,7 +17,8 @@ class CreatorCommunityCommentAdapter( private val creatorId: Long, private val modifyComment: (Long, String) -> Unit, private val onClickDelete: (Long) -> Unit, - private val onItemClick: (GetCommunityPostCommentListItem) -> Unit + private val onItemClick: (GetCommunityPostCommentListItem) -> Unit, + private val onClickProfile: (Long) -> Unit ) : RecyclerView.Adapter() { var items = mutableSetOf() @@ -33,6 +34,12 @@ class CreatorCommunityCommentAdapter( transformations(CircleCropTransformation()) } + binding.ivCommentProfile.setOnClickListener { + if (SharedPreferenceManager.userId != item.writerId) { + onClickProfile(item.writerId) + } + } + binding.tvComment.text = item.comment binding.tvCommentDate.text = item.date binding.tvCommentNickname.text = item.nickname diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListFragment.kt index 52e18d5..1175407 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentListFragment.kt @@ -20,6 +20,7 @@ import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.FragmentAudioContentCommentListBinding +import kr.co.vividnext.sodalive.dialog.MemberProfileDialog import kr.co.vividnext.sodalive.extensions.dpToPx import org.koin.android.ext.android.inject @@ -106,6 +107,14 @@ class CreatorCommunityCommentListFragment : BaseFragment Unit, - private val onClickDelete: (Long) -> Unit + private val onClickDelete: (Long) -> Unit, + private val onClickProfile: (Long) -> Unit ) : RecyclerView.Adapter() { var items = mutableSetOf() override fun onCreateViewHolder( @@ -46,7 +47,8 @@ class CreatorCommunityCommentReplyAdapter( showOptionMenu = { context, view, commentId, writerId, creatorId, onClickModify -> showOptionMenu(context, view, commentId, writerId, creatorId, onClickModify) }, - modifyComment = modifyComment + modifyComment = modifyComment, + onClickProfile = onClickProfile ) } } @@ -128,9 +130,16 @@ class CreatorCommunityCommentReplyItemViewHolder( private val showOptionMenu: ( Context, View, Long, Long, Long, onClickModify: () -> Unit ) -> Unit, - private val modifyComment: (Long, String) -> Unit + private val modifyComment: (Long, String) -> Unit, + private val onClickProfile: (Long) -> Unit ) : CreatorCommunityCommentReplyViewHolder(binding) { override fun bind(item: GetCommunityPostCommentListItem) { + binding.ivCommentProfile.setOnClickListener { + if (SharedPreferenceManager.userId != item.writerId) { + onClickProfile(item.writerId) + } + } + binding.ivCommentProfile.load(item.profileUrl) { crossfade(true) placeholder(R.drawable.ic_place_holder) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyFragment.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyFragment.kt index 51fb407..e9e8246 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyFragment.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/creator_community/all/comment/CreatorCommunityCommentReplyFragment.kt @@ -21,6 +21,7 @@ import kr.co.vividnext.sodalive.common.Constants import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.databinding.FragmentAudioContentCommentReplyBinding +import kr.co.vividnext.sodalive.dialog.MemberProfileDialog import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostCommentListItem import kr.co.vividnext.sodalive.extensions.dpToPx import org.koin.android.ext.android.inject @@ -127,6 +128,14 @@ class CreatorCommunityCommentReplyFragment : BaseFragment>> + + @GET("/member/profile/{id}") + fun getMemberProfile( + @Path("id") id: Long, + @Header("Authorization") authHeader: String + ): Single> } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt index a3fec9a..0a8f0cf 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/UserRepository.kt @@ -124,4 +124,9 @@ class UserRepository(private val userApi: UserApi) { ) fun getBlockedMemberIdList(token: String) = userApi.getBlockedMemberIdList(authHeader = token) + + fun getMemberProfile( + id: Long, + token: String + ) = userApi.getMemberProfile(id = id, authHeader = token) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/user/UserViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/user/UserViewModel.kt new file mode 100644 index 0000000..c27d1d9 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/user/UserViewModel.kt @@ -0,0 +1,165 @@ +package kr.co.vividnext.sodalive.user + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.orhanobut.logger.Logger +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers +import io.reactivex.rxjava3.schedulers.Schedulers +import kr.co.vividnext.sodalive.base.BaseViewModel +import kr.co.vividnext.sodalive.common.SharedPreferenceManager +import kr.co.vividnext.sodalive.report.ReportRepository +import kr.co.vividnext.sodalive.report.ReportRequest +import kr.co.vividnext.sodalive.report.ReportType + +class UserViewModel( + private val repository: UserRepository, + private val reportRepository: ReportRepository +) : BaseViewModel() { + private val _memberProfile = MutableLiveData() + val memberProfile: LiveData + get() = _memberProfile + + private var _isLoading = MutableLiveData(false) + val isLoading: LiveData + get() = _isLoading + + private val _toastLiveData = MutableLiveData() + val toastLiveData: LiveData + get() = _toastLiveData + + fun getMemberProfile(id: Long, onSuccess: () -> Unit) { + _isLoading.value = true + + compositeDisposable.add( + repository.getMemberProfile( + id = id, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + if (it.success && it.data != null) { + _memberProfile.value = it.data!! + onSuccess() + } else { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue( + "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + ) + ) + } + + fun memberBlock(memberId: Long, onSuccess: () -> Unit) { + _isLoading.value = true + compositeDisposable.add( + repository.memberBlock( + userId = memberId, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + + if (it.success) { + _toastLiveData.postValue("차단하였습니다.") + onSuccess() + } else { + val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + _toastLiveData.postValue(message) + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun memberUnBlock(memberId: Long, onSuccess: () -> Unit) { + _isLoading.value = true + compositeDisposable.add( + repository.memberUnBlock( + userId = memberId, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + _isLoading.value = false + + if (it.success) { + _toastLiveData.postValue("차단이 해제 되었습니다.") + onSuccess() + } else { + val message = it.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + _toastLiveData.postValue(message) + } + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + } + ) + ) + } + + fun report( + type: ReportType, + userId: Long, + reason: String = "프로필 신고", + onSuccess: () -> Unit + ) { + _isLoading.value = true + + val request = ReportRequest(type, reason, reportedMemberId = userId) + compositeDisposable.add( + reportRepository.report( + request = request, + token = "Bearer ${SharedPreferenceManager.token}" + ) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { + if (it.message != null) { + _toastLiveData.postValue(it.message) + } else { + _toastLiveData.postValue( + "신고가 접수되었습니다." + ) + } + + _isLoading.value = false + onSuccess() + }, + { + _isLoading.value = false + it.message?.let { message -> Logger.e(message) } + _toastLiveData.postValue("신고가 접수되었습니다.") + onSuccess() + } + ) + ) + } +} diff --git a/app/src/main/res/layout/dialog_member_profile.xml b/app/src/main/res/layout/dialog_member_profile.xml new file mode 100644 index 0000000..07b03f1 --- /dev/null +++ b/app/src/main/res/layout/dialog_member_profile.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + +