feat(profile): 크리에이터 상세정보를 노출한다

This commit is contained in:
2026-02-25 15:02:50 +09:00
parent c74d27f4ab
commit 5b83ae69dd
17 changed files with 601 additions and 3 deletions

View File

@@ -5,6 +5,7 @@ import io.reactivex.rxjava3.core.Single
import kr.co.vividnext.sodalive.common.ApiResponse
import kr.co.vividnext.sodalive.explorer.profile.GetCheersResponse
import kr.co.vividnext.sodalive.explorer.profile.GetCreatorProfileResponse
import kr.co.vividnext.sodalive.explorer.profile.detail.GetCreatorDetailResponse
import kr.co.vividnext.sodalive.explorer.profile.cheers.PostWriteCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.cheers.PutModifyCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.donation.GetDonationAllResponse
@@ -43,6 +44,12 @@ interface ExplorerApi {
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetCreatorProfileResponse>>
@GET("/explorer/profile/{id}/detail")
fun getCreatorDetail(
@Path("id") id: Long,
@Header("Authorization") authHeader: String
): Single<ApiResponse<GetCreatorDetailResponse>>
@GET("/explorer/profile/{id}/donation-rank")
fun getCreatorProfileDonationRanking(
@Path("id") id: Long,

View File

@@ -27,6 +27,11 @@ class ExplorerRepository(
authHeader = token
)
fun getCreatorDetail(id: Long, token: String) = api.getCreatorDetail(
id = id,
authHeader = token
)
fun getCreatorProfileCheers(
creatorId: Long,
page: Int,

View File

@@ -51,6 +51,7 @@ import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityP
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
import kr.co.vividnext.sodalive.explorer.profile.creator_community.relativeTimeText
import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteActivity
import kr.co.vividnext.sodalive.explorer.profile.detail.CreatorDetailDialog
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAdapter
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewActivity
import kr.co.vividnext.sodalive.explorer.profile.fantalk.UserProfileFantalkAllViewActivity
@@ -711,6 +712,7 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
if (creator.creatorId == SharedPreferenceManager.userId) {
binding.ivNotification.visibility = View.GONE
binding.tvNotificationCount.visibility = View.GONE
binding.tvNotificationCount.setOnClickListener(null)
binding.tvFollowerList.visibility = View.VISIBLE
binding.tvFollowerList.setOnClickListener {
@@ -728,6 +730,17 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
R.string.screen_user_profile_follower_count,
creator.notificationRecipientCount.moneyFormat()
)
binding.tvNotificationCount.setOnClickListener {
viewModel.getCreatorDetail(creator.creatorId) { detail ->
CreatorDetailDialog(
activity = this@UserProfileActivity,
layoutInflater = layoutInflater,
screenWidth = screenWidth,
detail = detail
).show()
}
}
}
if (creator.isFollow) {

View File

@@ -12,6 +12,7 @@ import kr.co.vividnext.sodalive.common.SodaLiveApplicationHolder
import kr.co.vividnext.sodalive.common.Utils
import kr.co.vividnext.sodalive.explorer.ExplorerRepository
import kr.co.vividnext.sodalive.explorer.profile.cheers.PutModifyCheersRequest
import kr.co.vividnext.sodalive.explorer.profile.detail.GetCreatorDetailResponse
import kr.co.vividnext.sodalive.report.ReportRepository
import kr.co.vividnext.sodalive.report.ReportRequest
import kr.co.vividnext.sodalive.report.ReportType
@@ -156,6 +157,43 @@ class UserProfileViewModel(
)
}
fun getCreatorDetail(userId: Long, onSuccess: (GetCreatorDetailResponse) -> Unit) {
_isLoading.value = true
compositeDisposable.add(
repository.getCreatorDetail(
id = userId,
token = "Bearer ${SharedPreferenceManager.token}"
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
_isLoading.value = false
if (it.success && it.data != null) {
onSuccess(it.data)
} else {
if (it.message != null) {
_toastLiveData.postValue(it.message)
} else {
_toastLiveData.postValue(
SodaLiveApplicationHolder.get()
.getString(R.string.common_error_unknown)
)
}
}
},
{
_isLoading.value = false
it.message?.let { message -> Logger.e(message) }
_toastLiveData.postValue(
SodaLiveApplicationHolder.get()
.getString(R.string.common_error_unknown)
)
}
)
)
}
fun follow(creatorId: Long, follow: Boolean = true, notify: Boolean = true) {
_isLoading.value = true
compositeDisposable.add(

View File

@@ -0,0 +1,157 @@
package kr.co.vividnext.sodalive.explorer.profile.detail
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.Window
import android.view.WindowManager
import android.webkit.URLUtil
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.appcompat.app.AlertDialog
import androidx.core.graphics.drawable.toDrawable
import androidx.core.net.toUri
import coil.load
import coil.transform.RoundedCornersTransformation
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.DialogCreatorDetailBinding
import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.moneyFormat
class CreatorDetailDialog(
private val activity: Activity,
layoutInflater: LayoutInflater,
private val screenWidth: Int,
private val detail: GetCreatorDetailResponse
) {
private data class SnsItem(
val url: String,
val iconResId: Int
)
private val alertDialog: AlertDialog
private val dialogView = DialogCreatorDetailBinding.inflate(layoutInflater)
init {
val dialogBuilder = AlertDialog.Builder(activity)
dialogBuilder.setView(dialogView.root)
alertDialog = dialogBuilder.create()
alertDialog.setCancelable(false)
alertDialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
alertDialog.window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
setupView()
bindData()
}
private fun setupView() {
dialogView.ivClose.setOnClickListener { dismiss() }
}
private fun bindData() {
dialogView.ivProfile.load(detail.profileImageUrl) {
crossfade(true)
placeholder(R.drawable.ic_place_holder)
transformations(RoundedCornersTransformation(16f.dpToPx()))
}
dialogView.tvNickname.text = detail.nickname
dialogView.tvDebutValue.text = getDebutValue()
dialogView.tvLiveCountValue.text = detail.activitySummary.liveCount.moneyFormat()
dialogView.tvLiveTimeValue.text = detail.activitySummary.liveTime.moneyFormat()
dialogView.tvLiveContributorCountValue.text = detail.activitySummary.liveContributorCount.moneyFormat()
dialogView.tvContentCountValue.text = detail.activitySummary.contentCount.moneyFormat()
bindSnsItems()
}
private fun getDebutValue(): String {
val debutDate = detail.debutDate.trim()
val dDay = detail.dDay.trim()
if (debutDate.isBlank() && dDay.isBlank()) {
return activity.getString(R.string.screen_creator_detail_debut_before)
}
return "$debutDate ($dDay)"
}
private fun bindSnsItems() {
val snsItems = listOf(
SnsItem(
url = detail.youtubeUrl.trim(),
iconResId = R.drawable.ic_sns_youtube
),
SnsItem(
url = detail.instagramUrl.trim(),
iconResId = R.drawable.ic_sns_instagram
),
SnsItem(
url = detail.kakaoOpenChatUrl.trim(),
iconResId = R.drawable.ic_sns_kakao
),
SnsItem(
url = detail.fancimmUrl.trim(),
iconResId = R.drawable.ic_sns_fancimm
),
SnsItem(
url = detail.xUrl.trim(),
iconResId = R.drawable.ic_sns_x
)
).filter { item ->
item.url.isNotBlank() && URLUtil.isValidUrl(item.url)
}
if (snsItems.isEmpty()) {
dialogView.llSectionSns.visibility = View.GONE
return
}
dialogView.llSectionSns.visibility = View.VISIBLE
dialogView.llSnsIcons.removeAllViews()
snsItems.forEachIndexed { index, item ->
val imageView = ImageView(activity).apply {
setImageResource(item.iconResId)
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
if (index > 0) {
marginStart = 12.dpToPx().toInt()
}
}
setOnClickListener {
openUrl(item.url)
}
}
dialogView.llSnsIcons.addView(imageView)
}
}
private fun openUrl(url: String) {
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
if (intent.resolveActivity(activity.packageManager) != null) {
activity.startActivity(intent)
}
}
private fun dismiss() {
alertDialog.dismiss()
}
fun show() {
alertDialog.show()
val lp = WindowManager.LayoutParams()
lp.copyFrom(alertDialog.window?.attributes)
lp.width = screenWidth - (48.dpToPx()).toInt()
lp.height = WindowManager.LayoutParams.WRAP_CONTENT
alertDialog.window?.attributes = lp
}
}

View File

@@ -0,0 +1,19 @@
package kr.co.vividnext.sodalive.explorer.profile.detail
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
import kr.co.vividnext.sodalive.explorer.profile.GetCreatorActivitySummary
@Keep
data class GetCreatorDetailResponse(
@SerializedName("nickname") val nickname: String,
@SerializedName("profileImageUrl") val profileImageUrl: String,
@SerializedName("debutDate") val debutDate: String,
@SerializedName("dday") val dDay: String,
@SerializedName("activitySummary") val activitySummary: GetCreatorActivitySummary,
@SerializedName("instagramUrl") val instagramUrl: String,
@SerializedName("fancimmUrl") val fancimmUrl: String,
@SerializedName("xurl") val xUrl: String,
@SerializedName("youtubeUrl") val youtubeUrl: String,
@SerializedName("kakaoOpenChatUrl") val kakaoOpenChatUrl: String
)