diff --git a/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchApiService.kt b/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchApiService.kt index 0b6ca61e..b178e474 100644 --- a/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchApiService.kt +++ b/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchApiService.kt @@ -6,10 +6,11 @@ import retrofit2.http.Query interface SearchApiService { - @GET("/api/v1/search") + @GET("/api/v2/search") suspend fun requestSearchTag( @Query("tag") tag: String, - @Query("end") end: Int + @Query("end") end: Int, + @Query("order") order: String ): NetworkResponse @GET("/api/v1/search/member") diff --git a/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchPagingSource.kt b/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchPagingSource.kt index 137aa329..41240eae 100644 --- a/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchPagingSource.kt +++ b/data/src/main/java/daily/dayo/data/datasource/remote/search/SearchPagingSource.kt @@ -5,18 +5,20 @@ import androidx.paging.PagingState import daily.dayo.data.mapper.toSearch import daily.dayo.domain.model.NetworkResponse import daily.dayo.domain.model.Search +import daily.dayo.domain.model.SearchOrder class SearchPagingSource( private val apiService: SearchApiService, private val size: Int, - private val tag: String + private val tag: String, + private val searchOrder: SearchOrder ) : PagingSource() { override suspend fun load( params: LoadParams ): LoadResult { val nextPageNumber = params.key ?: 0 - apiService.requestSearchTag(tag = tag, end = nextPageNumber).let { ApiResponse -> + apiService.requestSearchTag(tag = tag, end = nextPageNumber, order = searchOrder.toString()).let { ApiResponse -> return try { when (ApiResponse) { is NetworkResponse.Success -> { diff --git a/data/src/main/java/daily/dayo/data/repository/SearchRepositoryImpl.kt b/data/src/main/java/daily/dayo/data/repository/SearchRepositoryImpl.kt index ea8232b8..ea0cf721 100644 --- a/data/src/main/java/daily/dayo/data/repository/SearchRepositoryImpl.kt +++ b/data/src/main/java/daily/dayo/data/repository/SearchRepositoryImpl.kt @@ -7,13 +7,14 @@ import androidx.paging.PagingData import daily.dayo.data.datasource.local.SharedManager import daily.dayo.data.datasource.remote.search.SearchApiService import daily.dayo.data.datasource.remote.search.SearchFollowUserPagingSource -import daily.dayo.data.datasource.remote.search.SearchUserPagingSource import daily.dayo.data.datasource.remote.search.SearchPagingSource +import daily.dayo.data.datasource.remote.search.SearchUserPagingSource import daily.dayo.domain.model.NetworkResponse import daily.dayo.domain.model.Search import daily.dayo.domain.model.SearchHistory import daily.dayo.domain.model.SearchHistoryDetail import daily.dayo.domain.model.SearchHistoryType +import daily.dayo.domain.model.SearchOrder import daily.dayo.domain.model.SearchUser import daily.dayo.domain.repository.SearchRepository import kotlinx.coroutines.flow.Flow @@ -47,10 +48,11 @@ class SearchRepositoryImpl @Inject constructor( override fun updateSearchKeywordRecentList(keyword: String, requestSearchType: SearchHistoryType) { SharedManager(context = context).updateSearchHistory( SearchHistoryDetail( - history = keyword, - searchHistoryType = requestSearchType, - searchId = 0 - )) + history = keyword, + searchHistoryType = requestSearchType, + searchId = 0 + ) + ) } override fun requestSearchUser(nickname: String): Flow> = Pager(PagingConfig(pageSize = SEARCH_PAGE_SIZE)) { @@ -61,35 +63,43 @@ class SearchRepositoryImpl @Inject constructor( SearchFollowUserPagingSource(searchApiService, SEARCH_PAGE_SIZE, nickname) }.flow - override fun requestSearchTag(tag: String): Flow> = Pager(PagingConfig(pageSize = SEARCH_PAGE_SIZE)) { - SearchPagingSource(searchApiService, SEARCH_PAGE_SIZE, tag) + override fun requestSearchTag(tag: String, searchOrder: SearchOrder): Flow> = Pager(PagingConfig(pageSize = SEARCH_PAGE_SIZE)) { + SearchPagingSource(searchApiService, SEARCH_PAGE_SIZE, tag, searchOrder) }.flow - override suspend fun requestSearchTotalCount(tag: String, end: Int, searchHistoryType: SearchHistoryType) : Int = - when (searchHistoryType) { - SearchHistoryType.TAG -> { - searchApiService.requestSearchTag(tag, end).let { ApiResponse -> - when(ApiResponse) { - is NetworkResponse.Success -> { - return ApiResponse.body!!.totalCount - } - else -> return 0 + override suspend fun requestSearchTotalCount( + tag: String, + end: Int, + searchHistoryType: SearchHistoryType, + searchOrder: SearchOrder + ): Int = when (searchHistoryType) { + SearchHistoryType.TAG -> { + searchApiService.requestSearchTag(tag, end, searchOrder.toString()).let { ApiResponse -> + when (ApiResponse) { + is NetworkResponse.Success -> { + return ApiResponse.body!!.totalCount } + + else -> return 0 } } - SearchHistoryType.USER -> { - searchApiService.requestSearchUser(tag, end).let { ApiResponse -> - when(ApiResponse) { - is NetworkResponse.Success -> { - return ApiResponse.body!!.totalCount - } - else -> return 0 + } + + SearchHistoryType.USER -> { + searchApiService.requestSearchUser(tag, end).let { ApiResponse -> + when (ApiResponse) { + is NetworkResponse.Success -> { + return ApiResponse.body!!.totalCount } + + else -> return 0 } } - else -> 0 } + else -> 0 + } + companion object { private const val SEARCH_PAGE_SIZE = 10 } diff --git a/domain/src/main/java/daily/dayo/domain/model/Search.kt b/domain/src/main/java/daily/dayo/domain/model/Search.kt index e0c2f799..7fefa270 100644 --- a/domain/src/main/java/daily/dayo/domain/model/Search.kt +++ b/domain/src/main/java/daily/dayo/domain/model/Search.kt @@ -27,4 +27,12 @@ data class SearchHistoryDetail( enum class SearchHistoryType { USER, TAG -} \ No newline at end of file +} + +enum class SearchOrder { + NEW, OLD; + + override fun toString(): String { + return name.lowercase() + } +} diff --git a/domain/src/main/java/daily/dayo/domain/repository/SearchRepository.kt b/domain/src/main/java/daily/dayo/domain/repository/SearchRepository.kt index e9d37df8..9d690f8b 100644 --- a/domain/src/main/java/daily/dayo/domain/repository/SearchRepository.kt +++ b/domain/src/main/java/daily/dayo/domain/repository/SearchRepository.kt @@ -4,11 +4,12 @@ import androidx.paging.PagingData import daily.dayo.domain.model.Search import daily.dayo.domain.model.SearchHistory import daily.dayo.domain.model.SearchHistoryType +import daily.dayo.domain.model.SearchOrder import daily.dayo.domain.model.SearchUser import kotlinx.coroutines.flow.Flow interface SearchRepository { - fun requestSearchTag(tag: String): Flow> + fun requestSearchTag(tag: String, searchOrder: SearchOrder): Flow> fun requestSearchUser(nickname: String): Flow> fun requestSearchFollowUser(nickname: String): Flow> fun requestSearchKeywordRecentList(): SearchHistory @@ -22,6 +23,7 @@ interface SearchRepository { suspend fun requestSearchTotalCount( tag: String, end: Int, - searchHistoryType: SearchHistoryType + searchHistoryType: SearchHistoryType, + searchOrder: SearchOrder ): Int } \ No newline at end of file diff --git a/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTagUseCase.kt b/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTagUseCase.kt index a22ffea5..c0d80ca8 100644 --- a/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTagUseCase.kt +++ b/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTagUseCase.kt @@ -1,10 +1,11 @@ package daily.dayo.domain.usecase.search +import daily.dayo.domain.model.SearchOrder import daily.dayo.domain.repository.SearchRepository import javax.inject.Inject class RequestSearchTagUseCase @Inject constructor( private val searchRepository: SearchRepository ) { - operator fun invoke(tag: String) = searchRepository.requestSearchTag(tag) + operator fun invoke(tag: String, searchOrder: SearchOrder) = searchRepository.requestSearchTag(tag, searchOrder) } \ No newline at end of file diff --git a/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTotalCountUseCase.kt b/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTotalCountUseCase.kt index b6821421..c83e9cc5 100644 --- a/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTotalCountUseCase.kt +++ b/domain/src/main/java/daily/dayo/domain/usecase/search/RequestSearchTotalCountUseCase.kt @@ -1,12 +1,16 @@ package daily.dayo.domain.usecase.search import daily.dayo.domain.model.SearchHistoryType +import daily.dayo.domain.model.SearchOrder import daily.dayo.domain.repository.SearchRepository import javax.inject.Inject class RequestSearchTotalCountUseCase @Inject constructor( private val searchRepository: SearchRepository ) { - suspend operator fun invoke(tag: String, searchHistoryType: SearchHistoryType) = - searchRepository.requestSearchTotalCount(tag, 0, searchHistoryType) + suspend operator fun invoke( + tag: String, + searchHistoryType: SearchHistoryType, + searchOrder: SearchOrder = SearchOrder.NEW + ) = searchRepository.requestSearchTotalCount(tag, 0, searchHistoryType, searchOrder) } \ No newline at end of file diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt index d6ab802a..aa64311f 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt @@ -20,18 +20,18 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource +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.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.itemKey +import daily.dayo.domain.model.SearchOrder import daily.dayo.presentation.BuildConfig import daily.dayo.presentation.R import daily.dayo.presentation.common.extension.clickableSingle @@ -51,13 +51,13 @@ fun SearchPostHashtagScreen( onPostClick: (Long) -> Unit, searchViewModel: SearchViewModel = hiltViewModel() ) { - val isLatest by rememberSaveable { mutableStateOf(true) } // TODO api 수정 후 구현 + val searchHashtagOrder by searchViewModel.searchHashtagOrder.collectAsStateWithLifecycle() val hashtagPosts = searchViewModel.searchTagList.collectAsLazyPagingItems() val hashtagPostsCount by searchViewModel.searchTagTotalCount.collectAsStateWithLifecycle(0) LaunchedEffect(Unit) { with(searchViewModel) { - searchHashtag(hashtag = hashtag) + searchHashtag(hashtag = hashtag, searchOrder = searchHashtagOrder) } } @@ -94,7 +94,11 @@ fun SearchPostHashtagScreen( ) { // description item(span = { GridItemSpan(2) }) { - SearchResultDescription(hashtagPostsCount, isLatest) + SearchResultDescription( + resultCount = hashtagPostsCount, + searchOrder = searchHashtagOrder, + onClickSort = { searchViewModel.toggleSearchHashtagOrder(hashtag) } + ) } // posts @@ -125,7 +129,16 @@ fun SearchPostHashtagScreen( } @Composable -private fun SearchResultDescription(resultCount: Int, isLatest: Boolean) { +private fun SearchResultDescription( + resultCount: Int, + searchOrder: SearchOrder, + onClickSort: () -> Unit +) { + val sortResId = when (searchOrder) { + SearchOrder.NEW -> R.string.search_hashtag_sort_newest + SearchOrder.OLD -> R.string.search_hashtag_sort_oldest + } + Row(modifier = Modifier.fillMaxWidth()) { Row( verticalAlignment = Alignment.CenterVertically, @@ -149,16 +162,16 @@ private fun SearchResultDescription(resultCount: Int, isLatest: Boolean) { horizontalArrangement = Arrangement.End, modifier = Modifier .padding(vertical = 12.dp) - .weight(1f) + .clickableSingle { onClickSort() } ) { Icon( painter = painterResource(id = R.drawable.ic_swap_vertical), - contentDescription = if (isLatest) "최신순" else "오래된순", + contentDescription = stringResource(id = sortResId), tint = Gray1_50545B ) Text( style = DayoTheme.typography.caption1.copy(color = Gray2_767B83), - text = if (isLatest) "최신순" else "오래된순" + text = stringResource(id = sortResId) ) } } diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt index 70ad81a5..6dc82102 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.LazyColumn @@ -30,6 +31,7 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Divider +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Tab import androidx.compose.material3.TabRow @@ -204,21 +206,24 @@ fun SearchResultScreen( val coroutineScope = rememberCoroutineScope() val pagerState = rememberPagerState { 2 } - Surface( - color = colorResource(id = R.color.white_FFFFFF), - modifier = Modifier.fillMaxSize() - ) { - Column( - modifier = Modifier.fillMaxSize() - ) { + Scaffold( + topBar = { SearchActionbarLayout( + modifier = Modifier.statusBarsPadding(), initialKeyword = searchKeyword, onBackClick = onBackClick, onSearchClick = { keyword -> onSearchClick(keyword) } ) - + }, + containerColor = DayoTheme.colorScheme.background + ) { innerPadding -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { TabRow( selectedTabIndex = pagerState.currentPage, containerColor = White, @@ -415,6 +420,7 @@ fun SearchResultTagView( imageDescription = "searched Image", modifier = Modifier .matchParentSize() + .aspectRatio(1f) .clickableSingle( interactionSource = imageInteractionSource, indication = null, diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchScreen.kt index 8be57468..0d78b2dd 100644 --- a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchScreen.kt +++ b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchScreen.kt @@ -4,7 +4,6 @@ import androidx.activity.compose.BackHandler import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -17,6 +16,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn @@ -25,8 +25,9 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -119,36 +120,36 @@ fun SearchScreen( onKeywordDeleteClick: (String, SearchHistoryType) -> Unit, onHistoryClearClick: () -> Unit ) { - Surface( - color = colorResource(id = R.color.white_FFFFFF), - modifier = Modifier - .fillMaxWidth() - .fillMaxHeight() - ) { - Column { + Scaffold( + topBar = { SearchActionbarLayout( + modifier = Modifier.statusBarsPadding(), onBackClick = onBackClick, onSearchClick = onSearchClick ) - SetSearchHistoryLayout( - onKeywordClick = onSearchClick, - onKeywordDeleteClick = onKeywordDeleteClick, - onHistoryClearClick = onHistoryClearClick, - searchHistory = searchHistory - ) - } + }, + containerColor = DayoTheme.colorScheme.background + ) { innerPadding -> + SetSearchHistoryLayout( + modifier = Modifier.padding(innerPadding), + onKeywordClick = onSearchClick, + onKeywordDeleteClick = onKeywordDeleteClick, + onHistoryClearClick = onHistoryClearClick, + searchHistory = searchHistory + ) } } @Composable fun SearchActionbarLayout( initialKeyword: String = "", + modifier: Modifier, onBackClick: () -> Unit, onSearchClick: (String) -> Unit ) { Surface( color = colorResource(id = R.color.white_FFFFFF), - modifier = Modifier + modifier = modifier .height(IntrinsicSize.Min) .fillMaxWidth() ) { @@ -296,13 +297,14 @@ private fun SetClearSearchHistoryLayout(onHistoryClearClick: () -> Unit) { @Composable private fun SetSearchHistoryLayout( + modifier: Modifier, onKeywordClick: (String) -> Unit, onKeywordDeleteClick: (String, SearchHistoryType) -> Unit, onHistoryClearClick: () -> Unit, searchHistory: SearchHistory ) { LazyColumn( - modifier = Modifier + modifier = modifier .fillMaxSize() .background(DayoTheme.colorScheme.background) ) { diff --git a/presentation/src/main/java/daily/dayo/presentation/viewmodel/SearchViewModel.kt b/presentation/src/main/java/daily/dayo/presentation/viewmodel/SearchViewModel.kt index d52aad62..dd4cecd9 100644 --- a/presentation/src/main/java/daily/dayo/presentation/viewmodel/SearchViewModel.kt +++ b/presentation/src/main/java/daily/dayo/presentation/viewmodel/SearchViewModel.kt @@ -8,6 +8,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import daily.dayo.domain.model.Search import daily.dayo.domain.model.SearchHistory import daily.dayo.domain.model.SearchHistoryType +import daily.dayo.domain.model.SearchOrder import daily.dayo.domain.model.SearchUser import daily.dayo.domain.usecase.search.ClearSearchKeywordRecentUseCase import daily.dayo.domain.usecase.search.DeleteSearchKeywordRecentUseCase @@ -37,6 +38,9 @@ class SearchViewModel @Inject constructor( var searchKeyword = "" + private val _searchHashtagOrder = MutableStateFlow(SearchOrder.NEW) + val searchHashtagOrder get() = _searchHashtagOrder + private val _searchTagTotalCount = MutableStateFlow(0) val searchTagTotalCount get() = _searchTagTotalCount @@ -59,7 +63,11 @@ class SearchViewModel @Inject constructor( _searchHistory.emit(it) } - fun searchKeyword(keyword: String, keywordType: SearchHistoryType = SearchHistoryType.TAG) = + fun searchKeyword( + keyword: String, + keywordType: SearchHistoryType = SearchHistoryType.TAG, + searchOrder: SearchOrder = SearchOrder.NEW + ) { viewModelScope.launch { updateSearchKeywordRecentUseCase(keyword, keywordType) when (keywordType) { @@ -67,7 +75,7 @@ class SearchViewModel @Inject constructor( requestSearchTotalCountUseCase(keyword, SearchHistoryType.TAG).let { _searchTagTotalCount.emit(it) } - requestSearchTagUseCase(tag = keyword) + requestSearchTagUseCase(tag = keyword, searchOrder = searchOrder) .cachedIn(viewModelScope) .collectLatest { _searchTagList.emit(it) @@ -93,6 +101,7 @@ class SearchViewModel @Inject constructor( } } } + } suspend fun searchFollowUser(keyword: String) { requestSearchFollowUserUseCase(nickname = keyword) @@ -114,17 +123,26 @@ class SearchViewModel @Inject constructor( } } - fun searchHashtag(hashtag: String) { + fun searchHashtag(hashtag: String, searchOrder: SearchOrder) { viewModelScope.launch { - requestSearchTotalCountUseCase(tag = hashtag, SearchHistoryType.TAG).let { + requestSearchTotalCountUseCase(tag = hashtag, searchHistoryType = SearchHistoryType.TAG).let { _searchTagTotalCount.emit(it) } - requestSearchTagUseCase(tag = hashtag) + requestSearchTagUseCase(tag = hashtag, searchOrder = searchOrder) .cachedIn(viewModelScope) .collectLatest { _searchTagList.emit(it) } } } + + fun toggleSearchHashtagOrder(hashtag: String) { + val newOrder = when (_searchHashtagOrder.value) { + SearchOrder.NEW -> SearchOrder.OLD + SearchOrder.OLD -> SearchOrder.NEW + } + _searchHashtagOrder.value = newOrder + searchHashtag(hashtag, newOrder) + } } \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 5d3993ad..99efcf72 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -369,6 +369,8 @@ 검색어를 입력해주세요. + 최신순 + 오래된순 최근 검색어 추천 검색어 앗! 찾으시는 검색 결과가 없어요.