Skip to content
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/#306] 3차 스프린트 탐색 뷰 #320

Merged
merged 11 commits into from
Jan 10, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package com.terning.data.search.datasource
import com.terning.core.network.BaseResponse
import com.terning.data.search.dto.request.SearchRequestDto
import com.terning.data.search.dto.response.SearchAnnouncementResponseDto
import com.terning.data.search.dto.response.SearchBannersResponseDto
import com.terning.data.search.dto.response.SearchResultResponseDto

interface SearchDataSource {
suspend fun getSearch(request: SearchRequestDto): BaseResponse<SearchResultResponseDto>
suspend fun getSearchViews(): BaseResponse<SearchAnnouncementResponseDto>
suspend fun getSearchScraps(): BaseResponse<SearchAnnouncementResponseDto>
suspend fun getSearchBanners(): BaseResponse<SearchBannersResponseDto>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.terning.core.network.BaseResponse
import com.terning.data.search.datasource.SearchDataSource
import com.terning.data.search.dto.request.SearchRequestDto
import com.terning.data.search.dto.response.SearchAnnouncementResponseDto
import com.terning.data.search.dto.response.SearchBannersResponseDto
import com.terning.data.search.dto.response.SearchResultResponseDto
import com.terning.data.search.service.SearchService
import javax.inject.Inject
Expand All @@ -26,4 +27,7 @@ class SearchDataSourceImpl @Inject constructor(

override suspend fun getSearchScraps(): BaseResponse<SearchAnnouncementResponseDto> =
searchService.getSearchScrapsList()

override suspend fun getSearchBanners(): BaseResponse<SearchBannersResponseDto> =
searchService.getSearchBannerList()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.terning.data.search.dto.response

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class SearchBannersResponseDto(
@SerialName("banners")
val banners: List<BannerDto>,
) {
@Serializable
data class BannerDto(
@SerialName("imageUrl")
val imageUrl: String,
@SerialName("link")
val link: String,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.terning.data.search.mapper

import com.terning.data.search.dto.response.SearchBannersResponseDto
import com.terning.domain.search.entity.SearchBanner

fun SearchBannersResponseDto.toSearchBannerList(): List<SearchBanner> {
return banners.map {
SearchBanner(
imageUrl = it.imageUrl,
url = it.link,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package com.terning.data.search.repositoryimpl

import com.terning.data.search.datasource.SearchDataSource
import com.terning.data.search.dto.request.SearchRequestDto
import com.terning.data.search.mapper.toSearchBannerList
import com.terning.data.search.mapper.toSearchPopularAnnouncementList
import com.terning.data.search.mapper.toSearchResultList
import com.terning.domain.search.entity.SearchBanner
import com.terning.domain.search.entity.SearchPopularAnnouncement
import com.terning.domain.search.entity.SearchResult
import com.terning.domain.search.repository.SearchRepository
Expand Down Expand Up @@ -39,4 +41,9 @@ class SearchRepositoryImpl @Inject constructor(
runCatching {
searchDataSource.getSearchScraps().result.toSearchPopularAnnouncementList()
}

override suspend fun getSearchBannersList(): Result<List<SearchBanner>> =
kotlin.runCatching {
searchDataSource.getSearchBanners().result.toSearchBannerList()
}
Comment on lines +45 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 이제 봤는데 kotlin 지워줘야 할 것 같아요!

Suggested change
override suspend fun getSearchBannersList(): Result<List<SearchBanner>> =
kotlin.runCatching {
searchDataSource.getSearchBanners().result.toSearchBannerList()
}
override suspend fun getSearchBannersList(): Result<List<SearchBanner>> =
runCatching {
searchDataSource.getSearchBanners().result.toSearchBannerList()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.terning.data.search.service

import com.terning.core.network.BaseResponse
import com.terning.data.search.dto.response.SearchAnnouncementResponseDto
import com.terning.data.search.dto.response.SearchBannersResponseDto
import com.terning.data.search.dto.response.SearchResultResponseDto
import retrofit2.http.GET
import retrofit2.http.Query
Expand All @@ -20,4 +21,7 @@ interface SearchService {

@GET("api/v1/search/scraps")
suspend fun getSearchScrapsList(): BaseResponse<SearchAnnouncementResponseDto>

@GET("api/v1/search/banners")
suspend fun getSearchBannerList(): BaseResponse<SearchBannersResponseDto>
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.terning.domain.search.entity

data class SearchBanner(
val imageRes: Int,
val url: String
val imageUrl: String,
val url: String,
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.terning.domain.search.repository

import com.terning.domain.search.entity.SearchBanner
import com.terning.domain.search.entity.SearchPopularAnnouncement
import com.terning.domain.search.entity.SearchResult

Expand All @@ -10,6 +11,8 @@ interface SearchRepository {
page: Int,
size: Int,
): Result<List<SearchResult>>

suspend fun getSearchViewsList(): Result<List<SearchPopularAnnouncement>>
suspend fun getSearchScrapsList(): Result<List<SearchPopularAnnouncement>>
suspend fun getSearchBannersList(): Result<List<SearchBanner>>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.lazy.LazyColumn
Expand All @@ -30,22 +30,26 @@ import com.terning.core.designsystem.state.UiState
import com.terning.core.designsystem.theme.Black
import com.terning.core.designsystem.theme.TerningTheme
import com.terning.core.designsystem.theme.White
import com.terning.domain.search.entity.SearchBanner
import com.terning.domain.search.entity.SearchPopularAnnouncement
import com.terning.feature.search.R
import com.terning.feature.search.search.component.ImageSlider
import com.terning.feature.search.search.component.InternListType
import com.terning.feature.search.search.component.SearchInternList
import okhttp3.internal.toImmutableList
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList

@Composable
fun SearchRoute(
modifier: Modifier,
paddingValues: PaddingValues,
navigateToSearchProcess: () -> Unit,
navigateToIntern: (Long) -> Unit,
viewModel: SearchViewModel = hiltViewModel(),
) {
val lifecycleOwner = LocalLifecycleOwner.current
val context = LocalContext.current

val bannerState by viewModel.bannerState.collectAsStateWithLifecycle(lifecycleOwner = lifecycleOwner)
val viewState by viewModel.viewState.collectAsStateWithLifecycle(lifecycleOwner = lifecycleOwner)
val scrapState by viewModel.scrapState.collectAsStateWithLifecycle(lifecycleOwner = lifecycleOwner)
Comment on lines +52 to 54
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

궁금해서 물어보는 건데요...! 혹시 bannerState를 따로 추가해준 이유가 있나용...?! state를 나누는 기준이 궁금해졌어요!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

배너 / 조회수 많은 공고 / 스크랩 많은 공고로 state를 나누었습니다!


Expand All @@ -54,32 +58,38 @@ fun SearchRoute(
LaunchedEffect(key1 = true) {
viewModel.getSearchViews()
viewModel.getSearchScraps()
viewModel.getSearchBanners()
}

LaunchedEffect(viewModel.sideEffect, lifecycleOwner) {
viewModel.sideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle)
.collect { sideEffect ->
when (sideEffect) {
is SearchSideEffect.Toast -> {
is SearchSideEffect.ShowToast -> {
sideEffect.message
}
}
}
}

val bannerList = when (bannerState.searchBannersList) {
is UiState.Success -> (bannerState.searchBannersList as UiState.Success<List<SearchBanner>>).data.toImmutableList()
else -> emptyList<SearchBanner>().toImmutableList()
}

val searchViewsList = when (viewState.searchViewsList) {
is UiState.Success -> (viewState.searchViewsList as UiState.Success<List<com.terning.domain.search.entity.SearchPopularAnnouncement>>).data.toImmutableList()
else -> emptyList()
is UiState.Success -> (viewState.searchViewsList as UiState.Success<List<SearchPopularAnnouncement>>).data.toImmutableList()
else -> emptyList<SearchPopularAnnouncement>().toImmutableList()
}

val searchScrapsList = when (scrapState.searchScrapsList) {
is UiState.Success -> (scrapState.searchScrapsList as UiState.Success<List<com.terning.domain.search.entity.SearchPopularAnnouncement>>).data.toImmutableList()
else -> emptyList()
is UiState.Success -> (scrapState.searchScrapsList as UiState.Success<List<SearchPopularAnnouncement>>).data.toImmutableList()
else -> emptyList<SearchPopularAnnouncement>().toImmutableList()
}

SearchScreen(
modifier = modifier,
bannerList = SearchViewModel.bannerList,
paddingValues = paddingValues,
bannerList = bannerList,
searchViewsList = searchViewsList,
searchScrapsList = searchScrapsList,
navigateToSearchProcess = {
Expand All @@ -96,25 +106,25 @@ fun SearchRoute(
name = "quest_banner"
)
CustomTabsIntent.Builder().build()
.launchUrl(context, SearchViewModel.bannerList[pageIndex].url.toUri())
.launchUrl(context, bannerList[pageIndex].url.toUri())
}
)
}

@Composable
fun SearchScreen(
modifier: Modifier = Modifier,
bannerList: List<com.terning.domain.search.entity.SearchBanner>,
searchViewsList: List<com.terning.domain.search.entity.SearchPopularAnnouncement>,
searchScrapsList: List<com.terning.domain.search.entity.SearchPopularAnnouncement>,
paddingValues: PaddingValues,
bannerList: ImmutableList<SearchBanner>,
searchViewsList: ImmutableList<SearchPopularAnnouncement>,
searchScrapsList: ImmutableList<SearchPopularAnnouncement>,
navigateToSearchProcess: () -> Unit,
navigateToIntern: (Long) -> Unit,
onAdvertisementClick: (Int) -> Unit,
) {
Column(
modifier = modifier
.fillMaxSize()
modifier = Modifier
.background(White)
.padding(paddingValues)
) {
TerningImage(
painter = R.drawable.ic_terning_logo_typo,
Expand Down Expand Up @@ -145,7 +155,7 @@ fun SearchScreen(
LazyColumn {
item {
ImageSlider(
images = bannerList,
searchBanners = bannerList,
onAdvertisementClick = onAdvertisementClick,
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ package com.terning.feature.search.search
import androidx.annotation.StringRes

sealed class SearchSideEffect {
data class Toast(@StringRes val message: Int) : SearchSideEffect()
data class ShowToast(@StringRes val message: Int) : SearchSideEffect()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package com.terning.feature.search.search
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.terning.core.designsystem.state.UiState
import com.terning.feature.search.R
import com.terning.feature.search.search.model.SearchBannerListState
import com.terning.feature.search.search.model.SearchScrapsListState
import com.terning.feature.search.search.model.SearchViewsListState
import dagger.hilt.android.lifecycle.HiltViewModel
Expand All @@ -29,12 +29,17 @@ class SearchViewModel @Inject constructor(
MutableStateFlow(SearchScrapsListState())
val scrapState: StateFlow<SearchScrapsListState> = _scrapState.asStateFlow()

private val _bannerState: MutableStateFlow<SearchBannerListState> =
MutableStateFlow(SearchBannerListState())
val bannerState: StateFlow<SearchBannerListState> = _bannerState.asStateFlow()

private val _sideEffect: MutableSharedFlow<SearchSideEffect> = MutableSharedFlow()
val sideEffect = _sideEffect.asSharedFlow()

init {
getSearchViews()
getSearchScraps()
getSearchBanners()
}

fun getSearchViews() {
Expand All @@ -45,7 +50,7 @@ class SearchViewModel @Inject constructor(
searchViewsList = UiState.Success(searchViewsList)
)
}.onFailure {
_sideEffect.emit(SearchSideEffect.Toast(DesignSystemR.string.server_failure))
_sideEffect.emit(SearchSideEffect.ShowToast(DesignSystemR.string.server_failure))
}
}
}
Expand All @@ -58,25 +63,21 @@ class SearchViewModel @Inject constructor(
searchScrapsList = UiState.Success(searchScrapsList)
)
}.onFailure {
_sideEffect.emit(SearchSideEffect.Toast(DesignSystemR.string.server_failure))
_sideEffect.emit(SearchSideEffect.ShowToast(DesignSystemR.string.server_failure))
}
}
}

companion object {
val bannerList: List<com.terning.domain.search.entity.SearchBanner> = listOf(
com.terning.domain.search.entity.SearchBanner(
imageRes = R.drawable.img_ad_1,
url = "https://www.instagram.com/p/DBWCO97TRds/?igsh=bDhjMGxlMGliNDc2"
),
com.terning.domain.search.entity.SearchBanner(
imageRes = R.drawable.img_ad_2,
url = "https://www.instagram.com/terning_official/"
),
com.terning.domain.search.entity.SearchBanner(
imageRes = R.drawable.img_ad_3,
url = "https://forms.gle/4btEwEbUQ3JSjTKP7"
)
)
fun getSearchBanners() {
viewModelScope.launch {
searchRepository.getSearchBannersList()
.onSuccess { searchBannersList ->
_bannerState.value = _bannerState.value.copy(
searchBannersList = UiState.Success(searchBannersList)
)
}.onFailure {
_sideEffect.emit(SearchSideEffect.ShowToast(DesignSystemR.string.server_failure))
}
}
}
}
}
Loading
Loading