캐릭터 상세 문자열 리소스화
CharacterDetail/갤러리 탭 다국어 리소스 추가 UiText로 오류 메시지 지역화 처리
This commit is contained in:
@@ -17,7 +17,7 @@ class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
|
||||
val characterId = intent.getLongExtra(EXTRA_CHARACTER_ID, 0)
|
||||
|
||||
if (characterId <= 0) {
|
||||
showToast("잘못된 접근 입니다.")
|
||||
showToast(getString(R.string.character_detail_error_invalid_access))
|
||||
finish()
|
||||
return
|
||||
}
|
||||
@@ -28,11 +28,15 @@ class CharacterDetailActivity : BaseActivity<ActivityCharacterDetailBinding>(
|
||||
override fun setupView() {
|
||||
// 뒤로 가기
|
||||
binding.detailToolbar.tvBack.setOnClickListener { finish() }
|
||||
binding.detailToolbar.tvBack.text = "캐릭터 정보"
|
||||
binding.detailToolbar.tvBack.text = getString(R.string.screen_character_detail_title)
|
||||
|
||||
// 탭 구성: 상세, 갤러리
|
||||
binding.tabLayout.addTab(binding.tabLayout.newTab().setText("상세"))
|
||||
binding.tabLayout.addTab(binding.tabLayout.newTab().setText("갤러리"))
|
||||
binding.tabLayout.addTab(
|
||||
binding.tabLayout.newTab().setText(R.string.screen_character_detail_tab_info)
|
||||
)
|
||||
binding.tabLayout.addTab(
|
||||
binding.tabLayout.newTab().setText(R.string.screen_character_detail_tab_gallery)
|
||||
)
|
||||
|
||||
val characterId = intent.getLongExtra(EXTRA_CHARACTER_ID, 0)
|
||||
|
||||
|
||||
@@ -92,10 +92,9 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
||||
}
|
||||
|
||||
// 2) 에러 토스트 처리
|
||||
state.error?.let { errorMsg ->
|
||||
if (errorMsg.isNotBlank()) {
|
||||
showToast(errorMsg)
|
||||
}
|
||||
state.error?.let { error ->
|
||||
val message = error.asString(requireContext())
|
||||
if (message.isNotBlank()) showToast(message)
|
||||
}
|
||||
|
||||
// 2-1) 채팅방 생성 성공 처리 (이벤트)
|
||||
@@ -143,7 +142,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
||||
|
||||
if (detail.age != null) {
|
||||
binding.tvAge.visibility = View.VISIBLE
|
||||
binding.tvAge.text = "${detail.age}세"
|
||||
binding.tvAge.text = getString(R.string.character_detail_age, detail.age)
|
||||
} else {
|
||||
binding.tvAge.visibility = View.GONE
|
||||
}
|
||||
@@ -167,8 +166,8 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
||||
|
||||
binding.tvCharacterName.text = detail.name
|
||||
binding.tvCharacterStatus.text = when (detail.characterType) {
|
||||
CharacterType.CLONE -> "Clone"
|
||||
CharacterType.CHARACTER -> "Character"
|
||||
CharacterType.CLONE -> getString(R.string.chat_character_type_clone)
|
||||
CharacterType.CHARACTER -> getString(R.string.chat_character_type_character)
|
||||
}
|
||||
// 캐릭터 타입에 따른 배경 설정
|
||||
binding.tvCharacterStatus.setBackgroundResource(
|
||||
@@ -192,7 +191,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
||||
// 표시 상태는 항상 접힘 상태로 시작
|
||||
applyWorldviewCollapsedLayout()
|
||||
isWorldviewExpanded = false
|
||||
binding.tvWorldviewExpand.text = "더보기"
|
||||
binding.tvWorldviewExpand.text = getString(R.string.read_more)
|
||||
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_down)
|
||||
}
|
||||
|
||||
@@ -207,7 +206,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
||||
binding.llPersonalityExpand.visibility = if (needExpand) View.VISIBLE else View.GONE
|
||||
applyPersonalityCollapsedLayout()
|
||||
isPersonalityExpanded = false
|
||||
binding.tvPersonalityExpand.text = "더보기"
|
||||
binding.tvPersonalityExpand.text = getString(R.string.read_more)
|
||||
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_down)
|
||||
}
|
||||
|
||||
@@ -300,7 +299,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
||||
val idFromState = viewModel.uiState.value?.detail?.characterId ?: 0L
|
||||
val targetCharacterId = if (idFromState > 0) idFromState else characterId
|
||||
if (targetCharacterId <= 0) {
|
||||
showToast("잘못된 접근 입니다.")
|
||||
showToast(getString(R.string.character_detail_error_invalid_access))
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
@@ -313,13 +312,15 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
||||
.subscribe({ resp ->
|
||||
if (resp.success) {
|
||||
binding.etCommentInput.setText("")
|
||||
showToast("등록되었습니다.")
|
||||
showToast(getString(R.string.character_detail_comment_register_success))
|
||||
viewModel.load(targetCharacterId)
|
||||
} else {
|
||||
showToast(resp.message ?: "요청 중 오류가 발생했습니다")
|
||||
showToast(
|
||||
resp.message ?: getString(R.string.common_error_request)
|
||||
)
|
||||
}
|
||||
}, { e ->
|
||||
showToast(e.message ?: "요청 중 오류가 발생했습니다")
|
||||
showToast(e.message ?: getString(R.string.common_error_request))
|
||||
})
|
||||
compositeDisposable.add(d)
|
||||
}
|
||||
@@ -384,7 +385,7 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
||||
if (targetId > 0) {
|
||||
viewModel.createChatRoom(targetId)
|
||||
} else {
|
||||
showToast("잘못된 접근 입니다.")
|
||||
showToast(getString(R.string.character_detail_error_invalid_access))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,12 +396,12 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
||||
// 확장 상태
|
||||
binding.tvWorldviewContent.maxLines = Integer.MAX_VALUE
|
||||
binding.tvWorldviewContent.ellipsize = null
|
||||
binding.tvWorldviewExpand.text = "간략히"
|
||||
binding.tvWorldviewExpand.text = getString(R.string.read_less)
|
||||
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_up)
|
||||
} else {
|
||||
// 접힘 상태 (3줄)
|
||||
applyWorldviewCollapsedLayout()
|
||||
binding.tvWorldviewExpand.text = "더보기"
|
||||
binding.tvWorldviewExpand.text = getString(R.string.read_more)
|
||||
binding.ivWorldviewExpand.setImageResource(R.drawable.ic_chevron_down)
|
||||
}
|
||||
}
|
||||
@@ -415,11 +416,11 @@ class CharacterDetailFragment : BaseFragment<FragmentCharacterDetailBinding>(
|
||||
if (isPersonalityExpanded) {
|
||||
binding.tvPersonalityContent.maxLines = Integer.MAX_VALUE
|
||||
binding.tvPersonalityContent.ellipsize = null
|
||||
binding.tvPersonalityExpand.text = "간략히"
|
||||
binding.tvPersonalityExpand.text = getString(R.string.read_less)
|
||||
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_up)
|
||||
} else {
|
||||
applyPersonalityCollapsedLayout()
|
||||
binding.tvPersonalityExpand.text = "더보기"
|
||||
binding.tvPersonalityExpand.text = getString(R.string.read_more)
|
||||
binding.ivPersonalityExpand.setImageResource(R.drawable.ic_chevron_down)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ import androidx.lifecycle.MutableLiveData
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.chat.talk.room.CreateChatRoomRequest
|
||||
import kr.co.vividnext.sodalive.common.UiText
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
|
||||
/**
|
||||
@@ -24,7 +26,7 @@ class CharacterDetailViewModel(
|
||||
data class UiState(
|
||||
val detail: CharacterDetailResponse? = null,
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null,
|
||||
val error: UiText? = null,
|
||||
val chatRoomId: Long? = null
|
||||
)
|
||||
|
||||
@@ -49,7 +51,9 @@ class CharacterDetailViewModel(
|
||||
_uiState.value = UiState(
|
||||
detail = null,
|
||||
isLoading = false,
|
||||
error = response.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
error = response.message?.takeIf { it.isNotBlank() }
|
||||
?.let { UiText.DynamicString(it) }
|
||||
?: UiText.StringResource(R.string.common_error_unknown)
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -58,7 +62,7 @@ class CharacterDetailViewModel(
|
||||
_uiState.value = UiState(
|
||||
detail = null,
|
||||
isLoading = false,
|
||||
error = "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
error = UiText.StringResource(R.string.common_error_unknown)
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -89,7 +93,9 @@ class CharacterDetailViewModel(
|
||||
} else {
|
||||
_uiState.value = _uiState.value?.copy(
|
||||
isLoading = false,
|
||||
error = response.message ?: "채팅방 생성에 실패했습니다. 다시 시도해 주세요."
|
||||
error = response.message?.takeIf { it.isNotBlank() }
|
||||
?.let { UiText.DynamicString(it) }
|
||||
?: UiText.StringResource(R.string.character_detail_chat_room_create_failed)
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -97,7 +103,7 @@ class CharacterDetailViewModel(
|
||||
Logger.e(throwable, throwable.message ?: "")
|
||||
_uiState.value = _uiState.value?.copy(
|
||||
isLoading = false,
|
||||
error = "채팅방 생성 중 오류가 발생했습니다. 다시 시도해 주세요."
|
||||
error = UiText.StringResource(R.string.character_detail_chat_room_create_failed)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.view.View
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||
import kr.co.vividnext.sodalive.base.SodaDialog
|
||||
import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity.Companion.EXTRA_CHARACTER_ID
|
||||
@@ -106,11 +107,16 @@ class CharacterGalleryFragment : BaseFragment<FragmentCharacterGalleryBinding>(
|
||||
binding.clRatio.visibility = View.VISIBLE
|
||||
|
||||
val percent = (state.ratio * 100).toInt()
|
||||
binding.tvRatioLeft.text = "$percent% 보유중"
|
||||
binding.tvRatioLeft.text =
|
||||
getString(R.string.character_gallery_ratio_owned, percent)
|
||||
|
||||
val ownedStr = state.ownedCount.toString()
|
||||
val totalStr = state.totalCount.toString()
|
||||
val fullText = "$ownedStr / ${totalStr}개"
|
||||
val fullText = getString(
|
||||
R.string.character_gallery_ratio_count,
|
||||
state.ownedCount,
|
||||
state.totalCount
|
||||
)
|
||||
val spannable = android.text.SpannableString(fullText)
|
||||
val ownedColor = "#FDD453".toColorInt()
|
||||
spannable.setSpan(
|
||||
@@ -132,7 +138,10 @@ class CharacterGalleryFragment : BaseFragment<FragmentCharacterGalleryBinding>(
|
||||
binding.clRatio.visibility = View.GONE
|
||||
}
|
||||
|
||||
state.error?.let { showToast(it) }
|
||||
state.error?.let { error ->
|
||||
val message = error.asString(requireContext())
|
||||
if (message.isNotBlank()) showToast(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,13 +149,16 @@ class CharacterGalleryFragment : BaseFragment<FragmentCharacterGalleryBinding>(
|
||||
SodaDialog(
|
||||
activity = requireActivity(),
|
||||
layoutInflater = this.layoutInflater,
|
||||
title = "구매 확인",
|
||||
desc = "선택한 이미지를 구매하시겠습니까?",
|
||||
confirmButtonTitle = "${item.imagePriceCan}캔으로 구매",
|
||||
title = getString(R.string.character_gallery_purchase_title),
|
||||
desc = getString(R.string.character_gallery_purchase_desc),
|
||||
confirmButtonTitle = getString(
|
||||
R.string.character_gallery_purchase_confirm,
|
||||
item.imagePriceCan
|
||||
),
|
||||
confirmButtonClick = {
|
||||
viewModel.purchaseImage(item.id, position)
|
||||
},
|
||||
cancelButtonTitle = "취소"
|
||||
cancelButtonTitle = getString(R.string.cancel)
|
||||
).show(screenWidth)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ import androidx.lifecycle.MutableLiveData
|
||||
import com.orhanobut.logger.Logger
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||
import kr.co.vividnext.sodalive.R
|
||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||
import kr.co.vividnext.sodalive.common.UiText
|
||||
|
||||
class CharacterGalleryViewModel(
|
||||
private val repository: CharacterGalleryRepository
|
||||
@@ -18,7 +20,7 @@ class CharacterGalleryViewModel(
|
||||
val ratio: Float = 0f, // 0.0 ~ 1.0
|
||||
val items: List<CharacterImageListItemResponse> = emptyList(),
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null
|
||||
val error: UiText? = null
|
||||
)
|
||||
|
||||
private val _uiState = MutableLiveData(UiState())
|
||||
@@ -92,7 +94,9 @@ class CharacterGalleryViewModel(
|
||||
} else {
|
||||
_uiState.value = _uiState.value?.copy(
|
||||
isLoading = isRequesting || isPurchasing,
|
||||
error = response.message ?: "갤러리 정보를 불러오지 못했습니다."
|
||||
error = response.message?.takeIf { it.isNotBlank() }
|
||||
?.let { UiText.DynamicString(it) }
|
||||
?: UiText.StringResource(R.string.character_gallery_load_error)
|
||||
)
|
||||
}
|
||||
}, { throwable ->
|
||||
@@ -100,7 +104,7 @@ class CharacterGalleryViewModel(
|
||||
Logger.e(throwable, throwable.message ?: "")
|
||||
_uiState.value = _uiState.value?.copy(
|
||||
isLoading = isRequesting || isPurchasing,
|
||||
error = "네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요."
|
||||
error = UiText.StringResource(R.string.common_error_network_retry)
|
||||
)
|
||||
})
|
||||
)
|
||||
@@ -153,7 +157,9 @@ class CharacterGalleryViewModel(
|
||||
)
|
||||
} else {
|
||||
_uiState.value = _uiState.value?.copy(
|
||||
error = response.message ?: "구매에 실패했습니다."
|
||||
error = response.message?.takeIf { it.isNotBlank() }
|
||||
?.let { UiText.DynamicString(it) }
|
||||
?: UiText.StringResource(R.string.character_gallery_purchase_failed)
|
||||
)
|
||||
}
|
||||
},
|
||||
@@ -162,7 +168,7 @@ class CharacterGalleryViewModel(
|
||||
Logger.e(throwable, throwable.message ?: "")
|
||||
_uiState.value = _uiState.value?.copy(
|
||||
isLoading = isRequesting || isPurchasing,
|
||||
error = "네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요."
|
||||
error = UiText.StringResource(R.string.common_error_network_retry)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
16
app/src/main/java/kr/co/vividnext/sodalive/common/UiText.kt
Normal file
16
app/src/main/java/kr/co/vividnext/sodalive/common/UiText.kt
Normal file
@@ -0,0 +1,16 @@
|
||||
package kr.co.vividnext.sodalive.common
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
|
||||
sealed class UiText {
|
||||
data class DynamicString(val value: String) : UiText()
|
||||
class StringResource(@StringRes val resId: Int, vararg val args: Any) : UiText() {
|
||||
val formatArgs: Array<out Any> = args
|
||||
}
|
||||
|
||||
fun asString(context: Context): String = when (this) {
|
||||
is DynamicString -> value
|
||||
is StringResource -> context.getString(resId, *formatArgs)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user