diff --git a/core/src/main/java/com/terning/core/designsystem/component/bottomsheet/SortingBottomSheet.kt b/core/src/main/java/com/terning/core/designsystem/component/bottomsheet/SortingBottomSheet.kt index 806fefee3..abdfb0b9e 100644 --- a/core/src/main/java/com/terning/core/designsystem/component/bottomsheet/SortingBottomSheet.kt +++ b/core/src/main/java/com/terning/core/designsystem/component/bottomsheet/SortingBottomSheet.kt @@ -10,11 +10,8 @@ import androidx.compose.material3.Text import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -36,10 +33,10 @@ fun SortingBottomSheet( currentSortBy: Int, modifier: Modifier = Modifier, newSortBy: MutableState = mutableStateOf(currentSortBy), + onSortChange: (Int) -> Unit = {}, ) { val scope = rememberCoroutineScope() val sheetState = rememberModalBottomSheetState() - var currentSortBy by remember { mutableStateOf(currentSortBy) } TerningBasicBottomSheet( content = { @@ -75,6 +72,7 @@ fun SortingBottomSheet( .padding(vertical = 12.dp) .noRippleClickable { newSortBy.value = sortIndex + onSortChange(sortIndex) scope .launch { sheetState.hide() } .invokeOnCompletion { @@ -87,7 +85,7 @@ fun SortingBottomSheet( } } }, - onDismissRequest = { onDismiss() }, + onDismissRequest = onDismiss, sheetState = sheetState ) } diff --git a/core/src/main/java/com/terning/core/designsystem/component/textfield/SearchTextField.kt b/core/src/main/java/com/terning/core/designsystem/component/textfield/SearchTextField.kt index f030ccec4..1fd0590b3 100644 --- a/core/src/main/java/com/terning/core/designsystem/component/textfield/SearchTextField.kt +++ b/core/src/main/java/com/terning/core/designsystem/component/textfield/SearchTextField.kt @@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction import com.terning.core.designsystem.theme.Grey300 import com.terning.core.designsystem.theme.Grey400 @@ -27,6 +28,7 @@ import com.terning.core.designsystem.theme.TerningTheme @Composable fun SearchTextField( text: String = "", + textStyle: TextStyle = TerningTheme.typography.body2, onValueChange: (String) -> Unit = {}, modifier: Modifier, hint: String, @@ -42,7 +44,7 @@ fun SearchTextField( value = text, onValueChange = onValueChange, modifier = modifier, - textStyle = TerningTheme.typography.button3, + textStyle = textStyle, textColor = Grey400, cursorBrush = SolidColor(Grey300), drawLineColor = TerningMain, diff --git a/data/src/main/java/com/terning/data/dto/response/SearchResultResponseDto.kt b/data/src/main/java/com/terning/data/dto/response/SearchResultResponseDto.kt index 14e7e4bf2..2ce61a3b8 100644 --- a/data/src/main/java/com/terning/data/dto/response/SearchResultResponseDto.kt +++ b/data/src/main/java/com/terning/data/dto/response/SearchResultResponseDto.kt @@ -7,6 +7,8 @@ import kotlinx.serialization.Serializable data class SearchResultResponseDto( @SerialName("totalPages") val totalPages: Int, + @SerialName("totalCount") + val totalCount: Int, @SerialName("hasNext") val hasNext: Boolean, @SerialName("announcements") @@ -16,21 +18,21 @@ data class SearchResultResponseDto( data class SearchAnnouncementDto( @SerialName("internshipAnnouncementId") val internshipAnnouncementId: Long, - @SerialName("scrapId") - val scrapId: Long?, - @SerialName("dDay") - val dDay: String, - @SerialName("companyImage") - val companyImage: String, @SerialName("title") val title: String, + @SerialName("dDay") + val dDay: String, @SerialName("workingPeriod") val workingPeriod: String, + @SerialName("companyImage") + val companyImage: String, + @SerialName("isScrapped") + val isScrapped: Boolean, + @SerialName("deadline") + val deadline: String, @SerialName("startYearMonth") val startYearMonth: String, @SerialName("color") val color: String?, - @SerialName("deadline") - val deadline: String, ) -} \ No newline at end of file +} diff --git a/data/src/main/java/com/terning/data/mapper/search/SearchResultMapper.kt b/data/src/main/java/com/terning/data/mapper/search/SearchResultMapper.kt index 4d61da372..86eab1bb5 100644 --- a/data/src/main/java/com/terning/data/mapper/search/SearchResultMapper.kt +++ b/data/src/main/java/com/terning/data/mapper/search/SearchResultMapper.kt @@ -12,7 +12,7 @@ fun SearchResultResponseDto.toSearchResultList(): List { dDay = it.dDay, workingPeriod = it.workingPeriod, companyImage = it.companyImage, - scrapId = it.scrapId, + isScrapped = it.isScrapped, deadline = it.deadline, startYearMonth = it.startYearMonth, color = it.color diff --git a/domain/src/main/java/com/terning/domain/entity/search/SearchResult.kt b/domain/src/main/java/com/terning/domain/entity/search/SearchResult.kt index 057357870..3d0225003 100644 --- a/domain/src/main/java/com/terning/domain/entity/search/SearchResult.kt +++ b/domain/src/main/java/com/terning/domain/entity/search/SearchResult.kt @@ -6,8 +6,8 @@ data class SearchResult( val dDay: String, val workingPeriod: String, val companyImage: String, - val scrapId: Long?, + val isScrapped: Boolean, val deadline: String, val startYearMonth: String, val color: String?, -) \ No newline at end of file +) diff --git a/feature/src/main/java/com/terning/feature/calendar/list/CalendarListScreen.kt b/feature/src/main/java/com/terning/feature/calendar/list/CalendarListScreen.kt index cddf094bd..fd9dcb544 100644 --- a/feature/src/main/java/com/terning/feature/calendar/list/CalendarListScreen.kt +++ b/feature/src/main/java/com/terning/feature/calendar/list/CalendarListScreen.kt @@ -51,7 +51,7 @@ fun CalendarListRoute( modifier: Modifier = Modifier, navigateUp: () -> Unit, navigateToAnnouncement: (Long) -> Unit, - viewModel: CalendarListViewModel = hiltViewModel() + viewModel: CalendarListViewModel = hiltViewModel(), ) { val pagerState = LocalPagerState.current val lifecycleOwner = LocalLifecycleOwner.current @@ -130,7 +130,7 @@ private fun CalendarListScreen( uiState: CalendarListUiState, onClickInternship: (CalendarScrapDetail) -> Unit, onClickScrapButton: (Long) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { HorizontalPager( state = pagerState, @@ -195,7 +195,7 @@ private fun CalendarListScreen( @Composable private fun CalendarListEmpty( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Image( painter = painterResource( @@ -218,7 +218,7 @@ private fun CalendarListEmpty( private fun CalendarListScrapCancelDialog( scrapVisibility: Boolean, internshipAnnouncementId: Long?, - onDismissCancelDialog: (Boolean) -> Unit + onDismissCancelDialog: (Boolean) -> Unit, ) { if (scrapVisibility) { internshipAnnouncementId?.run { @@ -236,7 +236,7 @@ private fun CalendarListScrapPatchDialog( dialogVisibility: Boolean, internshipModel: CalendarScrapDetail?, navigateToAnnouncement: (Long) -> Unit, - onDismissInternDialog: () -> Unit, + onDismissInternDialog: (Boolean) -> Unit, onClickChangeColor: () -> Unit, ) { if (dialogVisibility) { diff --git a/feature/src/main/java/com/terning/feature/calendar/week/CalendarWeekScreen.kt b/feature/src/main/java/com/terning/feature/calendar/week/CalendarWeekScreen.kt index 206530448..3bad48937 100644 --- a/feature/src/main/java/com/terning/feature/calendar/week/CalendarWeekScreen.kt +++ b/feature/src/main/java/com/terning/feature/calendar/week/CalendarWeekScreen.kt @@ -60,7 +60,7 @@ fun CalendarWeekRoute( updateSelectedDate: (LocalDate) -> Unit, navigateUp: () -> Unit, modifier: Modifier = Modifier, - viewModel: CalendarWeekViewModel = hiltViewModel() + viewModel: CalendarWeekViewModel = hiltViewModel(), ) { val pagerState = LocalPagerState.current val lifecycleOwner = LocalLifecycleOwner.current @@ -138,7 +138,7 @@ private fun CalendarWeekScreen( updateSelectedDate: (LocalDate) -> Unit, onClickInternship: (CalendarScrapDetail) -> Unit, onClickScrapButton: (Long) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { var swiped by remember { mutableStateOf(false) } @@ -216,7 +216,7 @@ private fun CalendarWeekScreen( @Composable private fun CalendarWeekEmpty( - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Image( painter = painterResource( @@ -241,7 +241,7 @@ private fun CalendarWeekSuccess( scrapList: List, onScrapButtonClicked: (Long) -> Unit, onInternshipClicked: (CalendarScrapDetail) -> Unit, - selectedDate: LocalDate + selectedDate: LocalDate, ) { CalendarScrapList( selectedDate = selectedDate, @@ -257,7 +257,7 @@ private fun CalendarWeekSuccess( private fun CalendarWeekScrapCancelDialog( scrapVisibility: Boolean, internshipAnnouncementId: Long?, - onDismissCancelDialog: (Boolean) -> Unit + onDismissCancelDialog: (Boolean) -> Unit, ) { if (scrapVisibility) { internshipAnnouncementId?.run { @@ -275,7 +275,7 @@ private fun CalendarWeekScrapPatchDialog( dialogVisibility: Boolean, internshipModel: CalendarScrapDetail?, navigateToAnnouncement: (Long) -> Unit, - onDismissInternDialog: () -> Unit, + onDismissInternDialog: (Boolean) -> Unit, onClickChangeColor: () -> Unit, ) { if (dialogVisibility) { diff --git a/feature/src/main/java/com/terning/feature/dialog/detail/ScrapDialog.kt b/feature/src/main/java/com/terning/feature/dialog/detail/ScrapDialog.kt index 0928b1cc8..fbca9cf37 100644 --- a/feature/src/main/java/com/terning/feature/dialog/detail/ScrapDialog.kt +++ b/feature/src/main/java/com/terning/feature/dialog/detail/ScrapDialog.kt @@ -64,11 +64,11 @@ fun ScrapDialog( internshipAnnouncementId: Long, companyImage: String, isScrapped: Boolean, - onDismissRequest: () -> Unit = {}, + onDismissRequest: (Boolean) -> Unit = {}, onScrapAnnouncement: () -> Unit = {}, onClickChangeColor: () -> Unit = {}, onClickNavigateButton: (Long) -> Unit = {}, - viewModel: ScrapDialogViewModel = hiltViewModel() + viewModel: ScrapDialogViewModel = hiltViewModel(), ) { val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current @@ -83,12 +83,12 @@ fun ScrapDialog( is ScrapDialogSideEffect.DismissDialog -> { viewModel.initUiState() - onDismissRequest() + onDismissRequest(false) } is ScrapDialogSideEffect.PatchedScrap -> { onClickChangeColor() - onDismissRequest() + onDismissRequest(false) } is ScrapDialogSideEffect.NavigateToDetail -> onClickNavigateButton( @@ -97,7 +97,7 @@ fun ScrapDialog( is ScrapDialogSideEffect.ScrappedAnnouncement -> { onScrapAnnouncement() - onDismissRequest() + onDismissRequest(true) } } } @@ -150,7 +150,7 @@ private fun ScrapDialogScreen( onClickColorButton: (ColorType) -> Unit, onClickNavigateButton: () -> Unit, onClickColorChangeButton: () -> Unit, - onClickScrapButton: () -> Unit + onClickScrapButton: () -> Unit, ) { Box( modifier = Modifier @@ -277,7 +277,7 @@ private fun ScrapDialogScreen( @Composable private fun NewScrapButton( onClickScrapButton: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { RoundButton( style = TerningTheme.typography.button3, @@ -294,7 +294,7 @@ private fun DetailScrapButton( isColorChanged: Boolean, onClickNavigateButton: () -> Unit, onClickColorChangeButton: () -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { Row( modifier = modifier diff --git a/feature/src/main/java/com/terning/feature/intern/InternRoute.kt b/feature/src/main/java/com/terning/feature/intern/InternRoute.kt index 5dec1e4fc..2f8e19db9 100644 --- a/feature/src/main/java/com/terning/feature/intern/InternRoute.kt +++ b/feature/src/main/java/com/terning/feature/intern/InternRoute.kt @@ -109,7 +109,7 @@ fun InternScreen( internUiState: InternUiState, internInfo: InternInfo, onDismissCancelDialog: (Boolean) -> Unit, - onDismissScrapDialog: () -> Unit, + onDismissScrapDialog: (Boolean) -> Unit, onClickCancelButton: (InternInfo) -> Unit, onClickScrapButton: (InternInfo) -> Unit, ) { diff --git a/feature/src/main/java/com/terning/feature/search/search/SearchRoute.kt b/feature/src/main/java/com/terning/feature/search/search/SearchRoute.kt index 84454cd82..bee719001 100644 --- a/feature/src/main/java/com/terning/feature/search/search/SearchRoute.kt +++ b/feature/src/main/java/com/terning/feature/search/search/SearchRoute.kt @@ -117,6 +117,7 @@ fun SearchScreen( } ) { SearchTextField( + textStyle = TerningTheme.typography.detail2, hint = stringResource(R.string.search_text_field_hint), leftIcon = R.drawable.ic_nav_search, modifier = Modifier.fillMaxWidth(), diff --git a/feature/src/main/java/com/terning/feature/search/search/component/ImageSlider.kt b/feature/src/main/java/com/terning/feature/search/search/component/ImageSlider.kt index b6384976a..42e92a00e 100644 --- a/feature/src/main/java/com/terning/feature/search/search/component/ImageSlider.kt +++ b/feature/src/main/java/com/terning/feature/search/search/component/ImageSlider.kt @@ -6,11 +6,12 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale @@ -25,14 +26,20 @@ fun ImageSlider( images: List, ) { val pagerState = rememberPagerState(pageCount = { images.size }) + val autoScroll = remember { mutableStateOf(true) } - LaunchedEffect(Unit) { - while (true) { - delay(3000) - val nextPage = (pagerState.currentPage + 1) % pagerState.pageCount - pagerState.scrollToPage(nextPage) + LaunchedEffect(autoScroll.value) { + if (autoScroll.value) { + while (true) { + delay(2000) + if (!pagerState.isScrollInProgress) { + val nextPage = (pagerState.currentPage + 1) % pagerState.pageCount + pagerState.animateScrollToPage(nextPage) + } + } } } + Column( modifier .fillMaxWidth() diff --git a/feature/src/main/java/com/terning/feature/search/searchprocess/SearchProcessRoute.kt b/feature/src/main/java/com/terning/feature/search/searchprocess/SearchProcessRoute.kt index 09d4ff6b7..8ccadf8d5 100644 --- a/feature/src/main/java/com/terning/feature/search/searchprocess/SearchProcessRoute.kt +++ b/feature/src/main/java/com/terning/feature/search/searchprocess/SearchProcessRoute.kt @@ -15,12 +15,9 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf -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.focus.FocusRequester @@ -30,15 +27,14 @@ import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource 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 androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.flowWithLifecycle import androidx.navigation.NavHostController +import com.terning.core.designsystem.component.bottomsheet.SortingBottomSheet import com.terning.core.designsystem.component.button.SortingButton -import com.terning.core.designsystem.component.dialog.TerningBasicDialog import com.terning.core.designsystem.component.item.InternItemWithShadow import com.terning.core.designsystem.component.textfield.SearchTextField import com.terning.core.designsystem.component.topappbar.BackButtonTopAppBar @@ -46,19 +42,18 @@ import com.terning.core.designsystem.theme.CalRed import com.terning.core.designsystem.theme.Grey400 import com.terning.core.designsystem.theme.Grey500 import com.terning.core.designsystem.theme.TerningMain -import com.terning.core.designsystem.theme.TerningPointTheme import com.terning.core.designsystem.theme.TerningTheme import com.terning.core.designsystem.theme.White import com.terning.core.extension.addFocusCleaner import com.terning.core.extension.noRippleClickable import com.terning.core.extension.toast -import com.terning.domain.entity.intern.InternInfo +import com.terning.domain.entity.search.SearchResult import com.terning.feature.R import com.terning.feature.dialog.cancel.ScrapCancelDialog import com.terning.feature.dialog.detail.ScrapDialog import com.terning.feature.intern.navigation.navigateIntern +import com.terning.feature.search.searchprocess.models.SearchProcessState -private const val MAX_LINES = 1 @Composable fun SearchProcessRoute( @@ -66,11 +61,18 @@ fun SearchProcessRoute( navController: NavHostController, viewModel: SearchProcessViewModel = hiltViewModel(), ) { + val state by viewModel.state.collectAsStateWithLifecycle() + val internSearchResultData by viewModel.internSearchResultData.collectAsStateWithLifecycle() + val context = LocalContext.current val lifecycleOwner = LocalLifecycleOwner.current - val currentSortBy: MutableState = remember { - mutableIntStateOf(0) + LaunchedEffect(true) { + viewModel.getSearchList( + keyword = state.text, + page = 0, + size = 0 + ) } LaunchedEffect(viewModel.sideEffect, lifecycleOwner) { @@ -88,55 +90,88 @@ fun SearchProcessRoute( SearchProcessScreen( modifier = modifier, - navController = navController, - currentSortBy = currentSortBy, - viewModel = viewModel, - onDismissCancelDialog = { + navigateToIntern = { navController.navigateIntern(it) }, + navigateToBack = { navController.navigateUp() }, + state = state, + internSearchResultData = internSearchResultData, + updateText = { + viewModel.updateText(it) + }, + onSearchAction = { + viewModel.updateQuery(state.text) + viewModel.getSearchList( + keyword = state.text, + sortBy = state.currentSortBy, + page = 0, + size = 100, + ) + viewModel.updateShowSearchResults(true) + viewModel.updateExistSearchResults() + }, + onSortButtonClick = { + viewModel.updateSheetVisible(true) + }, + onDismissScrapDialog = { isScrapped, searchResult -> + viewModel.updateScrapDialogVisible(false) + viewModel.updateSearchResultScrapStatus( + searchResult.internshipAnnouncementId, + isScrapped + ) + }, + onDismissCancelDialog = { isScrapped, searchResult -> viewModel.updateScrapDialogVisible(false) + viewModel.updateSearchResultScrapStatus( + searchResult.internshipAnnouncementId, + !isScrapped + ) }, - onDismissScrapDialog = { viewModel.updateScrapDialogVisible(false) }, - onClickCancelButton = { - viewModel.updateScrapDialogVisible(true) + onDismissSheet = { + viewModel.updateSheetVisible(false) }, - onClickScrapButton = { + onScrapButtonClicked = { viewModel.updateScrapDialogVisible(true) + viewModel.updateSearchResult( + internshipId = it.internshipAnnouncementId, + title = it.title, + dDay = it.dDay, + deadline = it.deadline, + startYearMonth = it.startYearMonth, + workingPeriod = it.workingPeriod, + companyImage = it.companyImage, + isScrapped = it.isScrapped, + color = it.color + ) + }, + onSortChange = { + viewModel.updateSortBy(it) } ) } @Composable fun SearchProcessScreen( - currentSortBy: MutableState, modifier: Modifier = Modifier, - navController: NavHostController, - viewModel: SearchProcessViewModel = hiltViewModel(), - onDismissCancelDialog: (Boolean) -> Unit, - onDismissScrapDialog: () -> Unit, - onClickCancelButton: (Long) -> Unit, - onClickScrapButton: (InternInfo) -> Unit, + navigateToIntern: (Long) -> Unit, + navigateToBack: () -> Unit, + state: SearchProcessState = SearchProcessState(), + internSearchResultData: List = emptyList(), + updateText: (String) -> Unit = {}, + onSearchAction: () -> Unit = {}, + onSortButtonClick: () -> Unit = {}, + onDismissCancelDialog: (Boolean, SearchResult) -> Unit, + onDismissScrapDialog: (Boolean, SearchResult) -> Unit, + onDismissSheet: () -> Unit = {}, + onScrapButtonClicked: (SearchResult) -> Unit, + onSortChange: (Int) -> Unit = {}, ) { - val state by viewModel.state.collectAsStateWithLifecycle() - var sheetState by remember { mutableStateOf(false) } - val internSearchResultData by viewModel.internSearchResultData.collectAsStateWithLifecycle() - val dialogState by viewModel.dialogState.collectAsStateWithLifecycle() - val selectedInternIndex = remember { mutableIntStateOf(-1) } - val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current + val currentSortBy = remember { mutableIntStateOf(state.currentSortBy) } LaunchedEffect(Unit) { focusRequester.requestFocus() } - LaunchedEffect(true) { - viewModel.getSearchList( - keyword = state.text, - sortBy = SORT_BY, - page = 0, - size = 10 - ) - } - Column( modifier = modifier .fillMaxSize() @@ -149,7 +184,7 @@ fun SearchProcessScreen( if (state.showSearchResults) R.string.search_process_result_top_bar_title else R.string.search_process_top_bar_title ), - onBackButtonClick = { navController.navigateUp() }, + onBackButtonClick = navigateToBack, modifier = Modifier ) Column( @@ -171,26 +206,15 @@ fun SearchProcessScreen( SearchTextField( text = state.text, onValueChange = { newText -> - viewModel.updateText(newText) + updateText(newText) }, hint = stringResource(R.string.search_text_field_hint), leftIcon = R.drawable.ic_search_18, modifier = Modifier .focusRequester(focusRequester) .addFocusCleaner(focusManager), - onSearchAction = { - viewModel.getSearchList( - keyword = state.text, - sortBy = SORT_BY, - page = 0, - size = 10 - ) - viewModel.updateQuery(state.text) - viewModel.updateShowSearchResults(true) - viewModel.updateExistSearchResults(state.text) - } + onSearchAction = onSearchAction ) - if (state.showSearchResults) { Column( modifier = Modifier @@ -224,12 +248,13 @@ fun SearchProcessScreen( } Row { SortingButton( - sortBy = currentSortBy.value, - onCLick = { sheetState = true }, + sortBy = state.currentSortBy, + onCLick = onSortButtonClick, ) } } - if (internSearchResultData.isNotEmpty()) { + + if (state.existSearchResults) { LazyColumn( contentPadding = PaddingValues( top = 12.dp, @@ -237,27 +262,14 @@ fun SearchProcessScreen( ), verticalArrangement = Arrangement.spacedBy(12.dp) ) { - items(viewModel.internSearchResultData.value.size) { index -> - InternItemWithShadow( - modifier = Modifier.noRippleClickable { - navController.navigateIntern( - announcementId = internSearchResultData[index] - .internshipAnnouncementId - ) - }, - imageUrl = internSearchResultData[index].companyImage, - title = internSearchResultData[index].title, - dateDeadline = internSearchResultData[index].dDay, - workingPeriod = internSearchResultData[index].workingPeriod, - isScrapped = internSearchResultData[index].scrapId != null, - shadowWidth = 2.dp, - shadowRadius = 10.dp, + items(internSearchResultData.size) { index -> + SearchResultInternItem( + intern = internSearchResultData[index], + navigateToIntern = navigateToIntern, onScrapButtonClicked = { - viewModel.updateScrapDialogVisible(true) - viewModel.updateScrapped( - scrapped = internSearchResultData[index].scrapId != null - ) - selectedInternIndex.intValue = index + with(internSearchResultData[index]) { + onScrapButtonClicked(this) + } } ) } @@ -311,44 +323,66 @@ fun SearchProcessScreen( } } - if (dialogState.isScrapDialogVisible) { - TerningBasicDialog( - onDismissRequest = { viewModel.updateScrapDialogVisible(false) }, - content = { - val selectedIndex = selectedInternIndex.value - if (selectedIndex != -1) { - val selectedIntern = internSearchResultData[selectedIndex] - if (selectedIntern.scrapId != null) { - ScrapCancelDialog( - internshipAnnouncementId = selectedIntern.internshipAnnouncementId, - onDismissRequest = onDismissCancelDialog - ) - } else { - ScrapDialog( - title = selectedIntern.title, - scrapColor = CalRed, - deadline = selectedIntern.deadline, - startYearMonth = selectedIntern.startYearMonth, - workingPeriod = selectedIntern.workingPeriod, - internshipAnnouncementId = selectedIntern.internshipAnnouncementId, - companyImage = selectedIntern.companyImage, - isScrapped = false, - onDismissRequest = onDismissScrapDialog, - onClickChangeColor = { }, - onClickNavigateButton = { } - ) - } - } - } + if (state.sheetState) { + SortingBottomSheet( + currentSortBy = state.currentSortBy, + onDismiss = onDismissSheet, + newSortBy = currentSortBy, + onSortChange = onSortChange ) } + + if (state.isScrapDialogVisible) { + val searchResult = state.searchResult + if (searchResult.isScrapped) { + ScrapCancelDialog( + internshipAnnouncementId = searchResult.internshipAnnouncementId, + onDismissRequest = { isScrapped -> + onDismissCancelDialog(isScrapped, searchResult) + } + ) + } else { + ScrapDialog( + title = searchResult.title, + scrapColor = CalRed, + deadline = searchResult.deadline, + startYearMonth = searchResult.startYearMonth, + workingPeriod = searchResult.workingPeriod, + internshipAnnouncementId = searchResult.internshipAnnouncementId, + companyImage = searchResult.companyImage, + isScrapped = false, + onDismissRequest = { isScrapped -> + onDismissScrapDialog(isScrapped, searchResult) + } + ) + } + } } } -@Preview(showBackground = true) + @Composable -fun SearchProcessScreenPreview() { - TerningPointTheme {} +private fun SearchResultInternItem( + intern: SearchResult, + navigateToIntern: (Long) -> Unit, + onScrapButtonClicked: (Long) -> Unit, +) { + InternItemWithShadow( + modifier = Modifier + .noRippleClickable { + navigateToIntern(intern.internshipAnnouncementId) + }, + imageUrl = intern.companyImage, + title = intern.title, + dateDeadline = intern.dDay, + workingPeriod = intern.workingPeriod, + isScrapped = intern.isScrapped, + shadowRadius = 5.dp, + shadowWidth = 1.dp, + onScrapButtonClicked = { + onScrapButtonClicked(intern.internshipAnnouncementId) + }, + ) } -private const val SORT_BY = "deadlineSoon" \ No newline at end of file +private const val MAX_LINES = 1 \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/search/searchprocess/SearchProcessViewModel.kt b/feature/src/main/java/com/terning/feature/search/searchprocess/SearchProcessViewModel.kt index b8391af88..c2aeb5d59 100644 --- a/feature/src/main/java/com/terning/feature/search/searchprocess/SearchProcessViewModel.kt +++ b/feature/src/main/java/com/terning/feature/search/searchprocess/SearchProcessViewModel.kt @@ -2,16 +2,15 @@ package com.terning.feature.search.searchprocess import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.terning.core.type.SortBy import com.terning.domain.entity.search.SearchResult -import com.terning.domain.repository.ScrapRepository import com.terning.domain.repository.SearchRepository import com.terning.feature.R -import com.terning.feature.search.searchprocess.models.SearchDialogState import com.terning.feature.search.searchprocess.models.SearchProcessState -import com.terning.feature.search.searchprocess.models.SearchResultState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow @@ -22,37 +21,28 @@ import javax.inject.Inject @HiltViewModel class SearchProcessViewModel @Inject constructor( private val searchRepository: SearchRepository, - private val scrapRepository: ScrapRepository, ) : ViewModel() { - private val _state: MutableStateFlow = - MutableStateFlow(SearchProcessState()) - val state: StateFlow = _state.asStateFlow() - private val _sideEffect: MutableSharedFlow = MutableSharedFlow() - val sideEffect = _sideEffect.asSharedFlow() + private val _state = MutableStateFlow(SearchProcessState()) + val state: StateFlow = _state.asStateFlow() - private val _searchListState: MutableStateFlow = - MutableStateFlow(SearchResultState()) - val searchListState: StateFlow = _searchListState.asStateFlow() + private val _sideEffect = MutableSharedFlow() + val sideEffect: SharedFlow = _sideEffect.asSharedFlow() private val _internSearchResultData = MutableStateFlow>(emptyList()) val internSearchResultData: StateFlow> = _internSearchResultData.asStateFlow() - private val _dialogState: MutableStateFlow = - MutableStateFlow(SearchDialogState()) - val dialogState: StateFlow = _dialogState.asStateFlow() - fun getSearchList( keyword: String, - sortBy: String, + sortBy: Int = 0, page: Int, size: Int, ) { viewModelScope.launch { - searchRepository.getSearchList(keyword, sortBy, page, size) - .onSuccess { - _internSearchResultData.value = it + searchRepository.getSearchList(keyword, SortBy.entries[sortBy].type, page, size) + .onSuccess { results -> + _internSearchResultData.value = results } .onFailure { _sideEffect.emit(SearchProcessSideEffect.Toast(R.string.server_failure)) @@ -60,38 +50,75 @@ class SearchProcessViewModel @Inject constructor( } } + fun updateSearchResult( + internshipId: Long, + title: String, + dDay: String, + workingPeriod: String, + companyImage: String, + isScrapped: Boolean, + deadline: String, + startYearMonth: String, + color: String?, + ) { + _state.update { + it.copy( + searchResult = SearchResult( + internshipAnnouncementId = internshipId, + title = title, + dDay = dDay, + workingPeriod = workingPeriod, + companyImage = companyImage, + isScrapped = isScrapped, + deadline = deadline, + startYearMonth = startYearMonth, + color = color ?: "" + ) + ) + } + } + fun updateText(newText: String) { - _state.value = _state.value.copy(text = newText) + _state.update { it.copy(text = newText) } } fun updateQuery(query: String) { - _state.value = _state.value.copy(keyword = query) + _state.update { it.copy(keyword = query) } } fun updateShowSearchResults(show: Boolean) { - _state.value = _state.value.copy(showSearchResults = show) - _state.value = _state.value.copy(existSearchResults = true) + _state.update { it.copy(showSearchResults = show, existSearchResults = true) } } - fun updateExistSearchResults(query: String) { - val exist = - _internSearchResultData.value.any { it.title.contains(query, ignoreCase = true) } - _state.value = _state.value.copy(existSearchResults = exist) + fun updateExistSearchResults() { + _state.update { it.copy(existSearchResults = _internSearchResultData.value.isNotEmpty()) } } fun updateScrapDialogVisible(visible: Boolean) { - _dialogState.update { - it.copy(isScrapDialogVisible = visible) - } + _state.update { it.copy(isScrapDialogVisible = visible) } } - fun updateScrapped(scrapped: Boolean) { - _dialogState.update { - it.copy(scrapped = scrapped) - } + fun updateSheetVisible(visible: Boolean) { + _state.update { it.copy(sheetState = visible) } } - companion object { - private const val SORT_BY = "deadlineSoon" + fun updateSortBy(newSortBy: Int) { + _state.value = _state.value.copy(currentSortBy = newSortBy) + getSearchList( + keyword = _state.value.keyword, + sortBy = newSortBy, + page = 0, + size = 100 + ) + } + + fun updateSearchResultScrapStatus(internshipId: Long, isScrapped: Boolean) { + _internSearchResultData.value = _internSearchResultData.value.map { searchResult -> + if (searchResult.internshipAnnouncementId == internshipId) { + searchResult.copy(isScrapped = isScrapped) + } else { + searchResult + } + } } } diff --git a/feature/src/main/java/com/terning/feature/search/searchprocess/models/SearchProcessState.kt b/feature/src/main/java/com/terning/feature/search/searchprocess/models/SearchProcessState.kt index ea1f030dd..a397cd2c8 100644 --- a/feature/src/main/java/com/terning/feature/search/searchprocess/models/SearchProcessState.kt +++ b/feature/src/main/java/com/terning/feature/search/searchprocess/models/SearchProcessState.kt @@ -1,9 +1,33 @@ package com.terning.feature.search.searchprocess.models +import com.terning.core.state.UiState +import com.terning.core.type.SortBy +import com.terning.domain.entity.search.SearchResult data class SearchProcessState( + val loadState: UiState = UiState.Loading, + val currentSortBy: Int = 0, val text: String = "", val keyword: String = "", + val sortBy: SortBy = SortBy.EARLIEST, + val page: Int = 0, + val size: Int = 100, val showSearchResults: Boolean = false, val existSearchResults: Boolean = false, -) \ No newline at end of file + val isScrapDialogVisible: Boolean = false, + val scrapped: Boolean = false, + val selectedInternIndex: Int = 0, + val sheetState: Boolean = false, + val changeFilterState: Boolean = false, + val searchResult: SearchResult = SearchResult( + internshipAnnouncementId = 0, + title = "", + companyImage = "", + dDay = "", + workingPeriod = "", + isScrapped = false, + deadline = "", + startYearMonth = "", + color = "", + ), +)