diff --git a/app/src/main/java/com/terning/point/di/DataSourceModule.kt b/app/src/main/java/com/terning/point/di/DataSourceModule.kt index 1e4fafac6..b7f3ae9a8 100644 --- a/app/src/main/java/com/terning/point/di/DataSourceModule.kt +++ b/app/src/main/java/com/terning/point/di/DataSourceModule.kt @@ -1,7 +1,9 @@ package com.terning.point.di import com.terning.data.datasource.AuthDataSource +import com.terning.data.datasource.SearchDataSource import com.terning.data.datasourceimpl.AuthDataSourceImpl +import com.terning.data.datasourceimpl.SearchDataSourceImpl import dagger.Binds import dagger.Module import dagger.hilt.InstallIn @@ -15,4 +17,9 @@ abstract class DataSourceModule { @Binds @Singleton abstract fun bindAuthDataSource(authDataSourceImpl: AuthDataSourceImpl): AuthDataSource + + @Binds + @Singleton + abstract fun bindSearchViewsDataSource(searchViewsDataSourceImpl: SearchDataSourceImpl): + SearchDataSource } \ No newline at end of file diff --git a/app/src/main/java/com/terning/point/di/RepositoryModule.kt b/app/src/main/java/com/terning/point/di/RepositoryModule.kt index d18703ccf..82eca6c3f 100644 --- a/app/src/main/java/com/terning/point/di/RepositoryModule.kt +++ b/app/src/main/java/com/terning/point/di/RepositoryModule.kt @@ -1,8 +1,10 @@ package com.terning.point.di import com.terning.data.repositoryimpl.AuthRepositoryImpl +import com.terning.data.repositoryimpl.SearchViewsRepositoryImpl import com.terning.data.repositoryimpl.TokenRepositoryImpl import com.terning.domain.repository.AuthRepository +import com.terning.domain.repository.SearchRepository import com.terning.domain.repository.TokenRepository import dagger.Binds import dagger.Module @@ -21,4 +23,8 @@ abstract class RepositoryModule { @Binds @Singleton abstract fun bindTokenRepository(tokenRepositoryImpl: TokenRepositoryImpl): TokenRepository + + @Binds + @Singleton + abstract fun bindSearchViewsRepository(searchViewsRepositoryImpl: SearchViewsRepositoryImpl): SearchRepository } \ No newline at end of file diff --git a/app/src/main/java/com/terning/point/di/ServiceModule.kt b/app/src/main/java/com/terning/point/di/ServiceModule.kt index b62e3c64b..c8733f01e 100644 --- a/app/src/main/java/com/terning/point/di/ServiceModule.kt +++ b/app/src/main/java/com/terning/point/di/ServiceModule.kt @@ -1,6 +1,7 @@ package com.terning.point.di import com.terning.data.service.AuthService +import com.terning.data.service.SearchService import com.terning.point.di.qualifier.JWT import dagger.Module import dagger.Provides @@ -17,4 +18,9 @@ object ServiceModule { @Singleton fun provideAuthService(@JWT retrofit: Retrofit): AuthService = retrofit.create(AuthService::class.java) + + @Provides + @Singleton + fun provideSearchService(@JWT retrofit: Retrofit): SearchService = + retrofit.create(SearchService::class.java) } \ No newline at end of file diff --git a/core/src/main/java/com/terning/core/designsystem/component/button/ChangeFilterButton.kt b/core/src/main/java/com/terning/core/designsystem/component/button/ChangeFilterButton.kt new file mode 100644 index 000000000..c322ddb6c --- /dev/null +++ b/core/src/main/java/com/terning/core/designsystem/component/button/ChangeFilterButton.kt @@ -0,0 +1,84 @@ +package com.terning.core.designsystem.component.button + +import androidx.annotation.StringRes +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.interaction.collectIsPressedAsState +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ripple.LocalRippleTheme +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.terning.core.designsystem.theme.Grey400 +import com.terning.core.designsystem.theme.TerningMain +import com.terning.core.designsystem.theme.TerningSub1 +import com.terning.core.designsystem.theme.TerningSub5 +import com.terning.core.designsystem.theme.TerningTheme +import com.terning.core.designsystem.theme.White +import com.terning.core.util.NoRippleTheme + +@Composable +fun ChangeFilterButton( + isSelected: Boolean, + @StringRes text: Int, + cornerRadius: Dp, + paddingVertical: Dp, + onButtonClick: () -> Unit, + modifier: Modifier = Modifier, +) { + val interactionSource = remember { MutableInteractionSource() } + val isPressed by interactionSource.collectIsPressedAsState() + val backgroundColor = when { + !isSelected && !isPressed -> White + !isSelected && isPressed -> TerningSub5 + else -> TerningMain + } + val textColor = when { + !isSelected -> Grey400 + else -> White + } + val borderColor = when { + !isSelected && !isPressed -> TerningMain + !isSelected && isPressed -> TerningSub1 + else -> TerningMain + } + + CompositionLocalProvider(LocalRippleTheme provides NoRippleTheme) { + Button( + contentPadding = PaddingValues(vertical = paddingVertical), + modifier = modifier + .fillMaxWidth() + .wrapContentHeight(), + interactionSource = interactionSource, + colors = ButtonDefaults.buttonColors( + containerColor = backgroundColor, + contentColor = textColor, + ), + border = BorderStroke( + width = 1.dp, + color = borderColor + ), + shape = RoundedCornerShape(cornerRadius), + onClick = { onButtonClick() } + ) { + Text( + text = stringResource(id = text), + style = TerningTheme.typography.button3, + textAlign = TextAlign.Center, + ) + } + } +} diff --git a/core/src/main/java/com/terning/core/designsystem/component/item/InternItemWithShadow.kt b/core/src/main/java/com/terning/core/designsystem/component/item/InternItemWithShadow.kt new file mode 100644 index 000000000..f16382734 --- /dev/null +++ b/core/src/main/java/com/terning/core/designsystem/component/item/InternItemWithShadow.kt @@ -0,0 +1,41 @@ +package com.terning.core.designsystem.component.item + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.terning.core.designsystem.theme.Grey200 +import com.terning.core.designsystem.theme.White +import com.terning.core.extension.customShadow + +@Composable +fun InternItemWithShadow( + imageUrl: String, + title: String, + dateDeadline: String, + workingPeriod: String, + isScraped: Boolean, +) { + Box( + modifier = Modifier + .customShadow( + color = Grey200, + shadowRadius = 10.dp, + shadowWidth = 2.dp, + ) + .background( + color = White, + shape = RoundedCornerShape(10.dp) + ) + ) { + InternItem( + imageUrl = imageUrl, + title = title, + dateDeadline = dateDeadline, + workingPeriod = workingPeriod, + isScraped = isScraped + ) + } +} \ No newline at end of file diff --git a/core/src/main/java/com/terning/core/designsystem/component/textfield/TerningBasicTextField.kt b/core/src/main/java/com/terning/core/designsystem/component/textfield/TerningBasicTextField.kt index e73c1a3d5..d30599864 100644 --- a/core/src/main/java/com/terning/core/designsystem/component/textfield/TerningBasicTextField.kt +++ b/core/src/main/java/com/terning/core/designsystem/component/textfield/TerningBasicTextField.kt @@ -68,9 +68,13 @@ fun TerningBasicTextField( keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), keyboardActions = KeyboardActions( onDone = { - keyboardController?.hide() - focusManager.clearFocus() - onDoneAction?.invoke() + if (value.isNotBlank()) { + keyboardController?.hide() + focusManager.clearFocus() + if (onDoneAction != null) { + onDoneAction() + } + } } ), @@ -136,7 +140,6 @@ fun TerningBasicTextField( Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.dp), - modifier = Modifier.padding(vertical = 8.dp) ) { helperIcon?.let { Icon( diff --git a/data/src/main/java/com/terning/data/datasource/SearchDataSource.kt b/data/src/main/java/com/terning/data/datasource/SearchDataSource.kt new file mode 100644 index 000000000..cc15b29b6 --- /dev/null +++ b/data/src/main/java/com/terning/data/datasource/SearchDataSource.kt @@ -0,0 +1,8 @@ +package com.terning.data.datasource + +import com.terning.data.dto.BaseResponse +import com.terning.data.dto.response.SearchViewsResponseDto + +interface SearchDataSource { + suspend fun getSearchViews(): BaseResponse +} \ No newline at end of file diff --git a/data/src/main/java/com/terning/data/datasourceimpl/SearchDataSourceImpl.kt b/data/src/main/java/com/terning/data/datasourceimpl/SearchDataSourceImpl.kt new file mode 100644 index 000000000..7a5872e56 --- /dev/null +++ b/data/src/main/java/com/terning/data/datasourceimpl/SearchDataSourceImpl.kt @@ -0,0 +1,14 @@ +package com.terning.data.datasourceimpl + +import com.terning.data.datasource.SearchDataSource +import com.terning.data.dto.BaseResponse +import com.terning.data.dto.response.SearchViewsResponseDto +import com.terning.data.service.SearchService +import javax.inject.Inject + +class SearchDataSourceImpl @Inject constructor( + private val searchService: SearchService, +) : SearchDataSource { + override suspend fun getSearchViews(): BaseResponse = + searchService.getSearchViewsList() +} \ No newline at end of file diff --git a/data/src/main/java/com/terning/data/dto/response/SearchViewsResponseDto.kt b/data/src/main/java/com/terning/data/dto/response/SearchViewsResponseDto.kt new file mode 100644 index 000000000..1f928b498 --- /dev/null +++ b/data/src/main/java/com/terning/data/dto/response/SearchViewsResponseDto.kt @@ -0,0 +1,27 @@ +package com.terning.data.dto.response + +import com.terning.domain.entity.response.InternshipAnnouncement +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class SearchViewsResponseDto( + @SerialName("internshipAnnouncementId") + val internshipAnnouncementId: Long, + @SerialName("companyImage") + val companyImage: String, + @SerialName("title") + val title: String, +) { + + + fun toSearchViewsEntity(): List { + return listOf( + InternshipAnnouncement( + announcementId = internshipAnnouncementId, + companyImage = companyImage, + title = title, + ) + ) + } +} \ No newline at end of file diff --git a/data/src/main/java/com/terning/data/repositoryimpl/SearchViewsRepositoryImpl.kt b/data/src/main/java/com/terning/data/repositoryimpl/SearchViewsRepositoryImpl.kt new file mode 100644 index 000000000..3900c1f5e --- /dev/null +++ b/data/src/main/java/com/terning/data/repositoryimpl/SearchViewsRepositoryImpl.kt @@ -0,0 +1,15 @@ +package com.terning.data.repositoryimpl + +import com.terning.data.datasource.SearchDataSource +import com.terning.domain.entity.response.InternshipAnnouncement +import com.terning.domain.repository.SearchRepository +import javax.inject.Inject + +class SearchViewsRepositoryImpl @Inject constructor( + private val searchDataSource: SearchDataSource, +) : SearchRepository { + override suspend fun getSearchViewsList(): Result> = + runCatching { + searchDataSource.getSearchViews().result.toSearchViewsEntity() + } +} diff --git a/data/src/main/java/com/terning/data/service/SearchService.kt b/data/src/main/java/com/terning/data/service/SearchService.kt new file mode 100644 index 000000000..e6f3e27d1 --- /dev/null +++ b/data/src/main/java/com/terning/data/service/SearchService.kt @@ -0,0 +1,10 @@ +package com.terning.data.service + +import com.terning.data.dto.BaseResponse +import com.terning.data.dto.response.SearchViewsResponseDto +import retrofit2.http.GET + +interface SearchService { + @GET("api/v1/search/views") + suspend fun getSearchViewsList(): BaseResponse +} \ No newline at end of file diff --git a/domain/src/main/java/com/terning/domain/entity/response/InternshipAnnouncement.kt b/domain/src/main/java/com/terning/domain/entity/response/InternshipAnnouncement.kt new file mode 100644 index 000000000..c090f25a9 --- /dev/null +++ b/domain/src/main/java/com/terning/domain/entity/response/InternshipAnnouncement.kt @@ -0,0 +1,7 @@ +package com.terning.domain.entity.response + +data class InternshipAnnouncement( + val title: String, + val companyImage: String, + val announcementId: Long, +) \ No newline at end of file diff --git a/domain/src/main/java/com/terning/domain/repository/SearchRepository.kt b/domain/src/main/java/com/terning/domain/repository/SearchRepository.kt new file mode 100644 index 000000000..c4b83bdb6 --- /dev/null +++ b/domain/src/main/java/com/terning/domain/repository/SearchRepository.kt @@ -0,0 +1,7 @@ +package com.terning.domain.repository + +import com.terning.domain.entity.response.InternshipAnnouncement + +interface SearchRepository { + suspend fun getSearchViewsList(): Result> +} \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/home/changefilter/ChangeFilterRoute.kt b/feature/src/main/java/com/terning/feature/home/changefilter/ChangeFilterRoute.kt new file mode 100644 index 000000000..643857488 --- /dev/null +++ b/feature/src/main/java/com/terning/feature/home/changefilter/ChangeFilterRoute.kt @@ -0,0 +1,148 @@ +package com.terning.feature.home.changefilter + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.terning.core.designsystem.component.button.RectangleButton +import com.terning.core.designsystem.component.datepicker.DatePickerUI +import com.terning.core.designsystem.component.topappbar.BackButtonTopAppBar +import com.terning.core.designsystem.theme.TerningTheme +import com.terning.feature.R +import com.terning.feature.home.changefilter.component.ChangeFilteringRadioGroup +import com.terning.feature.home.changefilter.component.FilteringMainTitleText +import com.terning.feature.home.changefilter.component.FilteringSubTitleText +import com.terning.feature.home.home.HomeViewModel +import com.terning.feature.home.home.model.InternFilterData +import com.terning.feature.home.home.model.UserNameState +import com.terning.feature.home.home.navigation.navigateHome +import java.util.Calendar + +val currentYear = Calendar.getInstance().get(Calendar.YEAR) +val currentMonth = Calendar.getInstance().get(Calendar.MONTH) + +@Composable +fun ChangeFilterRoute( + navController: NavController, +) { + ChangeFilterScreen(navController) +} + +@Composable +fun ChangeFilterScreen( + navController: NavController, + viewModel: HomeViewModel = hiltViewModel(), +) { + Scaffold( + topBar = { + BackButtonTopAppBar( + title = stringResource(id = R.string.change_filter_top_bar_title), + onBackButtonClick = { navController.popBackStack() }, + modifier = Modifier + .shadow(elevation = 2.dp) + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding( + top = paddingValues.calculateTopPadding(), + ) + ) { + ShowTitle( + mainTitle = stringResource(id = R.string.change_filter_grade_main), + subTitle = stringResource(id = R.string.filtering_status1_sub), + modifier = Modifier.padding( + top = 31.dp, + start = 24.dp, + end = 24.dp + ) + ) + ChangeFilteringRadioGroup( + filterType = 0, + internFilterData = viewModel.userName.internFilter, + onButtonClick = { viewModel.setGrade(it) } + ) + + UserNameState( + userName = "남지우자랑스러운티엘이되", + internFilter = InternFilterData( + grade = 4, + workingPeriod = 1, + startYear = 2024, + startMonth = 7, + ) + ) + + ShowTitle( + mainTitle = stringResource(id = R.string.filtering_status2_title), + subTitle = stringResource(id = R.string.filtering_status2_sub), + modifier = Modifier.padding( + top = 39.dp, + start = 24.dp, + end = 24.dp + ) + ) + ChangeFilteringRadioGroup( + filterType = 1, + internFilterData = viewModel.userName.internFilter, + onButtonClick = { viewModel.setWorkingPeriod(it) } + ) + + ShowTitle( + mainTitle = stringResource(id = R.string.filtering_status3_title), + subTitle = stringResource(id = R.string.filtering_status3_sub), + modifier = Modifier.padding( + top = 39.dp, + start = 24.dp, + end = 24.dp + ) + ) + + Spacer(modifier = Modifier.weight(1f)) + DatePickerUI( + chosenYear = currentYear, + chosenMonth = currentMonth, + ) + Spacer(modifier = Modifier.weight(1f)) + + RectangleButton( + style = TerningTheme.typography.button0, + paddingVertical = 19.dp, + text = R.string.change_filter_save, + onButtonClick = { + navController.navigateHome() + } + ) + } + } +} + +@Composable +private fun ShowTitle( + mainTitle: String, + subTitle: String, + modifier: Modifier = Modifier, +) { + FilteringMainTitleText( + text = mainTitle, + modifier = modifier.padding() + ) + FilteringSubTitleText( + text = subTitle, + modifier = Modifier + .padding( + top = 3.dp, + bottom = 13.dp, + start = 24.dp, + end = 24.dp + ) + ) +} \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/home/changefilter/component/ChangeFilteringRadioGroup.kt b/feature/src/main/java/com/terning/feature/home/changefilter/component/ChangeFilteringRadioGroup.kt new file mode 100644 index 000000000..ca60a3b12 --- /dev/null +++ b/feature/src/main/java/com/terning/feature/home/changefilter/component/ChangeFilteringRadioGroup.kt @@ -0,0 +1,81 @@ +package com.terning.feature.home.changefilter.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +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.itemsIndexed +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.terning.core.designsystem.component.button.ChangeFilterButton +import com.terning.feature.R +import com.terning.feature.home.home.model.InternFilterData + +@Composable +fun ChangeFilteringRadioGroup( + filterType: Int, + internFilterData: InternFilterData?, + onButtonClick: (Int) -> Unit, + modifier: Modifier = Modifier, +) { + val options = if (filterType == 0) { + listOf( + R.string.filtering_status1_button1, + R.string.filtering_status1_button2, + R.string.filtering_status1_button3, + R.string.filtering_status1_button4, + ) + } else { + listOf( + R.string.filtering_status2_button1, + R.string.filtering_status2_button2, + R.string.filtering_status2_button3, + ) + } + + val selectedIndex = remember { mutableIntStateOf(0) } + var selectedButton = remember { mutableStateListOf(false, false, false, false) } + + if (filterType == 0) { + selectedButton[ + internFilterData?.grade ?: 0 + ] = true + } else { + selectedButton[ + internFilterData?.workingPeriod ?: 0 + ] = true + } + + LazyVerticalGrid( + columns = GridCells.Fixed(options.size), + horizontalArrangement = Arrangement.spacedBy(12.dp), + modifier = modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(horizontal = 24.dp) + + ) { + itemsIndexed(options) { index, option -> + ChangeFilterButton( + isSelected = selectedButton[index], + modifier = Modifier + .wrapContentHeight(), + text = options[index], + cornerRadius = 10.dp, + paddingVertical = 10.dp, + onButtonClick = { + selectedIndex.intValue = option + selectedButton.indices.forEach { i -> selectedButton[i] = false } + selectedButton[index] = true + onButtonClick(index) + } + ) + } + } +} \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/home/changefilter/navigation/ChangeFilterNavigation.kt b/feature/src/main/java/com/terning/feature/home/changefilter/navigation/ChangeFilterNavigation.kt new file mode 100644 index 000000000..5c2cab77f --- /dev/null +++ b/feature/src/main/java/com/terning/feature/home/changefilter/navigation/ChangeFilterNavigation.kt @@ -0,0 +1,30 @@ +package com.terning.feature.home.changefilter.navigation + +import androidx.navigation.NavController +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.NavOptions +import androidx.navigation.compose.composable +import com.terning.core.navigation.Route +import com.terning.feature.home.changefilter.ChangeFilterRoute +import kotlinx.serialization.Serializable + +fun NavController.navigateChangeFilter(navOptions: NavOptions? = null) { + navigate( + route = ChangeFilter, + navOptions = navOptions + ) +} + +fun NavGraphBuilder.changeFilterNavGraph( + navHostController: NavHostController +) { + composable { + ChangeFilterRoute( + navController = navHostController + ) + } +} + +@Serializable +data object ChangeFilter : Route \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/home/home/HomeRoute.kt b/feature/src/main/java/com/terning/feature/home/home/HomeRoute.kt index 4c03dd063..6d3559742 100644 --- a/feature/src/main/java/com/terning/feature/home/home/HomeRoute.kt +++ b/feature/src/main/java/com/terning/feature/home/home/HomeRoute.kt @@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold import androidx.compose.material3.Text @@ -28,23 +27,22 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle +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.item.InternItem +import com.terning.core.designsystem.component.item.InternItemWithShadow import com.terning.core.designsystem.component.topappbar.LogoTopAppBar import com.terning.core.designsystem.theme.Black import com.terning.core.designsystem.theme.Grey150 -import com.terning.core.designsystem.theme.Grey200 import com.terning.core.designsystem.theme.TerningTheme import com.terning.core.designsystem.theme.White -import com.terning.core.extension.customShadow import com.terning.feature.R +import com.terning.feature.home.changefilter.navigation.navigateChangeFilter import com.terning.feature.home.home.component.HomeFilteringEmptyIntern import com.terning.feature.home.home.component.HomeFilteringScreen import com.terning.feature.home.home.component.HomeRecommendEmptyIntern import com.terning.feature.home.home.component.HomeTodayEmptyIntern import com.terning.feature.home.home.component.HomeTodayIntern -import com.terning.feature.home.home.model.RecommendInternData import com.terning.feature.home.home.model.UserNameState import com.terning.feature.home.home.model.UserScrapState @@ -52,17 +50,23 @@ const val NAME_START_LENGTH = 7 const val NAME_END_LENGTH = 12 @Composable -fun HomeRoute() { +fun HomeRoute( + navController: NavHostController +) { val currentSortBy: MutableState = remember { mutableIntStateOf(0) } - HomeScreen(currentSortBy) + HomeScreen( + currentSortBy, + onChangeFilterClick = { navController.navigateChangeFilter() } + ) } @OptIn(ExperimentalFoundationApi::class) @Composable fun HomeScreen( currentSortBy: MutableState, + onChangeFilterClick: () -> Unit, viewModel: HomeViewModel = hiltViewModel(), ) { val userNameState = viewModel.userName @@ -113,7 +117,7 @@ fun HomeScreen( .background(White) ) { ShowRecommendTitle() - ShowInternFilter(userNameState = userNameState) + ShowInternFilter(userNameState = userNameState, onChangeFilterClick) HorizontalDivider( thickness = 4.dp, @@ -140,7 +144,17 @@ fun HomeScreen( if (userNameState.internFilter != null && recommendInternData.isNotEmpty()) { items(recommendInternData.size) { index -> - ShowRecommendIntern(recommendInternData[index]) + Box( + modifier = Modifier.padding(horizontal = 24.dp) + ) { + InternItemWithShadow( + imageUrl = recommendInternData[index].imgUrl, + title = recommendInternData[index].title, + dateDeadline = recommendInternData[index].dDay.toString(), + workingPeriod = recommendInternData[index].workingPeriod.toString(), + isScraped = recommendInternData[index].isScrapped, + ) + } } } } @@ -209,47 +223,30 @@ private fun ShowRecommendTitle() { } @Composable -private fun ShowInternFilter(userNameState: UserNameState) { - if (userNameState.internFilter == null) { +private fun ShowInternFilter(userNameState: UserNameState, onChangeFilterClick: () -> Unit) { + if (userNameState.internFilter?.grade == null) { HomeFilteringScreen( - grade = R.string.home_recommend_no_filtering_hyphen, - period = R.string.home_recommend_no_filtering_hyphen, - startYear = R.string.home_recommend_no_filtering_hyphen, - startMonth = R.string.home_recommend_no_filtering_hyphen + grade = stringResource(id = R.string.home_recommend_no_filtering_hyphen), + period = stringResource(id = R.string.home_recommend_no_filtering_hyphen), + startYear = stringResource(id = R.string.home_recommend_no_filtering_hyphen), + onChangeFilterClick = { onChangeFilterClick() }, ) } else { with(userNameState.internFilter) { HomeFilteringScreen( - grade = grade, - period = workingPeriod, - startYear = startYear, - startMonth = startMonth, + grade = (grade + 1).toString() + stringResource(id = R.string.home_recommend_filtering_grade), + period = stringResource( + id = when (workingPeriod) { + 0 -> R.string.filtering_status2_button1 + 1 -> R.string.filtering_status2_button2 + 2 -> R.string.filtering_status2_button3 + else -> R.string.home_recommend_no_filtering_hyphen + } + ), + startYear = startYear.toString() + stringResource(id = R.string.home_recommend_filtering_startYear) + + " " + startMonth.toString() + stringResource(id = R.string.home_recommend_filtering_startMonth), + onChangeFilterClick = { onChangeFilterClick() }, ) } } -} - -@Composable -private fun ShowRecommendIntern(recommendInternData: RecommendInternData) { - Box( - modifier = Modifier - .padding(horizontal = 24.dp) - .customShadow( - color = Grey200, - shadowRadius = 10.dp, - shadowWidth = 2.dp - ) - .background( - color = White, - shape = RoundedCornerShape(10.dp) - ) - ) { - InternItem( - imageUrl = recommendInternData.imgUrl, - title = recommendInternData.title, - dateDeadline = recommendInternData.dDay.toString(), - workingPeriod = recommendInternData.workingPeriod.toString(), - isScraped = recommendInternData.isScrapped, - ) - } } \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/home/home/HomeViewModel.kt b/feature/src/main/java/com/terning/feature/home/home/HomeViewModel.kt index 68fd0d2f1..94e29b4f7 100644 --- a/feature/src/main/java/com/terning/feature/home/home/HomeViewModel.kt +++ b/feature/src/main/java/com/terning/feature/home/home/HomeViewModel.kt @@ -10,8 +10,8 @@ import com.terning.core.designsystem.theme.CalGreen2 import com.terning.core.designsystem.theme.CalOrange1 import com.terning.core.designsystem.theme.CalPink import com.terning.core.designsystem.theme.CalYellow +import com.terning.feature.home.home.model.InternData import com.terning.feature.home.home.model.InternFilterData -import com.terning.feature.home.home.model.RecommendInternData import com.terning.feature.home.home.model.ScrapData import com.terning.feature.home.home.model.UserNameState import com.terning.feature.home.home.model.UserScrapState @@ -24,11 +24,11 @@ import javax.inject.Inject class HomeViewModel @Inject constructor( ) : ViewModel() { - private val _userName by mutableStateOf( + private val _userName by mutableStateOf( UserNameState( userName = "남지우자랑스러운티엘이되", internFilter = InternFilterData( - grade = 4, + grade = 1, workingPeriod = 1, startYear = 2024, startMonth = 7, @@ -37,7 +37,7 @@ class HomeViewModel @Inject constructor( ) val userName get() = _userName - private val _scrapState = MutableStateFlow( + private val _scrapState = MutableStateFlow( UserScrapState( isScrapExist = true, scrapData = getScrapData() @@ -45,11 +45,18 @@ class HomeViewModel @Inject constructor( ) val scrapData get() = _scrapState.asStateFlow() - private val _recommendInternState = MutableStateFlow>( -// getRecommendData() - listOf() + private val _recommendInternState = MutableStateFlow( + getRecommendData() ) val recommendInternData get() = _recommendInternState.asStateFlow() + + fun setGrade(grade: Int) { + userName.internFilter?.grade = grade + } + + fun setWorkingPeriod(workingPeriod: Int) { + userName.internFilter?.workingPeriod = workingPeriod + } } private fun getScrapData(): List = listOf( @@ -103,71 +110,71 @@ private fun getScrapData(): List = listOf( ), ) -private fun getRecommendData(): List = listOf( - RecommendInternData( +private fun getRecommendData(): List = listOf( + InternData( imgUrl = "https://reqres.in/img/faces/7-image.jpg", title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", dDay = 22, workingPeriod = 2, isScrapped = true, ), - RecommendInternData( + InternData( imgUrl = "https://reqres.in/img/faces/7-image.jpg", title = "ㅇㄻㅇㅁㄻㄹㅇㅁㅇㄹ", dDay = 9, workingPeriod = 6, isScrapped = false, ), - RecommendInternData( + InternData( imgUrl = "https://reqres.in/img/faces/7-image.jpg", title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", dDay = 2, workingPeriod = 4, isScrapped = true, ), - RecommendInternData( + InternData( imgUrl = "https://reqres.in/img/faces/7-image.jpg", title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", dDay = 2, workingPeriod = 4, isScrapped = false, ), - RecommendInternData( + InternData( imgUrl = "https://reqres.in/img/faces/7-image.jpg", title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", dDay = 2, workingPeriod = 4, isScrapped = true, ), - RecommendInternData( + InternData( imgUrl = "https://reqres.in/img/faces/7-image.jpg", title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", dDay = 2, workingPeriod = 4, isScrapped = true, ), - RecommendInternData( + InternData( imgUrl = "https://reqres.in/img/faces/7-image.jpg", title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", dDay = 2, workingPeriod = 4, isScrapped = false, ), - RecommendInternData( + InternData( imgUrl = "https://reqres.in/img/faces/7-image.jpg", title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", dDay = 2, workingPeriod = 4, isScrapped = true, ), - RecommendInternData( + InternData( imgUrl = "https://reqres.in/img/faces/7-image.jpg", title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", dDay = 2, workingPeriod = 4, isScrapped = false, ), - RecommendInternData( + InternData( imgUrl = "https://reqres.in/img/faces/7-image.jpg", title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", dDay = 2, diff --git a/feature/src/main/java/com/terning/feature/home/home/component/HomeFilteringScreen.kt b/feature/src/main/java/com/terning/feature/home/home/component/HomeFilteringScreen.kt index e22a775ed..8011d60cf 100644 --- a/feature/src/main/java/com/terning/feature/home/home/component/HomeFilteringScreen.kt +++ b/feature/src/main/java/com/terning/feature/home/home/component/HomeFilteringScreen.kt @@ -24,14 +24,15 @@ import com.terning.core.designsystem.theme.Grey300 import com.terning.core.designsystem.theme.TerningMain import com.terning.core.designsystem.theme.TerningTheme import com.terning.core.designsystem.theme.White +import com.terning.core.extension.noRippleClickable import com.terning.feature.R @Composable fun HomeFilteringScreen( - grade: Int, - period: Int, - startYear: Int, - startMonth: Int, + grade: String, + period: String, + startYear: String, + onChangeFilterClick: () -> Unit, modifier: Modifier = Modifier, ) { Row( @@ -48,7 +49,8 @@ fun HomeFilteringScreen( color = TerningMain, shape = RoundedCornerShape(5.dp), ) - .align(Alignment.CenterVertically), + .align(Alignment.CenterVertically) + .noRippleClickable { onChangeFilterClick() }, ) { Image( painter = painterResource(id = R.drawable.ic_home_filtering_28), @@ -68,29 +70,21 @@ fun HomeFilteringScreen( } HomeFilteringText( - text = grade.toString() + stringResource(id = R.string.home_recommend_filtering_grade), + text = grade, modifier = Modifier .padding(vertical = 7.dp) ) HomeFilteringDivider() HomeFilteringText( - text = stringResource( - id = when (period) { - 1 -> R.string.home_recommend_filtering_working_period_1 - 2 -> R.string.home_recommend_filtering_working_period_2 - 3 -> R.string.home_recommend_filtering_working_period_3 - else -> R.string.server_failure - } - ), + text = period, modifier = Modifier .padding(vertical = 7.dp) ) HomeFilteringDivider() HomeFilteringText( - text = startYear.toString() + stringResource(id = R.string.home_recommend_filtering_startYear) - + " " + startMonth.toString() + stringResource(id = R.string.home_recommend_filtering_startMonth), + text = startYear, modifier = Modifier - .padding(vertical = 7.dp) + .padding(top = 7.dp, bottom = 7.dp) ) } } diff --git a/feature/src/main/java/com/terning/feature/home/home/model/RecommendInternData.kt b/feature/src/main/java/com/terning/feature/home/home/model/InternData.kt similarity index 84% rename from feature/src/main/java/com/terning/feature/home/home/model/RecommendInternData.kt rename to feature/src/main/java/com/terning/feature/home/home/model/InternData.kt index 5b84c180a..4beafb192 100644 --- a/feature/src/main/java/com/terning/feature/home/home/model/RecommendInternData.kt +++ b/feature/src/main/java/com/terning/feature/home/home/model/InternData.kt @@ -1,6 +1,6 @@ package com.terning.feature.home.home.model -data class RecommendInternData( +data class InternData( val imgUrl: String, val title: String, val dDay: Int, diff --git a/feature/src/main/java/com/terning/feature/home/home/model/InternFilterData.kt b/feature/src/main/java/com/terning/feature/home/home/model/InternFilterData.kt index b819d24fb..894faaddd 100644 --- a/feature/src/main/java/com/terning/feature/home/home/model/InternFilterData.kt +++ b/feature/src/main/java/com/terning/feature/home/home/model/InternFilterData.kt @@ -1,8 +1,8 @@ package com.terning.feature.home.home.model data class InternFilterData( - val grade: Int, - val workingPeriod: Int, + var grade: Int, + var workingPeriod: Int, val startYear: Int, val startMonth: Int, ) diff --git a/feature/src/main/java/com/terning/feature/home/home/navigation/HometNavigation.kt b/feature/src/main/java/com/terning/feature/home/home/navigation/HometNavigation.kt index c49027a9d..88ce969af 100644 --- a/feature/src/main/java/com/terning/feature/home/home/navigation/HometNavigation.kt +++ b/feature/src/main/java/com/terning/feature/home/home/navigation/HometNavigation.kt @@ -2,6 +2,7 @@ package com.terning.feature.home.home.navigation import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController import androidx.navigation.NavOptions import androidx.navigation.compose.composable import com.terning.core.navigation.MainTabRoute @@ -15,9 +16,9 @@ fun NavController.navigateHome(navOptions: NavOptions? = null) { ) } -fun NavGraphBuilder.homeNavGraph() { +fun NavGraphBuilder.homeNavGraph(navHostController: NavHostController) { composable { - HomeRoute() + HomeRoute(navController = navHostController) } } 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 198943c7a..8a035648a 100644 --- a/feature/src/main/java/com/terning/feature/intern/InternRoute.kt +++ b/feature/src/main/java/com/terning/feature/intern/InternRoute.kt @@ -71,7 +71,9 @@ fun InternScreen( color = Grey200, offsetY = 2.dp ), - onBackButtonClick = {}, + onBackButtonClick = { + navController.navigateUp() + }, ) }, bottomBar = { diff --git a/feature/src/main/java/com/terning/feature/intern/component/InternCompanyInfo.kt b/feature/src/main/java/com/terning/feature/intern/component/InternCompanyInfo.kt index ed50ea608..4c5974242 100644 --- a/feature/src/main/java/com/terning/feature/intern/component/InternCompanyInfo.kt +++ b/feature/src/main/java/com/terning/feature/intern/component/InternCompanyInfo.kt @@ -54,7 +54,7 @@ fun InternCompanyInfo(modifier: Modifier) { ) { Image( painter = painterResource( - id = R.drawable.ic_nosearch + id = R.drawable.ic_empty_logo ), modifier = modifier.fillMaxWidth(), contentDescription = null, diff --git a/feature/src/main/java/com/terning/feature/main/MainNavigator.kt b/feature/src/main/java/com/terning/feature/main/MainNavigator.kt index f74336ff4..361fb6103 100644 --- a/feature/src/main/java/com/terning/feature/main/MainNavigator.kt +++ b/feature/src/main/java/com/terning/feature/main/MainNavigator.kt @@ -24,7 +24,7 @@ class MainNavigator( @Composable get() = navController .currentBackStackEntryAsState().value?.destination - val startDestination = SignIn + val startDestination = Search val currentTab: MainTab? @Composable get() = MainTab.find { tab -> diff --git a/feature/src/main/java/com/terning/feature/main/MainScreen.kt b/feature/src/main/java/com/terning/feature/main/MainScreen.kt index 49c481a29..e97b15b58 100644 --- a/feature/src/main/java/com/terning/feature/main/MainScreen.kt +++ b/feature/src/main/java/com/terning/feature/main/MainScreen.kt @@ -21,6 +21,7 @@ import com.terning.core.designsystem.theme.TerningMain import com.terning.core.designsystem.theme.White import com.terning.core.util.NoRippleInteractionSource import com.terning.feature.calendar.navigation.calendarNavGraph +import com.terning.feature.home.changefilter.navigation.changeFilterNavGraph import com.terning.feature.home.home.navigation.homeNavGraph import com.terning.feature.intern.navigation.internNavGraph import com.terning.feature.mypage.navigation.myPageNavGraph @@ -57,7 +58,7 @@ fun MainScreen( navController = navigator.navController, startDestination = navigator.startDestination ) { - homeNavGraph() + homeNavGraph(navHostController = navigator.navController) calendarNavGraph() searchNavGraph(navHostController = navigator.navController) myPageNavGraph() @@ -67,6 +68,7 @@ fun MainScreen( filteringTwoNavGraph(navHostController = navigator.navController) filteringThreeNavGraph(navHostController = navigator.navController) searchProcessNavGraph(navHostController = navigator.navController) + changeFilterNavGraph(navHostController = navigator.navController) startFilteringNavGraph(navHostController = navigator.navController) startHomeNavGraph(navHostController = navigator.navController) internNavGraph(navHostController = navigator.navController) 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 67e792d95..f554ba8eb 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 @@ -10,9 +10,15 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource 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.textfield.SearchTextField import com.terning.core.designsystem.component.topappbar.LogoTopAppBar @@ -20,6 +26,8 @@ import com.terning.core.designsystem.theme.Black import com.terning.core.designsystem.theme.Grey100 import com.terning.core.designsystem.theme.TerningTheme import com.terning.core.extension.noRippleClickable +import com.terning.core.state.UiState +import com.terning.domain.entity.response.InternshipAnnouncement import com.terning.feature.R import com.terning.feature.search.search.component.ImageSlider import com.terning.feature.search.search.component.InternListType @@ -29,16 +37,46 @@ import com.terning.feature.search.searchprocess.navigation.navigateSearchProcess @Composable fun SearchRoute( navController: NavHostController, + viewModel: SearchViewModel = hiltViewModel(), ) { - SearchScreen( - navController = navController - ) + val lifecycleOwner = LocalLifecycleOwner.current + + val state by viewModel.state.collectAsStateWithLifecycle(lifecycleOwner = lifecycleOwner) + + LaunchedEffect(key1 = true) { + viewModel.getSearchViews() + } + + LaunchedEffect(viewModel.sideEffect, lifecycleOwner) { + viewModel.sideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle) + .collect { sideEffect -> + when (sideEffect) { + is SearchViewsSideEffect.Toast -> { + sideEffect.message + } + } + } + } + + when (state.searchViewsList) { + is UiState.Loading -> {} + is UiState.Empty -> {} + is UiState.Failure -> {} + is UiState.Success -> { + SearchScreen( + navController = navController, + searchViewsList = (state.searchViewsList as UiState.Success>).data + ) + } + + } } @Composable fun SearchScreen( modifier: Modifier = Modifier, navController: NavHostController, + searchViewsList: List, ) { val images = listOf( R.drawable.ic_nav_search, @@ -91,14 +129,21 @@ fun SearchScreen( color = Black ) - SearchInternList(type = InternListType.VIEW) + SearchInternList( + type = InternListType.VIEW, + searchViewsList = searchViewsList, + navController = navController + ) HorizontalDivider( thickness = 4.dp, modifier = Modifier.padding(vertical = 8.dp), color = Grey100, ) - SearchInternList(type = InternListType.SCRAP) + SearchInternList( + type = InternListType.SCRAP, + searchViewsList = searchViewsList, + navController = navController + ) } } - } diff --git a/feature/src/main/java/com/terning/feature/search/search/SearchViewModel.kt b/feature/src/main/java/com/terning/feature/search/search/SearchViewModel.kt new file mode 100644 index 000000000..d99b6894b --- /dev/null +++ b/feature/src/main/java/com/terning/feature/search/search/SearchViewModel.kt @@ -0,0 +1,42 @@ +package com.terning.feature.search.search + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.terning.core.state.UiState +import com.terning.domain.repository.SearchRepository +import com.terning.feature.R +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SearchViewModel @Inject constructor( + private val searchRepository: SearchRepository, +) : ViewModel() { + private val _state: MutableStateFlow = MutableStateFlow(SearchViewsState()) + val state: StateFlow = _state.asStateFlow() + + private val _sideEffect: MutableSharedFlow = MutableSharedFlow() + val sideEffect = _sideEffect.asSharedFlow() + + init { + getSearchViews() + } + + fun getSearchViews() { + viewModelScope.launch { + searchRepository.getSearchViewsList().onSuccess { searchViewsList -> + _state.value = _state.value.copy( + searchViewsList = UiState.Success(searchViewsList) + ) + }.onFailure { + _sideEffect.emit(SearchViewsSideEffect.Toast(R.string.server_failure)) + } + } + } +} \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/search/search/SearchViewsSideEffect.kt b/feature/src/main/java/com/terning/feature/search/search/SearchViewsSideEffect.kt new file mode 100644 index 000000000..208b1fad3 --- /dev/null +++ b/feature/src/main/java/com/terning/feature/search/search/SearchViewsSideEffect.kt @@ -0,0 +1,7 @@ +package com.terning.feature.search.search + +import androidx.annotation.StringRes + +sealed class SearchViewsSideEffect { + data class Toast(@StringRes val message: Int) : SearchViewsSideEffect() +} \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/search/search/SearchViewsState.kt b/feature/src/main/java/com/terning/feature/search/search/SearchViewsState.kt new file mode 100644 index 000000000..d9cf41ed8 --- /dev/null +++ b/feature/src/main/java/com/terning/feature/search/search/SearchViewsState.kt @@ -0,0 +1,8 @@ +package com.terning.feature.search.search + +import com.terning.core.state.UiState +import com.terning.domain.entity.response.InternshipAnnouncement + +data class SearchViewsState( + var searchViewsList: UiState> = UiState.Loading, +) \ No newline at end of file diff --git a/feature/src/main/java/com/terning/feature/search/search/component/SearchIntern.kt b/feature/src/main/java/com/terning/feature/search/search/component/SearchIntern.kt index e2b6109f5..96e1c551a 100644 --- a/feature/src/main/java/com/terning/feature/search/search/component/SearchIntern.kt +++ b/feature/src/main/java/com/terning/feature/search/search/component/SearchIntern.kt @@ -1,29 +1,40 @@ package com.terning.feature.search.search.component -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement 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.layout.width +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import coil.compose.AsyncImage +import coil.request.ImageRequest import com.terning.core.designsystem.theme.Black -import com.terning.core.designsystem.theme.Grey400 import com.terning.core.designsystem.theme.TerningTheme import com.terning.core.designsystem.theme.White +import com.terning.core.extension.customShadow +import com.terning.core.extension.noRippleClickable +import com.terning.domain.entity.response.InternshipAnnouncement import com.terning.feature.R +import com.terning.feature.intern.navigation.navigateIntern @Composable -fun SearchIntern() { +fun SearchIntern( + searchViews: InternshipAnnouncement, + navController: NavHostController, +) { Column( verticalArrangement = Arrangement.spacedBy( 10.dp, @@ -31,29 +42,40 @@ fun SearchIntern() { ), horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier - .shadow( - elevation = 4.dp, - // TODO 효빈 그림자 PR 풀 받아서 바꾸기 - spotColor = Color(0x26DDDDDD), - ambientColor = Color(0x26DDDDDD) - ) + .width(140.dp) .background( color = White, shape = RoundedCornerShape(size = 5.dp) ) .padding(vertical = 8.dp) + .noRippleClickable { + navController.navigateIntern() + } ) { - Image( - painter = painterResource(id = R.drawable.ic_nav_search), - contentDescription = null, + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(searchViews.companyImage) + .crossfade(true) + .build(), + contentDescription = stringResource(id = R.string.search_image), + contentScale = ContentScale.Crop, modifier = Modifier - .fillMaxWidth() - .padding(16.dp) - .background(Grey400) + .height(76.dp) + .customShadow(offsetY = 3.dp) + .wrapContentSize() + .clip( + RoundedCornerShape( + topStart = 5.dp, + topEnd = 5.dp + ) + ) ) + Text( - text = "[유한킴벌리]\n그린캠프 w. 대학생 숲 \n활동가 모집", - modifier = Modifier.padding(horizontal = 8.dp), + text = "${searchViews.title} ${searchViews.title}${searchViews.title}${searchViews.title}", + modifier = Modifier + .padding(horizontal = 8.dp) + .wrapContentSize(), style = TerningTheme.typography.body6, color = Black, overflow = TextOverflow.Ellipsis, diff --git a/feature/src/main/java/com/terning/feature/search/search/component/SearchInternList.kt b/feature/src/main/java/com/terning/feature/search/search/component/SearchInternList.kt index 0060685c0..f8abf167e 100644 --- a/feature/src/main/java/com/terning/feature/search/search/component/SearchInternList.kt +++ b/feature/src/main/java/com/terning/feature/search/search/component/SearchInternList.kt @@ -9,13 +9,17 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController import com.terning.core.designsystem.theme.Grey400 import com.terning.core.designsystem.theme.TerningTheme +import com.terning.domain.entity.response.InternshipAnnouncement import com.terning.feature.R @Composable fun SearchInternList( type: InternListType, + searchViewsList: List, + navController: NavHostController ) { Column(modifier = Modifier.padding(horizontal = 24.dp)) { Text( @@ -33,8 +37,11 @@ fun SearchInternList( modifier = Modifier.padding(vertical = 8.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), ) { - items(5) { - SearchIntern() + items(searchViewsList.size) { index -> + SearchIntern( + searchViews = searchViewsList[index], + navController = navController + ) } } } 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 4bcedab18..26d84657b 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 @@ -3,17 +3,24 @@ package com.terning.feature.search.searchprocess import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Scaffold 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 @@ -28,6 +35,9 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController +import com.terning.core.designsystem.component.bottomsheet.SortingBottomSheet +import com.terning.core.designsystem.component.button.SortingButton +import com.terning.core.designsystem.component.item.InternItemWithShadow import com.terning.core.designsystem.component.textfield.SearchTextField import com.terning.core.designsystem.component.topappbar.BackButtonTopAppBar import com.terning.core.designsystem.theme.Grey400 @@ -43,18 +53,35 @@ private const val MAX_LINES = 1 fun SearchProcessRoute( navController: NavHostController, ) { + val currentSortBy: MutableState = remember { + mutableIntStateOf(0) + } SearchProcessScreen( navController = navController, + currentSortBy = currentSortBy ) } @Composable fun SearchProcessScreen( + currentSortBy: MutableState, modifier: Modifier = Modifier, navController: NavHostController, viewModel: SearchProcessViewModel = hiltViewModel(), ) { val state by viewModel.state.collectAsStateWithLifecycle() + var sheetState by remember { mutableStateOf(false) } + val internSearchResultData by viewModel.internSearchResultData.collectAsStateWithLifecycle() + + if (sheetState) { + SortingBottomSheet( + onDismiss = { + sheetState = false + }, + currentSortBy = currentSortBy.value, + newSortBy = currentSortBy + ) + } val focusRequester = remember { FocusRequester() } val focusManager = LocalFocusManager.current @@ -109,57 +136,90 @@ fun SearchProcessScreen( onDoneAction = { viewModel.updateQuery(state.text) viewModel.updateShowSearchResults(true) + viewModel.updateExistSearchResults(state.text) } ) if (state.showSearchResults) { Column( modifier = Modifier - .fillMaxWidth() - .padding(top = 87.dp), + .fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center ) { - Image( - painter = painterResource( - id = R.drawable.ic_nosearch - ), - contentDescription = stringResource( - id = R.string.search_process_no_result_icon + if (state.existSearchResults) { + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.End, + ) { + SortingButton( + sortBy = currentSortBy.value, + onCLick = { sheetState = true }, + ) + } + LazyColumn( + contentPadding = PaddingValues( + top = 12.dp, + bottom = 20.dp, + ), + verticalArrangement = Arrangement.spacedBy(12.dp) + ) { + items(internSearchResultData.size) { index -> + InternItemWithShadow( + imageUrl = internSearchResultData[index].imgUrl, + title = internSearchResultData[index].title, + dateDeadline = internSearchResultData[index].dDay.toString(), + workingPeriod = internSearchResultData[index].workingPeriod.toString(), + isScraped = internSearchResultData[index].isScrapped + ) + } + } + } else { + Spacer( + modifier = Modifier.padding(top = 87.dp) ) - ) - Row( - modifier = Modifier - .padding( - top = 16.dp, - bottom = 6.dp + Image( + painter = painterResource( + id = R.drawable.ic_empty_logo + ), + contentDescription = stringResource( + id = R.string.search_process_no_result_icon ) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center - ) { - Text( - text = state.query, - style = TerningTheme.typography.body1, - color = TerningMain, - maxLines = MAX_LINES, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.weight(1f, false) ) + Row( + modifier = Modifier + .padding( + top = 16.dp, + bottom = 6.dp + ) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + Text( + text = state.query, + style = TerningTheme.typography.body1, + color = TerningMain, + maxLines = MAX_LINES, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.weight(1f, false) + ) + Text( + text = stringResource(id = R.string.search_process_no_result_text_sub), + style = TerningTheme.typography.body1, + color = Grey400, + modifier = Modifier.wrapContentWidth() + ) + } Text( - text = stringResource(id = R.string.search_process_no_result_text_sub), + text = stringResource( + id = R.string.search_process_no_result_text_main + ), style = TerningTheme.typography.body1, color = Grey400, - modifier = Modifier.wrapContentWidth() ) } - Text( - text = stringResource( - id = R.string.search_process_no_result_text_main - ), - style = TerningTheme.typography.body1, - color = Grey400, - ) } } } @@ -171,7 +231,10 @@ fun SearchProcessScreen( fun SearchProcessScreenPreview() { TerningPointTheme { SearchProcessScreen( - navController = rememberNavController() + navController = rememberNavController(), + currentSortBy = remember { + mutableIntStateOf(0) + } ) } } 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 a88196b52..589f3106a 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 @@ -1,10 +1,12 @@ package com.terning.feature.search.searchprocess import androidx.lifecycle.ViewModel +import com.terning.feature.home.home.model.InternData import com.terning.feature.search.searchprocess.models.SearchProcessState import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import javax.inject.Inject @HiltViewModel @@ -14,6 +16,11 @@ class SearchProcessViewModel @Inject constructor() : ViewModel() { val state: StateFlow get() = _state + private val _internSearchResultState = MutableStateFlow( + getRecommendData() + ) + val internSearchResultData get() = _internSearchResultState.asStateFlow() + fun updateText(newText: String) { _state.value = _state.value.copy(text = newText) } @@ -24,5 +31,86 @@ class SearchProcessViewModel @Inject constructor() : ViewModel() { fun updateShowSearchResults(show: Boolean) { _state.value = _state.value.copy(showSearchResults = show) + _state.value = _state.value.copy(existSearchResults = true) + } + + + fun updateExistSearchResults(query: String) { + val exist = + _internSearchResultState.value.any { it.title.contains(query, ignoreCase = true) } + _state.value = _state.value.copy(existSearchResults = exist) } } + +private fun getRecommendData(): List = listOf( + InternData( + imgUrl = "https://reqres.in/img/faces/7-image.jpg", + title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", + dDay = 22, + workingPeriod = 2, + isScrapped = true, + ), + InternData( + imgUrl = "https://reqres.in/img/faces/7-image.jpg", + title = "ㅇㄻㅇㅁㄻㄹㅇㅁㅇㄹ", + dDay = 9, + workingPeriod = 6, + isScrapped = false, + ), + InternData( + imgUrl = "https://reqres.in/img/faces/7-image.jpg", + title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", + dDay = 2, + workingPeriod = 4, + isScrapped = true, + ), + InternData( + imgUrl = "https://reqres.in/img/faces/7-image.jpg", + title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", + dDay = 2, + workingPeriod = 4, + isScrapped = false, + ), + InternData( + imgUrl = "https://reqres.in/img/faces/7-image.jpg", + title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", + dDay = 2, + workingPeriod = 4, + isScrapped = true, + ), + InternData( + imgUrl = "https://reqres.in/img/faces/7-image.jpg", + title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", + dDay = 2, + workingPeriod = 4, + isScrapped = true, + ), + InternData( + imgUrl = "https://reqres.in/img/faces/7-image.jpg", + title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", + dDay = 2, + workingPeriod = 4, + isScrapped = false, + ), + InternData( + imgUrl = "https://reqres.in/img/faces/7-image.jpg", + title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", + dDay = 2, + workingPeriod = 4, + isScrapped = true, + ), + InternData( + imgUrl = "https://reqres.in/img/faces/7-image.jpg", + title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", + dDay = 2, + workingPeriod = 4, + isScrapped = false, + ), + InternData( + imgUrl = "https://reqres.in/img/faces/7-image.jpg", + title = "[유한킴벌리] 그린캠프 w.대학생 숲 활동가", + dDay = 2, + workingPeriod = 4, + isScrapped = true, + ), +) \ No newline at end of file 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 2f55b7287..f00b0f1ca 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,8 +1,12 @@ package com.terning.feature.search.searchprocess.models +import com.terning.feature.home.home.model.InternData + data class SearchProcessState( val text: String = "", val query: String = "", val showSearchResults: Boolean = false, + val existSearchResults: Boolean = false, + val searchResults: List = emptyList(), ) \ No newline at end of file diff --git a/feature/src/main/res/drawable/ic_nosearch.xml b/feature/src/main/res/drawable/ic_nosearch.xml deleted file mode 100644 index c63bd8fb7..000000000 --- a/feature/src/main/res/drawable/ic_nosearch.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/feature/src/main/res/values/strings.xml b/feature/src/main/res/values/strings.xml index 03194385a..bd05dff4f 100644 --- a/feature/src/main/res/values/strings.xml +++ b/feature/src/main/res/values/strings.xml @@ -31,6 +31,7 @@ 관심있는 인턴 공고 키워드를 검색해 보세요 요즘 대학생들에게 인기 있는 공고 지금 조회수가 많은 공고들이에요 + 공고 이미지 지금 스크랩수가 많은 공고들이에요 @@ -67,9 +68,6 @@ 내 계획에 딱 맞는 대학생 인턴 공고 필터링 학년 - 1개월 ~ 3개월 - 4개월 ~ 6개월 - 7개월 이상 필터링 설정에 일치하는 인턴 공고가 없어요! @@ -112,13 +110,10 @@ 원하는 근무 시작 시기에 맞는 인턴 공고를 찾아드려요 다음으로 - - 재학 상태를 선택해주세요 - 희망하는 인턴 근무 기간을 선택해주세요 - 입사를 계획중인 달을 선택해주세요 - 휴학중이라면, 휴학 전 마지막 수료 학년을 선택해주세요 - 선택한 기간 동안 근무할 수 있는 인턴 공고를 찾아드릴게요 - 선택한 달부터 근무를 시작할 수 있는 공고를 찾아드릴게요 + + 필터링 재설정 + 재학 상태를 선택해 주세요 + 저장하기 공유 아이콘