From a77c022e976ff7b73aba8bd9cb73a0b9a458a5d0 Mon Sep 17 00:00:00 2001 From: Lee Gaeul <91470334+gaeulzzang@users.noreply.github.com> Date: Sat, 12 Apr 2025 00:51:44 +0900 Subject: [PATCH 1/6] =?UTF-8?q?#5=20[FEAT]=20=EC=97=B0=EC=86=8D=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=84=A0=ED=83=9D=EB=90=9C=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=EC=9D=B4=20duration=20=EC=9D=B4=EC=83=81=EC=9D=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../appointmentCheck/AppointmentCheckRoute.kt | 17 +++---- .../AppointmentCheckViewModel.kt | 46 +++++++++++++++---- presentation/src/main/res/values/strings.xml | 3 ++ 3 files changed, 48 insertions(+), 18 deletions(-) diff --git a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt index c2b5faed..c0491be6 100644 --- a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt @@ -99,7 +99,7 @@ fun AppointmentCheckRoute( ) is AppointmentCheckSideEffect.ShowSnackBar -> onShowFailureSnackBar( - context.getString(sideEffect.message) + context.getString(sideEffect.message, duration / 60) ) } } @@ -112,13 +112,14 @@ fun AppointmentCheckRoute( onSelectedDataChange = { selectedData = it }, onBackButtonClick = appointmentCheckViewModel::navigateToGroupDetail, onConfirmButtonClick = { - // TODO: selectedData가 duration 이하인지 체크하는 로직 추가 - appointmentCheckViewModel.postTimeTable( - groupId, - appointmentId, - appointmentName, - selectedData - ) + if (appointmentCheckViewModel.isSelectedDataValid(duration/60, selectedData)) { + appointmentCheckViewModel.postTimeTable( + groupId, + appointmentId, + appointmentName, + selectedData + ) + } }, snackBarHostState = snackBarHostState, snackBarVisible = snackBarVisible diff --git a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt index 034bd5b0..6c7455ed 100644 --- a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt @@ -6,13 +6,17 @@ import com.sopt.core.type.DialogType import com.sopt.core.util.BaseViewModel import com.sopt.domain.entity.TimeEntity import com.sopt.domain.repository.AppointmentConfirmRepository +import com.sopt.presentation.R import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import timber.log.Timber import java.io.IOException +import java.time.Duration +import java.time.LocalDateTime import javax.inject.Inject @HiltViewModel @@ -71,6 +75,38 @@ class AppointmentCheckViewModel @Inject constructor( } } + fun isSelectedDataValid(duration: Long, selectedDate: List): Boolean { + if (selectedDate.isEmpty()) { + emitSideEffect(AppointmentCheckSideEffect.ShowSnackBar(R.string.sb_appointment_check_invalid)) + return false + } + + val sorted = selectedDate.sortedBy { LocalDateTime.parse(it.startTime) } + var currentStart = LocalDateTime.parse(sorted.first().startTime) + var currentEnd = LocalDateTime.parse(sorted.first().endTime) + + for (i in 1 until sorted.size) { + val nextStart = LocalDateTime.parse(sorted[i].startTime) + val nextEnd = LocalDateTime.parse(sorted[i].endTime) + + if (nextStart == currentEnd) { + currentEnd = nextEnd + } else { + val blockDurationInHours = Duration.between(currentStart, currentEnd).toHours() + if (blockDurationInHours >= duration) return true + + currentStart = nextStart + currentEnd = nextEnd + } + } + + val finalDurationInHours = Duration.between(currentStart, currentEnd).toHours() + if (finalDurationInHours >= duration) return true + + emitSideEffect(AppointmentCheckSideEffect.ShowSnackBar(R.string.sb_appointment_check_invalid)) + return false + } + fun showErrorDialog(show: Boolean, dialogType: DialogType) { _showErrorDialog.update { it.copy(first = show, second = dialogType) } } @@ -79,16 +115,6 @@ class AppointmentCheckViewModel @Inject constructor( emitSideEffect(AppointmentCheckSideEffect.NavigateUp) } - fun navigateToAppointment(groupId: Long, appointmentId: Long, appointmentName: String) { - emitSideEffect( - AppointmentCheckSideEffect.NavigateToAppointment( - groupId, - appointmentId, - appointmentName - ) - ) - } - fun navigateToGroupDetail(groupId: Long) { emitSideEffect(AppointmentCheckSideEffect.NavigateToGroupDetail(groupId)) } diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 38c6e1f1..123a0f1b 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -168,6 +168,9 @@ %1$s ~ %2$s ", " + + %1$d시간 이상 연속으로 선택해주세요 + %1$s ~ %2$s From c3edbf22592b7c6b161821fdb480283cffe62fc0 Mon Sep 17 00:00:00 2001 From: Lee Gaeul <91470334+gaeulzzang@users.noreply.github.com> Date: Sun, 13 Apr 2025 11:27:53 +0900 Subject: [PATCH 2/6] =?UTF-8?q?#5=20[MOD]=20=EB=AA=A8=EB=93=A0=20=EB=B8=94?= =?UTF-8?q?=EB=9F=AD=EC=9D=B4=20duration=20=EC=9D=B4=EC=83=81=EC=9D=B4?= =?UTF-8?q?=EC=96=B4=EC=95=BC=20true=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../appointmentCheck/AppointmentCheckRoute.kt | 14 +++++++++++-- .../AppointmentCheckViewModel.kt | 21 +++++++++++-------- presentation/src/main/res/values/strings.xml | 2 +- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt index c0491be6..2100341d 100644 --- a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt @@ -110,6 +110,7 @@ fun AppointmentCheckRoute( appointmentName = appointmentName, availablePeriods = rememberedAvailablePeriods, onSelectedDataChange = { selectedData = it }, + duration = duration, onBackButtonClick = appointmentCheckViewModel::navigateToGroupDetail, onConfirmButtonClick = { if (appointmentCheckViewModel.isSelectedDataValid(duration/60, selectedData)) { @@ -150,6 +151,7 @@ fun AppointmentCheckScreen( appointmentName: String, availablePeriods: List, onSelectedDataChange: (List) -> Unit = {}, + duration: Long, onBackButtonClick: (Long) -> Unit, onConfirmButtonClick: () -> Unit, snackBarHostState: SnackbarHostState, @@ -200,12 +202,19 @@ fun AppointmentCheckScreen( ) { Text( modifier = Modifier - .padding(top = 11.dp, start = 6.dp, bottom = 16.dp), - text = stringResource(R.string.title_appointment_check), + .padding(top = 11.dp, start = 6.dp), + text = stringResource(R.string.title_appointment_check, duration/60), color = NoostakTheme.colors.black, style = NoostakTheme.typography.h4Bold, textAlign = TextAlign.Start ) + Text( + modifier = Modifier.padding(start = 6.dp, bottom = 16.dp), + text = "어렵다면 '가능한 시간이 없어요'를 선택해주세요", + color = NoostakTheme.colors.gray900, + style = NoostakTheme.typography.c3Regular, + textAlign = TextAlign.Start + ) NoostakEditableTimeTable( availablePeriods = availablePeriods, modifier = Modifier.fillMaxWidth() @@ -252,6 +261,7 @@ fun PreviewAppointmentConfirmScreen() { endTime = "2024-09-07T18:00:00" ) ), + duration = 360, onBackButtonClick = {}, onConfirmButtonClick = {}, snackBarHostState = SnackbarHostState(), diff --git a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt index 6c7455ed..7dd5251f 100644 --- a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import timber.log.Timber import java.io.IOException import java.time.Duration import java.time.LocalDateTime @@ -92,19 +91,23 @@ class AppointmentCheckViewModel @Inject constructor( if (nextStart == currentEnd) { currentEnd = nextEnd } else { - val blockDurationInHours = Duration.between(currentStart, currentEnd).toHours() - if (blockDurationInHours >= duration) return true - + // 블록이 끊기면 검사 + val blockDuration = Duration.between(currentStart, currentEnd).toHours() + if (blockDuration < duration) { + emitSideEffect(AppointmentCheckSideEffect.ShowSnackBar(R.string.sb_appointment_check_invalid)) + return false + } currentStart = nextStart currentEnd = nextEnd } } + val finalBlockDuration = Duration.between(currentStart, currentEnd).toHours() + if (finalBlockDuration < duration) { + emitSideEffect(AppointmentCheckSideEffect.ShowSnackBar(R.string.sb_appointment_check_invalid)) + return false + } - val finalDurationInHours = Duration.between(currentStart, currentEnd).toHours() - if (finalDurationInHours >= duration) return true - - emitSideEffect(AppointmentCheckSideEffect.ShowSnackBar(R.string.sb_appointment_check_invalid)) - return false + return true } fun showErrorDialog(show: Boolean, dialogType: DialogType) { diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 123a0f1b..f92bcac4 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -89,7 +89,7 @@ 가능한 친구 %1$s 불가능한 친구 %1$s 약속 확정하기 - 가능한 시간을\n모두 선택해주세요 + %1$d시간 이상 연속으로 선택해주세요 확인 시간 입력에 실패했어요 추천 시간 From 5d2b34de4970e58cd3f4dc4da63bad7246d3efe6 Mon Sep 17 00:00:00 2001 From: Lee Gaeul <91470334+gaeulzzang@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:22:46 +0900 Subject: [PATCH 3/6] =?UTF-8?q?#5=20[FEAT]=20=EA=B0=80=EB=8A=A5=ED=95=9C?= =?UTF-8?q?=20=EC=8B=9C=EA=B0=84=EB=8C=80=20=EC=97=86=EC=96=B4=EC=9A=94=20?= =?UTF-8?q?CTA=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/NoostakEditableTimeTable.kt | 3 +- .../appointmentCheck/AppointmentCheckRoute.kt | 75 ++++++++++++++++--- .../AppointmentCheckViewModel.kt | 6 +- presentation/src/main/res/values/strings.xml | 3 +- 4 files changed, 72 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/com/sopt/core/designsystem/component/timetable/NoostakEditableTimeTable.kt b/core/src/main/java/com/sopt/core/designsystem/component/timetable/NoostakEditableTimeTable.kt index 8de5556a..5423421b 100644 --- a/core/src/main/java/com/sopt/core/designsystem/component/timetable/NoostakEditableTimeTable.kt +++ b/core/src/main/java/com/sopt/core/designsystem/component/timetable/NoostakEditableTimeTable.kt @@ -35,6 +35,7 @@ import com.sopt.domain.entity.TimeEntity fun NoostakEditableTimeTable( availablePeriods: List, modifier: Modifier = Modifier, + isChecked: Boolean = false, onSelectedTimesChanged: (List) -> Unit ) { val days = availablePeriods.size @@ -43,7 +44,7 @@ fun NoostakEditableTimeTable( availablePeriods.first().startTime, availablePeriods.first().endTime ) - val selectedCells = remember { mutableStateListOf>() } + val selectedCells = remember(key1 = isChecked) { mutableStateListOf>() } LazyColumn( modifier = modifier diff --git a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt index 2100341d..87442fc3 100644 --- a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt @@ -3,13 +3,20 @@ package com.sopt.presentation.appointment.appointmentCheck import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically +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.statusBarsPadding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -34,6 +41,7 @@ import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sopt.core.designsystem.component.button.NoostakBottomButton +import com.sopt.core.designsystem.component.checkbox.CircularCheckbox import com.sopt.core.designsystem.component.dialog.NoostakDialog import com.sopt.core.designsystem.component.snackbar.NoostakSnackBar import com.sopt.core.designsystem.component.snackbar.SNACK_BAR_DURATION @@ -41,6 +49,7 @@ import com.sopt.core.designsystem.component.timetable.NoostakEditableTimeTable import com.sopt.core.designsystem.component.topappbar.NoostakTopAppBar import com.sopt.core.designsystem.theme.NoostakAndroidTheme import com.sopt.core.designsystem.theme.NoostakTheme +import com.sopt.core.extension.noRippleClickable import com.sopt.domain.entity.TimeEntity import com.sopt.presentation.R import kotlinx.coroutines.delay @@ -62,6 +71,7 @@ fun AppointmentCheckRoute( val context = LocalContext.current val showErrorDialog by appointmentCheckViewModel.showErrorDialog.collectAsStateWithLifecycle() var selectedData by remember { mutableStateOf(emptyList()) } + var isChecked by remember { mutableStateOf(false) } val rememberedAvailablePeriods = remember { availablePeriods } val snackBarHostState = remember { SnackbarHostState() } val coroutineScope = rememberCoroutineScope() @@ -109,11 +119,25 @@ fun AppointmentCheckRoute( groupId = groupId, appointmentName = appointmentName, availablePeriods = rememberedAvailablePeriods, - onSelectedDataChange = { selectedData = it }, + onSelectedDataChange = { + selectedData = it + if (it.isNotEmpty()) isChecked = false + }, duration = duration, + isChecked = isChecked, + onCheckedChange = { + isChecked = it + if (isChecked) selectedData = emptyList() + Timber.d("isChecked: $isChecked selectedData: $selectedData") + }, onBackButtonClick = appointmentCheckViewModel::navigateToGroupDetail, onConfirmButtonClick = { - if (appointmentCheckViewModel.isSelectedDataValid(duration/60, selectedData)) { + if (appointmentCheckViewModel.isSelectedDataValid( + duration / 60, + selectedData, + isChecked + ) + ) { appointmentCheckViewModel.postTimeTable( groupId, appointmentId, @@ -152,6 +176,8 @@ fun AppointmentCheckScreen( availablePeriods: List, onSelectedDataChange: (List) -> Unit = {}, duration: Long, + isChecked: Boolean = false, + onCheckedChange: (Boolean) -> Unit = {}, onBackButtonClick: (Long) -> Unit, onConfirmButtonClick: () -> Unit, snackBarHostState: SnackbarHostState, @@ -202,22 +228,47 @@ fun AppointmentCheckScreen( ) { Text( modifier = Modifier - .padding(top = 11.dp, start = 6.dp), - text = stringResource(R.string.title_appointment_check, duration/60), + .padding(vertical = 12.dp), + text = stringResource(R.string.title_appointment_check, duration / 60), color = NoostakTheme.colors.black, style = NoostakTheme.typography.h4Bold, textAlign = TextAlign.Start ) - Text( - modifier = Modifier.padding(start = 6.dp, bottom = 16.dp), - text = "어렵다면 '가능한 시간이 없어요'를 선택해주세요", - color = NoostakTheme.colors.gray900, - style = NoostakTheme.typography.c3Regular, - textAlign = TextAlign.Start - ) + Row( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .border( + width = 0.5.dp, + color = NoostakTheme.colors.gray200, + shape = RoundedCornerShape(10.dp) + ) + .background( + color = if (isChecked) NoostakTheme.colors.gray50 else NoostakTheme.colors.white, + shape = RoundedCornerShape(10.dp) + ) + .noRippleClickable { onCheckedChange(!isChecked) } + .padding(12.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.cb_appointment_check_impossible), + modifier = Modifier.weight(1f), + style = NoostakTheme.typography.b5Regular, + color = NoostakTheme.colors.gray900 + ) + CircularCheckbox( + isChecked = isChecked, + onCheckedChange = { + onCheckedChange(it) + } + ) + } + Spacer(modifier = Modifier.height(16.dp)) NoostakEditableTimeTable( availablePeriods = availablePeriods, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + isChecked = isChecked ) { onSelectedDataChange(it) Timber.d("selectedData: $it") diff --git a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt index 7dd5251f..aae37fb0 100644 --- a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt +++ b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckViewModel.kt @@ -74,7 +74,11 @@ class AppointmentCheckViewModel @Inject constructor( } } - fun isSelectedDataValid(duration: Long, selectedDate: List): Boolean { + fun isSelectedDataValid(duration: Long, selectedDate: List, isChecked: Boolean): Boolean { + if (isChecked) { + return true + } + if (selectedDate.isEmpty()) { emitSideEffect(AppointmentCheckSideEffect.ShowSnackBar(R.string.sb_appointment_check_invalid)) return false diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index f92bcac4..ec38df6d 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -89,7 +89,7 @@ 가능한 친구 %1$s 불가능한 친구 %1$s 약속 확정하기 - %1$d시간 이상 연속으로 선택해주세요 + %1$d시간 이상\n가능한 시간대를 모두 선택해주세요 확인 시간 입력에 실패했어요 추천 시간 @@ -170,6 +170,7 @@ %1$d시간 이상 연속으로 선택해주세요 + 연속 가능한 시간이 없어요 %1$s ~ %2$s From 9a8f312582280b3d0f670a5a4e603621006b4acd Mon Sep 17 00:00:00 2001 From: Lee Gaeul <91470334+gaeulzzang@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:44:50 +0900 Subject: [PATCH 4/6] =?UTF-8?q?#5=20[FEAT]=20=EC=B2=B4=ED=81=AC=EB=B0=95?= =?UTF-8?q?=EC=8A=A4=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/checkbox/NoostakCheckbox.kt | 74 +++++++++++++++++++ .../appointmentCheck/AppointmentCheckRoute.kt | 42 +++-------- 2 files changed, 86 insertions(+), 30 deletions(-) create mode 100644 core/src/main/java/com/sopt/core/designsystem/component/checkbox/NoostakCheckbox.kt diff --git a/core/src/main/java/com/sopt/core/designsystem/component/checkbox/NoostakCheckbox.kt b/core/src/main/java/com/sopt/core/designsystem/component/checkbox/NoostakCheckbox.kt new file mode 100644 index 00000000..e42707a0 --- /dev/null +++ b/core/src/main/java/com/sopt/core/designsystem/component/checkbox/NoostakCheckbox.kt @@ -0,0 +1,74 @@ +package com.sopt.core.designsystem.component.checkbox + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.sopt.core.designsystem.theme.NoostakAndroidTheme +import com.sopt.core.designsystem.theme.NoostakTheme +import com.sopt.core.extension.noRippleClickable + +@Composable +fun NoostakCheckbox( + modifier: Modifier = Modifier, + text: String = "", + isChecked: Boolean = false, + onCheckedChange: (Boolean) -> Unit = {}, + textStyle: TextStyle = NoostakTheme.typography.b4SemiBold, + borderColor: Color = NoostakTheme.colors.gray500, + backgroundColorChecked: Color = NoostakTheme.colors.gray50, + backgroundColorUnchecked: Color = NoostakTheme.colors.white, + paddingValues: PaddingValues = PaddingValues(horizontal = 12.dp, vertical = 15.dp) +) { + Row( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight() + .border( + width = 0.5.dp, + color = borderColor, + shape = RoundedCornerShape(10.dp) + ) + .background( + color = if (isChecked) backgroundColorChecked else backgroundColorUnchecked, + shape = RoundedCornerShape(10.dp) + ) + .noRippleClickable { onCheckedChange(!isChecked) } + .padding(paddingValues), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = text, + modifier = Modifier.weight(1f), + style = textStyle, + color = NoostakTheme.colors.gray900 + ) + CircularCheckbox( + isChecked = isChecked, + onCheckedChange = { onCheckedChange(it) } + ) + } +} + +@Preview(showBackground = true) +@Composable +fun NoostakCheckboxPreview() { + NoostakAndroidTheme { + NoostakCheckbox( + isChecked = true, + text = "체크박스블라블라" + ) + } +} diff --git a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt index 87442fc3..f61e216b 100644 --- a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt @@ -7,6 +7,7 @@ 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.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -42,6 +43,7 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sopt.core.designsystem.component.button.NoostakBottomButton import com.sopt.core.designsystem.component.checkbox.CircularCheckbox +import com.sopt.core.designsystem.component.checkbox.NoostakCheckbox import com.sopt.core.designsystem.component.dialog.NoostakDialog import com.sopt.core.designsystem.component.snackbar.NoostakSnackBar import com.sopt.core.designsystem.component.snackbar.SNACK_BAR_DURATION @@ -234,36 +236,16 @@ fun AppointmentCheckScreen( style = NoostakTheme.typography.h4Bold, textAlign = TextAlign.Start ) - Row( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight() - .border( - width = 0.5.dp, - color = NoostakTheme.colors.gray200, - shape = RoundedCornerShape(10.dp) - ) - .background( - color = if (isChecked) NoostakTheme.colors.gray50 else NoostakTheme.colors.white, - shape = RoundedCornerShape(10.dp) - ) - .noRippleClickable { onCheckedChange(!isChecked) } - .padding(12.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = stringResource(R.string.cb_appointment_check_impossible), - modifier = Modifier.weight(1f), - style = NoostakTheme.typography.b5Regular, - color = NoostakTheme.colors.gray900 - ) - CircularCheckbox( - isChecked = isChecked, - onCheckedChange = { - onCheckedChange(it) - } - ) - } + NoostakCheckbox( + text = stringResource(R.string.cb_appointment_check_impossible), + isChecked = isChecked, + onCheckedChange = { + onCheckedChange(it) + }, + textStyle = NoostakTheme.typography.b5Regular, + borderColor = NoostakTheme.colors.gray200, + paddingValues = PaddingValues(horizontal = 12.dp, vertical = 14.dp) + ) Spacer(modifier = Modifier.height(16.dp)) NoostakEditableTimeTable( availablePeriods = availablePeriods, From 321ea6ad574066df74c9b6925c763cd0ac003144 Mon Sep 17 00:00:00 2001 From: Lee Gaeul <91470334+gaeulzzang@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:51:30 +0900 Subject: [PATCH 5/6] =?UTF-8?q?#5=20[FEAT]=20=EC=95=BD=EC=86=8D=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EC=B2=B4=ED=81=AC=EB=B0=95=EC=8A=A4=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../appointmentCheck/AppointmentCheckRoute.kt | 8 --- .../AppointmentCreateTimePickerRoute.kt | 59 +++++-------------- 2 files changed, 14 insertions(+), 53 deletions(-) diff --git a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt index f61e216b..f98495e4 100644 --- a/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/appointment/appointmentCheck/AppointmentCheckRoute.kt @@ -3,12 +3,9 @@ package com.sopt.presentation.appointment.appointmentCheck import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically -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.PaddingValues -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -16,8 +13,6 @@ 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.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState @@ -42,7 +37,6 @@ import androidx.compose.ui.zIndex import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.sopt.core.designsystem.component.button.NoostakBottomButton -import com.sopt.core.designsystem.component.checkbox.CircularCheckbox import com.sopt.core.designsystem.component.checkbox.NoostakCheckbox import com.sopt.core.designsystem.component.dialog.NoostakDialog import com.sopt.core.designsystem.component.snackbar.NoostakSnackBar @@ -51,7 +45,6 @@ import com.sopt.core.designsystem.component.timetable.NoostakEditableTimeTable import com.sopt.core.designsystem.component.topappbar.NoostakTopAppBar import com.sopt.core.designsystem.theme.NoostakAndroidTheme import com.sopt.core.designsystem.theme.NoostakTheme -import com.sopt.core.extension.noRippleClickable import com.sopt.domain.entity.TimeEntity import com.sopt.presentation.R import kotlinx.coroutines.delay @@ -130,7 +123,6 @@ fun AppointmentCheckRoute( onCheckedChange = { isChecked = it if (isChecked) selectedData = emptyList() - Timber.d("isChecked: $isChecked selectedData: $selectedData") }, onBackButtonClick = appointmentCheckViewModel::navigateToGroupDetail, onConfirmButtonClick = { diff --git a/presentation/src/main/java/com/sopt/presentation/appointmentCreate/appointmentCreateTimePicker/AppointmentCreateTimePickerRoute.kt b/presentation/src/main/java/com/sopt/presentation/appointmentCreate/appointmentCreateTimePicker/AppointmentCreateTimePickerRoute.kt index a88de1db..4ab6a248 100644 --- a/presentation/src/main/java/com/sopt/presentation/appointmentCreate/appointmentCreateTimePicker/AppointmentCreateTimePickerRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/appointmentCreate/appointmentCreateTimePicker/AppointmentCreateTimePickerRoute.kt @@ -4,8 +4,6 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically 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 @@ -17,11 +15,9 @@ 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.SnackbarHost import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState @@ -41,7 +37,7 @@ import androidx.compose.ui.tooling.preview.Preview 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.checkbox.NoostakCheckbox import com.sopt.core.designsystem.component.progressbar.NoostakProgressBar import com.sopt.core.designsystem.component.snackbar.NoostakSnackBar import com.sopt.core.designsystem.component.snackbar.SNACK_BAR_DURATION @@ -51,7 +47,6 @@ import com.sopt.core.designsystem.component.timepicker.NoostakTimePicker import com.sopt.core.designsystem.component.topappbar.NoostakTopAppBar import com.sopt.core.designsystem.theme.NoostakAndroidTheme import com.sopt.core.designsystem.theme.NoostakTheme -import com.sopt.core.extension.noRippleClickable import com.sopt.presentation.R import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -190,45 +185,19 @@ fun AppointmentCreateTimePickerScreen( 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 = NoostakTheme.colors.gray500, - shape = RoundedCornerShape(10.dp) - ) - .background( - color = if (isChecked) NoostakTheme.colors.gray50 else NoostakTheme.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 = NoostakTheme.typography.b4SemiBold, - color = NoostakTheme.colors.gray900 - ) - CircularCheckbox( - isChecked = isChecked, - onCheckedChange = { - isChecked = it - showPicker = !it - if (isChecked) { - selectedStartHour = null - selectedEndHour = null - } + NoostakCheckbox( + modifier = Modifier.padding(top = 18.dp), + text = stringResource(R.string.text_calendar_appointment_time_select), + isChecked = isChecked, + onCheckedChange = { + isChecked = it + showPicker = !isChecked + if (isChecked) { + selectedStartHour = null + selectedEndHour = null } - ) - } + } + ) Row( modifier = Modifier .fillMaxWidth() @@ -283,7 +252,7 @@ fun AppointmentCreateTimePickerScreen( null } else { "${ - selectedStartHour?.toString()?.padStart(2, '0') + selectedStartHour?.toString()?.padStart(2, '0') }:00 ~ ${adjustedEndHour.toString().padStart(2, '0')}:00" } onButtonClick( From 8f3dc4856f80de40f37574ceebe0c6e3e0101b8f Mon Sep 17 00:00:00 2001 From: Lee Gaeul <91470334+gaeulzzang@users.noreply.github.com> Date: Wed, 23 Apr 2025 12:52:41 +0900 Subject: [PATCH 6/6] =?UTF-8?q?#5=20[REFACTOR]=20ktlint=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../AppointmentCreateTimePickerRoute.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/presentation/src/main/java/com/sopt/presentation/appointmentCreate/appointmentCreateTimePicker/AppointmentCreateTimePickerRoute.kt b/presentation/src/main/java/com/sopt/presentation/appointmentCreate/appointmentCreateTimePicker/AppointmentCreateTimePickerRoute.kt index 4ab6a248..d63a98c8 100644 --- a/presentation/src/main/java/com/sopt/presentation/appointmentCreate/appointmentCreateTimePicker/AppointmentCreateTimePickerRoute.kt +++ b/presentation/src/main/java/com/sopt/presentation/appointmentCreate/appointmentCreateTimePicker/AppointmentCreateTimePickerRoute.kt @@ -252,7 +252,7 @@ fun AppointmentCreateTimePickerScreen( null } else { "${ - selectedStartHour?.toString()?.padStart(2, '0') + selectedStartHour?.toString()?.padStart(2, '0') }:00 ~ ${adjustedEndHour.toString().padStart(2, '0')}:00" } onButtonClick(