Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ import com.sopt.clody.data.remote.datasource.RemoteConfigDataSource
import com.sopt.clody.domain.appupdate.AppUpdateChecker
import com.sopt.clody.domain.model.AppUpdateState
import com.sopt.clody.domain.util.VersionComparator
import java.time.LocalDateTime
import java.time.format.TextStyle
import java.util.Locale
import java.time.ZoneId
import java.time.ZonedDateTime
import javax.inject.Inject

class AppUpdateCheckerImpl @Inject constructor(
Expand Down Expand Up @@ -34,29 +33,35 @@ class AppUpdateCheckerImpl @Inject constructor(
}
}

/**
* Firebase RemoteConfig 로부터 점검 시간에 해당 하는지 검사하는 함수.
*
* @return 점검 시간 해당 여부
* */
override suspend fun isUnderInspection(): Boolean {
val start = remoteConfigDataSource.getInspectionStart() ?: return false
val end = remoteConfigDataSource.getInspectionEnd() ?: return false
val now = LocalDateTime.now()
return now.isAfter(start) && now.isBefore(end)
}
val serverZone = ZoneId.of(SERVER_TIMEZONE)

override fun getInspectionTimeText(): String? {
val start = remoteConfigDataSource.getInspectionStart()
val end = remoteConfigDataSource.getInspectionEnd()
if (start == null || end == null) return null
val nowServer = ZonedDateTime.now(serverZone)
val startZ = start.atZone(serverZone)
val endZ = end.atZone(serverZone)

val startText = formatDateTimeWithDayOfWeek(start)
val endText = formatDateTimeWithDayOfWeek(end)
return "$startText ~ $endText"
return nowServer in startZ..endZ
}

