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/#79] 탐색 기본 뷰 / 조회수 많은 공고 통신 #89

Merged
merged 16 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/src/main/java/com/terning/point/di/DataSourceModule.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.terning.point.di

import com.terning.data.datasource.MockDataSource
import com.terning.data.datasource.SearchViewsDataSource
import com.terning.data.datasourceimpl.MockDataSourceImpl
import com.terning.data.datasourceimpl.SearchViewsDataSourceImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand All @@ -15,4 +17,9 @@ abstract class DataSourceModule {
@Binds
@Singleton
abstract fun bindMockDataSource(mockDataSourceImpl: MockDataSourceImpl): MockDataSource

@Binds
@Singleton
abstract fun bindSearchViewsDataSource(searchViewsDataSourceImpl: SearchViewsDataSourceImpl):
SearchViewsDataSource
}
6 changes: 6 additions & 0 deletions app/src/main/java/com/terning/point/di/RepositoryModule.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.terning.point.di

import com.terning.data.repositoryimpl.MockRepositoryImpl
import com.terning.data.repositoryimpl.SearchViewsRepositoryImpl
import com.terning.domain.repository.MockRepository
import com.terning.domain.repository.SearchViewsRepository
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
Expand All @@ -15,4 +17,8 @@ abstract class RepositoryModule {
@Binds
@Singleton
abstract fun bindMockRepository(mockRepositoryImpl: MockRepositoryImpl): MockRepository

@Binds
@Singleton
abstract fun bindSearchViewsRepository(searchViewsRepositoryImpl: SearchViewsRepositoryImpl): SearchViewsRepository
}
5 changes: 5 additions & 0 deletions app/src/main/java/com/terning/point/di/ServiceModule.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.terning.point.di

import com.terning.data.service.MockService
import com.terning.data.service.SearchService
import com.terning.point.di.qualifier.OPEN
import dagger.Module
import dagger.Provides
Expand All @@ -18,4 +19,8 @@ object ServiceModule {
fun provideMockService(@OPEN retrofit: Retrofit): MockService =
retrofit.create(MockService::class.java)

@Provides
@Singleton
fun provideSearchService(@OPEN retrofit: Retrofit): SearchService =
retrofit.create(SearchService::class.java)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Copy link
Member

Choose a reason for hiding this comment

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

isNotEmpty와 isNotBlank를 함께 사용하신 이유가 궁금해요!! 저는 isNotBlank가 isNotEmpty를 포함하고 있는 개념이라고 이해하고 있었거든요🥹

Copy link
Contributor Author

Choose a reason for hiding this comment

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

헉 예리하다 ㅜㅜ! 맞아요 isNotBlank 안에 isNotEmpty 개념이 포함되어서 isNotBlank만 써줘도 될 것 같습니닷

keyboardController?.hide()
focusManager.clearFocus()
onDoneAction?.invoke()
Copy link
Member

Choose a reason for hiding this comment

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

invoke()함수 사용하지 않아도 괜찮을 것 같다는 생각이 살짝쿵 드네요..!!!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

null 체크를 if문으로 하는 것으로 수정했습니다!

}
}
),

Expand Down
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()
}
}
10 changes: 10 additions & 0 deletions data/src/main/java/com/terning/data/service/SearchService.kt
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>
}
Copy link
Member

Choose a reason for hiding this comment

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

엔티티 네이밍에선 요청 응답 관련한 정보는 필요 없을 것 같다고 생각됩니다!!

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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
Expand Up @@ -71,7 +71,9 @@ fun InternScreen(
color = Grey200,
offsetY = 2.dp
),
onBackButtonClick = {},
onBackButtonClick = {
navController.navigateUp()
},
)
},
bottomBar = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 -> {}
Copy link
Member

Choose a reason for hiding this comment

The 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,
Expand Down Expand Up @@ -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,
Copy link
Member

Choose a reason for hiding this comment

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

여기두 네이밍을 바꾼다면 SearchRepository로 바꿀 수 있을 것 같네용

) : 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
)
Copy link
Member

Choose a reason for hiding this comment

The 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 {
Copy link
Member

Choose a reason for hiding this comment

The 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,
)
Copy link
Member

Choose a reason for hiding this comment

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

완벽함니다!!

Loading
Loading