캐릭터 상세 - 폰 언어 설정에 따라 번역 데이터를 조회하도록 수정

This commit is contained in:
2025-12-11 20:09:58 +09:00
parent 6f67c4e8e1
commit e2c7134f61
6 changed files with 73 additions and 17 deletions

View File

@@ -6,12 +6,12 @@ import kr.co.vividnext.sodalive.chat.character.detail.gallery.CharacterImageList
import kr.co.vividnext.sodalive.chat.character.detail.gallery.CharacterImagePurchaseRequest import kr.co.vividnext.sodalive.chat.character.detail.gallery.CharacterImagePurchaseRequest
import kr.co.vividnext.sodalive.chat.character.detail.gallery.CharacterImagePurchaseResponse import kr.co.vividnext.sodalive.chat.character.detail.gallery.CharacterImagePurchaseResponse
import kr.co.vividnext.sodalive.common.ApiResponse import kr.co.vividnext.sodalive.common.ApiResponse
import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.Header import retrofit2.http.Header
import retrofit2.http.POST
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query import retrofit2.http.Query
import retrofit2.http.Body
import retrofit2.http.POST
interface CharacterApi { interface CharacterApi {
@GET("/api/chat/character/main") @GET("/api/chat/character/main")
@@ -22,7 +22,8 @@ interface CharacterApi {
@GET("/api/chat/character/{characterId}") @GET("/api/chat/character/{characterId}")
fun getCharacterDetail( fun getCharacterDetail(
@Header("Authorization") authHeader: String, @Header("Authorization") authHeader: String,
@Path("characterId") characterId: Long @Path("characterId") characterId: Long,
@Query("languageCode") languageCode: String
): Single<ApiResponse<CharacterDetailResponse>> ): Single<ApiResponse<CharacterDetailResponse>>
@GET("/api/chat/character/image/list") @GET("/api/chat/character/image/list")

View File

@@ -23,6 +23,7 @@ import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity.Co
import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity import kr.co.vividnext.sodalive.chat.talk.room.ChatRoomActivity
import kr.co.vividnext.sodalive.common.LoadingDialog import kr.co.vividnext.sodalive.common.LoadingDialog
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.common.Utils.getCurrentLanguageCode
import kr.co.vividnext.sodalive.databinding.FragmentCharacterDetailBinding import kr.co.vividnext.sodalive.databinding.FragmentCharacterDetailBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
@@ -78,7 +79,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
setupView() setupView()
bindObservers() bindObservers()
viewModel.load(characterId) viewModel.load(characterId, languageCode = getCurrentLanguageCode(requireContext()))
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@@ -117,7 +118,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
// 기본 정보 // 기본 정보
if (detail.gender != null) { if (detail.gender != null) {
binding.tvGender.visibility = View.VISIBLE binding.tvGender.visibility = View.VISIBLE
binding.tvGender.text = detail.gender binding.tvGender.text = detail.translated?.gender ?: detail.gender
if (detail.gender == "남성") { if (detail.gender == "남성") {
binding.tvGender.setTextColor( binding.tvGender.setTextColor(
@@ -164,7 +165,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
View.VISIBLE View.VISIBLE
} }
binding.tvCharacterName.text = detail.name binding.tvCharacterName.text = detail.translated?.name ?: detail.name
binding.tvCharacterStatus.text = when (detail.characterType) { binding.tvCharacterStatus.text = when (detail.characterType) {
CharacterType.CLONE -> getString(R.string.chat_character_type_clone) CharacterType.CLONE -> getString(R.string.chat_character_type_clone)
CharacterType.CHARACTER -> getString(R.string.chat_character_type_character) CharacterType.CHARACTER -> getString(R.string.chat_character_type_character)
@@ -176,11 +177,13 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
CharacterType.CHARACTER -> R.drawable.bg_character_status_character CharacterType.CHARACTER -> R.drawable.bg_character_status_character
} }
) )
binding.tvCharacterDescription.text = detail.description binding.tvCharacterDescription.text = detail.translated?.description
binding.tvCharacterTags.text = detail.tags ?: detail.description
binding.tvCharacterTags.text = detail.translated?.tags ?: detail.tags
// 세계관 내용과 버튼 가시성 초기화 // 세계관 내용과 버튼 가시성 초기화
val worldviewText = detail.backgrounds?.description.orEmpty() val worldviewText = detail.translated?.background?.description
?: detail.backgrounds?.description.orEmpty()
binding.tvWorldviewContent.text = worldviewText binding.tvWorldviewContent.text = worldviewText
// 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용 // 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용
binding.tvWorldviewContent.post { binding.tvWorldviewContent.post {
@@ -196,7 +199,8 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
} }
// 성격 내용과 버튼 가시성 초기화 // 성격 내용과 버튼 가시성 초기화
val personalityText = detail.personalities?.description.orEmpty() val personalityText = detail.translated?.personality?.description
?: detail.personalities?.description.orEmpty()
binding.tvPersonalityContent.text = personalityText binding.tvPersonalityContent.text = personalityText
// 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용 // 먼저 전체 줄 수를 측정한 뒤 접힘 레이아웃 적용
binding.tvPersonalityContent.post { binding.tvPersonalityContent.post {
@@ -313,7 +317,10 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
if (resp.success) { if (resp.success) {
binding.etCommentInput.setText("") binding.etCommentInput.setText("")
showToast(getString(R.string.character_detail_comment_register_success)) showToast(getString(R.string.character_detail_comment_register_success))
viewModel.load(targetCharacterId) viewModel.load(
targetCharacterId,
languageCode = getCurrentLanguageCode(requireContext())
)
} else { } else {
showToast( showToast(
resp.message ?: getString(R.string.common_error_request) resp.message ?: getString(R.string.common_error_request)

View File

@@ -8,8 +8,15 @@ class CharacterDetailRepository(
private val characterApi: CharacterApi, private val characterApi: CharacterApi,
private val talkApi: TalkApi private val talkApi: TalkApi
) { ) {
fun getCharacterDetail(token: String, characterId: Long) = fun getCharacterDetail(
characterApi.getCharacterDetail(authHeader = token, characterId = characterId) token: String,
characterId: Long,
languageCode: String
) = characterApi.getCharacterDetail(
authHeader = token,
characterId = characterId,
languageCode = languageCode
)
fun createChatRoom(token: String, request: CreateChatRoomRequest) = fun createChatRoom(token: String, request: CreateChatRoomRequest) =
talkApi.createChatRoom(authHeader = token, request = request) talkApi.createChatRoom(authHeader = token, request = request)

View File

@@ -21,7 +21,8 @@ data class CharacterDetailResponse(
@SerializedName("characterType") val characterType: CharacterType, @SerializedName("characterType") val characterType: CharacterType,
@SerializedName("others") val others: List<OtherCharacter>, @SerializedName("others") val others: List<OtherCharacter>,
@SerializedName("latestComment") val latestComment: CharacterCommentResponse?, @SerializedName("latestComment") val latestComment: CharacterCommentResponse?,
@SerializedName("totalComments") val totalComments: Int @SerializedName("totalComments") val totalComments: Int,
@SerializedName("translated") val translated: TranslatedAiCharacterDetail?
) )
@Keep @Keep
@@ -51,3 +52,25 @@ data class CharacterBackgroundResponse(
@SerializedName("topic") val topic: String, @SerializedName("topic") val topic: String,
@SerializedName("description") val description: String @SerializedName("description") val description: String
) )
@Keep
data class TranslatedAiCharacterDetail(
@SerializedName("name") val name: String?,
@SerializedName("description") val description: String?,
@SerializedName("gender") val gender: String?,
@SerializedName("personality") val personality: TranslatedAiCharacterPersonality?,
@SerializedName("background") val background: TranslatedAiCharacterBackground?,
@SerializedName("tags") val tags: String?
)
@Keep
data class TranslatedAiCharacterPersonality(
@SerializedName("trait") val trait: String?,
@SerializedName("description") val description: String?
)
@Keep
data class TranslatedAiCharacterBackground(
@SerializedName("topic") val topic: String?,
@SerializedName("description") val description: String?
)

View File

@@ -8,8 +8,8 @@ import io.reactivex.rxjava3.schedulers.Schedulers
import kr.co.vividnext.sodalive.R import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.base.BaseViewModel import kr.co.vividnext.sodalive.base.BaseViewModel
import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomRequest import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomRequest
import kr.co.vividnext.sodalive.common.UiText
import kr.co.vividnext.sodalive.common.SharedPreferenceManager import kr.co.vividnext.sodalive.common.SharedPreferenceManager
import kr.co.vividnext.sodalive.common.UiText
/** /**
* 캐릭터 상세 화면에서 사용하는 ViewModel. * 캐릭터 상세 화면에서 사용하는 ViewModel.
@@ -33,12 +33,16 @@ class CharacterDetailViewModel(
private val _uiState = MutableLiveData(UiState()) private val _uiState = MutableLiveData(UiState())
val uiState: LiveData<UiState> get() = _uiState val uiState: LiveData<UiState> get() = _uiState
fun load(characterId: Long) { fun load(characterId: Long, languageCode: String) {
_uiState.value = _uiState.value?.copy(isLoading = true, error = null) _uiState.value = _uiState.value?.copy(isLoading = true, error = null)
val token = "Bearer ${SharedPreferenceManager.token}" val token = "Bearer ${SharedPreferenceManager.token}"
compositeDisposable.add( compositeDisposable.add(
repository.getCharacterDetail(token = token, characterId = characterId) repository.getCharacterDetail(
token = token,
characterId = characterId,
languageCode = languageCode
)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe( .subscribe(
@@ -46,6 +50,7 @@ class CharacterDetailViewModel(
val success = response.success val success = response.success
val data = response.data val data = response.data
if (success && data != null) { if (success && data != null) {
Logger.d("character detail: $data")
_uiState.value = UiState(detail = data, isLoading = false, error = null) _uiState.value = UiState(detail = data, isLoading = false, error = null)
} else { } else {
_uiState.value = UiState( _uiState.value = UiState(

View File

@@ -1,5 +1,7 @@
package kr.co.vividnext.sodalive.common package kr.co.vividnext.sodalive.common
import android.content.Context
import android.os.Build
import java.net.URLEncoder import java.net.URLEncoder
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
@@ -38,4 +40,15 @@ object Utils {
return "https://voiceon.onelink.me/RkTm?$encodedParams" return "https://voiceon.onelink.me/RkTm?$encodedParams"
} }
fun getCurrentLanguageCode(context: Context): String {
val config = context.resources.configuration
val locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.locales.get(0)
} else {
@Suppress("DEPRECATION")
config.locale
}
return locale.language // "ko", "en" 등
}
} }