From d35ca17a93a4b578821d9af160da4531a43f6387 Mon Sep 17 00:00:00 2001 From: twogarlic Date: Fri, 3 Jan 2025 00:29:20 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[UI]=20=EC=95=BD=EC=86=8D=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4,=20=EA=B8=B0=EA=B0=84=20=EC=9E=85=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/button/NoostakButton.kt | 38 +++ .../component/calendar/NoostakCalendar.kt | 277 ++++++++++++++++++ .../chip/NoostakCalendarCategoryChip.kt | 25 ++ .../component/textfield/NoostakTextField.kt | 59 ++++ .../main/res/drawable/ic_calendar_back.xml | 9 + .../main/res/drawable/ic_calendar_front.xml | 9 + .../com/sopt/domain/entity/CalendarEntity.kt | 11 + .../presentation/calendar/CalendarRoute.kt | 31 +- .../calendar/CalendarViewModel.kt | 22 ++ .../calendar/navigation/CalendarNavigation.kt | 45 ++- .../calendar/screen/DetailScreen.kt | 117 ++++++++ .../calendar/screen/InfoScreen.kt | 233 +++++++++++++++ .../drawable/ic_calendar_progress_long.xml | 9 + .../drawable/ic_calendar_progress_short.xml | 9 + .../res/drawable/ic_calendar_toggle_off.xml | 15 + .../res/drawable/ic_calendar_toggle_on.xml | 14 + 16 files changed, 903 insertions(+), 20 deletions(-) create mode 100644 core/src/main/java/com/sopt/core/designsystem/component/button/NoostakButton.kt create mode 100644 core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt create mode 100644 core/src/main/java/com/sopt/core/designsystem/component/chip/NoostakCalendarCategoryChip.kt create mode 100644 core/src/main/java/com/sopt/core/designsystem/component/textfield/NoostakTextField.kt create mode 100644 core/src/main/res/drawable/ic_calendar_back.xml create mode 100644 core/src/main/res/drawable/ic_calendar_front.xml create mode 100644 domain/src/main/java/com/sopt/domain/entity/CalendarEntity.kt create mode 100644 presentation/src/main/java/com/sopt/presentation/calendar/CalendarViewModel.kt create mode 100644 presentation/src/main/java/com/sopt/presentation/calendar/screen/DetailScreen.kt create mode 100644 presentation/src/main/java/com/sopt/presentation/calendar/screen/InfoScreen.kt create mode 100644 presentation/src/main/res/drawable/ic_calendar_progress_long.xml create mode 100644 presentation/src/main/res/drawable/ic_calendar_progress_short.xml create mode 100644 presentation/src/main/res/drawable/ic_calendar_toggle_off.xml create mode 100644 presentation/src/main/res/drawable/ic_calendar_toggle_on.xml diff --git a/core/src/main/java/com/sopt/core/designsystem/component/button/NoostakButton.kt b/core/src/main/java/com/sopt/core/designsystem/component/button/NoostakButton.kt new file mode 100644 index 0000000..2073589 --- /dev/null +++ b/core/src/main/java/com/sopt/core/designsystem/component/button/NoostakButton.kt @@ -0,0 +1,38 @@ +package com.sopt.core.designsystem.component.button + +import androidx.compose.ui.graphics.Color +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.material3.Button +import androidx.compose.ui.unit.dp +import com.sopt.core.designsystem.theme.NoostakTheme + +@Composable +fun NoostakButton( + text: String, + textColor: Color, + buttonColor: Color, + onButtonClick: () -> Unit, + modifier: Modifier = Modifier, + isEnabled: Boolean = true +) { + Button( + modifier = modifier, + shape = RoundedCornerShape(8.dp), + colors = ButtonDefaults.buttonColors( + containerColor = buttonColor, + disabledContainerColor = buttonColor.copy(alpha = 0.5f) // 비활성화 상태 색상 + ), + onClick = { onButtonClick() }, + enabled = isEnabled + ) { + Text( + text = text, + color = textColor, + style = NoostakTheme.typography.t3Bold + ) + } +} \ No newline at end of file diff --git a/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt b/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt new file mode 100644 index 0000000..2c53618 --- /dev/null +++ b/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt @@ -0,0 +1,277 @@ +package com.sopt.core.designsystem.component.calendar + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.sopt.core.R +import com.sopt.core.designsystem.theme.NoostakTheme +import java.time.LocalDate +import java.time.YearMonth + +@Composable +fun NoostakCalendar( + start: String, + end: String, + isSingleDate: Boolean, + isRangeSelected: (String, String) -> Unit, + modifier: Modifier = Modifier +) { + val typography = NoostakTheme.typography + val colors = NoostakTheme.colors + + var year by remember { mutableStateOf(LocalDate.now().year) } + var month by remember { mutableStateOf(LocalDate.now().monthValue) } + var selectedDates by remember { mutableStateOf>(emptyList()) } + var startDate by remember { mutableStateOf(start) } + var endDate by remember { mutableStateOf(end) } + var showMessage by remember { mutableStateOf(false) } + + val yearMonth = YearMonth.of(year, month) + val totalDays = yearMonth.lengthOfMonth() + val firstDay = LocalDate.of(year, month, 1).dayOfWeek.value % 7 + + LaunchedEffect(isSingleDate) { + selectedDates = emptyList() + startDate = "" + endDate = "" + } + + LaunchedEffect(showMessage) { + if (showMessage) { + kotlinx.coroutines.delay(3000) + showMessage = false + } + } + + Column(modifier = modifier) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(id = R.drawable.ic_calendar_back), + contentDescription = null, + modifier = Modifier + .size(16.dp) + .clickable { + if (month == 1) { + year -= 1 + month = 12 + } else { + month -= 1 + } + } + ) + Spacer(modifier = Modifier.width(10.dp)) + Text( + text = "$year 년 $month 월", + style = typography.b1SemiBold, + color = colors.gray900, + textAlign = TextAlign.Center, + ) + Spacer(modifier = Modifier.width(10.dp)) + Image( + painter = painterResource(id = R.drawable.ic_calendar_front), + contentDescription = null, + modifier = Modifier + .size(16.dp) + .clickable { + if (month == 12) { + year += 1 + month = 1 + } else { + month += 1 + } + } + ) + } + + Spacer(modifier = Modifier.height(30.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + listOf("일", "월", "화", "수", "목", "금", "토").forEach { day -> + Text( + text = day, + style = typography.c2SemiBold, + textAlign = TextAlign.Center, + color = colors.gray600, + modifier = Modifier.weight(1f) + ) + } + } + + Spacer(modifier = Modifier.height(16.dp)) + Column(verticalArrangement = Arrangement.spacedBy(15.dp)) { + var day = 1 + for (week in 0..5) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + (0..6).forEach { dayOfWeek -> + val dateText = when { + week == 0 && dayOfWeek < firstDay -> "" + day > totalDays -> "" + else -> (day++).toString() + } + + val dateValue = "$year-${month.toString().padStart(2, '0')}-${dateText.padStart(2, '0')}" + + val isSelected = dateValue in selectedDates + val isRange = !isSingleDate && + dateText.isNotEmpty() && + startDate.isNotEmpty() && + endDate.isNotEmpty() && + LocalDate.parse(dateValue) in LocalDate.parse(startDate)..LocalDate.parse(endDate) + + val isStart = dateValue == startDate + val isEnd = dateValue == endDate + + Box( + modifier = Modifier + .weight(1f) + .height(40.dp), + contentAlignment = Alignment.Center + ) { + when { + isSelected -> { + Box( + modifier = Modifier + .size(40.dp) + .background( + color = colors.blue300, + shape = androidx.compose.foundation.shape.CircleShape + ) + ) + } + isRange && !isStart && !isEnd -> { + Box( + modifier = Modifier + .fillMaxWidth() + .height(40.dp) + .background(colors.blue100) + ) + } + isStart -> { + Box( + modifier = Modifier + .fillMaxWidth() + .height(40.dp) + .padding(start = 20.dp) + .background(colors.blue100) + ) + } + isEnd -> { + Box( + modifier = Modifier + .fillMaxWidth() + .height(40.dp) + .padding(end = 20.dp) + .background(colors.blue100) + ) + } + } + + if (isStart || isEnd) { + Box( + modifier = Modifier + .size(40.dp) + .background( + color = colors.blue300, + shape = androidx.compose.foundation.shape.CircleShape + ) + ) + } + + Text( + text = dateText, + style = typography.c3Regular, + textAlign = TextAlign.Center, + color = colors.gray900, + modifier = Modifier.clickable(enabled = dateText.isNotEmpty()) { + if (isSingleDate) { + if (dateValue in selectedDates) { + selectedDates = selectedDates - dateValue + } else { + if (selectedDates.size < 7) { + selectedDates = selectedDates + dateValue + } else { + showMessage = true + } + } + } else { + val selectedDate = LocalDate.parse(dateValue) + if (startDate.isEmpty() || (startDate.isNotEmpty() && endDate.isNotEmpty())) { + startDate = dateValue + endDate = "" + } else { + val tempStart = LocalDate.parse(startDate) + val tempEnd = selectedDate + if (tempStart.isAfter(tempEnd)) { + if (tempStart.minusDays(7) > tempEnd) { + startDate = tempStart.minusDays(7).toString() + endDate = tempStart.toString() + showMessage = true + } else { + endDate = tempStart.toString() + startDate = tempEnd.toString() + } + } else { + if (tempStart.plusDays(7) < tempEnd) { + endDate = tempStart.plusDays(7).toString() + startDate = tempStart.toString() + showMessage = true + } else { + startDate = tempStart.toString() + endDate = tempEnd.toString() + } + } + } + isRangeSelected(startDate, endDate) + } + } + ) + } + } + } + } + } + + if (showMessage) { + Spacer(modifier = Modifier.height(1.dp)) + Box( + modifier = Modifier + .width(195.dp) + .height(42.dp) + .align(Alignment.CenterHorizontally) + .background( + color = colors.pink, + shape = androidx.compose.foundation.shape.RoundedCornerShape(30.dp) + ) + .padding(8.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "최대 7일까지 선택할 수 있어요", + style = typography.c2SemiBold, + color = colors.red01, + textAlign = TextAlign.Center + ) + } + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/sopt/core/designsystem/component/chip/NoostakCalendarCategoryChip.kt b/core/src/main/java/com/sopt/core/designsystem/component/chip/NoostakCalendarCategoryChip.kt new file mode 100644 index 0000000..01e0834 --- /dev/null +++ b/core/src/main/java/com/sopt/core/designsystem/component/chip/NoostakCalendarCategoryChip.kt @@ -0,0 +1,25 @@ +package com.sopt.core.designsystem.component.chip + +import androidx.compose.ui.graphics.Color +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.dp +import com.sopt.core.designsystem.theme.NoostakTheme + + +@Composable +fun NoostakCategoryChip2( + text: String, + textColor: Color, + backgroundColor: Color, + borderColor: Color +) { + NoostakChip( + text = text, + textStyle = NoostakTheme.typography.b4SemiBold, + textColor = textColor, + backgroundColor = backgroundColor, + borderColor = borderColor, + horizontalPaddingValues = 15.dp, + verticalPaddingValues = 6.dp + ) +} \ No newline at end of file diff --git a/core/src/main/java/com/sopt/core/designsystem/component/textfield/NoostakTextField.kt b/core/src/main/java/com/sopt/core/designsystem/component/textfield/NoostakTextField.kt new file mode 100644 index 0000000..d9897ec --- /dev/null +++ b/core/src/main/java/com/sopt/core/designsystem/component/textfield/NoostakTextField.kt @@ -0,0 +1,59 @@ +package com.sopt.core.designsystem.component.textfield + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.sopt.core.designsystem.theme.NoostakTheme +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsFocusedAsState + +@Composable +fun NoostakTextField( + value: String, + onValueChange: (String) -> Unit, + placeholder: String, + isEnabled: Boolean = true, + modifier: Modifier = Modifier +) { + + val interactionSource = remember { MutableInteractionSource() } + val isFocused = interactionSource.collectIsFocusedAsState().value + + TextField( + value = value, + onValueChange = { onValueChange(it) }, + placeholder = { + Text( + text = placeholder, + color = NoostakTheme.colors.gray500, + style = NoostakTheme.typography.b1SemiBold + ) + }, + enabled = isEnabled, + modifier = modifier + .height(52.dp) + .border( + width = 1.dp, + color = if (isFocused) NoostakTheme.colors.blue300 else NoostakTheme.colors.gray200, + shape = RoundedCornerShape(10.dp) + ), + colors = TextFieldDefaults.colors( + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + disabledContainerColor = Color.Transparent, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent + ), + textStyle = NoostakTheme.typography.b1SemiBold, + singleLine = true, + interactionSource = interactionSource + ) +} diff --git a/core/src/main/res/drawable/ic_calendar_back.xml b/core/src/main/res/drawable/ic_calendar_back.xml new file mode 100644 index 0000000..0997510 --- /dev/null +++ b/core/src/main/res/drawable/ic_calendar_back.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/src/main/res/drawable/ic_calendar_front.xml b/core/src/main/res/drawable/ic_calendar_front.xml new file mode 100644 index 0000000..6ba3cdf --- /dev/null +++ b/core/src/main/res/drawable/ic_calendar_front.xml @@ -0,0 +1,9 @@ + + + diff --git a/domain/src/main/java/com/sopt/domain/entity/CalendarEntity.kt b/domain/src/main/java/com/sopt/domain/entity/CalendarEntity.kt new file mode 100644 index 0000000..2af06d6 --- /dev/null +++ b/domain/src/main/java/com/sopt/domain/entity/CalendarEntity.kt @@ -0,0 +1,11 @@ +package com.sopt.domain.entity + +data class CalendarEntity( + val appointName: String = "", + val startDate: String = "", + val endDate: String = "", + val startTime: String = "", + val endTime: String = "", + val category: String = "", + val time: Int = 0 +) diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/CalendarRoute.kt b/presentation/src/main/java/com/sopt/presentation/calendar/CalendarRoute.kt index a40d371..51c6846 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/CalendarRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/CalendarRoute.kt @@ -5,24 +5,27 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview -import com.sopt.core.designsystem.theme.NoostakAndroidTheme -import com.sopt.core.designsystem.theme.NoostakTheme @Composable fun CalendarRoute( - paddingValues: PaddingValues + paddingValues: PaddingValues, + navigateToInfoScreen: () -> Unit ) { - CalendarScreen(paddingValues = paddingValues) + CalendarScreen( + paddingValues = paddingValues, + onNavigateToInfoScreen = navigateToInfoScreen + ) } @Composable fun CalendarScreen( - paddingValues: PaddingValues = PaddingValues() + paddingValues: PaddingValues = PaddingValues(), + onNavigateToInfoScreen: () -> Unit ) { Column( modifier = Modifier @@ -31,18 +34,8 @@ fun CalendarScreen( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - Text( - text = "Calendar Screen", - style = NoostakTheme.typography.h1Bold, - color = NoostakTheme.colors.blue600 - ) - } -} - -@Preview(showBackground = true) -@Composable -fun CalendarScreenPreview() { - NoostakAndroidTheme { - CalendarScreen() + Button(onClick = { onNavigateToInfoScreen() }) { + Text("InfoScreen ㄱㄱ") + } } } diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/CalendarViewModel.kt b/presentation/src/main/java/com/sopt/presentation/calendar/CalendarViewModel.kt new file mode 100644 index 0000000..5194d9a --- /dev/null +++ b/presentation/src/main/java/com/sopt/presentation/calendar/CalendarViewModel.kt @@ -0,0 +1,22 @@ +package com.sopt.presentation.calendar + +import androidx.lifecycle.ViewModel +import com.sopt.domain.entity.CalendarEntity +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class CalendarViewModel : ViewModel() { + private val _uiState = MutableStateFlow(CalendarEntity()) + val uiState: StateFlow = _uiState + + fun updateTime(newTime: Int) { + _uiState.value = _uiState.value.copy(time = newTime) + } + fun updateAppointName(name: String) { + _uiState.value = _uiState.value.copy(appointName = name) + } + + fun updateCategory(category: String) { + _uiState.value = _uiState.value.copy(category = category) + } +} diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt b/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt index 88a11e6..c574d33 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt @@ -1,12 +1,15 @@ package com.sopt.presentation.calendar.navigation import androidx.compose.foundation.layout.PaddingValues +import androidx.hilt.navigation.compose.hiltViewModel import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.sopt.core.navigation.MainTabRoute import com.sopt.presentation.calendar.CalendarRoute +import com.sopt.presentation.calendar.screen.DetailScreen +import com.sopt.presentation.calendar.screen.InfoScreen import kotlinx.serialization.Serializable fun NavController.navigateCalendar(navOptions: NavOptions? = null) { @@ -16,16 +19,56 @@ fun NavController.navigateCalendar(navOptions: NavOptions? = null) { ) } +fun NavController.navigateInfoScreen(navOptions: NavOptions? = null) { + navigate( + route = Info, + navOptions = navOptions + ) +} + +fun NavController.navigateDetailScreen(navOptions: NavOptions? = null) { + navigate( + route = Detail, + navOptions = navOptions + ) +} + fun NavGraphBuilder.calendarNavGraph( paddingValues: PaddingValues, navHostController: NavController ) { composable { CalendarRoute( - paddingValues = paddingValues + paddingValues = paddingValues, + navigateToInfoScreen = { + navHostController.navigateInfoScreen() + } ) } + + composable { + InfoScreen( + viewModel = hiltViewModel(), + onNextClick = { + navHostController.navigateDetailScreen() + } + ) + } + + composable { + DetailScreen( + onBackClick = { navHostController.navigateUp() }, + onNextClick = { + + }) + } } @Serializable data object Calendar : MainTabRoute + +@Serializable +data object Info : MainTabRoute + +@Serializable +data object Detail : MainTabRoute diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/screen/DetailScreen.kt b/presentation/src/main/java/com/sopt/presentation/calendar/screen/DetailScreen.kt new file mode 100644 index 0000000..3ba52f4 --- /dev/null +++ b/presentation/src/main/java/com/sopt/presentation/calendar/screen/DetailScreen.kt @@ -0,0 +1,117 @@ +package com.sopt.presentation.calendar.screen + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.sopt.core.designsystem.component.button.NoostakButton +import com.sopt.core.designsystem.component.calendar.NoostakCalendar +import com.sopt.core.designsystem.component.topappbar.NoostakTopAppBar +import com.sopt.core.designsystem.theme.NoostakTheme +import com.sopt.presentation.R + +@Composable +fun DetailScreen(onNextClick: () -> Unit, onBackClick: () -> Unit) { + var startDate by remember { mutableStateOf("") } + var endDate by remember { mutableStateOf("") } + var isSingleDateMode by remember { mutableStateOf(false) } + + val typography = NoostakTheme.typography + val colors = NoostakTheme.colors + + Scaffold( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp, vertical = 24.dp), + topBar = { + NoostakTopAppBar( + title = "약속 만들기", + modifier = Modifier.fillMaxWidth(), + isIconVisible = false + ) + } + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { + ProgressBar(progressBar = listOf(false, true, false)) + + HeaderText(text = "약속을 생성할 기간을\n선택해주세요") + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 23.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.weight(1f), + text = "하루씩 설정하기", + style = typography.b2Regular, + textAlign = TextAlign.Start, + color = colors.gray900 + ) + Image( + painter = painterResource( + id = if (isSingleDateMode) R.drawable.ic_calendar_toggle_on + else R.drawable.ic_calendar_toggle_off + ), + contentDescription = null, + contentScale = ContentScale.FillBounds, + modifier = Modifier + .clickable { + isSingleDateMode = !isSingleDateMode + } + ) + } + + Spacer(modifier = Modifier.height(24.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .height(1.dp) + .background(colors.gray200) + ) + Spacer(modifier = Modifier.height(17.dp)) + + NoostakCalendar( + start = startDate, + end = endDate, + isSingleDate = isSingleDateMode, + isRangeSelected = { start, end -> + startDate = start + endDate = end + } + ) + + Spacer(modifier = Modifier.weight(1f)) + + NoostakButton( + text = "다음", + textColor = colors.white, + buttonColor = if (startDate.isNotEmpty() && endDate.isNotEmpty()) colors.black else colors.gray500, + onButtonClick = onNextClick, + modifier = Modifier + .fillMaxWidth() + .height(54.dp), + isEnabled = startDate.isNotEmpty() && endDate.isNotEmpty() + ) + } + } +} + diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/screen/InfoScreen.kt b/presentation/src/main/java/com/sopt/presentation/calendar/screen/InfoScreen.kt new file mode 100644 index 0000000..0bfd6d7 --- /dev/null +++ b/presentation/src/main/java/com/sopt/presentation/calendar/screen/InfoScreen.kt @@ -0,0 +1,233 @@ +package com.sopt.presentation.calendar.screen + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +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.text.TextStyle +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.sopt.core.designsystem.component.button.NoostakButton +import com.sopt.core.designsystem.component.chip.NoostakCategoryChip2 +import com.sopt.core.designsystem.component.textfield.NoostakTextField +import com.sopt.core.designsystem.component.topappbar.NoostakTopAppBar +import com.sopt.core.designsystem.theme.NoostakTheme +import com.sopt.core.designsystem.theme.PretendardSemiBold +import com.sopt.presentation.R +import com.sopt.presentation.calendar.CalendarViewModel + +@Composable +fun InfoScreen(viewModel: CalendarViewModel, onNextClick: () -> Unit) { + val uiState by viewModel.uiState.collectAsState() + + val typography = NoostakTheme.typography + val colors = NoostakTheme.colors + + Scaffold( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp, vertical = 24.dp), + topBar = { + NoostakTopAppBar( + title = "약속 만들기", + modifier = Modifier.fillMaxWidth(), + isIconVisible = false + ) + } + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { + + ProgressBar(progressBar = listOf(true, false, false)) + + HeaderText(text = "약속 정보를 입력해주세요") + + SubHeaderText("약속 이름", Modifier.padding(top = 22.dp)) + + Box(modifier = Modifier.fillMaxWidth()) { + Column { + NoostakTextField( + value = uiState.appointName, + onValueChange = { newValue -> + if (newValue.length <= 20) viewModel.updateAppointName(newValue) + }, + placeholder = "이름을 입력해주세요.", + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp) + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(end = 4.dp, top = 4.dp), + horizontalArrangement = Arrangement.End + ) { + Text( + text = "${uiState.appointName.length}/20", + style = typography.b5Regular, + color = colors.gray500 + ) + } + } + } + + SubHeaderText("약속 카테고리", Modifier.padding(top = 41.dp)) + + Row( + horizontalArrangement = Arrangement.spacedBy(11.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp) + ) { + listOf("중요", "일정", "취미", "기타").forEach { category -> + val isSelected = uiState.category == category + Box( + modifier = Modifier + .background(Color.Transparent) + .clickable { viewModel.updateCategory(category) } + ) { + NoostakCategoryChip2( + text = category, + textColor = if (isSelected) Color.White else colors.black, + backgroundColor = if (isSelected) colors.black else Color.White, + borderColor = if (isSelected) Color.Transparent else colors.gray200 + ) + } + } + } + + SubHeaderText("소요시간", Modifier.padding(top = 41.dp)) + + Box( + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp) + .background(Color.White) + .height(48.dp), + contentAlignment = Alignment.CenterStart + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.End, + verticalAlignment = Alignment.CenterVertically + ) { + BasicTextField( + value = if (uiState.time == 0) "" else uiState.time.toString(), + onValueChange = { newValue -> + val timeValue = newValue.toIntOrNull() ?: 0 + viewModel.updateTime(timeValue) + }, + modifier = Modifier.width(50.dp), + textStyle = TextStyle( + fontFamily = PretendardSemiBold, + fontWeight = FontWeight.SemiBold, + fontSize = 16.sp, + lineHeight = 22.sp, + textAlign = TextAlign.End, + color = colors.black + ), + singleLine = true + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = "시간", + style = typography.b1SemiBold, + color = colors.gray700 + ) + } + } + + Box( + modifier = Modifier + .fillMaxWidth() + .height(2.dp) + .background(colors.gray200) + ) + + Spacer(modifier = Modifier.weight(1f)) + + NoostakButton( + text = "다음", + textColor = colors.white, + buttonColor = if (uiState.appointName.isNotBlank() && + uiState.category.isNotBlank() && + uiState.time > 0 + ) colors.black else colors.gray500, + onButtonClick = onNextClick, + modifier = Modifier + .fillMaxWidth() + .height(54.dp), + isEnabled = uiState.appointName.isNotBlank() && + uiState.category.isNotBlank() && + uiState.time > 0 + ) + } + } +} + +@Composable +fun HeaderText(text: String, modifier: Modifier = Modifier) { + val typography = NoostakTheme.typography + val colors = NoostakTheme.colors + + Text( + modifier = modifier + .fillMaxWidth() + .padding(top = 26.dp), + text = text, + style = typography.h4SemiBold, + textAlign = TextAlign.Start, + color = colors.gray900 + ) +} + +@Composable +fun SubHeaderText(text: String, modifier: Modifier) { + val typography = NoostakTheme.typography + val colors = NoostakTheme.colors + + Text( + modifier = modifier, + text = text, + style = typography.c2SemiBold, + color = colors.gray900 + ) +} + +@Composable +fun ProgressBar(progressBar: List) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 18.dp) + ) { + progressBar.forEachIndexed { index, isLong -> + Image( + painter = painterResource( + id = if (isLong) R.drawable.ic_calendar_progress_long else R.drawable.ic_calendar_progress_short + ), + contentDescription = null, + modifier = if (isLong) Modifier.height(16.dp) else Modifier.size(16.dp) + ) + if (index < progressBar.size - 1) Spacer(modifier = Modifier.width(10.dp)) + } + } +} \ No newline at end of file diff --git a/presentation/src/main/res/drawable/ic_calendar_progress_long.xml b/presentation/src/main/res/drawable/ic_calendar_progress_long.xml new file mode 100644 index 0000000..f26c8f5 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_calendar_progress_long.xml @@ -0,0 +1,9 @@ + + + diff --git a/presentation/src/main/res/drawable/ic_calendar_progress_short.xml b/presentation/src/main/res/drawable/ic_calendar_progress_short.xml new file mode 100644 index 0000000..e059ba7 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_calendar_progress_short.xml @@ -0,0 +1,9 @@ + + + diff --git a/presentation/src/main/res/drawable/ic_calendar_toggle_off.xml b/presentation/src/main/res/drawable/ic_calendar_toggle_off.xml new file mode 100644 index 0000000..479eac7 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_calendar_toggle_off.xml @@ -0,0 +1,15 @@ + + + + diff --git a/presentation/src/main/res/drawable/ic_calendar_toggle_on.xml b/presentation/src/main/res/drawable/ic_calendar_toggle_on.xml new file mode 100644 index 0000000..fceb24a --- /dev/null +++ b/presentation/src/main/res/drawable/ic_calendar_toggle_on.xml @@ -0,0 +1,14 @@ + + + + From 896181463f1a16ef63d6430667bf9bc8a7a6e1dd Mon Sep 17 00:00:00 2001 From: twogarlic Date: Fri, 10 Jan 2025 18:19:33 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[REFACTOR]=20=EC=8A=A4=ED=8A=B8=EB=A7=81=20?= =?UTF-8?q?=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/calendar/NoostakCalendar.kt | 3 ++- core/src/main/res/values/strings.xml | 2 ++ .../calendar/calendarInfo/CalendarInfoRoute.kt | 13 +++++++------ .../calendar/calendarPeriod/CalendarPeriodRoute.kt | 9 +++++---- presentation/src/main/res/values/strings.xml | 10 ++++++++++ 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt b/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt index 60460a9..2096abd 100644 --- a/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt +++ b/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.* 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.text.style.TextAlign import androidx.compose.ui.unit.dp import com.sopt.core.R @@ -250,7 +251,7 @@ fun NoostakCalendar( contentAlignment = Alignment.Center ){ NoostakSnackBar( - message = "최대 7일까지 선택할 수 있어요", + message = stringResource(R.string.text_noostak_calendar_7days), textStyle = typography.c2SemiBold, textColor = colors.red01, backgroundColor = colors.pink diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index c94d9d7..bb8ab0e 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -26,4 +26,6 @@ Add Profile Image By Camera Button + + 최대 7일까지 선택할 수 있어요 \ No newline at end of file diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoRoute.kt b/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoRoute.kt index 91ed9c7..0f79962 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoRoute.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -70,7 +71,7 @@ fun CalendarInfoScreen( .padding(horizontal = dimensionResource(id = R.dimen.horizontal_padding)), topBar = { NoostakTopAppBar( - title = "약속 만들기", + title = stringResource(R.string.text_calendar_appointment), isIconVisible = false ) } @@ -84,9 +85,9 @@ fun CalendarInfoScreen( NoostakProgressBar(progressBar = listOf(true, false, false)) - NoostakHeaderText(text = "약속 정보를 입력해주세요") + NoostakHeaderText(text = stringResource(R.string.text_calendar_appointment_write)) - NoostakSubHeaderText("약속 이름", Modifier.padding(top = 22.dp, bottom = 10.dp)) + NoostakSubHeaderText(stringResource(R.string.text_calendar_appointment_name), Modifier.padding(top = 22.dp, bottom = 10.dp)) NoostakTextField( textFieldType = TextFieldType.CALENDAR, @@ -102,7 +103,7 @@ fun CalendarInfoScreen( maxLength = 20 ) - NoostakSubHeaderText("약속 카테고리", Modifier.padding(top = 32.dp)) + NoostakSubHeaderText(stringResource(R.string.text_calendar_appointment_category), Modifier.padding(top = 32.dp)) Row( horizontalArrangement = Arrangement.spacedBy(11.dp), @@ -131,7 +132,7 @@ fun CalendarInfoScreen( } } - NoostakSubHeaderText("소요시간", Modifier.padding(top = 41.dp)) + NoostakSubHeaderText(stringResource(R.string.text_calendar_appointment_time), Modifier.padding(top = 41.dp)) Box( modifier = Modifier @@ -167,7 +168,7 @@ fun CalendarInfoScreen( Spacer(modifier = Modifier.weight(1f)) NoostakBottomButton( - text = "다음", + text = stringResource(R.string.text_calendar_appointment_next), onButtonClick = { val time = duration.toIntOrNull() ?: 0 onButtonClick(appointmentName, selectedCategory, time) diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodRoute.kt b/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodRoute.kt index 88860d0..9bff741 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodRoute.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.dimensionResource 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.hilt.navigation.compose.hiltViewModel @@ -82,7 +83,7 @@ fun CalendarPeriodScreen( .padding(horizontal = dimensionResource(id = R.dimen.horizontal_padding)), topBar = { NoostakTopAppBar( - title = "약속 만들기", + title = stringResource(R.string.text_calendar_appointment), isIconVisible = false ) } @@ -96,7 +97,7 @@ fun CalendarPeriodScreen( NoostakProgressBar(progressBar = listOf(false, true, false)) - NoostakHeaderText(text = "약속을 생성할 기간을\n선택해주세요") + NoostakHeaderText(text = stringResource(R.string.text_calendar_appointment_choose)) Row( modifier = Modifier @@ -106,7 +107,7 @@ fun CalendarPeriodScreen( horizontalArrangement = Arrangement.SpaceBetween ) { Text( - text = "하루씩 설정하기", + text = stringResource(R.string.text_calendar_appointment_one_day), style = typography.b2Regular, textAlign = TextAlign.Start, color = colors.gray900, @@ -148,7 +149,7 @@ fun CalendarPeriodScreen( Spacer(modifier = Modifier.weight(1f)) NoostakBottomButton( - text = "다음", + text = stringResource(R.string.text_calendar_appointment_next), onButtonClick = {}, isEnabled = startDate.isNotEmpty() && endDate.isNotEmpty(), deactivateColor = NoostakTheme.colors.gray500, diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 72ba89c..a3fd32d 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -98,4 +98,14 @@ 그룹 코드 보내기 그룹 코드가 복사되었습니다 + + 약속 만들기 + 약속 정보를 입력해주세요 + 약속 이름 + 약속 카테고리 + 소요시간 + 약속을 생성할 기간을\n선택해주세요 + 하루씩 설정하기 + 다음 + \ No newline at end of file From 06c6252ee45422a4b419b07294cbab8fccd71d46 Mon Sep 17 00:00:00 2001 From: twogarlic Date: Fri, 10 Jan 2025 18:30:02 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[REFACTOR]=20ktlint=20=ED=8F=AC=EB=A7=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/button/NoostakButton.kt | 6 ++-- .../component/calendar/NoostakCalendar.kt | 31 ++++++++++++++----- .../chip/NoostakCalendarCategoryChip.kt | 5 ++- .../component/text/NoostakHeaderText.kt | 2 +- .../component/text/NoostakSubHeaderText.kt | 2 +- .../presentation/calendar/CalendarRoute.kt | 6 ++-- .../calendar/CalendarSideEffect.kt | 1 - .../calendar/CalendarViewModel.kt | 2 +- .../calendarInfo/CalendarInfoRoute.kt | 28 +++++++++++++---- .../calendarInfo/CalendarInfoViewModel.kt | 6 ++-- .../calendarPeriod/CalendarPeriodRoute.kt | 31 +++++++++++++------ .../calendarPeriod/CalendarPeriodViewModel.kt | 6 ++-- .../calendar/navigation/CalendarNavigation.kt | 4 +-- 13 files changed, 84 insertions(+), 46 deletions(-) diff --git a/core/src/main/java/com/sopt/core/designsystem/component/button/NoostakButton.kt b/core/src/main/java/com/sopt/core/designsystem/component/button/NoostakButton.kt index 2073589..a4731a8 100644 --- a/core/src/main/java/com/sopt/core/designsystem/component/button/NoostakButton.kt +++ b/core/src/main/java/com/sopt/core/designsystem/component/button/NoostakButton.kt @@ -1,12 +1,12 @@ package com.sopt.core.designsystem.component.button -import androidx.compose.ui.graphics.Color import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.material3.Button +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.sopt.core.designsystem.theme.NoostakTheme @@ -35,4 +35,4 @@ fun NoostakButton( style = NoostakTheme.typography.t3Bold ) } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt b/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt index 2096abd..9414b89 100644 --- a/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt +++ b/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt @@ -3,10 +3,25 @@ package com.sopt.core.designsystem.component.calendar import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource @@ -129,10 +144,10 @@ fun NoostakCalendar( val isSelected = dateValue in selectedDates val isRange = !isSingleDate && - dateText.isNotEmpty() && - startDate.isNotEmpty() && - endDate.isNotEmpty() && - LocalDate.parse(dateValue) in LocalDate.parse(startDate)..LocalDate.parse(endDate) + dateText.isNotEmpty() && + startDate.isNotEmpty() && + endDate.isNotEmpty() && + LocalDate.parse(dateValue) in LocalDate.parse(startDate)..LocalDate.parse(endDate) val isStart = dateValue == startDate val isEnd = dateValue == endDate @@ -249,7 +264,7 @@ fun NoostakCalendar( Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center - ){ + ) { NoostakSnackBar( message = stringResource(R.string.text_noostak_calendar_7days), textStyle = typography.c2SemiBold, @@ -259,4 +274,4 @@ fun NoostakCalendar( } } } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/sopt/core/designsystem/component/chip/NoostakCalendarCategoryChip.kt b/core/src/main/java/com/sopt/core/designsystem/component/chip/NoostakCalendarCategoryChip.kt index 01e0834..ea9f71c 100644 --- a/core/src/main/java/com/sopt/core/designsystem/component/chip/NoostakCalendarCategoryChip.kt +++ b/core/src/main/java/com/sopt/core/designsystem/component/chip/NoostakCalendarCategoryChip.kt @@ -1,11 +1,10 @@ package com.sopt.core.designsystem.component.chip -import androidx.compose.ui.graphics.Color import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import com.sopt.core.designsystem.theme.NoostakTheme - @Composable fun NoostakCategoryChip2( text: String, @@ -22,4 +21,4 @@ fun NoostakCategoryChip2( horizontalPaddingValues = 15.dp, verticalPaddingValues = 6.dp ) -} \ No newline at end of file +} diff --git a/core/src/main/java/com/sopt/core/designsystem/component/text/NoostakHeaderText.kt b/core/src/main/java/com/sopt/core/designsystem/component/text/NoostakHeaderText.kt index 49f0429..287a633 100644 --- a/core/src/main/java/com/sopt/core/designsystem/component/text/NoostakHeaderText.kt +++ b/core/src/main/java/com/sopt/core/designsystem/component/text/NoostakHeaderText.kt @@ -20,4 +20,4 @@ fun NoostakHeaderText(text: String, modifier: Modifier = Modifier) { textAlign = TextAlign.Start, color = NoostakTheme.colors.gray900 ) -} \ No newline at end of file +} diff --git a/core/src/main/java/com/sopt/core/designsystem/component/text/NoostakSubHeaderText.kt b/core/src/main/java/com/sopt/core/designsystem/component/text/NoostakSubHeaderText.kt index 61c8c5a..d39f29c 100644 --- a/core/src/main/java/com/sopt/core/designsystem/component/text/NoostakSubHeaderText.kt +++ b/core/src/main/java/com/sopt/core/designsystem/component/text/NoostakSubHeaderText.kt @@ -13,4 +13,4 @@ fun NoostakSubHeaderText(text: String, modifier: Modifier) { style = NoostakTheme.typography.c2SemiBold, color = NoostakTheme.colors.gray900 ) -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/CalendarRoute.kt b/presentation/src/main/java/com/sopt/presentation/calendar/CalendarRoute.kt index 9441886..f2d253d 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/CalendarRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/CalendarRoute.kt @@ -28,16 +28,14 @@ fun CalendarRoute( CalendarScreen( paddingValues = paddingValues, - onNavigateToCalendarInfoScreen = calendarViewModel::navigateToCalendarInfoScreen, + onNavigateToCalendarInfoScreen = calendarViewModel::navigateToCalendarInfoScreen ) - } - @Composable fun CalendarScreen( paddingValues: PaddingValues = PaddingValues(), - onNavigateToCalendarInfoScreen: () -> Unit, + onNavigateToCalendarInfoScreen: () -> Unit ) { Column( modifier = androidx.compose.ui.Modifier diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/CalendarSideEffect.kt b/presentation/src/main/java/com/sopt/presentation/calendar/CalendarSideEffect.kt index 0f4c91e..5f09815 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/CalendarSideEffect.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/CalendarSideEffect.kt @@ -3,4 +3,3 @@ package com.sopt.presentation.calendar sealed class CalendarSideEffect { object NavigateToInfo : CalendarSideEffect() } - diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/CalendarViewModel.kt b/presentation/src/main/java/com/sopt/presentation/calendar/CalendarViewModel.kt index 78ae830..159e1b2 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/CalendarViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/CalendarViewModel.kt @@ -6,7 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import javax.inject.Inject @HiltViewModel -class CalendarViewModel @Inject constructor(): BaseViewModel() { +class CalendarViewModel @Inject constructor() : BaseViewModel() { fun navigateToCalendarInfoScreen() { emitSideEffect(CalendarSideEffect.NavigateToInfo) diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoRoute.kt b/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoRoute.kt index 0f79962..2e6da6c 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoRoute.kt @@ -2,13 +2,29 @@ package com.sopt.presentation.calendar.calendarInfo import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -19,14 +35,14 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.sopt.core.designsystem.component.button.NoostakBottomButton import com.sopt.core.designsystem.component.chip.NoostakChip +import com.sopt.core.designsystem.component.progressbar.NoostakProgressBar +import com.sopt.core.designsystem.component.text.NoostakHeaderText +import com.sopt.core.designsystem.component.text.NoostakSubHeaderText import com.sopt.core.designsystem.component.textfield.NoostakTextField import com.sopt.core.designsystem.component.topappbar.NoostakTopAppBar import com.sopt.core.designsystem.theme.NoostakTheme import com.sopt.core.type.TextFieldType import com.sopt.presentation.R -import com.sopt.core.designsystem.component.progressbar.NoostakProgressBar -import com.sopt.core.designsystem.component.text.NoostakHeaderText -import com.sopt.core.designsystem.component.text.NoostakSubHeaderText @Composable fun CalendarInfoRoute( @@ -138,7 +154,7 @@ fun CalendarInfoScreen( modifier = Modifier .fillMaxWidth() .padding(top = 10.dp) - .background(Color.White), + .background(Color.White) ) { Row( modifier = Modifier diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoViewModel.kt b/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoViewModel.kt index 41c7b36..733f21f 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/calendarInfo/CalendarInfoViewModel.kt @@ -9,8 +9,8 @@ import javax.inject.Inject class CalendarInfoViewModel @Inject constructor() : BaseViewModel() { val categories = immutableListOf("중요", "일정", "취미", "기타") - fun navigateToCalendarPeriod(appointmentName: String,category: String,time: Int) { - emitSideEffect(CalendarInfoSideEffect.NavigateToPeriod(appointmentName,category,time)) + fun navigateToCalendarPeriod(appointmentName: String, category: String, time: Int) { + emitSideEffect(CalendarInfoSideEffect.NavigateToPeriod(appointmentName, category, time)) } } @@ -20,4 +20,4 @@ sealed class CalendarInfoSideEffect { val category: String, val time: Int ) : CalendarInfoSideEffect() -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodRoute.kt b/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodRoute.kt index 9bff741..9ba1078 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodRoute.kt @@ -3,7 +3,17 @@ package com.sopt.presentation.calendar.calendarPeriod import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -23,11 +33,11 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.sopt.core.designsystem.component.button.NoostakBottomButton import com.sopt.core.designsystem.component.calendar.NoostakCalendar +import com.sopt.core.designsystem.component.progressbar.NoostakProgressBar +import com.sopt.core.designsystem.component.text.NoostakHeaderText import com.sopt.core.designsystem.component.topappbar.NoostakTopAppBar import com.sopt.core.designsystem.theme.NoostakTheme import com.sopt.presentation.R -import com.sopt.core.designsystem.component.progressbar.NoostakProgressBar -import com.sopt.core.designsystem.component.text.NoostakHeaderText @Composable fun CalendarPeriodRoute( @@ -68,7 +78,7 @@ fun CalendarPeriodScreen( category: String, time: Int, days: List -){ +) { var startDate by remember { mutableStateOf("") } var endDate by remember { mutableStateOf("") } var isSingleDateMode by remember { mutableStateOf(false) } @@ -110,13 +120,16 @@ fun CalendarPeriodScreen( text = stringResource(R.string.text_calendar_appointment_one_day), style = typography.b2Regular, textAlign = TextAlign.Start, - color = colors.gray900, + color = colors.gray900 ) - //색 지정되면 switch로 변경할게요 + // 색 지정되면 switch로 변경할게요 Image( painter = painterResource( - id = if (isSingleDateMode) R.drawable.ic_calendar_toggle_on - else R.drawable.ic_calendar_toggle_off + id = if (isSingleDateMode) { + R.drawable.ic_calendar_toggle_on + } else { + R.drawable.ic_calendar_toggle_off + } ), contentDescription = null, contentScale = ContentScale.FillBounds, @@ -156,8 +169,6 @@ fun CalendarPeriodScreen( activateColor = NoostakTheme.colors.gray900, modifier = Modifier.padding(bottom = 16.dp) ) - } } } - diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodViewModel.kt b/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodViewModel.kt index 8a4e67c..ec33f1b 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/calendarPeriod/CalendarPeriodViewModel.kt @@ -9,8 +9,8 @@ import javax.inject.Inject class CalendarPeriodViewModel @Inject constructor() : BaseViewModel() { val days = immutableListOf("일", "월", "화", "수", "목", "금", "토") - fun navigateToCalendarTimePicker(appointmentName: String,category: String,time: Int,startDate: String,endDate: String,dates: List) { - emitSideEffect(CalendarPeriodSideEffect.NavigateToTimePicker(appointmentName,category,time,startDate,endDate,dates)) + fun navigateToCalendarTimePicker(appointmentName: String, category: String, time: Int, startDate: String, endDate: String, dates: List) { + emitSideEffect(CalendarPeriodSideEffect.NavigateToTimePicker(appointmentName, category, time, startDate, endDate, dates)) } } @@ -23,4 +23,4 @@ sealed class CalendarPeriodSideEffect { val endDate: String, val dates: List ) : CalendarPeriodSideEffect() -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt b/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt index e0292b3..f5e0080 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt @@ -88,7 +88,7 @@ fun NavGraphBuilder.calendarNavGraph( appointmentName = args.appointmentName, category = args.category, time = args.time, - navigateToTimePicker = { appointmentName, category, time , startDate, endDate, dates -> + navigateToTimePicker = { appointmentName, category, time, startDate, endDate, dates -> navHostController.navigateCalendarTimePicker( appointmentName = appointmentName, category = category, @@ -123,4 +123,4 @@ data class CalendarTimePicker( val startDate: String = "", val endDate: String = "", val dates: List? = null -) : MainTabRoute \ No newline at end of file +) : MainTabRoute From 6f5ffe66741e3714d10df4b0ceb1bf050d007d34 Mon Sep 17 00:00:00 2001 From: twogarlic Date: Wed, 15 Jan 2025 22:48:13 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[UI]=20=ED=83=80=EC=9E=84=ED=94=BC=EC=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/checkbox/CircularCheckbox.kt | 50 +++ .../component/timepicker/NoostakTimePicker.kt | 335 ++++++++++++++++++ .../component/toggle/NoostakSwitch.kt | 37 ++ domain/build.gradle.kts | 4 +- .../CalendarTimePickerRoute.kt | 222 ++++++++++++ .../CalendarTimePickerViewModel.kt | 24 ++ .../src/main/res/drawable/ic_calendar_off.xml | 9 + .../src/main/res/drawable/ic_calendar_on.xml | 9 + 8 files changed, 688 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/com/sopt/core/designsystem/component/checkbox/CircularCheckbox.kt create mode 100644 core/src/main/java/com/sopt/core/designsystem/component/timepicker/NoostakTimePicker.kt create mode 100644 core/src/main/java/com/sopt/core/designsystem/component/toggle/NoostakSwitch.kt create mode 100644 presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerRoute.kt create mode 100644 presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerViewModel.kt create mode 100644 presentation/src/main/res/drawable/ic_calendar_off.xml create mode 100644 presentation/src/main/res/drawable/ic_calendar_on.xml diff --git a/core/src/main/java/com/sopt/core/designsystem/component/checkbox/CircularCheckbox.kt b/core/src/main/java/com/sopt/core/designsystem/component/checkbox/CircularCheckbox.kt new file mode 100644 index 0000000..9d3008d --- /dev/null +++ b/core/src/main/java/com/sopt/core/designsystem/component/checkbox/CircularCheckbox.kt @@ -0,0 +1,50 @@ +package com.sopt.core.designsystem.component.checkbox + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +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.unit.dp +import com.sopt.core.designsystem.theme.NoostakTheme + +@Composable +fun CircularCheckbox( + isChecked: Boolean, + onCheckedChange: (Boolean) -> Unit, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .size(24.dp) + .clip(CircleShape) + .border( + width = 2.dp, + color = if (isChecked) NoostakTheme.colors.blue600 else NoostakTheme.colors.gray500, + shape = CircleShape + ) + .clickable { onCheckedChange(!isChecked) }, + contentAlignment = Alignment.Center + ) { + if (isChecked) { + Box( + modifier = Modifier + .size(24.dp) + .clip(CircleShape) + .background(NoostakTheme.colors.blue600) + ) + Box( + modifier = Modifier + .size(10.dp) + .clip(CircleShape) + .background(NoostakTheme.colors.white) + ) + } + } +} \ No newline at end of file diff --git a/core/src/main/java/com/sopt/core/designsystem/component/timepicker/NoostakTimePicker.kt b/core/src/main/java/com/sopt/core/designsystem/component/timepicker/NoostakTimePicker.kt new file mode 100644 index 0000000..fe94a91 --- /dev/null +++ b/core/src/main/java/com/sopt/core/designsystem/component/timepicker/NoostakTimePicker.kt @@ -0,0 +1,335 @@ +package com.sopt.core.designsystem.component.timepicker + +import androidx.compose.foundation.background +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 +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.sopt.core.R +import com.sopt.core.designsystem.theme.NoostakTheme +import com.sopt.core.designsystem.theme.NoostakTheme.colors +import com.sopt.core.extension.noRippleClickable +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.map + +@Composable +fun Picker2( + items: List, + state: PickerState = rememberPickerState(), + modifier: Modifier = Modifier, + startIndex: Int = 0, + visibleItemsCount: Int = 3, + cornerShape: RoundedCornerShape = RoundedCornerShape(0.dp), + isLeft: Boolean = true +) { + val visibleItemsMiddle = visibleItemsCount / 2 + val listScrollCount = Integer.MAX_VALUE + val listScrollMiddle = listScrollCount / 2 + val listStartIndex = listScrollMiddle - listScrollMiddle % items.size - visibleItemsMiddle + startIndex + fun getItem(index: Int) = items[index % items.size] + val listState = rememberLazyListState(initialFirstVisibleItemIndex = listStartIndex) + val flingBehavior = rememberSnapFlingBehavior(lazyListState = listState) + + LaunchedEffect(listState) { + snapshotFlow { listState.firstVisibleItemIndex } + .map { index -> getItem(index + visibleItemsMiddle) } + .distinctUntilChanged() + .collect { item -> state.selectedItem = item } + } + + Box( + modifier = modifier + .height(152.dp) + ) { + Column( + modifier = Modifier + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(32.dp)) + Box( + modifier = Modifier + .padding( + start = if (isLeft) 10.5.dp else 0.dp, + end = if (!isLeft) 10.5.dp else 0.dp + ) + .fillMaxWidth() + .height(1.dp) + .background(NoostakTheme.colors.gray200) + ) + Spacer(modifier = Modifier.height(19.dp)) + Box( + modifier = Modifier + .fillMaxWidth() + .height(48.dp) + .background( + color = NoostakTheme.colors.blue100, + shape = cornerShape + ) + .padding(horizontal = 20.dp, vertical = 7.dp) + ) + Spacer(modifier = Modifier.height(18.dp)) + Box( + modifier = Modifier + .padding( + start = if (isLeft) 10.5.dp else 0.dp, + end = if (!isLeft) 10.5.dp else 0.dp + ) + .fillMaxWidth() + .height(1.dp) + .background(NoostakTheme.colors.gray200) + ) + } + LazyColumn( + state = listState, + flingBehavior = flingBehavior, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + ) { + items(listScrollCount) { index -> + PickerItem( + text = getItem(index).padStart(2, '0'), + isFirstItem = index == listState.firstVisibleItemIndex, + isSecondItem = index == listState.firstVisibleItemIndex + 1 + ) + } + } + } +} + +@Composable +fun PickerItem(text: String, isFirstItem: Boolean, isSecondItem: Boolean) { + val style = when { + isSecondItem -> NoostakTheme.typography.h3SemiBold + else -> NoostakTheme.typography.h4SemiBold.copy(color = colors.gray500) + } + + val height = when { + isSecondItem -> 48.dp + isFirstItem -> 52.dp + else -> 52.dp + } + + val alignment = when { + isSecondItem -> Alignment.CenterVertically + isFirstItem -> Alignment.Top + else -> Alignment.Bottom + } + + Text( + text = text, + style = style, + modifier = Modifier + .height(height) + .wrapContentHeight(alignment) + ) +} + +@Composable +fun rememberPickerState() = remember { PickerState() } + +class PickerState { + var selectedItem by mutableStateOf("") +} + +@Composable +fun NoostakTimePicker( + onTimeSelected: (startHour: Int, startMinute: Int, endHour: Int, endMinute: Int) -> Unit +) { + val typography = NoostakTheme.typography + val colors = NoostakTheme.colors + + var selectedStartHour by remember { mutableStateOf(0) } + var selectedStartMinute by remember { mutableStateOf(0) } + var selectedEndHour by remember { mutableStateOf(18) } + var selectedEndMinute by remember { mutableStateOf(0) } + + var isStartTimeEditing by remember { mutableStateOf(true) } // 시작 시간이 기본 활성화 + var isEndTimeEditing by remember { mutableStateOf(false) } + + val values = remember { (0..23).map { it.toString() } } + val units = remember { (0..59).map { it.toString() } } + val valuesPickerState = rememberPickerState() + val unitsPickerState = rememberPickerState() + + LaunchedEffect(isStartTimeEditing, isEndTimeEditing) { + if (isStartTimeEditing) { + valuesPickerState.selectedItem = selectedStartHour.toString() + unitsPickerState.selectedItem = selectedStartMinute.toString() + } else if (isEndTimeEditing) { + valuesPickerState.selectedItem = selectedEndHour.toString() + unitsPickerState.selectedItem = selectedEndMinute.toString() + } + } + + Column( + modifier = Modifier + .fillMaxWidth() + .height(301.dp) + .background(colors.gray50, RoundedCornerShape(20.dp)) + .padding(top = 27.dp) + .noRippleClickable { + isStartTimeEditing = false + isEndTimeEditing = false + }, + horizontalAlignment = Alignment.CenterHorizontally + ) { + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 58.5.dp), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + TimeDisplay( + label = stringResource(R.string.text_noostak_time_picker_start), + hour = selectedStartHour, + minute = selectedStartMinute, + isSelected = isStartTimeEditing, + onClick = { + isStartTimeEditing = true + isEndTimeEditing = false + } + ) + + Text( + text = stringResource(R.string.text_noostak_time_picker_wave), + style = typography.h1Bold, + color = colors.gray700 + ) + + TimeDisplay( + label = stringResource(R.string.text_noostak_time_picker_end), + hour = selectedEndHour, + minute = selectedEndMinute, + isSelected = isEndTimeEditing, + onClick = { + isStartTimeEditing = false + isEndTimeEditing = true + } + ) + } + + Box( + modifier = Modifier + .padding(top = 9.dp, bottom = 30.dp, start = 33.5.dp, end = 33.5.dp) + .fillMaxWidth() + .height(3.dp) + .background(colors.gray100) + ) + + Row( + modifier = Modifier + .padding(bottom = 27.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Picker2( + state = valuesPickerState, + items = values, + visibleItemsCount = 3, + modifier = Modifier + .weight(1f) + .padding(start = 75.dp) + .fillMaxWidth(), + cornerShape = RoundedCornerShape( + topStart = 30.dp, + bottomStart = 30.dp, + topEnd = 0.dp, + bottomEnd = 0.dp + ) + ) + Picker2( + state = unitsPickerState, + items = units, + visibleItemsCount = 3, + modifier = Modifier + .weight(1f) + .padding(end = 75.dp) + .fillMaxWidth(), + cornerShape = RoundedCornerShape( + topStart = 0.dp, + bottomStart = 0.dp, + topEnd = 30.dp, + bottomEnd = 30.dp + ), + isLeft = false + ) + } + + LaunchedEffect(valuesPickerState.selectedItem, unitsPickerState.selectedItem) { + if (isStartTimeEditing) { + selectedStartHour = valuesPickerState.selectedItem.toIntOrNull() ?: 0 + selectedStartMinute = unitsPickerState.selectedItem.toIntOrNull() ?: 0 + } else if (isEndTimeEditing) { + selectedEndHour = valuesPickerState.selectedItem.toIntOrNull() ?: 0 + selectedEndMinute = unitsPickerState.selectedItem.toIntOrNull() ?: 0 + } + onTimeSelected(selectedStartHour, selectedStartMinute, selectedEndHour, selectedEndMinute) + } + } +} +@Composable +fun TimeDisplay( + label: String, + hour: Int, + minute: Int, + isSelected: Boolean, + onClick: () -> Unit +) { + val typography = NoostakTheme.typography + val colors = NoostakTheme.colors + + var isClicked by remember { mutableStateOf(isSelected) } + + val textColor = when { + isSelected -> colors.blue600 + isClicked -> colors.gray900 + else -> colors.gray500 + } + + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.noRippleClickable { + isClicked = true + onClick() + } + ) { + Text( + text = label, + style = typography.c3Regular, + color = colors.gray700 + ) + Text( + text = String.format("%02d:%02d", hour, minute), + style = typography.h1Bold.copy(color = textColor) + ) + } +} + + diff --git a/core/src/main/java/com/sopt/core/designsystem/component/toggle/NoostakSwitch.kt b/core/src/main/java/com/sopt/core/designsystem/component/toggle/NoostakSwitch.kt new file mode 100644 index 0000000..37ee54f --- /dev/null +++ b/core/src/main/java/com/sopt/core/designsystem/component/toggle/NoostakSwitch.kt @@ -0,0 +1,37 @@ +package com.sopt.core.designsystem.component.toggle + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.sopt.core.designsystem.theme.NoostakTheme +import com.sopt.core.extension.noRippleClickable + + +@Composable +fun NoostakSwitch(checked: Boolean, onCheckedChange: (Boolean) -> Unit) { + val backgroundColor = if (checked) NoostakTheme.colors.mint else NoostakTheme.colors.gray200 + val thumbColor = NoostakTheme.colors.white + + Box( + modifier = Modifier + .size(width = 51.dp, height = 31.dp) + .background(backgroundColor, RoundedCornerShape(50)) + .padding(horizontal = 2.dp, vertical = 2.dp) + .noRippleClickable { onCheckedChange(!checked) }, + contentAlignment = if (checked) Alignment.CenterEnd else Alignment.CenterStart + ) { + Box( + modifier = Modifier + .size(27.dp) + .background(thumbColor, CircleShape) + ) + } +} + diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index 8a87ec3..ef58ef6 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -6,8 +6,8 @@ plugins { } java { - sourceCompatibility = JavaVersion.VERSION_21 - targetCompatibility = JavaVersion.VERSION_21 + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 } dependencies { diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerRoute.kt b/presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerRoute.kt new file mode 100644 index 0000000..599d629 --- /dev/null +++ b/presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerRoute.kt @@ -0,0 +1,222 @@ +package com.sopt.presentation.calendar.calendarTimePicker + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.sopt.core.designsystem.component.button.NoostakBottomButton +import com.sopt.core.designsystem.component.checkbox.CircularCheckbox +import com.sopt.core.designsystem.component.progressbar.NoostakProgressBar +import com.sopt.core.designsystem.component.text.NoostakHeaderText +import com.sopt.core.designsystem.component.text.NoostakSubHeaderText +import com.sopt.core.designsystem.component.timepicker.NoostakTimePicker +import com.sopt.core.designsystem.component.topappbar.NoostakTopAppBar +import com.sopt.core.designsystem.theme.NoostakTheme +import com.sopt.core.extension.noRippleClickable +import com.sopt.presentation.R + +@Composable +fun CalendarTimePickerRoute( + appointmentName: String, + category: String, + time: Int, + isSingleDateMode: Boolean, + dates: List, + navigateToCheck: (String, String, Int, Boolean, List, String) -> Unit, + calendarTimePickerViewModel: CalendarTimePickerViewModel = hiltViewModel() +) { + LaunchedEffect(key1 = calendarTimePickerViewModel.sideEffects) { + calendarTimePickerViewModel.sideEffects.collect { sideEffect -> + when (sideEffect) { + is CalendarTimePickerSideEffect.NavigateToCheck -> { + navigateToCheck( + sideEffect.appointmentName, + sideEffect.category, + sideEffect.time, + sideEffect.isSingleDateMode, + sideEffect.dates, + sideEffect.selectTime + ) + } + } + } + } + + CalendarTimePickerScreen( + onButtonClick = calendarTimePickerViewModel::navigateToCalendarCheck, + appointmentName = appointmentName, + category = category, + time = time, + isSingleDateMode = isSingleDateMode, + dates = dates + ) +} + +@Composable +fun CalendarTimePickerScreen( + onButtonClick: (String, String, Int, Boolean, List, String) -> Unit, + appointmentName: String, + category: String, + time: Int, + isSingleDateMode: Boolean, + dates: List +) { + + val typography = NoostakTheme.typography + val colors = NoostakTheme.colors + var isChecked by remember { mutableStateOf(false) } + var showPicker by remember { mutableStateOf(true) } + + var selectedStartHour by remember { mutableStateOf(0) } + var selectedStartMinute by remember { mutableStateOf(0) } + var selectedEndHour by remember { mutableStateOf(18) } + var selectedEndMinute by remember { mutableStateOf(0) } + + Scaffold( + modifier = Modifier + .statusBarsPadding() + .navigationBarsPadding(), + topBar = { + NoostakTopAppBar( + title = stringResource(R.string.text_calendar_appointment), + isIconVisible = false + ) + } + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + .padding(dimensionResource(id = R.dimen.horizontal_padding)) + ) { + Spacer(modifier = Modifier.height(18.dp)) + + NoostakProgressBar(progressBar = listOf(false, false, true)) + + NoostakHeaderText(text = stringResource(R.string.text_calendar_appointment_time_choose)) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 18.dp) + .height(54.dp) + .border( + width = 0.5.dp, + color = colors.gray500, + shape = RoundedCornerShape(10.dp) + ) + .background( + color = if (isChecked) colors.gray50 else colors.white, + shape = RoundedCornerShape(10.dp) + ) + .noRippleClickable { + isChecked = !isChecked + showPicker = !isChecked + } + .padding(horizontal = 12.dp, vertical = 15.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.text_calendar_appointment_time_select), + modifier = Modifier.weight(1f), + style = typography.b4SemiBold, + color = colors.gray900 + ) + CircularCheckbox( + isChecked = isChecked, + onCheckedChange = { + isChecked = it + showPicker = !it + if (isChecked) { + selectedStartHour = null + selectedStartMinute = null + selectedEndHour = null + selectedEndMinute = null + } + } + + ) + } + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 32.dp, bottom = 9.dp), + verticalAlignment = Alignment.CenterVertically + ) { + NoostakSubHeaderText(stringResource(R.string.text_calendar_appointment_time_check), modifier = Modifier.weight(1f)) + Box( + modifier = Modifier.size(24.dp), + contentAlignment = Alignment.Center + ) { + Image( + imageVector = ImageVector.vectorResource( + id = if (isChecked) R.drawable.ic_calendar_off else R.drawable.ic_calendar_on + ), + contentDescription = null, + modifier = Modifier.fillMaxSize() + ) + } + } + + if (showPicker) { + NoostakTimePicker { startHour, startMinute, endHour, endMinute -> + selectedStartHour = startHour + selectedStartMinute = startMinute + selectedEndHour = endHour + selectedEndMinute = endMinute + } + } + + Spacer(modifier = Modifier.weight(1f)) + + NoostakBottomButton( + text = stringResource(R.string.text_calendar_appointment_next), + onButtonClick = { + val selectTime = if (isChecked) null else "${selectedStartHour?.toString()?.padStart(2, '0')}:${selectedStartMinute?.toString()?.padStart(2, '0')} ~ ${selectedEndHour?.toString()?.padStart(2, '0')}:${selectedEndMinute?.toString()?.padStart(2, '0')}" + onButtonClick( + appointmentName, + category, + time, + isSingleDateMode, + dates, + selectTime ?: "null" + ) + }, + deactivateColor = NoostakTheme.colors.gray500, + activateColor = NoostakTheme.colors.gray900, + modifier = Modifier.padding(bottom = 16.dp) + ) + } + } +} + + + diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerViewModel.kt b/presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerViewModel.kt new file mode 100644 index 0000000..108bad5 --- /dev/null +++ b/presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerViewModel.kt @@ -0,0 +1,24 @@ +package com.sopt.presentation.calendar.calendarTimePicker + +import com.sopt.core.util.BaseViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class CalendarTimePickerViewModel @Inject constructor() : BaseViewModel() { + + fun navigateToCalendarCheck(appointmentName: String, category: String, time: Int, isSingleDateMode: Boolean, dates: List, selectTime: String) { + emitSideEffect(CalendarTimePickerSideEffect.NavigateToCheck(appointmentName, category, time, isSingleDateMode, dates, selectTime)) + } +} + +sealed class CalendarTimePickerSideEffect { + data class NavigateToCheck( + val appointmentName: String, + val category: String, + val time: Int, + val isSingleDateMode: Boolean, + val dates: List, + val selectTime: String + ) : CalendarTimePickerSideEffect() +} diff --git a/presentation/src/main/res/drawable/ic_calendar_off.xml b/presentation/src/main/res/drawable/ic_calendar_off.xml new file mode 100644 index 0000000..b75ab87 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_calendar_off.xml @@ -0,0 +1,9 @@ + + + diff --git a/presentation/src/main/res/drawable/ic_calendar_on.xml b/presentation/src/main/res/drawable/ic_calendar_on.xml new file mode 100644 index 0000000..b30644a --- /dev/null +++ b/presentation/src/main/res/drawable/ic_calendar_on.xml @@ -0,0 +1,9 @@ + + + From 505b32ae3ef1cfea5c4470e2b934640f87b2be7e Mon Sep 17 00:00:00 2001 From: twogarlic Date: Wed, 15 Jan 2025 23:36:13 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[REFACTOR]=20ktlint=20=ED=8F=AC=EB=A7=B7=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/designsystem/component/calendar/NoostakCalendar.kt | 2 +- .../core/designsystem/component/checkbox/CircularCheckbox.kt | 3 +-- .../designsystem/component/timepicker/NoostakTimePicker.kt | 4 +--- .../sopt/core/designsystem/component/toggle/NoostakSwitch.kt | 2 -- .../calendar/calendarTimePicker/CalendarTimePickerRoute.kt | 4 ---- .../presentation/calendar/navigation/CalendarNavigation.kt | 2 +- 6 files changed, 4 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt b/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt index f409558..604fc24 100644 --- a/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt +++ b/core/src/main/java/com/sopt/core/designsystem/component/calendar/NoostakCalendar.kt @@ -39,7 +39,7 @@ fun NoostakCalendar( start: String, end: String, isSingleDate: Boolean, - // isRangeSelected: (String, String) -> Unit, + // isRangeSelected: (String, String) -> Unit, isRangeSelected: (List) -> Unit, modifier: Modifier = Modifier, days: List diff --git a/core/src/main/java/com/sopt/core/designsystem/component/checkbox/CircularCheckbox.kt b/core/src/main/java/com/sopt/core/designsystem/component/checkbox/CircularCheckbox.kt index 9d3008d..4aed895 100644 --- a/core/src/main/java/com/sopt/core/designsystem/component/checkbox/CircularCheckbox.kt +++ b/core/src/main/java/com/sopt/core/designsystem/component/checkbox/CircularCheckbox.kt @@ -1,6 +1,5 @@ package com.sopt.core.designsystem.component.checkbox -import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable @@ -47,4 +46,4 @@ fun CircularCheckbox( ) } } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/sopt/core/designsystem/component/timepicker/NoostakTimePicker.kt b/core/src/main/java/com/sopt/core/designsystem/component/timepicker/NoostakTimePicker.kt index fe94a91..557bb86 100644 --- a/core/src/main/java/com/sopt/core/designsystem/component/timepicker/NoostakTimePicker.kt +++ b/core/src/main/java/com/sopt/core/designsystem/component/timepicker/NoostakTimePicker.kt @@ -197,7 +197,6 @@ fun NoostakTimePicker( }, horizontalAlignment = Alignment.CenterHorizontally ) { - Row( modifier = Modifier .fillMaxWidth() @@ -294,6 +293,7 @@ fun NoostakTimePicker( } } } + @Composable fun TimeDisplay( label: String, @@ -331,5 +331,3 @@ fun TimeDisplay( ) } } - - diff --git a/core/src/main/java/com/sopt/core/designsystem/component/toggle/NoostakSwitch.kt b/core/src/main/java/com/sopt/core/designsystem/component/toggle/NoostakSwitch.kt index 37ee54f..a4356ef 100644 --- a/core/src/main/java/com/sopt/core/designsystem/component/toggle/NoostakSwitch.kt +++ b/core/src/main/java/com/sopt/core/designsystem/component/toggle/NoostakSwitch.kt @@ -13,7 +13,6 @@ import androidx.compose.ui.unit.dp import com.sopt.core.designsystem.theme.NoostakTheme import com.sopt.core.extension.noRippleClickable - @Composable fun NoostakSwitch(checked: Boolean, onCheckedChange: (Boolean) -> Unit) { val backgroundColor = if (checked) NoostakTheme.colors.mint else NoostakTheme.colors.gray200 @@ -34,4 +33,3 @@ fun NoostakSwitch(checked: Boolean, onCheckedChange: (Boolean) -> Unit) { ) } } - diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerRoute.kt b/presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerRoute.kt index 599d629..131dc61 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/calendarTimePicker/CalendarTimePickerRoute.kt @@ -88,7 +88,6 @@ fun CalendarTimePickerScreen( isSingleDateMode: Boolean, dates: List ) { - val typography = NoostakTheme.typography val colors = NoostakTheme.colors var isChecked by remember { mutableStateOf(false) } @@ -217,6 +216,3 @@ fun CalendarTimePickerScreen( } } } - - - diff --git a/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt b/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt index 565e618..c046fb1 100644 --- a/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt +++ b/presentation/src/main/java/com/sopt/presentation/calendar/navigation/CalendarNavigation.kt @@ -67,7 +67,7 @@ fun NavController.navigateCalendarCheck( navOptions: NavOptions? = null ) { navigate( - route = CalendarCheck(appointmentName = appointmentName, category = category, time = time, isSingleDateMode = isSingleDateMode, dates = dates, selectTime=selectTime), + route = CalendarCheck(appointmentName = appointmentName, category = category, time = time, isSingleDateMode = isSingleDateMode, dates = dates, selectTime = selectTime), navOptions = navOptions ) }