feat(profile): 크리에이터 상세정보를 노출한다
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
BIN
app/src/main/res/drawable-xxhdpi/ic_sns_fancimm.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_sns_fancimm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_sns_instagram.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_sns_instagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_sns_kakao.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_sns_kakao.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_sns_x.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_sns_x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.8 KiB |
BIN
app/src/main/res/drawable-xxhdpi/ic_sns_youtube.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_sns_youtube.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.6 KiB |
3
app/src/main/res/drawable/ic_x_white.xml
Normal file
3
app/src/main/res/drawable/ic_x_white.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:src="@drawable/ic_close_white" />
|
||||
225
app/src/main/res/layout/dialog_creator_detail.xml
Normal file
225
app/src/main/res/layout/dialog_creator_detail.xml
Normal file
@@ -0,0 +1,225 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_round_corner_8_222222">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_close"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:contentDescription="@null"
|
||||
android:src="@drawable/ic_x_white" />
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/sv_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/iv_close"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginTop="12dp"
|
||||
android:scrollbars="none">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="24dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_profile"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/ic_place_holder" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_nickname"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:fontFamily="@font/bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="36sp"
|
||||
tools:text="크리에이터 닉네임" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_section_debut"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_debut_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/medium"
|
||||
android:text="@string/screen_creator_detail_debut"
|
||||
android:textColor="@color/color_b0bec5"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_debut_value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
tools:text="D-17" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_section_live_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_live_count_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/medium"
|
||||
android:text="@string/screen_creator_detail_live_count"
|
||||
android:textColor="@color/color_b0bec5"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_live_count_value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
tools:text="1,234" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_section_live_time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_live_time_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/medium"
|
||||
android:text="@string/screen_creator_detail_live_time"
|
||||
android:textColor="@color/color_b0bec5"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_live_time_value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
tools:text="2,345" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_section_live_contributor_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_live_contributor_count_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/medium"
|
||||
android:text="@string/screen_creator_detail_live_contributor_count"
|
||||
android:textColor="@color/color_b0bec5"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_live_contributor_count_value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
tools:text="12,345" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_section_content_count"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_content_count_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/medium"
|
||||
android:text="@string/screen_creator_detail_content_count"
|
||||
android:textColor="@color/color_b0bec5"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_content_count_value"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:fontFamily="@font/bold"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="20sp"
|
||||
tools:text="567" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_section_sns"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_sns_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/medium"
|
||||
android:text="@string/screen_creator_detail_sns"
|
||||
android:textColor="@color/color_b0bec5"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_sns_icons"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</RelativeLayout>
|
||||
@@ -774,7 +774,14 @@
|
||||
<string name="error_invalid_request">Invalid request.</string>
|
||||
<string name="screen_user_profile_share_channel">Share channel</string>
|
||||
<string name="screen_user_profile_follower_list">Follower list</string>
|
||||
<string name="screen_user_profile_follower_count">Followers %1$s</string>
|
||||
<string name="screen_user_profile_follower_count">Followers %1$s · Details ></string>
|
||||
<string name="screen_creator_detail_debut">Debut</string>
|
||||
<string name="screen_creator_detail_debut_before">Before debut</string>
|
||||
<string name="screen_creator_detail_live_count">Total live sessions</string>
|
||||
<string name="screen_creator_detail_live_time">Cumulative live time</string>
|
||||
<string name="screen_creator_detail_live_contributor_count">Cumulative live participants</string>
|
||||
<string name="screen_creator_detail_content_count">Registered content count</string>
|
||||
<string name="screen_creator_detail_sns">SNS</string>
|
||||
<string name="screen_user_profile_latest_content_scheduled">Scheduled</string>
|
||||
<string name="screen_user_profile_latest_content_point">Points</string>
|
||||
<string name="screen_user_profile_live_title">Live</string>
|
||||
|
||||
@@ -774,7 +774,14 @@
|
||||
<string name="error_invalid_request">無効なリクエストです。</string>
|
||||
<string name="screen_user_profile_share_channel">チャンネル共有</string>
|
||||
<string name="screen_user_profile_follower_list">フォロワーリスト</string>
|
||||
<string name="screen_user_profile_follower_count">フォロワー %1$s人</string>
|
||||
<string name="screen_user_profile_follower_count">フォロワー %1$s人 · 詳細情報 ></string>
|
||||
<string name="screen_creator_detail_debut">デビュー</string>
|
||||
<string name="screen_creator_detail_debut_before">デビュー前</string>
|
||||
<string name="screen_creator_detail_live_count">ライブ総回数</string>
|
||||
<string name="screen_creator_detail_live_time">ライブ累積時間</string>
|
||||
<string name="screen_creator_detail_live_contributor_count">ライブ累積参加者</string>
|
||||
<string name="screen_creator_detail_content_count">登録コンテンツ数</string>
|
||||
<string name="screen_creator_detail_sns">SNS</string>
|
||||
<string name="screen_user_profile_latest_content_scheduled">公開予定</string>
|
||||
<string name="screen_user_profile_latest_content_point">ポイント</string>
|
||||
<string name="screen_user_profile_live_title">ライブ</string>
|
||||
|
||||
@@ -773,7 +773,14 @@
|
||||
<string name="error_invalid_request">잘못된 요청입니다.</string>
|
||||
<string name="screen_user_profile_share_channel">채널 공유</string>
|
||||
<string name="screen_user_profile_follower_list">팔로워 리스트</string>
|
||||
<string name="screen_user_profile_follower_count">팔로워 %1$s명</string>
|
||||
<string name="screen_user_profile_follower_count">팔로워 %1$s명 · 상세정보 ></string>
|
||||
<string name="screen_creator_detail_debut">데뷔</string>
|
||||
<string name="screen_creator_detail_debut_before">데뷔전</string>
|
||||
<string name="screen_creator_detail_live_count">라이브 총 횟수</string>
|
||||
<string name="screen_creator_detail_live_time">라이브 누적 시간</string>
|
||||
<string name="screen_creator_detail_live_contributor_count">라이브 누적 참여자</string>
|
||||
<string name="screen_creator_detail_content_count">등록 콘텐츠 수</string>
|
||||
<string name="screen_creator_detail_sns">SNS</string>
|
||||
<string name="screen_user_profile_latest_content_scheduled">오픈예정</string>
|
||||
<string name="screen_user_profile_latest_content_point">포인트</string>
|
||||
<string name="screen_user_profile_live_title">라이브</string>
|
||||
|
||||
110
docs/20260225_크리에이터상세정보다이얼로그구현.md
Normal file
110
docs/20260225_크리에이터상세정보다이얼로그구현.md
Normal file
@@ -0,0 +1,110 @@
|
||||
# 크리에이터 상세정보 다이얼로그 구현 계획
|
||||
|
||||
## 구현 체크리스트
|
||||
- [x] 1단계: 크리에이터 상세정보 다이얼로그 요구사항 및 기존 구현 패턴을 확인한다.
|
||||
- 대상 파일: `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt`, `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/detail/GetCreatorDetailResponse.kt`, `app/src/main/java/kr/co/vividnext/sodalive/dialog/MemberProfileDialog.kt`, `app/src/main/res/values/strings.xml`
|
||||
- [x] 2단계: `kr.co.vividnext.sodalive.explorer.profile.detail` 패키지에 크리에이터 상세정보 Custom Dialog UI/로직을 구현한다.
|
||||
- 대상 파일: `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/detail/*.kt`, `app/src/main/res/layout/*.xml`, `app/src/main/res/drawable/*.xml`
|
||||
- [x] 3단계: `UserProfileActivity`에서 `tvNotificationCount` 클릭 시 상세정보 다이얼로그가 표시되도록 연결한다.
|
||||
- 대상 파일: `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileActivity.kt`, `app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileViewModel.kt`, `app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt`, `app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt`
|
||||
- [x] 4단계: 팔로워 문구를 `팔로워 OO명 · 상세정보 >` 형태로 국제화 문자열에 반영한다.
|
||||
- 대상 파일: `app/src/main/res/values/strings.xml`, `app/src/main/res/values-en/strings.xml`, `app/src/main/res/values-ja/strings.xml`
|
||||
- [x] 5단계: 진단/테스트/빌드 검증을 수행하고 문서 하단 검증 기록에 누적한다.
|
||||
- 대상 명령: `./gradlew :app:testDebugUnitTest`, `./gradlew :app:assembleDebug`
|
||||
|
||||
## 작업 메모
|
||||
- `@docs/20260224_AGENTS문서정비.md`의 제목/섹션 구조를 기준 포맷으로 채택한다.
|
||||
- 구현 중 범위 변경 시 체크리스트를 먼저 갱신한 뒤 코드 변경을 진행한다.
|
||||
- SNS 아이콘/정렬 정책 추가 반영: `ic_sns_*` 아이콘 사용, 노출 순서 `유튜브 -> 인스타그램 -> 오픈채팅 -> fancimm -> x` 고정.
|
||||
|
||||
## 검증 기록
|
||||
|
||||
### 1) 계획 문서 포맷 정렬
|
||||
- 무엇: 계획 문서를 `제목 -> 구현 체크리스트 -> 작업 메모 -> 검증 기록` 구조로 정렬.
|
||||
- 왜: 초기 기준 문서(`docs/20260224_AGENTS문서정비.md`)와 동일한 작성 규칙을 유지하기 위해.
|
||||
- 어떻게:
|
||||
- `docs/20260224_AGENTS문서정비.md`의 헤더 구조를 확인.
|
||||
- `docs/20260225_크리에이터상세정보다이얼로그구현.md`에 제목/체크리스트/메모 섹션을 추가 및 정리.
|
||||
- 결과: 계획 문서 형식이 기준 문서와 동일한 섹션 구조로 정렬됨.
|
||||
|
||||
### 2) 상세정보 다이얼로그 및 문자열 반영
|
||||
- 무엇: 크리에이터 상세정보 다이얼로그 구현 및 `tvNotificationCount` 연결, 팔로워 문구 국제화 반영.
|
||||
- 왜: 팔로워 수 클릭 시 상세정보 노출 UX와 다국어 문구 요구사항을 충족하기 위해.
|
||||
- 어떻게:
|
||||
- `CreatorDetailDialog`/`dialog_creator_detail.xml`/`ic_x_white.xml` 생성.
|
||||
- `ExplorerApi`/`ExplorerRepository`/`UserProfileViewModel`에 크리에이터 상세 API 호출 흐름 추가.
|
||||
- `UserProfileActivity`에서 `tvNotificationCount` 클릭 시 상세 다이얼로그 표시 연결.
|
||||
- `values/strings.xml`, `values-en/strings.xml`, `values-ja/strings.xml`의 팔로워 문구를 `... · ... >` 형태로 변경하고 상세정보 타이틀 문자열 추가.
|
||||
- 결과: 요구한 다이얼로그 표시 흐름과 국제화 문자열 반영 완료.
|
||||
|
||||
### 3) 진단/테스트/빌드 검증
|
||||
- 무엇: 변경 파일 진단 및 단위 테스트/디버그 빌드 수행.
|
||||
- 왜: 구현 반영 후 컴파일/리소스/테스트 안정성을 확인하기 위해.
|
||||
- 어떻게:
|
||||
- `lsp_diagnostics`를 수정한 Kotlin/XML 파일에 실행 시도.
|
||||
- `./gradlew :app:testDebugUnitTest :app:assembleDebug` 실행.
|
||||
- 결과:
|
||||
- LSP: 현재 환경에서 Kotlin/XML LSP 미설정(`No LSP server configured for extension: .kt/.xml`).
|
||||
- Gradle: `BUILD SUCCESSFUL` (unit test + debug assemble 통과).
|
||||
|
||||
### 4) 추가 수정 후 재검증
|
||||
- 무엇: SNS 항목 확장 반영(`fancimmUrl`, `websiteUrl`, `blogUrl`) 이후 재검증.
|
||||
- 왜: 마지막 코드 변경 이후에도 빌드/테스트가 정상인지 확인하기 위해.
|
||||
- 어떻게:
|
||||
- `lsp_diagnostics`로 `CreatorDetailDialog.kt` 진단 실행 시도.
|
||||
- `./gradlew :app:testDebugUnitTest :app:assembleDebug` 재실행.
|
||||
- 결과:
|
||||
- LSP: Kotlin LSP 미설정(`No LSP server configured for extension: .kt`).
|
||||
- Gradle: `BUILD SUCCESSFUL`.
|
||||
|
||||
### 5) 최종 변경 반영 후 검증
|
||||
- 무엇: SNS 아이템 노출 필드(오픈채팅/인스타그램/유튜브/팬심/X/웹사이트/블로그) 최종 반영 후 재검증.
|
||||
- 왜: 마지막 수정 이후에도 테스트/빌드 통과 상태를 보장하기 위해.
|
||||
- 어떻게:
|
||||
- `lsp_diagnostics`로 `CreatorDetailDialog.kt` 진단 실행 시도.
|
||||
- `./gradlew :app:testDebugUnitTest :app:assembleDebug` 실행.
|
||||
- 결과:
|
||||
- LSP: Kotlin LSP 미설정(`No LSP server configured for extension: .kt`).
|
||||
- Gradle: `BUILD SUCCESSFUL`.
|
||||
|
||||
### 6) SNS 순서/아이콘 정책 반영 후 재검증
|
||||
- 무엇: SNS 노출 순서를 `유튜브 -> 인스타그램 -> 오픈채팅 -> fancimm -> x`로 고정하고 `ic_sns_*` 아이콘 사용으로 통일.
|
||||
- 왜: 추가 요청된 UI 정책을 정확히 반영하기 위해.
|
||||
- 어떻게:
|
||||
- `CreatorDetailDialog.kt`의 SNS 리스트 순서와 아이콘 리소스를 `ic_sns_youtube`, `ic_sns_instagram`, `ic_sns_kakao`, `ic_sns_fancimm`, `ic_sns_x`로 수정.
|
||||
- `./gradlew :app:testDebugUnitTest :app:assembleDebug` 실행.
|
||||
- 결과: `BUILD SUCCESSFUL`.
|
||||
|
||||
### 7) 프로필 이미지 1:1 전체 폭 반영 후 재검증
|
||||
- 무엇: 상세 다이얼로그 프로필 이미지를 다이얼로그 가로 전체 폭으로 확장하고 1:1 비율로 고정.
|
||||
- 왜: 추가 요청된 UI 규격(가로 full + 정사각형 비율)을 충족하기 위해.
|
||||
- 어떻게:
|
||||
- `dialog_creator_detail.xml`에서 이미지 블록을 상단 full-width ConstraintLayout으로 분리.
|
||||
- `ImageView`를 `0dp x 0dp` + `app:layout_constraintDimensionRatio="1:1"`로 설정해 가로 기준 정사각형을 강제.
|
||||
- `./gradlew :app:testDebugUnitTest :app:assembleDebug` 실행.
|
||||
- 결과:
|
||||
- LSP: XML LSP 미설정(`No LSP server configured for extension: .xml`).
|
||||
- Gradle: `BUILD SUCCESSFUL`.
|
||||
|
||||
### 8) 최근 2회 수정 롤백 및 검증
|
||||
- 무엇: 직전 2회 수정(라운드/패딩/스크롤 구조 변경, 긴급 표시 수정)을 롤백.
|
||||
- 왜: 요청에 따라 최근 2회 수정 내용을 원복하기 위해.
|
||||
- 어떻게:
|
||||
- `dialog_creator_detail.xml`을 롤백 전 구조(루트 `wrap_content`, 스크롤 `wrap_content`, 이미지 `0dp/0dp + ratio 1:1`)로 복원.
|
||||
- `CreatorDetailDialog.kt`를 롤백 전 상태(`CircleCropTransformation`, 다이얼로그 높이 `WRAP_CONTENT`, `ivProfile.post` 제거)로 복원.
|
||||
- `./gradlew :app:testDebugUnitTest :app:assembleDebug` 실행.
|
||||
- 결과:
|
||||
- LSP: Kotlin/XML LSP 미설정(`No LSP server configured for extension: .kt/.xml`).
|
||||
- Gradle: `BUILD SUCCESSFUL`.
|
||||
|
||||
### 9) 라이브 누적 시간 섹션 추가
|
||||
- 무엇: `라이브 총 횟수` 아래에 동일 UI 패턴의 `라이브 누적 시간` 섹션을 추가하고 `liveTime` 값을 표시.
|
||||
- 왜: 추가 요청된 정보 항목을 동일한 상세정보 구조로 노출하기 위해.
|
||||
- 어떻게:
|
||||
- `dialog_creator_detail.xml`에 `ll_section_live_time`, `tv_live_time_title`, `tv_live_time_value` 추가.
|
||||
- `CreatorDetailDialog.kt`에서 `detail.activitySummary.liveTime`을 `moneyFormat()`으로 바인딩.
|
||||
- `values/strings.xml`, `values-en/strings.xml`, `values-ja/strings.xml`에 `screen_creator_detail_live_time` 문자열 추가.
|
||||
- `./gradlew :app:testDebugUnitTest :app:assembleDebug` 실행.
|
||||
- 결과:
|
||||
- LSP: Kotlin/XML LSP 미설정(`No LSP server configured for extension: .kt/.xml`).
|
||||
- Gradle: `BUILD SUCCESSFUL`.
|
||||
Reference in New Issue
Block a user