diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt index 7d303e3b..4a5fed03 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerApi.kt @@ -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> + @GET("/explorer/profile/{id}/detail") + fun getCreatorDetail( + @Path("id") id: Long, + @Header("Authorization") authHeader: String + ): Single> + @GET("/explorer/profile/{id}/donation-rank") fun getCreatorProfileDonationRanking( @Path("id") id: Long, diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt index 754f3581..a79c2861 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/ExplorerRepository.kt @@ -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, 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 46dcdf74..2ea137da 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 @@ -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( 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( 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) { diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileViewModel.kt index fbd64155..225982cf 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/UserProfileViewModel.kt @@ -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( diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/detail/CreatorDetailDialog.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/detail/CreatorDetailDialog.kt new file mode 100644 index 00000000..8bbb5e96 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/detail/CreatorDetailDialog.kt @@ -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 + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/detail/GetCreatorDetailResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/detail/GetCreatorDetailResponse.kt new file mode 100644 index 00000000..980ebc4b --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/explorer/profile/detail/GetCreatorDetailResponse.kt @@ -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 +) diff --git a/app/src/main/res/drawable-xxhdpi/ic_sns_fancimm.png b/app/src/main/res/drawable-xxhdpi/ic_sns_fancimm.png new file mode 100644 index 00000000..fac63517 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_sns_fancimm.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_sns_instagram.png b/app/src/main/res/drawable-xxhdpi/ic_sns_instagram.png new file mode 100644 index 00000000..9c3cf62c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_sns_instagram.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_sns_kakao.png b/app/src/main/res/drawable-xxhdpi/ic_sns_kakao.png new file mode 100644 index 00000000..b6d2059b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_sns_kakao.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_sns_x.png b/app/src/main/res/drawable-xxhdpi/ic_sns_x.png new file mode 100644 index 00000000..ca1a2c77 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_sns_x.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_sns_youtube.png b/app/src/main/res/drawable-xxhdpi/ic_sns_youtube.png new file mode 100644 index 00000000..885fbbef Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_sns_youtube.png differ diff --git a/app/src/main/res/drawable/ic_x_white.xml b/app/src/main/res/drawable/ic_x_white.xml new file mode 100644 index 00000000..974f1dc0 --- /dev/null +++ b/app/src/main/res/drawable/ic_x_white.xml @@ -0,0 +1,3 @@ + + diff --git a/app/src/main/res/layout/dialog_creator_detail.xml b/app/src/main/res/layout/dialog_creator_detail.xml new file mode 100644 index 00000000..5abfeb39 --- /dev/null +++ b/app/src/main/res/layout/dialog_creator_detail.xml @@ -0,0 +1,225 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-en/strings.xml b/app/src/main/res/values-en/strings.xml index 2a5bfb06..10c38b87 100644 --- a/app/src/main/res/values-en/strings.xml +++ b/app/src/main/res/values-en/strings.xml @@ -774,7 +774,14 @@ Invalid request. Share channel Follower list - Followers %1$s + Followers %1$s · Details > + Debut + Before debut + Total live sessions + Cumulative live time + Cumulative live participants + Registered content count + SNS Scheduled Points Live diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 88c39770..6dff89b1 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -774,7 +774,14 @@ 無効なリクエストです。 チャンネル共有 フォロワーリスト - フォロワー %1$s人 + フォロワー %1$s人 · 詳細情報 > + デビュー + デビュー前 + ライブ総回数 + ライブ累積時間 + ライブ累積参加者 + 登録コンテンツ数 + SNS 公開予定 ポイント ライブ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 858a359a..25d9a786 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -773,7 +773,14 @@ 잘못된 요청입니다. 채널 공유 팔로워 리스트 - 팔로워 %1$s명 + 팔로워 %1$s명 · 상세정보 > + 데뷔 + 데뷔전 + 라이브 총 횟수 + 라이브 누적 시간 + 라이브 누적 참여자 + 등록 콘텐츠 수 + SNS 오픈예정 포인트 라이브 diff --git a/docs/20260225_크리에이터상세정보다이얼로그구현.md b/docs/20260225_크리에이터상세정보다이얼로그구현.md new file mode 100644 index 00000000..bf174f76 --- /dev/null +++ b/docs/20260225_크리에이터상세정보다이얼로그구현.md @@ -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`.