fix(live): 종료 라이브 상대시간을 로컬 기준으로 국제화한다

This commit is contained in:
2026-03-05 11:24:01 +09:00
parent 2e700d4385
commit d83c4b12ec
6 changed files with 123 additions and 2 deletions

View File

@@ -8,5 +8,6 @@ data class GetLatestFinishedLiveResponse(
@SerializedName("memberId") val memberId: Long,
@SerializedName("nickname") val nickname: String,
@SerializedName("profileImageUrl") val profileImageUrl: String,
@SerializedName("timeAgo") val timeAgo: String
@SerializedName("timeAgo") val timeAgo: String,
@SerializedName("dateUtc") val dateUtc: String
)

View File

@@ -5,7 +5,14 @@ import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import kr.co.vividnext.sodalive.R
import kr.co.vividnext.sodalive.databinding.ItemLatestFinishedLiveBinding
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.TimeZone
class LatestFinishedLiveAdapter(
private val onClick: (Long) -> Unit
@@ -49,8 +56,87 @@ class LatestFinishedLiveAdapter(
.into(binding.ivProfile)
binding.tvNickname.text = item.nickname
binding.tvTimeAgo.text = item.timeAgo
binding.tvTimeAgo.text = relativeTimeText(item)
binding.root.setOnClickListener { onClick(item.memberId) }
}
private fun relativeTimeText(item: GetLatestFinishedLiveResponse): String {
val pastMillis = parseDateUtcToMillis(item.dateUtc)
if (pastMillis == null) {
return item.timeAgo
}
val timezone = TimeZone.getDefault()
val nowCalendar = Calendar.getInstance(timezone, Locale.getDefault())
val pastCalendar = Calendar.getInstance(timezone, Locale.getDefault()).apply {
timeInMillis = pastMillis
}
val minute = 60_000L
val hour = 60 * minute
val day = 24 * hour
val diff = (nowCalendar.timeInMillis - pastCalendar.timeInMillis).coerceAtLeast(0L)
return when {
diff < minute -> context.getString(R.string.latest_finished_live_time_just_now)
diff < hour -> {
val minutes = (diff / minute).toInt()
context.getString(R.string.latest_finished_live_time_minutes, minutes)
}
diff < day -> {
val hours = (diff / hour).toInt()
context.getString(R.string.latest_finished_live_time_hours, hours)
}
else -> {
val days = (diff / day).toInt()
context.getString(R.string.latest_finished_live_time_days, days)
}
}
}
private fun parseDateUtcToMillis(dateUtc: String?): Long? {
if (dateUtc.isNullOrBlank()) return null
val value = dateUtc.trim()
if (value.all { it.isDigit() }) {
return try {
val epoch = value.toLong()
if (value.length <= 10) epoch * 1000 else epoch
} catch (exception: NumberFormatException) {
null
}
}
val patterns = listOf(
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX",
"yyyy-MM-dd'T'HH:mm:ssXXX",
"yyyy-MM-dd'T'HH:mm:ss.SSSX",
"yyyy-MM-dd'T'HH:mm:ssX",
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd'T'HH:mm:ss.SSS",
"yyyy-MM-dd'T'HH:mm:ss",
"yyyy-MM-dd HH:mm:ss",
"yyyy/MM/dd HH:mm:ss"
)
for (pattern in patterns) {
try {
val dateFormat = SimpleDateFormat(pattern, Locale.US)
dateFormat.timeZone = TimeZone.getTimeZone("UTC")
val parsed: Date? = dateFormat.parse(value)
if (parsed != null) {
return parsed.time
}
} catch (exception: ParseException) {
continue
}
}
return null
}
}
}

View File

@@ -272,6 +272,10 @@
<string name="character_comment_time_days">%1$d d ago</string>
<string name="character_comment_time_months">%1$d mo ago</string>
<string name="character_comment_time_years">%1$d y ago</string>
<string name="latest_finished_live_time_just_now">Just now</string>
<string name="latest_finished_live_time_minutes">%1$d min ago</string>
<string name="latest_finished_live_time_hours">%1$d h ago</string>
<string name="latest_finished_live_time_days">%1$d d ago</string>
<string name="character_comment_error_empty">Enter the details.</string>
<string name="character_comment_error_report_reason">Enter the reason for reporting.</string>
<string name="character_comment_report_submitted">Your report has been submitted.</string>

View File

@@ -272,6 +272,10 @@
<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="latest_finished_live_time_just_now">たった今</string>
<string name="latest_finished_live_time_minutes">%1$d分前</string>
<string name="latest_finished_live_time_hours">%1$d時間前</string>
<string name="latest_finished_live_time_days">%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>

View File

@@ -271,6 +271,10 @@
<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="latest_finished_live_time_just_now">방금 전</string>
<string name="latest_finished_live_time_minutes">%1$d분 전</string>
<string name="latest_finished_live_time_hours">%1$d시간 전</string>
<string name="latest_finished_live_time_days">%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>

View File

@@ -0,0 +1,22 @@
# 종료 라이브 상대시간 국제화 적용
- [x] `GetLatestFinishedLiveResponse``dateUtc`를 기준으로 상대시간 계산 방식 정의 확인
- [x] `LatestFinishedLiveAdapter`에서 UTC -> 기기 타임존 기준 상대시간 계산 로직 적용
- [x] `방금 전 / OO분 전 / OO시간 전 / OO일 전` 문자열 국제화 리소스 적용
- [x] 변경 파일 진단 및 테스트/빌드 검증 수행
## 검증 기록
### 2026-03-05
- 무엇을: `LatestFinishedLiveAdapter`에서 `item.timeAgo` 직접 노출 대신 `dateUtc`를 UTC로 파싱한 후 기기 타임존 기준 현재 시각과 비교해 `방금 전 / 분 전 / 시간 전 / 일 전` 형태로 표시하도록 변경.
- 왜: 서버 문자열 의존을 줄이고, 기기 로컬 타임존 기준의 일관된 상대시간 표기 및 다국어 리소스 기반 UI를 적용하기 위해.
- 어떻게:
- `app/src/main/java/kr/co/vividnext/sodalive/live/LatestFinishedLiveAdapter.kt`에 UTC 파싱(`parseDateUtcToMillis`)과 상대시간 계산(`relativeTimeText`) 추가.
- `app/src/main/res/values/strings.xml`, `app/src/main/res/values-en/strings.xml`, `app/src/main/res/values-ja/strings.xml``latest_finished_live_time_*` 문자열 추가.
- LSP 진단 시도: `.kt`, `.xml` 확장자용 LSP 서버 미구성으로 자동 진단 불가 확인.
- 실행 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
- 결과: `BUILD SUCCESSFUL` (단위 테스트 및 디버그 빌드 성공, 기존 경고만 존재)
- 실행 명령: `./gradlew :app:lintDebug`
- 결과: `:app:lintDebug FAILED` (기존 이슈로 판단되는 `AndroidManifest.xml``com.facebook.FacebookActivity` MissingClass 포함, 총 16 errors/573 warnings)
- 재검증 명령: `./gradlew :app:testDebugUnitTest :app:assembleDebug`
- 재검증 결과: `BUILD SUCCESSFUL` (어댑터 파싱 패턴 보강 후에도 테스트/빌드 정상)