From 558f74d8613eee0990ed9d0efd700d985d1c8e83 Mon Sep 17 00:00:00 2001 From: klaus Date: Wed, 13 Aug 2025 02:21:43 +0900 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=EC=BA=90=EB=A6=AD=ED=84=B0=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=EC=97=90=EC=84=9C=20=EC=B1=84=ED=8C=85?= =?UTF-8?q?=EB=B0=A9=20=EC=83=9D=EC=84=B1=20=ED=9B=84=20ChatRoomActivity?= =?UTF-8?q?=EB=A1=9C=20=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ChatRoomActivity에 EXTRA_ROOM_ID 및 newIntent 추가 - CharacterDetailActivity에서 chatRoomId 수신 시 화면 이동 처리 - 이벤트 소비 유지로 중복 네비게이션 방지 --- app/src/main/AndroidManifest.xml | 2 + .../detail/CharacterDetailActivity.kt | 21 +++++++ .../detail/CharacterDetailRepository.kt | 12 +++- .../detail/CharacterDetailViewModel.kt | 59 ++++++++++++++++++- .../vividnext/sodalive/chat/talk/TalkApi.kt | 10 ++++ .../chat/talk/room/ChatRoomActivity.kt | 26 ++++++++ .../chat/talk/room/CreateChatRoomRequest.kt | 9 +++ .../chat/talk/room/CreateChatRoomResponse.kt | 9 +++ .../java/kr/co/vividnext/sodalive/di/AppDI.kt | 2 +- .../main/res/layout/activity_chat_room.xml | 6 ++ 10 files changed, 150 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/CreateChatRoomRequest.kt create mode 100644 app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/CreateChatRoomResponse.kt create mode 100644 app/src/main/res/layout/activity_chat_room.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bcf2571a..40997222 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -285,5 +285,7 @@ + + diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailActivity.kt index 26a48128..85dfb314 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailActivity.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailActivity.kt @@ -14,6 +14,7 @@ import kr.co.vividnext.sodalive.base.BaseActivity import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.databinding.ActivityCharacterDetailBinding import kr.co.vividnext.sodalive.extensions.dpToPx +import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity import org.koin.androidx.viewmodel.ext.android.viewModel class CharacterDetailActivity : BaseActivity( @@ -104,6 +105,18 @@ class CharacterDetailActivity : BaseActivity( binding.llPersonalityExpand.setOnClickListener { togglePersonalityExpand() } + + // 대화하기 버튼 클릭: 채팅방 생성 API 호출 + binding.btnChat.setOnClickListener { + val idFromState = viewModel.uiState.value?.detail?.characterId ?: 0L + val idFromIntent = intent.getLongExtra(EXTRA_CHARACTER_ID, 0L) + val targetId = if (idFromState > 0) idFromState else idFromIntent + if (targetId > 0) { + viewModel.createChatRoom(targetId) + } else { + showToast("잘못된 접근 입니다.") + } + } } private fun bindObservers() { @@ -122,6 +135,14 @@ class CharacterDetailActivity : BaseActivity( } } + // 2-1) 채팅방 생성 성공 처리 (이벤트) + state.chatRoomId?.let { roomId -> + showToast("채팅방이 생성되었습니다. (ID: $roomId)") + // 생성된 채팅방으로 이동 처리 + startActivity(ChatRoomActivity.newIntent(this, roomId)) + viewModel.consumeChatRoomCreated() + } + // 3) 상세 데이터가 있을 경우에만 기존 UI 바인딩 수행 val detail = state.detail ?: return@observe diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailRepository.kt index 6ebe11de..140cd25b 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailRepository.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailRepository.kt @@ -1,8 +1,16 @@ package kr.co.vividnext.sodalive.chat.character.detail import kr.co.vividnext.sodalive.chat.character.CharacterApi +import kr.co.vividnext.sodalive.chat.talk.TalkApi +import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomRequest -class CharacterDetailRepository(private val api: CharacterApi) { +class CharacterDetailRepository( + private val characterApi: CharacterApi, + private val talkApi: TalkApi +) { fun getCharacterDetail(token: String, characterId: Long) = - api.getCharacterDetail(authHeader = token, characterId = characterId) + characterApi.getCharacterDetail(authHeader = token, characterId = characterId) + + fun createChatRoom(token: String, request: CreateChatRoomRequest) = + talkApi.createChatRoom(authHeader = token, request = request) } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailViewModel.kt index 4ecc4c93..35c188cc 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailViewModel.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/character/detail/CharacterDetailViewModel.kt @@ -6,6 +6,7 @@ import com.orhanobut.logger.Logger import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import kr.co.vividnext.sodalive.base.BaseViewModel +import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomRequest import kr.co.vividnext.sodalive.common.SharedPreferenceManager /** @@ -23,7 +24,8 @@ class CharacterDetailViewModel( data class UiState( val detail: CharacterDetailResponse? = null, val isLoading: Boolean = false, - val error: String? = null + val error: String? = null, + val chatRoomId: Long? = null ) private val _uiState = MutableLiveData(UiState()) @@ -44,14 +46,65 @@ class CharacterDetailViewModel( if (success && data != null) { _uiState.value = UiState(detail = data, isLoading = false, error = null) } else { - _uiState.value = UiState(detail = null, isLoading = false, error = response.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + _uiState.value = UiState( + detail = null, + isLoading = false, + error = response.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) } }, { throwable -> Logger.e(throwable, throwable.message ?: "") - _uiState.value = UiState(detail = null, isLoading = false, error = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.") + _uiState.value = UiState( + detail = null, + isLoading = false, + error = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요." + ) } ) ) } + + fun createChatRoom(characterId: Long) { + // 기존 상태 유지하면서 로딩/에러/이벤트만 변경 + val current = _uiState.value + _uiState.value = current?.copy(isLoading = true, error = null, chatRoomId = null) + + val token = "Bearer ${SharedPreferenceManager.token}" + val request = CreateChatRoomRequest(characterId) + + compositeDisposable.add( + repository.createChatRoom(token = token, request = request) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { response -> + val success = response.success + val data = response.data + if (success && data != null) { + _uiState.value = _uiState.value?.copy( + isLoading = false, + chatRoomId = data.chatRoomId + ) + } else { + _uiState.value = _uiState.value?.copy( + isLoading = false, + error = response.message ?: "채팅방 생성에 실패했습니다. 다시 시도해 주세요." + ) + } + }, + { throwable -> + Logger.e(throwable, throwable.message ?: "") + _uiState.value = _uiState.value?.copy( + isLoading = false, + error = "채팅방 생성 중 오류가 발생했습니다. 다시 시도해 주세요." + ) + } + ) + ) + } + + fun consumeChatRoomCreated() { + _uiState.value = _uiState.value?.copy(chatRoomId = null) + } } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkApi.kt index f3177e71..c52b6ac3 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkApi.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/TalkApi.kt @@ -1,13 +1,23 @@ package kr.co.vividnext.sodalive.chat.talk import io.reactivex.rxjava3.core.Single +import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomRequest +import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomResponse import kr.co.vividnext.sodalive.common.ApiResponse +import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.Header +import retrofit2.http.POST interface TalkApi { @GET("/api/chat/room/list") fun getTalkRooms( @Header("Authorization") authHeader: String ): Single>> + + @POST("/api/chat/room/create") + fun createChatRoom( + @Header("Authorization") authHeader: String, + @Body request: CreateChatRoomRequest + ): Single> } diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt new file mode 100644 index 00000000..01fad949 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/ChatRoomActivity.kt @@ -0,0 +1,26 @@ +package kr.co.vividnext.sodalive.chat.talk.room + +import android.content.Context +import android.content.Intent +import kr.co.vividnext.sodalive.base.BaseActivity +import kr.co.vividnext.sodalive.databinding.ActivityChatRoomBinding + +class ChatRoomActivity : BaseActivity( + ActivityChatRoomBinding::inflate +) { + override fun setupView() { + // TODO: roomId를 활용한 채팅방 초기화 로직 추가 예정 + val roomId = intent.getLongExtra(EXTRA_ROOM_ID, 0L) + // 필요 시 roomId 유효성 체크 및 UI 바인딩 + } + + companion object { + const val EXTRA_ROOM_ID: String = "extra_room_id" + + fun newIntent(context: Context, roomId: Long): Intent { + return Intent(context, ChatRoomActivity::class.java).apply { + putExtra(EXTRA_ROOM_ID, roomId) + } + } + } +} diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/CreateChatRoomRequest.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/CreateChatRoomRequest.kt new file mode 100644 index 00000000..e9090889 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/CreateChatRoomRequest.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.chat.talk.room + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class CreateChatRoomRequest( + @SerializedName("characterId") val characterId: Long +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/CreateChatRoomResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/CreateChatRoomResponse.kt new file mode 100644 index 00000000..12ee56b9 --- /dev/null +++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/talk/room/CreateChatRoomResponse.kt @@ -0,0 +1,9 @@ +package kr.co.vividnext.sodalive.chat.talk.room + +import androidx.annotation.Keep +import com.google.gson.annotations.SerializedName + +@Keep +data class CreateChatRoomResponse( + @SerializedName("chatRoomId") val chatRoomId: Long +) diff --git a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt index c62f1bb4..632af85c 100644 --- a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt +++ b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt @@ -400,7 +400,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) { factory { PointStatusRepository(get()) } factory { HomeRepository(get()) } factory { CharacterTabRepository(get()) } - factory { CharacterDetailRepository(get()) } + factory { CharacterDetailRepository(get(), get()) } factory { TalkTabRepository(get()) } } diff --git a/app/src/main/res/layout/activity_chat_room.xml b/app/src/main/res/layout/activity_chat_room.xml new file mode 100644 index 00000000..13544084 --- /dev/null +++ b/app/src/main/res/layout/activity_chat_room.xml @@ -0,0 +1,6 @@ + + + +