diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt index 56c63c60..0909bcd7 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/auth/signup/SignUpViewModel.kt @@ -175,7 +175,7 @@ class SignUpViewModel @AssistedInject constructor( private fun validateNickname(nickname: String): Boolean { val state = withState(this@SignUpViewModel) { it } setState { copy(nicknameMaxLength = languageProvider.getNicknameMaxLength()) } - val regex = "^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣]{2,${state.nicknameMaxLength}$".toRegex() + val regex = "^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣]{2,${state.nicknameMaxLength}}$".toRegex() return nickname.matches(regex) } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/component/dialog/ClodyDialog.kt b/app/src/main/java/com/sopt/clody/presentation/ui/component/dialog/ClodyDialog.kt index c0846c94..cdd8265d 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/component/dialog/ClodyDialog.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/component/dialog/ClodyDialog.kt @@ -77,6 +77,7 @@ fun ClodyDialog( Text( text = titleMassage, + textAlign = TextAlign.Center, style = ClodyTheme.typography.body1SemiBold, ) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/component/timepicker/YearMonthPicker.kt b/app/src/main/java/com/sopt/clody/presentation/ui/component/timepicker/YearMonthPicker.kt index 181cc474..e91a665e 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/component/timepicker/YearMonthPicker.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/component/timepicker/YearMonthPicker.kt @@ -2,6 +2,8 @@ package com.sopt.clody.presentation.ui.component.timepicker import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.detectVerticalDragGestures +import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -14,16 +16,29 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.IconButton import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.sopt.clody.R import com.sopt.clody.presentation.ui.component.button.ClodyButton @@ -31,6 +46,9 @@ import com.sopt.clody.presentation.utils.extension.YearMonthLabelUtil import com.sopt.clody.presentation.utils.extension.toLocalizedMonthLabel import com.sopt.clody.presentation.utils.extension.toLocalizedYearLabel import com.sopt.clody.ui.theme.ClodyTheme +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map +import java.util.Locale @Composable fun YearMonthPicker( @@ -42,6 +60,9 @@ fun YearMonthPicker( val yearItems = remember { (YearMonthLabelUtil.MIN_YEAR..YearMonthLabelUtil.MAX_YEAR).toList() } val monthItems = remember { (1..12).toList() } + val yearLabelItems = yearItems.map { it.toLocalizedYearLabel() } + val monthLabelItems = monthItems.map { it.toLocalizedMonthLabel() } + val yearPickerState = rememberPickerState() val monthPickerState = rememberPickerState() @@ -88,9 +109,6 @@ fun YearMonthPicker( } } - val yearLabelItems = yearItems.map { it.toLocalizedYearLabel() } - val monthLabelItems = monthItems.map { it.toLocalizedMonthLabel() } - Box(modifier = Modifier.fillMaxWidth()) { Box( modifier = Modifier @@ -103,29 +121,52 @@ fun YearMonthPicker( modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { - Spacer(modifier = Modifier.weight(1f)) - YearMonthPickerItem( - state = yearPickerState, - items = yearLabelItems, - startIndex = startYearIndex, - visibleItemsCount = 5, - infiniteScroll = false, - modifier = Modifier.weight(1f), - textModifier = Modifier.padding(8.dp), - ) - Spacer(modifier = Modifier.width(20.dp)) - YearMonthPickerItem( - state = monthPickerState, - items = monthLabelItems, - startIndex = startMonthIndex, - visibleItemsCount = 5, - infiniteScroll = false, - modifier = Modifier.weight(1f), - textModifier = Modifier.padding(8.dp), - ) - Spacer(modifier = Modifier.weight(1f)) + if (LocalConfiguration.current.locales[0] == Locale.KOREA) { + Spacer(modifier = Modifier.width(20.dp)) + YearMonthPickerItem( + state = yearPickerState, + items = yearLabelItems, + startIndex = startYearIndex, + visibleItemsCount = 5, + infiniteScroll = false, + modifier = Modifier.weight(1f), + textModifier = Modifier.padding(8.dp), + ) + YearMonthPickerItem( + state = monthPickerState, + items = monthLabelItems, + startIndex = startMonthIndex, + visibleItemsCount = 5, + infiniteScroll = false, + modifier = Modifier.weight(1f), + textModifier = Modifier.padding(8.dp), + ) + Spacer(modifier = Modifier.width(20.dp)) + } else { + Spacer(modifier = Modifier.width(20.dp)) + YearMonthPickerItem( + state = monthPickerState, + items = monthLabelItems, + startIndex = startMonthIndex, + visibleItemsCount = 5, + infiniteScroll = false, + modifier = Modifier.weight(1f), + textModifier = Modifier.padding(8.dp), + ) + YearMonthPickerItem( + state = yearPickerState, + items = yearLabelItems, + startIndex = startYearIndex, + visibleItemsCount = 5, + infiniteScroll = false, + modifier = Modifier.weight(1f), + textModifier = Modifier.padding(8.dp), + ) + Spacer(modifier = Modifier.width(20.dp)) + } } } + ClodyButton( onClick = { val year = yearItems[yearLabelItems.indexOf(yearPickerState.selectedItem)] @@ -142,3 +183,87 @@ fun YearMonthPicker( } } } + +@Composable +fun YearMonthPickerItem( + modifier: Modifier = Modifier, + items: List, + state: PickerState = rememberPickerState(), + startIndex: Int = 0, + visibleItemsCount: Int, + textModifier: Modifier = Modifier, + infiniteScroll: Boolean = true, +) { + val visibleItemsMiddle = visibleItemsCount / 2 + val emptyItems = List(visibleItemsMiddle) { "" } + val paddedItems = emptyItems + items + emptyItems + val listScrollCount = if (infiniteScroll) Integer.MAX_VALUE else paddedItems.size + val listScrollMiddle = listScrollCount / 2 + val listStartIndex = if (infiniteScroll) { + listScrollMiddle - listScrollMiddle % paddedItems.size - visibleItemsMiddle + startIndex + } else { + startIndex + visibleItemsMiddle + } + + fun getItem(index: Int) = paddedItems.getOrNull(index).orEmpty() + + val listState = rememberLazyListState(initialFirstVisibleItemIndex = listStartIndex) + val flingBehavior = rememberSnapFlingBehavior(lazyListState = listState) + + val itemHeightPixels = remember { mutableIntStateOf(0) } + val itemHeightDp = with(LocalDensity.current) { itemHeightPixels.intValue.toDp() } + + val fadingEdgeGradient = remember { + Brush.verticalGradient( + 0f to Color.White.copy(alpha = 0.9f), + 0.1f to Color.White.copy(alpha = 0.8f), + 0.2f to Color.White.copy(alpha = 0.7f), + 0.3f to Color.White.copy(alpha = 0.6f), + 0.4f to Color.Transparent, + 0.5f to Color.Transparent, + 0.6f to Color.Transparent, + 0.7f to Color.White.copy(alpha = 0.7f), + 0.8f to Color.White.copy(alpha = 0.8f), + 0.9f to Color.White.copy(alpha = 0.9f), + ) + } + + LaunchedEffect(listState) { + snapshotFlow { listState.firstVisibleItemIndex } + .map { index -> getItem(index + visibleItemsMiddle) } + .distinctUntilChanged() + .collect { item -> state.selectedItem = item } + } + + Box(modifier = modifier) { + LazyColumn( + state = listState, + flingBehavior = flingBehavior, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .height(itemHeightDp * visibleItemsCount) + .pointerInput(Unit) { + detectVerticalDragGestures { change, dragAmount -> + change.consume() + } + } + .drawWithContent { + drawContent() + drawRect(fadingEdgeGradient, size = size) + }, + ) { + items(listScrollCount) { index -> + Text( + text = getItem(index), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + style = ClodyTheme.typography.head3Medium.copy(color = ClodyTheme.colors.gray01), + modifier = Modifier + .onSizeChanged { size -> itemHeightPixels.intValue = size.height } + .then(textModifier), + ) + } + } + } +} diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/component/timepicker/YearMonthPickerItem.kt b/app/src/main/java/com/sopt/clody/presentation/ui/component/timepicker/YearMonthPickerItem.kt deleted file mode 100644 index 813f6ac7..00000000 --- a/app/src/main/java/com/sopt/clody/presentation/ui/component/timepicker/YearMonthPickerItem.kt +++ /dev/null @@ -1,123 +0,0 @@ -package com.sopt.clody.presentation.ui.component.timepicker - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.gestures.detectVerticalDragGestures -import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import com.sopt.clody.ui.theme.ClodyTheme -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.map - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun YearMonthPickerItem( - modifier: Modifier = Modifier, - items: List, - state: PickerState = rememberPickerState(), - startIndex: Int = 0, - visibleItemsCount: Int, - textModifier: Modifier = Modifier, - infiniteScroll: Boolean = true, -) { - val visibleItemsMiddle = visibleItemsCount / 2 - val emptyItems = List(visibleItemsMiddle) { "" } - val paddedItems = emptyItems + items + emptyItems - val listScrollCount = if (infiniteScroll) Integer.MAX_VALUE else paddedItems.size - val listScrollMiddle = listScrollCount / 2 - val listStartIndex = if (infiniteScroll) { - listScrollMiddle - listScrollMiddle % paddedItems.size - visibleItemsMiddle + startIndex - } else { - startIndex + visibleItemsMiddle - } - - fun getItem(index: Int) = paddedItems.getOrNull(index).orEmpty() - - val listState = rememberLazyListState(initialFirstVisibleItemIndex = listStartIndex) - val flingBehavior = rememberSnapFlingBehavior(lazyListState = listState) - - val itemHeightPixels = remember { mutableIntStateOf(0) } - val itemHeightDp = with(LocalDensity.current) { itemHeightPixels.intValue.toDp() } - - val fadingEdgeGradient = remember { - Brush.verticalGradient( - 0f to Color.White.copy(alpha = 0.9f), - 0.1f to Color.White.copy(alpha = 0.8f), - 0.2f to Color.White.copy(alpha = 0.7f), - 0.3f to Color.White.copy(alpha = 0.6f), - 0.4f to Color.Transparent, - 0.5f to Color.Transparent, - 0.6f to Color.Transparent, - 0.7f to Color.White.copy(alpha = 0.7f), - 0.8f to Color.White.copy(alpha = 0.8f), - 0.9f to Color.White.copy(alpha = 0.9f), - ) - } - - LaunchedEffect(listState) { - snapshotFlow { listState.firstVisibleItemIndex } - .map { index -> getItem(index + visibleItemsMiddle) } - .distinctUntilChanged() - .collect { item -> state.selectedItem = item } - } - - Box(modifier = modifier) { - LazyColumn( - state = listState, - flingBehavior = flingBehavior, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier - .fillMaxWidth() - .height(itemHeightDp * visibleItemsCount) - .pointerInput(Unit) { - detectVerticalDragGestures { change, dragAmount -> - change.consume() - } - } - .drawWithContent { - drawContent() - drawRect(fadingEdgeGradient, size = size) - }, - ) { - items(listScrollCount) { index -> - Text( - text = getItem(index), - maxLines = 1, - overflow = TextOverflow.Ellipsis, - style = ClodyTheme.typography.head3Medium.copy(color = ClodyTheme.colors.gray01), - modifier = Modifier - .onSizeChanged { size -> itemHeightPixels.intValue = size.height } - .then(textModifier), - ) - } - } - } -} - -@Preview(showBackground = true) -@Composable -fun PreviewPicker() { - YearMonthPickerItem( - items = (1..99).map { it.toString() }, - visibleItemsCount = 5, - ) -} diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/component/DiaryListTopAppBar.kt b/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/component/DiaryListTopAppBar.kt index a2b0d8ae..406866b5 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/component/DiaryListTopAppBar.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/component/DiaryListTopAppBar.kt @@ -29,8 +29,8 @@ import com.sopt.clody.ui.theme.ClodyTheme @OptIn(ExperimentalMaterial3Api::class) @Composable fun DiaryListTopAppBar( - selectedYear: Int, - selectedMonth: Int, + selectedYear: String, + selectedMonth: String, showYearMonthPicker: () -> Unit, onClickCalendar: () -> Unit, ) { diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/screen/DiaryListScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/screen/DiaryListScreen.kt index cc86c8f9..ae5e732a 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/screen/DiaryListScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/screen/DiaryListScreen.kt @@ -25,6 +25,8 @@ import com.sopt.clody.presentation.ui.diarylist.component.EmptyDiaryList import com.sopt.clody.presentation.ui.diarylist.component.MonthlyDiaryList import com.sopt.clody.presentation.utils.amplitude.AmplitudeConstraints import com.sopt.clody.presentation.utils.amplitude.AmplitudeUtils +import com.sopt.clody.presentation.utils.extension.toLocalizedMonthLabel +import com.sopt.clody.presentation.utils.extension.toLocalizedYearLabel import com.sopt.clody.presentation.utils.navigation.Route import com.sopt.clody.ui.theme.ClodyTheme @@ -130,8 +132,8 @@ fun DiaryListScreen( Scaffold( topBar = { DiaryListTopAppBar( - selectedYear = selectedYearInDiaryList, - selectedMonth = selectedMonthInDiaryList, + selectedYear = selectedYearInDiaryList.toLocalizedYearLabel(), + selectedMonth = selectedMonthInDiaryList.toLocalizedMonthLabel(), showYearMonthPicker = showYearMonthPicker, onClickCalendar = onClickCalendar, ) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/screen/DiaryListViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/screen/DiaryListViewModel.kt index 5d5354b4..f3ea9ae2 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/screen/DiaryListViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/diarylist/screen/DiaryListViewModel.kt @@ -4,7 +4,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.sopt.clody.data.remote.util.NetworkUtil import com.sopt.clody.domain.repository.DiaryRepository -import com.sopt.clody.presentation.utils.extension.getDayOfWeek import com.sopt.clody.presentation.utils.network.ErrorMessages.FAILURE_NETWORK_MESSAGE import com.sopt.clody.presentation.utils.network.ErrorMessages.FAILURE_TEMPORARY_MESSAGE import com.sopt.clody.presentation.utils.network.ErrorMessages.UNKNOWN_ERROR @@ -12,6 +11,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch +import java.time.LocalDate +import java.time.format.TextStyle +import java.util.Locale import javax.inject.Inject @HiltViewModel @@ -74,7 +76,9 @@ class DiaryListViewModel @Inject constructor( val year = diaryDate[0].toInt() val month = diaryDate[1].toInt() val day = diaryDate[2].toInt() - val dayOfWeek = getDayOfWeek(year, month, day) + + val date = LocalDate.of(year, month, day) + val dayOfWeek = date.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.getDefault()) _selectedDiaryDate.value = DiaryDate(year, month, day, dayOfWeek) } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/home/calendar/component/DailyDiaryListItem.kt b/app/src/main/java/com/sopt/clody/presentation/ui/home/calendar/component/DailyDiaryListItem.kt index f4af2913..7268a3aa 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/home/calendar/component/DailyDiaryListItem.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/home/calendar/component/DailyDiaryListItem.kt @@ -17,6 +17,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -26,6 +27,7 @@ import com.sopt.clody.data.remote.dto.response.DailyDiariesResponseDto import com.sopt.clody.ui.theme.ClodyTheme import kotlinx.datetime.DayOfWeek import java.time.LocalDate +import java.time.format.TextStyle @Composable fun DailyDiaryListItem( @@ -48,16 +50,16 @@ fun DailyDiaryListItem( ) { Text( text = "${date.month.value}.${date.dayOfMonth}", - style = ClodyTheme.typography.body3Medium, + style = ClodyTheme.typography.body2Medium, color = ClodyTheme.colors.gray04, modifier = Modifier.padding(vertical = 3.dp), ) Text( - text = stringResource( - id = R.string.home_daily_diary_day_of_week, - dayOfWeek.toKoreanShortLabel(), + text = dayOfWeek.getDisplayName( + TextStyle.FULL, + LocalConfiguration.current.locales.let { if (it.isEmpty) java.util.Locale.getDefault() else it[0] }, ), - style = ClodyTheme.typography.body2Medium, + style = ClodyTheme.typography.body2SemiBold, color = ClodyTheme.colors.gray02, modifier = Modifier.padding(horizontal = 6.dp, vertical = 2.dp), ) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/home/calendar/component/WeekHeader.kt b/app/src/main/java/com/sopt/clody/presentation/ui/home/calendar/component/WeekHeader.kt index 9751c3fb..41e86052 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/home/calendar/component/WeekHeader.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/home/calendar/component/WeekHeader.kt @@ -11,54 +11,18 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.sopt.clody.ui.theme.ClodyTheme import kotlinx.datetime.DayOfWeek - -enum class WeekLang { - KOREAN, ENGLISH -} - -fun DayOfWeek.toKoreanShortLabel(): String { - return when (this) { - DayOfWeek.SUNDAY -> "일" - DayOfWeek.MONDAY -> "월" - DayOfWeek.TUESDAY -> "화" - DayOfWeek.WEDNESDAY -> "수" - DayOfWeek.THURSDAY -> "목" - DayOfWeek.FRIDAY -> "금" - DayOfWeek.SATURDAY -> "토" - } -} - -fun DayOfWeek.toEnglishShortLabel(): String { - return when (this) { - DayOfWeek.SUNDAY -> "Sun" - DayOfWeek.MONDAY -> "Mon" - DayOfWeek.TUESDAY -> "Tue" - DayOfWeek.WEDNESDAY -> "Wed" - DayOfWeek.THURSDAY -> "Thu" - DayOfWeek.FRIDAY -> "Fri" - DayOfWeek.SATURDAY -> "Sat" - } -} - -fun DayOfWeek.getLabel(lang: WeekLang): String { - return when (lang) { - WeekLang.KOREAN -> this.toKoreanShortLabel() - WeekLang.ENGLISH -> this.toEnglishShortLabel() - } -} +import java.time.format.TextStyle @Composable fun WeekHeader( modifier: Modifier = Modifier, itemWidth: Dp = (LocalConfiguration.current.screenWidthDp.dp - 40.dp) / 7, - lang: WeekLang = WeekLang.KOREAN, ) { - val weekLabelArray = listOf( + val dayOfWeekArray = listOf( DayOfWeek.SUNDAY, DayOfWeek.MONDAY, DayOfWeek.TUESDAY, @@ -68,19 +32,17 @@ fun WeekHeader( DayOfWeek.SATURDAY, ) - val labels = weekLabelArray.map { it.getLabel(lang) } - Row( horizontalArrangement = Arrangement.SpaceBetween, modifier = modifier.fillMaxWidth(), ) { - labels.forEach { week -> + dayOfWeekArray.forEach { week -> Box( modifier = Modifier.width(itemWidth), contentAlignment = Alignment.Center, ) { Text( - text = week, + text = week.getDisplayName(TextStyle.NARROW, LocalConfiguration.current.locales[0]), color = ClodyTheme.colors.gray05, style = ClodyTheme.typography.detail1Medium, textAlign = TextAlign.Center, @@ -89,15 +51,3 @@ fun WeekHeader( } } } - -@Preview(showBackground = true) -@Composable -fun WeekHeaderKoreanPreview() { - WeekHeader(lang = WeekLang.KOREAN) -} - -@Preview(showBackground = true) -@Composable -fun WeekHeaderEnglishPreview() { - WeekHeader(lang = WeekLang.ENGLISH) -} diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/home/component/HomeTopAppBar.kt b/app/src/main/java/com/sopt/clody/presentation/ui/home/component/HomeTopAppBar.kt index 264207a8..6bac1a49 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/home/component/HomeTopAppBar.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/home/component/HomeTopAppBar.kt @@ -1,16 +1,22 @@ package com.sopt.clody.presentation.ui.home.component import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.IconButton +import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.sopt.clody.R import com.sopt.clody.ui.theme.ClodyTheme @@ -21,21 +27,34 @@ fun HomeTopAppBar( onClickDiaryList: () -> Unit, onClickSetting: () -> Unit, onShowYearMonthPickerStateChange: (Boolean) -> Unit, - selectedYear: Int, - selectedMonth: Int, + selectedYear: String, + selectedMonth: String, ) { CenterAlignedTopAppBar( title = { Box( - modifier = Modifier - .padding(start = 16.dp), + modifier = Modifier.padding(start = 16.dp), contentAlignment = Alignment.Center, ) { - YearAndMonthTitle( - onShowYearMonthPickerStateChange, - selectedYear, - selectedMonth, - ) + Row( + modifier = Modifier.clickable( + onClick = { onShowYearMonthPickerStateChange(true) }, + indication = null, + interactionSource = remember { MutableInteractionSource() }, + ), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + text = stringResource(R.string.home_year_month_format, selectedYear, selectedMonth), + style = ClodyTheme.typography.head4, + color = ClodyTheme.colors.gray01, + ) + Image( + painter = painterResource(id = R.drawable.ic_home_under_arrow), + contentDescription = "choose month", + modifier = Modifier.padding(horizontal = 6.dp, vertical = 6.dp), + ) + } } }, navigationIcon = { diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/home/component/YearAndMonthTitle.kt b/app/src/main/java/com/sopt/clody/presentation/ui/home/component/YearAndMonthTitle.kt deleted file mode 100644 index 209515cd..00000000 --- a/app/src/main/java/com/sopt/clody/presentation/ui/home/component/YearAndMonthTitle.kt +++ /dev/null @@ -1,49 +0,0 @@ -package com.sopt.clody.presentation.ui.home.component - -import androidx.compose.foundation.Image -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.sopt.clody.R -import com.sopt.clody.ui.theme.ClodyTheme - -@Composable -fun YearAndMonthTitle( - onShowYearMonthPickerStateChange: (Boolean) -> Unit, - selectedYear: Int, - selectedMonth: Int, -) { - val text = stringResource(R.string.home_year_month_format, selectedYear, selectedMonth) - - Column { - Row( - modifier = Modifier.clickable( - onClick = { onShowYearMonthPickerStateChange(true) }, - indication = null, - interactionSource = remember { MutableInteractionSource() }, - ), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - text = text, - style = ClodyTheme.typography.head4, - color = ClodyTheme.colors.gray01, - ) - Image( - painter = painterResource(id = R.drawable.ic_home_under_arrow), - contentDescription = "choose month", - modifier = Modifier.padding(horizontal = 6.dp, vertical = 6.dp), - ) - } - } -} diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/home/screen/HomeScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/home/screen/HomeScreen.kt index 693a5fab..71956140 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/home/screen/HomeScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/home/screen/HomeScreen.kt @@ -45,6 +45,8 @@ import com.sopt.clody.presentation.ui.home.component.DiaryStateButton import com.sopt.clody.presentation.ui.home.component.HomeTopAppBar import com.sopt.clody.presentation.utils.amplitude.AmplitudeConstraints import com.sopt.clody.presentation.utils.amplitude.AmplitudeUtils +import com.sopt.clody.presentation.utils.extension.toLocalizedMonthLabel +import com.sopt.clody.presentation.utils.extension.toLocalizedYearLabel import com.sopt.clody.presentation.utils.navigation.Route import com.sopt.clody.ui.theme.ClodyTheme import kotlinx.coroutines.async @@ -345,8 +347,8 @@ fun HomeScreen( }, onClickSetting = onClickSetting, onShowYearMonthPickerStateChange = { newState -> homeViewModel.setShowYearMonthPickerState(newState) }, - selectedYear = selectedYear, - selectedMonth = selectedMonth, + selectedYear = selectedYear.toLocalizedYearLabel(), + selectedMonth = selectedMonth.toLocalizedMonthLabel(), ) }, containerColor = ClodyTheme.colors.white, @@ -375,7 +377,7 @@ fun HomeScreen( } is CalendarState.Error -> { - homeViewModel.setErrorState(true, calendarState.message ?: stringResource(R.string.home_error_unknown)) + homeViewModel.setErrorState(true, calendarState.message) } } diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/replydiary/ReplyDiaryScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/replydiary/ReplyDiaryScreen.kt index 65efb8b1..c391ff08 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/replydiary/ReplyDiaryScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/replydiary/ReplyDiaryScreen.kt @@ -41,6 +41,7 @@ import com.sopt.clody.presentation.ui.component.LoadingScreen import com.sopt.clody.presentation.utils.amplitude.AmplitudeConstraints import com.sopt.clody.presentation.utils.amplitude.AmplitudeUtils import com.sopt.clody.presentation.utils.extension.heightForScreenPercentage +import com.sopt.clody.presentation.utils.extension.toLocalizedMonthLabel import com.sopt.clody.ui.theme.ClodyTheme @Composable @@ -118,7 +119,7 @@ fun ReplyDiaryScreen( modifier = Modifier.statusBarsPadding(), title = { Text( - text = stringResource(R.string.reply_month_and_date, month, date), + text = stringResource(R.string.reply_month_and_date, month.toLocalizedMonthLabel(), date.toString()), style = ClodyTheme.typography.head4, color = ClodyTheme.colors.gray01, ) @@ -137,13 +138,12 @@ fun ReplyDiaryScreen( content = { innerPadding -> Box( modifier = Modifier - .fillMaxSize() .background(ClodyTheme.colors.white) .padding(innerPadding), ) { Column( modifier = Modifier - .fillMaxWidth() + .fillMaxSize() .padding(horizontal = 24.dp) .padding(bottom = 28.dp) .clip(RoundedCornerShape(16.dp)) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/text/DiaryTitleText.kt b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/text/DiaryTitleText.kt deleted file mode 100644 index a1376eae..00000000 --- a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/component/text/DiaryTitleText.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.sopt.clody.presentation.ui.writediary.component.text - -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.sopt.clody.ui.theme.ClodyTheme - -@Composable -fun DiaryTitleText(date: String, separator: String, day: String, modifier: Modifier = Modifier) { - Text( - text = "$date$separator$day", - style = ClodyTheme.typography.head2, - color = ClodyTheme.colors.gray01, - ) -} - -@Preview(showBackground = true) -@Composable -fun PreviewDiaryTitleText() { - DiaryTitleText(date = "6월 26일", separator = " / ", day = "목요일") -} diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryScreen.kt b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryScreen.kt index b00eba12..da0b7fa8 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryScreen.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -39,7 +40,6 @@ import com.sopt.clody.presentation.ui.component.dialog.FailureDialog import com.sopt.clody.presentation.ui.component.toast.ClodyToastMessage import com.sopt.clody.presentation.ui.writediary.component.bottomsheet.DeleteWriteDiaryBottomSheet import com.sopt.clody.presentation.ui.writediary.component.button.AddDiaryEntryFAB -import com.sopt.clody.presentation.ui.writediary.component.text.DiaryTitleText import com.sopt.clody.presentation.ui.writediary.component.textfield.WriteDiaryTextField import com.sopt.clody.presentation.ui.writediary.component.tooltip.TooltipIcon import com.sopt.clody.presentation.ui.writediary.component.topbar.WriteDiaryTopBar @@ -50,6 +50,7 @@ import com.sopt.clody.presentation.utils.base.ClodyPreview import com.sopt.clody.presentation.utils.extension.LaunchedEffectWhenStarted import com.sopt.clody.presentation.utils.extension.getDayOfWeek import com.sopt.clody.presentation.utils.extension.heightForScreenPercentage +import com.sopt.clody.presentation.utils.extension.toLocalizedMonthLabel import com.sopt.clody.ui.theme.ClodyTheme @Composable @@ -242,10 +243,15 @@ fun WriteDiaryScreen( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - DiaryTitleText( - date = stringResource(R.string.write_diary_month_and_date, month, day), - separator = " ", - day = getDayOfWeek(year, month, day), + Text( + text = stringResource( + R.string.write_diary_month_date_day_of_week, + getDayOfWeek(year, month, day), + month.toLocalizedMonthLabel(), + day, + ), + style = ClodyTheme.typography.head2, + color = ClodyTheme.colors.gray01, ) Spacer(modifier = Modifier.weight(1f)) TooltipIcon(tooltipsText = stringResource(id = R.string.write_diary_help_message)) diff --git a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt index fc19175d..61c133cc 100644 --- a/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt +++ b/app/src/main/java/com/sopt/clody/presentation/ui/writediary/screen/WriteDiaryViewModel.kt @@ -159,7 +159,7 @@ class WriteDiaryViewModel @Inject constructor( private fun isValidEntry(text: String): Boolean { val textWithoutSpaces = text.replace("\\s".toRegex(), "") - return textWithoutSpaces.matches(Regex("^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣\\W]{2,${_diaryMaxLength.value}$")) + return textWithoutSpaces.matches(Regex("^[a-zA-Z가-힣0-9ㄱ-ㅎㅏ-ㅣ가-힣\\W]{2,${_diaryMaxLength.value}}$")) } private fun checkLimitMessage() { diff --git a/app/src/main/java/com/sopt/clody/presentation/utils/extension/DateLabelExtension.kt b/app/src/main/java/com/sopt/clody/presentation/utils/extension/DateLabelExtension.kt new file mode 100644 index 00000000..f326f70c --- /dev/null +++ b/app/src/main/java/com/sopt/clody/presentation/utils/extension/DateLabelExtension.kt @@ -0,0 +1,23 @@ +package com.sopt.clody.presentation.utils.extension + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import com.sopt.clody.R +import kotlinx.datetime.Month +import java.time.format.TextStyle +import java.util.Locale + +@Composable +fun Int.toLocalizedYearLabel(): String = stringResource(R.string.year_format, this) + +@Composable +fun Int.toLocalizedMonthLabel(): String { + val locales = LocalContext.current.resources.configuration.locales + val locale = if (locales.isEmpty) Locale.getDefault() else locales[0] + return if (locale.language == "ko") { + stringResource(R.string.month_format, this) + } else { + Month.of(this).getDisplayName(TextStyle.FULL, Locale.ENGLISH) + } +} diff --git a/app/src/main/java/com/sopt/clody/presentation/utils/extension/GetDayOfWeek.kt b/app/src/main/java/com/sopt/clody/presentation/utils/extension/GetDayOfWeek.kt index 52ceb54f..d07a5ab8 100644 --- a/app/src/main/java/com/sopt/clody/presentation/utils/extension/GetDayOfWeek.kt +++ b/app/src/main/java/com/sopt/clody/presentation/utils/extension/GetDayOfWeek.kt @@ -1,10 +1,13 @@ package com.sopt.clody.presentation.utils.extension +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration import java.time.LocalDate import java.time.format.TextStyle -import java.util.Locale +@Composable fun getDayOfWeek(year: Int, month: Int, day: Int): String { val date = LocalDate.of(year, month, day) - return date.dayOfWeek.getDisplayName(TextStyle.FULL, Locale.KOREAN) + val locale = LocalConfiguration.current.locales.let { if (it.isEmpty) java.util.Locale.getDefault() else it[0] } + return date.dayOfWeek.getDisplayName(TextStyle.FULL, locale) } diff --git a/app/src/main/java/com/sopt/clody/presentation/utils/extension/StringExt.kt b/app/src/main/java/com/sopt/clody/presentation/utils/extension/StringExt.kt index 6eeb8e26..ff32b0d7 100644 --- a/app/src/main/java/com/sopt/clody/presentation/utils/extension/StringExt.kt +++ b/app/src/main/java/com/sopt/clody/presentation/utils/extension/StringExt.kt @@ -1,9 +1,14 @@ package com.sopt.clody.presentation.utils.extension +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.sopt.clody.R + +@Composable fun String.convertTo12HourFormat(): String { val (hourBefore, minuteBefore) = this.split(":").map { it.toInt() } - val amPm = if (hourBefore < 12) "오전" else "오후" + val amPm = if (hourBefore < 12) stringResource(R.string.time_am) else stringResource(R.string.time_pm) val hourAfter = when { hourBefore == 0 -> 12 @@ -11,9 +16,9 @@ fun String.convertTo12HourFormat(): String { else -> hourBefore } - val minuteAfter = if (minuteBefore == 0) "00" else minuteBefore + val minuteAfter = if (minuteBefore == 0) "00" else minuteBefore.toString() - return String.format("$amPm ${hourAfter}시 ${minuteAfter}분") + return String.format(stringResource(R.string.notification_setting_selected_time, amPm, hourAfter, minuteAfter)) } fun Triple.to24HourFormat(): String { diff --git a/app/src/main/java/com/sopt/clody/presentation/utils/extension/YearMonthLabelUtil.kt b/app/src/main/java/com/sopt/clody/presentation/utils/extension/YearMonthLabelUtil.kt index cbddfeb0..0e1896e2 100644 --- a/app/src/main/java/com/sopt/clody/presentation/utils/extension/YearMonthLabelUtil.kt +++ b/app/src/main/java/com/sopt/clody/presentation/utils/extension/YearMonthLabelUtil.kt @@ -1,16 +1,6 @@ package com.sopt.clody.presentation.utils.extension -import androidx.compose.runtime.Composable -import androidx.compose.ui.res.stringResource -import com.sopt.clody.R - object YearMonthLabelUtil { const val MIN_YEAR = 2000 const val MAX_YEAR = 2030 } - -@Composable -fun Int.toLocalizedYearLabel(): String = stringResource(R.string.year_format, this) - -@Composable -fun Int.toLocalizedMonthLabel(): String = stringResource(R.string.month_format, this) diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 46e4a0c4..daad26b3 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -42,12 +42,13 @@ 클로버 %1$d개 - %1$d년 %2$d월 + %1$d년 + %1$d월 + %1$s %2$s 작성된 감사 일기가 없어요! 임시저장된 일기가 있어요. %1$d. %2$s - %1$s요일 일기 쓰기 답장 확인 @@ -55,7 +56,7 @@ 보내기 - %1$d월 %2$d일 + %2$s %3$s일 %1$s 신조어, 비속어, 이모지 작성은 불가능해요 일상 속 작은 감사함을 적어보세요 2~50자 까지 입력할 수 있어요. @@ -69,11 +70,11 @@ 열어보기 - %1$d월 %2$d일 + %1$s %2$s일 %1$s님을 위한 행운의 답장 - %1$s년 %2$s월 + %1$s %2$s 작성된 감사일기가 없어요 %1$s일 /%1$s @@ -103,7 +104,7 @@ 일기 작성 알림 받기 이어쓰기 알림 받기 알림 시간 - %1$s %2$s시 %3$s분 + %1$s %2$d시 %3$s분 답장 도착 알림 받기 @@ -187,8 +188,6 @@ 오전 오후 - %1$d년 - %1$d월 데이터를 불러오는데 실패했습니다. 알 수 없는 오류가 발생했습니다. 일기 삭제 중 오류가 발생했습니다. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5e61e957..ee023ea5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -42,12 +42,13 @@ %1$d Clovers - %2$d %1$d + %1$d + %1$d + %2$s %1$s No gratitude entries yet. There are existing entries in drafts. %1$d. %2$s - %1$s Write a Journal See my Reply @@ -55,7 +56,7 @@ Send - %1$d %2$d + %1$s, %2$s %3$s Please avoid using slang, profanity, or emojis. Something you\'re grateful for today. Please enter between 2 and 100 characters. @@ -69,8 +70,8 @@ Open Reply - %1$d %2$d - A lucky reply for %1$d + %1$s %2$s + A lucky reply for %1$s %2$s %1$s @@ -103,7 +104,7 @@ Journal Reminder Draft Reminder Reminder Time - %2$s:%3$s %1$s + %2$d:%3$s %1$s Reply Notification @@ -166,7 +167,7 @@ Change reminder time Save - Heads up- drafts expire\\nDon\'t miss Lody\'s reply! + Heads up- drafts expire\nDon\'t miss Lody\'s reply! To receive a reminder before the reply deadline,\nconfirm notification permissions. [Settings > Apps > Clody > Notifications] Turn on Notifications @@ -187,8 +188,6 @@ AM PM - %1$d - %1$d An error occurred while deleting the diary. Chrome must be installed to log in. Login is not available on rooted devices for security reasons.