diff --git a/Android/app/src/main/AndroidManifest.xml b/Android/app/src/main/AndroidManifest.xml index 33a406e9e..e150ec270 100644 --- a/Android/app/src/main/AndroidManifest.xml +++ b/Android/app/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ (val _data: T?, val message: String?) { + + data class Success(val data : R) : ApiState( + _data = data, + message = null + ) + data class Error(val exception: String?) : ApiState( + _data = null, + message = exception + ) + object Loading: ApiState( + _data = null, + message = null + ) + object Empty: ApiState( + _data = null, + message = null + ) +} diff --git a/Android/app/src/main/java/com/team16/airbnb/data/datasource/HomeDataSource.kt b/Android/app/src/main/java/com/team16/airbnb/data/datasource/HomeDataSource.kt index 35fcf7a6b..2ee90bbf4 100644 --- a/Android/app/src/main/java/com/team16/airbnb/data/datasource/HomeDataSource.kt +++ b/Android/app/src/main/java/com/team16/airbnb/data/datasource/HomeDataSource.kt @@ -3,12 +3,15 @@ package com.team16.airbnb.data.datasource import com.team16.airbnb.R import com.team16.airbnb.data.model.NearInfo import com.team16.airbnb.data.network.HomeApi +import com.team16.airbnb.data.network.SearchApi +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn import javax.inject.Inject import javax.inject.Singleton @Singleton -class HomeDataSource @Inject constructor(private val api: HomeApi) { +class HomeDataSource @Inject constructor(private val homeApi: HomeApi, private val searchApi: SearchApi) { private val list = listOf( NearInfo( @@ -105,4 +108,11 @@ class HomeDataSource @Inject constructor(private val api: HomeApi) { fun getHeroInfo() = flow { emit(R.drawable.hero_image) } + + + // 가까운 여행지 조회 + fun getNearList() = flow{ + emit(searchApi.getNearList("around")) + }.flowOn(Dispatchers.IO) + } \ No newline at end of file diff --git a/Android/app/src/main/java/com/team16/airbnb/data/dto/near/NearResult.kt b/Android/app/src/main/java/com/team16/airbnb/data/dto/near/NearResult.kt new file mode 100644 index 000000000..dc57ae067 --- /dev/null +++ b/Android/app/src/main/java/com/team16/airbnb/data/dto/near/NearResult.kt @@ -0,0 +1,15 @@ +package com.team16.airbnb.data.dto.near + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + + +@Serializable +data class NearResult( + @SerialName("content") + val content: String? = "", + @SerialName("imageUrl") + val imageUrl: String? = "", + @SerialName("title") + val title: String? = "" +) \ No newline at end of file diff --git a/Android/app/src/main/java/com/team16/airbnb/data/dto/near/NearResultResponseDto.kt b/Android/app/src/main/java/com/team16/airbnb/data/dto/near/NearResultResponseDto.kt new file mode 100644 index 000000000..e02ab8b67 --- /dev/null +++ b/Android/app/src/main/java/com/team16/airbnb/data/dto/near/NearResultResponseDto.kt @@ -0,0 +1,11 @@ +package com.team16.airbnb.data.dto.near + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + + +@Serializable +data class NearResultResponseDto( + @SerialName("result") + val nearResult: List? = listOf() +) \ No newline at end of file diff --git a/Android/app/src/main/java/com/team16/airbnb/data/network/HomeApi.kt b/Android/app/src/main/java/com/team16/airbnb/data/network/HomeApi.kt index d6b6905b8..42021fcf7 100644 --- a/Android/app/src/main/java/com/team16/airbnb/data/network/HomeApi.kt +++ b/Android/app/src/main/java/com/team16/airbnb/data/network/HomeApi.kt @@ -1,4 +1,6 @@ package com.team16.airbnb.data.network interface HomeApi { + + } \ No newline at end of file diff --git a/Android/app/src/main/java/com/team16/airbnb/data/network/SearchApi.kt b/Android/app/src/main/java/com/team16/airbnb/data/network/SearchApi.kt new file mode 100644 index 000000000..e7fa7fef2 --- /dev/null +++ b/Android/app/src/main/java/com/team16/airbnb/data/network/SearchApi.kt @@ -0,0 +1,12 @@ +package com.team16.airbnb.data.network + +import com.team16.airbnb.data.dto.near.NearResultResponseDto +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface SearchApi { + + @GET("main") + suspend fun getNearList(@Query("type") type: String): NearResultResponseDto +} \ No newline at end of file diff --git a/Android/app/src/main/java/com/team16/airbnb/data/repository/HomeRepository.kt b/Android/app/src/main/java/com/team16/airbnb/data/repository/HomeRepository.kt index 4b447b82f..92f1c8e43 100644 --- a/Android/app/src/main/java/com/team16/airbnb/data/repository/HomeRepository.kt +++ b/Android/app/src/main/java/com/team16/airbnb/data/repository/HomeRepository.kt @@ -13,4 +13,6 @@ class HomeRepository @Inject constructor(private val dataSource: HomeDataSource) fun getRecommendThem() = dataSource.getRecommendTheme() fun getHeroInfo() = dataSource.getHeroInfo() + + fun getNearList() = dataSource.getNearList() } \ No newline at end of file diff --git a/Android/app/src/main/java/com/team16/airbnb/di/NetworkModule.kt b/Android/app/src/main/java/com/team16/airbnb/di/NetworkModule.kt index 059875b35..17c587aed 100644 --- a/Android/app/src/main/java/com/team16/airbnb/di/NetworkModule.kt +++ b/Android/app/src/main/java/com/team16/airbnb/di/NetworkModule.kt @@ -1,10 +1,15 @@ package com.team16.airbnb.di +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory import com.team16.airbnb.data.network.HomeApi +import com.team16.airbnb.data.network.SearchApi import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.MediaType +import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.OkHttpClient import okhttp3.logging.HttpLoggingInterceptor import retrofit2.Retrofit @@ -33,7 +38,8 @@ object NetworkModule { return Retrofit.Builder() .client(okHttpClient) .baseUrl(BASE_URL) - .addConverterFactory(GsonConverterFactory.create()) + //.addConverterFactory(GsonConverterFactory.create()) + .addConverterFactory(Json.asConverterFactory("application/json".toMediaTypeOrNull()!!)) .build() } @@ -42,4 +48,10 @@ object NetworkModule { fun provideHomeApi(retrofit: Retrofit): HomeApi { return retrofit.create(HomeApi::class.java) } + + @Provides + @Singleton + fun provideSearchApi(retrofit: Retrofit): SearchApi { + return retrofit.create(SearchApi::class.java) + } } \ No newline at end of file diff --git a/Android/app/src/main/java/com/team16/airbnb/ui/home/HomeViewModel.kt b/Android/app/src/main/java/com/team16/airbnb/ui/home/HomeViewModel.kt index e322d711f..f0a666bea 100644 --- a/Android/app/src/main/java/com/team16/airbnb/ui/home/HomeViewModel.kt +++ b/Android/app/src/main/java/com/team16/airbnb/ui/home/HomeViewModel.kt @@ -2,10 +2,15 @@ package com.team16.airbnb.ui.home import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.team16.airbnb.common.ApiState +import com.team16.airbnb.data.dto.near.NearResult import com.team16.airbnb.data.model.NearInfo import com.team16.airbnb.data.repository.HomeRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import javax.inject.Inject @@ -21,6 +26,9 @@ class HomeViewModel @Inject constructor(private val repository: HomeRepository) private val _recommendTheme = MutableStateFlow>(emptyList()) val recommendTheme = _recommendTheme + private val _nearListStateFlow = MutableStateFlow>>(ApiState.Empty) + val nearListStaeFlow: StateFlow>> =_nearListStateFlow + init { getNearTripList() getRecommendTheme() @@ -45,4 +53,18 @@ class HomeViewModel @Inject constructor(private val repository: HomeRepository) } } } + + fun getNearList(){ + viewModelScope.launch { + _nearListStateFlow.value = ApiState.Loading + repository.getNearList() + .catch { e -> + _nearListStateFlow.value = ApiState.Error(e.message) + }.collect{ data -> + data.nearResult?.let { + _nearListStateFlow.value = ApiState.Success(it) + } + } + } + } } \ No newline at end of file diff --git a/Android/app/src/main/java/com/team16/airbnb/ui/search/PopularAdapter.kt b/Android/app/src/main/java/com/team16/airbnb/ui/search/PopularAdapter.kt index 3023770c0..23a59935f 100644 --- a/Android/app/src/main/java/com/team16/airbnb/ui/search/PopularAdapter.kt +++ b/Android/app/src/main/java/com/team16/airbnb/ui/search/PopularAdapter.kt @@ -5,6 +5,7 @@ import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView +import com.team16.airbnb.data.dto.near.NearResult import com.team16.airbnb.data.model.Data import com.team16.airbnb.data.model.NearInfo import com.team16.airbnb.databinding.ItemPopularBinding @@ -15,7 +16,7 @@ private const val BODY = 1 class PopularAdapter( private val startActivity: () -> Unit -): ListAdapter(PopularDiffUtil) { +): ListAdapter(PopularDiffUtil) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when(viewType) { HEADER -> PopularHeaderViewHolder(ItemPopularHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)) @@ -37,12 +38,12 @@ class PopularAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when(position) { 0 -> (holder as PopularHeaderViewHolder) - else -> (holder as PopularViewHolder).bind(getItem(position - 1) as NearInfo) + else -> (holder as PopularViewHolder).bind(getItem(position - 1)) } } inner class PopularViewHolder(private val binding: ItemPopularBinding): RecyclerView.ViewHolder(binding.root) { - fun bind(item: NearInfo) { + fun bind(item: NearResult) { binding.item = item setOnClickItem() } @@ -56,11 +57,11 @@ class PopularAdapter( class PopularHeaderViewHolder(private val binding: ItemPopularHeaderBinding): RecyclerView.ViewHolder(binding.root) - private object PopularDiffUtil: DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: Data, newItem: Data) = - oldItem.hashCode() == newItem.hashCode() + private object PopularDiffUtil: DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: NearResult, newItem: NearResult) = + oldItem.title == newItem.title - override fun areContentsTheSame(oldItem: Data, newItem: Data) = + override fun areContentsTheSame(oldItem: NearResult, newItem: NearResult) = oldItem == newItem } diff --git a/Android/app/src/main/java/com/team16/airbnb/ui/search/SearchFragment.kt b/Android/app/src/main/java/com/team16/airbnb/ui/search/SearchFragment.kt index 96eb656c2..741806523 100644 --- a/Android/app/src/main/java/com/team16/airbnb/ui/search/SearchFragment.kt +++ b/Android/app/src/main/java/com/team16/airbnb/ui/search/SearchFragment.kt @@ -8,6 +8,7 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.EditText import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -15,10 +16,13 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import com.team16.airbnb.R +import com.team16.airbnb.common.ApiState import com.team16.airbnb.databinding.FragmentSearchBinding import com.team16.airbnb.ui.MainActivity import com.team16.airbnb.ui.home.HomeViewModel import com.team16.airbnb.ui.search.detail.DetailSearchActivity +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch class SearchFragment : Fragment() { @@ -41,8 +45,13 @@ class SearchFragment : Fragment() { setBackButton() setEraseButton() - setEditText() setPopularList() + + binding.etSearch.textChangesToFlow().debounce(1000) + .onEach { + if(it.toString().isEmpty()) viewModel.getNearList() + + }.launchIn(viewLifecycleOwner.lifecycleScope) } private fun setBackButton() { @@ -58,32 +67,41 @@ class SearchFragment : Fragment() { } } - private fun setEditText() { - binding.etSearch.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { - } + fun EditText.textChangesToFlow(): Flow { + // flow 콜백 받기 + return callbackFlow { + val listener = object : TextWatcher { + override fun beforeTextChanged( + s: CharSequence?, + start: Int, + count: Int, + after: Int + ) { + Unit + } - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + // 값 내보내기 + trySend(s).isSuccess + } + override fun afterTextChanged(s: Editable?) { + Unit + } } - override fun afterTextChanged(s: Editable?) { - Log.d("AppTest", "text len : ${s.toString().length}") - if (s.toString().isNotEmpty()) { - binding.ivEraseText.visibility = View.VISIBLE - - // 검색어 기준 정보 가져오기 - - } else { - binding.ivEraseText.visibility = View.INVISIBLE - - // 검색어 x 인 경우 리스트 보여주기 + // 리스너 달아주기 + addTextChangedListener(listener) - } + // 콜백이 사라질 때 실행됨 + awaitClose { + removeTextChangedListener(listener) } - }) + }.onStart { + emit(text) // EidtText의 getText + } } private fun setPopularList() { @@ -96,14 +114,38 @@ class SearchFragment : Fragment() { binding.rvSearchList.adapter = adapter - viewLifecycleOwner.lifecycleScope.launch { + /* viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.nearTripList.collect { adapter.submitList(it) } } + }*/ + + viewLifecycleOwner.lifecycleScope.launch { + viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED){ + viewModel.nearListStaeFlow.collect{ + when(it){ + is ApiState.Loading -> { + Log.d("AppTest", "popularlist/ load data started") + } + is ApiState.Error -> { + Log.d("AppTest", "popularlist/ load data Error, ${it.message}") + } + is ApiState.Success -> { + Log.d("AppTest", "popularlist/ load data Success") + adapter.submitList(it.data) + } + is ApiState.Empty -> { + + } + } + } + } } + viewModel.getNearList() + } } \ No newline at end of file diff --git a/Android/app/src/main/res/layout/item_popular.xml b/Android/app/src/main/res/layout/item_popular.xml index f0d18ed25..a3af35a82 100644 --- a/Android/app/src/main/res/layout/item_popular.xml +++ b/Android/app/src/main/res/layout/item_popular.xml @@ -6,7 +6,7 @@ + type="com.team16.airbnb.data.dto.near.NearResult" /> @@ -25,7 +25,7 @@ app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" android:scaleType="fitXY" - imageUrl="@{item.image}"/> + imageUrl="@{item.imageUrl}"/>