댓글 목록 문자열 리소스화
댓글/답글 UI, 시간 포맷, 오류 문구 다국어 적용
This commit is contained in:
@@ -17,6 +17,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
|||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
|
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.databinding.FragmentCharacterCommentListBinding
|
import kr.co.vividnext.sodalive.databinding.FragmentCharacterCommentListBinding
|
||||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
@@ -173,8 +174,7 @@ class CharacterCommentListFragment : BaseFragment<FragmentCharacterCommentListBi
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewModel.toastLiveData.observe(viewLifecycleOwner) { msg ->
|
viewModel.toastLiveData.observe(viewLifecycleOwner) { msg ->
|
||||||
|
msg?.let { showToast(it.asString(requireContext())) }
|
||||||
msg?.let { showToast(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.totalCommentCount.observe(viewLifecycleOwner) { count ->
|
viewModel.totalCommentCount.observe(viewLifecycleOwner) { count ->
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ import com.orhanobut.logger.Logger
|
|||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
import kr.co.vividnext.sodalive.common.UiText
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
|
||||||
class CharacterCommentListViewModel(
|
class CharacterCommentListViewModel(
|
||||||
private val repository: CharacterCommentRepository
|
private val repository: CharacterCommentRepository
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val _toastLiveData = MutableLiveData<String?>()
|
private val _toastLiveData = MutableLiveData<UiText?>()
|
||||||
val toastLiveData: LiveData<String?>
|
val toastLiveData: LiveData<UiText?>
|
||||||
get() = _toastLiveData
|
get() = _toastLiveData
|
||||||
|
|
||||||
private val _isLoading = MutableLiveData(false)
|
private val _isLoading = MutableLiveData(false)
|
||||||
@@ -80,14 +82,16 @@ class CharacterCommentListViewModel(
|
|||||||
// 응답 아이템 전달 (비어있어도 전달) — UI는 addAll 처리
|
// 응답 아이템 전달 (비어있어도 전달) — UI는 addAll 처리
|
||||||
_commentList.postValue(items)
|
_commentList.postValue(items)
|
||||||
} else {
|
} else {
|
||||||
val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
val message = resp.message?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { UiText.DynamicString(it) }
|
||||||
|
?: UiText.StringResource(R.string.common_error_unknown)
|
||||||
_toastLiveData.postValue(message)
|
_toastLiveData.postValue(message)
|
||||||
onFailure?.invoke()
|
onFailure?.invoke()
|
||||||
}
|
}
|
||||||
}, { e ->
|
}, { e ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
Logger.e(e, "Character comments load failed")
|
Logger.e(e, "Character comments load failed")
|
||||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
_toastLiveData.postValue(UiText.StringResource(R.string.common_error_unknown))
|
||||||
onFailure?.invoke()
|
onFailure?.invoke()
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -95,7 +99,7 @@ class CharacterCommentListViewModel(
|
|||||||
|
|
||||||
fun createComment(characterId: Long, comment: String) {
|
fun createComment(characterId: Long, comment: String) {
|
||||||
if (comment.isBlank()) {
|
if (comment.isBlank()) {
|
||||||
_toastLiveData.postValue("내용을 입력하세요")
|
_toastLiveData.postValue(UiText.StringResource(R.string.character_comment_error_empty))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (_isLoading.value == true) return
|
if (_isLoading.value == true) return
|
||||||
@@ -119,13 +123,15 @@ class CharacterCommentListViewModel(
|
|||||||
cursor = null
|
cursor = null
|
||||||
getCommentList(characterId)
|
getCommentList(characterId)
|
||||||
} else {
|
} else {
|
||||||
val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
val message = resp.message?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { UiText.DynamicString(it) }
|
||||||
|
?: UiText.StringResource(R.string.common_error_unknown)
|
||||||
_toastLiveData.postValue(message)
|
_toastLiveData.postValue(message)
|
||||||
}
|
}
|
||||||
}, { e ->
|
}, { e ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
Logger.e(e, "Character comment create failed")
|
Logger.e(e, "Character comment create failed")
|
||||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
_toastLiveData.postValue(UiText.StringResource(R.string.common_error_unknown))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -151,20 +157,24 @@ class CharacterCommentListViewModel(
|
|||||||
cursor = null
|
cursor = null
|
||||||
getCommentList(characterId)
|
getCommentList(characterId)
|
||||||
} else {
|
} else {
|
||||||
val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
val message = resp.message?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { UiText.DynamicString(it) }
|
||||||
|
?: UiText.StringResource(R.string.common_error_unknown)
|
||||||
_toastLiveData.postValue(message)
|
_toastLiveData.postValue(message)
|
||||||
}
|
}
|
||||||
}, { e ->
|
}, { e ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
Logger.e(e, "Character comment delete failed")
|
Logger.e(e, "Character comment delete failed")
|
||||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
_toastLiveData.postValue(UiText.StringResource(R.string.common_error_unknown))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reportComment(characterId: Long, commentId: Long, reason: String) {
|
fun reportComment(characterId: Long, commentId: Long, reason: String) {
|
||||||
if (reason.isBlank()) {
|
if (reason.isBlank()) {
|
||||||
_toastLiveData.postValue("신고 사유를 입력하세요")
|
_toastLiveData.postValue(
|
||||||
|
UiText.StringResource(R.string.character_comment_error_report_reason)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (_isLoading.value == true) return
|
if (_isLoading.value == true) return
|
||||||
@@ -182,15 +192,19 @@ class CharacterCommentListViewModel(
|
|||||||
.subscribe({ resp ->
|
.subscribe({ resp ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
if (resp.success) {
|
if (resp.success) {
|
||||||
_toastLiveData.postValue("신고가 접수되었습니다.")
|
_toastLiveData.postValue(
|
||||||
|
UiText.StringResource(R.string.character_comment_report_submitted)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
val message = resp.message?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { UiText.DynamicString(it) }
|
||||||
|
?: UiText.StringResource(R.string.common_error_unknown)
|
||||||
_toastLiveData.postValue(message)
|
_toastLiveData.postValue(message)
|
||||||
}
|
}
|
||||||
}, { e ->
|
}, { e ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
Logger.e(e, "Character comment report failed")
|
Logger.e(e, "Character comment report failed")
|
||||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
_toastLiveData.postValue(UiText.StringResource(R.string.common_error_unknown))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class CharacterReplyHeaderVH(
|
|||||||
) : CharacterReplyVH(binding) {
|
) : CharacterReplyVH(binding) {
|
||||||
override fun bind(item: Any) {
|
override fun bind(item: Any) {
|
||||||
val data = item as CharacterCommentResponse
|
val data = item as CharacterCommentResponse
|
||||||
|
val context = binding.root.context
|
||||||
if (data.memberProfileImage.isNotBlank()) {
|
if (data.memberProfileImage.isNotBlank()) {
|
||||||
binding.ivCommentProfile.load(data.memberProfileImage) {
|
binding.ivCommentProfile.load(data.memberProfileImage) {
|
||||||
crossfade(true)
|
crossfade(true)
|
||||||
@@ -71,7 +72,7 @@ class CharacterReplyHeaderVH(
|
|||||||
binding.ivCommentProfile.setImageResource(R.drawable.ic_placeholder_profile)
|
binding.ivCommentProfile.setImageResource(R.drawable.ic_placeholder_profile)
|
||||||
}
|
}
|
||||||
binding.tvCommentNickname.text = data.memberNickname
|
binding.tvCommentNickname.text = data.memberNickname
|
||||||
binding.tvCommentDate.text = timeAgo(data.createdAt)
|
binding.tvCommentDate.text = formatCommentTime(context, data.createdAt)
|
||||||
binding.tvComment.text = data.comment
|
binding.tvComment.text = data.comment
|
||||||
binding.tvWriteReply.visibility = View.GONE
|
binding.tvWriteReply.visibility = View.GONE
|
||||||
binding.ivMenu.visibility = View.GONE
|
binding.ivMenu.visibility = View.GONE
|
||||||
@@ -86,6 +87,7 @@ class CharacterReplyItemVH(
|
|||||||
|
|
||||||
override fun bind(item: Any) {
|
override fun bind(item: Any) {
|
||||||
val data = item as CharacterReplyResponse
|
val data = item as CharacterReplyResponse
|
||||||
|
val context = binding.root.context
|
||||||
if (data.memberProfileImage.isNotBlank()) {
|
if (data.memberProfileImage.isNotBlank()) {
|
||||||
binding.ivCommentProfile.load(data.memberProfileImage) {
|
binding.ivCommentProfile.load(data.memberProfileImage) {
|
||||||
crossfade(true)
|
crossfade(true)
|
||||||
@@ -98,7 +100,7 @@ class CharacterReplyItemVH(
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.tvCommentNickname.text = data.memberNickname
|
binding.tvCommentNickname.text = data.memberNickname
|
||||||
binding.tvCommentDate.text = timeAgo(data.createdAt)
|
binding.tvCommentDate.text = formatCommentTime(context, data.createdAt)
|
||||||
binding.tvComment.text = data.comment
|
binding.tvComment.text = data.comment
|
||||||
|
|
||||||
val isOwner = data.memberId == currentUserId
|
val isOwner = data.memberId == currentUserId
|
||||||
@@ -109,17 +111,3 @@ class CharacterReplyItemVH(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun timeAgo(createdAtMillis: Long): String {
|
|
||||||
val now = System.currentTimeMillis()
|
|
||||||
val diff = (now - createdAtMillis).coerceAtLeast(0)
|
|
||||||
val minutes = diff / 60_000
|
|
||||||
if (minutes < 1) return "방금전"
|
|
||||||
if (minutes < 60) return "${minutes}분전"
|
|
||||||
val hours = minutes / 60
|
|
||||||
if (hours < 24) return "${hours}시간전"
|
|
||||||
val days = hours / 24
|
|
||||||
if (days < 365) return "${days}일전"
|
|
||||||
val years = days / 365
|
|
||||||
return "${years}년전"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import coil.transform.CircleCropTransformation
|
|||||||
import kr.co.vividnext.sodalive.R
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.base.BaseFragment
|
import kr.co.vividnext.sodalive.base.BaseFragment
|
||||||
import kr.co.vividnext.sodalive.common.LoadingDialog
|
import kr.co.vividnext.sodalive.common.LoadingDialog
|
||||||
|
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.databinding.FragmentCharacterCommentReplyBinding
|
import kr.co.vividnext.sodalive.databinding.FragmentCharacterCommentReplyBinding
|
||||||
import kr.co.vividnext.sodalive.extensions.dpToPx
|
import kr.co.vividnext.sodalive.extensions.dpToPx
|
||||||
@@ -179,7 +180,7 @@ class CharacterCommentReplyFragment : BaseFragment<FragmentCharacterCommentReply
|
|||||||
if (loading) loadingDialog.show(screenWidth) else loadingDialog.dismiss()
|
if (loading) loadingDialog.show(screenWidth) else loadingDialog.dismiss()
|
||||||
}
|
}
|
||||||
viewModel.toastLiveData.observe(viewLifecycleOwner) { msg ->
|
viewModel.toastLiveData.observe(viewLifecycleOwner) { msg ->
|
||||||
msg?.let { showToast(it) }
|
msg?.let { showToast(it.asString(requireContext())) }
|
||||||
}
|
}
|
||||||
viewModel.replies.observe(viewLifecycleOwner) { list ->
|
viewModel.replies.observe(viewLifecycleOwner) { list ->
|
||||||
// 헤더(원본 댓글)는 index 0에 유지, 나머지를 교체
|
// 헤더(원본 댓글)는 index 0에 유지, 나머지를 교체
|
||||||
|
|||||||
@@ -6,14 +6,16 @@ import com.orhanobut.logger.Logger
|
|||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.schedulers.Schedulers
|
import io.reactivex.rxjava3.schedulers.Schedulers
|
||||||
import kr.co.vividnext.sodalive.base.BaseViewModel
|
import kr.co.vividnext.sodalive.base.BaseViewModel
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
import kr.co.vividnext.sodalive.common.SharedPreferenceManager
|
||||||
|
import kr.co.vividnext.sodalive.common.UiText
|
||||||
|
|
||||||
class CharacterCommentReplyViewModel(
|
class CharacterCommentReplyViewModel(
|
||||||
private val repository: CharacterCommentRepository
|
private val repository: CharacterCommentRepository
|
||||||
) : BaseViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val _toastLiveData = MutableLiveData<String?>()
|
private val _toastLiveData = MutableLiveData<UiText?>()
|
||||||
val toastLiveData: LiveData<String?> get() = _toastLiveData
|
val toastLiveData: LiveData<UiText?> get() = _toastLiveData
|
||||||
|
|
||||||
private val _isLoading = MutableLiveData(false)
|
private val _isLoading = MutableLiveData(false)
|
||||||
val isLoading: LiveData<Boolean> get() = _isLoading
|
val isLoading: LiveData<Boolean> get() = _isLoading
|
||||||
@@ -65,20 +67,22 @@ class CharacterCommentReplyViewModel(
|
|||||||
cursor = resp.data.cursor
|
cursor = resp.data.cursor
|
||||||
page += 1
|
page += 1
|
||||||
} else {
|
} else {
|
||||||
val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
val message = resp.message?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { UiText.DynamicString(it) }
|
||||||
|
?: UiText.StringResource(R.string.common_error_unknown)
|
||||||
_toastLiveData.postValue(message)
|
_toastLiveData.postValue(message)
|
||||||
}
|
}
|
||||||
}, { e ->
|
}, { e ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
Logger.e(e, "Character replies load failed")
|
Logger.e(e, "Character replies load failed")
|
||||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
_toastLiveData.postValue(UiText.StringResource(R.string.common_error_unknown))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createReply(characterId: Long, comment: String) {
|
fun createReply(characterId: Long, comment: String) {
|
||||||
if (comment.isBlank()) {
|
if (comment.isBlank()) {
|
||||||
_toastLiveData.postValue("내용을 입력하세요")
|
_toastLiveData.postValue(UiText.StringResource(R.string.character_comment_error_empty))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val originalId = _original.value?.commentId ?: return
|
val originalId = _original.value?.commentId ?: return
|
||||||
@@ -110,13 +114,15 @@ class CharacterCommentReplyViewModel(
|
|||||||
val current = _replies.value ?: emptyList()
|
val current = _replies.value ?: emptyList()
|
||||||
_replies.postValue(current + listOf(me))
|
_replies.postValue(current + listOf(me))
|
||||||
} else {
|
} else {
|
||||||
val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
val message = resp.message?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { UiText.DynamicString(it) }
|
||||||
|
?: UiText.StringResource(R.string.common_error_unknown)
|
||||||
_toastLiveData.postValue(message)
|
_toastLiveData.postValue(message)
|
||||||
}
|
}
|
||||||
}, { e ->
|
}, { e ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
Logger.e(e, "Character reply create failed")
|
Logger.e(e, "Character reply create failed")
|
||||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
_toastLiveData.postValue(UiText.StringResource(R.string.common_error_unknown))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -139,20 +145,24 @@ class CharacterCommentReplyViewModel(
|
|||||||
val current = _replies.value ?: emptyList()
|
val current = _replies.value ?: emptyList()
|
||||||
_replies.postValue(current.filterNot { it.replyId == replyId })
|
_replies.postValue(current.filterNot { it.replyId == replyId })
|
||||||
} else {
|
} else {
|
||||||
val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
val message = resp.message?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { UiText.DynamicString(it) }
|
||||||
|
?: UiText.StringResource(R.string.common_error_unknown)
|
||||||
_toastLiveData.postValue(message)
|
_toastLiveData.postValue(message)
|
||||||
}
|
}
|
||||||
}, { e ->
|
}, { e ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
Logger.e(e, "Character reply delete failed")
|
Logger.e(e, "Character reply delete failed")
|
||||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
_toastLiveData.postValue(UiText.StringResource(R.string.common_error_unknown))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun reportReply(characterId: Long, replyId: Long, reason: String) {
|
fun reportReply(characterId: Long, replyId: Long, reason: String) {
|
||||||
if (reason.isBlank()) {
|
if (reason.isBlank()) {
|
||||||
_toastLiveData.postValue("신고 사유를 입력하세요")
|
_toastLiveData.postValue(
|
||||||
|
UiText.StringResource(R.string.character_comment_error_report_reason)
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (_isLoading.value == true) return
|
if (_isLoading.value == true) return
|
||||||
@@ -170,15 +180,19 @@ class CharacterCommentReplyViewModel(
|
|||||||
.subscribe({ resp ->
|
.subscribe({ resp ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
if (resp.success) {
|
if (resp.success) {
|
||||||
_toastLiveData.postValue("신고가 접수되었습니다.")
|
_toastLiveData.postValue(
|
||||||
|
UiText.StringResource(R.string.character_comment_report_submitted)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
val message = resp.message ?: "알 수 없는 오류가 발생했습니다. 다시 시도해 주세요."
|
val message = resp.message?.takeIf { it.isNotBlank() }
|
||||||
|
?.let { UiText.DynamicString(it) }
|
||||||
|
?: UiText.StringResource(R.string.common_error_unknown)
|
||||||
_toastLiveData.postValue(message)
|
_toastLiveData.postValue(message)
|
||||||
}
|
}
|
||||||
}, { e ->
|
}, { e ->
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
Logger.e(e, "Character reply report failed")
|
Logger.e(e, "Character reply report failed")
|
||||||
_toastLiveData.postValue("알 수 없는 오류가 발생했습니다. 다시 시도해 주세요.")
|
_toastLiveData.postValue(UiText.StringResource(R.string.common_error_unknown))
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,14 @@ class CharacterCommentReportBottomSheet : BottomSheetDialogFragment() {
|
|||||||
|
|
||||||
var onSubmit: ((String) -> Unit)? = null
|
var onSubmit: ((String) -> Unit)? = null
|
||||||
|
|
||||||
private var reasons: ArrayList<String>? = null
|
private var reasons: ArrayList<String> = ArrayList()
|
||||||
private var selectedIndex: Int = -1
|
private var selectedIndex: Int = -1
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
reasons = arguments?.getStringArrayList(ARG_REASONS) ?: DEFAULT_REASONS
|
val defaultReasons = resources.getStringArray(R.array.character_comment_report_reasons)
|
||||||
|
.toCollection(ArrayList())
|
||||||
|
reasons = arguments?.getStringArrayList(ARG_REASONS) ?: defaultReasons
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@@ -49,7 +51,7 @@ class CharacterCommentReportBottomSheet : BottomSheetDialogFragment() {
|
|||||||
tvTitle.text = getString(R.string.report_title)
|
tvTitle.text = getString(R.string.report_title)
|
||||||
setReportEnabled(btnReport, false)
|
setReportEnabled(btnReport, false)
|
||||||
|
|
||||||
val items = reasons ?: DEFAULT_REASONS
|
val items = reasons
|
||||||
val textColor = ContextCompat.getColor(requireContext(), R.color.white)
|
val textColor = ContextCompat.getColor(requireContext(), R.color.white)
|
||||||
|
|
||||||
// RadioButton 동적 생성 및 단일 선택 처리
|
// RadioButton 동적 생성 및 단일 선택 처리
|
||||||
@@ -65,7 +67,8 @@ class CharacterCommentReportBottomSheet : BottomSheetDialogFragment() {
|
|||||||
// 폰트: pretendard_regular
|
// 폰트: pretendard_regular
|
||||||
try {
|
try {
|
||||||
typeface = ResourcesCompat.getFont(context, R.font.pretendard_regular)
|
typeface = ResourcesCompat.getFont(context, R.font.pretendard_regular)
|
||||||
} catch (_: Exception) { /* 폰트 미존재 대비 안전 처리 */ }
|
} catch (_: Exception) { /* 폰트 미존재 대비 안전 처리 */
|
||||||
|
}
|
||||||
// 항목 간 간격: 기존 paddingVertical 12dp의 1.3배 -> 15.6dp
|
// 항목 간 간격: 기존 paddingVertical 12dp의 1.3배 -> 15.6dp
|
||||||
val vPadPx = (14f * resources.displayMetrics.density).toInt()
|
val vPadPx = (14f * resources.displayMetrics.density).toInt()
|
||||||
setPadding(paddingLeft, vPadPx, paddingRight, vPadPx)
|
setPadding(paddingLeft, vPadPx, paddingRight, vPadPx)
|
||||||
@@ -106,16 +109,6 @@ class CharacterCommentReportBottomSheet : BottomSheetDialogFragment() {
|
|||||||
companion object {
|
companion object {
|
||||||
private const val ARG_REASONS = "arg_reasons"
|
private const val ARG_REASONS = "arg_reasons"
|
||||||
|
|
||||||
private val DEFAULT_REASONS = arrayListOf(
|
|
||||||
"원치 않는 상업성 콘텐츠 또는 스팸",
|
|
||||||
"아동 학대",
|
|
||||||
"증오시 표현 또는 노골적인 폭력",
|
|
||||||
"테러 조장",
|
|
||||||
"희롱 또는 괴롭힘",
|
|
||||||
"자살 또는 자해",
|
|
||||||
"잘못된 정보"
|
|
||||||
)
|
|
||||||
|
|
||||||
fun newInstance(reasons: ArrayList<String>? = null): CharacterCommentReportBottomSheet {
|
fun newInstance(reasons: ArrayList<String>? = null): CharacterCommentReportBottomSheet {
|
||||||
return CharacterCommentReportBottomSheet().apply {
|
return CharacterCommentReportBottomSheet().apply {
|
||||||
arguments = bundleOf(ARG_REASONS to reasons)
|
arguments = bundleOf(ARG_REASONS to reasons)
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package kr.co.vividnext.sodalive.chat.character.comment
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
|
|
||||||
|
fun formatCommentTime(context: Context, createdAtMillis: Long): String {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val diff = (now - createdAtMillis).coerceAtLeast(0)
|
||||||
|
val minutes = diff / 60_000
|
||||||
|
return when {
|
||||||
|
minutes < 1 -> context.getString(R.string.character_comment_time_just_now)
|
||||||
|
minutes < 60 -> context.getString(R.string.character_comment_time_minutes, minutes)
|
||||||
|
minutes < 1_440 -> {
|
||||||
|
val hours = minutes / 60
|
||||||
|
context.getString(R.string.character_comment_time_hours, hours)
|
||||||
|
}
|
||||||
|
minutes < 525_600 -> {
|
||||||
|
val days = minutes / 1_440
|
||||||
|
context.getString(R.string.character_comment_time_days, days)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val years = minutes / 525_600
|
||||||
|
context.getString(R.string.character_comment_time_years, years)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package kr.co.vividnext.sodalive.chat.character.comment
|
package kr.co.vividnext.sodalive.chat.character.comment
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -20,6 +21,7 @@ class CharacterCommentsAdapter(
|
|||||||
inner class VH(private val binding: ItemCharacterCommentBinding) :
|
inner class VH(private val binding: ItemCharacterCommentBinding) :
|
||||||
RecyclerView.ViewHolder(binding.root) {
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
fun bind(item: CharacterCommentResponse) {
|
fun bind(item: CharacterCommentResponse) {
|
||||||
|
val context = binding.root.context
|
||||||
if (item.memberProfileImage.isNotBlank()) {
|
if (item.memberProfileImage.isNotBlank()) {
|
||||||
binding.ivCommentProfile.load(item.memberProfileImage) {
|
binding.ivCommentProfile.load(item.memberProfileImage) {
|
||||||
crossfade(true)
|
crossfade(true)
|
||||||
@@ -32,12 +34,15 @@ class CharacterCommentsAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
binding.tvCommentNickname.text = item.memberNickname
|
binding.tvCommentNickname.text = item.memberNickname
|
||||||
binding.tvCommentDate.text = timeAgo(item.createdAt)
|
binding.tvCommentDate.text = formatCommentTime(context, item.createdAt)
|
||||||
binding.tvComment.text = item.comment
|
binding.tvComment.text = item.comment
|
||||||
binding.tvWriteReply.text = if (item.replyCount > 0) {
|
binding.tvWriteReply.text = if (item.replyCount > 0) {
|
||||||
"답글 ${item.replyCount}개"
|
context.getString(
|
||||||
|
R.string.character_comment_reply_count,
|
||||||
|
item.replyCount
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
"답글 쓰기"
|
context.getString(R.string.character_comment_write_reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
val isOwner = item.memberId == currentUserId
|
val isOwner = item.memberId == currentUserId
|
||||||
@@ -61,17 +66,3 @@ class CharacterCommentsAdapter(
|
|||||||
|
|
||||||
override fun getItemCount(): Int = items.size
|
override fun getItemCount(): Int = items.size
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun timeAgo(createdAtMillis: Long): String {
|
|
||||||
val now = System.currentTimeMillis()
|
|
||||||
val diff = (now - createdAtMillis).coerceAtLeast(0)
|
|
||||||
val minutes = diff / 60_000
|
|
||||||
if (minutes < 1) return "방금전"
|
|
||||||
if (minutes < 60) return "${minutes}분전"
|
|
||||||
val hours = minutes / 60
|
|
||||||
if (hours < 24) return "${hours}시간전"
|
|
||||||
val days = hours / 24
|
|
||||||
if (days < 365) return "${days}일전"
|
|
||||||
val years = days / 365
|
|
||||||
return "${years}년전"
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingVertical="16dp"
|
android:paddingVertical="16dp"
|
||||||
android:text="신고"
|
android:text="@string/report_title"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="18sp" />
|
android:textSize="18sp" />
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingVertical="16dp"
|
android:paddingVertical="16dp"
|
||||||
android:text="삭제"
|
android:text="@string/confirm_delete_title"
|
||||||
android:textColor="#EF5350"
|
android:textColor="#EF5350"
|
||||||
android:textSize="18sp" />
|
android:textSize="18sp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="13.3dp"
|
android:layout_marginStart="13.3dp"
|
||||||
android:fontFamily="@font/gmarket_sans_medium"
|
android:fontFamily="@font/gmarket_sans_medium"
|
||||||
android:text="댓글"
|
android:text="@string/character_comment_title"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="14.7sp"
|
android:textSize="14.7sp"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/iv_close"
|
app:layout_constraintBottom_toBottomOf="@+id/iv_close"
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:fontFamily="@font/pretendard_regular"
|
android:fontFamily="@font/pretendard_regular"
|
||||||
android:hint="댓글을 입력해보세요"
|
android:hint="@string/character_comment_input_hint"
|
||||||
android:imeOptions="actionSend"
|
android:imeOptions="actionSend"
|
||||||
android:importantForAutofill="no"
|
android:importantForAutofill="no"
|
||||||
android:inputType="textCapSentences|textMultiLine"
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
android:drawablePadding="6.7dp"
|
android:drawablePadding="6.7dp"
|
||||||
android:fontFamily="@font/gmarket_sans_medium"
|
android:fontFamily="@font/gmarket_sans_medium"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:text="답글"
|
android:text="@string/character_comment_reply_title"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="14.7sp"
|
android:textSize="14.7sp"
|
||||||
app:drawableStartCompat="@drawable/ic_back"
|
app:drawableStartCompat="@drawable/ic_back"
|
||||||
@@ -79,7 +79,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:fontFamily="@font/pretendard_regular"
|
android:fontFamily="@font/pretendard_regular"
|
||||||
android:hint="댓글을 입력해보세요"
|
android:hint="@string/character_comment_input_hint"
|
||||||
android:imeOptions="actionSend"
|
android:imeOptions="actionSend"
|
||||||
android:importantForAutofill="no"
|
android:importantForAutofill="no"
|
||||||
android:inputType="textCapSentences|textMultiLine"
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:fontFamily="@font/pretendard_bold"
|
android:fontFamily="@font/pretendard_bold"
|
||||||
android:text="답글 쓰기"
|
android:text="@string/character_comment_write_reply"
|
||||||
android:textColor="@color/color_3bb9f1"
|
android:textColor="@color/color_3bb9f1"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
app:layout_constraintStart_toStartOf="@+id/tv_comment"
|
app:layout_constraintStart_toStartOf="@+id/tv_comment"
|
||||||
|
|||||||
@@ -194,6 +194,37 @@
|
|||||||
<string name="character_gallery_purchase_confirm">Buy with %1$d cans</string>
|
<string name="character_gallery_purchase_confirm">Buy with %1$d cans</string>
|
||||||
<string name="character_gallery_load_error">Failed to load gallery info.</string>
|
<string name="character_gallery_load_error">Failed to load gallery info.</string>
|
||||||
<string name="character_gallery_purchase_failed">Failed to complete the purchase.</string>
|
<string name="character_gallery_purchase_failed">Failed to complete the purchase.</string>
|
||||||
|
<!-- Character comments -->
|
||||||
|
<string name="character_comment_title">Comments</string>
|
||||||
|
<string name="character_comment_reply_title">Replies</string>
|
||||||
|
<string name="character_comment_input_hint">@string/character_detail_comment_input_hint</string>
|
||||||
|
<string name="character_comment_write_reply">Write a reply</string>
|
||||||
|
<string name="character_comment_reply_count">%1$d replies</string>
|
||||||
|
<string name="character_comment_time_just_now">Just now</string>
|
||||||
|
<string name="character_comment_time_minutes">%1$d min ago</string>
|
||||||
|
<string name="character_comment_time_hours">%1$d hr ago</string>
|
||||||
|
<string name="character_comment_time_days">%1$d day ago</string>
|
||||||
|
<string name="character_comment_time_years">%1$d yr ago</string>
|
||||||
|
<string name="character_comment_error_empty">Please enter a message.</string>
|
||||||
|
<string name="character_comment_error_report_reason">Please enter a report reason.</string>
|
||||||
|
<string name="character_comment_report_submitted">Report submitted.</string>
|
||||||
|
<string name="character_comment_load_failed">@string/common_error_unknown</string>
|
||||||
|
<string name="character_comment_report_reason_commercial">Unwanted commercial content or spam</string>
|
||||||
|
<string name="character_comment_report_reason_child_abuse">Child abuse</string>
|
||||||
|
<string name="character_comment_report_reason_hate_or_violence">Hate speech or graphic violence</string>
|
||||||
|
<string name="character_comment_report_reason_terror">Promoting terrorism</string>
|
||||||
|
<string name="character_comment_report_reason_harassment">Harassment or bullying</string>
|
||||||
|
<string name="character_comment_report_reason_self_harm">Suicide or self-harm</string>
|
||||||
|
<string name="character_comment_report_reason_misinformation">Misinformation</string>
|
||||||
|
<string-array name="character_comment_report_reasons">
|
||||||
|
<item>@string/character_comment_report_reason_commercial</item>
|
||||||
|
<item>@string/character_comment_report_reason_child_abuse</item>
|
||||||
|
<item>@string/character_comment_report_reason_hate_or_violence</item>
|
||||||
|
<item>@string/character_comment_report_reason_terror</item>
|
||||||
|
<item>@string/character_comment_report_reason_harassment</item>
|
||||||
|
<item>@string/character_comment_report_reason_self_harm</item>
|
||||||
|
<item>@string/character_comment_report_reason_misinformation</item>
|
||||||
|
</string-array>
|
||||||
<string name="screen_character_tab_recent_title">Recent characters</string>
|
<string name="screen_character_tab_recent_title">Recent characters</string>
|
||||||
<string name="screen_character_tab_popular_title">Popular characters</string>
|
<string name="screen_character_tab_popular_title">Popular characters</string>
|
||||||
<string name="screen_character_tab_new_title">New characters</string>
|
<string name="screen_character_tab_new_title">New characters</string>
|
||||||
|
|||||||
@@ -194,6 +194,37 @@
|
|||||||
<string name="character_gallery_purchase_confirm">%1$dキャンで購入</string>
|
<string name="character_gallery_purchase_confirm">%1$dキャンで購入</string>
|
||||||
<string name="character_gallery_load_error">ギャラリー情報を読み込めませんでした。</string>
|
<string name="character_gallery_load_error">ギャラリー情報を読み込めませんでした。</string>
|
||||||
<string name="character_gallery_purchase_failed">購入に失敗しました。</string>
|
<string name="character_gallery_purchase_failed">購入に失敗しました。</string>
|
||||||
|
<!-- Character comments -->
|
||||||
|
<string name="character_comment_title">コメント</string>
|
||||||
|
<string name="character_comment_reply_title">返信</string>
|
||||||
|
<string name="character_comment_input_hint">@string/character_detail_comment_input_hint</string>
|
||||||
|
<string name="character_comment_write_reply">返信を書く</string>
|
||||||
|
<string name="character_comment_reply_count">返信 %1$d件</string>
|
||||||
|
<string name="character_comment_time_just_now">たった今</string>
|
||||||
|
<string name="character_comment_time_minutes">%1$d分前</string>
|
||||||
|
<string name="character_comment_time_hours">%1$d時間前</string>
|
||||||
|
<string name="character_comment_time_days">%1$d日前</string>
|
||||||
|
<string name="character_comment_time_years">%1$d年前</string>
|
||||||
|
<string name="character_comment_error_empty">内容を入力してください。</string>
|
||||||
|
<string name="character_comment_error_report_reason">通報理由を入力してください。</string>
|
||||||
|
<string name="character_comment_report_submitted">通報が受け付けられました。</string>
|
||||||
|
<string name="character_comment_load_failed">@string/common_error_unknown</string>
|
||||||
|
<string name="character_comment_report_reason_commercial">望まない商業コンテンツまたはスパム</string>
|
||||||
|
<string name="character_comment_report_reason_child_abuse">児童虐待</string>
|
||||||
|
<string name="character_comment_report_reason_hate_or_violence">ヘイトスピーチまたは露骨な暴力</string>
|
||||||
|
<string name="character_comment_report_reason_terror">テロの助長</string>
|
||||||
|
<string name="character_comment_report_reason_harassment">嫌がらせやいじめ</string>
|
||||||
|
<string name="character_comment_report_reason_self_harm">自殺や自傷行為</string>
|
||||||
|
<string name="character_comment_report_reason_misinformation">誤情報</string>
|
||||||
|
<string-array name="character_comment_report_reasons">
|
||||||
|
<item>@string/character_comment_report_reason_commercial</item>
|
||||||
|
<item>@string/character_comment_report_reason_child_abuse</item>
|
||||||
|
<item>@string/character_comment_report_reason_hate_or_violence</item>
|
||||||
|
<item>@string/character_comment_report_reason_terror</item>
|
||||||
|
<item>@string/character_comment_report_reason_harassment</item>
|
||||||
|
<item>@string/character_comment_report_reason_self_harm</item>
|
||||||
|
<item>@string/character_comment_report_reason_misinformation</item>
|
||||||
|
</string-array>
|
||||||
<string name="screen_character_tab_recent_title">最近話したキャラクター</string>
|
<string name="screen_character_tab_recent_title">最近話したキャラクター</string>
|
||||||
<string name="screen_character_tab_popular_title">人気キャラクター</string>
|
<string name="screen_character_tab_popular_title">人気キャラクター</string>
|
||||||
<string name="screen_character_tab_new_title">新着キャラクター</string>
|
<string name="screen_character_tab_new_title">新着キャラクター</string>
|
||||||
|
|||||||
@@ -193,6 +193,37 @@
|
|||||||
<string name="character_gallery_purchase_confirm">%1$d캔으로 구매</string>
|
<string name="character_gallery_purchase_confirm">%1$d캔으로 구매</string>
|
||||||
<string name="character_gallery_load_error">갤러리 정보를 불러오지 못했습니다.</string>
|
<string name="character_gallery_load_error">갤러리 정보를 불러오지 못했습니다.</string>
|
||||||
<string name="character_gallery_purchase_failed">구매에 실패했습니다.</string>
|
<string name="character_gallery_purchase_failed">구매에 실패했습니다.</string>
|
||||||
|
<!-- Character comments -->
|
||||||
|
<string name="character_comment_title">댓글</string>
|
||||||
|
<string name="character_comment_reply_title">답글</string>
|
||||||
|
<string name="character_comment_input_hint">@string/character_detail_comment_input_hint</string>
|
||||||
|
<string name="character_comment_write_reply">답글 쓰기</string>
|
||||||
|
<string name="character_comment_reply_count">답글 %1$d개</string>
|
||||||
|
<string name="character_comment_time_just_now">방금전</string>
|
||||||
|
<string name="character_comment_time_minutes">%1$d분전</string>
|
||||||
|
<string name="character_comment_time_hours">%1$d시간전</string>
|
||||||
|
<string name="character_comment_time_days">%1$d일전</string>
|
||||||
|
<string name="character_comment_time_years">%1$d년전</string>
|
||||||
|
<string name="character_comment_error_empty">내용을 입력하세요</string>
|
||||||
|
<string name="character_comment_error_report_reason">신고 사유를 입력하세요</string>
|
||||||
|
<string name="character_comment_report_submitted">신고가 접수되었습니다.</string>
|
||||||
|
<string name="character_comment_load_failed">@string/common_error_unknown</string>
|
||||||
|
<string name="character_comment_report_reason_commercial">원치 않는 상업성 콘텐츠 또는 스팸</string>
|
||||||
|
<string name="character_comment_report_reason_child_abuse">아동 학대</string>
|
||||||
|
<string name="character_comment_report_reason_hate_or_violence">증오시 표현 또는 노골적인 폭력</string>
|
||||||
|
<string name="character_comment_report_reason_terror">테러 조장</string>
|
||||||
|
<string name="character_comment_report_reason_harassment">희롱 또는 괴롭힘</string>
|
||||||
|
<string name="character_comment_report_reason_self_harm">자살 또는 자해</string>
|
||||||
|
<string name="character_comment_report_reason_misinformation">잘못된 정보</string>
|
||||||
|
<string-array name="character_comment_report_reasons">
|
||||||
|
<item>@string/character_comment_report_reason_commercial</item>
|
||||||
|
<item>@string/character_comment_report_reason_child_abuse</item>
|
||||||
|
<item>@string/character_comment_report_reason_hate_or_violence</item>
|
||||||
|
<item>@string/character_comment_report_reason_terror</item>
|
||||||
|
<item>@string/character_comment_report_reason_harassment</item>
|
||||||
|
<item>@string/character_comment_report_reason_self_harm</item>
|
||||||
|
<item>@string/character_comment_report_reason_misinformation</item>
|
||||||
|
</string-array>
|
||||||
<string name="screen_character_tab_recent_title">최근 대화한 캐릭터</string>
|
<string name="screen_character_tab_recent_title">최근 대화한 캐릭터</string>
|
||||||
<string name="screen_character_tab_popular_title">인기 캐릭터</string>
|
<string name="screen_character_tab_popular_title">인기 캐릭터</string>
|
||||||
<string name="screen_character_tab_new_title">신규 캐릭터</string>
|
<string name="screen_character_tab_new_title">신규 캐릭터</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user