커뮤니티 게시글 상대 시간 표기 다국어 지원

This commit is contained in:
2025-12-19 14:21:26 +09:00
parent c66fdf63db
commit 9adc45095b
7 changed files with 115 additions and 3 deletions

View File

@@ -49,6 +49,7 @@ import kr.co.vividnext.sodalive.dialog.MemberProfileDialog
import kr.co.vividnext.sodalive.explorer.profile.cheers.UserProfileCheersAdapter import kr.co.vividnext.sodalive.explorer.profile.cheers.UserProfileCheersAdapter
import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.CreatorCommunityAllActivity
import kr.co.vividnext.sodalive.explorer.profile.creator_community.relativeTimeText
import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteActivity import kr.co.vividnext.sodalive.explorer.profile.creator_community.write.CreatorCommunityWriteActivity
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAdapter import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAdapter
import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewActivity import kr.co.vividnext.sodalive.explorer.profile.donation.UserProfileDonationAllViewActivity
@@ -965,7 +966,7 @@ class UserProfileActivity : BaseActivity<ActivityUserProfileBinding>(
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
layout.tvCreatorNickname.text = item.creatorNickname layout.tvCreatorNickname.text = item.creatorNickname
layout.tvDate.text = item.date layout.tvDate.text = item.relativeTimeText(this)
layout.tvContent.text = item.content layout.tvContent.text = item.content
layout.ivPostImage.loadUrl(item.imageUrl) { layout.ivPostImage.loadUrl(item.imageUrl) {

View File

@@ -12,6 +12,7 @@ import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityBinding import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityBinding
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.loadUrl import kr.co.vividnext.sodalive.extensions.loadUrl
import kr.co.vividnext.sodalive.explorer.profile.creator_community.relativeTimeText
class CreatorCommunityAdapter( class CreatorCommunityAdapter(
private val width: Int, private val width: Int,
@@ -31,7 +32,7 @@ class CreatorCommunityAdapter(
transformations(CircleCropTransformation()) transformations(CircleCropTransformation())
} }
binding.tvCreatorNickname.text = item.creatorNickname binding.tvCreatorNickname.text = item.creatorNickname
binding.tvDate.text = item.date binding.tvDate.text = item.relativeTimeText(context)
binding.tvContent.text = item.content binding.tvContent.text = item.content
binding.ivPostImage.loadUrl(item.imageUrl) { binding.ivPostImage.loadUrl(item.imageUrl) {

View File

@@ -2,6 +2,14 @@ package kr.co.vividnext.sodalive.explorer.profile.creator_community
import androidx.annotation.Keep import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import android.content.Context
import kr.co.vividnext.sodalive.R
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone
@Keep @Keep
data class GetCommunityPostListResponse( data class GetCommunityPostListResponse(
@@ -14,6 +22,7 @@ data class GetCommunityPostListResponse(
@SerializedName("content") val content: String, @SerializedName("content") val content: String,
@SerializedName("price") val price: Int, @SerializedName("price") val price: Int,
@SerializedName("date") val date: String, @SerializedName("date") val date: String,
@SerializedName("dateUtc") val dateUtc: String,
@SerializedName("isCommentAvailable") val isCommentAvailable: Boolean, @SerializedName("isCommentAvailable") val isCommentAvailable: Boolean,
@SerializedName("isAdult") val isAdult: Boolean, @SerializedName("isAdult") val isAdult: Boolean,
@SerializedName("isLike") var isLike: Boolean, @SerializedName("isLike") var isLike: Boolean,
@@ -23,3 +32,100 @@ data class GetCommunityPostListResponse(
@SerializedName("firstComment") val firstComment: GetCommunityPostCommentListItem?, @SerializedName("firstComment") val firstComment: GetCommunityPostCommentListItem?,
@SerializedName("isExpand") var isExpand: Boolean = false @SerializedName("isExpand") var isExpand: Boolean = false
) )
/**
* dateUtc 를 디바이스 로컬 타임존 기준의 상대 시간 문자열로 변환한다.
* 규칙:
* - 1분 미만: 방금 전
* - 1시간 미만: OO분 전
* - 1일 미만: OO시간 전
* - 1개월 미만: OO일 전
* - 1년 미만: OO개월 전
* - 그 외: OO년 전
* 기존 다국어 문자열 리소스(character_comment_time_*)를 사용한다.
*/
fun GetCommunityPostListResponse.relativeTimeText(context: Context): String {
val pastMillis = parseServerUtcToMillis(dateUtc)
?: return context.getString(R.string.character_comment_time_just_now)
val nowMillis = System.currentTimeMillis()
var diff = nowMillis - pastMillis
if (diff < 0) diff = 0 // 미래 보호
val minute = 60_000L
val hour = 60 * minute
val day = 24 * hour
if (diff < minute) {
return context.getString(R.string.character_comment_time_just_now)
}
if (diff < hour) {
val minutes = (diff / minute).toInt()
return context.getString(R.string.character_comment_time_minutes, minutes)
}
if (diff < day) {
val hours = (diff / hour).toInt()
return context.getString(R.string.character_comment_time_hours, hours)
}
if (diff < 30 * day) {
val days = (diff / day).toInt()
return context.getString(R.string.character_comment_time_days, days)
}
// 개월/년 계산은 캘린더를 사용해 더 정확히 계산
val tz = TimeZone.getDefault()
val calNow = Calendar.getInstance(tz, Locale.getDefault())
val calPast = Calendar.getInstance(tz, Locale.getDefault())
calPast.timeInMillis = pastMillis
// 연도 계산 (아직 기념일 전이면 1년 빼기)
var years = calNow.get(Calendar.YEAR) - calPast.get(Calendar.YEAR)
val nowMonth = calNow.get(Calendar.MONTH)
val pastMonth = calPast.get(Calendar.MONTH)
val nowDay = calNow.get(Calendar.DAY_OF_MONTH)
val pastDay = calPast.get(Calendar.DAY_OF_MONTH)
if (nowMonth < pastMonth || (nowMonth == pastMonth && nowDay < pastDay)) {
years -= 1
}
if (years < 1) {
var months = (calNow.get(Calendar.YEAR) - calPast.get(Calendar.YEAR)) * 12 + (nowMonth - pastMonth)
if (nowDay < pastDay) months -= 1
if (months < 1) months = 1 // 최소 1개월
return context.getString(R.string.character_comment_time_months, months)
}
return context.getString(R.string.character_comment_time_years, years)
}
// 서버에서 내려오는 dateUtc 문자열을 UTC 기준 millis 로 파싱한다.
private fun parseServerUtcToMillis(dateUtc: String?): Long? {
if (dateUtc.isNullOrBlank()) return null
val s = dateUtc.trim()
// epoch millis 가능성
if (s.all { it.isDigit() }) {
return try { s.toLong() } catch (_: NumberFormatException) { null }
}
// 자주 쓰이는 포맷들 (UTC 가정)
val patterns = listOf(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd HH:mm:ss",
"yyyy/MM/dd HH:mm:ss"
)
for (p in patterns) {
try {
val sdf = SimpleDateFormat(p, Locale.US)
sdf.timeZone = TimeZone.getTimeZone("UTC")
val date: Date? = sdf.parse(s)
if (date != null) return date.time
} catch (_: ParseException) { }
}
return null
}

View File

@@ -27,6 +27,7 @@ import kr.co.vividnext.sodalive.databinding.ItemCreatorCommunityAllBinding
import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostCommentListItem import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostCommentListItem
import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse import kr.co.vividnext.sodalive.explorer.profile.creator_community.GetCommunityPostListResponse
import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityContentItem import kr.co.vividnext.sodalive.explorer.profile.creator_community.all.player.CreatorCommunityContentItem
import kr.co.vividnext.sodalive.explorer.profile.creator_community.relativeTimeText
import kr.co.vividnext.sodalive.extensions.dpToPx import kr.co.vividnext.sodalive.extensions.dpToPx
import kr.co.vividnext.sodalive.extensions.loadUrl import kr.co.vividnext.sodalive.extensions.loadUrl
import java.util.regex.Pattern import java.util.regex.Pattern
@@ -53,7 +54,7 @@ class CreatorCommunityAllAdapter(
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("NotifyDataSetChanged", "SetTextI18n") @SuppressLint("NotifyDataSetChanged", "SetTextI18n")
fun bind(item: GetCommunityPostListResponse, index: Int) { fun bind(item: GetCommunityPostListResponse, index: Int) {
binding.tvDate.text = item.date binding.tvDate.text = item.relativeTimeText(context)
binding.tvNickname.text = item.creatorNickname binding.tvNickname.text = item.creatorNickname
binding.ivCreatorProfile.loadUrl(item.creatorProfileUrl) { binding.ivCreatorProfile.loadUrl(item.creatorProfileUrl) {
crossfade(true) crossfade(true)

View File

@@ -267,6 +267,7 @@
<string name="character_comment_time_minutes">%1$d min ago</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_hours">%1$d hr ago</string>
<string name="character_comment_time_days">%1$d day ago</string> <string name="character_comment_time_days">%1$d day ago</string>
<string name="character_comment_time_months">%1$d mo ago</string>
<string name="character_comment_time_years">%1$d yr 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_empty">Please enter a message.</string>
<string name="character_comment_error_report_reason">Please enter a report reason.</string> <string name="character_comment_error_report_reason">Please enter a report reason.</string>

View File

@@ -267,6 +267,7 @@
<string name="character_comment_time_minutes">%1$d分前</string> <string name="character_comment_time_minutes">%1$d分前</string>
<string name="character_comment_time_hours">%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_days">%1$d日前</string>
<string name="character_comment_time_months">%1$dか月前</string>
<string name="character_comment_time_years">%1$d年前</string> <string name="character_comment_time_years">%1$d年前</string>
<string name="character_comment_error_empty">内容を入力してください。</string> <string name="character_comment_error_empty">内容を入力してください。</string>
<string name="character_comment_error_report_reason">通報理由を入力してください。</string> <string name="character_comment_error_report_reason">通報理由を入力してください。</string>

View File

@@ -266,6 +266,7 @@
<string name="character_comment_time_minutes">%1$d분전</string> <string name="character_comment_time_minutes">%1$d분전</string>
<string name="character_comment_time_hours">%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_days">%1$d일전</string>
<string name="character_comment_time_months">%1$d개월전</string>
<string name="character_comment_time_years">%1$d년전</string> <string name="character_comment_time_years">%1$d년전</string>
<string name="character_comment_error_empty">내용을 입력하세요</string> <string name="character_comment_error_empty">내용을 입력하세요</string>
<string name="character_comment_error_report_reason">신고 사유를 입력하세요</string> <string name="character_comment_error_report_reason">신고 사유를 입력하세요</string>