feat(chat-room): 대화 설정 다이얼로그 구현 및 채팅방 초기화 API 연동

- MoreDialog UI 구성 및 동작(배경 스위치/변경, 대화 초기화, 신고하기)
- 방별 배경 표시 SharedPreferences 저장 및 화면 반영
- TalkApi에 resetChatRoom 엔드포인트 추가, Repository 메서드 추가
- ChatRoomActivity와 다이얼로그 연동, 초기화 플로우 구현
This commit is contained in:
2025-08-26 13:30:27 +09:00
parent b3553f80c6
commit 9b1a83bd69
5 changed files with 285 additions and 12 deletions

View File

@@ -28,6 +28,13 @@ interface TalkApi {
@Body request: CreateChatRoomRequest @Body request: CreateChatRoomRequest
): Single<ApiResponse<CreateChatRoomResponse>> ): Single<ApiResponse<CreateChatRoomResponse>>
// 채팅방 초기화 API (채팅방 생성 응답과 동일 구조 반환)
@POST("/api/chat/room/{roomId}/reset")
fun resetChatRoom(
@Header("Authorization") authHeader: String,
@Path("roomId") roomId: Long
): Single<ApiResponse<CreateChatRoomResponse>>
// 통합 채팅방 입장 API // 통합 채팅방 입장 API
@GET("/api/chat/room/{roomId}/enter") @GET("/api/chat/room/{roomId}/enter")
fun enterChatRoom( fun enterChatRoom(

View File

@@ -184,4 +184,13 @@ class ChatRepository(
insert.andThen(Single.just(serverMsg)).subscribeOn(Schedulers.io()) insert.andThen(Single.just(serverMsg)).subscribeOn(Schedulers.io())
} }
} }
/**
* 대화 초기화: 서버에 리셋 요청 → 새 채팅방 ID 반환
*/
fun resetChatRoom(token: String, roomId: Long): Single<CreateChatRoomResponse> {
return talkApi.resetChatRoom(authHeader = token, roomId = roomId)
.subscribeOn(Schedulers.io())
.map { ensureSuccess(it) }
}
} }

View File

