fix(live): 종료 라이브 상대시간을 로컬 기준으로 국제화한다
This commit is contained in:
@@ -8,5 +8,6 @@ data class GetLatestFinishedLiveResponse(
|
|||||||
@SerializedName("memberId") val memberId: Long,
|
@SerializedName("memberId") val memberId: Long,
|
||||||
@SerializedName("nickname") val nickname: String,
|
@SerializedName("nickname") val nickname: String,
|
||||||
@SerializedName("profileImageUrl") val profileImageUrl: String,
|
@SerializedName("profileImageUrl") val profileImageUrl: String,
|
||||||
@SerializedName("timeAgo") val timeAgo: String
|
@SerializedName("timeAgo") val timeAgo: String,
|
||||||
|
@SerializedName("dateUtc") val dateUtc: String
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,7 +5,14 @@ import android.content.Context
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import com.bumptech.glide.request.RequestOptions
|
||||||
|
import kr.co.vividnext.sodalive.R
|
||||||
import kr.co.vividnext.sodalive.databinding.ItemLatestFinishedLiveBinding
|
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(
|
class LatestFinishedLiveAdapter(
|
||||||
private val onClick: (Long) -> Unit
|
private val onClick: (Long) -> Unit
|
||||||
@@ -49,8 +56,87 @@ class LatestFinishedLiveAdapter(
|
|||||||
.into(binding.ivProfile)
|
.into(binding.ivProfile)
|
||||||
|
|
||||||
binding.tvNickname.text = item.nickname
|
binding.tvNickname.text = item.nickname
|
||||||
binding.tvTimeAgo.text = item.timeAgo
|
binding.tvTimeAgo.text = relativeTimeText(item)
|
||||||
binding.root.setOnClickListener { onClick(item.memberId) }
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,6 +272,10 @@
|
|||||||
<string name="character_comment_time_days">%1$d d ago</string>
|
<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_months">%1$d mo ago</string>
|
||||||
<string name="character_comment_time_years">%1$d y 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_empty">Enter the details.</string>
|
||||||
<string name="character_comment_error_report_reason">Enter the reason for reporting.</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>
|
<string name="character_comment_report_submitted">Your report has been submitted.</string>
|
||||||
|
|||||||
@@ -272,6 +272,10 @@
|
|||||||
<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_months">%1$dヶ月前</string>
|
||||||
<string name="character_comment_time_years">%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_empty">内容を入力してください。</string>
|
||||||
<string name="character_comment_error_report_reason">通報理由を入力してください。</string>
|
<string name="character_comment_error_report_reason">通報理由を入力してください。</string>
|
||||||
<string name="character_comment_report_submitted">通報を受け付けました。</string>
|
<string name="character_comment_report_submitted">通報を受け付けました。</string>
|
||||||
|
|||||||
@@ -271,6 +271,10 @@
|
|||||||
<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_months">%1$d개월전</string>
|
||||||
<string name="character_comment_time_years">%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_empty">내용을 입력하세요</string>
|
||||||
<string name="character_comment_error_report_reason">신고 사유를 입력하세요</string>
|
<string name="character_comment_error_report_reason">신고 사유를 입력하세요</string>
|
||||||
<string name="character_comment_report_submitted">신고가 접수되었습니다.</string>
|
<string name="character_comment_report_submitted">신고가 접수되었습니다.</string>
|
||||||
|
|||||||
22
docs/20260305_종료라이브상대시간국제화적용.md
Normal file
22
docs/20260305_종료라이브상대시간국제화적용.md
Normal 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` (어댑터 파싱 패턴 보강 후에도 테스트/빌드 정상)
|
||||||
Reference in New Issue
Block a user