feat(chat): 채팅방 배경 사진 변경 기능 추가

- ChatRoomMoreDialog에서 배경 사진 변경 Picker 연결
- my-list API 추가 및 Repository 위임 추가
- 배경 선택 Dialog(3열 Grid, 4:5 비율) 및 선택 상태 UI 구현
- SharedPreferences로 roomId별 배경 URL 저장/로드
- ChatRoomActivity에 배경 저장/적용 헬퍼 추가 및 기본 프로필 적용 로직 구현
This commit is contained in:
2025-08-27 02:37:20 +09:00
parent 2e837bec5d
commit a941d0bfab
9 changed files with 410 additions and 45 deletions

View File

@@ -33,6 +33,16 @@ interface CharacterApi {
@Query("size") size: Int @Query("size") size: Int
): Single<ApiResponse<CharacterImageListResponse>> ): Single<ApiResponse<CharacterImageListResponse>>
// 내 배경 이미지 리스트 (프로필 + 무료 + 구매 이미지)
// getCharacterImageList와 파라미터/응답 동일, 엔드포인트만 다름
@GET("/api/chat/character/image/my-list")
fun getMyCharacterImageList(
@Header("Authorization") authHeader: String,
@Query("characterId") characterId: Long,
@Query("page") page: Int,
@Query("size") size: Int
): Single<ApiResponse<CharacterImageListResponse>>
@POST("/api/chat/character/image/purchase") @POST("/api/chat/character/image/purchase")
fun purchaseCharacterImage( fun purchaseCharacterImage(
@Header("Authorization") authHeader: String, @Header("Authorization") authHeader: String,

View File

@@ -8,6 +8,9 @@ class CharacterGalleryRepository(
fun getCharacterImageList(token: String, characterId: Long, page: Int, size: Int) = fun getCharacterImageList(token: String, characterId: Long, page: Int, size: Int) =
characterApi.getCharacterImageList(authHeader = token, characterId = characterId, page = page, size = size) characterApi.getCharacterImageList(authHeader = token, characterId = characterId, page = page, size = size)
fun getMyCharacterImageList(token: String, characterId: Long, page: Int, size: Int) =
characterApi.getMyCharacterImageList(authHeader = token, characterId = characterId, page = page, size = size)
fun purchaseCharacterImage(token: String, imageId: Long) = fun purchaseCharacterImage(token: String, imageId: Long) =
characterApi.purchaseCharacterImage( characterApi.purchaseCharacterImage(
authHeader = token, authHeader = token,

View File

@@ -0,0 +1,227 @@
package kr.co.vividnext.sodalive.chat.talk.room
import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.edit
import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.load
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.CompositeDisposable
import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.chat.character.detail.gallery.CharacterGalleryRepository
import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.databinding.FragmentChatBackgroundPickerBinding
import kr.co.vividnext.sodalive.databinding.ItemChatBackgroundImageBinding
import org.koin.android.ext.android.inject
/**
* 채팅방 배경 이미지 선택 다이얼로그
* - 3열 Grid, 간격 0, 4:5 비율
* - 캐릭터 프로필 + 무료 이미지 + 내가 구매한 이미지 (my-list)
* - 선택 항목은 1dp #3bb9f1 테두리 및 우하단 "현재 배경" 라벨 표시
*/
class ChatBackgroundPickerDialogFragment : DialogFragment() {
private var _binding: FragmentChatBackgroundPickerBinding? = null
private val binding get() = _binding!!
private val repository: CharacterGalleryRepository by inject()
private val compositeDisposable = CompositeDisposable()
private lateinit var loadingDialog: LoadingDialog
private var roomId: Long = 0L
private var characterId: Long = 0L
private var profileUrl: String = ""
private val prefsName = "chat_room_prefs"
private fun bgUrlKey(roomId: Long) = "chat_bg_url_room_$roomId"
private fun bgImageIdKey(roomId: Long) = "chat_bg_image_id_room_$roomId"
private lateinit var adapter: BgAdapter
private val items = mutableListOf<BgItem>()
private var selectedUrl: String? = null
private var selectedId: Long? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NO_TITLE, android.R.style.Theme_Black_NoTitleBar_Fullscreen)
roomId = arguments?.getLong(ARG_ROOM_ID) ?: 0L
// Activity에서 characterId/profileUrl 조회 (공개 getter 사용 예정)
val act = activity as? ChatRoomActivity
characterId = act?.getCharacterId() ?: 0L
profileUrl = act?.getCharacterProfileUrl().orEmpty()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentChatBackgroundPickerBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
setupUi()
loadData()
}
private fun setupUi() {
binding.ivClose.setOnClickListener { dismiss() }
binding.tvTitle.text = "배경 사진 선택"
binding.rvGrid.layoutManager = GridLayoutManager(requireContext(), 3)
adapter = BgAdapter { item ->
onSelect(item)
}
binding.rvGrid.adapter = adapter
}
private fun loadData() {
// 초기 선택: 저장된 값 사용 (URL + ID 병행)
val prefs = requireActivity().getSharedPreferences(prefsName, Context.MODE_PRIVATE)
selectedUrl = prefs.getString(bgUrlKey(roomId), null)
val savedId = prefs.getLong(bgImageIdKey(roomId), -1L)
selectedId = if (savedId > 0) savedId else null
items.clear()
if (characterId > 0) {
val token = "Bearer ${SharedPreferenceManager.token}"
loadingDialog.show(resources.displayMetrics.widthPixels)
val d = repository.getMyCharacterImageList(token, characterId, page = 0, size = 60)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ resp ->
val list = resp.data?.items.orEmpty()
items.addAll(list.map { BgItem(id = it.id, url = it.imageUrl) })
adapter.submit(items, selectedId, selectedUrl)
// 마이그레이션: 과거에 URL만 저장되어 있고 ID가 비어 있는 경우
if (selectedId == null && !selectedUrl.isNullOrBlank()) {
val found = items.firstOrNull { it.url == selectedUrl }
if (found != null) {
selectedId = found.id
// UI 갱신
adapter.updateSelected(found)
// 영구 저장(ID 동기화)
val p = requireActivity().getSharedPreferences(prefsName, Context.MODE_PRIVATE)
p.edit {
putLong(bgImageIdKey(roomId), found.id)
}
}
}
loadingDialog.dismiss()
}, { _ ->
// 실패 시에도 현재까지의 목록 표시(없으면 빈 목록)
adapter.submit(items, selectedId, selectedUrl)
loadingDialog.dismiss()
})
compositeDisposable.add(d)
} else {
// characterId가 없으면 서버 요청 불가: 현재 저장된 선택 상태만 반영
adapter.submit(items, selectedId, selectedUrl)
}
}
private fun onSelect(item: BgItem) {
selectedUrl = item.url
selectedId = item.id.takeIf { it > 0 }
saveAndApply(item)
adapter.updateSelected(item)
}
private fun saveAndApply(item: BgItem) {
val prefs = requireActivity().getSharedPreferences(prefsName, Context.MODE_PRIVATE)
prefs.edit {
putString(bgUrlKey(roomId), item.url)
if (item.id > 0) putLong(bgImageIdKey(roomId), item.id) else remove(bgImageIdKey(roomId))
}
(activity as? ChatRoomActivity)?.setChatBackground(item.url)
}
override fun onDestroyView() {
super.onDestroyView()
try {
if (this::loadingDialog.isInitialized) loadingDialog.dismiss()
} catch (_: Throwable) { }
compositeDisposable.clear()
_binding = null
}
data class BgItem(val id: Long, val url: String)
private class BgAdapter(
private val onClick: (BgItem) -> Unit
) : RecyclerView.Adapter<BgVH>() {
private val data = mutableListOf<BgItem>()
private var selectedUrl: String? = null
private var selectedId: Long? = null
@SuppressLint("NotifyDataSetChanged")
fun submit(items: List<BgItem>, selectedId: Long?, selectedUrl: String?) {
data.clear()
data.addAll(items)
this.selectedId = selectedId
this.selectedUrl = selectedUrl
notifyDataSetChanged()
}
@SuppressLint("NotifyDataSetChanged")
fun updateSelected(item: BgItem) {
this.selectedId = item.id.takeIf { it > 0 }
this.selectedUrl = item.url
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BgVH {
val binding = ItemChatBackgroundImageBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return BgVH(binding, onClick)
}
override fun getItemCount(): Int = data.size
override fun onBindViewHolder(holder: BgVH, position: Int) {
holder.bind(data[position], selectedId, selectedUrl)
}
}
private class BgVH(
private val binding: ItemChatBackgroundImageBinding,
private val onClick: (BgItem) -> Unit
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: BgItem, selectedId: Long?, selectedUrl: String?) {
binding.ivImage.load(item.url) {
placeholder(R.drawable.ic_placeholder_profile)
error(R.drawable.ic_placeholder_profile)
}
val selected = (selectedId != null && item.id == selectedId) ||
(selectedUrl != null && selectedUrl == item.url)
binding.tvCurrent.visibility = if (selected) View.VISIBLE else View.GONE
binding.viewBorder.visibility = if (selected) View.VISIBLE else View.GONE
binding.root.setOnClickListener { onClick(item) }
}
}
companion object {
private const val ARG_ROOM_ID = "arg_room_id"
fun newInstance(roomId: Long): ChatBackgroundPickerDialogFragment {
val f = ChatBackgroundPickerDialogFragment()
f.arguments = Bundle().apply { putLong(ARG_ROOM_ID, roomId) }
return f
}
}
}

View File

@@ -119,11 +119,8 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
// 프로필 이미지 (공용 유틸 + 둥근 모서리 적용) // 프로필 이미지 (공용 유틸 + 둥근 모서리 적용)
loadProfileImage(binding.ivProfile, info.profileImageUrl) loadProfileImage(binding.ivProfile, info.profileImageUrl)
// 배경 프로필 이미지 (5.5) // 배경 이미지: 저장된 값 우선, 없으면 프로필로 저장/적용
binding.ivBackgroundProfile.load(info.profileImageUrl) { applyBackgroundFromPrefsOrProfile(info.profileImageUrl)
placeholder(R.drawable.ic_placeholder_profile)
error(R.drawable.ic_placeholder_profile)
}
// 타입 배지 텍스트 및 배경 // 타입 배지 텍스트 및 배경
val (badgeText, badgeBg) = when (info.characterType) { val (badgeText, badgeBg) = when (info.characterType) {
@@ -840,6 +837,37 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
binding.viewCharacterDim.isVisible = visible binding.viewCharacterDim.isVisible = visible
} }
private fun bgUrlPrefKey(): String = "chat_bg_url_room_$roomId"
fun setChatBackground(url: String) {
binding.ivBackgroundProfile.load(url) {
placeholder(R.drawable.ic_placeholder_profile)
error(R.drawable.ic_placeholder_profile)
}
}
private fun applyBackgroundFromPrefsOrProfile(profileUrl: String) {
val key = bgUrlPrefKey()
val saved = prefs.getString(key, null)
val target = when {
!saved.isNullOrBlank() -> saved
profileUrl.isNotBlank() -> {
prefs.edit { putString(key, profileUrl) }
profileUrl
}
else -> null
}
if (!target.isNullOrBlank()) {
setChatBackground(target)
} else {
binding.ivBackgroundProfile.setImageResource(R.drawable.ic_placeholder_profile)
}
}
fun getCharacterId(): Long = characterInfo?.characterId ?: 0L
fun getCharacterProfileUrl(): String = characterInfo?.profileImageUrl ?: ""
fun onResetChatRequested() { fun onResetChatRequested() {
val title = "대화 초기화" val title = "대화 초기화"
val desc = "지금까지의 대화가 모두 초기화 되고 새롭게 대화를 시작합니다." val desc = "지금까지의 대화가 모두 초기화 되고 새롭게 대화를 시작합니다."

View File

@@ -57,18 +57,11 @@ class ChatRoomMoreDialogFragment : DialogFragment() {
switch?.toggle() switch?.toggle()
} }
// 배경 사진 변경 (임시 안내) // 배경 사진 변경: 배경 선택 다이얼로그 표시
view.findViewById<LinearLayout>(R.id.row_bg_change)?.setOnClickListener { view.findViewById<LinearLayout>(R.id.row_bg_change)?.setOnClickListener {
// TODO: 배경 선택 다이얼로그 연결 (기본 프로필 + 구매한 캐릭터 이미지) val roomIdArg = arguments?.getLong(ARG_ROOM_ID) ?: 0L
SodaDialog( ChatBackgroundPickerDialogFragment.newInstance(roomIdArg)
requireActivity(), .show(parentFragmentManager, "ChatBackgroundPicker")
layoutInflater,
title = getString(R.string.app_name),
desc = "배경 사진 변경은 곧 제공됩니다.",
confirmButtonTitle = "확인",
confirmButtonClick = {},
cancelButtonTitle = ""
).show(resources.displayMetrics.widthPixels)
} }
// 대화 초기화: Activity에 위임 // 대화 초기화: Activity에 위임

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@android:color/transparent" />
<stroke
android:width="1dp"
android:color="#3BB9F1" />
</shape>

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/color_131313">
<LinearLayout
android:id="@+id/toolbar"
android:layout_width="0dp"
android:layout_height="56dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/iv_close"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="닫기"
android:src="@drawable/ic_back" />
<TextView
android:id="@+id/tv_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_weight="1"
android:ellipsize="end"
android:fontFamily="@font/pretendard_bold"
android:maxLines="1"
android:text="배경 사진 선택"
android:textColor="#FFFFFFFF"
android:textSize="20sp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_grid"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipToPadding="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/color_131313"> android:background="@color/color_131313">
@@ -14,8 +13,7 @@
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="16dp" android:paddingHorizontal="16dp"
android:paddingEnd="16dp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"> app:layout_constraintTop_toTopOf="parent">
@@ -32,25 +30,25 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="12dp" android:layout_marginStart="12dp"
android:layout_weight="1"
android:ellipsize="end" android:ellipsize="end"
android:fontFamily="@font/pretendard_bold" android:fontFamily="@font/pretendard_bold"
android:maxLines="1" android:maxLines="1"
android:text="대화 설정" android:text="대화 설정"
android:textColor="#FFFFFFFF" android:textColor="#FFFFFFFF"
android:textSize="18sp" android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_close" app:layout_constraintStart_toEndOf="@id/iv_close" />
tools:text="대화 설정" />
</LinearLayout> </LinearLayout>
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/scroll" android:id="@+id/scroll"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/toolbar" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"> app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/toolbar">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -64,16 +62,16 @@
android:layout_height="56dp" android:layout_height="56dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="20dp" android:paddingHorizontal="24dp">
android:paddingEnd="20dp">
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:fontFamily="@font/pretendard_bold"
android:text="배경 사진" android:text="배경 사진"
android:textColor="#FFFFFFFF" android:textColor="#B0BEC5"
android:textSize="16sp" /> android:textSize="18sp" />
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/sw_background" android:id="@+id/sw_background"
@@ -93,15 +91,15 @@
android:layout_height="56dp" android:layout_height="56dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="20dp" android:paddingHorizontal="24dp">
android:paddingEnd="20dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_bold"
android:text="배경 사진 변경" android:text="배경 사진 변경"
android:textColor="#FFFFFFFF" android:textColor="#B0BEC5"
android:textSize="16sp" /> android:textSize="18sp" />
</LinearLayout> </LinearLayout>
<View <View
@@ -115,25 +113,25 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:paddingStart="20dp" android:paddingHorizontal="24dp"
android:paddingEnd="20dp" android:paddingVertical="12dp">
android:paddingTop="12dp"
android:paddingBottom="12dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_bold"
android:text="대화 초기화" android:text="대화 초기화"
android:textColor="#FFFFFFFF" android:textColor="#B0BEC5"
android:textSize="16sp" /> android:textSize="18sp" />
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="6dp" android:layout_marginTop="6dp"
android:fontFamily="@font/pretendard_regular"
android:text="지금까지의 대화가 모두 초기화 되고 새롭게 대화를 시작합니다." android:text="지금까지의 대화가 모두 초기화 되고 새롭게 대화를 시작합니다."
android:textColor="#B3FFFFFF" android:textColor="#B3FFFFFF"
android:textSize="13sp" /> android:textSize="16sp" />
</LinearLayout> </LinearLayout>
<View <View
@@ -148,15 +146,15 @@
android:layout_height="56dp" android:layout_height="56dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="20dp" android:paddingHorizontal="24dp">
android:paddingEnd="20dp">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/pretendard_bold"
android:text="신고하기" android:text="신고하기"
android:textColor="#FFFFFFFF" android:textColor="#B0BEC5"
android:textSize="16sp" /> android:textSize="18sp" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_image"
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="@null"
android:scaleType="centerCrop"
app:layout_constraintDimensionRatio="4:5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 선택 시 표시되는 1dp 테두리 (#3bb9f1) -->
<View
android:id="@+id/view_border"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/bg_chat_bg_selected_border"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/iv_image"
app:layout_constraintEnd_toEndOf="@id/iv_image"
app:layout_constraintStart_toStartOf="@id/iv_image"
app:layout_constraintTop_toTopOf="@id/iv_image" />
<!-- 우하단 '현재 배경' 라벨 -->
<TextView
android:id="@+id/tv_current"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="6dp"
android:background="@drawable/bg_round_corner_6_7_3bb9f1"
android:fontFamily="@font/pretendard_regular"
android:paddingHorizontal="6dp"
android:paddingVertical="2dp"
android:includeFontPadding="false"
android:text="현재 배경"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/iv_image"
app:layout_constraintEnd_toEndOf="@id/iv_image" />
</androidx.constraintlayout.widget.ConstraintLayout>