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

[1.1.0/AN_FEAT] 배틀 구도 날씨 저장 #343

Merged
merged 20 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ package poke.rogue.helper.presentation.battle
import WeatherSpinnerAdapter
import android.app.Activity
import android.os.Bundle
import android.view.View
import android.widget.AdapterView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
Expand All @@ -15,14 +13,16 @@ import poke.rogue.helper.presentation.base.toolbar.ToolbarActivity
import poke.rogue.helper.presentation.battle.model.SelectionData
import poke.rogue.helper.presentation.battle.model.WeatherUiModel
import poke.rogue.helper.presentation.battle.selection.BattleSelectionActivity
import poke.rogue.helper.presentation.battle.view.itemSelectListener
import poke.rogue.helper.presentation.util.context.colorOf
import poke.rogue.helper.presentation.util.parcelable
import poke.rogue.helper.presentation.util.repeatOnStarted
import poke.rogue.helper.presentation.util.view.setImage
import timber.log.Timber

class BattleActivity : ToolbarActivity<ActivityBattleBinding>(R.layout.activity_battle) {
private val viewModel by viewModels<BattleViewModel> {
BattleViewModel.factory(DefaultBattleRepository.instance(this))
BattleViewModel.factory(DefaultBattleRepository.instance(applicationContext))
murjune marked this conversation as resolved.
Show resolved Hide resolved
}
private val weatherAdapter by lazy {
WeatherSpinnerAdapter(this)
Expand Down Expand Up @@ -58,19 +58,8 @@ class BattleActivity : ToolbarActivity<ActivityBattleBinding>(R.layout.activity_
private fun initSpinner() {
binding.spinnerWeather.adapter = weatherAdapter
binding.spinnerWeather.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>,
view: View?,
position: Int,
id: Long,
) {
val selectedWeather = parent.getItemAtPosition(position) as WeatherUiModel
viewModel.updateWeather(selectedWeather)
}

override fun onNothingSelected(parent: AdapterView<*>?) {
}
itemSelectListener<WeatherUiModel> {
viewModel.updateWeather(it)
}
}

Expand All @@ -80,6 +69,13 @@ class BattleActivity : ToolbarActivity<ActivityBattleBinding>(R.layout.activity_
weatherAdapter.updateWeathers(it)
}
}
repeatOnStarted {
viewModel.weatherPos
.collect {
Timber.tag("weatherPos").d("weatherPos: $it")
murjune marked this conversation as resolved.
Show resolved Hide resolved
binding.spinnerWeather.setSelection(it)
}
}

repeatOnStarted {
viewModel.selectedState.collect {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
Expand Down Expand Up @@ -37,6 +39,21 @@ class BattleViewModel(
private val _selectedState = MutableStateFlow(BattleSelectionsState.DEFAULT)
val selectedState = _selectedState.asStateFlow()

val weatherPos: StateFlow<Int> =
combine(
battleRepository.savedWeatherStream(),
weathers,
) { weather, weathers ->
if (weather == null || weathers.isEmpty()) return@combine null
if (weathers.any { it.id == weather.id }.not()) return@combine null
murjune marked this conversation as resolved.
Show resolved Hide resolved
val selectedWeather = weathers.first { it.id == weather.id }
// update selected weather
_selectedState.value = selectedState.value.copy(weather = BattleSelectionUiState.Selected(selectedWeather))
// return position
weathers.indexOfFirst { it.id == weather.id }
}.filterNotNull()
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), 0)

private val _navigateToSelection = MutableSharedFlow<SelectionNavigationData>()
val navigateToSelection = _navigateToSelection.asSharedFlow()

Expand Down Expand Up @@ -91,12 +108,12 @@ class BattleViewModel(
private fun initSavedSelection() {
viewModelScope.launch {
launch {
battleRepository.savedPokemon().first()?.let {
battleRepository.savedPokemonStream().first()?.let {
updateOpponentPokemon(it.toSelectionUi())
}
}
launch {
battleRepository.savedPokemonWithSkill().first()?.let { (pokemon, skill) ->
battleRepository.savedPokemonWithSkillStream().first()?.let { (pokemon, skill) ->
updateMyPokemon(pokemon.toSelectionUi(), skill.toUi())
}
}
Expand All @@ -107,6 +124,7 @@ class BattleViewModel(
viewModelScope.launch {
val selectedWeather = BattleSelectionUiState.Selected(newWeather)
_selectedState.value = selectedState.value.copy(weather = selectedWeather)
battleRepository.saveWeather(newWeather.id)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package poke.rogue.helper.presentation.battle.view

import android.view.View
import android.widget.AdapterView

inline fun <reified T> itemSelectListener(crossinline onSelected: (T) -> Unit): AdapterView.OnItemSelectedListener {
Copy link
Contributor

Choose a reason for hiding this comment

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

crossinline 으로 해야 할 이유가 있나요?

비지역 반환을 방지해야하는 게 맞나..?
crossinline 한 번도 안 해봐서 잘 모르겠슴둥

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sh1mj1 저는 no-inline 람다 블록에서 inline 람다를 호출할 필요성이 있을 때 crossinline 키워드를 사용합니다!

그리고, 다른 함수로 람다식을 argument로서 전달해줘야하는 상황이라면 noinline 을 사용합니다 ㅎㅎ


여담으로 inline 키워드를 활용하면 비지역 반환을 사용할 수 있긴 합니다만, 비지역 반환을 선호하지 않습니다.
사용할 일도 크게 없을 뿐 더러, 무분별하게 사용할 경우 코드를 읽는 사람 입장에서 헷갈리기 때문입니다.

가끔 코테 풀때 repeat{} 내부에서 비지역 반환을 사용한 경우 말고 사용한 적이 없네요 😉

심지는 어떨 때 비지역 반환을 사용하시는지 궁금하네요! 😁

var isSpinnerInitialized = false
return object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long,
) {
if (isSpinnerInitialized) {
val selectedData = parent?.getItemAtPosition(position)
val castedData =
requireNotNull(selectedData as? T) { "Selected data is not a ${T::class.simpleName}" }
onSelected(castedData)
} else {
isSpinnerInitialized = true
murjune marked this conversation as resolved.
Show resolved Hide resolved
}
}

override fun onNothingSelected(parent: AdapterView<*>?) {}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ class LocalBattleDataSource(private val battleDataStore: BattleDataStore) {
battleDataStore.savePokemon(pokemonId)
}

fun pokemonWithSkill(): Flow<PokemonWithSkillIds?> = battleDataStore.pokemonWithSkillId().map { it?.toData() }
suspend fun saveWeather(weatherId: String) {
battleDataStore.saveWeather(weatherId)
}

fun weatherIdStream(): Flow<String?> = battleDataStore.weatherId()

fun pokemonWithSkillStream(): Flow<PokemonWithSkillIds?> = battleDataStore.pokemonWithSkillId().map { it?.toData() }

fun pokemonId(): Flow<String?> = battleDataStore.pokemonId()
fun pokemonIdStream(): Flow<String?> = battleDataStore.pokemonId()
murjune marked this conversation as resolved.
Show resolved Hide resolved

companion object {
private var instance: LocalBattleDataSource? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ interface BattleRepository {
skillId: String,
)

suspend fun savedPokemon(): Flow<Pokemon?>
suspend fun saveWeather(weatherId: String)

suspend fun savedPokemonWithSkill(): Flow<PokemonWithSkill?>
fun savedWeatherStream(): Flow<Weather?>
murjune marked this conversation as resolved.
Show resolved Hide resolved

fun savedPokemonStream(): Flow<Pokemon?>

fun savedPokemonWithSkillStream(): Flow<PokemonWithSkill?>
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,29 +40,37 @@ class DefaultBattleRepository(
opponentPokemonId = opponentPokemonId,
)

override suspend fun savePokemon(pokemonId: String) {
localBattleDataSource.savePokemon(pokemonId)
}
override suspend fun savePokemon(pokemonId: String) = localBattleDataSource.savePokemon(pokemonId)
Copy link
Contributor

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.

어,,? 그러게요

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아 예니가 아래에 = 을 사용했길래 통일성 맞추려고 사용했던 것 같습니다!


override suspend fun savePokemonWithSkill(
pokemonId: String,
skillId: String,
) {
localBattleDataSource.savePokemonWithSkill(pokemonId, skillId)
}
) = localBattleDataSource.savePokemonWithSkill(pokemonId, skillId)

override suspend fun savedPokemon(): Flow<Pokemon?> =
localBattleDataSource.pokemonId().map { it?.let { pokemonRepository.pokemon(it) } }
override fun savedPokemonStream(): Flow<Pokemon?> =
localBattleDataSource.pokemonIdStream().map {
it?.let { pokemonRepository.pokemon(it) }
}

override suspend fun savedPokemonWithSkill(): Flow<PokemonWithSkill?> =
localBattleDataSource.pokemonWithSkill().map {
override fun savedPokemonWithSkillStream(): Flow<PokemonWithSkill?> =
localBattleDataSource.pokemonWithSkillStream().map {
it?.let { pokemonWithSkill ->
val pokemon = pokemonRepository.pokemon(pokemonWithSkill.pokemonId)
val skill = skill(pokemon.dexNumber, pokemonWithSkill.skillId)
PokemonWithSkill(pokemon, skill)
}
}

override suspend fun saveWeather(weatherId: String) = localBattleDataSource.saveWeather(weatherId)

override fun savedWeatherStream(): Flow<Weather?> =
localBattleDataSource.weatherIdStream().map {
if (it == null) {
return@map null
}
weathers().find { weather -> weather.id == it }
murjune marked this conversation as resolved.
Show resolved Hide resolved
}

private suspend fun skill(
dexNumber: Long,
skillId: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

package poke.rogue.helper.local.datastore

import android.content.Context
Expand Down Expand Up @@ -31,6 +30,17 @@ class BattleDataStore(private val context: Context) {
}
}

suspend fun saveWeather(weatherId: String) {
context.dataStore.edit {
it[WEATHER_SELECTION_KEY] = weatherId
}
}

fun weatherId(): Flow<String?> =
context.dataStore.data.map { preferences ->
preferences[WEATHER_SELECTION_KEY]
}

fun pokemonWithSkillId(): Flow<SavedPokemonWithSkill?> =
context.dataStore.data.map { preference ->
val pokemonId = preference[PAIR_POKEMON_SELECTION_KEY]
Expand All @@ -49,6 +59,7 @@ class BattleDataStore(private val context: Context) {

private companion object {
const val BATTLE_PREFERENCE_NAME = "battle"
val WEATHER_SELECTION_KEY = stringPreferencesKey("weather_selection")
val PAIR_POKEMON_SELECTION_KEY = stringPreferencesKey("pair_pokemon_selection")
val PAIR_SKILL_SELECTION_KEY = stringPreferencesKey("pair_skill_selection")
val SINGLE_POKEMON_SELECTION_KEY = stringPreferencesKey("single_pokemon_selection")
Expand Down
Loading