Skip to content

Commit

Permalink
[AN_UI] 포켓몬 도감 목록에서 포켓몬 이름을 통해 검색 (#60)
Browse files Browse the repository at this point in the history
* feat: 포켓몬 목록의 검색 바 ui 를 추가한다

* feat: 포켓몬 리스트에 서치바를 추가한다

* feat: 포켓몬 이름으로 검색한 포켓몬 결과 목록을 구한다

* feat: 포켓몬 목록 검색 바에서 포켓몬 이름을 검색하면 그를 포함하는 포켓몬을 구한다

* feat: 포켓몬 쿼리 리스너에서 이름으로 포켓몬들을 쿼리한다

* refactor: 포켓몬 검색 쿼리를 데이터바인딩으로 쿼리한다

* feat: 가짜 포켓몬 목록 데이터 소스에서 쿼리된 포켓몬 목록을 리턴한다

* feat: 포켓몬 목록 레포지토리에서 모든 포켓몬 목록과 검색된 포켓몬 목록을 flow 로 래핑하여 리턴한다

* feat: 포켓몬 목록 데이터 리스트를 ui 모델로 변환한다

* refactor: 레포지토리의 Flow 로 감싸진 결과를 가지고 ui 에 쿼리된 포켓몬을 표시한다

* refactor: 포켓몬 목록 레포지토리의 기존 함수 제거하고 함수명을 변경한다

- 포켓몬 목록을 리턴하는 함수 제거
- 포켓몬 목록 플로우를 리턴하는 함수 이름 변경
    pokemons2 -> pokemons

* refactor: 포켓몬 목록 프래그먼트에 필요없는 애노테이션 제거

* chore(PokemonQueryListener -> PokemonQueryHandler) 이름 변경

- 새로 정해진 컨벤션에 맞춰 수정한다

* refactor: 가짜 포켓몬 목록 데이터소스의 메서드 네이밍 변경

- searchedPokemons -> pokemons

* feet: 가짜 포켓몬 목록 레포지토리의 Flow 가 아닌 결과를 리턴하는 메서드

* refactor: 포켓몬 목록 뷰모델에서 flow 로 래핑되지 않은 포케몬 목록 반환 함수를 사용한다

* refactor: PokemonListRepository 의 flow 를 리턴하는 함수 제거

* refactor(PokeMonItemClickListener -> PokemonDetailNavigateHandler) 이름 변경

* refactor: 포켓몬 목록 뷰모델 map 함수를 mapLatest 로 바꾼다
  • Loading branch information
sh1mj1 authored Jul 23, 2024
1 parent 894294e commit a576535
Show file tree
Hide file tree
Showing 15 changed files with 129 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import poke.rogue.helper.data.model.Type
class FakePokemonListDataSource {
fun pokemons(): List<Pokemon> = POKEMONS

fun pokemons(query: String): List<Pokemon> =
POKEMONS.filter { pokemon ->
pokemon.name.contains(query, ignoreCase = true)
}

companion object {
private const val FORMAT_POKEMON_IMAGE_URL =
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ class FakePokemonListRepository(
private val pokemonListDataSource: FakePokemonListDataSource,
) : PokemonListRepository {
override fun pokemons(): List<Pokemon> = pokemonListDataSource.pokemons()

override fun pokemons(query: String): List<Pokemon> = pokemonListDataSource.pokemons(query)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ import poke.rogue.helper.data.model.Pokemon

interface PokemonListRepository {
fun pokemons(): List<Pokemon>

fun pokemons(query: String): List<Pokemon>
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import poke.rogue.helper.databinding.ItemPokemonListPokemonBinding
import poke.rogue.helper.presentation.dex.model.PokemonUiModel
import poke.rogue.helper.presentation.util.view.ItemDiffCallback

class PokemonAdapter(private val onClickPokeMonItem: PokeMonItemClickListener) :
class PokemonAdapter(private val onClickPokeMonItem: PokemonDetailNavigateHandler) :
ListAdapter<PokemonUiModel, PoketmonViewHolder>(poketmonComparator) {
override fun onCreateViewHolder(
parent: ViewGroup,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package poke.rogue.helper.presentation.dex

fun interface PokemonDetailNavigateHandler {
fun navigateToPokemonDetail(pokemonId: Long)
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class PokemonListFragment :
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)

binding.viewModel = viewModel
binding.lifecycleOwner = viewLifecycleOwner

initAdapter()
initObservers()
}
Expand All @@ -53,11 +57,19 @@ class PokemonListFragment :
}

private fun initObservers() {
observeDisplayedPokemons()
observeNavigateToDetail()
}

private fun observeDisplayedPokemons() {
repeatOnStarted {
viewModel.uiState.collect { pokemonUiModels ->
pokemonAdapter.submitList(pokemonUiModels)
}
}
}

private fun observeNavigateToDetail() {
repeatOnStarted {
viewModel.navigateToDetailEvent.collect { pokemonId ->
parentFragmentManager.commit {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,16 @@ package poke.rogue.helper.presentation.dex
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import poke.rogue.helper.data.model.Pokemon
import poke.rogue.helper.data.repository.PokemonListRepository
Expand All @@ -16,24 +21,45 @@ import poke.rogue.helper.presentation.dex.model.PokemonUiModel
import poke.rogue.helper.presentation.dex.model.toUi

class PokemonListViewModel(
pokemonListRepository: PokemonListRepository,
) : ViewModel(), PokeMonItemClickListener {
private val _uiState =
MutableStateFlow(
pokemonListRepository.pokemons().map(Pokemon::toUi),
)
private val pokemonListRepository: PokemonListRepository,
) : ViewModel(), PokemonDetailNavigateHandler, PokemonQueryHandler {
private val searchQuery = MutableStateFlow("")

val uiState: StateFlow<List<PokemonUiModel>> = _uiState.asStateFlow()
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
val uiState: StateFlow<List<PokemonUiModel>> =
searchQuery
.debounce(300)
.mapLatest { query ->
queriedPokemons(query)
}
.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(5000L),
pokemonListRepository.pokemons().map(Pokemon::toUi),
)

private val _navigateToDetailEvent = MutableSharedFlow<Long>()
val navigateToDetailEvent = _navigateToDetailEvent.asSharedFlow()

override fun onClickPokemon(pokemonId: Long) {
override fun navigateToPokemonDetail(pokemonId: Long) {
viewModelScope.launch {
_navigateToDetailEvent.emit(pokemonId)
}
}

override fun onQueryName(name: String) {
viewModelScope.launch {
searchQuery.emit(name)
}
}

private fun queriedPokemons(query: String): List<PokemonUiModel> {
if (query.isEmpty()) {
return pokemonListRepository.pokemons().map(Pokemon::toUi)
}
return pokemonListRepository.pokemons(query).map(Pokemon::toUi)
}

companion object {
fun factory(pokemonListRepository: PokemonListRepository): ViewModelProvider.Factory =
BaseViewModelFactory {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package poke.rogue.helper.presentation.dex

fun interface PokemonQueryHandler {
fun onQueryName(name: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import poke.rogue.helper.presentation.dex.model.PokemonUiModel

class PoketmonViewHolder(
private val binding: ItemPokemonListPokemonBinding,
private val onClickPokeMonItem: PokeMonItemClickListener,
private val onClickPokeMonItem: PokemonDetailNavigateHandler,
) : RecyclerView.ViewHolder(binding.root) {
init {
binding.listener = onClickPokeMonItem
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package poke.rogue.helper.presentation.dex

import androidx.appcompat.widget.SearchView
import androidx.databinding.BindingAdapter

@BindingAdapter("onQueryTextChange")
fun setOnQueryTextListener(
searchView: SearchView,
onQueryTextChangeListener: PokemonQueryHandler,
) {
searchView.setOnQueryTextListener(
object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?): Boolean = false

override fun onQueryTextChange(newText: String?): Boolean {
onQueryTextChangeListener.onQueryName(newText.toString())
return true
}
},
)
}
10 changes: 10 additions & 0 deletions android/app/src/main/res/drawable/rounded_searchbar_background.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/poke_white" />
<corners android:radius="40dp" />
<padding
android:bottom="4dp"
android:left="4dp"
android:right="4dp"
android:top="4dp" />
</shape>
28 changes: 25 additions & 3 deletions android/app/src/main/res/layout/fragment_pokemon_list.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,42 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<data>

<variable
name="viewModel"
type="poke.rogue.helper.presentation.dex.PokemonListViewModel" />

</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".presentation.dex.PokemonListFragment">

<androidx.appcompat.widget.SearchView
android:id="@+id/search_bar_pokemon_list"
android:layout_width="0dp"
android:layout_height="?attr/actionBarSize"
android:layout_marginHorizontal="15dp"
android:background="@drawable/rounded_searchbar_background"
android:hint="@string/dex_list_search_pokemon_hint"
app:iconifiedByDefault="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:onQueryTextChange="@{viewModel}"
app:queryHint="@string/dex_list_search_pokemon_hint" />

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_pokemon_list"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:spanCount="3"
tools:listitem="@layout/item_pokemon_list_pokemon" />
app:layout_constraintTop_toBottomOf="@id/search_bar_pokemon_list"
tools:listitem="@layout/item_pokemon_list_pokemon"
tools:spanCount="3" />

</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
4 changes: 2 additions & 2 deletions android/app/src/main/res/layout/item_pokemon_list_pokemon.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@

<variable
name="listener"
type="poke.rogue.helper.presentation.dex.PokeMonItemClickListener" />
type="poke.rogue.helper.presentation.dex.PokemonDetailNavigateHandler" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/shape_pokemon_corner_radius"
android:onClick="@{() -> listener.onClickPokemon(pokemon.id)}"
android:onClick="@{() -> listener.navigateToPokemonDetail(pokemon.id)}"
android:paddingBottom="6dp">

<androidx.appcompat.widget.AppCompatImageView
Expand Down
4 changes: 3 additions & 1 deletion android/app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
<string name="home_menu_dex">포켓몬 도감</string>
<string name="home_menu_ability">특성 도감</string>
<string name="home_menu_tip">꿀팁</string>
<string name="pokemon_list_search_pokemon_hint">포켓몬 이름으로 검색하세요.</string>

<!-- dex-->
<string name="dex_stat_format">%03d/%03d</string>
<string name="dex_poke_name_format">%s #%d</string>
<string name="dex_list_search_pokemon_hint">포켓몬 이름으로 검색하세요.</string>

</resources>

0 comments on commit a576535

Please sign in to comment.