diff --git a/composeApp/src/androidMain/kotlin/com/chukchukhaksa/mobile/preview/designsystem/Container.kt b/composeApp/src/androidMain/kotlin/com/chukchukhaksa/mobile/preview/designsystem/Container.kt index 1293f1977..5e0bdcf7d 100644 --- a/composeApp/src/androidMain/kotlin/com/chukchukhaksa/mobile/preview/designsystem/Container.kt +++ b/composeApp/src/androidMain/kotlin/com/chukchukhaksa/mobile/preview/designsystem/Container.kt @@ -5,7 +5,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import com.chukchukhaksa.mobile.common.designsystem.component.container.SuwikiBoardContainer import com.chukchukhaksa.mobile.common.designsystem.component.container.CchEditContainer -import com.chukchukhaksa.mobile.common.designsystem.component.container.SuwikiSelectionContainer +import com.chukchukhaksa.mobile.common.designsystem.component.container.CchSelectionButton import com.chukchukhaksa.mobile.common.designsystem.theme.SuwikiTheme @Composable @@ -39,6 +39,6 @@ fun TimetableEditContainerPreview() { @Composable fun SuwikiSelectionContainerPreview() { SuwikiTheme { - SuwikiSelectionContainer(title = "title") + CchSelectionButton(title = "title") } } diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/App.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/App.kt index dda8abcca..9fb3a8ede 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/App.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/App.kt @@ -58,6 +58,7 @@ fun App( timetableNavGraph( padding = innerPadding, popBackStack = navigator::popBackStackIfNotHome, + navigateTimetableNameInput = navigator::navigateTimetableNameInput, navigateTimetableEditor = navigator::navigateTimetableEditor, navigateTimetableList = navigator::navigateTimetableList, navigateOpenLecture = navigator::navigateOpenLecture, @@ -65,7 +66,7 @@ fun App( onShowToast = viewModel::onShowToast, navigateCellEditor = navigator::navigateCellEditor, navigateSemesterSelect = navigator::navigateSemesterSelect, - navigateTimetable = navigator::navigatetimetable, + navigateTimetable = navigator::navigateTimetable, ) } diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/MainNavigator.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/MainNavigator.kt index aeee1bc8e..54bb7769f 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/MainNavigator.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/MainNavigator.kt @@ -12,6 +12,7 @@ import com.chukchukhaksa.mobile.presentation.timetable.navigation.navigateOpenLe import com.chukchukhaksa.mobile.presentation.timetable.navigation.navigateSemesterSelect import com.chukchukhaksa.mobile.presentation.timetable.navigation.navigateTimetable import com.chukchukhaksa.mobile.presentation.timetable.navigation.navigateTimetableEditor +import com.chukchukhaksa.mobile.presentation.timetable.navigation.navigateTimetableNameInput import com.chukchukhaksa.mobile.presentation.timetable.navigation.navigateTimetableList class MainNavigator( @@ -24,8 +25,12 @@ class MainNavigator( navController.navigateCellEditor(argument) } + fun navigateTimetableNameInput(argument: TimetableEditorArgument = TimetableEditorArgument()) { + navController.navigateTimetableNameInput(argument) + } + fun navigateTimetableEditor(argument: TimetableEditorArgument = TimetableEditorArgument()) { - navController.navigateTimetableEditor(argument) + navController.navigateTimetableEditor(argument) } fun navigateTimetableList() { @@ -40,7 +45,7 @@ class MainNavigator( navController.navigateOpenLecture() } - fun navigatetimetable() { + fun navigateTimetable() { navController.navigateTimetable() } diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/button/TextFieldClearButton.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/button/TextFieldClearButton.kt index 764e94af4..106081fff 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/button/TextFieldClearButton.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/button/TextFieldClearButton.kt @@ -1,12 +1,15 @@ package com.chukchukhaksa.mobile.common.designsystem.component.button +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.unit.dp import chukchukhaksa.composeapp.generated.resources.Res import chukchukhaksa.composeapp.generated.resources.ic_textfield_clear +import com.chukchukhaksa.mobile.common.designsystem.theme.Gray500 import com.chukchukhaksa.mobile.common.designsystem.theme.Gray95 import com.chukchukhaksa.mobile.common.ui.cchClickable import org.jetbrains.compose.resources.painterResource @@ -18,10 +21,11 @@ fun TextFieldClearButton( ) { Icon( modifier = modifier + .size(24.dp) .clip(CircleShape) .cchClickable(onClick = onClick), painter = painterResource(resource = Res.drawable.ic_textfield_clear), - tint = Gray95, + tint = Gray500, contentDescription = "", ) } diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/container/SuwikiSelectionContainer.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/container/CchSelectionButton.kt similarity index 57% rename from composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/container/SuwikiSelectionContainer.kt rename to composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/container/CchSelectionButton.kt index 511ea3c86..29196d7b2 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/container/SuwikiSelectionContainer.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/container/CchSelectionButton.kt @@ -1,11 +1,12 @@ package com.chukchukhaksa.mobile.common.designsystem.component.container -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -13,37 +14,36 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.unit.dp import chukchukhaksa.composeapp.generated.resources.Res -import chukchukhaksa.composeapp.generated.resources.ic_dropdown_arrow_down -import com.chukchukhaksa.mobile.common.designsystem.theme.Gray95 -import com.chukchukhaksa.mobile.common.designsystem.theme.GrayF6 -import com.chukchukhaksa.mobile.common.designsystem.theme.SuwikiTheme +import chukchukhaksa.composeapp.generated.resources.ic_arrow_sm +import com.chukchukhaksa.mobile.common.designsystem.theme.CchTheme +import com.chukchukhaksa.mobile.common.designsystem.theme.Gray100 +import com.chukchukhaksa.mobile.common.designsystem.theme.Gray600 import org.jetbrains.compose.resources.painterResource @Composable -fun SuwikiSelectionContainer( +fun CchSelectionButton( modifier: Modifier = Modifier, title: String = "", onClick: () -> Unit = {}, ) { Row( modifier = modifier - .clip(RoundedCornerShape(10.dp)) + .clip(RoundedCornerShape(6.dp)) .clickable(onClick = onClick) - .background(GrayF6) - .padding( - horizontal = 9.dp, - vertical = 6.dp, - ), + .background(Gray100) + .padding(vertical = 8.dp, horizontal = 12.dp), verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(2.dp) ) { Text( text = title, - color = Gray95, - style = SuwikiTheme.typography.body6, + color = Gray600, + style = CchTheme.typography.bodySm, ) - Image( - painter = painterResource(resource = Res.drawable.ic_dropdown_arrow_down), + Icon( + painter = painterResource(resource = Res.drawable.ic_arrow_sm), contentDescription = "", + tint = Gray600 ) } } diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt index 89896a877..0403b518a 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/common/designsystem/component/textfield/CchRegularTextField.kt @@ -30,10 +30,12 @@ import com.chukchukhaksa.mobile.common.designsystem.theme.White100 fun CchRegularTextField( modifier: Modifier = Modifier, value: String = "", - onValueChanged: (String) -> Unit = {}, placeholder: String = "", + isActive: Boolean = false, + onValueChanged: (String) -> Unit = {}, + onClickClearButton: () -> Unit = {} ) { - val (borderColor, textColor, textStyle) = if(value.isEmpty()) { + val (borderColor, textColor, textStyle) = if(!isActive) { Triple(Gray200, Gray400, CchTheme.typography.bodyLg) } else { Triple(Purple600, Black100, CchTheme.typography.bodyLgStrong) @@ -66,7 +68,9 @@ fun CchRegularTextField( } innerTextField() } - Spacer(modifier = Modifier.width(8.dp)) + if (isActive) { + TextFieldClearButton(onClick = onClickClearButton) + } } }, ) diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/di/PresentationModules.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/di/PresentationModules.kt index 60cb50eb5..3b6d28471 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/di/PresentationModules.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/di/PresentationModules.kt @@ -5,6 +5,7 @@ import com.chukchukhaksa.mobile.presentation.timetable.timetable.component.botto import com.chukchukhaksa.mobile.presentation.timetable.celleditor.CellEditorViewModel import com.chukchukhaksa.mobile.presentation.timetable.openlecture.OpenLectureViewModel import com.chukchukhaksa.mobile.presentation.timetable.timetable.TimetableViewModel +import com.chukchukhaksa.mobile.presentation.timetable.timetablenameinput.TimetableNameInputViewModel import com.chukchukhaksa.mobile.presentation.timetable.timetableeditor.TimetableEditorViewModel import com.chukchukhaksa.mobile.presentation.timetable.timetablelist.TimetableListViewModel import com.chukchukhaksa.mobile.presentation.timetable.semesterselect.SemesterSelectViewModel @@ -14,6 +15,7 @@ import org.koin.dsl.module val presentationModule = module { viewModelOf(::TimetableViewModel) viewModelOf(::TimetableListViewModel) + viewModelOf(::TimetableNameInputViewModel) viewModelOf(::TimetableEditorViewModel) viewModelOf(::MainViewModel) viewModelOf(::CellEditorViewModel) diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/celleditor/CellEditorScreen.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/celleditor/CellEditorScreen.kt index a83112a33..cea5e7845 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/celleditor/CellEditorScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/celleditor/CellEditorScreen.kt @@ -174,6 +174,7 @@ fun CellEditorScreen( value = uiState.lectureName, onValueChanged = onValueChangeLectureName, placeholder = stringResource(Res.string.add_cell_screen_input_lecture_name), + isActive = uiState.lectureName.isNotEmpty() ) } @@ -194,6 +195,7 @@ fun CellEditorScreen( value = uiState.professorName, onValueChanged = onValueChangeProfessorName, placeholder = stringResource(Res.string.add_cell_screen_input_professor_name), + isActive = uiState.professorName.isNotEmpty(), ) } diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/navigation/TimetableNavigation.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/navigation/TimetableNavigation.kt index f7d10a7ec..0c5688fdd 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/navigation/TimetableNavigation.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/navigation/TimetableNavigation.kt @@ -13,13 +13,18 @@ import com.chukchukhaksa.mobile.presentation.timetable.navigation.argument.CellE import com.chukchukhaksa.mobile.presentation.timetable.navigation.argument.TimetableEditorArgument import com.chukchukhaksa.mobile.presentation.timetable.openlecture.OpenLectureRoute import com.chukchukhaksa.mobile.presentation.timetable.timetable.TimetableRoute -import com.chukchukhaksa.mobile.presentation.timetable.timetableeditor.TimetableEditorRoute +import com.chukchukhaksa.mobile.presentation.timetable.timetablenameinput.TimetableNameInputRoute import com.chukchukhaksa.mobile.presentation.timetable.timetablelist.TimetableListRoute import com.chukchukhaksa.mobile.presentation.timetable.semesterselect.SemesterSelectRoute +import com.chukchukhaksa.mobile.presentation.timetable.timetableeditor.TimetableEditorRoute import kotlinx.serialization.json.Json +fun NavController.navigateTimetableNameInput(argument: TimetableEditorArgument = TimetableEditorArgument()) { + navigate(TimetableRoute.timetableNameInputRoute(Json.encodeToUri(argument))) +} + fun NavController.navigateTimetableEditor(argument: TimetableEditorArgument = TimetableEditorArgument()) { - navigate(TimetableRoute.timetableEditorRoute(Json.encodeToUri(argument))) + navigate(TimetableRoute.timetableEditorRoute(Json.encodeToUri(argument))) } fun NavController.navigateTimetableList() { @@ -47,6 +52,7 @@ fun NavController.navigateTimetable() { fun NavGraphBuilder.timetableNavGraph( padding: PaddingValues, popBackStack: () -> Unit, + navigateTimetableNameInput: (TimetableEditorArgument) -> Unit, navigateTimetableEditor: (TimetableEditorArgument) -> Unit, navigateTimetableList: () -> Unit, navigateOpenLecture: () -> Unit, @@ -69,7 +75,7 @@ fun NavGraphBuilder.timetableNavGraph( } composable( - route = TimetableRoute.timetableEditorRoute( + route = TimetableRoute.timetableNameInputRoute( "{${TimetableRoute.TIMETABLE_EDITOR_ARGUMENT}}", ), arguments = listOf( @@ -79,7 +85,7 @@ fun NavGraphBuilder.timetableNavGraph( }, ), ) { - TimetableEditorRoute( + TimetableNameInputRoute( navigateTimetable = navigateTimetable, popBackStack = popBackStack, handleException = handleException, @@ -87,6 +93,24 @@ fun NavGraphBuilder.timetableNavGraph( ) } + composable( + route = TimetableRoute.timetableEditorRoute( + "{${TimetableRoute.TIMETABLE_EDITOR_ARGUMENT}}", + ), + arguments = listOf( + navArgument(TimetableRoute.TIMETABLE_EDITOR_ARGUMENT) { + type = NavType.StringType + nullable = true + }, + ), + ) { + TimetableEditorRoute( + popBackStack = popBackStack, + handleException = handleException, + onShowToast = onShowToast, + ) + } + composable(route = TimetableRoute.openLectureRoute) { OpenLectureRoute( selectedOpenMajor = null, @@ -130,7 +154,7 @@ fun NavGraphBuilder.timetableNavGraph( ) { SemesterSelectRoute( popBackStack = popBackStack, - navigateTimetableEditor = navigateTimetableEditor, + navigateTimetableNameInput = navigateTimetableNameInput, ) } } @@ -143,6 +167,7 @@ object TimetableRoute { const val CELL_EDITOR_ARGUMENT = "cell-editor-argument" const val TIMETABLE_EDITOR_ARGUMENT = "timetable-editor-argument" + fun timetableNameInputRoute(timetableEditor: String) = "$route/name-input/$timetableEditor" fun timetableEditorRoute(timetableEditor: String) = "$route/editor/$timetableEditor" fun cellEditorRoute(cellEditor: String) = "cell-editor/$cellEditor" } diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectContract.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectContract.kt index 92ee6d195..07e40ef40 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectContract.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectContract.kt @@ -14,7 +14,7 @@ data class SemesterSelectState( ) sealed interface SemesterSelectSideEffect { - data class NavigateTimetableEditor(val semester: TimetableEditorArgument): SemesterSelectSideEffect + data class NavigateTimetableNameInput(val semester: TimetableEditorArgument): SemesterSelectSideEffect } val semesterList: PersistentList = run { diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectScreen.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectScreen.kt index 8ea6d5160..20885b764 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectScreen.kt @@ -30,19 +30,19 @@ import org.koin.compose.viewmodel.koinViewModel fun SemesterSelectRoute( viewModel: SemesterSelectViewModel = koinViewModel(), popBackStack: () -> Unit = {}, - navigateTimetableEditor: (TimetableEditorArgument) -> Unit, + navigateTimetableNameInput: (TimetableEditorArgument) -> Unit, ) { val uiState by viewModel.mviStore.uiState.collectAsStateWithLifecycle() viewModel.mviStore.sideEffects.collectWithLifecycle { sideEffect -> when (sideEffect) { - is SemesterSelectSideEffect.NavigateTimetableEditor -> navigateTimetableEditor(sideEffect.semester) + is SemesterSelectSideEffect.NavigateTimetableNameInput -> navigateTimetableNameInput(sideEffect.semester) } } SemesterSelectScreen( uiState = uiState, onClickBackButton = popBackStack, onClickSemester = viewModel::updateSelectedSemesterIndex, - onClickNextButton = viewModel::navigateTimetableEditor, + onClickNextButton = viewModel::navigateTimetableNameInput, ) } diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectViewModel.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectViewModel.kt index 893d34de9..6d49d98ae 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/semesterselect/SemesterSelectViewModel.kt @@ -16,9 +16,9 @@ class SemesterSelectViewModel : ViewModel() { fun updateSelectSemester(semester: Semester?) = mviStore.setState { copy(selectSemester = semester) } - fun navigateTimetableEditor(semester: Semester?) { + fun navigateTimetableNameInput(semester: Semester?) { if (semester != null) { - mviStore.postSideEffect(SemesterSelectSideEffect.NavigateTimetableEditor(semester.toTimetableEditorArgument())) + mviStore.postSideEffect(SemesterSelectSideEffect.NavigateTimetableNameInput(semester.toTimetableEditorArgument())) } } diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt index a7c750ab8..f264dd8a2 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorContract.kt @@ -7,20 +7,24 @@ import com.chukchukhaksa.mobile.presentation.timetable.semesterselect.Semester data class TimetableEditorState( val name: String = "", + val preName: String = "", + val isSheetOpenSemester: Boolean = false, + val preSelectedSemesterPosition: Int? = null, val selectedSemesterPosition: Int? = null, ) { val semester = selectedSemesterPosition?.let { semesterList.getOrNull(it) } - val buttonEnabled = name.isNotEmpty() + val buttonEnabled = (name.isNotEmpty() && preName != name || preSelectedSemesterPosition != selectedSemesterPosition) } internal fun TimetableEditorArgument.toState() = TimetableEditorState( name = name, + preName = name, selectedSemesterPosition = semesterList.indexOf(Semester(year, semester)), + preSelectedSemesterPosition = semesterList.indexOf(Semester(year, semester)), ) sealed interface TimetableEditorSideEffect { - data object NavigateTimetable : TimetableEditorSideEffect data object PopBackStack : TimetableEditorSideEffect - data object NeedSelectSemesterToast : TimetableEditorSideEffect + data object ShowEditSaveToast : TimetableEditorSideEffect data class HandleException(val throwable: Throwable) : TimetableEditorSideEffect } diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt index dd582bce1..0b6a7adea 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorScreen.kt @@ -9,49 +9,40 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.width -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import chukchukhaksa.composeapp.generated.resources.Res -import chukchukhaksa.composeapp.generated.resources.create_timetable_need_select_semester import chukchukhaksa.composeapp.generated.resources.create_timetable_screen_placeholder import com.chukchukhaksa.mobile.common.designsystem.component.SuwikiBackground import com.chukchukhaksa.mobile.common.designsystem.component.appbar.CchAppBarWithTitle +import com.chukchukhaksa.mobile.common.designsystem.component.bottomsheet.CchSelectBottomSheet import com.chukchukhaksa.mobile.common.designsystem.component.button.CchBasicButton +import com.chukchukhaksa.mobile.common.designsystem.component.container.CchSelectionButton import com.chukchukhaksa.mobile.common.designsystem.component.textfield.CchRegularTextField -import com.chukchukhaksa.mobile.common.designsystem.theme.Black100 -import com.chukchukhaksa.mobile.common.designsystem.theme.CchTheme import com.chukchukhaksa.mobile.common.designsystem.theme.White import com.chukchukhaksa.mobile.common.ui.collectWithLifecycle -import org.jetbrains.compose.resources.getString +import com.chukchukhaksa.mobile.presentation.timetable.semesterselect.semesterList +import kotlinx.collections.immutable.toPersistentList import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel @Composable fun TimetableEditorRoute( - viewModel: TimetableEditorViewModel = koinViewModel(), - navigateTimetable: () -> Unit, - popBackStack: () -> Unit, - handleException: (Throwable) -> Unit, - onShowToast: (String, Dp) -> Unit, + viewModel: TimetableEditorViewModel = koinViewModel(), + popBackStack: () -> Unit, + handleException: (Throwable) -> Unit, + onShowToast: (String, Dp) -> Unit, ) { val uiState by viewModel.mviStore.uiState.collectAsStateWithLifecycle() viewModel.mviStore.sideEffects.collectWithLifecycle { sideEffect -> when (sideEffect) { is TimetableEditorSideEffect.HandleException -> handleException(sideEffect.throwable) TimetableEditorSideEffect.PopBackStack -> popBackStack() - TimetableEditorSideEffect.NeedSelectSemesterToast -> onShowToast( - getString(Res.string.create_timetable_need_select_semester), - 70.dp, - ) - TimetableEditorSideEffect.NavigateTimetable -> navigateTimetable() + TimetableEditorSideEffect.ShowEditSaveToast -> onShowToast("변경사항이 저장되었습니다.", 111.dp) } } TimetableEditorScreen( @@ -59,17 +50,26 @@ fun TimetableEditorRoute( onValueChangeTimetableName = viewModel::updateName, onClickBack = viewModel::popBackStack, onClickCompleteButton = viewModel::upsertTimetable, + onClickSelectionContainer = viewModel::showSemesterBottomSheet, + hideSemesterBottomSheet = viewModel::hideSemesterBottomSheet, + onClickSemesterItem = { position -> + viewModel.hideSemesterBottomSheet() + viewModel.updateSemesterPosition(position) + }, onClickTextFieldClearButton = { viewModel.updateName("") }, ) } @Composable fun TimetableEditorScreen( - uiState: TimetableEditorState = TimetableEditorState(), - onValueChangeTimetableName: (String) -> Unit = {}, - onClickTextFieldClearButton: () -> Unit = {}, - onClickBack: () -> Unit = {}, - onClickCompleteButton: () -> Unit = {}, + uiState: TimetableEditorState = TimetableEditorState(), + onValueChangeTimetableName: (String) -> Unit = {}, + onClickTextFieldClearButton: () -> Unit = {}, + onClickBack: () -> Unit = {}, + onClickCompleteButton: () -> Unit = {}, + onClickSelectionContainer: () -> Unit = {}, + hideSemesterBottomSheet: () -> Unit = {}, + onClickSemesterItem: (Int) -> Unit = {}, ) { SuwikiBackground { Column( @@ -84,24 +84,21 @@ fun TimetableEditorScreen( ) Column( - modifier = Modifier.padding(16.dp), - horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(vertical = 8.dp, horizontal = 16.dp), ) { - Text( - modifier = Modifier - .width(240.dp) - .padding(top = 8.dp), - text = "선택한 학기의 시간표 이름을 정해주세요", - style = CchTheme.typography.titleLg, - color = Black100, - textAlign = TextAlign.Center, + CchSelectionButton( + modifier = Modifier.padding(horizontal = 4.dp), + title = uiState.semester?.toText() ?: "수강학기 선택", + onClick = onClickSelectionContainer, ) CchRegularTextField( - modifier = Modifier.padding(top = 196.dp, start = 4.dp, end = 4.dp), + modifier = Modifier.padding(top = 12.dp, start = 4.dp, end = 4.dp), value = uiState.name, - onValueChanged = onValueChangeTimetableName, placeholder = stringResource(Res.string.create_timetable_screen_placeholder), + isActive = uiState.name.isNotEmpty() && uiState.name != uiState.preName, + onValueChanged = onValueChangeTimetableName, + onClickClearButton = onClickTextFieldClearButton, ) Spacer(modifier = Modifier.weight(1f)) @@ -110,13 +107,21 @@ fun TimetableEditorScreen( modifier = Modifier .consumeWindowInsets(WindowInsets.navigationBars) .imePadding(), - text = "시간표 생성하기", + text = "변경사항 저장하기", enable = uiState.buttonEnabled, onClick = onClickCompleteButton, ) } } } + if (uiState.isSheetOpenSemester) { + CchSelectBottomSheet( + onDismissRequest = hideSemesterBottomSheet, + onClickItem = { onClickSemesterItem(it) }, + itemList = semesterList.map { it.toText() }.toPersistentList(), + selectedPosition = uiState.selectedSemesterPosition, + ) + } } //@Preview diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorViewModel.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorViewModel.kt index bd0df3ffe..70262db7d 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetableeditor/TimetableEditorViewModel.kt @@ -26,6 +26,18 @@ class TimetableEditorViewModel( timetableEditorArgument.toState(), ) + fun showSemesterBottomSheet() { + mviStore.setState { copy(isSheetOpenSemester = true) } + } + + fun hideSemesterBottomSheet() { + mviStore.setState { copy(isSheetOpenSemester = false) } + } + + fun updateSemesterPosition(position: Int) { + mviStore.setState { copy(selectedSemesterPosition = position) } + } + fun updateName(name: String) { mviStore.setState { copy(name = name) } } @@ -34,7 +46,6 @@ class TimetableEditorViewModel( val state = mviStore.uiState.value if (state.semester == null) { - mviStore.postSideEffect(TimetableEditorSideEffect.NeedSelectSemesterToast) return@launch } @@ -59,7 +70,8 @@ class TimetableEditorViewModel( useCase .onSuccess { - mviStore.postSideEffect(TimetableEditorSideEffect.NavigateTimetable) +// mviStore.postSideEffect(TimetableEditorSideEffect.PopBackStack) + mviStore.postSideEffect(TimetableEditorSideEffect.ShowEditSaveToast) }.onFailure { mviStore.postSideEffect(TimetableEditorSideEffect.HandleException(it)) } diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListContract.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListContract.kt index d9aefc57f..f658fba81 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListContract.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListContract.kt @@ -13,7 +13,7 @@ data class TimetableListState( sealed interface TimetableListSideEffect { data class HandleException(val throwable: Throwable) : TimetableListSideEffect data class NavigateTimetableEditor(val argument: TimetableEditorArgument) : - TimetableListSideEffect + TimetableListSideEffect data object NavigateSemesterSelect : TimetableListSideEffect data object PopBackStack : TimetableListSideEffect diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt index 3db487eb5..138396dc2 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListScreen.kt @@ -18,7 +18,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import chukchukhaksa.composeapp.generated.resources.Res -import chukchukhaksa.composeapp.generated.resources.add_cell_screen_need_professor_name import chukchukhaksa.composeapp.generated.resources.delete_timetable_dialog_body import chukchukhaksa.composeapp.generated.resources.delete_timetable_dialog_title import chukchukhaksa.composeapp.generated.resources.timetable_list_screen_empty_timetable @@ -33,9 +32,7 @@ import com.chukchukhaksa.mobile.common.designsystem.theme.SuwikiTheme import com.chukchukhaksa.mobile.common.designsystem.theme.White100 import com.chukchukhaksa.mobile.common.model.Timetable import com.chukchukhaksa.mobile.common.ui.collectWithLifecycle -import com.chukchukhaksa.mobile.presentation.timetable.celleditor.CellEditorSideEffect import com.chukchukhaksa.mobile.presentation.timetable.navigation.argument.TimetableEditorArgument -import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListViewModel.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListViewModel.kt index e3f468f10..5d153fccc 100644 --- a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablelist/TimetableListViewModel.kt @@ -8,7 +8,6 @@ import com.chukchukhaksa.mobile.common.ui.mviStore import com.chukchukhaksa.mobile.domain.timetable.usecase.DeleteTimetableUseCase import com.chukchukhaksa.mobile.domain.timetable.usecase.GetAllTimetableUseCase import com.chukchukhaksa.mobile.domain.timetable.usecase.SetMainTimetableCreateTime -import com.chukchukhaksa.mobile.presentation.timetable.celleditor.CellEditorSideEffect import com.chukchukhaksa.mobile.presentation.timetable.navigation.argument.toTimetableEditorArgument import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.launch @@ -58,7 +57,7 @@ class TimetableListViewModel( } fun navigateTimetableEditor(timetable: Timetable) { - mviStore.postSideEffect(TimetableListSideEffect.NavigateTimetableEditor(timetable.toTimetableEditorArgument())) + mviStore.postSideEffect(TimetableListSideEffect.NavigateTimetableEditor(timetable.toTimetableEditorArgument())) } fun navigateSemesterSelect() { diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt new file mode 100644 index 000000000..61615cd57 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputContract.kt @@ -0,0 +1,25 @@ +package com.chukchukhaksa.mobile.presentation.timetable.timetablenameinput + +import com.chukchukhaksa.mobile.presentation.timetable.navigation.argument.TimetableEditorArgument +import com.chukchukhaksa.mobile.presentation.timetable.semesterselect.semesterList +import com.chukchukhaksa.mobile.presentation.timetable.semesterselect.Semester + + +data class TimetableNameInputState( + val name: String = "", + val semester: Semester = Semester("", "") +) { + val buttonEnabled = name.isNotEmpty() +} + +internal fun TimetableEditorArgument.toState() = TimetableNameInputState( + name = name, + semester = Semester(year, semester), +) + +sealed interface TimetableNameInputSideEffect { + data object NavigateTimetable : TimetableNameInputSideEffect + data object PopBackStack : TimetableNameInputSideEffect + data object NeedSelectSemesterToast : TimetableNameInputSideEffect + data class HandleException(val throwable: Throwable) : TimetableNameInputSideEffect +} diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt new file mode 100644 index 000000000..f4b04b01a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputScreen.kt @@ -0,0 +1,130 @@ +package com.chukchukhaksa.mobile.presentation.timetable.timetablenameinput + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.consumeWindowInsets +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.navigationBars +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import chukchukhaksa.composeapp.generated.resources.Res +import chukchukhaksa.composeapp.generated.resources.create_timetable_need_select_semester +import chukchukhaksa.composeapp.generated.resources.create_timetable_screen_placeholder +import com.chukchukhaksa.mobile.common.designsystem.component.SuwikiBackground +import com.chukchukhaksa.mobile.common.designsystem.component.appbar.CchAppBarWithTitle +import com.chukchukhaksa.mobile.common.designsystem.component.button.CchBasicButton +import com.chukchukhaksa.mobile.common.designsystem.component.textfield.CchRegularTextField +import com.chukchukhaksa.mobile.common.designsystem.theme.Black100 +import com.chukchukhaksa.mobile.common.designsystem.theme.CchTheme +import com.chukchukhaksa.mobile.common.designsystem.theme.White +import com.chukchukhaksa.mobile.common.ui.collectWithLifecycle +import org.jetbrains.compose.resources.getString +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel + +@Composable +fun TimetableNameInputRoute( + viewModel: TimetableNameInputViewModel = koinViewModel(), + navigateTimetable: () -> Unit, + popBackStack: () -> Unit, + handleException: (Throwable) -> Unit, + onShowToast: (String, Dp) -> Unit, +) { + val uiState by viewModel.mviStore.uiState.collectAsStateWithLifecycle() + viewModel.mviStore.sideEffects.collectWithLifecycle { sideEffect -> + when (sideEffect) { + is TimetableNameInputSideEffect.HandleException -> handleException(sideEffect.throwable) + TimetableNameInputSideEffect.PopBackStack -> popBackStack() + TimetableNameInputSideEffect.NeedSelectSemesterToast -> onShowToast( + getString(Res.string.create_timetable_need_select_semester), + 70.dp, + ) + TimetableNameInputSideEffect.NavigateTimetable -> navigateTimetable() + } + } + TimetableNameInputScreen( + uiState = uiState, + onValueChangeTimetableName = viewModel::updateName, + onClickBack = viewModel::popBackStack, + onClickCompleteButton = viewModel::upsertTimetable, + onClickTextFieldClearButton = { viewModel.updateName("") }, + ) +} + +@Composable +fun TimetableNameInputScreen( + uiState: TimetableNameInputState = TimetableNameInputState(), + onValueChangeTimetableName: (String) -> Unit = {}, + onClickTextFieldClearButton: () -> Unit = {}, + onClickBack: () -> Unit = {}, + onClickCompleteButton: () -> Unit = {}, +) { + SuwikiBackground { + Column( + modifier = Modifier + .fillMaxSize() + .background(White), + ) { + CchAppBarWithTitle( + title = "${uiState.semester?.year}년 ${uiState.semester?.semester}학기", + isShowAddButton = false, + onClickBackButton = { onClickBack() }, + ) + + Column( + modifier = Modifier.padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Text( + modifier = Modifier + .width(240.dp) + .padding(top = 8.dp), + text = "선택한 학기의 시간표 이름을 정해주세요", + style = CchTheme.typography.titleLg, + color = Black100, + textAlign = TextAlign.Center, + ) + + CchRegularTextField( + modifier = Modifier.padding(top = 196.dp, start = 4.dp, end = 4.dp), + value = uiState.name, + placeholder = stringResource(Res.string.create_timetable_screen_placeholder), + isActive = uiState.name.isNotEmpty(), + onValueChanged = onValueChangeTimetableName, + onClickClearButton = onClickTextFieldClearButton, + ) + + Spacer(modifier = Modifier.weight(1f)) + + CchBasicButton( + modifier = Modifier + .consumeWindowInsets(WindowInsets.navigationBars) + .imePadding(), + text = "시간표 생성하기", + enable = uiState.buttonEnabled, + onClick = onClickCompleteButton, + ) + } + } + } +} + +//@Preview +//@Composable +//fun TimetableEditorScreenPreview() { +// SuwikiTheme { +// TimetableEditorScreen() +// } +//} diff --git a/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputViewModel.kt b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputViewModel.kt new file mode 100644 index 000000000..2cf2e1dce --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/chukchukhaksa/mobile/presentation/timetable/timetablenameinput/TimetableNameInputViewModel.kt @@ -0,0 +1,66 @@ +package com.chukchukhaksa.mobile.presentation.timetable.timetablenameinput + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.chukchukhaksa.mobile.common.ui.MviStore +import com.chukchukhaksa.mobile.common.ui.decodeFromUri +import com.chukchukhaksa.mobile.common.ui.mviStore +import com.chukchukhaksa.mobile.domain.timetable.usecase.InsertTimetableUseCase +import com.chukchukhaksa.mobile.domain.timetable.usecase.UpdateTimetableUseCase +import com.chukchukhaksa.mobile.presentation.timetable.navigation.TimetableRoute +import com.chukchukhaksa.mobile.presentation.timetable.navigation.argument.TimetableEditorArgument +import kotlinx.coroutines.launch +import kotlinx.serialization.json.Json + + +class TimetableNameInputViewModel( + private val insertTimetableUseCase: InsertTimetableUseCase, + private val updateTimetableUseCase: UpdateTimetableUseCase, + savedStateHandle: SavedStateHandle, +) : ViewModel() { + private val argument = savedStateHandle.get(TimetableRoute.TIMETABLE_EDITOR_ARGUMENT)!! + private val timetableEditorArgument = Json.decodeFromUri(argument) + private val isEditMode = timetableEditorArgument.isEditMode + val mviStore: MviStore = mviStore( + timetableEditorArgument.toState(), + ) + + fun updateName(name: String) { + mviStore.setState { copy(name = name) } + } + + fun upsertTimetable() = viewModelScope.launch { + val state = mviStore.uiState.value + + val useCase = if (isEditMode) { + updateTimetableUseCase( + param = UpdateTimetableUseCase.Param( + createTime = timetableEditorArgument.createTime, + name = state.name, + year = state.semester.year, + semester = state.semester.semester, + ), + ) + } else { + insertTimetableUseCase( + param = InsertTimetableUseCase.Param( + name = state.name, + year = state.semester.year, + semester = state.semester.semester, + ), + ) + } + + useCase + .onSuccess { + mviStore.postSideEffect(TimetableNameInputSideEffect.NavigateTimetable) + }.onFailure { + mviStore.postSideEffect(TimetableNameInputSideEffect.HandleException(it)) + } + } + + fun popBackStack() { + mviStore.postSideEffect(TimetableNameInputSideEffect.PopBackStack) + } +}