private fun formatDateTimeWithDayOfWeek(dateTime: LocalDateTime): String {
val dayOfWeek = dateTime.dayOfWeek.getDisplayName(TextStyle.SHORT, Locale.KOREAN)
val month = dateTime.monthValue
val day = dateTime.dayOfMonth
val hour = dateTime.hour.toString().padStart(2, '0')
/**
* Firebase RemoteConfig 로부터 점검 시간을 가져와 반환하는 함수.
*
* @return 점검 시작 시간과 종료 시간을 "2025-08-11T18:00:00" 형식으로 반환
* */
override suspend fun getInspectionTimeText(): Pair<String, String>? {
val start = remoteConfigDataSource.getInspectionStart() ?: return null
val end = remoteConfigDataSource.getInspectionEnd() ?: return null
return start.toString() to end.toString()
}

return "$month/$day($dayOfWeek) ${hour}시"
companion object {
private const val SERVER_TIMEZONE = "Asia/Seoul"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ import com.sopt.clody.domain.model.AppUpdateState
interface AppUpdateChecker {
suspend fun getAppUpdateState(currentVersion: String): AppUpdateState
suspend fun isUnderInspection(): Boolean
fun getInspectionTimeText(): String?
suspend fun getInspectionTimeText(): Pair<String, String>?
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
Expand Down Expand Up @@ -63,14 +64,14 @@ fun InspectionDialog(
)
Spacer(modifier = Modifier.height(20.dp))
Text(
text = "보다 안정적인 클로디 서비스를 위해\n시스템 점검 중이에요. 곧 다시 만나요!",
text = stringResource(R.string.dialog_inspection_title),
color = ClodyTheme.colors.gray03,
textAlign = TextAlign.Center,
style = ClodyTheme.typography.body3Medium,
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "점검시간 : $inspectionTime",
text = stringResource(R.string.dialog_inspection_description, inspectionTime),
color = ClodyTheme.colors.gray04,
textAlign = TextAlign.Center,
style = ClodyTheme.typography.body3Medium,
Expand All @@ -84,7 +85,7 @@ fun InspectionDialog(
colors = ButtonDefaults.buttonColors(ClodyTheme.colors.mainYellow),
) {
Text(
text = "확인",
text = stringResource(R.string.dialog_inspection_confirm),
color = ClodyTheme.colors.gray02,
style = ClodyTheme.typography.body3SemiBold,
)
Expand All @@ -100,7 +101,7 @@ fun InspectionDialog(
private fun PreviewInspectionDialog() {
BasePreview {
InspectionDialog(
inspectionTime = "",
inspectionTime = "Dec 14 (Wed) 12:00 PM ~ Mar 15 (Wed) 10:00 PM",
onDismiss = {},
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.sopt.clody.domain.model.AppUpdateState
import com.sopt.clody.domain.repository.TokenRepository
import com.sopt.clody.presentation.utils.amplitude.AmplitudeConstraints
import com.sopt.clody.presentation.utils.amplitude.AmplitudeUtils
import com.sopt.clody.presentation.utils.language.LanguageProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
Expand All @@ -25,6 +26,7 @@ class SplashViewModel @AssistedInject constructor(
@Assisted initialState: SplashContract.SplashState,
private val tokenRepository: TokenRepository,
private val appUpdateChecker: AppUpdateChecker,
private val languageProvider: LanguageProvider,
) : MavericksViewModel<SplashContract.SplashState>(initialState) {

private val _intents = Channel<SplashContract.SplashIntent>(BUFFERED)
Expand Down Expand Up @@ -64,7 +66,10 @@ class SplashViewModel @AssistedInject constructor(

private suspend fun checkInspectionAndHandle(): Boolean {
if (appUpdateChecker.isUnderInspection()) {
val inspectionText = appUpdateChecker.getInspectionTimeText()
val inspectionTextRaw = appUpdateChecker.getInspectionTimeText()
val inspectionText = inspectionTextRaw?.let { (start, end) ->
languageProvider.getInspectionTimeText(start, end)
}
setState {
copy(
showInspectionDialog = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.sopt.clody.presentation.ui.setting.screen.SettingOptionUrls

interface LanguageProvider {
fun getCurrentLanguageTag(): String
fun getInspectionTimeText(start: String, end: String): String?
fun getLoginType(): OAuthProvider
fun getNicknameMaxLength(): Int
fun getDiaryMaxLength(): Int
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ package com.sopt.clody.presentation.utils.language

import com.sopt.clody.data.datastore.OAuthProvider
import com.sopt.clody.presentation.ui.setting.screen.SettingOptionUrls
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.Locale
import javax.inject.Inject

Expand All @@ -13,6 +17,31 @@ class LanguageProviderImpl @Inject constructor() : LanguageProvider {
override fun getCurrentLanguageTag(): String =
locale.toLanguageTag() // e.g., "ko-KR" or "en-US"

override fun getInspectionTimeText(start: String, end: String): String? {
return runCatching {
val serverZone = ZoneId.of(SERVER_TIMEZONE)
val userZone = ZoneId.systemDefault()

val startUser = LocalDateTime.parse(start).atZone(serverZone).withZoneSameInstant(userZone)
val endUser = LocalDateTime.parse(end).atZone(serverZone).withZoneSameInstant(userZone)

formatInspectionTime(startUser, endUser)
}.getOrNull()
}

private fun formatInspectionTime(startUser: ZonedDateTime, endUser: ZonedDateTime): String {
return if (isKorean()) {
val koPattern = DateTimeFormatter.ofPattern(INSPECTION_TIME_FORMAT_KO, Locale.KOREAN)
"${startUser.format(koPattern)} ~ ${endUser.format(koPattern)}"
} else {
val enDateFormatter = DateTimeFormatter.ofPattern(INSPECTION_DATE_FORMAT_EN, Locale.ENGLISH)
val enTimeFormatter = DateTimeFormatter.ofPattern(INSPECTION_TIME_FORMAT_EN, Locale.ENGLISH)
val left = "${startUser.format(enDateFormatter)}, ${startUser.format(enTimeFormatter)}"
val right = "${endUser.format(enDateFormatter)} ${endUser.format(enTimeFormatter)}"
"$left ~ $right"
}
}

override fun getLoginType(): OAuthProvider =
if (isKorean()) OAuthProvider.KAKAO else OAuthProvider.GOOGLE

Expand All @@ -26,10 +55,14 @@ class LanguageProviderImpl @Inject constructor() : LanguageProvider {
if (isKorean()) option.koUrl else option.enUrl

companion object {
const val LANGUAGE_KO = "ko"
const val NICKNAME_MAX_LENGTH_EN = 15
const val NICKNAME_MAX_LENGTH_KO = 10
const val DIARY_MAX_LENGTH_EN = 100
const val DIARY_MAX_LENGTH_KO = 50
private const val LANGUAGE_KO = "ko"
private const val SERVER_TIMEZONE = "Asia/Seoul"
private const val INSPECTION_TIME_FORMAT_KO = "M/d(E) HH시mm분"
private const val INSPECTION_DATE_FORMAT_EN = "MMM d (EEE)"
private const val INSPECTION_TIME_FORMAT_EN = "HH:mm"
private const val NICKNAME_MAX_LENGTH_EN = 15
private const val NICKNAME_MAX_LENGTH_KO = 10
private const val DIARY_MAX_LENGTH_EN = 100
private const val DIARY_MAX_LENGTH_KO = 50
}
}
4 changes: 4 additions & 0 deletions app/src/main/res/values-ko/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@
<string name="dialog_revoke_confirm">탈퇴할래요</string>
<string name="dialog_revoke_dismiss">아니요</string>

<string name="dialog_inspection_title">보다 안정적인 클로디 서비스를 위해\n시스템 점검 중이에요. 곧 다시 만나요!</string>
<string name="dialog_inspection_description">점검시간 : %1$s</string>
<string name="dialog_inspection_confirm">확인</string>

<!-- 토스트 메시지 -->
<string name="toast_home_draft_alarm_enabled">이어쓰기 알림 설정을 완료했어요.</string>
<string name="toast_write_diary_entry_limit">최대 5개까지 작성할 수 있어요.</string>
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@
<string name="dialog_revoke_confirm">Withdraw</string>
<string name="dialog_revoke_dismiss">Cancel</string>

<string name="dialog_inspection_title">We\'re conducting system maintenance\nto improve your experience on Clody.\nSee you again soon!</string>
<string name="dialog_inspection_description">[Maintenance Time]\n%1$s</string>
<string name="dialog_inspection_confirm">OK</string>

<!-- Usage : Toast Message -->
<string name="toast_home_draft_alarm_enabled">Continue writing reminders are now on!</string>
<string name="toast_write_diary_entry_empty">Field required to send.</string>
Expand Down