-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FEAT/#79] 탐색 기본 뷰 / 조회수 많은 공고 통신 #89
Changes from 14 commits
b019e89
783674c
a6ebc71
a759a73
a3e34da
9eeedf7
3756872
396e4f7
b5bb2cd
4dad804
572efd4
3375f8d
a939cff
08d7b39
afd35a6
4d3990d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -68,9 +68,11 @@ fun TerningBasicTextField( | |
keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), | ||
keyboardActions = KeyboardActions( | ||
onDone = { | ||
keyboardController?.hide() | ||
focusManager.clearFocus() | ||
onDoneAction?.invoke() | ||
if (value.isNotEmpty() && value.isNotBlank()) { | ||
keyboardController?.hide() | ||
focusManager.clearFocus() | ||
onDoneAction?.invoke() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. invoke()함수 사용하지 않아도 괜찮을 것 같다는 생각이 살짝쿵 드네요..!!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. null 체크를 if문으로 하는 것으로 수정했습니다! |
||
} | ||
} | ||
), | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.terning.data.datasource | ||
|
||
import com.terning.data.dto.BaseResponse | ||
import com.terning.data.dto.response.SearchViewsResponseDto | ||
|
||
interface SearchViewsDataSource { | ||
suspend fun getSearchViews(): BaseResponse<SearchViewsResponseDto> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.terning.data.datasourceimpl | ||
|
||
import com.terning.data.datasource.SearchViewsDataSource | ||
import com.terning.data.dto.BaseResponse | ||
import com.terning.data.dto.response.SearchViewsResponseDto | ||
import com.terning.data.service.SearchService | ||
import javax.inject.Inject | ||
|
||
class SearchViewsDataSourceImpl @Inject constructor( | ||
private val searchService: SearchService, | ||
) : SearchViewsDataSource { | ||
override suspend fun getSearchViews(): BaseResponse<SearchViewsResponseDto> = | ||
searchService.getSearchViewsList() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.terning.data.dto.response | ||
|
||
import com.terning.domain.entity.response.SearchViewsResponseModel | ||
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<SearchViewsResponseModel> = | ||
listOf( | ||
SearchViewsResponseModel( | ||
announcementId = internshipAnnouncementId, | ||
companyImage = companyImage, | ||
title = title, | ||
) | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.terning.data.repositoryimpl | ||
|
||
import com.terning.data.datasource.SearchViewsDataSource | ||
import com.terning.domain.entity.response.SearchViewsResponseModel | ||
import com.terning.domain.repository.SearchViewsRepository | ||
import javax.inject.Inject | ||
|
||
class SearchViewsRepositoryImpl @Inject constructor( | ||
private val searchViewsDataSource: SearchViewsDataSource, | ||
) : SearchViewsRepository { | ||
override suspend fun getSearchViewsList(): Result<List<SearchViewsResponseModel>> = | ||
runCatching { | ||
searchViewsDataSource | ||
.getSearchViews() | ||
.result | ||
.toSearchViewsEntity() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<SearchViewsResponseDto> | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 엔티티 네이밍에선 요청 응답 관련한 정보는 필요 없을 것 같다고 생각됩니다!! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 앗 InternshipAnnouncement으로 통일하겠씁니다! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.terning.domain.entity.response | ||
|
||
data class SearchViewsResponseModel( | ||
val title: String, | ||
val companyImage: String, | ||
val announcementId: Long, | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.terning.domain.repository | ||
|
||
import com.terning.domain.entity.response.SearchViewsResponseModel | ||
|
||
interface SearchViewsRepository { | ||
suspend fun getSearchViewsList(): Result<List<SearchViewsResponseModel>> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,16 +10,24 @@ 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 | ||
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.SearchViewsResponseModel | ||
import com.terning.feature.R | ||
import com.terning.feature.search.search.component.ImageSlider | ||
import com.terning.feature.search.search.component.InternListType | ||
|
@@ -29,16 +37,44 @@ 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 -> {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기서 toast 확장함수 써주면 좋을 것 같아요!! sideEffect.message 쓰면 toast를 띄울 수 있다고 합니다(샤라웃 투 이유빈❤️) |
||
} | ||
} | ||
} | ||
|
||
when (state.searchViewsList) { | ||
is UiState.Loading -> {} | ||
is UiState.Empty -> {} | ||
is UiState.Failure -> {} | ||
is UiState.Success -> { | ||
SearchScreen( | ||
navController = navController, | ||
searchViewsList = (state.searchViewsList as UiState.Success<List<SearchViewsResponseModel>>).data | ||
) | ||
} | ||
|
||
} | ||
} | ||
|
||
@Composable | ||
fun SearchScreen( | ||
modifier: Modifier = Modifier, | ||
navController: NavHostController, | ||
searchViewsList: List<SearchViewsResponseModel>, | ||
) { | ||
val images = listOf( | ||
R.drawable.ic_nav_search, | ||
|
@@ -91,13 +127,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 | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package com.terning.feature.search.search | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import com.terning.core.state.UiState | ||
import com.terning.domain.entity.response.SearchViewsResponseModel | ||
import com.terning.domain.repository.SearchViewsRepository | ||
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 searchViewsRepository: SearchViewsRepository, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기두 네이밍을 바꾼다면 |
||
) : ViewModel() { | ||
private val _state: MutableStateFlow<SearchViewsState> = MutableStateFlow(SearchViewsState()) | ||
val state: StateFlow<SearchViewsState> = _state.asStateFlow() | ||
|
||
private val _sideEffect: MutableSharedFlow<SearchViewsSideEffect> = MutableSharedFlow() | ||
val sideEffect = _sideEffect.asSharedFlow() | ||
|
||
init { | ||
getSearchViews() | ||
} | ||
|
||
fun getSearchViews() { | ||
viewModelScope.launch { | ||
searchViewsRepository.getSearchViewsList().onSuccess { response -> | ||
val searchViewsList = response.map { entity -> | ||
SearchViewsResponseModel( | ||
title = entity.title, | ||
companyImage = entity.companyImage, | ||
announcementId = entity.announcementId | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. DTO쪽에서 매핑해주면 더 깔끔할 것 같아요!! |
||
} | ||
_state.value = _state.value.copy( | ||
searchViewsList = UiState.Success(searchViewsList) | ||
) | ||
_sideEffect.emit(SearchViewsSideEffect.Toast(R.string.server_success)) | ||
}.onFailure { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 서버통신에 성공했을 때는 따로 토스트 안 띄워줘도 될 것 같아용 |
||
_sideEffect.emit(SearchViewsSideEffect.Toast(R.string.server_failure)) | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
package com.terning.feature.search.search | ||
|
||
import com.terning.core.state.UiState | ||
import com.terning.domain.entity.response.SearchViewsResponseModel | ||
|
||
data class SearchViewsState( | ||
var searchViewsList: UiState<List<SearchViewsResponseModel>> = UiState.Loading, | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 완벽함니다!! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isNotEmpty와 isNotBlank를 함께 사용하신 이유가 궁금해요!! 저는 isNotBlank가 isNotEmpty를 포함하고 있는 개념이라고 이해하고 있었거든요🥹
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
헉 예리하다 ㅜㅜ! 맞아요 isNotBlank 안에 isNotEmpty 개념이 포함되어서 isNotBlank만 써줘도 될 것 같습니닷