From 9496f5adaabc377f1dd177161cfbd22602b8f5d2 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Thu, 14 Nov 2024 11:30:30 +0900 Subject: [PATCH 01/60] =?UTF-8?q?[Add]=20=ED=95=99=EA=B8=B0=20=ED=8E=B8?= =?UTF-8?q?=EC=A7=91=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=20ui?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/component/FilledTextButton.kt | 13 +- .../timetable/component/SemesterButton.kt | 63 ++-- .../feature/timetable/view/SemesterScreen.kt | 2 +- .../view/dialog/EditSemesterDialog.kt | 296 ++++++++++++++++++ .../timetable/src/main/res/values/strings.xml | 6 +- 5 files changed, 349 insertions(+), 31 deletions(-) create mode 100644 feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/FilledTextButton.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/FilledTextButton.kt index 75e482e87..69f54a40a 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/FilledTextButton.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/FilledTextButton.kt @@ -22,7 +22,8 @@ import `in`.koreatech.koin.core.designsystem.theme.KoinTheme enum class FilledButtonType { Normal, - Danger + Danger, + Neutral } @Composable @@ -41,6 +42,13 @@ private fun buttonStyleByType(type: FilledButtonType): ButtonColors = when (type disabledContainerColor = KoinTheme.colors.neutral300, disabledContentColor = KoinTheme.colors.neutral600 ) + + FilledButtonType.Neutral -> ButtonColors( + containerColor = KoinTheme.colors.neutral300, + contentColor = KoinTheme.colors.neutral0, + disabledContainerColor = KoinTheme.colors.neutral300, + disabledContentColor = KoinTheme.colors.neutral600 + ) } @Composable @@ -52,6 +60,7 @@ fun FilledTextButton( buttonStyle: FilledButtonType = FilledButtonType.Normal, buttonShape: Shape = FilledTextButtonDefaults.shape, textStyle: TextStyle = FilledTextButtonDefaults.textStyle, + contentPadding: PaddingValues = FilledTextButtonDefaults.contentPadding, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } ) { val buttonColors = buttonStyleByType(type = buttonStyle) @@ -63,7 +72,7 @@ fun FilledTextButton( colors = buttonColors, elevation = null, border = null, - contentPadding = FilledTextButtonDefaults.contentPadding, + contentPadding = contentPadding, interactionSource = interactionSource ) { Text(text = text, style = textStyle) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/SemesterButton.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/SemesterButton.kt index 274d4cf82..7b9b16aa7 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/SemesterButton.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/SemesterButton.kt @@ -1,47 +1,56 @@ package `in`.koreatech.koin.feature.timetable.component +import androidx.compose.foundation.BorderStroke +import androidx.compose.material3.ButtonColors import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.component.button.FilledButton import `in`.koreatech.koin.core.designsystem.component.button.FilledButtonColors import `in`.koreatech.koin.core.designsystem.component.button.OutlinedBoxButton -import `in`.koreatech.koin.core.designsystem.component.button.OutlinedBoxButtonColors - - -@Stable -data class SemesterButtonState( - val isAdded: Boolean = false, - val isDeleted: Boolean = false -) +import `in`.koreatech.koin.core.designsystem.theme.KoinTheme @Composable fun SemesterButton( text: String, - state: SemesterButtonState, - onClick: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + isExisted: Boolean = false, + isSelected: Boolean = false, + onClick: () -> Unit = {} ) { - if (state.isAdded) { + if (isExisted) { + val color = if (isSelected) FilledButtonColors.Danger else FilledButtonColors.Success FilledButton( modifier = modifier, text = text, onClick = onClick, - colors = FilledButtonColors.Success - ) - } else if (state.isDeleted) { - FilledButton( - modifier = modifier, - text = text, - onClick = onClick, - colors = FilledButtonColors.Danger + colors = color ) } else { - OutlinedBoxButton( - modifier = modifier, - text = text, - onClick = onClick, - colors = OutlinedBoxButtonColors.Neutral - ) + if (isSelected) { + FilledButton( + modifier = modifier, + text = text, + onClick = onClick, + colors = FilledButtonColors.Success + ) + } else { + OutlinedBoxButton( + modifier = modifier, + text = text, + textStyle = KoinTheme.typography.medium16, + onClick = onClick, + colors = ButtonColors( + containerColor = KoinTheme.colors.neutral0, + contentColor = KoinTheme.colors.neutral800, + disabledContainerColor = KoinTheme.colors.neutral300, + disabledContentColor = KoinTheme.colors.neutral800 + ), + border = BorderStroke( + width = 1.dp, + color = KoinTheme.colors.neutral300 + ) + ) + } } } \ No newline at end of file diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt index 5e7c70d3f..c4ed164e6 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt @@ -175,7 +175,7 @@ private fun LazyListScope.TimetableFrameBlock( if (!isAnonymous) { Text( modifier = Modifier.noRippleClickable { onClickEditTimetable() }, - text = stringResource(id = R.string.semester_edit_timetable), + text = stringResource(id = R.string.semester_edit_timetable_frame), style = KoinTheme.typography.bold16 ) } diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt new file mode 100644 index 000000000..9f0c753e5 --- /dev/null +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt @@ -0,0 +1,296 @@ +package `in`.koreatech.koin.feature.timetable.view.dialog + +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +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.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.IconButton +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.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.core.designsystem.component.button.OutlinedBoxButton +import `in`.koreatech.koin.core.designsystem.component.button.OutlinedBoxButtonColors +import `in`.koreatech.koin.core.designsystem.component.icon.StableIcon +import `in`.koreatech.koin.core.designsystem.theme.KoinTheme +import `in`.koreatech.koin.feature.timetable.R +import `in`.koreatech.koin.feature.timetable.component.FilledButtonType +import `in`.koreatech.koin.feature.timetable.component.FilledTextButton +import `in`.koreatech.koin.feature.timetable.component.SemesterButton +import `in`.koreatech.koin.feature.timetable.model.SemesterModel +import `in`.koreatech.koin.feature.timetable.model.SemesterType +import java.time.LocalDate + +// TODO::hyeok minsdk 올라가면 제거 +@RequiresApi(Build.VERSION_CODES.O) +@Composable +fun EditSemesterDialogImpl( + years: List, + userSemesters: List, + modifier: Modifier = Modifier, + onConfirm: (List) -> Unit = {}, + onDismiss: () -> Unit = {}, +) { + var currentYear by remember { mutableStateOf(LocalDate.now().year) } + var selectedSemesters by remember(currentYear, userSemesters) { mutableStateOf(listOf()) } + + var isShowingSelectYearDialog by remember { mutableStateOf(false) } + + // TODO::hyeok 연도 눌렀을 때 깜빡거림 + Box { + if (isShowingSelectYearDialog) { + SelectYearDialog( + currentYear = currentYear, + yearList = years, + onDismiss = { isShowingSelectYearDialog = false }, + onSelectYear = { + currentYear = it + isShowingSelectYearDialog = false + } + ) + } else { + EditSemesterDialog( + modifier = Modifier, + currentYear = currentYear, + userSemesters = userSemesters.filter { it.year == currentYear }.map { it.type }, + selectedSemesters = selectedSemesters, + onClickSemester = { clickedSemester -> + if (selectedSemesters.contains(clickedSemester)) + selectedSemesters = selectedSemesters.filter { it != clickedSemester } + else + selectedSemesters = selectedSemesters + clickedSemester + }, + onClickYear = { isShowingSelectYearDialog = true }, + onConfirm = { onConfirm(selectedSemesters.map { SemesterModel(currentYear, it) }) }, + onDismiss = onDismiss + ) + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun EditSemesterDialog( + currentYear: Int, + userSemesters: List, + selectedSemesters: List, + modifier: Modifier = Modifier, + onClickYear: () -> Unit = {}, + onClickSemester: (SemesterType) -> Unit = {}, + onConfirm: () -> Unit = {}, + onDismiss: () -> Unit = {} +) { + BasicAlertDialog( + modifier = modifier + .background(color = KoinTheme.colors.neutral0, shape = KoinTheme.shapes.extraSmall), + onDismissRequest = onDismiss + ) { + Box { + FilledTextButton( + modifier = Modifier + .padding(top = 12.dp, start = 24.dp) + .align(Alignment.TopStart) + .height(24.dp), + text = stringResource(id = R.string.semester_edit_year, currentYear), + textStyle = KoinTheme.typography.medium12.copy( + color = KoinTheme.colors.neutral800 + ), + buttonStyle = FilledButtonType.Neutral, + contentPadding = PaddingValues(horizontal = 20.dp), + onClick = onClickYear + ) + Column( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.TopCenter) + .padding(24.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + Text(text = "학기 편집", style = KoinTheme.typography.bold16) + + LazyVerticalGrid( + columns = GridCells.Fixed(2), + contentPadding = PaddingValues(0.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + horizontalArrangement = Arrangement.spacedBy(28.dp), + ) { + items(SemesterType.entries) { + SemesterButton( + text = stringResource(id = it.stringRes), + isExisted = userSemesters.contains(it), + isSelected = selectedSemesters.contains(it), + onClick = { onClickSemester(it) } + ) + } + } + + Row( + modifier = Modifier.wrapContentHeight(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + OutlinedBoxButton( + modifier = Modifier + .height(48.dp) + .weight(1.0F), + text = stringResource(id = R.string.common_cancellation), + colors = OutlinedBoxButtonColors.Neutral, + onClick = onDismiss + ) + FilledTextButton( + modifier = Modifier + .height(48.dp) + .weight(1.0F), + text = stringResource(id = R.string.semester_edit_apply), + onClick = onConfirm + ) + } + } + + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun SelectYearDialog( + currentYear: Int, + yearList: List, + modifier: Modifier = Modifier, + onDismiss: () -> Unit = {}, + onSelectYear: (Int) -> Unit = {} +) { + BasicAlertDialog( + modifier = modifier + .background(color = KoinTheme.colors.neutral0, shape = KoinTheme.shapes.extraSmall) + .padding(top = 12.dp, start = 24.dp, end = 24.dp, bottom = 40.dp), + onDismissRequest = onDismiss + ) { + Column( + verticalArrangement = Arrangement.spacedBy(10.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom + ) { + Text( + text = "연도 선택", + style = KoinTheme.typography.medium18.copy( + color = KoinTheme.colors.primary500 + ) + ) + IconButton( + modifier = Modifier + .size(24.dp), + onClick = onDismiss + ) { + StableIcon( + drawableResId = R.drawable.ic_close, + ) + } + } + + LazyVerticalGrid( + columns = GridCells.Fixed(2), + contentPadding = PaddingValues(0.dp), + verticalArrangement = Arrangement.spacedBy(10.dp), + horizontalArrangement = Arrangement.spacedBy(28.dp), + ) { + items(yearList) { year -> + OutlinedBoxButton( + modifier = Modifier + .height(40.dp), + text = year.toString(), + textStyle = KoinTheme.typography.regular15, + colors = ButtonColors( + containerColor = KoinTheme.colors.neutral0, + contentColor = KoinTheme.colors.neutral800, + disabledContainerColor = KoinTheme.colors.neutral300, + disabledContentColor = KoinTheme.colors.neutral800 + ), + border = BorderStroke( + width = 1.dp, + color = KoinTheme.colors.neutral300 + ), + enabled = currentYear != year, + onClick = { onSelectYear(year) } + ) + } + } + } + } +} + + +@RequiresApi(Build.VERSION_CODES.O) +@Preview +@Composable +private fun EditSemesterDialogPreview() { + EditSemesterDialog( + currentYear = 2024, + userSemesters = listOf( + SemesterType.Fall, + SemesterType.Winter, + ), + selectedSemesters = listOf( + SemesterType.Winter + ), + onClickYear = {}, + onClickSemester = {}, + onConfirm = {}, + onDismiss = {} + ) +} + +@Preview +@Composable +private fun SelectYearDialogContentPreview() { + SelectYearDialog(currentYear = 2023, yearList = listOf(2019, 2020, 2021, 2022, 2023, 2024).sortedDescending()) +} + +@Preview +@Composable +@RequiresApi(Build.VERSION_CODES.O) +private fun EditSemesterDialogImplPreview() { + var userSemesters by remember { mutableStateOf(listOf()) } + EditSemesterDialogImpl( + years = listOf(2019, 2020, 2021, 2022, 2023, 2024), + userSemesters = userSemesters, + onConfirm = { selectedSemester -> + userSemesters = userSemesters.toMutableList().apply { + selectedSemester.forEach { + if (contains(it)) { + remove(it) + } else { + add(it) + } + } + } + }, + onDismiss = {} + ) +} \ No newline at end of file diff --git a/feature/timetable/src/main/res/values/strings.xml b/feature/timetable/src/main/res/values/strings.xml index 64d55ce6a..0f8212040 100644 --- a/feature/timetable/src/main/res/values/strings.xml +++ b/feature/timetable/src/main/res/values/strings.xml @@ -74,7 +74,11 @@ 로그인 을 통해 학기를 편집하고 계절학기와 예비 시간표를 작성할 수 있어요. - 설정 + %d %s + 설정 + %d년도 + 적용하기 + 학기가 존재하지 않습니다. 학기를 추가해주세요. 중복된 시간입니다. From 3c30a834e9b728399865d4def20b1fdf28830b4a Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Thu, 14 Nov 2024 11:31:32 +0900 Subject: [PATCH 02/60] =?UTF-8?q?[Add]=20=ED=95=99=EA=B8=B0=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20string.xml=20=EB=A6=AC=EC=86=8C=EC=8A=A4?= =?UTF-8?q?=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/feature/timetable/view/SemesterScreen.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt index c4ed164e6..60aaaf45d 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt @@ -104,8 +104,13 @@ private fun LazyListScope.SemesterBlock( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { + Text( - text = semesterModel.year.toString() + " " + stringResource(id = semesterModel.type.stringRes), + text = stringResource( + id = R.string.semester_semester_format, + semesterModel.year, + stringResource(id = semesterModel.type.stringRes) + ), style = KoinTheme.typography.bold20.copy( color = KoinTheme.colors.neutral800 ) From 66c8993c744db3c937beb874eb86cb3ef1923ef2 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 14 Nov 2024 15:38:11 +0900 Subject: [PATCH 03/60] =?UTF-8?q?[fix]=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=EB=94=94=ED=85=8C=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/timetable/state/CustomContentState.kt | 6 +++--- .../view/dialog/TimetableTimePickerDialog.kt | 14 ++++++-------- .../timetable/viewmodel/TimetableViewModel.kt | 6 ++++++ .../timetable/src/main/res/drawable/ic_plus.xml | 15 ++------------- 4 files changed, 17 insertions(+), 24 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/CustomContentState.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/CustomContentState.kt index a1b4fb894..c5a0113a0 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/CustomContentState.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/CustomContentState.kt @@ -44,10 +44,10 @@ data class CustomContentState( } Lecture( id = 0, - name = schedule, - professor = professor, + name = schedule.trim(), + professor = professor.trim(), classTime = classTimes, - place = places + place = places.trim() ).let { lectures.add(it) } return lectures diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/TimetableTimePickerDialog.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/TimetableTimePickerDialog.kt index cd95d05fb..5c63371b2 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/TimetableTimePickerDialog.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/TimetableTimePickerDialog.kt @@ -131,13 +131,13 @@ fun TimetableTimePickerDialog( } }, selectedTextStyle = KoinTheme.typography.bold20.copy( - color = KoinTheme.colors.neutral700, textAlign = TextAlign.End ), unselectedTextStyle = KoinTheme.typography.bold20.copy( - color = KoinTheme.colors.neutral500, textAlign = TextAlign.End ), + selectedItemColor = KoinTheme.colors.neutral700, + unselectedItemColor = KoinTheme.colors.neutral500, brushVerticalGradient = Brush.verticalGradient( 0f to Color.Transparent, 0.5f to Color.Black, @@ -169,12 +169,10 @@ fun TimetableTimePickerDialog( 1 } }, - selectedTextStyle = KoinTheme.typography.bold20.copy( - color = KoinTheme.colors.neutral700 - ), - unselectedTextStyle = KoinTheme.typography.bold20.copy( - color = KoinTheme.colors.neutral500 - ), + selectedTextStyle = KoinTheme.typography.bold20, + unselectedTextStyle = KoinTheme.typography.bold20, + selectedItemColor = KoinTheme.colors.neutral700, + unselectedItemColor = KoinTheme.colors.neutral500, modifier = Modifier.weight(.45f), brushVerticalGradient = Brush.verticalGradient( 0f to Color.Transparent, diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt index 9eb5e5d2c..acfd95ccf 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt @@ -514,6 +514,7 @@ class TimetableViewModel @Inject constructor( clickedTimetableEvents = emptyList(), etcClickedTimetableEvents = emptyList(), selectedLecture = null, + bottomSheetCollapse = true, loading = false ) _customContentState.value = CustomContentState() @@ -574,6 +575,10 @@ class TimetableViewModel @Inject constructor( } fun addCustomExtraContent() { + if (customContentState.value.data.size >= 5) { + _sideEffect.value = TimetableSideEffect.SnackBar(""""시간 및 장소 추가" 는 최대 5개까지 가능합니다.""") + return + } val editContent = customContentState.value.data.toMutableList() if (customContentState.value.data.isEmpty()) { editContent.add(CustomExtraContentState(id = 0)) @@ -780,6 +785,7 @@ class TimetableViewModel @Inject constructor( timetableLectures = timetableLectures, timetableEvents = timetableLectures.getTimetableEvents(), clickedTimetableEvents = emptyList(), + etcClickedTimetableEvents = emptyList(), selectedLecture = null, ) updateIsLectureDuplicationDialogVisible(false) diff --git a/feature/timetable/src/main/res/drawable/ic_plus.xml b/feature/timetable/src/main/res/drawable/ic_plus.xml index 6a8da3bc1..aaa669195 100644 --- a/feature/timetable/src/main/res/drawable/ic_plus.xml +++ b/feature/timetable/src/main/res/drawable/ic_plus.xml @@ -4,17 +4,6 @@ android:viewportWidth="24" android:viewportHeight="24"> - + android:pathData="M12,2C10.022,2 8.089,2.586 6.444,3.685C4.8,4.784 3.518,6.346 2.761,8.173C2.004,10 1.806,12.011 2.192,13.951C2.578,15.891 3.53,17.673 4.929,19.071C6.327,20.47 8.109,21.422 10.049,21.808C11.989,22.194 14,21.996 15.827,21.239C17.654,20.482 19.216,19.2 20.315,17.556C21.413,15.911 22,13.978 22,12C22,10.687 21.741,9.386 21.239,8.173C20.736,6.96 20,5.858 19.071,4.929C18.142,4 17.04,3.264 15.827,2.761C14.614,2.259 13.313,2 12,2ZM12,20C10.418,20 8.871,19.531 7.555,18.652C6.24,17.773 5.214,16.523 4.609,15.061C4.003,13.6 3.845,11.991 4.154,10.439C4.462,8.887 5.224,7.462 6.343,6.343C7.462,5.224 8.887,4.462 10.439,4.154C11.991,3.845 13.6,4.003 15.061,4.609C16.523,5.214 17.773,6.24 18.652,7.555C19.531,8.871 20,10.418 20,12C20,14.122 19.157,16.157 17.657,17.657C16.157,19.157 14.122,20 12,20ZM16,11H13V8C13,7.735 12.895,7.48 12.707,7.293C12.52,7.105 12.265,7 12,7C11.735,7 11.48,7.105 11.293,7.293C11.105,7.48 11,7.735 11,8V11H8C7.735,11 7.48,11.105 7.293,11.293C7.105,11.48 7,11.735 7,12C7,12.265 7.105,12.52 7.293,12.707C7.48,12.895 7.735,13 8,13H11V16C11,16.265 11.105,16.52 11.293,16.707C11.48,16.895 11.735,17 12,17C12.265,17 12.52,16.895 12.707,16.707C12.895,16.52 13,16.265 13,16V13H16C16.265,13 16.52,12.895 16.707,12.707C16.895,12.52 17,12.265 17,12C17,11.735 16.895,11.48 16.707,11.293C16.52,11.105 16.265,11 16,11Z" + android:fillColor="#175C8E"/> From a110921b23c6c6713b3a65234144897c328b3961 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Thu, 14 Nov 2024 16:30:16 +0900 Subject: [PATCH 04/60] =?UTF-8?q?[Add]=20=EB=B9=84=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=9C=A0=EC=A0=80=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=ED=94=84=EB=A0=88=EC=9E=84=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../GetSemesterTimetableFramesUseCase.kt | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterTimetableFramesUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterTimetableFramesUseCase.kt index a8d362da6..bfe9640c8 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterTimetableFramesUseCase.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterTimetableFramesUseCase.kt @@ -3,25 +3,37 @@ package `in`.koreatech.koin.domain.usecase.timetable import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame import `in`.koreatech.koin.domain.repository.TimetableRepository import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map import javax.inject.Inject class GetSemesterTimetableFramesUseCase @Inject constructor( private val timetableRepository: TimetableRepository ) { suspend operator fun invoke(isAnonymous: Boolean, semesters: List): Flow>> = flow { - val map = mutableMapOf>() - if(isAnonymous) { - // TODO::hyeok 로컬 저장소에서 가져오도록 분기? + if (isAnonymous) { + emitAll(timetableRepository.getSemesters().map { + it.map { + it to listOf( + TimetableFrame( + id = 0, + timetableName = "시간표1", + true + ) + ) + }.toMap() + }) } else { + val map = mutableMapOf>() + semesters.forEach { semester -> timetableRepository.getTimetableFrames(semester).collect { map.put(semester, it.sortedBy { !it.isMain }) } } + emit(map) } - - emit(map) } } \ No newline at end of file From a43f37e376d83fa2b74bbf0f7c089f6a2bb6ace3 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Thu, 14 Nov 2024 18:01:25 +0900 Subject: [PATCH 05/60] =?UTF-8?q?[Add]=20=ED=81=B4=EB=A6=AD=ED=95=9C=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=ED=91=9C=20=EC=A0=95=EB=B3=B4=20TimetableAct?= =?UTF-8?q?ivity=20=EB=A1=9C=20=EC=A0=84=EB=8B=AC=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/viewmodel/SemesterViewModel.kt | 28 +++++++++++++-- .../koin/ui/timetablev2/TimetableActivity.kt | 7 ++++ .../timetablev2/TimetableSemesterActivity.kt | 34 +++++++++++++++++-- 3 files changed, 64 insertions(+), 5 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index e26270b28..cc0bacd65 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -30,9 +30,12 @@ class SemesterViewModel @Inject constructor( private val _dialogUiState: MutableStateFlow = MutableStateFlow(SemesterDialogUiState()) val dialogUiState: StateFlow get() = _dialogUiState.asStateFlow() - val isAnonymous: StateFlow = getUserStatusUseCase() - .map { it.isAnonymous } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), false) + private val _isAnonymous: MutableStateFlow = MutableStateFlow(false) + val isAnonymous: StateFlow = _isAnonymous.asStateFlow() + + val semesters: StateFlow> = getSemestersUseCase() + .map { it.map { it.toSemesterModel() }} + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), emptyList()) val userSemesters: StateFlow> = isAnonymous .flatMapLatest { getUserSemestersUseCase(it) } @@ -44,6 +47,21 @@ class SemesterViewModel @Inject constructor( .map { it.mapKeys { it.key.toSemesterModel() } } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), emptyMap()) + val years: StateFlow> = semesters + .map { it.map { it.year }.distinct() } + .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), emptyList()) + + fun updateIsAnonymous(isAnonymous: Boolean) { + _isAnonymous.value = isAnonymous + } + + fun updateEditTimetableDialogVisibility(isVisible: Boolean) { + _dialogUiState.value = _dialogUiState.value.copy(isEditTimetableDialogVisible = isVisible) + } + + fun updateEditSemesterDialogVisible(isVisible: Boolean) { + _dialogUiState.value = _dialogUiState.value.copy(isEditSemesterDialogVisible = isVisible) + } fun onClickTimetable(timetableFrame: TimetableFrame) { // TODO::hyeok 시간표로 이동 @@ -58,6 +76,10 @@ class SemesterViewModel @Inject constructor( isEditTimetableDialogVisible = true ) } + + fun onEditSemesters(semesterModels: List) { + // 학기 추가 및 삭제 + } } data class SemesterDialogUiState( diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index cb1321517..ce88ce490 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -43,6 +43,7 @@ import `in`.koreatech.koin.feature.timetable.viewmodel.TimetableViewModel import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerActivity import `in`.koreatech.koin.ui.navigation.state.MenuState import kotlinx.coroutines.launch +import timber.log.Timber @AndroidEntryPoint class TimetableActivity : KoinNavigationDrawerActivity() { @@ -57,6 +58,12 @@ class TimetableActivity : KoinNavigationDrawerActivity() { private val registerTimetableSemesterActivityResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { // handle activity result api + if(it.resultCode == RESULT_OK) { + it.data?.getBundleExtra(TimetableSemesterActivity.BUNDLE_EXTRA_KEY)?.let { + Timber.d("Timetable frame ID: ${it.getInt(TimetableSemesterActivity.TIMETABLE_FRAME_ID)}") + Timber.d("Semester string: ${it.getString(TimetableSemesterActivity.SEMESTER)}") + } + } } override fun onCreate(savedInstanceState: Bundle?) { diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index 715823019..3cb65cead 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -1,8 +1,11 @@ package `in`.koreatech.koin.ui.timetablev2 +import android.os.Build import android.os.Bundle import androidx.activity.viewModels +import androidx.annotation.RequiresApi import androidx.compose.runtime.getValue +import androidx.core.os.bundleOf import androidx.lifecycle.compose.collectAsStateWithLifecycle import dagger.hilt.android.AndroidEntryPoint import `in`.koreatech.koin.R @@ -10,6 +13,8 @@ import `in`.koreatech.koin.core.activity.ActivityBase import `in`.koreatech.koin.core.designsystem.theme.KoinTheme import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.ActivityTimetableSemesterBinding +import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame +import `in`.koreatech.koin.feature.timetable.model.SemesterModel import `in`.koreatech.koin.feature.timetable.view.SemesterScreen import `in`.koreatech.koin.feature.timetable.viewmodel.SemesterViewModel import timber.log.Timber @@ -21,12 +26,13 @@ class TimetableSemesterActivity : ActivityBase() { private val binding by dataBinding() private val viewModel by viewModels() + @RequiresApi(Build.VERSION_CODES.O) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_timetable_semester) getIntentBundle { isAnonymous -> - // TODO : 도혁씨 이거 intent 익명일 때 값 받아오는 콜백 함수! Timber.e("isAnonymous : $isAnonymous") + viewModel.updateIsAnonymous(isAnonymous) } binding.timetableListComposeView.setContent { @@ -35,16 +41,25 @@ class TimetableSemesterActivity : ActivityBase() { val isAnonymous by viewModel.isAnonymous.collectAsStateWithLifecycle() val userTimetables by viewModel.userTimetableFrames.collectAsStateWithLifecycle() + val userSemesters by viewModel.userSemesters.collectAsStateWithLifecycle() + val years by viewModel.years.collectAsStateWithLifecycle() + SemesterScreen( userTimetables = userTimetables, isAnonymous = isAnonymous, - onClickTimetable = viewModel::onClickTimetable, + onClickTimetable = ::finishActivityWithResult, onClickAddTimetable = viewModel::onClickAddTimetable, onClickEditTimetable = viewModel::onClickEditTimetable ) if (dialogUiState.isEditSemesterDialogVisible) { // TODO::hyeok 학기 수정 다이얼로그 +// EditSemesterDialogImpl( +// years = years, +// userSemesters = userSemesters, +// onConfirm = viewModel::onEditSemesters, +// onDismiss = { viewModel.updateEditSemesterDialogVisible(false) } +// ) } else if (dialogUiState.isEditTimetableDialogVisible) { // TODO::hyeok 시간표 수정 다이얼로그 } @@ -59,7 +74,22 @@ class TimetableSemesterActivity : ActivityBase() { } } + private fun finishActivityWithResult(semester: SemesterModel, timetableFrame: TimetableFrame) { + intent?.putExtra( + BUNDLE_EXTRA_KEY, + bundleOf( + SEMESTER to semester.toSemester(), + TIMETABLE_FRAME_ID to timetableFrame.id + ) + ) + setResult(RESULT_OK, intent) + finish() + } + companion object { private const val SCREEN_TITLE = "시간표 목록" + const val BUNDLE_EXTRA_KEY = "BUNDLE_EXTRA_KEY" + const val SEMESTER = "semester" + const val TIMETABLE_FRAME_ID = "timetableFrameId" } } \ No newline at end of file From a8bae9d9925d27a4dea6bc57ae20287421e982c5 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 14 Nov 2024 20:29:34 +0900 Subject: [PATCH 06/60] =?UTF-8?q?[add]=20TimetableActivityV2=20=EC=A7=84?= =?UTF-8?q?=EC=9E=85=EC=A0=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../KoinNavigationDrawerActivity.kt | 53 ++++++++++--------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt index 5822d523d..4d7e1ce78 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt @@ -297,11 +297,12 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), } MenuState.Timetable -> { - if (userInfoFlow.value.isAnonymous) { - goToAnonymousTimeTableActivity() - } else { - goToTimetableActivity() - } + goToTimetableActivity() +// if (userInfoFlow.value.isAnonymous) { +// goToAnonymousTimeTableActivity() +// } else { +// goToTimetableActivity() +// } } MenuState.LoginOrLogout -> { @@ -475,39 +476,39 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), /** * 완성되면 아래 함수 주석 풀고 연결 */ + private fun goToTimetableActivity() { + if (menuState != MenuState.Main) { + goToActivityFinish(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) + } else { + val intent = Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java).apply { + if (koinNavigationDrawerViewModel.userInfoFlow.value.isAnonymous) { + putExtra("isAnonymous", true) + } else { + putExtra("isAnonymous", false) + } + } + EventLogger.logClickEvent( + action = EventAction.USER, + label = "hamburger", + value = "시간표" + ) + startActivity(intent) + } + } + // private fun goToTimetableActivity() { // if (menuState != MenuState.Main) { // goToActivityFinish(Intent(this, TimetableActivity::class.java)) // } else { -// val intent = Intent(this, TimetableActivity::class.java).apply { -// if (koinNavigationDrawerViewModel.userInfoFlow.value.isAnonymous) { -// putExtra("isAnonymous", true) -// } else { -// putExtra("isAnonymous", false) -// } -// } // EventLogger.logClickEvent( // action = EventAction.USER, // label = "hamburger", // value = "시간표" // ) -// startActivity(intent) +// startActivity(Intent(this, TimetableActivity::class.java)) // } // } - private fun goToTimetableActivity() { - if (menuState != MenuState.Main) { - goToActivityFinish(Intent(this, TimetableActivity::class.java)) - } else { - EventLogger.logClickEvent( - action = EventAction.USER, - label = "hamburger", - value = "시간표" - ) - startActivity(Intent(this, TimetableActivity::class.java)) - } - } - private fun goToArticleActivity() { val intent = Intent(this, ArticleActivity::class.java) From e1cfb3f482870d4ae5086ae59b639245bf126470 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 14 Nov 2024 21:41:42 +0900 Subject: [PATCH 07/60] =?UTF-8?q?[fix]=20=ED=95=99=EB=B6=80=20=EB=A6=AC?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/model/TimetableConstants.kt | 27 ++++++++++--------- .../view/dialog/SelectDepartmentDialog.kt | 4 +-- .../koin/ui/timetablev2/TimetableActivity.kt | 4 +-- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/model/TimetableConstants.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/model/TimetableConstants.kt index 5930bc042..3970942e4 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/model/TimetableConstants.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/model/TimetableConstants.kt @@ -9,6 +9,20 @@ import java.time.LocalTime object TimetableConstants { val days = listOf("월", "화", "수", "목", "금") + val departments = listOf( + "디자인ㆍ건축공학부", + "고용서비스정책학과", + "기계공학부", + "메카트로닉스공학부", + "산업경영학부", + "전기ㆍ전자ㆍ통신공학부", + "컴퓨터공학부", + "에너지신소재화학공학부", + "HRD학과", + "교양학부", + "안전공학과", + "융합학과", + ) const val eventHeight = 64 } @@ -44,18 +58,7 @@ val dummyLecture = Lecture( ) ) -val dummyDepartment = listOf( - "디자인ㆍ건축공학부", - "고용서비스정책학과", - "기계공학부", - "메카트로닉스공학부", - "산업경영학부", - "전기ㆍ전자ㆍ통신공학부", - "컴퓨터공학부", - "에너지신소재화학공학부", - "HRD학과", - "교양학부", -) + val defaultColors = listOf( diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/SelectDepartmentDialog.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/SelectDepartmentDialog.kt index 103528dea..86f77c0f1 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/SelectDepartmentDialog.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/SelectDepartmentDialog.kt @@ -30,7 +30,7 @@ import `in`.koreatech.koin.core.designsystem.theme.KoinTheme import `in`.koreatech.koin.feature.timetable.R import `in`.koreatech.koin.feature.timetable.component.DepartmentRadioButton import `in`.koreatech.koin.feature.timetable.component.FilledTextButton -import `in`.koreatech.koin.feature.timetable.model.dummyDepartment +import `in`.koreatech.koin.feature.timetable.model.TimetableConstants.departments @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -138,7 +138,7 @@ private fun SelectDepartmentDialogPreview() { KoinTheme { SelectDepartmentDialog( department = "", - departments = dummyDepartment, + departments = departments, onConfirm = {}, onDismiss = {}, ) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index cb1321517..78fd99068 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -27,7 +27,7 @@ import `in`.koreatech.koin.core.designsystem.component.snackbar.showSnackBarWith import `in`.koreatech.koin.core.designsystem.theme.KoinTheme import `in`.koreatech.koin.core.util.KeyboardUtils import `in`.koreatech.koin.databinding.ActivityTimetableBinding -import `in`.koreatech.koin.feature.timetable.model.dummyDepartment +import `in`.koreatech.koin.feature.timetable.model.TimetableConstants.departments import `in`.koreatech.koin.feature.timetable.state.BottomSheetUI import `in`.koreatech.koin.feature.timetable.state.TimetableSideEffect import `in`.koreatech.koin.feature.timetable.utils.BitmapUtils @@ -140,7 +140,7 @@ class TimetableActivity : KoinNavigationDrawerActivity() { if (dialogState.isSelectDepartmentVisible) { SelectDepartmentDialog( department = searchEngineState.department, - departments = dummyDepartment, + departments = departments, onConfirm = viewModel::updateDepartment, onDismiss = viewModel::updateIsSelectDepartmentDialogVisible ) From eb0b96cef21028413b239eac3819a15f0bd46600 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 14 Nov 2024 22:05:16 +0900 Subject: [PATCH 08/60] =?UTF-8?q?[fix]=20=EC=9A=94=EC=B2=AD=20DTO=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/data/api/auth/TimetableAuthApi.kt | 6 +++++ .../repository/TimetableRepositoryImpl.kt | 3 ++- .../request/timetable/LecturesQueryRequest.kt | 22 ++++++++++++------- .../remote/TimetableRemoteDataSource.kt | 6 +++++ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt b/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt index ed743f330..8270fb28f 100644 --- a/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt +++ b/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt @@ -1,5 +1,6 @@ package `in`.koreatech.koin.data.api.auth +import `in`.koreatech.koin.data.request.timetable.LecturesCustomQueryRequest import `in`.koreatech.koin.data.request.timetable.LecturesQueryRequest import `in`.koreatech.koin.data.request.timetable.TimetableFrameCreateQueryRequest import `in`.koreatech.koin.data.request.timetable.TimetableFrameQueryRequest @@ -35,6 +36,11 @@ interface TimetableAuthApi { @Body lectures: LecturesQueryRequest ): TimetableLecturesResponse + @POST("/v2/timetables/lecture") + suspend fun postTimetableLectures( + @Body lectures: LecturesCustomQueryRequest + ): TimetableLecturesResponse + @PUT("/v2/timetables/frame/{id}") suspend fun putTimetableFrame( @Path("id") id: Int, diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index d21f0919e..6e2c35ca8 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -2,6 +2,7 @@ package `in`.koreatech.koin.data.repository import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import `in`.koreatech.koin.data.request.timetable.LecturesCustomQueryRequest import `in`.koreatech.koin.data.request.timetable.LecturesQueryRequest import `in`.koreatech.koin.data.request.timetable.toCustomLectureQueryRequest import `in`.koreatech.koin.data.request.timetable.toLectureQueryRequest @@ -80,7 +81,7 @@ class TimetableRepositoryImpl @Inject constructor( } override suspend fun postTimetableCustomLectures(frameId: Int, lectures: List): Result = runCatching { - timetableRemoteDataSource.postTimetableLectures(LecturesQueryRequest( + timetableRemoteDataSource.postTimetableLectures(LecturesCustomQueryRequest( timetableFrameId = frameId, timetableLecture = lectures.map { it.toCustomLectureQueryRequest() } )).toTimetableLectures() diff --git a/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt b/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt index dfc985138..bcf4177b7 100644 --- a/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt +++ b/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt @@ -10,7 +10,19 @@ data class LecturesQueryRequest( val timetableLecture: List, ) +data class LecturesCustomQueryRequest( + @SerializedName("timetable_frame_id") + val timetableFrameId: Int, + @SerializedName("timetable_lecture") + val timetableLecture: List, +) + data class LectureQueryRequest( + @SerializedName("lecture_id") + val lectureId: Int, +) + +data class LectureCustomQueryRequest( @SerializedName("lecture_id") val lectureId: Int?, @SerializedName("class_title") @@ -27,7 +39,7 @@ data class LectureQueryRequest( val memo: String?, ) -fun Lecture.toCustomLectureQueryRequest() = LectureQueryRequest( +fun Lecture.toCustomLectureQueryRequest() = LectureCustomQueryRequest( classTitle = name, classTime = classTime, classPlace = place ?: "", @@ -39,11 +51,5 @@ fun Lecture.toCustomLectureQueryRequest() = LectureQueryRequest( fun Lecture.toLectureQueryRequest() = LectureQueryRequest( - lectureId = id, - classTitle = name, - classTime = classTime, - classPlace = "", - professor = professor, - grades = grades, - memo = "" + lectureId = id ) diff --git a/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt index 9fa87eb23..adcee7dd0 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt @@ -2,6 +2,8 @@ package `in`.koreatech.koin.data.source.remote import `in`.koreatech.koin.data.api.TimetableApi import `in`.koreatech.koin.data.api.auth.TimetableAuthApi +import `in`.koreatech.koin.data.request.timetable.LectureCustomQueryRequest +import `in`.koreatech.koin.data.request.timetable.LecturesCustomQueryRequest import `in`.koreatech.koin.data.request.timetable.LecturesQueryRequest import `in`.koreatech.koin.data.request.timetable.TimetableFrameCreateQueryRequest import `in`.koreatech.koin.data.request.timetable.TimetableFrameQueryRequest @@ -44,6 +46,10 @@ class TimetableRemoteDataSource @Inject constructor( ): TimetableLecturesResponse = timetableAuthApi.postTimetableLectures(lectures) + suspend fun postTimetableLectures( + lectures: LecturesCustomQueryRequest + ): TimetableLecturesResponse = timetableAuthApi.postTimetableLectures(lectures) + suspend fun postTimetableFrame( frame: TimetableFrameCreateQueryRequest ): TimetableFrameResponse = timetableAuthApi.postTimetableFrame(frame) From ebf8330e0623ec30b540a1ddc46dd0be380f1258 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 14 Nov 2024 22:13:09 +0900 Subject: [PATCH 09/60] =?UTF-8?q?[fix]=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=EC=A7=84=EC=9E=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../KoinNavigationDrawerActivity.kt | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt index 4d7e1ce78..21e795277 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt @@ -297,12 +297,12 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), } MenuState.Timetable -> { - goToTimetableActivity() -// if (userInfoFlow.value.isAnonymous) { -// goToAnonymousTimeTableActivity() -// } else { -// goToTimetableActivity() -// } +// goToTimetableActivity() + if (userInfoFlow.value.isAnonymous) { + goToAnonymousTimeTableActivity() + } else { + goToTimetableActivity() + } } MenuState.LoginOrLogout -> { @@ -476,39 +476,39 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), /** * 완성되면 아래 함수 주석 풀고 연결 */ - private fun goToTimetableActivity() { - if (menuState != MenuState.Main) { - goToActivityFinish(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) - } else { - val intent = Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java).apply { - if (koinNavigationDrawerViewModel.userInfoFlow.value.isAnonymous) { - putExtra("isAnonymous", true) - } else { - putExtra("isAnonymous", false) - } - } - EventLogger.logClickEvent( - action = EventAction.USER, - label = "hamburger", - value = "시간표" - ) - startActivity(intent) - } - } - // private fun goToTimetableActivity() { // if (menuState != MenuState.Main) { -// goToActivityFinish(Intent(this, TimetableActivity::class.java)) +// goToActivityFinish(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) // } else { +// val intent = Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java).apply { +// if (koinNavigationDrawerViewModel.userInfoFlow.value.isAnonymous) { +// putExtra("isAnonymous", true) +// } else { +// putExtra("isAnonymous", false) +// } +// } // EventLogger.logClickEvent( // action = EventAction.USER, // label = "hamburger", // value = "시간표" // ) -// startActivity(Intent(this, TimetableActivity::class.java)) +// startActivity(intent) // } // } + private fun goToTimetableActivity() { + if (menuState != MenuState.Main) { + goToActivityFinish(Intent(this, TimetableActivity::class.java)) + } else { + EventLogger.logClickEvent( + action = EventAction.USER, + label = "hamburger", + value = "시간표" + ) + startActivity(Intent(this, TimetableActivity::class.java)) + } + } + private fun goToArticleActivity() { val intent = Intent(this, ArticleActivity::class.java) From 9c4f01351d20eb1a65d7b091c21bc81fe1a3df1c Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Fri, 15 Nov 2024 09:45:26 +0900 Subject: [PATCH 10/60] =?UTF-8?q?[fix]=20=EC=9A=94=EC=B2=AD=20DTO=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/data/api/auth/TimetableAuthApi.kt | 6 --- .../repository/TimetableRepositoryImpl.kt | 3 +- .../request/timetable/LecturesQueryRequest.kt | 37 ++++++++----------- .../remote/TimetableRemoteDataSource.kt | 7 ---- 4 files changed, 17 insertions(+), 36 deletions(-) diff --git a/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt b/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt index 8270fb28f..ed743f330 100644 --- a/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt +++ b/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt @@ -1,6 +1,5 @@ package `in`.koreatech.koin.data.api.auth -import `in`.koreatech.koin.data.request.timetable.LecturesCustomQueryRequest import `in`.koreatech.koin.data.request.timetable.LecturesQueryRequest import `in`.koreatech.koin.data.request.timetable.TimetableFrameCreateQueryRequest import `in`.koreatech.koin.data.request.timetable.TimetableFrameQueryRequest @@ -36,11 +35,6 @@ interface TimetableAuthApi { @Body lectures: LecturesQueryRequest ): TimetableLecturesResponse - @POST("/v2/timetables/lecture") - suspend fun postTimetableLectures( - @Body lectures: LecturesCustomQueryRequest - ): TimetableLecturesResponse - @PUT("/v2/timetables/frame/{id}") suspend fun putTimetableFrame( @Path("id") id: Int, diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index 6e2c35ca8..d21f0919e 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -2,7 +2,6 @@ package `in`.koreatech.koin.data.repository import com.google.gson.Gson import com.google.gson.reflect.TypeToken -import `in`.koreatech.koin.data.request.timetable.LecturesCustomQueryRequest import `in`.koreatech.koin.data.request.timetable.LecturesQueryRequest import `in`.koreatech.koin.data.request.timetable.toCustomLectureQueryRequest import `in`.koreatech.koin.data.request.timetable.toLectureQueryRequest @@ -81,7 +80,7 @@ class TimetableRepositoryImpl @Inject constructor( } override suspend fun postTimetableCustomLectures(frameId: Int, lectures: List): Result = runCatching { - timetableRemoteDataSource.postTimetableLectures(LecturesCustomQueryRequest( + timetableRemoteDataSource.postTimetableLectures(LecturesQueryRequest( timetableFrameId = frameId, timetableLecture = lectures.map { it.toCustomLectureQueryRequest() } )).toTimetableLectures() diff --git a/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt b/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt index bcf4177b7..463fccfe7 100644 --- a/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt +++ b/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt @@ -10,46 +10,41 @@ data class LecturesQueryRequest( val timetableLecture: List, ) -data class LecturesCustomQueryRequest( - @SerializedName("timetable_frame_id") - val timetableFrameId: Int, - @SerializedName("timetable_lecture") - val timetableLecture: List, -) - data class LectureQueryRequest( - @SerializedName("lecture_id") - val lectureId: Int, -) - -data class LectureCustomQueryRequest( @SerializedName("lecture_id") val lectureId: Int?, @SerializedName("class_title") - val classTitle: String, + val classTitle: String?, @SerializedName("class_time") - val classTime: List, + val classTime: List?, @SerializedName("class_place") val classPlace: String, @SerializedName("professor") - val professor: String, + val professor: String?, @SerializedName("grades") - val grades: String?, + val grades: String, @SerializedName("memo") - val memo: String?, + val memo: String, ) -fun Lecture.toCustomLectureQueryRequest() = LectureCustomQueryRequest( + +fun Lecture.toCustomLectureQueryRequest() = LectureQueryRequest( classTitle = name, classTime = classTime, classPlace = place ?: "", professor = professor, lectureId = null, - grades = null, - memo = null + grades = grades, + memo = "" ) fun Lecture.toLectureQueryRequest() = LectureQueryRequest( - lectureId = id + classTitle = null, + classTime = null, + classPlace = place ?: "", + professor = null, + lectureId = id, + grades = "0", + memo = "" ) diff --git a/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt index adcee7dd0..0f545471f 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt @@ -2,8 +2,6 @@ package `in`.koreatech.koin.data.source.remote import `in`.koreatech.koin.data.api.TimetableApi import `in`.koreatech.koin.data.api.auth.TimetableAuthApi -import `in`.koreatech.koin.data.request.timetable.LectureCustomQueryRequest -import `in`.koreatech.koin.data.request.timetable.LecturesCustomQueryRequest import `in`.koreatech.koin.data.request.timetable.LecturesQueryRequest import `in`.koreatech.koin.data.request.timetable.TimetableFrameCreateQueryRequest import `in`.koreatech.koin.data.request.timetable.TimetableFrameQueryRequest @@ -45,11 +43,6 @@ class TimetableRemoteDataSource @Inject constructor( lectures: LecturesQueryRequest ): TimetableLecturesResponse = timetableAuthApi.postTimetableLectures(lectures) - - suspend fun postTimetableLectures( - lectures: LecturesCustomQueryRequest - ): TimetableLecturesResponse = timetableAuthApi.postTimetableLectures(lectures) - suspend fun postTimetableFrame( frame: TimetableFrameCreateQueryRequest ): TimetableFrameResponse = timetableAuthApi.postTimetableFrame(frame) From 79fecd0a34a243bd3f311ea5a75607f17d9b4f4d Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Fri, 15 Nov 2024 09:56:47 +0900 Subject: [PATCH 11/60] =?UTF-8?q?[fix]=20grade=20=EC=9A=94=EC=B2=AD=20?= =?UTF-8?q?=EA=B0=92=200=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/data/request/timetable/LecturesQueryRequest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt b/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt index 463fccfe7..7832cadd7 100644 --- a/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt +++ b/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt @@ -34,7 +34,7 @@ fun Lecture.toCustomLectureQueryRequest() = LectureQueryRequest( classPlace = place ?: "", professor = professor, lectureId = null, - grades = grades, + grades = "0", memo = "" ) From 15f679e0c198dc202abde6561987cc587d3dff62 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Sun, 17 Nov 2024 20:06:32 +0900 Subject: [PATCH 12/60] =?UTF-8?q?[Add]=20=EC=9E=91=EC=97=85=20=EA=B3=BC?= =?UTF-8?q?=EC=A0=951?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/data/api/auth/TimetableAuthApi.kt | 8 +- .../repository/TimetableRepositoryImpl.kt | 32 ++- .../timetable/TimetableFrameQueryRequest.kt | 2 +- .../remote/TimetableRemoteDataSource.kt | 8 +- .../timetable/request/TimetableFrameQuery.kt | 2 +- .../domain/repository/TimetableRepository.kt | 8 +- .../usecase/timetable/AddSemesterUseCase.kt | 18 ++ .../timetable/AddTimetableFrameUseCase.kt | 18 ++ .../timetable/DeleteSemesterUseCase.kt | 16 ++ .../timetable/DeleteTimetableFrameUseCase.kt | 13 ++ .../timetable/UpdateTimetableFrameUseCase.kt | 27 +++ .../feature/timetable/view/SemesterScreen.kt | 28 ++- .../view/dialog/DeleteSemesterDialog.kt | 103 ++++++++++ .../view/dialog/EditSemesterDialog.kt | 4 +- .../view/dialog/EditTimetableFrameDialog.kt | 35 ++-- .../timetable/viewmodel/SemesterViewModel.kt | 188 +++++++++++++++--- .../timetable/src/main/res/values/strings.xml | 7 + .../KoinNavigationDrawerActivity.kt | 4 +- .../koin/ui/timetablev2/TimetableActivity.kt | 2 +- .../timetablev2/TimetableSemesterActivity.kt | 83 ++++++-- 20 files changed, 514 insertions(+), 92 deletions(-) create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/AddSemesterUseCase.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/AddTimetableFrameUseCase.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/DeleteSemesterUseCase.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/DeleteTimetableFrameUseCase.kt create mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateTimetableFrameUseCase.kt create mode 100644 feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/DeleteSemesterDialog.kt diff --git a/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt b/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt index ed743f330..75c4fecd7 100644 --- a/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt +++ b/data/src/main/java/in/koreatech/koin/data/api/auth/TimetableAuthApi.kt @@ -47,7 +47,9 @@ interface TimetableAuthApi { ): TimetableFrameResponse @DELETE("/v2/timetables/frame") - suspend fun deleteTimetableFrame() + suspend fun deleteTimetableFrame( + @Query("id") frameId: Int + ): Response @GET("/v2/timetables/frames") suspend fun getTimetableFrames( @@ -71,5 +73,7 @@ interface TimetableAuthApi { ): Response @DELETE("/v2/all/timetables/frame") - suspend fun deleteAllTimetableFrame() + suspend fun deleteAllTimetableFrame( + @Query("semester") semester: String, + ): Response } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index d21f0919e..a8d0ec26a 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -3,6 +3,8 @@ package `in`.koreatech.koin.data.repository import com.google.gson.Gson import com.google.gson.reflect.TypeToken import `in`.koreatech.koin.data.request.timetable.LecturesQueryRequest +import `in`.koreatech.koin.data.request.timetable.TimetableFrameCreateQueryRequest +import `in`.koreatech.koin.data.request.timetable.TimetableFrameQueryRequest import `in`.koreatech.koin.data.request.timetable.toCustomLectureQueryRequest import `in`.koreatech.koin.data.request.timetable.toLectureQueryRequest import `in`.koreatech.koin.data.request.timetable.toTimetableLecturesQueryRequest @@ -46,7 +48,7 @@ class TimetableRepositoryImpl @Inject constructor( timetableRemoteDataSource.getTimetableLectures(timetableFrameId).toTimetableLectures() } - override suspend fun getTimetableLectures(semester: String): Result = runCatching{ + override suspend fun getTimetableLectures(semester: String): Result = runCatching { val timetableLecturesString = timetableDataStore.getString(semester).firstOrNull().orEmpty() val timetableLecturesType = object : TypeToken() {}.type try { @@ -68,8 +70,14 @@ class TimetableRepositoryImpl @Inject constructor( } } - override suspend fun putTimetableFrame(id: Int, frame: TimetableFrameQuery): TimetableFrame { - TODO("Not yet implemented") + override suspend fun putTimetableFrame(id: Int, frame: TimetableFrameQuery): Result = runCatching { + timetableRemoteDataSource.putTimetableFrame( + id, + TimetableFrameQueryRequest( + frame.timetableName, + frame.isMain + ) + ).toTimetableFrameResponse() } override suspend fun postTimetableLectures(frameId: Int, lectures: List): Result = runCatching { @@ -87,12 +95,18 @@ class TimetableRepositoryImpl @Inject constructor( } - override suspend fun postTimetableFrame(frame: TimetableFrameCreateQuery): TimetableFrame { - TODO("Not yet implemented") + override suspend fun postTimetableFrame(frame: TimetableFrameCreateQuery): Result = runCatching { + timetableRemoteDataSource.postTimetableFrame( + TimetableFrameCreateQueryRequest( + semester = frame.semester, + timetableName = frame.timetableName + ) + ).toTimetableFrameResponse() } - override suspend fun deleteTimetableFrame() { - TODO("Not yet implemented") + + override suspend fun deleteTimetableFrame(frameId: Int): Result = runCatching { + timetableRemoteDataSource.deleteTimetableFrame(frameId) } override suspend fun deleteTimetableLecture(id: Int): Result = runCatching { @@ -107,7 +121,7 @@ class TimetableRepositoryImpl @Inject constructor( timetableRemoteDataSource.deleteTimetableLectures(lectureIds) } - override suspend fun deleteAllTimetableFrame() { - TODO("Not yet implemented") + override suspend fun deleteAllTimetableFrame(semester: String): Result = runCatching { + timetableRemoteDataSource.deleteAllTimetableFrame(semester) } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/request/timetable/TimetableFrameQueryRequest.kt b/data/src/main/java/in/koreatech/koin/data/request/timetable/TimetableFrameQueryRequest.kt index e94c2676a..d523b9e28 100644 --- a/data/src/main/java/in/koreatech/koin/data/request/timetable/TimetableFrameQueryRequest.kt +++ b/data/src/main/java/in/koreatech/koin/data/request/timetable/TimetableFrameQueryRequest.kt @@ -13,5 +13,5 @@ data class TimetableFrameCreateQueryRequest( @SerializedName("semester") val semester: String, @SerializedName("timetable_name") - val timetableName: Boolean, + val timetableName: String?, ) \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt index 9fa87eb23..49fc165ba 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/remote/TimetableRemoteDataSource.kt @@ -48,7 +48,9 @@ class TimetableRemoteDataSource @Inject constructor( frame: TimetableFrameCreateQueryRequest ): TimetableFrameResponse = timetableAuthApi.postTimetableFrame(frame) - suspend fun deleteTimetableFrame() = timetableAuthApi.deleteTimetableFrame() + suspend fun deleteTimetableFrame( + frameId: Int + ) = timetableAuthApi.deleteTimetableFrame(frameId) suspend fun deleteTimetableLecture( @@ -64,5 +66,7 @@ class TimetableRemoteDataSource @Inject constructor( lectureIds: List ) = timetableAuthApi.deleteTimetableLectures(lectureIds) - suspend fun deleteAllTimetableFrame() = timetableAuthApi.deleteAllTimetableFrame() + suspend fun deleteAllTimetableFrame( + semester: String + ) = timetableAuthApi.deleteAllTimetableFrame(semester) } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/timetable/request/TimetableFrameQuery.kt b/domain/src/main/java/in/koreatech/koin/domain/model/timetable/request/TimetableFrameQuery.kt index 86cd4c30e..fec4018bb 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/model/timetable/request/TimetableFrameQuery.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/model/timetable/request/TimetableFrameQuery.kt @@ -7,5 +7,5 @@ data class TimetableFrameQuery( data class TimetableFrameCreateQuery( val semester: String, - val timetableName: Boolean, + val timetableName: String?, ) diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt index 79955fc69..fe263a8a1 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt @@ -19,16 +19,16 @@ interface TimetableRepository { suspend fun putTimetableLectures(lectures: TimetableLecturesQuery): TimetableLectures suspend fun putTimetableLectures(key: String, value: TimetableLectures): Result - suspend fun putTimetableFrame(id: Int, frame: TimetableFrameQuery): TimetableFrame + suspend fun putTimetableFrame(id: Int, frame: TimetableFrameQuery): Result suspend fun postTimetableLectures(frameId: Int, lectures: List): Result suspend fun postTimetableCustomLectures(frameId: Int, lectures: List): Result - suspend fun postTimetableFrame(frame: TimetableFrameCreateQuery): TimetableFrame + suspend fun postTimetableFrame(frame: TimetableFrameCreateQuery): Result - suspend fun deleteTimetableFrame() + suspend fun deleteTimetableFrame(frameId: Int): Result suspend fun deleteTimetableLecture(id: Int): Result suspend fun deleteTimetableLectures(lectureIds: List): Result suspend fun deleteTimetableFrameLecture(frameId: Int, lectureId: Int): Result - suspend fun deleteAllTimetableFrame() + suspend fun deleteAllTimetableFrame(semester: String): Result } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/AddSemesterUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/AddSemesterUseCase.kt new file mode 100644 index 000000000..9fd3cad04 --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/AddSemesterUseCase.kt @@ -0,0 +1,18 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.model.timetable.request.TimetableFrameCreateQuery +import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + +class AddSemesterUseCase @Inject constructor( + private val timetableRepository: TimetableRepository +) { + + suspend operator fun invoke(semester: String): Result { + return timetableRepository.postTimetableFrame(TimetableFrameCreateQuery( + semester = semester, + null + )) + } +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/AddTimetableFrameUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/AddTimetableFrameUseCase.kt new file mode 100644 index 000000000..910de9e9b --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/AddTimetableFrameUseCase.kt @@ -0,0 +1,18 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.model.timetable.request.TimetableFrameCreateQuery +import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + +class AddTimetableFrameUseCase @Inject constructor( + private val timetableRepository: TimetableRepository +) { + // TODO::hyeok 시간표 이름 인자에서 없애고 내부에서 처리하는게 나을듯 + suspend operator fun invoke(semester: String, timetableName: String): Result { + return timetableRepository.postTimetableFrame(TimetableFrameCreateQuery( + semester = semester, + timetableName = timetableName + )) + } +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/DeleteSemesterUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/DeleteSemesterUseCase.kt new file mode 100644 index 000000000..9eb989304 --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/DeleteSemesterUseCase.kt @@ -0,0 +1,16 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + + +/** + * 학기에 포함된 시간표들을 모두 삭제함 + */ +class DeleteSemesterUseCase @Inject constructor( + private val timetableRepository: TimetableRepository +){ + suspend operator fun invoke(semester: String): Result { + return timetableRepository.deleteAllTimetableFrame(semester) + } +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/DeleteTimetableFrameUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/DeleteTimetableFrameUseCase.kt new file mode 100644 index 000000000..a531cf761 --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/DeleteTimetableFrameUseCase.kt @@ -0,0 +1,13 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + +class DeleteTimetableFrameUseCase @Inject constructor( + private val timetableRepository: TimetableRepository +) { + + suspend operator fun invoke(frameId: Int): Result { + return timetableRepository.deleteTimetableFrame(frameId) + } +} \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateTimetableFrameUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateTimetableFrameUseCase.kt new file mode 100644 index 000000000..82ed32c7d --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/UpdateTimetableFrameUseCase.kt @@ -0,0 +1,27 @@ +package `in`.koreatech.koin.domain.usecase.timetable + +import `in`.koreatech.koin.domain.model.timetable.request.TimetableFrameQuery +import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame +import `in`.koreatech.koin.domain.repository.TimetableRepository +import javax.inject.Inject + + +class UpdateTimetableFrameUseCase @Inject constructor( + private val timetableRepository: TimetableRepository +) { + /** + * 시간표 프레임 정보 업데이트, + * @param id 대상 시간표 프레임 + * @param name 변경할 프레임 이름 + * @param isMain 변경할 프레임 기본 시간표 여부 + */ + suspend operator fun invoke(id: Int, name: String, isMain: Boolean): Result { + return timetableRepository.putTimetableFrame( + id, + TimetableFrameQuery( + timetableName = name, + isMain = isMain + ) + ) + } +} \ No newline at end of file diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt index 60aaaf45d..5d58a1b8c 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt @@ -38,9 +38,9 @@ fun SemesterScreen( userTimetables: Map>, isAnonymous: Boolean, modifier: Modifier = Modifier, - onClickTimetable: (TimetableFrame) -> Unit = {}, - onClickAddTimetable: () -> Unit = {}, - onClickEditTimetable: () -> Unit = {}, + onClickTimetable: (SemesterModel, TimetableFrame) -> Unit = { _, _ -> }, + onClickAddTimetable: (SemesterModel) -> Unit = {}, + onClickEditTimetable: (SemesterModel, TimetableFrame) -> Unit = {_, _ -> }, ) { Box( modifier = modifier @@ -70,9 +70,15 @@ fun SemesterScreen( semesterModel = semesterModel, timetableFrames = timetableFrames, isAnonymous = isAnonymous, - onClickTimetable = onClickTimetable, - onClickAddTimetable = onClickAddTimetable, - onClickEditTimetable = onClickEditTimetable + onClickTimetable = { timetableFrame -> + onClickTimetable(semesterModel, timetableFrame) + }, + onClickAddTimetable = { + onClickAddTimetable(semesterModel) + }, + onClickEditTimetable = { + onClickEditTimetable(semesterModel, it) + } ) } } @@ -87,7 +93,7 @@ private fun LazyListScope.SemesterBlock( isAnonymous: Boolean, onClickTimetable: (TimetableFrame) -> Unit = {}, onClickAddTimetable: () -> Unit = {}, - onClickEditTimetable: () -> Unit = {} + onClickEditTimetable: (TimetableFrame) -> Unit = {} ) { item { Column( @@ -134,7 +140,7 @@ private fun LazyListScope.SemesterBlock( timetableFrame = frame, isAnonymous = isAnonymous, onClickTimetable = onClickTimetable, - onClickEditTimetable = onClickAddTimetable + onClickEditTimetable = onClickEditTimetable ) } } @@ -143,7 +149,7 @@ private fun LazyListScope.TimetableFrameBlock( timetableFrame: TimetableFrame, isAnonymous: Boolean, onClickTimetable: (TimetableFrame) -> Unit = {}, - onClickEditTimetable: () -> Unit + onClickEditTimetable: (TimetableFrame) -> Unit ) { item { HorizontalDivider( @@ -179,7 +185,9 @@ private fun LazyListScope.TimetableFrameBlock( if (!isAnonymous) { Text( - modifier = Modifier.noRippleClickable { onClickEditTimetable() }, + modifier = Modifier.noRippleClickable { + onClickEditTimetable(timetableFrame) + }, text = stringResource(id = R.string.semester_edit_timetable_frame), style = KoinTheme.typography.bold16 ) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/DeleteSemesterDialog.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/DeleteSemesterDialog.kt new file mode 100644 index 000000000..116e56414 --- /dev/null +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/DeleteSemesterDialog.kt @@ -0,0 +1,103 @@ +package `in`.koreatech.koin.feature.timetable.view.dialog + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringArrayResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.core.designsystem.theme.KoinTheme +import `in`.koreatech.koin.feature.timetable.R +import `in`.koreatech.koin.feature.timetable.component.FilledButtonType +import `in`.koreatech.koin.feature.timetable.component.FilledTextButton +import `in`.koreatech.koin.feature.timetable.component.HighlightedText + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DeleteSemesterDialog( + modifier: Modifier = Modifier, + onDismiss: () -> Unit = {}, + onConfirm: () -> Unit = {} +) { + BasicAlertDialog( + onDismissRequest = onDismiss, + modifier = modifier, + ) { + Box( + modifier = Modifier + .wrapContentWidth() + .wrapContentHeight() + .background(color = KoinTheme.colors.neutral0, shape = KoinTheme.shapes.extraSmall) + .padding(24.dp) + ) { + Column { + HighlightedText( + texts = stringArrayResource(id = R.array.delete_semester_title), + highlightIndices = listOf(1), + defaultStyle = KoinTheme.typography.medium16, + highlightStyle = KoinTheme.typography.bold16.copy( + color = KoinTheme.colors.danger700 + ) + ) + Spacer(modifier = Modifier.height(24.dp)) + Row( + modifier = Modifier.wrapContentHeight(), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + OutlinedButton( + modifier = Modifier + .height(48.dp) + .weight(1.0F), + colors = ButtonColors( + containerColor = KoinTheme.colors.neutral0, + contentColor = KoinTheme.colors.neutral500, + disabledContainerColor = KoinTheme.colors.neutral400, + disabledContentColor = KoinTheme.colors.neutral500 + ), + shape = MaterialTheme.shapes.extraSmall, + contentPadding = PaddingValues(0.dp), + border = BorderStroke(1.dp, KoinTheme.colors.neutral500), + onClick = { onDismiss() } + ) { + Text(text = stringResource(id = R.string.common_cancellation), style = KoinTheme.typography.medium15, color = KoinTheme.colors.neutral600) + } + FilledTextButton( + modifier = Modifier + .height(48.dp) + .weight(1.0F), + text = stringResource(id = R.string.delete_semester_confirmation), + buttonStyle = FilledButtonType.Danger, + onClick = { onConfirm() } + ) + } + } + + } + } +} + + +@Preview +@Composable +private fun DeleteSemesterUseCasePreview() { + DeleteSemesterDialog() +} \ No newline at end of file diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt index 9f0c753e5..2d89d6bb9 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt @@ -60,7 +60,9 @@ fun EditSemesterDialogImpl( var isShowingSelectYearDialog by remember { mutableStateOf(false) } // TODO::hyeok 연도 눌렀을 때 깜빡거림 - Box { + Box( + modifier = modifier + ) { if (isShowingSelectYearDialog) { SelectYearDialog( currentYear = currentYear, diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt index 4b607a3a3..a9190252f 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt @@ -36,28 +36,24 @@ import `in`.koreatech.koin.core.designsystem.component.button.FilledButtonColors import `in`.koreatech.koin.core.designsystem.component.button.OutlinedBoxButton import `in`.koreatech.koin.core.designsystem.component.button.OutlinedBoxButtonColors import `in`.koreatech.koin.core.designsystem.theme.KoinTheme +import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame import `in`.koreatech.koin.feature.timetable.R import `in`.koreatech.koin.feature.timetable.component.FilledButtonType import `in`.koreatech.koin.feature.timetable.component.FilledTextButton import `in`.koreatech.koin.feature.timetable.component.HighlightedText import `in`.koreatech.koin.feature.timetable.component.TextCheckbox -data class TimeTableFrameState( - val id: Int, - val timetableName: String, - val isMain: Boolean -) @Composable fun EditTimetableFrameDialog( - timetableFrameState: TimeTableFrameState, + timetableFrameState: TimetableFrame?, onDismiss: () -> Unit, - onConfirmEdit: (TimeTableFrameState) -> Unit, - onDeleteFrame: (TimeTableFrameState) -> Unit, + onConfirmEdit: (TimetableFrame) -> Unit, + onDeleteFrame: () -> Unit, modifier: Modifier = Modifier ) { - var isMain by remember { mutableStateOf(timetableFrameState.isMain) } - var timetableName by remember { mutableStateOf(timetableFrameState.timetableName) } + var isMain by remember { mutableStateOf(timetableFrameState?.isMain ?: false) } + var timetableName by remember { mutableStateOf(timetableFrameState?.timetableName ?: "") } var showingDeleteDialog by remember { mutableStateOf(false) } EditTimetableFrameDialog( @@ -66,12 +62,15 @@ fun EditTimetableFrameDialog( isMain = isMain, onDismiss = onDismiss, onConfirm = { - onConfirmEdit( - timetableFrameState.copy( - timetableName = timetableName, - isMain = isMain + timetableFrameState?.let { + onConfirmEdit( + it.copy( + timetableName = timetableName, + isMain = isMain + ) ) - ) + } + }, onClickDelete = { showingDeleteDialog = true }, onValueChanged = { @@ -87,7 +86,9 @@ fun EditTimetableFrameDialog( timetableName = timetableName, onDismiss = { showingDeleteDialog = false }, onConfirm = { - onDeleteFrame(timetableFrameState) + timetableFrameState?.let { + onDeleteFrame() + } } ) } @@ -268,7 +269,7 @@ private fun DeleteTimetableFrameDialog( private fun EditTimetableFrameDialogPreview() { KoinTheme { EditTimetableFrameDialog( - timetableFrameState = TimeTableFrameState( + timetableFrameState = TimetableFrame( id = 1, timetableName = "시간표1", isMain = true diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index cc0bacd65..664231b64 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -4,9 +4,15 @@ import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import `in`.koreatech.koin.core.viewmodel.BaseViewModel import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame +import `in`.koreatech.koin.domain.usecase.timetable.AddSemesterUseCase +import `in`.koreatech.koin.domain.usecase.timetable.AddTimetableFrameUseCase +import `in`.koreatech.koin.domain.usecase.timetable.DeleteSemesterUseCase +import `in`.koreatech.koin.domain.usecase.timetable.DeleteTimetableFrameUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetSemesterTimetableFramesUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetSemestersUseCase +import `in`.koreatech.koin.domain.usecase.timetable.GetTimetableFramesUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetUserSemestersUseCase +import `in`.koreatech.koin.domain.usecase.timetable.UpdateTimetableFrameUseCase import `in`.koreatech.koin.domain.usecase.user.GetUserStatusUseCase import `in`.koreatech.koin.feature.timetable.model.SemesterModel import `in`.koreatech.koin.feature.timetable.utils.toSemesterModel @@ -14,9 +20,13 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -24,35 +34,62 @@ class SemesterViewModel @Inject constructor( private val getUserSemestersUseCase: GetUserSemestersUseCase, private val getSemestersUseCase: GetSemestersUseCase, private val getUserStatusUseCase: GetUserStatusUseCase, - private val getSemesterTimetableFramesUseCase: GetSemesterTimetableFramesUseCase + private val getSemesterTimetableFramesUseCase: GetSemesterTimetableFramesUseCase, + private val getTimetableFramesUseCase: GetTimetableFramesUseCase, + private val deleteSemesterUseCase: DeleteSemesterUseCase, + private val addSemesterUseCase: AddSemesterUseCase, + private val addTimetableFrameUseCase: AddTimetableFrameUseCase, + private val updateTimetableFrameUseCase: UpdateTimetableFrameUseCase, + private val deleteTimetableFrameUseCase: DeleteTimetableFrameUseCase ) : BaseViewModel() { - private val _dialogUiState: MutableStateFlow = MutableStateFlow(SemesterDialogUiState()) - val dialogUiState: StateFlow get() = _dialogUiState.asStateFlow() + private val _dialogUiState: MutableStateFlow = MutableStateFlow(SemesterDialogUiState()) + val dialogUiState: StateFlow = _dialogUiState.asStateFlow() - private val _isAnonymous: MutableStateFlow = MutableStateFlow(false) - val isAnonymous: StateFlow = _isAnonymous.asStateFlow() + private val _isAnonymous: MutableStateFlow = MutableStateFlow(true) + val isAnonymous: StateFlow = _isAnonymous.asStateFlow() - val semesters: StateFlow> = getSemestersUseCase() - .map { it.map { it.toSemesterModel() }} - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), emptyList()) + private val _userSemester: MutableStateFlow> = MutableStateFlow(emptyList()) + val userSemesters2: StateFlow> = _userSemester.asStateFlow() - val userSemesters: StateFlow> = isAnonymous - .flatMapLatest { getUserSemestersUseCase(it) } + private val _userTimetableFrames: MutableStateFlow>> = MutableStateFlow(emptyMap()) + val userTimetableFrames2: StateFlow>> = _userTimetableFrames.asStateFlow() + + val semesters: StateFlow> = getSemestersUseCase() .map { it.map { it.toSemesterModel() } } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), emptyList()) - val userTimetableFrames = userSemesters - .flatMapLatest { getSemesterTimetableFramesUseCase(isAnonymous.value, it.map { it.toSemester() }) } - .map { it.mapKeys { it.key.toSemesterModel() } } - .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), emptyMap()) - val years: StateFlow> = semesters .map { it.map { it.year }.distinct() } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), emptyList()) + fun initData() { + viewModelScope.launch { + getUserSemestersUseCase(_isAnonymous.value) + .catch { Timber.d("Fail to getUserSemestersUseCase on initData()| message: ${it.message}") } + .map { it.map { it.toSemesterModel() } } + .collect { + _userSemester.value = it + } + // scan + val tmp = mutableMapOf>() + userSemesters2.value + .map { it.toSemester() } + .forEach { semester -> + getTimetableFramesUseCase(semester) + .catch { Timber.d("Fail to getUserSemestersUseCase on initData()| message: ${it.message}") } + .collect { + tmp.put(semester.toSemesterModel(), it) + } + } + _userTimetableFrames.value = tmp + } + } + fun updateIsAnonymous(isAnonymous: Boolean) { - _isAnonymous.value = isAnonymous + viewModelScope.launch { + _isAnonymous.value = isAnonymous + } } fun updateEditTimetableDialogVisibility(isVisible: Boolean) { @@ -63,26 +100,125 @@ class SemesterViewModel @Inject constructor( _dialogUiState.value = _dialogUiState.value.copy(isEditSemesterDialogVisible = isVisible) } - fun onClickTimetable(timetableFrame: TimetableFrame) { - // TODO::hyeok 시간표로 이동 + fun updateDeleteSemesterDialogVisible(isVisible: Boolean) { + _dialogUiState.value = _dialogUiState.value.copy(isDeleteSemesterDialogVisible = isVisible) } - fun onClickAddTimetable() { - // TODO::hyeok 새로운 시간표 추가 + fun updateSelectedSemesters(semesterModels: List) { + _dialogUiState.value = _dialogUiState.value.copy(selectedSemesters = semesterModels) } - fun onClickEditTimetable() { + fun onClickAddTimetable(target: SemesterModel) { + viewModelScope.launch { + addTimetableFrameUseCase( + semester = target.toSemester(), + timetableName = "시간표${(userTimetableFrames2.value[target]?.size ?: 1) + 1}" + ).onSuccess { addedFrame -> + _userTimetableFrames.update { it -> + it.mapValues { + if (it.key == target) + it.value + addedFrame + else + it.value + } + } + }.onFailure { + Timber.d("시간표 추가 실패") + } + } + } + + fun onClickEditTimetable(targetSemester: SemesterModel, targetFrame: TimetableFrame) { _dialogUiState.value = _dialogUiState.value.copy( - isEditTimetableDialogVisible = true + isEditTimetableDialogVisible = true, + editedSemester = targetSemester, + editedTimetableFrame = targetFrame ) } - fun onEditSemesters(semesterModels: List) { - // 학기 추가 및 삭제 + /** + * @input 유저가 선택한 학기 리스트 + */ + fun updateUserSemesters() { + viewModelScope.launch { + dialogUiState.value.selectedSemesters.forEach { semester -> + if (userSemesters2.value.contains(semester)) { + deleteSemesterUseCase(semester.toSemester()).onSuccess { + _userSemester.update { + it - semester + } + _userTimetableFrames.update { + it - semester + } + } + } else { + addSemesterUseCase(semester.toSemester()).onSuccess { addedFrame -> + _userSemester.update { + it + semester + } + _userTimetableFrames.update { + it + (semester to listOf(addedFrame)) + } + } + } + } + } + } + + fun editTimetableFrame(timetableFrame: TimetableFrame) { + Timber.d("change timetable from ${dialogUiState} to $timetableFrame") + viewModelScope.launch { + updateTimetableFrameUseCase( + id = timetableFrame.id, + name = timetableFrame.timetableName, + isMain = timetableFrame.isMain, + ).onSuccess { + _userTimetableFrames.update { + it.mapValues { + it.value.map { if (it.id == timetableFrame.id) timetableFrame else it } + } + } + }.onFailure { + Timber.d("시간표 프레임 수정 실패") + } + } + } + + fun deleteTimetableFrame() { + viewModelScope.launch { + dialogUiState.value.editedTimetableFrame?.let { target -> + deleteTimetableFrameUseCase( + frameId = target.id + ).onSuccess { + dialogUiState.value.editedSemester?.let { + refreshSemesterTimetableFrames(it) + } + }.onFailure { + Timber.d("시간표 프레임 삭제 실패") + } + } + } + } + + private suspend fun refreshSemesterTimetableFrames(semester: SemesterModel) { + getTimetableFramesUseCase(semester.toSemester()) + .catch { Timber.d("Fail to getTimetableFramesUseCase on refreshSemesterTimetableFrames()| message: ${it.message}") } + .firstOrNull() + ?.let { newFrames -> + _userTimetableFrames.update { + it.mapValues { + if (it.key == semester) newFrames else it.value + } + } + } } } data class SemesterDialogUiState( + val editedSemester: SemesterModel? = null, + val selectedSemesters: List = emptyList(), + val editedTimetableFrame: TimetableFrame? = null, val isEditTimetableDialogVisible: Boolean = false, - val isEditSemesterDialogVisible: Boolean = false + val isEditSemesterDialogVisible: Boolean = false, + val isDeleteSemesterDialogVisible: Boolean = false ) \ No newline at end of file diff --git a/feature/timetable/src/main/res/values/strings.xml b/feature/timetable/src/main/res/values/strings.xml index 0f8212040..cf7b305b0 100644 --- a/feature/timetable/src/main/res/values/strings.xml +++ b/feature/timetable/src/main/res/values/strings.xml @@ -34,6 +34,13 @@ 삭제한 개인 일정은 복구할 수 없어요. 삭제하기 + + 시간표가 작성되어 있는 학기가있어요. 해당 학기를 제외할 경우 + " 학기 내 시간표도 함께 삭제" + 돼요. + + 삭제하기 + 로그인이 필요한 기능이에요. 로그인 하시겠어요? 로그인하기 diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt index 5822d523d..80a4872f7 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt @@ -45,6 +45,7 @@ import `in`.koreatech.koin.ui.setting.SettingActivity import `in`.koreatech.koin.ui.store.activity.StoreActivity import `in`.koreatech.koin.ui.timetable.TimetableActivity import `in`.koreatech.koin.ui.timetable.TimetableAnonymousActivity +import `in`.koreatech.koin.ui.timetablev2.TimetableSemesterActivity import `in`.koreatech.koin.util.ext.addDrawerListener import `in`.koreatech.koin.util.ext.blueStatusBar import `in`.koreatech.koin.util.ext.closeDrawer @@ -314,7 +315,8 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), MenuState.Article -> goToArticleActivity() MenuState.Contact -> { - goToContactWebActivity() + startActivity(Intent(this@KoinNavigationDrawerActivity, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) +// goToContactWebActivity() } else -> Unit diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index ce88ce490..e22b7175e 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -319,7 +319,7 @@ class TimetableActivity : KoinNavigationDrawerActivity() { } private fun getUserExtra(callback: (isAnonymous: Boolean) -> Unit) { - callback(intent.getBooleanExtra("isAnonymous", true)) + callback(intent.getBooleanExtra("isAnonymous", false)) } private fun startToTimetableSemesterActivity() { diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index 3cb65cead..02d57178e 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -1,21 +1,23 @@ package `in`.koreatech.koin.ui.timetablev2 -import android.os.Build import android.os.Bundle import androidx.activity.viewModels -import androidx.annotation.RequiresApi import androidx.compose.runtime.getValue import androidx.core.os.bundleOf import androidx.lifecycle.compose.collectAsStateWithLifecycle import dagger.hilt.android.AndroidEntryPoint import `in`.koreatech.koin.R import `in`.koreatech.koin.core.activity.ActivityBase +import `in`.koreatech.koin.core.appbar.AppBarBase import `in`.koreatech.koin.core.designsystem.theme.KoinTheme import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.ActivityTimetableSemesterBinding import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame import `in`.koreatech.koin.feature.timetable.model.SemesterModel import `in`.koreatech.koin.feature.timetable.view.SemesterScreen +import `in`.koreatech.koin.feature.timetable.view.dialog.DeleteSemesterDialog +import `in`.koreatech.koin.feature.timetable.view.dialog.EditSemesterDialogImpl +import `in`.koreatech.koin.feature.timetable.view.dialog.EditTimetableFrameDialog import `in`.koreatech.koin.feature.timetable.viewmodel.SemesterViewModel import timber.log.Timber @@ -26,24 +28,75 @@ class TimetableSemesterActivity : ActivityBase() { private val binding by dataBinding() private val viewModel by viewModels() - @RequiresApi(Build.VERSION_CODES.O) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_timetable_semester) getIntentBundle { isAnonymous -> Timber.e("isAnonymous : $isAnonymous") - viewModel.updateIsAnonymous(isAnonymous) + viewModel.updateIsAnonymous(false) } + viewModel.initData() binding.timetableListComposeView.setContent { KoinTheme { val dialogUiState by viewModel.dialogUiState.collectAsStateWithLifecycle() val isAnonymous by viewModel.isAnonymous.collectAsStateWithLifecycle() - val userTimetables by viewModel.userTimetableFrames.collectAsStateWithLifecycle() + val userTimetables by viewModel.userTimetableFrames2.collectAsStateWithLifecycle() - val userSemesters by viewModel.userSemesters.collectAsStateWithLifecycle() + val userSemesters by viewModel.userSemesters2.collectAsStateWithLifecycle() val years by viewModel.years.collectAsStateWithLifecycle() + if (dialogUiState.isEditSemesterDialogVisible) { + EditSemesterDialogImpl( + years = years, + userSemesters = userSemesters, + onConfirm = { selectedSemesters -> + viewModel.updateSelectedSemesters(selectedSemesters) + var isLectureExist = false + selectedSemesters.forEach { + // TODO::hyeok 강의 유무 확인해서 띄우기 + if(userTimetables.contains(it) && userTimetables[it]?.isEmpty() == true) + isLectureExist = true + } + + if(isLectureExist) { + viewModel.updateDeleteSemesterDialogVisible(true) + } else { + viewModel.updateUserSemesters() + } + }, + onDismiss = { viewModel.updateEditSemesterDialogVisible(false) } + ) + } + if (dialogUiState.isEditTimetableDialogVisible) { + EditTimetableFrameDialog( + timetableFrameState = dialogUiState.editedTimetableFrame, + onDismiss = { viewModel.updateEditTimetableDialogVisibility(false) }, + onConfirmEdit = { + viewModel.editTimetableFrame(it) + viewModel.updateEditTimetableDialogVisibility(false) + }, + onDeleteFrame = { + viewModel.deleteTimetableFrame() + viewModel.updateEditTimetableDialogVisibility(false) + } + ) + } + if (dialogUiState.isDeleteSemesterDialogVisible) { + DeleteSemesterDialog( + onDismiss = { + viewModel.updateDeleteSemesterDialogVisible(false) + viewModel.updateEditSemesterDialogVisible(false) + }, + onConfirm = { + viewModel.updateUserSemesters() + viewModel.updateDeleteSemesterDialogVisible(false) + viewModel.updateEditSemesterDialogVisible(false) + + } + ) + } + SemesterScreen( userTimetables = userTimetables, isAnonymous = isAnonymous, @@ -52,17 +105,13 @@ class TimetableSemesterActivity : ActivityBase() { onClickEditTimetable = viewModel::onClickEditTimetable ) - if (dialogUiState.isEditSemesterDialogVisible) { - // TODO::hyeok 학기 수정 다이얼로그 -// EditSemesterDialogImpl( -// years = years, -// userSemesters = userSemesters, -// onConfirm = viewModel::onEditSemesters, -// onDismiss = { viewModel.updateEditSemesterDialogVisible(false) } -// ) - } else if (dialogUiState.isEditTimetableDialogVisible) { - // TODO::hyeok 시간표 수정 다이얼로그 - } + } + } + + binding.timetableListAppbar.setOnClickListener { + when (it.id) { + AppBarBase.getLeftButtonId() -> onBackPressed() + AppBarBase.getRightButtonId() -> viewModel.updateEditSemesterDialogVisible(true) } } } From 8d7a672402a08daf0b5606a0d04810ba419f60d0 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 17 Nov 2024 22:14:47 +0900 Subject: [PATCH 13/60] =?UTF-8?q?[del]=20GetSemesterUseCase=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/usecase/timetable/GetSemesterUseCase.kt | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterUseCase.kt diff --git a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterUseCase.kt b/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterUseCase.kt deleted file mode 100644 index 66b7bdfb8..000000000 --- a/domain/src/main/java/in/koreatech/koin/domain/usecase/timetable/GetSemesterUseCase.kt +++ /dev/null @@ -1,13 +0,0 @@ -package `in`.koreatech.koin.domain.usecase.timetable - -import `in`.koreatech.koin.domain.repository.TimetableRepository -import kotlinx.coroutines.flow.Flow -import javax.inject.Inject - -@Deprecated("GetUserSemestersUseCase 로 변경") -class GetSemesterUseCase @Inject constructor( - private val timetableRepository: TimetableRepository -) { - operator fun invoke(isAnonymous: Boolean): Flow> = - if (isAnonymous) timetableRepository.getSemesters() else timetableRepository.getSemesterCheck() -} \ No newline at end of file From 95af304fc23c559f1db8bfe8fcef277ea30c94df Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 17 Nov 2024 22:15:07 +0900 Subject: [PATCH 14/60] =?UTF-8?q?[add]=20=EB=A1=9C=EB=94=A9=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/viewmodel/TimetableViewModel.kt | 61 +++++++++++++++---- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt index acfd95ccf..502329c1c 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt @@ -12,8 +12,8 @@ import `in`.koreatech.koin.domain.usecase.timetable.AddTimetableLectureUseCase import `in`.koreatech.koin.domain.usecase.timetable.DeleteTimetableFrameLectureUseCase import `in`.koreatech.koin.domain.usecase.timetable.DeleteTimetableLectureUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetLecturesUseCase -import `in`.koreatech.koin.domain.usecase.timetable.GetSemesterUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetTimetableFramesUseCase +import `in`.koreatech.koin.domain.usecase.timetable.GetUserSemestersUseCase import `in`.koreatech.koin.feature.timetable.model.TimetableEvent import `in`.koreatech.koin.feature.timetable.state.BottomSheetUI import `in`.koreatech.koin.feature.timetable.state.CustomContentState @@ -43,13 +43,12 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import timber.log.Timber -import java.time.LocalTime import javax.inject.Inject @HiltViewModel class TimetableViewModel @Inject constructor( private val getLecturesUseCase: GetLecturesUseCase, - private val getSemesterUseCase: GetSemesterUseCase, + private val getSemesterUseCase: GetUserSemestersUseCase, private val getTimetableFramesUseCase: GetTimetableFramesUseCase, private val addTimetableLectureUseCase: AddTimetableLectureUseCase, private val deleteTimetableLectureUseCase: DeleteTimetableLectureUseCase, @@ -97,7 +96,6 @@ class TimetableViewModel @Inject constructor( updateLoading(true) val semesters = getSemester(state.value.isAnonymous) val semester = semesters.firstOrNull().orEmpty() - // TODO: 로그인 시, 학기를 불러올 때 Empty 이면 학기 추가를 해야하는 경고문 추가하기 (디자인 없어서 추가해야 함) _lectures.value = getLectures(semester) @@ -120,22 +118,22 @@ class TimetableViewModel @Inject constructor( } false -> { - // TODO : 로그인 시 시간표 수업 불러오기 val timetableFrames = getTimetableFrames(semester).ifEmpty { updateSemesters(semesters, semester) return@launch } - val frameId = timetableFrames.find { it.isMain }?.id - if (frameId == null) { + val frame = timetableFrames.find { it.isMain } + if (frame == null) { updateSemesters(semesters, semester) return@launch } - timetableRepository.getTimetableLectures(frameId) + timetableRepository.getTimetableLectures(frame.id) .onSuccess { timetableLectures -> _state.value = _state.value.copy( range = timetableLectures.formatTimeRange(), frameId = timetableLectures.timetableFrameId, + timetableName = frame.timetableName, semesters = semesters, timetableEvents = timetableLectures.getTimetableEvents(), currentSemester = semester, @@ -148,8 +146,36 @@ class TimetableViewModel @Inject constructor( } } } + } + } + fun getRefreshData(frameId: Int, semester: String, frameName: String) { // TODO : 시간표 제목도 보내주쇼.. + if (state.value.frameId == frameId) return + viewModelScope.launch { + updateLoading(true) + _lectures.value = getLectures(semester) + when (state.value.isAnonymous) { + true -> { + } + false -> { + timetableRepository.getTimetableLectures(frameId) + .onSuccess { timetableLectures -> + _state.value = _state.value.copy( + range = timetableLectures.formatTimeRange(), + frameId = timetableLectures.timetableFrameId, + timetableName = frameName, + timetableEvents = timetableLectures.getTimetableEvents(), + currentSemester = semester, + timetableLectures = timetableLectures, + loading = false + ) + }.onFailure { + updateLoading(false) + Timber.e("getTimetableLectures Remote Error Message : ${it.message}") + } + } + } } } @@ -501,6 +527,7 @@ class TimetableViewModel @Inject constructor( } viewModelScope.launch { + updateLoading(true) timetableRepository.postTimetableCustomLectures( frameId = state.value.frameId, lectures = customContentState.value.toLectures() @@ -636,9 +663,10 @@ class TimetableViewModel @Inject constructor( false -> { viewModelScope.launch { + updateLoading(true) addTimetableLectureUseCase( frameId = state.value.frameId, - lectures = listOf(lecture) // TODO : updateTimetableLectures 함수 파라미터 lecture를 리스트로 변경 필요 + lectures = listOf(lecture) ).onSuccess { timetableLectures -> _state.value = _state.value.copy( range = timetableLectures.formatTimeRange(), @@ -691,8 +719,8 @@ class TimetableViewModel @Inject constructor( } false -> { - // TODO : 로그인 시, 강의 삭제 액션 viewModelScope.launch { + updateLoading(true) deleteTimetableFrameLectureUseCase( state.value.frameId, lecture.id @@ -707,13 +735,16 @@ class TimetableViewModel @Inject constructor( etcClickedTimetableEvents = emptyList(), selectedLecture = null, timetableLectures = timetableLectures, + loading = false ) }.onFailure { // TODO : 강의 불러오기 실패 + updateLoading(false) Timber.e("getTimetableLectures Remote Error Message : ${it.message}") } }.onFailure { // TODO : 강의 삭제 실패 + updateLoading(false) Timber.e("deleteTimetableLectureUseCase Remote Error Message : ${it.message}") } } @@ -731,6 +762,7 @@ class TimetableViewModel @Inject constructor( ) viewModelScope.launch { + updateLoading(true) timetableRepository.putTimetableLectures(state.value.currentSemester, timetables) .onSuccess { timetableLectures -> _state.value = _state.value.copy( @@ -741,6 +773,7 @@ class TimetableViewModel @Inject constructor( etcClickedTimetableEvents = emptyList(), bottomSheetCollapse = true, selectedLecture = null, + loading = false ) updateIsLectureDuplicationDialogVisible(false) }.onFailure { @@ -751,6 +784,7 @@ class TimetableViewModel @Inject constructor( } } else { viewModelScope.launch { + updateLoading(true) deleteTimetableLectureUseCase(id).onSuccess { timetableRepository.getTimetableLectures(state.value.frameId) .onSuccess { timetableLectures -> @@ -763,13 +797,14 @@ class TimetableViewModel @Inject constructor( bottomSheetCollapse = true, selectedLecture = null, timetableLectures = timetableLectures, + loading = false, ) }.onFailure { - // TODO : 강의 불러오기 실패 + updateLoading(false) Timber.e("getTimetableLectures Remote Error Message : ${it.message}") } }.onFailure { - // TODO : 강의 삭제 실패 + updateLoading(false) Timber.e("removeTimetableLectureById Remote Error Message : ${it.message}") } } @@ -778,6 +813,7 @@ class TimetableViewModel @Inject constructor( private fun postLocalTimetableLectures(timetables: TimetableLectures) { viewModelScope.launch { + updateLoading(true) timetableRepository.putTimetableLectures(state.value.currentSemester, timetables) .onSuccess { timetableLectures -> _state.value = _state.value.copy( @@ -787,6 +823,7 @@ class TimetableViewModel @Inject constructor( clickedTimetableEvents = emptyList(), etcClickedTimetableEvents = emptyList(), selectedLecture = null, + loading = false ) updateIsLectureDuplicationDialogVisible(false) }.onFailure { From d482740e0f561b773b095c1e3d3673557783a985 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 17 Nov 2024 22:15:29 +0900 Subject: [PATCH 15/60] =?UTF-8?q?[add]=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/ui/login/LoginActivity.kt | 12 +++++++++ .../koin/ui/timetablev2/TimetableActivity.kt | 26 ++++++++++++++----- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt index 5bffd0f52..300e042b0 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/login/LoginActivity.kt @@ -23,6 +23,7 @@ import `in`.koreatech.koin.ui.forgotpassword.ForgotPasswordActivity import `in`.koreatech.koin.ui.login.viewmodel.LoginViewModel import `in`.koreatech.koin.ui.main.activity.MainActivity import `in`.koreatech.koin.ui.signup.SignupActivity +import `in`.koreatech.koin.ui.timetablev2.TimetableActivity import `in`.koreatech.koin.util.SnackbarUtil import `in`.koreatech.koin.util.ext.hideKeyboard import `in`.koreatech.koin.util.ext.textString @@ -80,6 +81,11 @@ class LoginActivity : ActivityBase(R.layout.activity_login) { finish() } } else { + if (handleTimetableIntent()) { + startActivity(Intent(this@LoginActivity, TimetableActivity::class.java)) + finish() + return + } startActivity(Intent(this@LoginActivity, MainActivity::class.java)) finish() } @@ -151,6 +157,12 @@ class LoginActivity : ActivityBase(R.layout.activity_login) { isBusinessButton.setOnClickListener { startActivity(Intent(this@LoginActivity, BusinessLoginActivity::class.java)) } + + } + + private fun handleTimetableIntent(): Boolean { + val bundle = intent.getBundleExtra(TimetableActivity.BUNDLE_LOGIN_EXTRA_KEY) + return bundle?.getBoolean(TimetableActivity.NAV_TIMETABLE, false) ?: false } override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index a5448d58f..3819417f6 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -27,6 +27,7 @@ import `in`.koreatech.koin.core.designsystem.component.snackbar.showSnackBarWith import `in`.koreatech.koin.core.designsystem.theme.KoinTheme import `in`.koreatech.koin.core.util.KeyboardUtils import `in`.koreatech.koin.databinding.ActivityTimetableBinding +import `in`.koreatech.koin.feature.timetable.component.CircleLoadingBar import `in`.koreatech.koin.feature.timetable.model.TimetableConstants.departments import `in`.koreatech.koin.feature.timetable.state.BottomSheetUI import `in`.koreatech.koin.feature.timetable.state.TimetableSideEffect @@ -40,6 +41,7 @@ import `in`.koreatech.koin.feature.timetable.view.dialog.ScheduleDuplicationDial import `in`.koreatech.koin.feature.timetable.view.dialog.SelectDepartmentDialog import `in`.koreatech.koin.feature.timetable.view.dialog.TimetableTimePickerDialog import `in`.koreatech.koin.feature.timetable.viewmodel.TimetableViewModel +import `in`.koreatech.koin.ui.login.LoginActivity import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerActivity import `in`.koreatech.koin.ui.navigation.state.MenuState import kotlinx.coroutines.launch @@ -58,10 +60,11 @@ class TimetableActivity : KoinNavigationDrawerActivity() { private val registerTimetableSemesterActivityResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { // handle activity result api - if(it.resultCode == RESULT_OK) { + if (it.resultCode == RESULT_OK) { it.data?.getBundleExtra(TimetableSemesterActivity.BUNDLE_EXTRA_KEY)?.let { - Timber.d("Timetable frame ID: ${it.getInt(TimetableSemesterActivity.TIMETABLE_FRAME_ID)}") - Timber.d("Semester string: ${it.getString(TimetableSemesterActivity.SEMESTER)}") + val frameId = it.getInt(TimetableSemesterActivity.TIMETABLE_FRAME_ID) + val semester = it.getString(TimetableSemesterActivity.SEMESTER) + viewModel.getRefreshData(frameId, semester.orEmpty(), "") } } } @@ -155,7 +158,7 @@ class TimetableActivity : KoinNavigationDrawerActivity() { if (dialogState.isLoginVisible) { RequestLoginDialog( - onConfirm = {}, // TODO : 로그인 화면으로 연결 + onConfirm = ::startToLoginActivity, // TODO : 로그인 화면으로 연결 onDismiss = viewModel::updateIsLoginDialogVisible ) } @@ -195,11 +198,11 @@ class TimetableActivity : KoinNavigationDrawerActivity() { TimetableScreen( range = state.range, - loading = state.loading, lectures = lectures, detailLecture = state.detailLecture, semesters = state.semesters, currentSemester = state.currentSemester, + timetableName = state.timetableName, selectedLecture = state.selectedLecture, timetableEvents = state.timetableEvents, clickedTimetableEvents = state.clickedTimetableEvents, @@ -214,7 +217,7 @@ class TimetableActivity : KoinNavigationDrawerActivity() { onSearchTextChange = viewModel::updateSearchText, onClickTimetableSchedule = { startToTimetableSemesterActivity() - }, // TODO : 학기 시간표 선택 + }, onClickDownloadTimetable = { scope.launch { saveTimetable(graphicsLayer.toImageBitmap().asAndroidBitmap()) @@ -286,6 +289,8 @@ class TimetableActivity : KoinNavigationDrawerActivity() { onClickAddCustomContent = viewModel::addCustomExtraContent, onClickRemoveCustomContent = viewModel::removeCustomExtraContent ) + + CircleLoadingBar(loading = state.loading) } CustomSnackBarHost(snackBarHost) @@ -329,6 +334,13 @@ class TimetableActivity : KoinNavigationDrawerActivity() { registerTimetableSemesterActivityResult.launch(intent) } + private fun startToLoginActivity() { + Intent(this, LoginActivity::class.java).apply { + putExtra(BUNDLE_LOGIN_EXTRA_KEY, bundleOf(NAV_TIMETABLE to true)) + }.let(::startActivity) + finish() + } + private fun saveTimetable(bitmap: Bitmap) { BitmapUtils(this).saveBitmapImage(bitmap).let { if (it) { @@ -357,6 +369,8 @@ class TimetableActivity : KoinNavigationDrawerActivity() { companion object { private const val SCREEN_TITLE = "시간표" const val BUNDLE_EXTRA_KEY = "BUNDLE_EXTRA_KEY" + const val BUNDLE_LOGIN_EXTRA_KEY = "BUNDLE_EXTRA_KEY" + const val NAV_TIMETABLE = "timetable" const val IS_ANONYMOUS = "isAnonymous" } } From 6f009f46bed04294d9e1d5ee957a5241e5c83bce Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 17 Nov 2024 22:15:53 +0900 Subject: [PATCH 16/60] =?UTF-8?q?[add]=20=ED=95=99=EA=B8=B0=20=ED=85=8D?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/TimetableScheduleBox.kt | 69 +++++++++++++++++-- .../feature/timetable/state/TimetableState.kt | 1 + .../feature/timetable/view/TimetableScreen.kt | 15 ++-- 3 files changed, 73 insertions(+), 12 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableScheduleBox.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableScheduleBox.kt index 33bf08d55..16997ac02 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableScheduleBox.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableScheduleBox.kt @@ -2,20 +2,25 @@ package `in`.koreatech.koin.feature.timetable.component 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.padding +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Text +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.core.designsystem.noRippleClickable import `in`.koreatech.koin.core.designsystem.theme.KoinTheme @Composable fun TimetableScheduleBox( + currentSemester: String, + timetableName: String, modifier: Modifier = Modifier, onClick: () -> Unit = {} ) { @@ -27,19 +32,71 @@ fun TimetableScheduleBox( color = KoinTheme.colors.neutral300, shape = RoundedCornerShape(10.dp) ) - .clickable { onClick() } + .widthIn(max = (LocalConfiguration.current.screenWidthDp / 2).dp) + .noRippleClickable { onClick() } .padding(5.dp) ) { Text( - text = "2학년 2학기 / 시간표1", + text = if (currentSemester.isEmpty()) "" else currentSemester.toSemesterTitle( + timetableName + ), style = KoinTheme.typography.regular14, - color = KoinTheme.colors.neutral800 + color = KoinTheme.colors.neutral800, + maxLines = 1, + overflow = TextOverflow.Ellipsis ) } } +private fun String.toSemesterTitle(timetableName: String): String { + return try { + val content = this.split("-") + val prefixContent = when (content.size) { + 1 -> { + val year = content.getOrNull(0)?.substring(0, 4).orEmpty() + val semester = content.getOrNull(0)?.substring(4).orEmpty() + if (year.isEmpty()) { + "" + } else { + if (semester.isEmpty()) { + "${year}학년" + } else { + "${year}학년 ${semester}학기" + } + } + } + + 2 -> { + val year = content.getOrNull(0).orEmpty() + val semester = content.getOrNull(1).orEmpty() + if (year.isEmpty()) { + "" + } else { + if (semester.isEmpty()) { + "${year}학년" + } else { + "${year}학년 ${semester}학기" + } + } + } + + else -> "" + } + if (timetableName.isEmpty()) { + prefixContent + } else { + "$prefixContent / $timetableName" + } + } catch (e: Exception) { + "" + } +} + @Preview @Composable private fun TimetableScheduleBoxPreview() { - TimetableScheduleBox() + TimetableScheduleBox( + currentSemester = "2024-여름", + timetableName = "시간표1" + ) } \ No newline at end of file diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/TimetableState.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/TimetableState.kt index 08624e3c3..c87e04ea4 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/TimetableState.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/TimetableState.kt @@ -10,6 +10,7 @@ data class TimetableState( val range: Int = 9, // 강의 시간 범위 (9시~18시 : 9(최소), 9시~24시 : 15(최대)) val frameId: Int = 0, // 시간표 프레임 ID val currentSemester: String = "", // 현재 선택된 학기 + val timetableName: String = "", // 현재 선택된 학기의 시간표 이름 val semesters: List = emptyList(), // 전체 학기 val detailLecture: TimetableLecture? = null, // 선택된 강의 클릭 후 나오는 강의 상세 데이터 val deleteLecture: TimetableLecture? = null, diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/TimetableScreen.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/TimetableScreen.kt index a1be748b4..0b63dd33b 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/TimetableScreen.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/TimetableScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.material.rememberBottomSheetScaffoldState import androidx.compose.material.rememberBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -31,7 +32,6 @@ import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.util.pxToDp import `in`.koreatech.koin.domain.model.timetable.response.Lecture import `in`.koreatech.koin.domain.model.timetable.response.TimetableLecture -import `in`.koreatech.koin.feature.timetable.component.CircleLoadingBar import `in`.koreatech.koin.feature.timetable.component.TimetableDownloadBox import `in`.koreatech.koin.feature.timetable.component.TimetableScheduleBox import `in`.koreatech.koin.feature.timetable.model.TimetableEvent @@ -42,12 +42,12 @@ import `in`.koreatech.koin.feature.timetable.state.CustomExtraContentState @Composable fun TimetableScreen( - loading: Boolean, range: Int, lectures: List, detailLecture: TimetableLecture?, semesters: List, currentSemester: String, + timetableName: String, customContents: CustomContentState, selectedLecture: Lecture?, searchText: String, @@ -85,7 +85,7 @@ fun TimetableScreen( onClickAddCustomContent: () -> Unit = {}, onClickRemoveCustomContent: (id: Int) -> Unit = {}, ) { - var bottomSheetHeight by remember { mutableStateOf(0f) } + var bottomSheetHeight by remember { mutableFloatStateOf(0f) } BottomSheetScaffold( modifier = modifier, @@ -150,7 +150,11 @@ fun TimetableScreen( .padding(horizontal = 24.dp), horizontalArrangement = Arrangement.SpaceBetween, ) { - TimetableScheduleBox(onClick = onClickTimetableSchedule) + TimetableScheduleBox( + currentSemester = currentSemester, + timetableName = timetableName, + onClick = onClickTimetableSchedule + ) TimetableDownloadBox(onClick = onClickDownloadTimetable) } Timetable( @@ -163,7 +167,6 @@ fun TimetableScreen( onEventClick = onClickTimetableEvent ) } - CircleLoadingBar(loading = loading) } } @@ -192,13 +195,13 @@ private fun Modifier.dynamicPadding( @Composable private fun TimetableScreenPreview() { TimetableScreen( - loading = false, range = 9, customContents = CustomContentState(), lectures = listOf(dummyLecture), detailLecture = dummyLecture.toTimetableLecture(), semesters = listOf("20242"), currentSemester = "20242", + timetableName = "시간표1", selectedLecture = null, searchText = "", bottomSheetContentMode = TimetableBottomSheetContentMode.BASIC, From e5e2850a949b325b882d28cf92cba060b805ae7d Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 17 Nov 2024 22:16:06 +0900 Subject: [PATCH 17/60] =?UTF-8?q?[fix]=20noRippleClickable=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/timetable/component/TimetableDownloadBox.kt | 3 ++- .../feature/timetable/component/TimetableEventTime.kt | 5 +++-- .../timetable/section/TimetableBottomSheetHeader.kt | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableDownloadBox.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableDownloadBox.kt index 4bb61b6ea..ad953fd05 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableDownloadBox.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableDownloadBox.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import `in`.koreatech.koin.core.designsystem.noRippleClickable import `in`.koreatech.koin.core.designsystem.theme.KoinTheme import `in`.koreatech.koin.feature.timetable.R @@ -35,7 +36,7 @@ fun TimetableDownloadBox( color = KoinTheme.colors.neutral300, shape = RoundedCornerShape(10.dp) ) - .clickable { onClick() } + .noRippleClickable { onClick() } .padding(5.dp), ) { Text( diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableEventTime.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableEventTime.kt index f2c5eef89..34f110a60 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableEventTime.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableEventTime.kt @@ -32,6 +32,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.dashedBorder +import `in`.koreatech.koin.core.designsystem.noRippleClickable import `in`.koreatech.koin.core.designsystem.theme.KoinTheme import `in`.koreatech.koin.feature.timetable.model.TimetableEvent import `in`.koreatech.koin.feature.timetable.model.dummyEvent @@ -93,7 +94,7 @@ private fun TimetableBasicEventTime( var contentHeight by rememberSaveable { mutableIntStateOf(0) } var contentLineCount by rememberSaveable { mutableIntStateOf(0) } - // 1시간 30분이상일 경우 professor text 가 내려가서 보이지 않는 오류 해결해보자.. + // TODO : 1시간 30분이상일 경우 professor text 가 내려가서 보이지 않는 오류 해결해보자.. if (height < titleHeight + contentHeight) { titleMaxLine = titleLineCount - 1 contentMaxLine = if (contentLineCount == 1) { @@ -114,7 +115,7 @@ private fun TimetableBasicEventTime( bottomEnd = timetableSelectedEventTimeBottomEndRound(range, event) ) ) - .clickable { onEventTimeClick(event) } + .noRippleClickable { onEventTimeClick(event) } .onGloballyPositioned { height = it.size.height } diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/section/TimetableBottomSheetHeader.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/section/TimetableBottomSheetHeader.kt index c331f1192..5fd24c322 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/section/TimetableBottomSheetHeader.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/section/TimetableBottomSheetHeader.kt @@ -1,6 +1,5 @@ package `in`.koreatech.koin.feature.timetable.section -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -9,6 +8,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview +import `in`.koreatech.koin.core.designsystem.noRippleClickable import `in`.koreatech.koin.core.designsystem.theme.KoinTheme import `in`.koreatech.koin.feature.timetable.R import `in`.koreatech.koin.feature.timetable.view.TimetableBottomSheetContentMode @@ -32,7 +32,7 @@ fun TimetableBottomSheetHeader( TimetableBottomSheetContentMode.BASIC -> KoinTheme.typography.medium18.copy(color = KoinTheme.colors.primary500) TimetableBottomSheetContentMode.CUSTOM -> KoinTheme.typography.bold18.copy(color = KoinTheme.colors.primary600) }, - modifier = Modifier.clickable { + modifier = Modifier.noRippleClickable { onClickAddCustomLectureMode() } ) @@ -42,7 +42,7 @@ fun TimetableBottomSheetHeader( TimetableBottomSheetContentMode.BASIC -> KoinTheme.typography.bold18.copy(color = KoinTheme.colors.primary600) TimetableBottomSheetContentMode.CUSTOM -> KoinTheme.typography.medium18.copy(color = KoinTheme.colors.primary500) }, - modifier = Modifier.clickable { + modifier = Modifier.noRippleClickable { onClickAddLectureMode(TimetableBottomSheetContentMode.BASIC) } ) @@ -50,7 +50,7 @@ fun TimetableBottomSheetHeader( text = stringResource(id = R.string.timetable_bottom_sheet_complete), style = KoinTheme.typography.medium18, color = KoinTheme.colors.neutral800, - modifier = Modifier.clickable { + modifier = Modifier.noRippleClickable { onComplete() } ) From 878663714ed1f11caaa18cedce0885fc0aba8546 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 17 Nov 2024 22:16:19 +0900 Subject: [PATCH 18/60] =?UTF-8?q?[del]=20import=20=EB=AC=B8=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/feature/timetable/TimetableViewModelTest.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/feature/timetable/src/test/java/in/koreatech/koin/feature/timetable/TimetableViewModelTest.kt b/feature/timetable/src/test/java/in/koreatech/koin/feature/timetable/TimetableViewModelTest.kt index e1ab1f485..9b8870edb 100644 --- a/feature/timetable/src/test/java/in/koreatech/koin/feature/timetable/TimetableViewModelTest.kt +++ b/feature/timetable/src/test/java/in/koreatech/koin/feature/timetable/TimetableViewModelTest.kt @@ -10,7 +10,6 @@ import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame import `in`.koreatech.koin.domain.model.timetable.response.TimetableLectures import `in`.koreatech.koin.domain.repository.TimetableRepository import `in`.koreatech.koin.domain.usecase.timetable.GetLecturesUseCase -import `in`.koreatech.koin.domain.usecase.timetable.GetSemesterUseCase import `in`.koreatech.koin.domain.usecase.timetable.GetTimetableFramesUseCase import `in`.koreatech.koin.feature.timetable.viewmodel.TimetableViewModel import kotlinx.coroutines.Dispatchers From 111c343b1fbdf4e537445a102dd729bb9f7e1b2a Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 17 Nov 2024 22:29:31 +0900 Subject: [PATCH 19/60] =?UTF-8?q?[fix]=20snackbar=20=EC=95=A0=EB=8B=88?= =?UTF-8?q?=EB=A9=94=EC=9D=B4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/snackbar/CustomSnackBarHost.kt | 86 +++++++++---------- 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt index 46ec1810f..3408dc9a7 100644 --- a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt +++ b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt @@ -9,8 +9,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost @@ -32,6 +30,7 @@ import `in`.koreatech.koin.core.designsystem.theme.KoinTheme @Composable fun CustomSnackBarHost( hotState: SnackbarHostState, + modifier: Modifier = Modifier, radius: Dp = 0.dp, messageTextStyle: TextStyle = KoinTheme.typography.regular12.copy( color = Color.White @@ -44,23 +43,27 @@ fun CustomSnackBarHost( paddingValues: PaddingValues = PaddingValues(bottom = 20.dp, start = 10.dp, end = 10.dp), innerPaddingValues: PaddingValues = PaddingValues(horizontal = 10.dp, vertical = 16.dp) ) { - SnackbarHost( - hostState = hotState, - ) { snackbarData -> - SnackBarContent( - messageText = snackbarData.visuals.message, - actionLabelText = snackbarData.visuals.actionLabel ?: "", - radius = radius, - background = background, - messageTextStyle = messageTextStyle, - actionLabelTextStyle = actionLabelTextStyle, - alignment = alignment, - paddingValues = paddingValues, - innerPaddingValues = innerPaddingValues, - onAction = { snackbarData.dismiss() } - ) + Box( + modifier = modifier + .fillMaxSize() + .padding(paddingValues), + contentAlignment = alignment + ) { + SnackbarHost( + hostState = hotState, + ) { snackbarData -> + SnackBarContent( + messageText = snackbarData.visuals.message, + actionLabelText = snackbarData.visuals.actionLabel ?: "", + radius = radius, + background = background, + messageTextStyle = messageTextStyle, + actionLabelTextStyle = actionLabelTextStyle, + innerPaddingValues = innerPaddingValues, + onAction = { snackbarData.dismiss() } + ) + } } - } @Composable @@ -76,43 +79,36 @@ private fun SnackBarContent( actionLabelTextStyle: TextStyle = KoinTheme.typography.regular12.copy( color = Color.White ), - alignment: Alignment = Alignment.BottomCenter, - paddingValues: PaddingValues = PaddingValues(bottom = 20.dp, start = 10.dp, end = 10.dp), innerPaddingValues: PaddingValues = PaddingValues(horizontal = 10.dp, vertical = 16.dp), onAction: () -> Unit = {} ) { Box( modifier = modifier - .fillMaxSize() - .padding(paddingValues), - contentAlignment = alignment + .fillMaxWidth() + .clip(RoundedCornerShape(radius)) + .background(background) + .padding(innerPaddingValues) ) { - Box( - modifier = modifier - .fillMaxWidth() - .clip(RoundedCornerShape(radius)) - .background(background) - .padding(innerPaddingValues) + Row( + verticalAlignment = Alignment.CenterVertically, ) { - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - Spacer(modifier = Modifier.width(4.dp)) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = messageText, + style = messageTextStyle, + modifier = Modifier.weight(1f), + ) + Spacer(modifier = Modifier.weight(0.05f)) + if (actionLabelText.isNotEmpty()) { Text( - text = messageText, - style = messageTextStyle, - modifier = Modifier.weight(1f), + text = actionLabelText, + style = actionLabelTextStyle, + modifier = Modifier + .weight(0.1f) + .noRippleClickable { onAction() } ) - Spacer(modifier = Modifier.weight(0.05f)) - if (actionLabelText.isNotEmpty()) { - Text( - text = actionLabelText, - style = actionLabelTextStyle, - modifier = Modifier.weight(0.1f).noRippleClickable { onAction() } - ) - } - } + } } } From 6cdee91d4c424c5e2d343f7eb83dac72c055a603 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Sun, 17 Nov 2024 22:34:49 +0900 Subject: [PATCH 20/60] [fix] bottom padding --- .../koin/feature/timetable/view/TimetableBottomSheet.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/TimetableBottomSheet.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/TimetableBottomSheet.kt index 4218fab25..fb30d1258 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/TimetableBottomSheet.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/TimetableBottomSheet.kt @@ -72,7 +72,7 @@ fun TimetableBottomSheet( ), ) { TimetableBottomSheetHeader( - modifier = Modifier.padding(bottom = 4.dp), + modifier = Modifier.padding(bottom = 10.dp), mode = bottomSheetContentMode, onClickAddLectureMode = onClickAddLectureMode, onClickAddCustomLectureMode = onClickAddCustomLectureMode, From ffc9e40ca7e61cd9721eececa39ab077b8dc51ba Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Mon, 18 Nov 2024 16:24:28 +0900 Subject: [PATCH 21/60] =?UTF-8?q?[add]=20timetable=20v2=20navigation=20?= =?UTF-8?q?=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../KoinNavigationDrawerActivity.kt | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt index 958275d19..293f08810 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt @@ -298,12 +298,12 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), } MenuState.Timetable -> { -// goToTimetableActivity() - if (userInfoFlow.value.isAnonymous) { - goToAnonymousTimeTableActivity() - } else { - goToTimetableActivity() - } + goToTimetableActivity() +// if (userInfoFlow.value.isAnonymous) { +// goToAnonymousTimeTableActivity() +// } else { +// goToTimetableActivity() +// } } MenuState.LoginOrLogout -> { @@ -478,39 +478,39 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), /** * 완성되면 아래 함수 주석 풀고 연결 */ -// private fun goToTimetableActivity() { -// if (menuState != MenuState.Main) { -// goToActivityFinish(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) -// } else { -// val intent = Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java).apply { -// if (koinNavigationDrawerViewModel.userInfoFlow.value.isAnonymous) { -// putExtra("isAnonymous", true) -// } else { -// putExtra("isAnonymous", false) -// } -// } -// EventLogger.logClickEvent( -// action = EventAction.USER, -// label = "hamburger", -// value = "시간표" -// ) -// startActivity(intent) -// } -// } - private fun goToTimetableActivity() { if (menuState != MenuState.Main) { - goToActivityFinish(Intent(this, TimetableActivity::class.java)) + goToActivityFinish(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) } else { + val intent = Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java).apply { + if (koinNavigationDrawerViewModel.userInfoFlow.value.isAnonymous) { + putExtra("isAnonymous", true) + } else { + putExtra("isAnonymous", false) + } + } EventLogger.logClickEvent( action = EventAction.USER, label = "hamburger", value = "시간표" ) - startActivity(Intent(this, TimetableActivity::class.java)) + startActivity(intent) } } +// private fun goToTimetableActivity() { +// if (menuState != MenuState.Main) { +// goToActivityFinish(Intent(this, TimetableActivity::class.java)) +// } else { +// EventLogger.logClickEvent( +// action = EventAction.USER, +// label = "hamburger", +// value = "시간표" +// ) +// startActivity(Intent(this, TimetableActivity::class.java)) +// } +// } + private fun goToArticleActivity() { val intent = Intent(this, ArticleActivity::class.java) From 57a4a1567b7f5691c555f1cea858458344fe50b6 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Tue, 19 Nov 2024 09:08:20 +0900 Subject: [PATCH 22/60] =?UTF-8?q?[Add]=20=ED=95=99=EA=B8=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=99=84=EB=A3=8C=EC=8B=9C=20=EC=8B=9C=EA=B0=84?= =?UTF-8?q?=ED=91=9C=20=EC=9D=B4=EB=A6=84=EC=9D=84=20=EB=B2=88=EB=93=A4?= =?UTF-8?q?=EC=97=90=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetablev2/TimetableSemesterActivity.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index 02d57178e..ecfb3f627 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -78,7 +78,6 @@ class TimetableSemesterActivity : ActivityBase() { }, onDeleteFrame = { viewModel.deleteTimetableFrame() - viewModel.updateEditTimetableDialogVisibility(false) } ) } @@ -92,7 +91,6 @@ class TimetableSemesterActivity : ActivityBase() { viewModel.updateUserSemesters() viewModel.updateDeleteSemesterDialogVisible(false) viewModel.updateEditSemesterDialogVisible(false) - } ) } @@ -124,13 +122,14 @@ class TimetableSemesterActivity : ActivityBase() { } private fun finishActivityWithResult(semester: SemesterModel, timetableFrame: TimetableFrame) { - intent?.putExtra( - BUNDLE_EXTRA_KEY, - bundleOf( - SEMESTER to semester.toSemester(), - TIMETABLE_FRAME_ID to timetableFrame.id - ) - ) + bundleOf().apply { + putString(SEMESTER, semester.toSemester()) + if(!viewModel.isAnonymous.value) { + putInt(TIMETABLE_FRAME_ID, timetableFrame.id) + putString(TIMETABLE_FRAME_NAME, timetableFrame.timetableName) + } + } + setResult(RESULT_OK, intent) finish() } @@ -140,5 +139,6 @@ class TimetableSemesterActivity : ActivityBase() { const val BUNDLE_EXTRA_KEY = "BUNDLE_EXTRA_KEY" const val SEMESTER = "semester" const val TIMETABLE_FRAME_ID = "timetableFrameId" + const val TIMETABLE_FRAME_NAME = "timetableFrameName" } } \ No newline at end of file From 2ef1212fb7052b418d4cde541e1ff9a14db8a0a7 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Tue, 19 Nov 2024 09:11:30 +0900 Subject: [PATCH 23/60] =?UTF-8?q?[Fix]=20=ED=95=99=EA=B8=B0=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=ED=99=95=EC=9D=B8=20=EB=AA=A8=EB=8B=AC=20=EC=A0=9C?= =?UTF-8?q?=EB=8C=80=EB=A1=9C=20=EC=95=88=EB=9C=A8=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- feature/timetable/src/main/res/values/strings.xml | 6 +++--- .../koin/ui/timetablev2/TimetableSemesterActivity.kt | 12 +++--------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/feature/timetable/src/main/res/values/strings.xml b/feature/timetable/src/main/res/values/strings.xml index cf7b305b0..f081b2581 100644 --- a/feature/timetable/src/main/res/values/strings.xml +++ b/feature/timetable/src/main/res/values/strings.xml @@ -35,9 +35,9 @@ 삭제하기 - 시간표가 작성되어 있는 학기가있어요. 해당 학기를 제외할 경우 - " 학기 내 시간표도 함께 삭제" - 돼요. + 시간표가 작성되어 있는 학기를 제외할 경우 + " 학기 내 시간표도 함께 " + 삭제돼요. 삭제하기 diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index ecfb3f627..9e0df5878 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -52,16 +52,10 @@ class TimetableSemesterActivity : ActivityBase() { userSemesters = userSemesters, onConfirm = { selectedSemesters -> viewModel.updateSelectedSemesters(selectedSemesters) - var isLectureExist = false - selectedSemesters.forEach { - // TODO::hyeok 강의 유무 확인해서 띄우기 - if(userTimetables.contains(it) && userTimetables[it]?.isEmpty() == true) - isLectureExist = true - } - - if(isLectureExist) { + if(selectedSemesters.any { it in userSemesters}) viewModel.updateDeleteSemesterDialogVisible(true) - } else { + else { + viewModel.updateEditSemesterDialogVisible(false) viewModel.updateUserSemesters() } }, From e699751040a1b1f74ab1fc2d96090269ae0a7021 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Tue, 19 Nov 2024 09:15:34 +0900 Subject: [PATCH 24/60] =?UTF-8?q?[Fix]=20=ED=95=99=EA=B8=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=8B=9C=20=EC=A0=95=EB=A0=AC=20=EC=95=88=EB=90=98?= =?UTF-8?q?=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95,=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/feature/timetable/model/SemesterModel.kt | 9 +++++++-- .../feature/timetable/viewmodel/SemesterViewModel.kt | 12 ++++++------ .../koin/ui/timetablev2/TimetableSemesterActivity.kt | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/model/SemesterModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/model/SemesterModel.kt index ed862ee7f..d3e5de817 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/model/SemesterModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/model/SemesterModel.kt @@ -7,14 +7,19 @@ import `in`.koreatech.koin.feature.timetable.R data class SemesterModel( val year: Int, val type: SemesterType -) { +) : Comparable { fun toSemester(): String { return year.toString() + type.format } + + override fun compareTo(other: SemesterModel): Int { + // 연도, 학기로 내림차순 정렬 + return (other.year * 10 + other.type.ordinal) - (year * 10 + type.ordinal) + } } @Stable -enum class SemesterType(@StringRes val stringRes: Int, val format: String) { +enum class SemesterType(@StringRes val stringRes: Int, val format: String) { Spring(R.string.semester_type_spring, "1"), Summer(R.string.semester_type_summer, "-여름"), Fall(R.string.semester_type_fall, "2"), diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index 664231b64..f997bc649 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -50,10 +50,10 @@ class SemesterViewModel @Inject constructor( val isAnonymous: StateFlow = _isAnonymous.asStateFlow() private val _userSemester: MutableStateFlow> = MutableStateFlow(emptyList()) - val userSemesters2: StateFlow> = _userSemester.asStateFlow() + val userSemesters: StateFlow> = _userSemester.asStateFlow() private val _userTimetableFrames: MutableStateFlow>> = MutableStateFlow(emptyMap()) - val userTimetableFrames2: StateFlow>> = _userTimetableFrames.asStateFlow() + val userTimetableFrames: StateFlow>> = _userTimetableFrames.asStateFlow() val semesters: StateFlow> = getSemestersUseCase() .map { it.map { it.toSemesterModel() } } @@ -73,7 +73,7 @@ class SemesterViewModel @Inject constructor( } // scan val tmp = mutableMapOf>() - userSemesters2.value + userSemesters.value .map { it.toSemester() } .forEach { semester -> getTimetableFramesUseCase(semester) @@ -112,7 +112,7 @@ class SemesterViewModel @Inject constructor( viewModelScope.launch { addTimetableFrameUseCase( semester = target.toSemester(), - timetableName = "시간표${(userTimetableFrames2.value[target]?.size ?: 1) + 1}" + timetableName = "시간표${(userTimetableFrames.value[target]?.size ?: 1) + 1}" ).onSuccess { addedFrame -> _userTimetableFrames.update { it -> it.mapValues { @@ -142,7 +142,7 @@ class SemesterViewModel @Inject constructor( fun updateUserSemesters() { viewModelScope.launch { dialogUiState.value.selectedSemesters.forEach { semester -> - if (userSemesters2.value.contains(semester)) { + if (userSemesters.value.contains(semester)) { deleteSemesterUseCase(semester.toSemester()).onSuccess { _userSemester.update { it - semester @@ -157,7 +157,7 @@ class SemesterViewModel @Inject constructor( it + semester } _userTimetableFrames.update { - it + (semester to listOf(addedFrame)) + (it + (semester to listOf(addedFrame))).toSortedMap() } } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index 9e0df5878..4b63c9dd7 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -41,9 +41,9 @@ class TimetableSemesterActivity : ActivityBase() { KoinTheme { val dialogUiState by viewModel.dialogUiState.collectAsStateWithLifecycle() val isAnonymous by viewModel.isAnonymous.collectAsStateWithLifecycle() - val userTimetables by viewModel.userTimetableFrames2.collectAsStateWithLifecycle() + val userTimetables by viewModel.userTimetableFrames.collectAsStateWithLifecycle() - val userSemesters by viewModel.userSemesters2.collectAsStateWithLifecycle() + val userSemesters by viewModel.userSemesters.collectAsStateWithLifecycle() val years by viewModel.years.collectAsStateWithLifecycle() if (dialogUiState.isEditSemesterDialogVisible) { From 56299ccc5110952c751859fce24d5e7551c9c6a5 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Tue, 19 Nov 2024 21:53:32 +0900 Subject: [PATCH 25/60] =?UTF-8?q?[Fix]=20=ED=94=84=EB=A0=88=EC=9E=84=20?= =?UTF-8?q?=ED=8E=B8=EC=A7=91=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EC=95=88=EB=9C=A8?= =?UTF-8?q?=EB=8A=94=EA=B1=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../view/dialog/EditTimetableFrameDialog.kt | 1 + .../ui/timetablev2/TimetableSemesterActivity.kt | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt index a9190252f..9a8f82098 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt @@ -86,6 +86,7 @@ fun EditTimetableFrameDialog( timetableName = timetableName, onDismiss = { showingDeleteDialog = false }, onConfirm = { + showingDeleteDialog = false timetableFrameState?.let { onDeleteFrame() } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index 4b63c9dd7..71eff105a 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -2,18 +2,23 @@ package `in`.koreatech.koin.ui.timetablev2 import android.os.Bundle import androidx.activity.viewModels +import androidx.compose.material3.SnackbarHostState +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember import androidx.core.os.bundleOf import androidx.lifecycle.compose.collectAsStateWithLifecycle import dagger.hilt.android.AndroidEntryPoint import `in`.koreatech.koin.R import `in`.koreatech.koin.core.activity.ActivityBase import `in`.koreatech.koin.core.appbar.AppBarBase +import `in`.koreatech.koin.core.designsystem.component.snackbar.CustomSnackBarHost import `in`.koreatech.koin.core.designsystem.theme.KoinTheme import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.ActivityTimetableSemesterBinding import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame import `in`.koreatech.koin.feature.timetable.model.SemesterModel +import `in`.koreatech.koin.feature.timetable.state.TimetableSideEffect import `in`.koreatech.koin.feature.timetable.view.SemesterScreen import `in`.koreatech.koin.feature.timetable.view.dialog.DeleteSemesterDialog import `in`.koreatech.koin.feature.timetable.view.dialog.EditSemesterDialogImpl @@ -40,9 +45,11 @@ class TimetableSemesterActivity : ActivityBase() { binding.timetableListComposeView.setContent { KoinTheme { val dialogUiState by viewModel.dialogUiState.collectAsStateWithLifecycle() + val sideEffect by viewModel.sideEffect.collectAsStateWithLifecycle() + val snackBarHost = remember { SnackbarHostState() } + val isAnonymous by viewModel.isAnonymous.collectAsStateWithLifecycle() val userTimetables by viewModel.userTimetableFrames.collectAsStateWithLifecycle() - val userSemesters by viewModel.userSemesters.collectAsStateWithLifecycle() val years by viewModel.years.collectAsStateWithLifecycle() @@ -72,6 +79,7 @@ class TimetableSemesterActivity : ActivityBase() { }, onDeleteFrame = { viewModel.deleteTimetableFrame() + viewModel.updateEditTimetableDialogVisibility(false) } ) } @@ -97,6 +105,10 @@ class TimetableSemesterActivity : ActivityBase() { onClickEditTimetable = viewModel::onClickEditTimetable ) + CustomSnackBarHost(snackBarHost) + LaunchedEffect(sideEffect) { + + } } } From 199be6574506c301b2cbf7049ca91bf83edd4589 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Tue, 19 Nov 2024 21:54:54 +0900 Subject: [PATCH 26/60] =?UTF-8?q?[Add]=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=90=98=EB=8F=8C=EB=A6=AC=EA=B8=B0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/TimetableRepositoryImpl.kt | 3 +- .../domain/repository/TimetableRepository.kt | 2 +- .../timetable/viewmodel/SemesterViewModel.kt | 151 ++++++++++++++++-- .../timetablev2/TimetableSemesterActivity.kt | 5 +- 4 files changed, 146 insertions(+), 15 deletions(-) diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index a8d0ec26a..55f420a6a 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -58,8 +58,9 @@ class TimetableRepositoryImpl @Inject constructor( } } - override suspend fun putTimetableLectures(lectures: TimetableLecturesQuery): TimetableLectures = + override suspend fun putTimetableLectures(lectures: TimetableLecturesQuery): Result = runCatching { timetableRemoteDataSource.putTimetableLectures(lectures.toTimetableLecturesQueryRequest()).toTimetableLectures() + } override suspend fun putTimetableLectures(key: String, value: TimetableLectures): Result = runCatching { timetableDataStore.putString(key, gson.toJson(value)) diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt index fe263a8a1..1896235ea 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt @@ -17,7 +17,7 @@ interface TimetableRepository { suspend fun getTimetableLectures(timetableFrameId: Int): Result suspend fun getTimetableLectures(semester: String): Result - suspend fun putTimetableLectures(lectures: TimetableLecturesQuery): TimetableLectures + suspend fun putTimetableLectures(lectures: TimetableLecturesQuery): Result suspend fun putTimetableLectures(key: String, value: TimetableLectures): Result suspend fun putTimetableFrame(id: Int, frame: TimetableFrameQuery): Result diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index f997bc649..2a49c691a 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -3,7 +3,11 @@ package `in`.koreatech.koin.feature.timetable.viewmodel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import `in`.koreatech.koin.core.viewmodel.BaseViewModel +import `in`.koreatech.koin.domain.model.timetable.request.TimetableLectureQuery +import `in`.koreatech.koin.domain.model.timetable.request.TimetableLecturesQuery import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame +import `in`.koreatech.koin.domain.model.timetable.response.TimetableLectures +import `in`.koreatech.koin.domain.repository.TimetableRepository import `in`.koreatech.koin.domain.usecase.timetable.AddSemesterUseCase import `in`.koreatech.koin.domain.usecase.timetable.AddTimetableFrameUseCase import `in`.koreatech.koin.domain.usecase.timetable.DeleteSemesterUseCase @@ -15,6 +19,7 @@ import `in`.koreatech.koin.domain.usecase.timetable.GetUserSemestersUseCase import `in`.koreatech.koin.domain.usecase.timetable.UpdateTimetableFrameUseCase import `in`.koreatech.koin.domain.usecase.user.GetUserStatusUseCase import `in`.koreatech.koin.feature.timetable.model.SemesterModel +import `in`.koreatech.koin.feature.timetable.state.TimetableSideEffect import `in`.koreatech.koin.feature.timetable.utils.toSemesterModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -31,6 +36,7 @@ import javax.inject.Inject @HiltViewModel class SemesterViewModel @Inject constructor( + private val timetableRepository: TimetableRepository, private val getUserSemestersUseCase: GetUserSemestersUseCase, private val getSemestersUseCase: GetSemestersUseCase, private val getUserStatusUseCase: GetUserStatusUseCase, @@ -46,6 +52,9 @@ class SemesterViewModel @Inject constructor( private val _dialogUiState: MutableStateFlow = MutableStateFlow(SemesterDialogUiState()) val dialogUiState: StateFlow = _dialogUiState.asStateFlow() + private val _sideEffect: MutableStateFlow = MutableStateFlow(TimetableSideEffect.Nothing) + val sideEffect: StateFlow = _sideEffect.asStateFlow() + private val _isAnonymous: MutableStateFlow = MutableStateFlow(true) val isAnonymous: StateFlow = _isAnonymous.asStateFlow() @@ -79,7 +88,10 @@ class SemesterViewModel @Inject constructor( getTimetableFramesUseCase(semester) .catch { Timber.d("Fail to getUserSemestersUseCase on initData()| message: ${it.message}") } .collect { - tmp.put(semester.toSemesterModel(), it) + // 기본 시간표가 첫 번째에 오도록 정렬 + it.sortedByDescending { it.isMain }.also { sortedFrames -> + tmp.put(semester.toSemesterModel(), sortedFrames) + } } } _userTimetableFrames.value = tmp @@ -173,10 +185,8 @@ class SemesterViewModel @Inject constructor( name = timetableFrame.timetableName, isMain = timetableFrame.isMain, ).onSuccess { - _userTimetableFrames.update { - it.mapValues { - it.value.map { if (it.id == timetableFrame.id) timetableFrame else it } - } + dialogUiState.value.editedSemester?.let { + refreshSemesterTimetableFrames(it) } }.onFailure { Timber.d("시간표 프레임 수정 실패") @@ -186,6 +196,20 @@ class SemesterViewModel @Inject constructor( fun deleteTimetableFrame() { viewModelScope.launch { + // 삭제된 시간표에 담긴 강의 캐싱 + dialogUiState.value.takeIf { + it.editedSemester != null && it.editedTimetableFrame != null + }?.let { uiState -> + timetableRepository.getTimetableLectures( + uiState.editedTimetableFrame!!.id + ).onSuccess { + _dialogUiState.value = _dialogUiState.value.copy( + deletedTimetableLectures = it + ) + } + } + + // 강의 삭제 dialogUiState.value.editedTimetableFrame?.let { target -> deleteTimetableFrameUseCase( frameId = target.id @@ -200,14 +224,120 @@ class SemesterViewModel @Inject constructor( } } + /** TODO::hyeok usecase로 옮기면 좋을듯 + * 학기의 마지막 시간표를 지웠으면 학기도 같이 사라지기 때문에, 새로 추가를 해야함 + */ + fun restoreTimetableFrame() { + viewModelScope.launch { + dialogUiState.value.takeIf { + it.editedSemester != null + && it.editedTimetableFrame != null + && it.deletedTimetableLectures != null + }?.let { uiState -> + var targetFrame: TimetableFrame? = null + var isRestoredSemester: Boolean = false + + // 학기가 함께 삭제되었는지 확인 + if (userSemesters.value.contains(uiState.editedSemester)) { + // 학기가 삭제되지 않았다면, 바로 프레임 추가 + addTimetableFrameUseCase( + uiState.editedSemester!!.toSemester(), + uiState.editedTimetableFrame!!.timetableName + ).onSuccess { + updateTimetableFrameUseCase( + it.id, + uiState.editedTimetableFrame!!.timetableName, + uiState.editedTimetableFrame!!.isMain + ).onSuccess { + targetFrame = it + } + } + } else { + // 학기가 삭제되었다면, 새로 학기를 새로 추가하고 추가된 학기를 변경 + addSemesterUseCase( + uiState.editedSemester!!.toSemester() + ).onSuccess { + isRestoredSemester = true + updateTimetableFrameUseCase( + it.id, + uiState.editedTimetableFrame!!.timetableName, + uiState.editedTimetableFrame!!.isMain + ).onSuccess { + targetFrame = it + } + } + } + + // 학기 추가 or 프레임 추가가 정상적으로 동작한 경우 강의들 복구 + targetFrame?.let { targetFrame -> + timetableRepository.putTimetableLectures( + TimetableLecturesQuery( + timetableFrameId = targetFrame.id, + timetableLecture = uiState.deletedTimetableLectures!!.timetable.map { + TimetableLectureQuery( + id = it.id, + lectureId = it.lectureId, + classTitle = it.classTitle, + classTime = it.classTime, + classPlace = it.classPlace, + professor = it.professor, + grades = it.grades, + memo = it.memo + ) + } + ) + ).onSuccess { + + }.onFailure { + if (isRestoredSemester) { + deleteSemesterUseCase(uiState.editedSemester!!.toSemester()) + } else { + deleteTimetableFrameUseCase(targetFrame.id) + } + } + refreshSemesterTimetableFrames(semester = uiState.editedSemester) + } + } + } + } + + + /** + * _userTimetableFrames 을 서버 데이터로 갱신 + * 시간표가 존재하지 않는 학기인 경우 삭제함 + */ private suspend fun refreshSemesterTimetableFrames(semester: SemesterModel) { getTimetableFramesUseCase(semester.toSemester()) .catch { Timber.d("Fail to getTimetableFramesUseCase on refreshSemesterTimetableFrames()| message: ${it.message}") } .firstOrNull() - ?.let { newFrames -> - _userTimetableFrames.update { - it.mapValues { - if (it.key == semester) newFrames else it.value + .let { newFrames -> + if (newFrames.isNullOrEmpty()) { + _userSemester.update { + it - semester + } + _userTimetableFrames.update { + it - semester + } + } else { + _userSemester.update { + if (it.contains(semester)) + it + else + it + semester + } + _userTimetableFrames.update { + it.let { + if (it.containsKey(semester)) { + it + } else { + it + (semester to emptyList()) + } + }.mapValues { + if (it.key == semester) { + newFrames.sortedByDescending { it.isMain } + } else + it.value + }.toSortedMap() } } } @@ -216,8 +346,9 @@ class SemesterViewModel @Inject constructor( data class SemesterDialogUiState( val editedSemester: SemesterModel? = null, - val selectedSemesters: List = emptyList(), val editedTimetableFrame: TimetableFrame? = null, + val deletedTimetableLectures: TimetableLectures? = null, + val selectedSemesters: List = emptyList(), val isEditTimetableDialogVisible: Boolean = false, val isEditSemesterDialogVisible: Boolean = false, val isDeleteSemesterDialogVisible: Boolean = false diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index 71eff105a..3b83b78df 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -18,7 +18,6 @@ import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.ActivityTimetableSemesterBinding import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame import `in`.koreatech.koin.feature.timetable.model.SemesterModel -import `in`.koreatech.koin.feature.timetable.state.TimetableSideEffect import `in`.koreatech.koin.feature.timetable.view.SemesterScreen import `in`.koreatech.koin.feature.timetable.view.dialog.DeleteSemesterDialog import `in`.koreatech.koin.feature.timetable.view.dialog.EditSemesterDialogImpl @@ -59,7 +58,7 @@ class TimetableSemesterActivity : ActivityBase() { userSemesters = userSemesters, onConfirm = { selectedSemesters -> viewModel.updateSelectedSemesters(selectedSemesters) - if(selectedSemesters.any { it in userSemesters}) + if (selectedSemesters.any { it in userSemesters }) viewModel.updateDeleteSemesterDialogVisible(true) else { viewModel.updateEditSemesterDialogVisible(false) @@ -130,7 +129,7 @@ class TimetableSemesterActivity : ActivityBase() { private fun finishActivityWithResult(semester: SemesterModel, timetableFrame: TimetableFrame) { bundleOf().apply { putString(SEMESTER, semester.toSemester()) - if(!viewModel.isAnonymous.value) { + if (!viewModel.isAnonymous.value) { putInt(TIMETABLE_FRAME_ID, timetableFrame.id) putString(TIMETABLE_FRAME_NAME, timetableFrame.timetableName) } From 345c299e3f0e05f0d31dd1b4eb2536877dff339f Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 01:14:48 +0900 Subject: [PATCH 27/60] =?UTF-8?q?[Add]=20=EC=8A=A4=EB=82=B5=EB=B0=94=20Act?= =?UTF-8?q?ion=20=EC=BD=9C=EB=B0=B1=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../designsystem/component/snackbar/CustomSnackBarHost.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt index 46ec1810f..100048beb 100644 --- a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt +++ b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt @@ -42,7 +42,8 @@ fun CustomSnackBarHost( background: Color = Color.Black, alignment: Alignment = Alignment.BottomCenter, paddingValues: PaddingValues = PaddingValues(bottom = 20.dp, start = 10.dp, end = 10.dp), - innerPaddingValues: PaddingValues = PaddingValues(horizontal = 10.dp, vertical = 16.dp) + innerPaddingValues: PaddingValues = PaddingValues(horizontal = 10.dp, vertical = 16.dp), + onAction: (() -> Unit)? = null ) { SnackbarHost( hostState = hotState, @@ -57,7 +58,10 @@ fun CustomSnackBarHost( alignment = alignment, paddingValues = paddingValues, innerPaddingValues = innerPaddingValues, - onAction = { snackbarData.dismiss() } + onAction = { + onAction?.invoke() + snackbarData.dismiss() + } ) } From 3244e11d5cdbdcd755441c529315a9399547643f Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 01:15:44 +0900 Subject: [PATCH 28/60] =?UTF-8?q?[Add]=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=90=98=EB=8F=8C=EB=A6=AC=EA=B8=B0=20?= =?UTF-8?q?=EC=8A=A4=EB=82=B5=EB=B0=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/viewmodel/SemesterViewModel.kt | 131 ++++++++++-------- .../timetablev2/TimetableSemesterActivity.kt | 23 ++- 2 files changed, 94 insertions(+), 60 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index 2a49c691a..3365d4e03 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -21,6 +21,7 @@ import `in`.koreatech.koin.domain.usecase.user.GetUserStatusUseCase import `in`.koreatech.koin.feature.timetable.model.SemesterModel import `in`.koreatech.koin.feature.timetable.state.TimetableSideEffect import `in`.koreatech.koin.feature.timetable.utils.toSemesterModel +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -72,6 +73,8 @@ class SemesterViewModel @Inject constructor( .map { it.map { it.year }.distinct() } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), emptyList()) + private var _isRestorePerformed = false + fun initData() { viewModelScope.launch { getUserSemestersUseCase(_isAnonymous.value) @@ -120,6 +123,10 @@ class SemesterViewModel @Inject constructor( _dialogUiState.value = _dialogUiState.value.copy(selectedSemesters = semesterModels) } + fun updateSideEffect(sideEffect: TimetableSideEffect) { + _sideEffect.value = sideEffect + } + fun onClickAddTimetable(target: SemesterModel) { viewModelScope.launch { addTimetableFrameUseCase( @@ -228,77 +235,83 @@ class SemesterViewModel @Inject constructor( * 학기의 마지막 시간표를 지웠으면 학기도 같이 사라지기 때문에, 새로 추가를 해야함 */ fun restoreTimetableFrame() { - viewModelScope.launch { - dialogUiState.value.takeIf { - it.editedSemester != null - && it.editedTimetableFrame != null - && it.deletedTimetableLectures != null - }?.let { uiState -> - var targetFrame: TimetableFrame? = null - var isRestoredSemester: Boolean = false + if (!_isRestorePerformed) + _isRestorePerformed = true + viewModelScope.launch { + delay(500L) + _isRestorePerformed = false + } + viewModelScope.launch { + dialogUiState.value.takeIf { + it.editedSemester != null + && it.editedTimetableFrame != null + && it.deletedTimetableLectures != null + }?.let { uiState -> + var targetFrame: TimetableFrame? = null + var isRestoredSemester: Boolean = false - // 학기가 함께 삭제되었는지 확인 - if (userSemesters.value.contains(uiState.editedSemester)) { - // 학기가 삭제되지 않았다면, 바로 프레임 추가 - addTimetableFrameUseCase( - uiState.editedSemester!!.toSemester(), - uiState.editedTimetableFrame!!.timetableName - ).onSuccess { - updateTimetableFrameUseCase( - it.id, - uiState.editedTimetableFrame!!.timetableName, - uiState.editedTimetableFrame!!.isMain + // 학기가 함께 삭제되었는지 확인 + if (userSemesters.value.contains(uiState.editedSemester)) { + // 학기가 삭제되지 않았다면, 바로 프레임 추가 + addTimetableFrameUseCase( + uiState.editedSemester!!.toSemester(), + uiState.editedTimetableFrame!!.timetableName ).onSuccess { - targetFrame = it + updateTimetableFrameUseCase( + it.id, + uiState.editedTimetableFrame!!.timetableName, + uiState.editedTimetableFrame!!.isMain + ).onSuccess { + targetFrame = it + } } - } - } else { - // 학기가 삭제되었다면, 새로 학기를 새로 추가하고 추가된 학기를 변경 - addSemesterUseCase( - uiState.editedSemester!!.toSemester() - ).onSuccess { - isRestoredSemester = true - updateTimetableFrameUseCase( - it.id, - uiState.editedTimetableFrame!!.timetableName, - uiState.editedTimetableFrame!!.isMain + } else { + // 학기가 삭제되었다면, 새로 학기를 새로 추가하고 추가된 학기를 변경 + addSemesterUseCase( + uiState.editedSemester!!.toSemester() ).onSuccess { - targetFrame = it + isRestoredSemester = true + updateTimetableFrameUseCase( + it.id, + uiState.editedTimetableFrame!!.timetableName, + uiState.editedTimetableFrame!!.isMain + ).onSuccess { + targetFrame = it + } } } - } - // 학기 추가 or 프레임 추가가 정상적으로 동작한 경우 강의들 복구 - targetFrame?.let { targetFrame -> - timetableRepository.putTimetableLectures( - TimetableLecturesQuery( - timetableFrameId = targetFrame.id, - timetableLecture = uiState.deletedTimetableLectures!!.timetable.map { - TimetableLectureQuery( - id = it.id, - lectureId = it.lectureId, - classTitle = it.classTitle, - classTime = it.classTime, - classPlace = it.classPlace, - professor = it.professor, - grades = it.grades, - memo = it.memo - ) - } - ) - ).onSuccess { + // 학기 추가 or 프레임 추가가 정상적으로 동작한 경우 강의들 복구 + targetFrame?.let { targetFrame -> + timetableRepository.putTimetableLectures( + TimetableLecturesQuery( + timetableFrameId = targetFrame.id, + timetableLecture = uiState.deletedTimetableLectures!!.timetable.map { + TimetableLectureQuery( + id = it.id, + lectureId = it.lectureId, + classTitle = it.classTitle, + classTime = it.classTime, + classPlace = it.classPlace, + professor = it.professor, + grades = it.grades, + memo = it.memo + ) + } + ) + ).onSuccess { - }.onFailure { - if (isRestoredSemester) { - deleteSemesterUseCase(uiState.editedSemester!!.toSemester()) - } else { - deleteTimetableFrameUseCase(targetFrame.id) + }.onFailure { + if (isRestoredSemester) { + deleteSemesterUseCase(uiState.editedSemester!!.toSemester()) + } else { + deleteTimetableFrameUseCase(targetFrame.id) + } } + refreshSemesterTimetableFrames(semester = uiState.editedSemester) } - refreshSemesterTimetableFrames(semester = uiState.editedSemester) } } - } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index 3b83b78df..175fcac3c 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -2,6 +2,7 @@ package `in`.koreatech.koin.ui.timetablev2 import android.os.Bundle import androidx.activity.viewModels +import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -13,11 +14,13 @@ import `in`.koreatech.koin.R import `in`.koreatech.koin.core.activity.ActivityBase import `in`.koreatech.koin.core.appbar.AppBarBase import `in`.koreatech.koin.core.designsystem.component.snackbar.CustomSnackBarHost +import `in`.koreatech.koin.core.designsystem.component.snackbar.showSnackBarWithDismiss import `in`.koreatech.koin.core.designsystem.theme.KoinTheme import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.ActivityTimetableSemesterBinding import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame import `in`.koreatech.koin.feature.timetable.model.SemesterModel +import `in`.koreatech.koin.feature.timetable.state.TimetableSideEffect import `in`.koreatech.koin.feature.timetable.view.SemesterScreen import `in`.koreatech.koin.feature.timetable.view.dialog.DeleteSemesterDialog import `in`.koreatech.koin.feature.timetable.view.dialog.EditSemesterDialogImpl @@ -79,6 +82,7 @@ class TimetableSemesterActivity : ActivityBase() { onDeleteFrame = { viewModel.deleteTimetableFrame() viewModel.updateEditTimetableDialogVisibility(false) + viewModel.updateSideEffect(TimetableSideEffect.SnackBar("${dialogUiState.editedTimetableFrame?.timetableName}가 삭제되었어요")) } ) } @@ -104,9 +108,26 @@ class TimetableSemesterActivity : ActivityBase() { onClickEditTimetable = viewModel::onClickEditTimetable ) - CustomSnackBarHost(snackBarHost) + CustomSnackBarHost( + hotState = snackBarHost, + onAction = { + viewModel.restoreTimetableFrame() + } + ) + LaunchedEffect(sideEffect) { + when (val effect = sideEffect) { + is TimetableSideEffect.SnackBar -> { + snackBarHost.showSnackBarWithDismiss( + message = effect.message, + actionLabel = "되돌리기", + duration = SnackbarDuration.Short + ) + viewModel.updateSideEffect(TimetableSideEffect.Nothing) + } + is TimetableSideEffect.Nothing -> Unit + } } } } From 4056cf86f80856ad239fb21a9e4378687849d461 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 01:48:10 +0900 Subject: [PATCH 29/60] =?UTF-8?q?[Add]=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=EB=B9=84=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EB=8C=80=EC=9D=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/timetable/view/SemesterScreen.kt | 6 ++- .../timetable/viewmodel/SemesterViewModel.kt | 31 +++++++++---- .../timetablev2/TimetableSemesterActivity.kt | 43 +++++++++++++++++-- 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt index 5d58a1b8c..bcb0903f4 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt @@ -40,7 +40,8 @@ fun SemesterScreen( modifier: Modifier = Modifier, onClickTimetable: (SemesterModel, TimetableFrame) -> Unit = { _, _ -> }, onClickAddTimetable: (SemesterModel) -> Unit = {}, - onClickEditTimetable: (SemesterModel, TimetableFrame) -> Unit = {_, _ -> }, + onClickEditTimetable: (SemesterModel, TimetableFrame) -> Unit = { _, _ -> }, + onClickLoginText: () -> Unit = {} ) { Box( modifier = modifier @@ -55,6 +56,9 @@ fun SemesterScreen( ) { if (isAnonymous) { HighlightedText( + modifier = Modifier + .padding(vertical = 6.dp) + .noRippleClickable { onClickLoginText() }, texts = stringArrayResource(id = R.array.semester_anonymous_login), highlightIndices = listOf(0), defaultStyle = KoinTheme.typography.medium14.copy(color = KoinTheme.colors.neutral500), diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index 3365d4e03..c651d8dad 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -77,25 +77,33 @@ class SemesterViewModel @Inject constructor( fun initData() { viewModelScope.launch { + // 유저가 추가한 학기 불러옴 getUserSemestersUseCase(_isAnonymous.value) .catch { Timber.d("Fail to getUserSemestersUseCase on initData()| message: ${it.message}") } .map { it.map { it.toSemesterModel() } } .collect { _userSemester.value = it } - // scan + + // 유저가 추가한 학기의 프레임 불러옴 val tmp = mutableMapOf>() userSemesters.value .map { it.toSemester() } .forEach { semester -> - getTimetableFramesUseCase(semester) - .catch { Timber.d("Fail to getUserSemestersUseCase on initData()| message: ${it.message}") } - .collect { - // 기본 시간표가 첫 번째에 오도록 정렬 - it.sortedByDescending { it.isMain }.also { sortedFrames -> - tmp.put(semester.toSemesterModel(), sortedFrames) + if(isAnonymous.value) { + // 익명이면 모든 프레임의 이름은 '시간표1' + tmp.put(semester.toSemesterModel(), listOf(TimetableFrame(0, "시간표1", isMain = true))) + } else { + getTimetableFramesUseCase(semester) + .catch { Timber.d("Fail to getUserSemestersUseCase on initData()| message: ${it.message}") } + .collect { + // 기본 시간표가 첫 번째에 오도록 정렬 + it.sortedByDescending { it.isMain }.also { sortedFrames -> + tmp.put(semester.toSemesterModel(), sortedFrames) + } } - } + } + } _userTimetableFrames.value = tmp } @@ -119,6 +127,10 @@ class SemesterViewModel @Inject constructor( _dialogUiState.value = _dialogUiState.value.copy(isDeleteSemesterDialogVisible = isVisible) } + fun updateRequestLoginDialogVisible(isVisible: Boolean) { + _dialogUiState.value = _dialogUiState.value.copy(isRequestLoginDialogVisible = isVisible) + } + fun updateSelectedSemesters(semesterModels: List) { _dialogUiState.value = _dialogUiState.value.copy(selectedSemesters = semesterModels) } @@ -364,5 +376,6 @@ data class SemesterDialogUiState( val selectedSemesters: List = emptyList(), val isEditTimetableDialogVisible: Boolean = false, val isEditSemesterDialogVisible: Boolean = false, - val isDeleteSemesterDialogVisible: Boolean = false + val isDeleteSemesterDialogVisible: Boolean = false, + val isRequestLoginDialogVisible: Boolean = false ) \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index 175fcac3c..ed8fc5f23 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -25,6 +25,7 @@ import `in`.koreatech.koin.feature.timetable.view.SemesterScreen import `in`.koreatech.koin.feature.timetable.view.dialog.DeleteSemesterDialog import `in`.koreatech.koin.feature.timetable.view.dialog.EditSemesterDialogImpl import `in`.koreatech.koin.feature.timetable.view.dialog.EditTimetableFrameDialog +import `in`.koreatech.koin.feature.timetable.view.dialog.RequestLoginDialog import `in`.koreatech.koin.feature.timetable.viewmodel.SemesterViewModel import timber.log.Timber @@ -99,13 +100,43 @@ class TimetableSemesterActivity : ActivityBase() { } ) } + if (dialogUiState.isRequestLoginDialogVisible) { + RequestLoginDialog( + onConfirm = { + // TODO::Hyeok 로그인 화면으로 이동 + Timber.d("로그인 화면으로 이동") + viewModel.updateRequestLoginDialogVisible(false) + }, + onDismiss = { + viewModel.updateRequestLoginDialogVisible(false) + } + ) + } SemesterScreen( userTimetables = userTimetables, isAnonymous = isAnonymous, onClickTimetable = ::finishActivityWithResult, - onClickAddTimetable = viewModel::onClickAddTimetable, - onClickEditTimetable = viewModel::onClickEditTimetable + onClickAddTimetable = { + if (viewModel.isAnonymous.value) { + viewModel.updateRequestLoginDialogVisible(true) + } else { + viewModel.onClickAddTimetable(it) + } + }, + onClickEditTimetable = { semester, frame -> + if (viewModel.isAnonymous.value) { + viewModel.updateRequestLoginDialogVisible(true) + } else { + viewModel.onClickEditTimetable( + semester, + frame + ) + } + }, + onClickLoginText = { + viewModel.updateRequestLoginDialogVisible(true) + } ) CustomSnackBarHost( @@ -135,7 +166,13 @@ class TimetableSemesterActivity : ActivityBase() { binding.timetableListAppbar.setOnClickListener { when (it.id) { AppBarBase.getLeftButtonId() -> onBackPressed() - AppBarBase.getRightButtonId() -> viewModel.updateEditSemesterDialogVisible(true) + AppBarBase.getRightButtonId() -> { + if (viewModel.isAnonymous.value) { + viewModel.updateRequestLoginDialogVisible(true) + } else { + viewModel.updateEditSemesterDialogVisible(true) + } + } } } } From 2b780a2125edac7ebfcc1742f42cc93562101f9d Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 02:14:23 +0900 Subject: [PATCH 30/60] =?UTF-8?q?[Add]=20TextCheckBox=20=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94=20=EC=83=89=EC=83=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/component/TextCheckbox.kt | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TextCheckbox.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TextCheckbox.kt index a1f97de81..991e5ba20 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TextCheckbox.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TextCheckbox.kt @@ -27,11 +27,12 @@ fun TextCheckbox( textStyle: TextStyle, isChecked: Boolean, onCheckChanged: (Boolean) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + enabled: Boolean = true ) { Row( modifier = modifier.noRippleClickable { - onCheckChanged(!isChecked) + if(enabled) onCheckChanged(!isChecked) }, horizontalArrangement = Arrangement.spacedBy(5.dp), verticalAlignment = Alignment.CenterVertically @@ -40,13 +41,16 @@ fun TextCheckbox( TriStateCheckbox( state = ToggleableState(isChecked), onClick = null, + enabled = enabled, colors = CheckboxDefaults.colors().copy( checkedBoxColor = KoinTheme.colors.primary500, checkedCheckmarkColor = KoinTheme.colors.neutral0, checkedBorderColor = KoinTheme.colors.primary500, uncheckedBoxColor = KoinTheme.colors.neutral0, uncheckedCheckmarkColor = KoinTheme.colors.neutral0, - uncheckedBorderColor = KoinTheme.colors.neutral400 + uncheckedBorderColor = KoinTheme.colors.neutral400, + disabledCheckedBoxColor = KoinTheme.colors.primary300, + disabledBorderColor = KoinTheme.colors.primary300, ) ) } @@ -83,4 +87,18 @@ private fun TextCheckboxUncheckedPreview() { onCheckChanged = {} ) } +} +@Preview() +@Composable +private fun TextCheckboxDisabledPreview() { + KoinTheme { + TextCheckbox( + modifier = Modifier.background(KoinTheme.colors.neutral0), + text = "체크박스", + textStyle = KoinTheme.typography.regular15, + isChecked = true, + onCheckChanged = {}, + enabled = false + ) + } } \ No newline at end of file From b853dc9923bf59f2e1d62c0ba81927a00fb7deae Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 02:23:07 +0900 Subject: [PATCH 31/60] =?UTF-8?q?[Add]=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=ED=8E=B8=EC=A7=91=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B2=B4=ED=81=AC=EB=B0=95=EC=8A=A4=20=EB=B9=84?= =?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/timetable/view/dialog/EditTimetableFrameDialog.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt index 9a8f82098..807fdcf5f 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt @@ -60,6 +60,7 @@ fun EditTimetableFrameDialog( modifier = modifier, timetableName = timetableName, isMain = isMain, + isCheckboxEnabled = timetableFrameState?.let{ !it.isMain } ?: true, onDismiss = onDismiss, onConfirm = { timetableFrameState?.let { @@ -100,6 +101,7 @@ fun EditTimetableFrameDialog( private fun EditTimetableFrameDialog( timetableName: String, isMain: Boolean, + isCheckboxEnabled: Boolean, onDismiss: () -> Unit, onConfirm: () -> Unit, onClickDelete: () -> Unit, @@ -168,6 +170,7 @@ private fun EditTimetableFrameDialog( text = stringResource(id = R.string.edit_titletable_frame_main), textStyle = KoinTheme.typography.medium14, isChecked = isMain, + enabled = isCheckboxEnabled, onCheckChanged = onCheckChanged ) Row( From f1e4335f6456a1f459bca526b310463e7bb905ea Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 02:41:00 +0900 Subject: [PATCH 32/60] =?UTF-8?q?[Fix]=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=8A=A4=EB=82=B5=EB=B0=94=20ui=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/snackbar/CustomSnackBarHost.kt | 14 +++++++++++++- .../ui/timetablev2/TimetableSemesterActivity.kt | 11 +++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt index 100048beb..c8a45f5a2 100644 --- a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt +++ b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -110,9 +111,10 @@ private fun SnackBarContent( Spacer(modifier = Modifier.weight(0.05f)) if (actionLabelText.isNotEmpty()) { Text( + modifier = Modifier.weight(0.2f).noRippleClickable { onAction() }, text = actionLabelText, style = actionLabelTextStyle, - modifier = Modifier.weight(0.1f).noRippleClickable { onAction() } + textAlign = TextAlign.End ) } @@ -147,4 +149,14 @@ private fun SnackBarContentPreview() { messageText = "스낵바 메시지", actionLabelText = "닫기", ) +} + +@Preview(showBackground = true, showSystemUi = true) +@Composable +private fun SnackBarContentPreview2() { + SnackBarContent( + messageText = "스낵바 메시지", + actionLabelText = "되돌리기", + paddingValues = PaddingValues(bottom = 0.dp, start = 10.dp, end = 10.dp), + ) } \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index ed8fc5f23..b81a2e467 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -2,11 +2,13 @@ package `in`.koreatech.koin.ui.timetablev2 import android.os.Bundle import androidx.activity.viewModels +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.ui.unit.dp import androidx.core.os.bundleOf import androidx.lifecycle.compose.collectAsStateWithLifecycle import dagger.hilt.android.AndroidEntryPoint @@ -141,6 +143,15 @@ class TimetableSemesterActivity : ActivityBase() { CustomSnackBarHost( hotState = snackBarHost, + radius = 6.dp, + messageTextStyle = KoinTheme.typography.regular14.copy( + color = KoinTheme.colors.neutral0 + ), + actionLabelTextStyle = KoinTheme.typography.regular14.copy( + color = KoinTheme.colors.sub500 + ), + background = KoinTheme.colors.primary700, + innerPaddingValues = PaddingValues(horizontal = 16.dp, vertical = 20.dp), onAction = { viewModel.restoreTimetableFrame() } From a3f72b4ffef3c1941b0ad960ce5342a87d66faac Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 03:08:48 +0900 Subject: [PATCH 33/60] =?UTF-8?q?[Fix]=20=EB=B3=B4=EC=97=AC=EC=A7=80?= =?UTF-8?q?=EB=8A=94=20=EC=8B=9C=EA=B0=84=ED=91=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=B5=9C=EB=8C=80=20=EA=B8=B8=EC=9D=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/timetable/view/SemesterScreen.kt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt index bcb0903f4..6a4bf2d3d 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/SemesterScreen.kt @@ -11,6 +11,8 @@ 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.layout.widthIn import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListScope @@ -22,6 +24,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import `in`.koreatech.koin.core.designsystem.component.icon.StableIcon @@ -170,20 +173,26 @@ private fun LazyListScope.TimetableFrameBlock( verticalAlignment = Alignment.CenterVertically ) { Row( - horizontalArrangement = Arrangement.spacedBy(20.dp), + modifier = Modifier.weight(1f), + horizontalArrangement = Arrangement.spacedBy(0.dp), verticalAlignment = Alignment.CenterVertically ) { Text( + modifier = Modifier.weight(1f), text = timetableFrame.timetableName, style = KoinTheme.typography.medium18.copy( KoinTheme.colors.neutral800 - ) + ), + overflow = TextOverflow.Ellipsis, + maxLines = 1 ) if (!isAnonymous && timetableFrame.isMain) { + Spacer(modifier = Modifier.width(10.dp)) StableIcon( drawableResId = R.drawable.ic_flag_main, tint = Color.Unspecified ) + Spacer(modifier = Modifier.width(16.dp)) } } @@ -206,14 +215,14 @@ private fun SemesterScreenPreview() { SemesterScreen( userTimetables = mutableMapOf( SemesterModel(2024, SemesterType.Fall) to listOf( - TimetableFrame(0, "시간표1", true), + TimetableFrame(0, "시간표3시간표3시간표3시간표3시간표3시간표3시간표3시간표3시간표3시간표3시간표3", true), TimetableFrame(1, "시간표2", false), TimetableFrame(2, "시간표3", false), ), SemesterModel(2024, SemesterType.Spring) to listOf(TimetableFrame(0, "시간표1", true)), SemesterModel(2024, SemesterType.Winter) to listOf( TimetableFrame(0, "시간표1", true), - TimetableFrame(2, "시간표3", false) + TimetableFrame(2, "시간표3시간표3시간표3시간표3시간표3시간표3시간표3시간표3시간표3시간표3시간표3", false) ), ), isAnonymous = false, From e239bc21c3c5fad10d4cd4413932f64b33e3e2c5 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 18:55:12 +0900 Subject: [PATCH 34/60] =?UTF-8?q?[Fix]=20TODO=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/feature/timetable/view/dialog/EditSemesterDialog.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt index 2d89d6bb9..e737b0593 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditSemesterDialog.kt @@ -44,8 +44,7 @@ import `in`.koreatech.koin.feature.timetable.model.SemesterModel import `in`.koreatech.koin.feature.timetable.model.SemesterType import java.time.LocalDate -// TODO::hyeok minsdk 올라가면 제거 -@RequiresApi(Build.VERSION_CODES.O) + @Composable fun EditSemesterDialogImpl( years: List, @@ -59,7 +58,6 @@ fun EditSemesterDialogImpl( var isShowingSelectYearDialog by remember { mutableStateOf(false) } - // TODO::hyeok 연도 눌렀을 때 깜빡거림 Box( modifier = modifier ) { From 3c6da5b6d46547fb8e546c2551c609587d1a6c71 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 19:05:19 +0900 Subject: [PATCH 35/60] =?UTF-8?q?[Add]=20=ED=95=99=EA=B8=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=84=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/data/repository/TimetableRepositoryImpl.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index 55f420a6a..dfad9bef8 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -10,6 +10,7 @@ import `in`.koreatech.koin.data.request.timetable.toLectureQueryRequest import `in`.koreatech.koin.data.request.timetable.toTimetableLecturesQueryRequest import `in`.koreatech.koin.data.source.datastore.TimetableDataStore import `in`.koreatech.koin.data.source.remote.TimetableRemoteDataSource +import `in`.koreatech.koin.data.util.getErrorResponse import `in`.koreatech.koin.domain.model.timetable.request.TimetableFrameCreateQuery import `in`.koreatech.koin.domain.model.timetable.request.TimetableFrameQuery import `in`.koreatech.koin.domain.model.timetable.request.TimetableLecturesQuery @@ -20,6 +21,7 @@ import `in`.koreatech.koin.domain.repository.TimetableRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flow +import retrofit2.HttpException import javax.inject.Inject class TimetableRepositoryImpl @Inject constructor( @@ -103,6 +105,9 @@ class TimetableRepositoryImpl @Inject constructor( timetableName = frame.timetableName ) ).toTimetableFrameResponse() + }.recoverCatching { + if(it is HttpException) throw Exception(it.getErrorResponse().message ?: "") + else throw it } From 12543737d74790de320a3a7c96de40ab09d56160 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 19:05:46 +0900 Subject: [PATCH 36/60] =?UTF-8?q?[Add]=20SemesterSideEffect=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/timetable/state/SemesterSideEffect.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/SemesterSideEffect.kt diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/SemesterSideEffect.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/SemesterSideEffect.kt new file mode 100644 index 000000000..9c1b06f25 --- /dev/null +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/state/SemesterSideEffect.kt @@ -0,0 +1,13 @@ +package `in`.koreatech.koin.feature.timetable.state + +sealed class SemesterSideEffect { + data class SnackBar( + val message: String + ): SemesterSideEffect() + + data class Toast( + val message: String + ): SemesterSideEffect() + + data object Nothing: SemesterSideEffect() +} \ No newline at end of file From 9440050160757721c2d85f939513aab8de889fb5 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 19:20:49 +0900 Subject: [PATCH 37/60] =?UTF-8?q?[Add]=20=EC=97=90=EB=9F=AC=20=ED=86=A0?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/viewmodel/SemesterViewModel.kt | 15 ++++++++++----- .../timetablev2/TimetableSemesterActivity.kt | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index c651d8dad..9751719c2 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -19,7 +19,7 @@ import `in`.koreatech.koin.domain.usecase.timetable.GetUserSemestersUseCase import `in`.koreatech.koin.domain.usecase.timetable.UpdateTimetableFrameUseCase import `in`.koreatech.koin.domain.usecase.user.GetUserStatusUseCase import `in`.koreatech.koin.feature.timetable.model.SemesterModel -import `in`.koreatech.koin.feature.timetable.state.TimetableSideEffect +import `in`.koreatech.koin.feature.timetable.state.SemesterSideEffect import `in`.koreatech.koin.feature.timetable.utils.toSemesterModel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -53,8 +53,8 @@ class SemesterViewModel @Inject constructor( private val _dialogUiState: MutableStateFlow = MutableStateFlow(SemesterDialogUiState()) val dialogUiState: StateFlow = _dialogUiState.asStateFlow() - private val _sideEffect: MutableStateFlow = MutableStateFlow(TimetableSideEffect.Nothing) - val sideEffect: StateFlow = _sideEffect.asStateFlow() + private val _sideEffect: MutableStateFlow = MutableStateFlow(SemesterSideEffect.Nothing) + val sideEffect: StateFlow = _sideEffect.asStateFlow() private val _isAnonymous: MutableStateFlow = MutableStateFlow(true) val isAnonymous: StateFlow = _isAnonymous.asStateFlow() @@ -135,7 +135,7 @@ class SemesterViewModel @Inject constructor( _dialogUiState.value = _dialogUiState.value.copy(selectedSemesters = semesterModels) } - fun updateSideEffect(sideEffect: TimetableSideEffect) { + fun updateSideEffect(sideEffect: SemesterSideEffect) { _sideEffect.value = sideEffect } @@ -190,6 +190,10 @@ class SemesterViewModel @Inject constructor( _userTimetableFrames.update { (it + (semester to listOf(addedFrame))).toSortedMap() } + }.onFailure { + it.message?.let { errorMessage -> + _sideEffect.value = SemesterSideEffect.Toast(errorMessage) + } } } } @@ -247,7 +251,7 @@ class SemesterViewModel @Inject constructor( * 학기의 마지막 시간표를 지웠으면 학기도 같이 사라지기 때문에, 새로 추가를 해야함 */ fun restoreTimetableFrame() { - if (!_isRestorePerformed) + if (!_isRestorePerformed) { _isRestorePerformed = true viewModelScope.launch { delay(500L) @@ -324,6 +328,7 @@ class SemesterViewModel @Inject constructor( } } } + } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index b81a2e467..3568a727c 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -18,11 +18,12 @@ import `in`.koreatech.koin.core.appbar.AppBarBase import `in`.koreatech.koin.core.designsystem.component.snackbar.CustomSnackBarHost import `in`.koreatech.koin.core.designsystem.component.snackbar.showSnackBarWithDismiss import `in`.koreatech.koin.core.designsystem.theme.KoinTheme +import `in`.koreatech.koin.core.toast.ToastUtil import `in`.koreatech.koin.core.util.dataBinding import `in`.koreatech.koin.databinding.ActivityTimetableSemesterBinding import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame import `in`.koreatech.koin.feature.timetable.model.SemesterModel -import `in`.koreatech.koin.feature.timetable.state.TimetableSideEffect +import `in`.koreatech.koin.feature.timetable.state.SemesterSideEffect import `in`.koreatech.koin.feature.timetable.view.SemesterScreen import `in`.koreatech.koin.feature.timetable.view.dialog.DeleteSemesterDialog import `in`.koreatech.koin.feature.timetable.view.dialog.EditSemesterDialogImpl @@ -85,7 +86,7 @@ class TimetableSemesterActivity : ActivityBase() { onDeleteFrame = { viewModel.deleteTimetableFrame() viewModel.updateEditTimetableDialogVisibility(false) - viewModel.updateSideEffect(TimetableSideEffect.SnackBar("${dialogUiState.editedTimetableFrame?.timetableName}가 삭제되었어요")) + viewModel.updateSideEffect(SemesterSideEffect.SnackBar("${dialogUiState.editedTimetableFrame?.timetableName}가 삭제되었어요")) } ) } @@ -159,16 +160,21 @@ class TimetableSemesterActivity : ActivityBase() { LaunchedEffect(sideEffect) { when (val effect = sideEffect) { - is TimetableSideEffect.SnackBar -> { + is SemesterSideEffect.SnackBar -> { snackBarHost.showSnackBarWithDismiss( message = effect.message, actionLabel = "되돌리기", duration = SnackbarDuration.Short ) - viewModel.updateSideEffect(TimetableSideEffect.Nothing) + viewModel.updateSideEffect(SemesterSideEffect.Nothing) } - is TimetableSideEffect.Nothing -> Unit + is SemesterSideEffect.Toast -> { + ToastUtil.getInstance().makeShort(effect.message) + viewModel.updateSideEffect(SemesterSideEffect.Nothing) + } + + is SemesterSideEffect.Nothing -> Unit } } } @@ -198,9 +204,9 @@ class TimetableSemesterActivity : ActivityBase() { private fun finishActivityWithResult(semester: SemesterModel, timetableFrame: TimetableFrame) { bundleOf().apply { putString(SEMESTER, semester.toSemester()) + putString(TIMETABLE_FRAME_NAME, timetableFrame.timetableName) if (!viewModel.isAnonymous.value) { putInt(TIMETABLE_FRAME_ID, timetableFrame.id) - putString(TIMETABLE_FRAME_NAME, timetableFrame.timetableName) } } From d64437d9b48e195405807be157fae4327dfe4ecf Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Wed, 20 Nov 2024 19:30:39 +0900 Subject: [PATCH 38/60] =?UTF-8?q?[Fix]=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD=ED=95=9C=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/ui/navigation/KoinNavigationDrawerActivity.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt index 80a4872f7..ab9ae3ddb 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt @@ -315,8 +315,7 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), MenuState.Article -> goToArticleActivity() MenuState.Contact -> { - startActivity(Intent(this@KoinNavigationDrawerActivity, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) -// goToContactWebActivity() + goToContactWebActivity() } else -> Unit From d18347ce4093ab65f78bc7a7a0a5e781417e76ef Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Wed, 20 Nov 2024 20:15:49 +0900 Subject: [PATCH 39/60] [fix] custombarhost --- .../component/snackbar/CustomSnackBarHost.kt | 37 +++++-------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt index 79aff645d..4b7fd1792 100644 --- a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt +++ b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt @@ -62,12 +62,13 @@ fun CustomSnackBarHost( messageTextStyle = messageTextStyle, actionLabelTextStyle = actionLabelTextStyle, innerPaddingValues = innerPaddingValues, + onAction = { + onAction?.invoke() + snackbarData.dismiss() + } ) } - onAction?.invoke() - snackbarData.dismiss() - onAction = { - } + } } @@ -106,24 +107,14 @@ private fun SnackBarContent( Spacer(modifier = Modifier.weight(0.05f)) if (actionLabelText.isNotEmpty()) { Text( + modifier = Modifier + .weight(0.2f) + .noRippleClickable { onAction() }, text = actionLabelText, style = actionLabelTextStyle, - modifier = Modifier - .weight(0.1f) - .noRippleClickable { onAction() } + textAlign = TextAlign.End ) - Spacer(modifier = Modifier.weight(0.05f)) - if (actionLabelText.isNotEmpty()) { - Text( - modifier = Modifier.weight(0.2f).noRippleClickable { onAction() }, - text = actionLabelText, - style = actionLabelTextStyle, - textAlign = TextAlign.End - ) - } - } - } } } @@ -155,13 +146,3 @@ private fun SnackBarContentPreview() { actionLabelText = "닫기", ) } - -@Preview(showBackground = true, showSystemUi = true) -@Composable -private fun SnackBarContentPreview2() { - SnackBarContent( - messageText = "스낵바 메시지", - actionLabelText = "되돌리기", - paddingValues = PaddingValues(bottom = 0.dp, start = 10.dp, end = 10.dp), - ) -} \ No newline at end of file From 569b09a40008ee45a435d55b837f27f611a45cd9 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Wed, 20 Nov 2024 21:36:01 +0900 Subject: [PATCH 40/60] =?UTF-8?q?[fix]=20=EA=B0=80=EB=B3=B4=EC=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/viewmodel/SemesterViewModel.kt | 7 +++- .../timetable/viewmodel/TimetableViewModel.kt | 18 ++++++-- .../koin/ui/timetablev2/TimetableActivity.kt | 18 ++++++-- .../timetablev2/TimetableSemesterActivity.kt | 41 +++++++++++++------ 4 files changed, 64 insertions(+), 20 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index 9751719c2..0be007d60 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import timber.log.Timber +import java.lang.Thread.State import javax.inject.Inject @HiltViewModel @@ -56,6 +57,9 @@ class SemesterViewModel @Inject constructor( private val _sideEffect: MutableStateFlow = MutableStateFlow(SemesterSideEffect.Nothing) val sideEffect: StateFlow = _sideEffect.asStateFlow() + private val _timetableSemester: MutableStateFlow = MutableStateFlow("") + val timetableSemester: StateFlow = _timetableSemester.asStateFlow() + private val _isAnonymous: MutableStateFlow = MutableStateFlow(true) val isAnonymous: StateFlow = _isAnonymous.asStateFlow() @@ -109,9 +113,10 @@ class SemesterViewModel @Inject constructor( } } - fun updateIsAnonymous(isAnonymous: Boolean) { + fun updateIntentData(isAnonymous: Boolean) { viewModelScope.launch { _isAnonymous.value = isAnonymous + _timetableSemester.value = } } diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt index 502329c1c..667feae6f 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt @@ -149,16 +149,28 @@ class TimetableViewModel @Inject constructor( } } - fun getRefreshData(frameId: Int, semester: String, frameName: String) { // TODO : 시간표 제목도 보내주쇼.. - if (state.value.frameId == frameId) return + fun getRefreshData(frameId: Int, semester: String, frameName: String) { viewModelScope.launch { updateLoading(true) _lectures.value = getLectures(semester) when (state.value.isAnonymous) { true -> { - + timetableRepository.getTimetableLectures(semester) + .onSuccess { timetableLectures -> + _state.value = _state.value.copy( + range = timetableLectures.formatTimeRange(), + timetableEvents = timetableLectures.getTimetableEvents(), + currentSemester = semester, + timetableLectures = timetableLectures, + loading = false + ) + }.onFailure { + updateLoading(false) + Timber.e("getTimetableLectures Local Error Message : ${it.message}") + } } false -> { + if (state.value.frameId == frameId) return@launch timetableRepository.getTimetableLectures(frameId) .onSuccess { timetableLectures -> _state.value = _state.value.copy( diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index 3819417f6..4ea1102d9 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -63,8 +63,10 @@ class TimetableActivity : KoinNavigationDrawerActivity() { if (it.resultCode == RESULT_OK) { it.data?.getBundleExtra(TimetableSemesterActivity.BUNDLE_EXTRA_KEY)?.let { val frameId = it.getInt(TimetableSemesterActivity.TIMETABLE_FRAME_ID) - val semester = it.getString(TimetableSemesterActivity.SEMESTER) - viewModel.getRefreshData(frameId, semester.orEmpty(), "") + val semester = it.getString(TimetableSemesterActivity.SEMESTER).orEmpty() + val frameName = + it.getString(TimetableSemesterActivity.TIMETABLE_FRAME_NAME).orEmpty() + viewModel.getRefreshData(frameId, semester, frameName) } } } @@ -102,7 +104,7 @@ class TimetableActivity : KoinNavigationDrawerActivity() { hideKeyboard(sheetState.isCollapsed) setAppbarEvent { - lectures.ifEmpty { + state.semesters.ifEmpty { viewModel.updateSideEffect(TimetableSideEffect.SnackBar(getString(R.string.timetable_error_no_semester))) return@setAppbarEvent } @@ -329,7 +331,13 @@ class TimetableActivity : KoinNavigationDrawerActivity() { private fun startToTimetableSemesterActivity() { val intent = Intent(this, TimetableSemesterActivity::class.java).apply { - putExtra(BUNDLE_EXTRA_KEY, bundleOf(IS_ANONYMOUS to viewModel.state.value.isAnonymous)) + putExtra( + BUNDLE_EXTRA_KEY, bundleOf( + IS_ANONYMOUS to viewModel.state.value.isAnonymous, + SEMESTER to viewModel.state.value.currentSemester, + FRAME_ID to viewModel.state.value.frameId + ) + ) } registerTimetableSemesterActivityResult.launch(intent) } @@ -372,5 +380,7 @@ class TimetableActivity : KoinNavigationDrawerActivity() { const val BUNDLE_LOGIN_EXTRA_KEY = "BUNDLE_EXTRA_KEY" const val NAV_TIMETABLE = "timetable" const val IS_ANONYMOUS = "isAnonymous" + const val SEMESTER = "semester" + const val FRAME_ID = "frameId" } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index 3568a727c..cc9915230 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -1,6 +1,8 @@ package `in`.koreatech.koin.ui.timetablev2 +import android.content.Intent import android.os.Bundle +import androidx.activity.OnBackPressedCallback import androidx.activity.viewModels import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material3.SnackbarDuration @@ -39,12 +41,20 @@ class TimetableSemesterActivity : ActivityBase() { private val binding by dataBinding() private val viewModel by viewModels() +// val onBackPressedCallback = object : OnBackPressedCallback(true){ +// override fun handleOnBackPressed() { +// // handle event +// finishActivityWithResult(viewModel.semesters.value.to) +// } +// } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_timetable_semester) - getIntentBundle { isAnonymous -> - Timber.e("isAnonymous : $isAnonymous") - viewModel.updateIsAnonymous(false) + getIntentBundle { bundle -> + val isAnonymous = bundle.getBoolean(TimetableActivity.IS_ANONYMOUS, false) + val semester = bundle.getString(TimetableActivity.SEMESTER).orEmpty() + viewModel.updateIntentData(isAnonymous) } viewModel.initData() @@ -194,20 +204,27 @@ class TimetableSemesterActivity : ActivityBase() { } } - private fun getIntentBundle(callback: (isAnonymous: Boolean) -> Unit) { - val bundle = intent.getBundleExtra(TimetableActivity.BUNDLE_EXTRA_KEY) - bundle?.getBoolean(TimetableActivity.IS_ANONYMOUS)?.let { + + private fun getIntentBundle(callback: (bundle: Bundle) -> Unit) { + intent.getBundleExtra(TimetableActivity.BUNDLE_EXTRA_KEY)?.let { callback(it) - } + } ?: return } private fun finishActivityWithResult(semester: SemesterModel, timetableFrame: TimetableFrame) { - bundleOf().apply { - putString(SEMESTER, semester.toSemester()) - putString(TIMETABLE_FRAME_NAME, timetableFrame.timetableName) - if (!viewModel.isAnonymous.value) { - putInt(TIMETABLE_FRAME_ID, timetableFrame.id) + val intent = Intent().apply { + val bundle = if (!viewModel.isAnonymous.value) { + bundleOf( + SEMESTER to semester.toSemester(), + TIMETABLE_FRAME_ID to timetableFrame.id, + TIMETABLE_FRAME_NAME to timetableFrame.timetableName + ) + } else { + bundleOf( + SEMESTER to semester.toSemester() + ) } + putExtra(BUNDLE_EXTRA_KEY, bundle) } setResult(RESULT_OK, intent) From e8a30328bc920112d553f53b178524ea3f70ab03 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Thu, 21 Nov 2024 00:20:30 +0900 Subject: [PATCH 41/60] =?UTF-8?q?[Add]=20=ED=95=99=EA=B8=B0=20->=20?= =?UTF-8?q?=EC=8B=9C=EA=B0=84=ED=91=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=9E=91=EC=97=85=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/viewmodel/SemesterViewModel.kt | 94 +++++++++++++++++-- .../koin/ui/timetablev2/TimetableActivity.kt | 4 +- .../timetablev2/TimetableSemesterActivity.kt | 43 +++++++-- 3 files changed, 124 insertions(+), 17 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index 0be007d60..e8a9a8bfd 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -33,7 +33,6 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import timber.log.Timber -import java.lang.Thread.State import javax.inject.Inject @HiltViewModel @@ -57,8 +56,17 @@ class SemesterViewModel @Inject constructor( private val _sideEffect: MutableStateFlow = MutableStateFlow(SemesterSideEffect.Nothing) val sideEffect: StateFlow = _sideEffect.asStateFlow() - private val _timetableSemester: MutableStateFlow = MutableStateFlow("") - val timetableSemester: StateFlow = _timetableSemester.asStateFlow() + private val _currentTimetableSemester: MutableStateFlow = MutableStateFlow("") + val currentTimetableSemester: StateFlow = _currentTimetableSemester.asStateFlow() + + private val _currentTimetableId: MutableStateFlow = MutableStateFlow(-1) + val currentTimetableId: StateFlow = _currentTimetableId.asStateFlow() + + private val _currentTimetableName: MutableStateFlow = MutableStateFlow("") + val currentTimetableName: StateFlow = _currentTimetableName.asStateFlow() + + private val _originalTimetableId: MutableStateFlow = MutableStateFlow(-1) + val originalTimetableId: StateFlow = _currentTimetableId.asStateFlow() private val _isAnonymous: MutableStateFlow = MutableStateFlow(true) val isAnonymous: StateFlow = _isAnonymous.asStateFlow() @@ -94,7 +102,7 @@ class SemesterViewModel @Inject constructor( userSemesters.value .map { it.toSemester() } .forEach { semester -> - if(isAnonymous.value) { + if (isAnonymous.value) { // 익명이면 모든 프레임의 이름은 '시간표1' tmp.put(semester.toSemesterModel(), listOf(TimetableFrame(0, "시간표1", isMain = true))) } else { @@ -113,10 +121,13 @@ class SemesterViewModel @Inject constructor( } } - fun updateIntentData(isAnonymous: Boolean) { + fun updateIntentData(isAnonymous: Boolean, timetableFrameId: Int, semester: String, frameName: String) { viewModelScope.launch { _isAnonymous.value = isAnonymous - _timetableSemester.value = + _currentTimetableId.value = timetableFrameId + _originalTimetableId.value = timetableFrameId + _currentTimetableSemester.value = semester + _currentTimetableName.value = frameName } } @@ -202,6 +213,14 @@ class SemesterViewModel @Inject constructor( } } } + + // 시간표에서 진입한 학기가 삭제된 경우 + if (!userSemesters.value.contains(_currentTimetableSemester.value.toSemesterModel())) { + Timber.d("userSemesters: ${userSemesters.value}") + Timber.d("userTimetableFrames: ${userTimetableFrames.value}") + // 가장 최근 학기의 기본 시간표로 설정 + updateCurrentTimetableDataToLatest() + } } } @@ -216,6 +235,12 @@ class SemesterViewModel @Inject constructor( dialogUiState.value.editedSemester?.let { refreshSemesterTimetableFrames(it) } + + // 시간표에 보여지고 있는 프레임인 경우, 같이 이름 변경 + if (timetableFrame.id == currentTimetableId.value) { + _currentTimetableName.value = timetableFrame.timetableName + } + }.onFailure { Timber.d("시간표 프레임 수정 실패") } @@ -245,6 +270,25 @@ class SemesterViewModel @Inject constructor( dialogUiState.value.editedSemester?.let { refreshSemesterTimetableFrames(it) } + + // 시간표에서 선택한 프레임이 삭제된 경우.. + if (currentTimetableId.value == target.id) { + // 학기가 함께 삭제된 경우 가장 최근 학기의 기본 시간표로 이동 + if (!userSemesters.value.contains(dialogUiState.value.editedSemester)) { + updateCurrentTimetableDataToLatest() + return@onSuccess + } + + // 학기가 함께 삭제되지 않는 경우엔, 삭제된 시간표 대신 그 학기의 기본 시간표로 이동 + // 학기를 찾을 수 없으면, 가장 최근 시간표로 이동 + userTimetableFrames.value + .get(currentTimetableSemester.value.toSemesterModel()) + ?.find { it.isMain} + ?.let { + _currentTimetableId.value = it.id + _currentTimetableName.value = it.timetableName + } ?: updateCurrentTimetableDataToLatest() + } }.onFailure { Timber.d("시간표 프레임 삭제 실패") } @@ -321,10 +365,23 @@ class SemesterViewModel @Inject constructor( } ) ).onSuccess { - + // _originalTimetableId 랑 editedTimetableFrame.id 이랑 같다면 + // 시간표에 보여지고 있는 시간표가 삭제 후 복구된 경우 + if (_originalTimetableId.value == dialogUiState.value.editedTimetableFrame!!.id) { + + // 복구된 frame 으로 변경 + _currentTimetableId.value = targetFrame.id + _originalTimetableId.value = targetFrame.id + _currentTimetableName.value = targetFrame.timetableName + + // 학기도 복구된 경우 변경 + if(isRestoredSemester) { + _currentTimetableSemester.value = uiState.editedSemester.toSemester() + } + } }.onFailure { if (isRestoredSemester) { - deleteSemesterUseCase(uiState.editedSemester!!.toSemester()) + deleteSemesterUseCase(uiState.editedSemester.toSemester()) } else { deleteTimetableFrameUseCase(targetFrame.id) } @@ -377,6 +434,27 @@ class SemesterViewModel @Inject constructor( } } } + + /** + * 가장 최근 학기의 기본 시간표로 전부 갱신 + */ + private fun updateCurrentTimetableDataToLatest() { + if(userSemesters.value.isEmpty() || userTimetableFrames.value.isEmpty()) { + // 학기가 비어있는 상태 + _currentTimetableSemester.value = "" + _currentTimetableName.value = "" + _currentTimetableId.value = -1 + } + userSemesters.value.first().let { + _currentTimetableSemester.value = it.toSemester() + userTimetableFrames.value.get(it)?.find { it.isMain }.let { + it!! + }.let { + _currentTimetableName.value = it.timetableName + _currentTimetableId.value = it.id + } + } + } } data class SemesterDialogUiState( diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt index 4ea1102d9..12371fe3b 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableActivity.kt @@ -335,7 +335,8 @@ class TimetableActivity : KoinNavigationDrawerActivity() { BUNDLE_EXTRA_KEY, bundleOf( IS_ANONYMOUS to viewModel.state.value.isAnonymous, SEMESTER to viewModel.state.value.currentSemester, - FRAME_ID to viewModel.state.value.frameId + FRAME_ID to viewModel.state.value.frameId, + FRAME_NAME to viewModel.state.value.timetableName ) ) } @@ -382,5 +383,6 @@ class TimetableActivity : KoinNavigationDrawerActivity() { const val IS_ANONYMOUS = "isAnonymous" const val SEMESTER = "semester" const val FRAME_ID = "frameId" + const val FRAME_NAME = "frameName" } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index cc9915230..3b48b4432 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -41,12 +41,11 @@ class TimetableSemesterActivity : ActivityBase() { private val binding by dataBinding() private val viewModel by viewModels() -// val onBackPressedCallback = object : OnBackPressedCallback(true){ -// override fun handleOnBackPressed() { -// // handle event -// finishActivityWithResult(viewModel.semesters.value.to) -// } -// } + val onBackPressedCallback = object : OnBackPressedCallback(true){ + override fun handleOnBackPressed() { + finishActivityWithResult() + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -54,9 +53,12 @@ class TimetableSemesterActivity : ActivityBase() { getIntentBundle { bundle -> val isAnonymous = bundle.getBoolean(TimetableActivity.IS_ANONYMOUS, false) val semester = bundle.getString(TimetableActivity.SEMESTER).orEmpty() - viewModel.updateIntentData(isAnonymous) + val frameId = bundle.getInt(TimetableActivity.FRAME_ID) + val frameName = bundle.getString(TimetableActivity.FRAME_NAME).orEmpty() + viewModel.updateIntentData(isAnonymous, frameId, semester, frameName) } viewModel.initData() + onBackPressedDispatcher.addCallback(onBackPressedCallback) binding.timetableListComposeView.setContent { KoinTheme { @@ -192,7 +194,9 @@ class TimetableSemesterActivity : ActivityBase() { binding.timetableListAppbar.setOnClickListener { when (it.id) { - AppBarBase.getLeftButtonId() -> onBackPressed() + AppBarBase.getLeftButtonId() -> { + onBackPressedDispatcher.onBackPressed() + } AppBarBase.getRightButtonId() -> { if (viewModel.isAnonymous.value) { viewModel.updateRequestLoginDialogVisible(true) @@ -231,6 +235,29 @@ class TimetableSemesterActivity : ActivityBase() { finish() } + private fun finishActivityWithResult() { + Timber.d("semester: ${viewModel.currentTimetableSemester.value}") + Timber.d("Timetable frame id: ${viewModel.currentTimetableId.value}") + Timber.d("timetable frame name: ${viewModel.currentTimetableName.value}") +// val intent = Intent().apply{ +// val bundle = if (!viewModel.isAnonymous.value) { +// bundleOf( +// SEMESTER to viewModel.currentTimetableSemester.value, +// TIMETABLE_FRAME_ID to viewModel.currentTimetableId.value, +// TIMETABLE_FRAME_NAME to viewModel.currentTimetableName.value, +// ) +// } else { +// bundleOf( +// SEMESTER to viewModel.currentTimetableSemester.value +// ) +// } +// putExtra(BUNDLE_EXTRA_KEY, bundle) +// } +// +// setResult(RESULT_OK, intent) +// finish() + } + companion object { private const val SCREEN_TITLE = "시간표 목록" const val BUNDLE_EXTRA_KEY = "BUNDLE_EXTRA_KEY" From faf77ee7f0df171ae87b90f034c6fdd20e280415 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 21 Nov 2024 00:33:42 +0900 Subject: [PATCH 42/60] [add] loading false --- .../koin/feature/timetable/viewmodel/TimetableViewModel.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt index 667feae6f..1eed6f764 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt @@ -170,7 +170,10 @@ class TimetableViewModel @Inject constructor( } } false -> { - if (state.value.frameId == frameId) return@launch + if (state.value.frameId == frameId) { + updateLoading(false) + return@launch + } timetableRepository.getTimetableLectures(frameId) .onSuccess { timetableLectures -> _state.value = _state.value.copy( From de61fdc83e431e38b638a584d76221f2a9c96c9e Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 21 Nov 2024 00:39:23 +0900 Subject: [PATCH 43/60] =?UTF-8?q?[add]=20=EB=B9=84=EC=97=88=EC=9D=84=20?= =?UTF-8?q?=EB=95=8C,=20=ED=95=99=EA=B8=B0=20=EC=B6=94=EA=B0=80=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=80=EC=94=A8=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/feature/timetable/component/TimetableScheduleBox.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableScheduleBox.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableScheduleBox.kt index 16997ac02..9bc8200f9 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableScheduleBox.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/component/TimetableScheduleBox.kt @@ -37,7 +37,7 @@ fun TimetableScheduleBox( .padding(5.dp) ) { Text( - text = if (currentSemester.isEmpty()) "" else currentSemester.toSemesterTitle( + text = if (currentSemester.isEmpty()) "학기 추가하기" else currentSemester.toSemesterTitle( timetableName ), style = KoinTheme.typography.regular14, From e11dc6f6218a845d5d4b272e7f253ef3beaf2953 Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Thu, 21 Nov 2024 00:42:36 +0900 Subject: [PATCH 44/60] =?UTF-8?q?[Fix]=20=ED=95=99=EA=B8=B0=20empty=20?= =?UTF-8?q?=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/viewmodel/SemesterViewModel.kt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index e8a9a8bfd..f63c4db5f 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -439,12 +439,13 @@ class SemesterViewModel @Inject constructor( * 가장 최근 학기의 기본 시간표로 전부 갱신 */ private fun updateCurrentTimetableDataToLatest() { + // 학기가 비어있는 경우 기본 값 전달 if(userSemesters.value.isEmpty() || userTimetableFrames.value.isEmpty()) { - // 학기가 비어있는 상태 - _currentTimetableSemester.value = "" - _currentTimetableName.value = "" - _currentTimetableId.value = -1 + updateCurrentTimetableDataToEmpty() + return } + + // 학기와 시간표가 비어있지 않은 경우 userSemesters.value.first().let { _currentTimetableSemester.value = it.toSemester() userTimetableFrames.value.get(it)?.find { it.isMain }.let { @@ -455,6 +456,12 @@ class SemesterViewModel @Inject constructor( } } } + + private fun updateCurrentTimetableDataToEmpty() { + _currentTimetableSemester.value = "" + _currentTimetableName.value = "" + _currentTimetableId.value = -1 + } } data class SemesterDialogUiState( From ff158f15e05238de37bc698753cb22dafa1e2d2b Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 21 Nov 2024 00:53:06 +0900 Subject: [PATCH 45/60] =?UTF-8?q?[add]=20empty=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/feature/timetable/viewmodel/SemesterViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index f63c4db5f..73acc08e5 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -215,7 +215,7 @@ class SemesterViewModel @Inject constructor( } // 시간표에서 진입한 학기가 삭제된 경우 - if (!userSemesters.value.contains(_currentTimetableSemester.value.toSemesterModel())) { + if (_currentTimetableSemester.value.isEmpty() || !userSemesters.value.contains(_currentTimetableSemester.value.toSemesterModel())) { Timber.d("userSemesters: ${userSemesters.value}") Timber.d("userTimetableFrames: ${userTimetableFrames.value}") // 가장 최근 학기의 기본 시간표로 설정 From dfd4b3a53fada3b18e0352f009f8ecbb3b125e78 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 21 Nov 2024 01:47:25 +0900 Subject: [PATCH 46/60] =?UTF-8?q?[fix]=20=EB=B9=88=20=ED=95=99=EA=B8=B0=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../timetable/viewmodel/TimetableViewModel.kt | 13 +++++ .../timetablev2/TimetableSemesterActivity.kt | 53 ++++++++++++------- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt index 1eed6f764..530f453f4 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt @@ -162,6 +162,7 @@ class TimetableViewModel @Inject constructor( timetableEvents = timetableLectures.getTimetableEvents(), currentSemester = semester, timetableLectures = timetableLectures, + bottomSheetCollapse = true, loading = false ) }.onFailure { @@ -174,6 +175,16 @@ class TimetableViewModel @Inject constructor( updateLoading(false) return@launch } + val semesters = getSemester(state.value.isAnonymous) + if (frameId == -1) { + _state.value = TimetableState().copy( + isAnonymous = state.value.isAnonymous, + semesters = semesters, + bottomSheetCollapse = true, + loading = false + ) + return@launch + } timetableRepository.getTimetableLectures(frameId) .onSuccess { timetableLectures -> _state.value = _state.value.copy( @@ -182,6 +193,8 @@ class TimetableViewModel @Inject constructor( timetableName = frameName, timetableEvents = timetableLectures.getTimetableEvents(), currentSemester = semester, + semesters = semesters, + bottomSheetCollapse = true, timetableLectures = timetableLectures, loading = false ) diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index 3b48b4432..b184b8b94 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -41,9 +41,21 @@ class TimetableSemesterActivity : ActivityBase() { private val binding by dataBinding() private val viewModel by viewModels() - val onBackPressedCallback = object : OnBackPressedCallback(true){ + private val onBackPressedCallback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { - finishActivityWithResult() + if (viewModel.userTimetableFrames.value.isEmpty()) { + finishActivityWithResult( + semester = "", + frameId = -1, + timetableName = "" + ) + } else { + finishActivityWithResult( + semester = viewModel.currentTimetableSemester.value, + frameId = viewModel.currentTimetableId.value, + timetableName = viewModel.currentTimetableName.value + ) + } } } @@ -197,6 +209,7 @@ class TimetableSemesterActivity : ActivityBase() { AppBarBase.getLeftButtonId() -> { onBackPressedDispatcher.onBackPressed() } + AppBarBase.getRightButtonId() -> { if (viewModel.isAnonymous.value) { viewModel.updateRequestLoginDialogVisible(true) @@ -235,27 +248,27 @@ class TimetableSemesterActivity : ActivityBase() { finish() } - private fun finishActivityWithResult() { + private fun finishActivityWithResult(semester: String, frameId: Int, timetableName: String) { Timber.d("semester: ${viewModel.currentTimetableSemester.value}") Timber.d("Timetable frame id: ${viewModel.currentTimetableId.value}") Timber.d("timetable frame name: ${viewModel.currentTimetableName.value}") -// val intent = Intent().apply{ -// val bundle = if (!viewModel.isAnonymous.value) { -// bundleOf( -// SEMESTER to viewModel.currentTimetableSemester.value, -// TIMETABLE_FRAME_ID to viewModel.currentTimetableId.value, -// TIMETABLE_FRAME_NAME to viewModel.currentTimetableName.value, -// ) -// } else { -// bundleOf( -// SEMESTER to viewModel.currentTimetableSemester.value -// ) -// } -// putExtra(BUNDLE_EXTRA_KEY, bundle) -// } -// -// setResult(RESULT_OK, intent) -// finish() + val intent = Intent().apply { + val bundle = if (!viewModel.isAnonymous.value) { + bundleOf( + SEMESTER to semester, + TIMETABLE_FRAME_ID to frameId, + TIMETABLE_FRAME_NAME to timetableName, + ) + } else { + bundleOf( + SEMESTER to viewModel.currentTimetableSemester.value + ) + } + putExtra(BUNDLE_EXTRA_KEY, bundle) + } + + setResult(RESULT_OK, intent) + finish() } companion object { From a52967c226158088a38aa0c0acf6955922079d9f Mon Sep 17 00:00:00 2001 From: DohyeokKim Date: Thu, 21 Nov 2024 02:31:23 +0900 Subject: [PATCH 47/60] =?UTF-8?q?[Fix]=20=EC=B5=9C=EA=B7=BC=20=ED=95=99?= =?UTF-8?q?=EA=B8=B0=20=EC=8B=9C=EA=B0=84=ED=91=9C=EA=B0=80=20=EB=B0=98?= =?UTF-8?q?=ED=99=98=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/timetable/viewmodel/SemesterViewModel.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index 73acc08e5..1470d1c38 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -446,10 +446,11 @@ class SemesterViewModel @Inject constructor( } // 학기와 시간표가 비어있지 않은 경우 - userSemesters.value.first().let { - _currentTimetableSemester.value = it.toSemester() - userTimetableFrames.value.get(it)?.find { it.isMain }.let { - it!! + userTimetableFrames.value.entries.first().let { entry -> + _currentTimetableSemester.value = entry.key.toSemester() + entry.value.find { it.isMain }.let { + Timber.d("메인이 없는 시간표가 존재함!!") + it ?: entry.value.first() }.let { _currentTimetableName.value = it.timetableName _currentTimetableId.value = it.id From 033ce56eb9e0133395160bc0cae7d13db3d23532 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 21 Nov 2024 03:28:03 +0900 Subject: [PATCH 48/60] =?UTF-8?q?[fix]=20=ED=9E=98=EB=93=A4=EC=96=B4=20?= =?UTF-8?q?=EC=9D=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/TimetableRepositoryImpl.kt | 16 +++++++++++++++ .../request/timetable/LecturesQueryRequest.kt | 19 ++++++++++++++++++ .../domain/repository/TimetableRepository.kt | 2 ++ .../timetable/viewmodel/SemesterViewModel.kt | 20 ++++--------------- .../timetablev2/TimetableSemesterActivity.kt | 13 ++++++++++-- 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt index dfad9bef8..37aa5658a 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/TimetableRepositoryImpl.kt @@ -16,6 +16,7 @@ import `in`.koreatech.koin.domain.model.timetable.request.TimetableFrameQuery import `in`.koreatech.koin.domain.model.timetable.request.TimetableLecturesQuery import `in`.koreatech.koin.domain.model.timetable.response.Lecture import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame +import `in`.koreatech.koin.domain.model.timetable.response.TimetableLecture import `in`.koreatech.koin.domain.model.timetable.response.TimetableLectures import `in`.koreatech.koin.domain.repository.TimetableRepository import kotlinx.coroutines.flow.Flow @@ -97,6 +98,21 @@ class TimetableRepositoryImpl @Inject constructor( )).toTimetableLectures() } + override suspend fun postTimetableBasicLectures(frameId: Int, lectures: List): Result = runCatching { + val queryLectures = lectures.map { + if (it.lectureId == 0) { + it.toCustomLectureQueryRequest() + } else { + it.toLectureQueryRequest() + } + } + + timetableRemoteDataSource.postTimetableLectures(LecturesQueryRequest( + timetableFrameId = frameId, + timetableLecture = queryLectures + )).toTimetableLectures() + } + override suspend fun postTimetableFrame(frame: TimetableFrameCreateQuery): Result = runCatching { timetableRemoteDataSource.postTimetableFrame( diff --git a/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt b/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt index 7832cadd7..6344d817a 100644 --- a/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt +++ b/data/src/main/java/in/koreatech/koin/data/request/timetable/LecturesQueryRequest.kt @@ -2,6 +2,7 @@ package `in`.koreatech.koin.data.request.timetable import com.google.gson.annotations.SerializedName import `in`.koreatech.koin.domain.model.timetable.response.Lecture +import `in`.koreatech.koin.domain.model.timetable.response.TimetableLecture data class LecturesQueryRequest( @SerializedName("timetable_frame_id") @@ -26,7 +27,25 @@ data class LectureQueryRequest( @SerializedName("memo") val memo: String, ) +fun TimetableLecture.toCustomLectureQueryRequest() = LectureQueryRequest( + classTitle = classTitle, + classTime = classTime, + classPlace = classPlace, + professor = professor, + lectureId = null, + grades = "0", + memo = "" +) +fun TimetableLecture.toLectureQueryRequest() = LectureQueryRequest( + classTitle = null, + classTime = null, + classPlace = classPlace, + professor = null, + lectureId = lectureId, + grades = "0", + memo = "" +) fun Lecture.toCustomLectureQueryRequest() = LectureQueryRequest( classTitle = name, diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt index 1896235ea..1cca786fd 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/TimetableRepository.kt @@ -5,6 +5,7 @@ import `in`.koreatech.koin.domain.model.timetable.request.TimetableFrameQuery import `in`.koreatech.koin.domain.model.timetable.request.TimetableLecturesQuery import `in`.koreatech.koin.domain.model.timetable.response.Lecture import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame +import `in`.koreatech.koin.domain.model.timetable.response.TimetableLecture import `in`.koreatech.koin.domain.model.timetable.response.TimetableLectures import kotlinx.coroutines.flow.Flow @@ -23,6 +24,7 @@ interface TimetableRepository { suspend fun postTimetableLectures(frameId: Int, lectures: List): Result suspend fun postTimetableCustomLectures(frameId: Int, lectures: List): Result + suspend fun postTimetableBasicLectures(frameId: Int, lectures: List): Result suspend fun postTimetableFrame(frame: TimetableFrameCreateQuery): Result suspend fun deleteTimetableFrame(frameId: Int): Result diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt index 1470d1c38..3fd3de2cd 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/SemesterViewModel.kt @@ -5,6 +5,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import `in`.koreatech.koin.core.viewmodel.BaseViewModel import `in`.koreatech.koin.domain.model.timetable.request.TimetableLectureQuery import `in`.koreatech.koin.domain.model.timetable.request.TimetableLecturesQuery +import `in`.koreatech.koin.domain.model.timetable.response.Lecture import `in`.koreatech.koin.domain.model.timetable.response.TimetableFrame import `in`.koreatech.koin.domain.model.timetable.response.TimetableLectures import `in`.koreatech.koin.domain.repository.TimetableRepository @@ -348,22 +349,9 @@ class SemesterViewModel @Inject constructor( // 학기 추가 or 프레임 추가가 정상적으로 동작한 경우 강의들 복구 targetFrame?.let { targetFrame -> - timetableRepository.putTimetableLectures( - TimetableLecturesQuery( - timetableFrameId = targetFrame.id, - timetableLecture = uiState.deletedTimetableLectures!!.timetable.map { - TimetableLectureQuery( - id = it.id, - lectureId = it.lectureId, - classTitle = it.classTitle, - classTime = it.classTime, - classPlace = it.classPlace, - professor = it.professor, - grades = it.grades, - memo = it.memo - ) - } - ) + timetableRepository.postTimetableBasicLectures( + frameId = targetFrame.id, + lectures = uiState.deletedTimetableLectures!!.timetable ).onSuccess { // _originalTimetableId 랑 editedTimetableFrame.id 이랑 같다면 // 시간표에 보여지고 있는 시간표가 삭제 후 복구된 경우 diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index b184b8b94..38f423b0c 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -32,6 +32,9 @@ import `in`.koreatech.koin.feature.timetable.view.dialog.EditSemesterDialogImpl import `in`.koreatech.koin.feature.timetable.view.dialog.EditTimetableFrameDialog import `in`.koreatech.koin.feature.timetable.view.dialog.RequestLoginDialog import `in`.koreatech.koin.feature.timetable.viewmodel.SemesterViewModel +import `in`.koreatech.koin.ui.login.LoginActivity +import `in`.koreatech.koin.ui.timetablev2.TimetableActivity.Companion.BUNDLE_LOGIN_EXTRA_KEY +import `in`.koreatech.koin.ui.timetablev2.TimetableActivity.Companion.NAV_TIMETABLE import timber.log.Timber @@ -130,8 +133,7 @@ class TimetableSemesterActivity : ActivityBase() { if (dialogUiState.isRequestLoginDialogVisible) { RequestLoginDialog( onConfirm = { - // TODO::Hyeok 로그인 화면으로 이동 - Timber.d("로그인 화면으로 이동") + startToLoginActivity() viewModel.updateRequestLoginDialogVisible(false) }, onDismiss = { @@ -228,6 +230,13 @@ class TimetableSemesterActivity : ActivityBase() { } ?: return } + private fun startToLoginActivity() { + Intent(this, LoginActivity::class.java).apply { + putExtra(BUNDLE_LOGIN_EXTRA_KEY, bundleOf(NAV_TIMETABLE to true)) + }.let(::startActivity) + finish() + } + private fun finishActivityWithResult(semester: SemesterModel, timetableFrame: TimetableFrame) { val intent = Intent().apply { val bundle = if (!viewModel.isAnonymous.value) { From 572173a625cb60eed8adde9a31477a99cb6aa1fb Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 21 Nov 2024 03:35:51 +0900 Subject: [PATCH 49/60] =?UTF-8?q?[fix]=20=EC=B0=90=EB=A7=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/snackbar/CustomSnackBarHost.kt | 15 ++++++++------- .../view/dialog/EditTimetableFrameDialog.kt | 3 ++- .../ui/timetablev2/TimetableSemesterActivity.kt | 9 --------- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt index 4b7fd1792..5ae1edef7 100644 --- a/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt +++ b/core/designsystem/src/main/java/in/koreatech/koin/core/designsystem/component/snackbar/CustomSnackBarHost.kt @@ -32,17 +32,18 @@ import `in`.koreatech.koin.core.designsystem.theme.KoinTheme fun CustomSnackBarHost( hotState: SnackbarHostState, modifier: Modifier = Modifier, - radius: Dp = 0.dp, - messageTextStyle: TextStyle = KoinTheme.typography.regular12.copy( - color = Color.White + radius: Dp = 6.dp, + + messageTextStyle: TextStyle =KoinTheme.typography.regular14.copy( + color = KoinTheme.colors.neutral0 ), - actionLabelTextStyle: TextStyle = KoinTheme.typography.regular12.copy( - color = Color.White + actionLabelTextStyle: TextStyle = KoinTheme.typography.regular14.copy( + color = KoinTheme.colors.sub500 ), - background: Color = Color.Black, + background: Color = KoinTheme.colors.primary700, alignment: Alignment = Alignment.BottomCenter, paddingValues: PaddingValues = PaddingValues(bottom = 20.dp, start = 10.dp, end = 10.dp), - innerPaddingValues: PaddingValues = PaddingValues(horizontal = 10.dp, vertical = 16.dp), + innerPaddingValues: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 20.dp), onAction: (() -> Unit)? = null ) { Box( diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt index 807fdcf5f..bc23d7769 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/view/dialog/EditTimetableFrameDialog.kt @@ -219,7 +219,8 @@ private fun DeleteTimetableFrameDialog( modifier = Modifier .wrapContentWidth() .wrapContentHeight(), - shape = KoinTheme.shapes.extraSmall + shape = KoinTheme.shapes.extraSmall, + color = Color.White ) { Column( modifier = Modifier diff --git a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt index 38f423b0c..3fb54b945 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/timetablev2/TimetableSemesterActivity.kt @@ -170,15 +170,6 @@ class TimetableSemesterActivity : ActivityBase() { CustomSnackBarHost( hotState = snackBarHost, - radius = 6.dp, - messageTextStyle = KoinTheme.typography.regular14.copy( - color = KoinTheme.colors.neutral0 - ), - actionLabelTextStyle = KoinTheme.typography.regular14.copy( - color = KoinTheme.colors.sub500 - ), - background = KoinTheme.colors.primary700, - innerPaddingValues = PaddingValues(horizontal = 16.dp, vertical = 20.dp), onAction = { viewModel.restoreTimetableFrame() } From 2f1941194b04e071bcea0427d2706016f191782c Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 21 Nov 2024 03:40:30 +0900 Subject: [PATCH 50/60] =?UTF-8?q?[fix]=20default=20value=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/timetable/viewmodel/TimetableViewModel.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt index 530f453f4..aa808379c 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt @@ -181,6 +181,11 @@ class TimetableViewModel @Inject constructor( isAnonymous = state.value.isAnonymous, semesters = semesters, bottomSheetCollapse = true, + selectedLecture = null, + etcClickedTimetableEvents = emptyList(), + clickedTimetableEvents = emptyList(), + bottomSheetUI = BottomSheetUI.DEFAULT, + bottomSheetMode = TimetableBottomSheetContentMode.BASIC, loading = false ) return@launch @@ -194,8 +199,13 @@ class TimetableViewModel @Inject constructor( timetableEvents = timetableLectures.getTimetableEvents(), currentSemester = semester, semesters = semesters, + selectedLecture = null, + etcClickedTimetableEvents = emptyList(), + clickedTimetableEvents = emptyList(), bottomSheetCollapse = true, timetableLectures = timetableLectures, + bottomSheetUI = BottomSheetUI.DEFAULT, + bottomSheetMode = TimetableBottomSheetContentMode.BASIC, loading = false ) }.onFailure { From 53ce18aab8d8b076afc28424dc7eeb5cbc3a1334 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Thu, 21 Nov 2024 03:42:47 +0900 Subject: [PATCH 51/60] =?UTF-8?q?[fix]=20=EB=B9=84=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8=20=EC=8B=9C=EA=B0=84=ED=91=9C=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=ED=94=84=EB=A1=9C=ED=8D=BC?= =?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/feature/timetable/viewmodel/TimetableViewModel.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt index aa808379c..259545326 100644 --- a/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt +++ b/feature/timetable/src/main/java/in/koreatech/koin/feature/timetable/viewmodel/TimetableViewModel.kt @@ -163,6 +163,11 @@ class TimetableViewModel @Inject constructor( currentSemester = semester, timetableLectures = timetableLectures, bottomSheetCollapse = true, + selectedLecture = null, + etcClickedTimetableEvents = emptyList(), + clickedTimetableEvents = emptyList(), + bottomSheetUI = BottomSheetUI.DEFAULT, + bottomSheetMode = TimetableBottomSheetContentMode.BASIC, loading = false ) }.onFailure { From 1c5dbd5a8109b336166a6b63a324683920056453 Mon Sep 17 00:00:00 2001 From: Jokwanhee Date: Fri, 22 Nov 2024 11:21:44 +0900 Subject: [PATCH 52/60] =?UTF-8?q?[fix]=20=EC=8B=9C=EA=B0=84=ED=91=9C=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4?= =?UTF-8?q?=EC=85=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../KoinNavigationDrawerActivity.kt | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt index 73012eefd..6a82dd220 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/navigation/KoinNavigationDrawerActivity.kt @@ -298,12 +298,12 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), } MenuState.Timetable -> { - goToTimetableActivity() -// if (userInfoFlow.value.isAnonymous) { -// goToAnonymousTimeTableActivity() -// } else { -// goToTimetableActivity() -// } +// goToTimetableActivity() + if (userInfoFlow.value.isAnonymous) { + goToAnonymousTimeTableActivity() + } else { + goToTimetableActivity() + } } MenuState.LoginOrLogout -> { @@ -477,39 +477,39 @@ abstract class KoinNavigationDrawerActivity : ActivityBase(), /** * 완성되면 아래 함수 주석 풀고 연결 */ - private fun goToTimetableActivity() { - if (menuState != MenuState.Main) { - goToActivityFinish(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) - } else { - val intent = Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java).apply { - if (koinNavigationDrawerViewModel.userInfoFlow.value.isAnonymous) { - putExtra("isAnonymous", true) - } else { - putExtra("isAnonymous", false) - } - } - EventLogger.logClickEvent( - action = EventAction.USER, - label = "hamburger", - value = "시간표" - ) - startActivity(intent) - } - } - // private fun goToTimetableActivity() { // if (menuState != MenuState.Main) { -// goToActivityFinish(Intent(this, TimetableActivity::class.java)) +// goToActivityFinish(Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java)) // } else { +// val intent = Intent(this, `in`.koreatech.koin.ui.timetablev2.TimetableActivity::class.java).apply { +// if (koinNavigationDrawerViewModel.userInfoFlow.value.isAnonymous) { +// putExtra("isAnonymous", true) +// } else { +// putExtra("isAnonymous", false) +// } +// } // EventLogger.logClickEvent( // action = EventAction.USER, // label = "hamburger", // value = "시간표" // ) -// startActivity(Intent(this, TimetableActivity::class.java)) +// startActivity(intent) // } // } + private fun goToTimetableActivity() { + if (menuState != MenuState.Main) { + goToActivityFinish(Intent(this, TimetableActivity::class.java)) + } else { + EventLogger.logClickEvent( + action = EventAction.USER, + label = "hamburger", + value = "시간표" + ) + startActivity(Intent(this, TimetableActivity::class.java)) + } + } + private fun goToArticleActivity() { val intent = Intent(this, ArticleActivity::class.java) From e29afb1655398f8c52c7cadcd724a919ecb31850 Mon Sep 17 00:00:00 2001 From: wateralsie Date: Tue, 19 Nov 2024 15:21:15 +0900 Subject: [PATCH 53/60] =?UTF-8?q?add:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EB=B0=B0=EB=84=88=20ui=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../drawable-xhdpi/ic_main_article_bell.png | Bin 0 -> 13693 bytes .../drawable-xxhdpi/ic_main_article_bell.png | Bin 0 -> 25127 bytes .../res/drawable/ic_main_article_bell.png | Bin 0 -> 5060 bytes core/src/main/res/values/colors.xml | 1 + .../res/layout/main_card_article_noti.xml | 66 ++++++++++++++++++ koin/src/main/res/values/strings.xml | 2 + 6 files changed, 69 insertions(+) create mode 100644 core/src/main/res/drawable-xhdpi/ic_main_article_bell.png create mode 100644 core/src/main/res/drawable-xxhdpi/ic_main_article_bell.png create mode 100644 core/src/main/res/drawable/ic_main_article_bell.png create mode 100644 koin/src/main/res/layout/main_card_article_noti.xml diff --git a/core/src/main/res/drawable-xhdpi/ic_main_article_bell.png b/core/src/main/res/drawable-xhdpi/ic_main_article_bell.png new file mode 100644 index 0000000000000000000000000000000000000000..7242724826c7e0b1b9519aec9e24cabcebadac8e GIT binary patch literal 13693 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 auKh1_6Pv8`i7V#-0000#CFF1F9 #F7941E + #D47415 #000000 #1F1F1F diff --git a/koin/src/main/res/layout/main_card_article_noti.xml b/koin/src/main/res/layout/main_card_article_noti.xml new file mode 100644 index 000000000..287aa011c --- /dev/null +++ b/koin/src/main/res/layout/main_card_article_noti.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/koin/src/main/res/values/strings.xml b/koin/src/main/res/values/strings.xml index f4c10d4f3..9454b633d 100644 --- a/koin/src/main/res/values/strings.xml +++ b/koin/src/main/res/values/strings.xml @@ -617,6 +617,8 @@ 검색어를 입력해주세요. 일치하는 공지글이 없습니다.\n다른 키워드로 다시 시도해주세요. 지금 인기있는 공지 + 자취방 양도글, 가장 먼저 확인하고 싶을 때? + 공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기 첨부파일 다운로드 중 파일을 다운로드합니다. From 56ff4784d85cf4933c225e15918f2c69660f968b Mon Sep 17 00:00:00 2001 From: wateralsie Date: Thu, 21 Nov 2024 22:04:44 +0900 Subject: [PATCH 54/60] =?UTF-8?q?add:=20recyclerview=EC=97=90=20=ED=82=A4?= =?UTF-8?q?=EC=9B=8C=EB=93=9C=20=EC=95=8C=EB=A6=BC=20=EB=B0=B0=EB=84=88=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/ui/main/activity/MainActivity.kt | 29 ++++-- .../koin/ui/main/adapter/HotArticleAdapter.kt | 88 ++++++++++++++----- .../koin/ui/main/state/ArticleMainState.kt | 6 ++ 3 files changed, 97 insertions(+), 26 deletions(-) create mode 100644 koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt index c8fbc055b..73ba11139 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt @@ -54,6 +54,7 @@ import `in`.koreatech.koin.ui.main.adapter.BusPagerAdapter import `in`.koreatech.koin.ui.main.adapter.DiningContainerViewPager2Adapter import `in`.koreatech.koin.ui.main.adapter.HotArticleAdapter import `in`.koreatech.koin.ui.main.adapter.StoreCategoriesRecyclerAdapter +import `in`.koreatech.koin.ui.main.state.ArticleMainState import `in`.koreatech.koin.ui.main.viewmodel.MainActivityViewModel import `in`.koreatech.koin.ui.navigation.KoinNavigationDrawerTimeActivity import `in`.koreatech.koin.ui.navigation.state.MenuState @@ -79,10 +80,16 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { lateinit var onboardingManager: OnboardingManager private val hotArticleAdapter = HotArticleAdapter( - onClick = { + onNotiClick = { + val intent = Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse("koin://article/activity?fragment=article_keyword") + } + startActivity(intent) + }, + onArticleClick = { val intent = Intent(Intent.ACTION_VIEW).apply { data = - Uri.parse("koin://article/activity?fragment=article_detail&article_id=${it.id}&board_id=${it.board.id}") + Uri.parse("koin://article/activity?fragment=article_detail&article_id=${it.id}&board_id=${it.boardId}") } startActivity(intent) } @@ -245,7 +252,9 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { val intent = Intent(this@MainActivity, BusSearchActivity::class.java) startActivity(intent) }, - modifier = Modifier.fillMaxWidth().padding(horizontal = 20.dp) + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) ) } } @@ -292,7 +301,16 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.hotArticles.collect { - hotArticleAdapter.submitList(it) + hotArticleAdapter.submitList( + listOf( + ArticleMainState.KeywordNoti( + "자취방 양도글, 가장 먼저 확인하고 싶을 때?", + "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기" + ), + ) + it.map { + ArticleMainState.Content(it.title, it.id, it.board.id) + } + ) } } } @@ -424,7 +442,7 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { private fun initDiningABTest() { binding.textSeeMoreDining.setOnClickListener { Intent(this, DiningActivity::class.java).run { - startActivity(this) + startActivity(this) } } lifecycleScope.launch { @@ -434,6 +452,7 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { ExperimentGroup.MAIN_DINING_NEW -> { binding.textSeeMoreDining.visibility = View.VISIBLE } + ExperimentGroup.MAIN_DINING_ORIGINAL -> { binding.textSeeMoreDining.visibility = View.GONE } diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/HotArticleAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/main/adapter/HotArticleAdapter.kt index 83b8cab51..98aa5b5b0 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/HotArticleAdapter.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/adapter/HotArticleAdapter.kt @@ -6,51 +6,97 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import `in`.koreatech.koin.databinding.MainCardArticleBinding -import `in`.koreatech.koin.ui.article.state.ArticleHeaderState +import `in`.koreatech.koin.databinding.MainCardArticleNotiBinding +import `in`.koreatech.koin.ui.article.viewmodel.KeywordInputUiState +import `in`.koreatech.koin.ui.main.state.ArticleMainState class HotArticleAdapter( - private val onClick: (ArticleHeaderState) -> Unit + private val onNotiClick: () -> Unit, + private val onArticleClick: (ArticleMainState.Content) -> Unit ) : - ListAdapter(diffCallback) { + ListAdapter(diffCallback) { + + inner class KeywordNotiViewHolder( + private val binding: MainCardArticleNotiBinding + ) : RecyclerView.ViewHolder(binding.root) { + fun bind(content: ArticleMainState.KeywordNoti) { + binding.textHotNotiTitle.text = content.title + binding.textHotNotiSub.text = content.sub + binding.cardViewArticleHeader.setOnClickListener { onNotiClick() } + } + } inner class HotArticleViewHolder( private val binding: MainCardArticleBinding ) : RecyclerView.ViewHolder(binding.root) { - fun bind(articleHeader: ArticleHeaderState) { - binding.textArticleTitle.text = articleHeader.title - binding.cardViewArticleHeader.setOnClickListener { onClick(articleHeader) } + fun bind(content: ArticleMainState.Content) { + binding.textArticleTitle.text = content.title + binding.cardViewArticleHeader.setOnClickListener { onArticleClick(content) } + } + } + + override fun getItemViewType(position: Int): Int { + return when (getItem(position)) { + is ArticleMainState.KeywordNoti -> TYPE_NOTI + is ArticleMainState.Content -> TYPE_ARTICLE + else -> throw IllegalArgumentException("Invalid type of data - $position") } } override fun onCreateViewHolder( parent: ViewGroup, viewType: Int - ): HotArticleViewHolder { - val binding = MainCardArticleBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - return HotArticleViewHolder(binding) + ): RecyclerView.ViewHolder { + return when (viewType) { + TYPE_NOTI -> { + KeywordNotiViewHolder( + MainCardArticleNotiBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + TYPE_ARTICLE -> { + HotArticleViewHolder( + MainCardArticleBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + else -> throw IllegalArgumentException("Invalid type of view type $viewType") + } } - override fun onBindViewHolder(holder: HotArticleViewHolder, position: Int) { - holder.bind(getItem(position)) + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (val item = getItem(position)) { + is ArticleMainState.KeywordNoti -> (holder as KeywordNotiViewHolder).bind(item) + is ArticleMainState.Content -> (holder as HotArticleViewHolder).bind(item) + } } companion object { - private val diffCallback = object : DiffUtil.ItemCallback() { + const val TYPE_NOTI = 0 + const val TYPE_ARTICLE = 1 + + private val diffCallback = object : DiffUtil.ItemCallback() { override fun areItemsTheSame( - oldItem: ArticleHeaderState, - newItem: ArticleHeaderState + oldItem: ArticleMainState, + newItem: ArticleMainState ): Boolean { - return oldItem.id == newItem.id + return when { + oldItem is ArticleMainState.KeywordNoti && newItem is ArticleMainState.KeywordNoti -> oldItem.title == newItem.title + oldItem is ArticleMainState.Content && newItem is ArticleMainState.Content -> oldItem.id == newItem.id + else -> false + } } override fun areContentsTheSame( - oldItem: ArticleHeaderState, - newItem: ArticleHeaderState + oldItem: ArticleMainState, + newItem: ArticleMainState ): Boolean { return oldItem == newItem } diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt b/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt new file mode 100644 index 000000000..eec511ab3 --- /dev/null +++ b/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt @@ -0,0 +1,6 @@ +package `in`.koreatech.koin.ui.main.state + +sealed class ArticleMainState { + data class KeywordNoti(val title: String, val sub: String): ArticleMainState() + data class Content(val title: String, val id: Int, val boardId: Int): ArticleMainState() +} \ No newline at end of file From 1071391d98389fe96126ffa4d77a42eda764775c Mon Sep 17 00:00:00 2001 From: wateralsie Date: Thu, 21 Nov 2024 22:17:19 +0900 Subject: [PATCH 55/60] =?UTF-8?q?chore:=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/ui/main/activity/MainActivity.kt | 10 +++++----- ...ArticleAdapter.kt => ArticleMainAdapter.kt} | 17 ++++++++--------- .../koin/ui/main/state/ArticleMainState.kt | 2 +- .../main/res/layout/main_card_article_noti.xml | 18 +++++++++--------- 4 files changed, 23 insertions(+), 24 deletions(-) rename koin/src/main/java/in/koreatech/koin/ui/main/adapter/{HotArticleAdapter.kt => ArticleMainAdapter.kt} (83%) diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt index 73ba11139..c9ad5060f 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt @@ -52,7 +52,7 @@ import `in`.koreatech.koin.ui.bus.BusActivity import `in`.koreatech.koin.ui.dining.DiningActivity import `in`.koreatech.koin.ui.main.adapter.BusPagerAdapter import `in`.koreatech.koin.ui.main.adapter.DiningContainerViewPager2Adapter -import `in`.koreatech.koin.ui.main.adapter.HotArticleAdapter +import `in`.koreatech.koin.ui.main.adapter.ArticleMainAdapter import `in`.koreatech.koin.ui.main.adapter.StoreCategoriesRecyclerAdapter import `in`.koreatech.koin.ui.main.state.ArticleMainState import `in`.koreatech.koin.ui.main.viewmodel.MainActivityViewModel @@ -79,7 +79,7 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { @Inject lateinit var onboardingManager: OnboardingManager - private val hotArticleAdapter = HotArticleAdapter( + private val articleMainAdapter = ArticleMainAdapter( onNotiClick = { val intent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("koin://article/activity?fragment=article_keyword") @@ -214,7 +214,7 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { } viewPagerHotArticle.apply { - adapter = hotArticleAdapter + adapter = articleMainAdapter offscreenPageLimit = 3 enableAutoScroll(this@MainActivity, 5_000) } @@ -301,9 +301,9 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.hotArticles.collect { - hotArticleAdapter.submitList( + articleMainAdapter.submitList( listOf( - ArticleMainState.KeywordNoti( + ArticleMainState.Noti( "자취방 양도글, 가장 먼저 확인하고 싶을 때?", "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기" ), diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/HotArticleAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/main/adapter/ArticleMainAdapter.kt similarity index 83% rename from koin/src/main/java/in/koreatech/koin/ui/main/adapter/HotArticleAdapter.kt rename to koin/src/main/java/in/koreatech/koin/ui/main/adapter/ArticleMainAdapter.kt index 98aa5b5b0..681f97a3f 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/HotArticleAdapter.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/adapter/ArticleMainAdapter.kt @@ -7,10 +7,9 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import `in`.koreatech.koin.databinding.MainCardArticleBinding import `in`.koreatech.koin.databinding.MainCardArticleNotiBinding -import `in`.koreatech.koin.ui.article.viewmodel.KeywordInputUiState import `in`.koreatech.koin.ui.main.state.ArticleMainState -class HotArticleAdapter( +class ArticleMainAdapter( private val onNotiClick: () -> Unit, private val onArticleClick: (ArticleMainState.Content) -> Unit ) : @@ -19,10 +18,10 @@ class HotArticleAdapter( inner class KeywordNotiViewHolder( private val binding: MainCardArticleNotiBinding ) : RecyclerView.ViewHolder(binding.root) { - fun bind(content: ArticleMainState.KeywordNoti) { - binding.textHotNotiTitle.text = content.title - binding.textHotNotiSub.text = content.sub - binding.cardViewArticleHeader.setOnClickListener { onNotiClick() } + fun bind(content: ArticleMainState.Noti) { + binding.textArticleNotiTitle.text = content.title + binding.textArticleNotiSub.text = content.sub + binding.cardViewArticleNoti.setOnClickListener { onNotiClick() } } } @@ -38,7 +37,7 @@ class HotArticleAdapter( override fun getItemViewType(position: Int): Int { return when (getItem(position)) { - is ArticleMainState.KeywordNoti -> TYPE_NOTI + is ArticleMainState.Noti -> TYPE_NOTI is ArticleMainState.Content -> TYPE_ARTICLE else -> throw IllegalArgumentException("Invalid type of data - $position") } @@ -73,7 +72,7 @@ class HotArticleAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (val item = getItem(position)) { - is ArticleMainState.KeywordNoti -> (holder as KeywordNotiViewHolder).bind(item) + is ArticleMainState.Noti -> (holder as KeywordNotiViewHolder).bind(item) is ArticleMainState.Content -> (holder as HotArticleViewHolder).bind(item) } } @@ -88,7 +87,7 @@ class HotArticleAdapter( newItem: ArticleMainState ): Boolean { return when { - oldItem is ArticleMainState.KeywordNoti && newItem is ArticleMainState.KeywordNoti -> oldItem.title == newItem.title + oldItem is ArticleMainState.Noti && newItem is ArticleMainState.Noti -> oldItem.title == newItem.title oldItem is ArticleMainState.Content && newItem is ArticleMainState.Content -> oldItem.id == newItem.id else -> false } diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt b/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt index eec511ab3..b2f392077 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt @@ -1,6 +1,6 @@ package `in`.koreatech.koin.ui.main.state sealed class ArticleMainState { - data class KeywordNoti(val title: String, val sub: String): ArticleMainState() + data class Noti(val title: String, val sub: String): ArticleMainState() data class Content(val title: String, val id: Int, val boardId: Int): ArticleMainState() } \ No newline at end of file diff --git a/koin/src/main/res/layout/main_card_article_noti.xml b/koin/src/main/res/layout/main_card_article_noti.xml index 287aa011c..4d69c6d9a 100644 --- a/koin/src/main/res/layout/main_card_article_noti.xml +++ b/koin/src/main/res/layout/main_card_article_noti.xml @@ -9,7 +9,7 @@ android:layout_marginHorizontal="20dp"> + app:layout_constraintTop_toTopOf="@id/ic_article_noti" /> From 3d2981973dcf2c935279ffae4dc94e17d68e8247 Mon Sep 17 00:00:00 2001 From: wateralsie Date: Fri, 22 Nov 2024 13:57:14 +0900 Subject: [PATCH 56/60] =?UTF-8?q?add:=20=EC=9D=BC=EC=A3=BC=EC=9D=BC?= =?UTF-8?q?=EB=A7=88=EB=8B=A4=20=EB=B0=B0=EB=84=88=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=B3=80=EA=B2=BD=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/data/constant/Constant.kt | 4 ++- .../data/repository/ArticleRepositoryImpl.kt | 14 ++++++++++ .../data/source/datastore/ArticleDataStore.kt | 24 ++++++++++++++++ .../source/local/ArticleLocalDataSource.kt | 8 ++++++ .../koin/domain/model/article/ArticleNoti.kt | 13 +++++++++ .../domain/repository/ArticleRepository.kt | 3 ++ .../koin/ui/main/activity/MainActivity.kt | 13 ++------- .../koin/ui/main/state/ArticleMainState.kt | 16 ++++++++++- .../main/viewmodel/MainActivityViewModel.kt | 28 +++++++++++++++++-- 9 files changed, 108 insertions(+), 15 deletions(-) create mode 100644 domain/src/main/java/in/koreatech/koin/domain/model/article/ArticleNoti.kt diff --git a/data/src/main/java/in/koreatech/koin/data/constant/Constant.kt b/data/src/main/java/in/koreatech/koin/data/constant/Constant.kt index 8383e0908..e45cf78b2 100644 --- a/data/src/main/java/in/koreatech/koin/data/constant/Constant.kt +++ b/data/src/main/java/in/koreatech/koin/data/constant/Constant.kt @@ -6,4 +6,6 @@ const val BUS_REQUEST_TIME_FORMAT = "HH:mm" const val STORE_OPEN_TIME_FORMAT = "HH:mm" const val STORE_CLOSE_TIME_FORMAT = "HH:mm" -const val STORE_UPDATED_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss" // 2018-03-23 20:25:24 \ No newline at end of file +const val STORE_UPDATED_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss" // 2018-03-23 20:25:24 + +const val WEEK_IN_MILLIS = 7 * 24 * 60 * 60 * 1000L \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/repository/ArticleRepositoryImpl.kt b/data/src/main/java/in/koreatech/koin/data/repository/ArticleRepositoryImpl.kt index a2ccf7f03..ea45a0b7e 100644 --- a/data/src/main/java/in/koreatech/koin/data/repository/ArticleRepositoryImpl.kt +++ b/data/src/main/java/in/koreatech/koin/data/repository/ArticleRepositoryImpl.kt @@ -5,7 +5,9 @@ import `in`.koreatech.koin.data.source.local.ArticleLocalDataSource import `in`.koreatech.koin.data.source.remote.ArticleRemoteDataSource import `in`.koreatech.koin.domain.model.article.Article import `in`.koreatech.koin.domain.model.article.ArticleHeader +import `in`.koreatech.koin.domain.model.article.ArticleNoti import `in`.koreatech.koin.domain.model.article.ArticlePagination +import `in`.koreatech.koin.domain.model.article.articleNotiContent import `in`.koreatech.koin.domain.model.user.User import `in`.koreatech.koin.domain.repository.ArticleRepository import `in`.koreatech.koin.domain.repository.UserRepository @@ -149,6 +151,18 @@ class ArticleRepositoryImpl @Inject constructor( } } + override fun fetchKeywordNoti(): Flow { + return flow { + emit(articleNotiContent[articleLocalDataSource.fetchKeywordNotiIndex()]) + } + } + + override fun saveKeywordNotiIndex(): Flow { + return flow { + emit(articleLocalDataSource.saveKeywordNotiIndex()) + } + } + override fun fetchSearchedArticles( query: String, boardId: Int, diff --git a/data/src/main/java/in/koreatech/koin/data/source/datastore/ArticleDataStore.kt b/data/src/main/java/in/koreatech/koin/data/source/datastore/ArticleDataStore.kt index 3752ffdfd..9cacd67c0 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/datastore/ArticleDataStore.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/datastore/ArticleDataStore.kt @@ -3,8 +3,12 @@ package `in`.koreatech.koin.data.source.datastore import androidx.datastore.core.DataStore import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.intPreferencesKey +import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey import com.google.gson.Gson +import `in`.koreatech.koin.data.constant.WEEK_IN_MILLIS +import `in`.koreatech.koin.domain.model.article.articleNotiContent import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf @@ -77,8 +81,28 @@ class ArticleDataStore @Inject constructor( } } + suspend fun fetchKeywordNotiIndex(): Int { + return dataStore.data.first()[KEY_NOTI_INDEX] ?: 0 + } + + suspend fun saveKeywordNotiIndex() { + dataStore.edit { preferences -> + var notiIndex = preferences[KEY_NOTI_INDEX] ?: 0 + val lastUpdateTime = preferences[KEY_NOTI_LAST_UPDATE] ?: 0L + val currentTime = System.currentTimeMillis() + + if (currentTime - lastUpdateTime >= WEEK_IN_MILLIS) { + notiIndex = (notiIndex + 1) % articleNotiContent.size + preferences[KEY_NOTI_INDEX] = notiIndex + preferences[KEY_NOTI_LAST_UPDATE] = lastUpdateTime + } + } + } + companion object { private val KEY_SEARCH_HISTORY = stringPreferencesKey("search_history") private val KEY_MY_KEYWORD = stringPreferencesKey("my_keyword") + private val KEY_NOTI_INDEX = intPreferencesKey("noti_index") + private val KEY_NOTI_LAST_UPDATE = longPreferencesKey("noti_last_update_time") } } \ No newline at end of file diff --git a/data/src/main/java/in/koreatech/koin/data/source/local/ArticleLocalDataSource.kt b/data/src/main/java/in/koreatech/koin/data/source/local/ArticleLocalDataSource.kt index bbbe7e1f4..10e1e1886 100644 --- a/data/src/main/java/in/koreatech/koin/data/source/local/ArticleLocalDataSource.kt +++ b/data/src/main/java/in/koreatech/koin/data/source/local/ArticleLocalDataSource.kt @@ -35,4 +35,12 @@ class ArticleLocalDataSource @Inject constructor( suspend fun deleteKeyword(keyword: String) { articleDataStore.deleteKeyword(keyword) } + + suspend fun fetchKeywordNotiIndex(): Int { + return articleDataStore.fetchKeywordNotiIndex() + } + + suspend fun saveKeywordNotiIndex() { + articleDataStore.saveKeywordNotiIndex() + } } \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/article/ArticleNoti.kt b/domain/src/main/java/in/koreatech/koin/domain/model/article/ArticleNoti.kt new file mode 100644 index 000000000..db8da92ed --- /dev/null +++ b/domain/src/main/java/in/koreatech/koin/domain/model/article/ArticleNoti.kt @@ -0,0 +1,13 @@ +package `in`.koreatech.koin.domain.model.article + +data class ArticleNoti ( + val title: String, + val sub: String +) + +val articleNotiContent = listOf( + ArticleNoti("자취방 양도글, 가장 먼저 확인하고 싶을 때?", "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기"), + ArticleNoti("키워드가 포함된 공지가 업로드 되면\n가장 먼저 알림을 보내드려요!", "키워드 알림 설정 바로가기"), + ArticleNoti("근로 공지, 놓치고 싶지 않다면?", "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기"), + ArticleNoti("해외탐방 공지, 놓치고 싶지 않다면?", "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기") +) \ No newline at end of file diff --git a/domain/src/main/java/in/koreatech/koin/domain/repository/ArticleRepository.kt b/domain/src/main/java/in/koreatech/koin/domain/repository/ArticleRepository.kt index a274f21a8..411b3a9a5 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/repository/ArticleRepository.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/repository/ArticleRepository.kt @@ -2,6 +2,7 @@ package `in`.koreatech.koin.domain.repository import `in`.koreatech.koin.domain.model.article.Article import `in`.koreatech.koin.domain.model.article.ArticleHeader +import `in`.koreatech.koin.domain.model.article.ArticleNoti import `in`.koreatech.koin.domain.model.article.ArticlePagination import kotlinx.coroutines.flow.Flow @@ -15,6 +16,8 @@ interface ArticleRepository { fun fetchKeywordSuggestions(): Flow> fun saveKeyword(keyword: String): Flow fun deleteKeyword(keyword: String): Flow + fun fetchKeywordNoti(): Flow + fun saveKeywordNotiIndex(): Flow fun fetchSearchedArticles(query: String, boardId: Int, page: Int, limit: Int): Flow fun fetchMostSearchedKeywords(count: Int): Flow> fun fetchSearchHistory(): Flow> diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt index c9ad5060f..95a9b6d65 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt @@ -300,17 +300,8 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { private fun initViewModel() = with(viewModel) { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.hotArticles.collect { - articleMainAdapter.submitList( - listOf( - ArticleMainState.Noti( - "자취방 양도글, 가장 먼저 확인하고 싶을 때?", - "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기" - ), - ) + it.map { - ArticleMainState.Content(it.title, it.id, it.board.id) - } - ) + viewModel.articleMain.collect { + articleMainAdapter.submitList(it) } } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt b/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt index b2f392077..14f6914e0 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt @@ -1,6 +1,20 @@ package `in`.koreatech.koin.ui.main.state +import `in`.koreatech.koin.domain.model.article.ArticleHeader +import `in`.koreatech.koin.domain.model.article.ArticleNoti + sealed class ArticleMainState { data class Noti(val title: String, val sub: String): ArticleMainState() data class Content(val title: String, val id: Int, val boardId: Int): ArticleMainState() -} \ No newline at end of file +} + +fun ArticleHeader.toContent() = ArticleMainState.Content( + title = title, + id = id, + boardId = boardId +) + +fun ArticleNoti.toNoti() = ArticleMainState.Noti( + title = title, + sub = sub +) \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt index 76ed8f5bc..296d1f89e 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt @@ -13,6 +13,8 @@ import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.core.viewmodel.BaseViewModel import `in`.koreatech.koin.domain.error.bus.BusErrorHandler +import `in`.koreatech.koin.domain.model.article.ArticleNoti +import `in`.koreatech.koin.domain.model.article.articleNotiContent import `in`.koreatech.koin.domain.model.bus.BusNode import `in`.koreatech.koin.domain.model.dining.Dining import `in`.koreatech.koin.domain.model.dining.DiningType @@ -28,11 +30,15 @@ import `in`.koreatech.koin.domain.util.onFailure import `in`.koreatech.koin.domain.util.onSuccess import `in`.koreatech.koin.ui.article.state.ArticleHeaderState import `in`.koreatech.koin.ui.article.state.toArticleHeaderState +import `in`.koreatech.koin.ui.main.state.ArticleMainState +import `in`.koreatech.koin.ui.main.state.toContent +import `in`.koreatech.koin.ui.main.state.toNoti import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow @@ -55,10 +61,10 @@ class MainActivityViewModel @Inject constructor( private val _busNode = MutableLiveData>(BusNode.Koreatech to BusNode.Terminal) - val hotArticles: StateFlow> = + val hotArticles: StateFlow> = articleRepository.fetchHotArticleHeaders() .map { - it.take(HOT_ARTICLE_COUNT).map { article -> article.toArticleHeaderState() } + it.take(HOT_ARTICLE_COUNT).map { article -> article.toContent() } }.catch { }.stateIn( @@ -66,6 +72,24 @@ class MainActivityViewModel @Inject constructor( started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList() ) + val articleNoti: StateFlow = + articleRepository.fetchKeywordNoti() + .map { it.toNoti() } + .stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = articleNotiContent.first().toNoti() + ) + + val articleMain: StateFlow> = combine( + articleNoti, hotArticles + ) { noti, article -> + listOf(noti) + article + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = emptyList() + ) private val _selectedPosition = MutableLiveData(0) val selectedPosition: LiveData get() = _selectedPosition From 1d055ba1a9e4dac33228f591e2b074d064506290 Mon Sep 17 00:00:00 2001 From: wateralsie Date: Sat, 23 Nov 2024 08:46:41 +0900 Subject: [PATCH 57/60] =?UTF-8?q?add:=20AB=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koreatech/koin/core/abtest/Experiment.kt | 6 +++- .../koin/ui/main/activity/MainActivity.kt | 29 ++++++++++++++----- .../main/viewmodel/MainActivityViewModel.kt | 12 ++++++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/in/koreatech/koin/core/abtest/Experiment.kt b/core/src/main/java/in/koreatech/koin/core/abtest/Experiment.kt index e3a182729..f906f97d2 100644 --- a/core/src/main/java/in/koreatech/koin/core/abtest/Experiment.kt +++ b/core/src/main/java/in/koreatech/koin/core/abtest/Experiment.kt @@ -7,7 +7,8 @@ enum class Experiment( BENEFIT_STORE("Benefit", ExperimentGroup.A, ExperimentGroup.B), DINING_SHARE("campus_share_v1", ExperimentGroup.SHARE_ORIGINAL, ExperimentGroup.SHARE_NEW), - MAIN_DINING_SEE_MORE("c_main_dining_v1", ExperimentGroup.MAIN_DINING_ORIGINAL, ExperimentGroup.MAIN_DINING_NEW); + MAIN_DINING_SEE_MORE("c_main_dining_v1", ExperimentGroup.MAIN_DINING_ORIGINAL, ExperimentGroup.MAIN_DINING_NEW), + MAIN_ARTICLE_KEYWORD_BANNER("c_keyword_ banner_v1", ExperimentGroup.MAIN_BANNER_ORIGINAL, ExperimentGroup.MAIN_BANNER_NEW); init { require(experimentGroups.isNotEmpty()) { "Experiment should have at least one group" } @@ -23,4 +24,7 @@ object ExperimentGroup { const val MAIN_DINING_ORIGINAL = "main_dining_original" const val MAIN_DINING_NEW = "main_dining_new" + + const val MAIN_BANNER_ORIGINAL = "banner_original" + const val MAIN_BANNER_NEW = "banner_new" } \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt index 95a9b6d65..080273241 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt @@ -173,6 +173,7 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { } private fun initView() = with(binding) { + initArticleBannerABTest() initDiningABTest() binding.nestedScrollViewMain.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY -> val offset = binding.nestedScrollViewMain.computeVerticalScrollOffset() @@ -298,13 +299,6 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { } private fun initViewModel() = with(viewModel) { - lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.articleMain.collect { - articleMainAdapter.submitList(it) - } - } - } observeLiveData(isLoading) { binding.mainSwipeRefreshLayout.isRefreshing = it } @@ -430,6 +424,27 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { } } + private fun initArticleBannerABTest() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.bannerABTestExperimentGroup.collect { + when (it) { + ExperimentGroup.MAIN_BANNER_NEW -> { + viewModel.articleMain.collect { + articleMainAdapter.submitList(it) + } + } + ExperimentGroup.MAIN_BANNER_ORIGINAL -> { + viewModel.hotArticles.collect { + articleMainAdapter.submitList(it) + } + } + } + } + } + } + } + private fun initDiningABTest() { binding.textSeeMoreDining.setOnClickListener { Intent(this, DiningActivity::class.java).run { diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt index 296d1f89e..bf0b176b3 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt @@ -101,6 +101,18 @@ class MainActivityViewModel @Inject constructor( private val _storeCategories = MutableLiveData>(emptyList()) val storeCategories: LiveData> get() = _storeCategories + val bannerABTestExperimentGroup = flow { + abTestUseCase(Experiment.MAIN_ARTICLE_KEYWORD_BANNER.experimentTitle).onSuccess { + emit(it) + }.onFailure { + emit(Experiment.MAIN_ARTICLE_KEYWORD_BANNER.experimentGroups.first()) + } + }.stateIn( + scope = viewModelScope, + started = SharingStarted.WhileSubscribed(5_000), + initialValue = Experiment.MAIN_ARTICLE_KEYWORD_BANNER.experimentGroups.first() + ) + val diningABTestExperimentGroup = flow { abTestUseCase(Experiment.MAIN_DINING_SEE_MORE.experimentTitle).onSuccess { emit(it) From d0333896e59cc8aa2eb79d7b1042a68db0740f24 Mon Sep 17 00:00:00 2001 From: wateralsie Date: Sat, 23 Nov 2024 10:07:24 +0900 Subject: [PATCH 58/60] =?UTF-8?q?add:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20AB=ED=85=8C=EC=8A=A4=ED=8A=B8,=20=EC=9D=B8?= =?UTF-8?q?=EA=B8=B0=20=EA=B3=B5=EC=A7=80=20=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/core/constant/AnalyticsConstant.kt | 2 ++ .../koin/ui/main/activity/MainActivity.kt | 2 ++ .../main/viewmodel/MainActivityViewModel.kt | 36 +++++++++++++++---- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt b/core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt index f67801369..2e6df476d 100644 --- a/core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt +++ b/core/src/main/java/in/koreatech/koin/core/constant/AnalyticsConstant.kt @@ -99,6 +99,8 @@ object AnalyticsConstant { const val CAMPUS_DINING_1 = "CAMPUS_dining_1" const val CAMPUS_NOTICE_1 = "CAMPUS_notice_1" + const val POPULAR_NOTICE_BANNER = "popular_notice_banner" + const val TO_MANAGE_KEYWORD = "to_manage_keyword" } const val PREVIOUS_PAGE = "previous_page" diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt index 080273241..9eba4676a 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt @@ -81,12 +81,14 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { private val articleMainAdapter = ArticleMainAdapter( onNotiClick = { +// EventLogger.logClickEvent(EventAction.CAMPUS, AnalyticsConstant.Label.TO_MANAGE_KEYWORD, "noti click") val intent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("koin://article/activity?fragment=article_keyword") } startActivity(intent) }, onArticleClick = { + EventLogger.logClickEvent(EventAction.CAMPUS, AnalyticsConstant.Label.POPULAR_NOTICE_BANNER, it.title) val intent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("koin://article/activity?fragment=article_detail&article_id=${it.id}&board_id=${it.boardId}") diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt b/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt index bf0b176b3..5d8b3598c 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/viewmodel/MainActivityViewModel.kt @@ -13,7 +13,6 @@ import `in`.koreatech.koin.core.analytics.EventLogger import `in`.koreatech.koin.core.constant.AnalyticsConstant import `in`.koreatech.koin.core.viewmodel.BaseViewModel import `in`.koreatech.koin.domain.error.bus.BusErrorHandler -import `in`.koreatech.koin.domain.model.article.ArticleNoti import `in`.koreatech.koin.domain.model.article.articleNotiContent import `in`.koreatech.koin.domain.model.bus.BusNode import `in`.koreatech.koin.domain.model.dining.Dining @@ -28,8 +27,6 @@ import `in`.koreatech.koin.domain.util.DiningUtil import `in`.koreatech.koin.domain.util.TimeUtil import `in`.koreatech.koin.domain.util.onFailure import `in`.koreatech.koin.domain.util.onSuccess -import `in`.koreatech.koin.ui.article.state.ArticleHeaderState -import `in`.koreatech.koin.ui.article.state.toArticleHeaderState import `in`.koreatech.koin.ui.main.state.ArticleMainState import `in`.koreatech.koin.ui.main.state.toContent import `in`.koreatech.koin.ui.main.state.toNoti @@ -44,7 +41,6 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn -import timber.log.Timber import javax.inject.Inject @HiltViewModel @@ -104,6 +100,25 @@ class MainActivityViewModel @Inject constructor( val bannerABTestExperimentGroup = flow { abTestUseCase(Experiment.MAIN_ARTICLE_KEYWORD_BANNER.experimentTitle).onSuccess { emit(it) + when (it) { + ExperimentGroup.MAIN_BANNER_NEW -> { + EventLogger.logCustomEvent( + "AB_TEST", + "a/b test 로깅(키워드관리 진입 배너)", + AnalyticsConstant.Label.CAMPUS_NOTICE_1, + "진입점O" + ) + } + + ExperimentGroup.MAIN_BANNER_ORIGINAL -> { + EventLogger.logCustomEvent( + "AB_TEST", + "a/b test 로깅(키워드관리 진입 배너)", + AnalyticsConstant.Label.CAMPUS_NOTICE_1, + "진입점X" + ) + } + } }.onFailure { emit(Experiment.MAIN_ARTICLE_KEYWORD_BANNER.experimentGroups.first()) } @@ -118,10 +133,19 @@ class MainActivityViewModel @Inject constructor( emit(it) when (it) { ExperimentGroup.MAIN_DINING_NEW -> { - EventLogger.logClickEvent(EventAction.CAMPUS, AnalyticsConstant.Label.CAMPUS_DINING_1, "더보기O") + EventLogger.logClickEvent( + EventAction.CAMPUS, + AnalyticsConstant.Label.CAMPUS_DINING_1, + "더보기O" + ) } + ExperimentGroup.MAIN_DINING_ORIGINAL -> { - EventLogger.logClickEvent(EventAction.CAMPUS, AnalyticsConstant.Label.CAMPUS_DINING_1, "더보기X") + EventLogger.logClickEvent( + EventAction.CAMPUS, + AnalyticsConstant.Label.CAMPUS_DINING_1, + "더보기X" + ) } } }.onFailure { From d366d74172cf37f03b711c98177bdbf39c0881eb Mon Sep 17 00:00:00 2001 From: wateralsie Date: Sat, 23 Nov 2024 11:13:15 +0900 Subject: [PATCH 59/60] =?UTF-8?q?fix:=20AB=ED=85=8C=EC=8A=A4=ED=8A=B8=20ac?= =?UTF-8?q?tivity=EC=97=90=20=EC=A0=9C=EB=8C=80=EB=A1=9C=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20=EC=95=88=EB=90=98=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in/koreatech/koin/ui/main/activity/MainActivity.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt index 9eba4676a..dfdde059d 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt @@ -61,6 +61,7 @@ import `in`.koreatech.koin.ui.navigation.state.MenuState import `in`.koreatech.koin.ui.store.activity.CallBenefitStoreActivity import `in`.koreatech.koin.ui.store.contract.StoreActivityContract import `in`.koreatech.koin.util.ext.observeLiveData +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import javax.inject.Inject @@ -429,15 +430,15 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { private fun initArticleBannerABTest() { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.bannerABTestExperimentGroup.collect { + viewModel.bannerABTestExperimentGroup.collectLatest { when (it) { ExperimentGroup.MAIN_BANNER_NEW -> { - viewModel.articleMain.collect { + viewModel.articleMain.collectLatest { articleMainAdapter.submitList(it) } } ExperimentGroup.MAIN_BANNER_ORIGINAL -> { - viewModel.hotArticles.collect { + viewModel.hotArticles.collectLatest { articleMainAdapter.submitList(it) } } From 3d3b65ab1ee92597b2c87f700b2507446c331dce Mon Sep 17 00:00:00 2001 From: wateralsie Date: Sat, 23 Nov 2024 11:13:54 +0900 Subject: [PATCH 60/60] =?UTF-8?q?add:=20=ED=82=A4=EC=9B=8C=EB=93=9C=20?= =?UTF-8?q?=EC=95=8C=EB=A6=BC=20=EB=B0=B0=EB=84=88=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EB=A1=9C=EA=B9=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../koin/domain/model/article/ArticleNoti.kt | 11 ++++++----- .../koreatech/koin/ui/main/activity/MainActivity.kt | 2 +- .../koin/ui/main/adapter/ArticleMainAdapter.kt | 4 ++-- .../koreatech/koin/ui/main/state/ArticleMainState.kt | 5 +++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/domain/src/main/java/in/koreatech/koin/domain/model/article/ArticleNoti.kt b/domain/src/main/java/in/koreatech/koin/domain/model/article/ArticleNoti.kt index db8da92ed..b2d3af30a 100644 --- a/domain/src/main/java/in/koreatech/koin/domain/model/article/ArticleNoti.kt +++ b/domain/src/main/java/in/koreatech/koin/domain/model/article/ArticleNoti.kt @@ -2,12 +2,13 @@ package `in`.koreatech.koin.domain.model.article data class ArticleNoti ( val title: String, - val sub: String + val sub: String, + val value: String ) val articleNotiContent = listOf( - ArticleNoti("자취방 양도글, 가장 먼저 확인하고 싶을 때?", "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기"), - ArticleNoti("키워드가 포함된 공지가 업로드 되면\n가장 먼저 알림을 보내드려요!", "키워드 알림 설정 바로가기"), - ArticleNoti("근로 공지, 놓치고 싶지 않다면?", "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기"), - ArticleNoti("해외탐방 공지, 놓치고 싶지 않다면?", "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기") + ArticleNoti("자취방 양도글, 가장 먼저 확인하고 싶을 때?", "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기", "자취방 양도"), + ArticleNoti("키워드가 포함된 공지가 업로드 되면\n가장 먼저 알림을 보내드려요!", "키워드 알림 설정 바로가기", "안내글"), + ArticleNoti("근로 공지, 놓치고 싶지 않다면?", "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기", "근로"), + ArticleNoti("해외탐방 공지, 놓치고 싶지 않다면?", "공지가 업로드 되면 바로 알려주는\n키워드 알림 설정하러가기", "해외탐방") ) \ No newline at end of file diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt index dfdde059d..dcf4e4a44 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/activity/MainActivity.kt @@ -82,7 +82,7 @@ class MainActivity : KoinNavigationDrawerTimeActivity() { private val articleMainAdapter = ArticleMainAdapter( onNotiClick = { -// EventLogger.logClickEvent(EventAction.CAMPUS, AnalyticsConstant.Label.TO_MANAGE_KEYWORD, "noti click") + EventLogger.logClickEvent(EventAction.CAMPUS, AnalyticsConstant.Label.TO_MANAGE_KEYWORD, it.value) val intent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("koin://article/activity?fragment=article_keyword") } diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/ArticleMainAdapter.kt b/koin/src/main/java/in/koreatech/koin/ui/main/adapter/ArticleMainAdapter.kt index 681f97a3f..ceae6a309 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/adapter/ArticleMainAdapter.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/adapter/ArticleMainAdapter.kt @@ -10,7 +10,7 @@ import `in`.koreatech.koin.databinding.MainCardArticleNotiBinding import `in`.koreatech.koin.ui.main.state.ArticleMainState class ArticleMainAdapter( - private val onNotiClick: () -> Unit, + private val onNotiClick: (ArticleMainState.Noti) -> Unit, private val onArticleClick: (ArticleMainState.Content) -> Unit ) : ListAdapter(diffCallback) { @@ -21,7 +21,7 @@ class ArticleMainAdapter( fun bind(content: ArticleMainState.Noti) { binding.textArticleNotiTitle.text = content.title binding.textArticleNotiSub.text = content.sub - binding.cardViewArticleNoti.setOnClickListener { onNotiClick() } + binding.cardViewArticleNoti.setOnClickListener { onNotiClick(content) } } } diff --git a/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt b/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt index 14f6914e0..b7325eeed 100644 --- a/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt +++ b/koin/src/main/java/in/koreatech/koin/ui/main/state/ArticleMainState.kt @@ -4,7 +4,7 @@ import `in`.koreatech.koin.domain.model.article.ArticleHeader import `in`.koreatech.koin.domain.model.article.ArticleNoti sealed class ArticleMainState { - data class Noti(val title: String, val sub: String): ArticleMainState() + data class Noti(val title: String, val sub: String, val value: String): ArticleMainState() data class Content(val title: String, val id: Int, val boardId: Int): ArticleMainState() } @@ -16,5 +16,6 @@ fun ArticleHeader.toContent() = ArticleMainState.Content( fun ArticleNoti.toNoti() = ArticleMainState.Noti( title = title, - sub = sub + sub = sub, + value = value ) \ No newline at end of file