@@ -67,6 +67,8 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
setupRecyclerView() setupRecyclerView()
setupInputArea() setupInputArea()
loadInitialMessages() loadInitialMessages()
// 배경 표시 설정 적용
applyBackgroundVisibility()
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@@ -78,7 +80,7 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
// 더보기 클릭 시 전체화면 다이얼로그 표시 // 더보기 클릭 시 전체화면 다이얼로그 표시
binding.ivMore.setOnClickListener { binding.ivMore.setOnClickListener {
ChatRoomMoreDialogFragment().show(supportFragmentManager, "ChatRoomMoreDialog") ChatRoomMoreDialogFragment.newInstance(roomId).show(supportFragmentManager, "ChatRoomMoreDialog")
} }
// 5.3: characterInfo가 있으면 헤더 바인딩, 없으면 기본 플레이스홀더 유지 // 5.3: characterInfo가 있으면 헤더 바인딩, 없으면 기본 플레이스홀더 유지
@@ -685,6 +687,39 @@ class ChatRoomActivity : BaseActivity<ActivityChatRoomBinding>(
dialog.show(supportFragmentManager, "ChatImageCarousel") dialog.show(supportFragmentManager, "ChatImageCarousel")
} }
fun applyBackgroundVisibility() {
val visible = prefs.getBoolean("chat_bg_visible_room_$roomId", true)
binding.ivBackgroundProfile.isVisible = visible
binding.viewCharacterDim.isVisible = visible
}
fun onResetChatRequested() {
val title = "대화 초기화"
val desc = "지금까지의 대화가 모두 초기화 되고 새롭게 대화를 시작합니다."
SodaDialog(
activity = this,
layoutInflater = this.layoutInflater,
title = title,
desc = desc,
confirmButtonTitle = "초기화",
confirmButtonClick = {
val token = "Bearer ${SharedPreferenceManager.token}"
val disposable = chatRepository.resetChatRoom(token = token, roomId = roomId)
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ response ->
val intent = newIntent(this, response.chatRoomId)
startActivity(intent)
finish()
}, { error ->
showToast(error.message ?: "대화 초기화에 실패했어요.")
})
compositeDisposable.add(disposable)
},
cancelButtonTitle = "취소",
cancelButtonClick = { /* no-op */ }
).show(resources.displayMetrics.widthPixels)
}
companion object { companion object {
const val EXTRA_ROOM_ID: String = "extra_room_id" const val EXTRA_ROOM_ID: String = "extra_room_id"

View File

@@ -1,15 +1,23 @@
package kr.co.vividnext.sodalive.chat.talk.room package kr.co.vividnext.sodalive.chat.talk.room
import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.switchmaterial.SwitchMaterial
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.SodaDialog
/** /**
* 채팅방 우측 상단 더보기 버튼 클릭 시 표시되는 전체화면 다이얼로그. * 채팅방 우측 상단 더보기 버튼 클릭 시 표시되는 전체화면 다이얼로그.
* 내용은 추후 구성 예정이며 현재는 플레이스홀더 UI만 표시합니다. * - 배경 사진 표시 스위치
* - 배경 사진 변경(추후 상세 다이얼로그 예정)
* - 대화 초기화(확인 후 API 호출은 Activity에서 처리)
* - 신고하기(임시 안내)
*/ */
class ChatRoomMoreDialogFragment : DialogFragment() { class ChatRoomMoreDialogFragment : DialogFragment() {
@@ -26,4 +34,73 @@ class ChatRoomMoreDialogFragment : DialogFragment() {
): View? { ): View? {
return inflater.inflate(R.layout.fragment_chat_room_more_dialog, container, false) return inflater.inflate(R.layout.fragment_chat_room_more_dialog, container, false)
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val roomId = arguments?.getLong(ARG_ROOM_ID) ?: 0L
// 닫기 버튼
view.findViewById<ImageView>(R.id.iv_close)?.setOnClickListener { dismiss() }
val prefs = requireActivity().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
val bgKey = bgPrefKey(roomId)
val switch = view.findViewById<SwitchMaterial>(R.id.sw_background)
switch?.isChecked = prefs.getBoolean(bgKey, true)
switch?.setOnCheckedChangeListener { _, isChecked ->
prefs.edit().putBoolean(bgKey, isChecked).apply()
(activity as? ChatRoomActivity)?.applyBackgroundVisibility()
}
// 행 클릭 시 스위치 토글
view.findViewById<LinearLayout>(R.id.row_bg_switch)?.setOnClickListener {
switch?.toggle()
}
// 배경 사진 변경 (임시 안내)
view.findViewById<LinearLayout>(R.id.row_bg_change)?.setOnClickListener {
// TODO: 배경 선택 다이얼로그 연결 (기본 프로필 + 구매한 캐릭터 이미지)
SodaDialog(
requireActivity(),
layoutInflater,
title = getString(R.string.app_name),
desc = "배경 사진 변경은 곧 제공됩니다.",
confirmButtonTitle = "확인",
confirmButtonClick = {},
cancelButtonTitle = ""
).show(resources.displayMetrics.widthPixels)
}
// 대화 초기화: Activity에 위임
view.findViewById<LinearLayout>(R.id.row_reset)?.setOnClickListener {
(activity as? ChatRoomActivity)?.onResetChatRequested()
}
// 신고하기 (임시 안내)
view.findViewById<LinearLayout>(R.id.row_report)?.setOnClickListener {
SodaDialog(
requireActivity(),
layoutInflater,
title = "신고하기",
desc = "신고하기 기능은 준비 중입니다.",
confirmButtonTitle = "확인",
confirmButtonClick = {},
cancelButtonTitle = ""
).show(resources.displayMetrics.widthPixels)
}
}
companion object {
private const val ARG_ROOM_ID = "arg_room_id"
private const val PREFS_NAME = "chat_room_prefs"
private fun bgPrefKey(roomId: Long) = "chat_bg_visible_room_$roomId"
fun newInstance(roomId: Long): ChatRoomMoreDialogFragment {
val f = ChatRoomMoreDialogFragment()
val args = Bundle()
args.putLong(ARG_ROOM_ID, roomId)
f.arguments = args
return f
}
}
} }

View File

@@ -1,20 +1,165 @@
<?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="#CC000000"> android:background="@color/color_131313">
<TextView <!-- DetailToolbar -->
android:id="@+id/tv_placeholder" <LinearLayout
android:layout_width="wrap_content" android:id="@+id/toolbar"
android:layout_height="wrap_content" android:layout_width="0dp"
android:text="준비중" android:layout_height="56dp"
android:textColor="@android:color/white" android:background="@android:color/transparent"
android:textSize="18sp" android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent" android:orientation="horizontal"
android:paddingStart="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">
<ImageView
android:id="@+id/iv_close"
android:layout_width="24dp"
android:layout_height="24dp"
android:contentDescription="@string/a11y_back"
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:ellipsize="end"
android:fontFamily="@font/pretendard_bold"
android:maxLines="1"
android:text="대화 설정"
android:textColor="#FFFFFFFF"
android:textSize="18sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/iv_close"
tools:text="대화 설정" />
</LinearLayout>
<androidx.core.widget.NestedScrollView
android:id="@+id/scroll"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- 배경 사진 스위치 -->
<LinearLayout
android:id="@+id/row_bg_switch"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="20dp"
android:paddingEnd="20dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="배경 사진"
android:textColor="#FFFFFFFF"
android:textSize="16sp" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/sw_background"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#26FFFFFF" />
<!-- 배경 사진 변경 -->
<LinearLayout
android:id="@+id/row_bg_change"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="20dp"
android:paddingEnd="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="배경 사진 변경"
android:textColor="#FFFFFFFF"
android:textSize="16sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#26FFFFFF" />
<!-- 대화 초기화 -->
<LinearLayout
android:id="@+id/row_reset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="대화 초기화"
android:textColor="#FFFFFFFF"
android:textSize="16sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:text="지금까지의 대화가 모두 초기화 되고 새롭게 대화를 시작합니다."
android:textColor="#B3FFFFFF"
android:textSize="13sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#26FFFFFF" />
<!-- 신고하기 -->
<LinearLayout
android:id="@+id/row_report"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingStart="20dp"
android:paddingEnd="20dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="신고하기"
android:textColor="#FFFFFFFF"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>