diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 6ac73280..64197610 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -17,6 +17,9 @@ android:supportsRtl="true" android:theme="@style/Theme.PokeRogueHelper" tools:targetApi="31"> + diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/BattleActivity.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/BattleActivity.kt index 59b55320..ced0411f 100644 --- a/android/app/src/main/java/poke/rogue/helper/presentation/battle/BattleActivity.kt +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/BattleActivity.kt @@ -10,6 +10,8 @@ import poke.rogue.helper.R import poke.rogue.helper.databinding.ActivityBattleBinding import poke.rogue.helper.presentation.base.toolbar.ToolbarActivity import poke.rogue.helper.presentation.battle.model.WeatherUiModel +import poke.rogue.helper.presentation.battle.selection.BattleSelectionActivity +import poke.rogue.helper.presentation.util.context.startActivity import poke.rogue.helper.presentation.util.repeatOnStarted class BattleActivity : ToolbarActivity(R.layout.activity_battle) { @@ -30,6 +32,10 @@ class BattleActivity : ToolbarActivity(R.layout.activity_ private fun initView() { binding.vm = viewModel binding.lifecycleOwner = this + + binding.ivMinePokemon.setOnClickListener { + startActivity { } + } } private fun initSpinner() { diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/BattleSelectionUiState.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/BattleSelectionUiState.kt new file mode 100644 index 00000000..2c8ccca3 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/BattleSelectionUiState.kt @@ -0,0 +1,7 @@ +package poke.rogue.helper.presentation.battle + +sealed interface BattleSelectionUiState { + data class Selected(val selected: T) : BattleSelectionUiState + + data object Empty : BattleSelectionUiState +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/model/PokemonSelectionUiModel.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/model/PokemonSelectionUiModel.kt new file mode 100644 index 00000000..9acda68a --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/model/PokemonSelectionUiModel.kt @@ -0,0 +1,62 @@ +package poke.rogue.helper.presentation.battle.model + +import poke.rogue.helper.presentation.type.model.TypeUiModel + +data class PokemonSelectionUiModel( + val id: String, + val dexNumber: Long, + val name: String, + val frontImageUrl: String, + val backImageUrl: String, + val types: List, +) { + companion object { + val DUMMY = + listOf( + PokemonSelectionUiModel( + id = "Bulbasaur", + dexNumber = 1, + name = "이상해씨", + frontImageUrl = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/1.png", + backImageUrl = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/1.png", + types = + listOf( + TypeUiModel.GRASS, + TypeUiModel.POISON, + ), + ), + PokemonSelectionUiModel( + id = "Charmander", + dexNumber = 4, + name = "파이리", + frontImageUrl = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/4.png", + backImageUrl = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/4.png", + types = listOf(TypeUiModel.FIRE), + ), + PokemonSelectionUiModel( + id = "Squirtle", + dexNumber = 7, + name = "꼬북이", + frontImageUrl = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/7.png", + backImageUrl = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/7.png", + types = listOf(TypeUiModel.WATER), + ), + PokemonSelectionUiModel( + id = "Pikachu", + dexNumber = 25, + name = "피카츄", + frontImageUrl = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/25.png", + backImageUrl = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/25.png", + types = listOf(TypeUiModel.ELECTRIC), + ), + PokemonSelectionUiModel( + id = "Charizard", + dexNumber = 6, + name = "리자몽", + frontImageUrl = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/6.png", + backImageUrl = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/6.png", + types = listOf(TypeUiModel.FIRE, TypeUiModel.FLYING), + ), + ) + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/model/SkillSelectionUiModel.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/model/SkillSelectionUiModel.kt new file mode 100644 index 00000000..88dcccf2 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/model/SkillSelectionUiModel.kt @@ -0,0 +1,46 @@ +package poke.rogue.helper.presentation.battle.model + +import poke.rogue.helper.presentation.type.model.TypeUiModel + +data class SkillSelectionUiModel( + val id: String, + val name: String, + val type: TypeUiModel, + val category: String, +) { + companion object { + val DUMMY = + listOf( + SkillSelectionUiModel( + id = "1", + name = "몸통박치기", + type = TypeUiModel.NORMAL, + category = "Physical Attack", + ), + SkillSelectionUiModel( + id = "2", + name = "울음소리", + type = TypeUiModel.NORMAL, + category = "Status Change", + ), + SkillSelectionUiModel( + id = "3", + name = "덩굴채찍", + type = TypeUiModel.GRASS, + category = "Physical Attack", + ), + SkillSelectionUiModel( + id = "4", + name = "성장", + type = TypeUiModel.NORMAL, + category = "Status Change", + ), + SkillSelectionUiModel( + id = "5", + name = "씨뿌리기", + type = TypeUiModel.GRASS, + category = "Status Change", + ), + ) + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionActivity.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionActivity.kt new file mode 100644 index 00000000..0db0c6f4 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionActivity.kt @@ -0,0 +1,57 @@ +package poke.rogue.helper.presentation.battle.selection + +import android.os.Bundle +import androidx.activity.viewModels +import androidx.appcompat.widget.Toolbar +import poke.rogue.helper.R +import poke.rogue.helper.databinding.ActivityBattleSelectionBinding +import poke.rogue.helper.presentation.base.error.ErrorHandleActivity +import poke.rogue.helper.presentation.base.error.ErrorHandleViewModel +import poke.rogue.helper.presentation.battle.BattleSelectionUiState +import poke.rogue.helper.presentation.util.repeatOnStarted +import poke.rogue.helper.presentation.util.view.setImage + +class BattleSelectionActivity : + ErrorHandleActivity(R.layout.activity_battle_selection) { + private val viewModel by viewModels() + private val selectionPagerAdapter: BattleSelectionPagerAdapter by lazy { + BattleSelectionPagerAdapter(this) + } + + override val errorViewModel: ErrorHandleViewModel + get() = viewModel + + override val toolbar: Toolbar + get() = binding.toolbarBattleSelection + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initViews() + initObserver() + } + + private fun initViews() { + binding.lifecycleOwner = this + binding.vm = viewModel + binding.pagerBattleSelection.adapter = selectionPagerAdapter + binding.pagerBattleSelection.isUserInputEnabled = false + } + + private fun initObserver() { + repeatOnStarted { + viewModel.selectedPokemon.collect { selectionState -> + if (selectionState is BattleSelectionUiState.Selected) { + val selected = selectionState.selected + binding.ivPokemon.setImage(selected.frontImageUrl) + binding.toolbarBattleSelection.title = selected.name + } + } + } + + repeatOnStarted { + viewModel.currentStep.collect { + binding.pagerBattleSelection.currentItem = it.ordinal + } + } + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionHandler.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionHandler.kt new file mode 100644 index 00000000..7cb1f879 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionHandler.kt @@ -0,0 +1,10 @@ +package poke.rogue.helper.presentation.battle.selection + +import poke.rogue.helper.presentation.battle.model.PokemonSelectionUiModel +import poke.rogue.helper.presentation.battle.model.SkillSelectionUiModel + +interface BattleSelectionHandler { + fun selectPokemon(pokemon: PokemonSelectionUiModel) + + fun selectSkill(skill: SkillSelectionUiModel) +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionPagerAdapter.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionPagerAdapter.kt new file mode 100644 index 00000000..a3ade56d --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionPagerAdapter.kt @@ -0,0 +1,20 @@ +package poke.rogue.helper.presentation.battle.selection + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import poke.rogue.helper.presentation.battle.selection.pokemon.PokemonSelectionFragment +import poke.rogue.helper.presentation.battle.selection.skill.SkillSelectionFragment + +class BattleSelectionPagerAdapter(fragmentActivity: FragmentActivity) : + FragmentStateAdapter(fragmentActivity) { + private val pages = SelectionStep.entries + + override fun getItemCount(): Int = pages.size + + override fun createFragment(position: Int): Fragment = + when (pages[position]) { + SelectionStep.POKEMON_SELECTION -> PokemonSelectionFragment() + SelectionStep.SKILL_SELECTION -> SkillSelectionFragment() + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionViewModel.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionViewModel.kt new file mode 100644 index 00000000..4bea52eb --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/BattleSelectionViewModel.kt @@ -0,0 +1,58 @@ +package poke.rogue.helper.presentation.battle.selection + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import poke.rogue.helper.analytics.AnalyticsLogger +import poke.rogue.helper.analytics.analyticsLogger +import poke.rogue.helper.presentation.base.error.ErrorHandleViewModel +import poke.rogue.helper.presentation.battle.BattleSelectionUiState +import poke.rogue.helper.presentation.battle.model.PokemonSelectionUiModel +import poke.rogue.helper.presentation.battle.model.SkillSelectionUiModel + +class BattleSelectionViewModel( + logger: AnalyticsLogger = analyticsLogger(), +) : ErrorHandleViewModel(logger), BattleSelectionHandler, NavigationHandler { + private val _pokemons = MutableStateFlow(PokemonSelectionUiModel.DUMMY) + val pokemons = _pokemons.asStateFlow() + + private val _selectedPokemon = + MutableStateFlow>(BattleSelectionUiState.Empty) + val selectedPokemon = _selectedPokemon.asStateFlow() + + private val _skills = MutableStateFlow(SkillSelectionUiModel.DUMMY) + val skills = _skills.asStateFlow() + + private val _selectedSkill = + MutableStateFlow>(BattleSelectionUiState.Empty) + val selectedSkill = _selectedSkill.asStateFlow() + + private val _currentStep = MutableStateFlow(SelectionStep.POKEMON_SELECTION) + val currentStep: StateFlow = _currentStep.asStateFlow() + + override fun selectPokemon(pokemon: PokemonSelectionUiModel) { + _selectedPokemon.value = BattleSelectionUiState.Selected(pokemon) + } + + override fun selectSkill(skill: SkillSelectionUiModel) { + _selectedSkill.value = BattleSelectionUiState.Selected(skill) + } + + override fun navigateToNextPage() { + val nextPage = + when (currentStep.value) { + SelectionStep.POKEMON_SELECTION -> SelectionStep.SKILL_SELECTION + SelectionStep.SKILL_SELECTION -> return // TODO : Complete Selection + } + _currentStep.value = nextPage + } + + override fun navigateToPrevPage() { + val prevPage = + when (currentStep.value) { + SelectionStep.POKEMON_SELECTION -> return + SelectionStep.SKILL_SELECTION -> SelectionStep.POKEMON_SELECTION + } + _currentStep.value = prevPage + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/NavigationHandler.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/NavigationHandler.kt new file mode 100644 index 00000000..94e19706 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/NavigationHandler.kt @@ -0,0 +1,7 @@ +package poke.rogue.helper.presentation.battle.selection + +interface NavigationHandler { + fun navigateToNextPage() + + fun navigateToPrevPage() +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/SelectionBindingAdapter.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/SelectionBindingAdapter.kt new file mode 100644 index 00000000..19c55d79 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/SelectionBindingAdapter.kt @@ -0,0 +1,19 @@ +package poke.rogue.helper.presentation.battle.selection + +import android.view.View +import androidx.databinding.BindingAdapter +import poke.rogue.helper.R + +@BindingAdapter("invisible") +fun View.setInvisible(invisible: Boolean) { + visibility = if (invisible) View.INVISIBLE else View.VISIBLE +} + +@BindingAdapter("selectedBackground") +fun View.setBackground(isSelected: Boolean) { + if (isSelected) { + setBackgroundResource(R.drawable.bg_battle_selected_border) + } else { + setBackgroundResource(R.drawable.bg_battle_default) + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/SelectionStep.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/SelectionStep.kt new file mode 100644 index 00000000..821bf185 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/SelectionStep.kt @@ -0,0 +1,15 @@ +package poke.rogue.helper.presentation.battle.selection + +enum class SelectionStep(val buttonText: String) { + POKEMON_SELECTION("다음"), + SKILL_SELECTION("선택 완료"), + ; + + fun isLastStep(hasSkillSelection: Boolean): Boolean { + return if (hasSkillSelection) { + this == SKILL_SELECTION + } else { + this == POKEMON_SELECTION + } + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/pokemon/PokemonSelectionAdapter.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/pokemon/PokemonSelectionAdapter.kt new file mode 100644 index 00000000..d59ef184 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/pokemon/PokemonSelectionAdapter.kt @@ -0,0 +1,66 @@ +package poke.rogue.helper.presentation.battle.selection.pokemon + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import poke.rogue.helper.databinding.ItemBattlePokemonSelectionBinding +import poke.rogue.helper.presentation.battle.model.PokemonSelectionUiModel +import poke.rogue.helper.presentation.battle.selection.BattleSelectionHandler +import poke.rogue.helper.presentation.util.view.ItemDiffCallback + +class PokemonSelectionAdapter( + private val selectionHandler: BattleSelectionHandler, +) : ListAdapter(pokemonComparator) { + private var selectedPokemonId: String? = null + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): PokemonSelectionViewHolder = + PokemonSelectionViewHolder( + ItemBattlePokemonSelectionBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ), + selectionHandler, + ) + + override fun onBindViewHolder( + viewHolder: PokemonSelectionViewHolder, + position: Int, + ) { + val pokemon = getItem(position) + val isSelected = pokemon.id == selectedPokemonId + viewHolder.bind(pokemon, isSelected) + } + + fun updateSelectedPokemon(selectedId: String) { + var previousSelectedPosition: Int? = null + var newSelectedPosition: Int? = null + + currentList.forEachIndexed { index, pokemon -> + if (pokemon.id == selectedPokemonId) { + previousSelectedPosition = index + } + if (pokemon.id == selectedId) { + newSelectedPosition = index + } + if (previousSelectedPosition != null && newSelectedPosition != null) { + return@forEachIndexed + } + } + + selectedPokemonId = selectedId + previousSelectedPosition?.let { notifyItemChanged(it) } + newSelectedPosition?.let { notifyItemChanged(it) } + } + + companion object { + private val pokemonComparator = + ItemDiffCallback( + onItemsTheSame = { oldItem, newItem -> oldItem.id == newItem.id }, + onContentsTheSame = { oldItem, newItem -> oldItem == newItem }, + ) + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/pokemon/PokemonSelectionFragment.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/pokemon/PokemonSelectionFragment.kt new file mode 100644 index 00000000..47a88cb9 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/pokemon/PokemonSelectionFragment.kt @@ -0,0 +1,64 @@ +package poke.rogue.helper.presentation.battle.selection.pokemon + +import android.os.Bundle +import android.view.View +import androidx.appcompat.widget.Toolbar +import androidx.fragment.app.activityViewModels +import poke.rogue.helper.R +import poke.rogue.helper.databinding.FragmentPokemonSelectionBinding +import poke.rogue.helper.presentation.base.error.ErrorHandleFragment +import poke.rogue.helper.presentation.base.error.ErrorHandleViewModel +import poke.rogue.helper.presentation.battle.BattleSelectionUiState +import poke.rogue.helper.presentation.battle.selection.BattleSelectionViewModel +import poke.rogue.helper.presentation.util.repeatOnStarted +import poke.rogue.helper.presentation.util.view.LinearSpacingItemDecoration +import poke.rogue.helper.presentation.util.view.dp + +class PokemonSelectionFragment : + ErrorHandleFragment(R.layout.fragment_pokemon_selection) { + private val sharedViewModel: BattleSelectionViewModel by activityViewModels() + private val pokemonAdapter: PokemonSelectionAdapter by lazy { + PokemonSelectionAdapter(sharedViewModel) + } + + override val errorViewModel: ErrorHandleViewModel + get() = sharedViewModel + override val toolbar: Toolbar? + get() = null + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + initViews() + initObserver() + binding.lifecycleOwner = viewLifecycleOwner + } + + private fun initViews() { + with(binding.rvPokemons) { + adapter = pokemonAdapter + addItemDecoration( + LinearSpacingItemDecoration(spacing = 4.dp, false), + ) + } + } + + private fun initObserver() { + repeatOnStarted { + sharedViewModel.pokemons.collect { + pokemonAdapter.submitList(it) + } + } + + repeatOnStarted { + sharedViewModel.selectedPokemon.collect { selectionState -> + if (selectionState is BattleSelectionUiState.Selected) { + val selected = selectionState.selected + pokemonAdapter.updateSelectedPokemon(selected.id) + } + } + } + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/pokemon/PokemonSelectionViewHolder.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/pokemon/PokemonSelectionViewHolder.kt new file mode 100644 index 00000000..7b399eb5 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/pokemon/PokemonSelectionViewHolder.kt @@ -0,0 +1,29 @@ +package poke.rogue.helper.presentation.battle.selection.pokemon + +import androidx.recyclerview.widget.RecyclerView +import poke.rogue.helper.databinding.ItemBattlePokemonSelectionBinding +import poke.rogue.helper.presentation.battle.model.PokemonSelectionUiModel +import poke.rogue.helper.presentation.battle.selection.BattleSelectionHandler +import poke.rogue.helper.presentation.biome.BiomeTypesAdapter +import poke.rogue.helper.presentation.util.view.dp + +class PokemonSelectionViewHolder( + private val binding: ItemBattlePokemonSelectionBinding, + private val selectionHandler: BattleSelectionHandler, +) : RecyclerView.ViewHolder(binding.root) { + fun bind( + pokemonSelectionUiModel: PokemonSelectionUiModel, + isSelected: Boolean, + ) { + binding.pokemon = pokemonSelectionUiModel + binding.isSelected = isSelected + binding.selectionHandler = selectionHandler + + val typeAdapter = BiomeTypesAdapter(context = binding.root.context, binding.flexboxTypes) + typeAdapter.addTypes( + types = pokemonSelectionUiModel.types, + spacingBetweenTypes = 8.dp, + iconSize = 18.dp, + ) + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/skill/SkillSelectionAdapter.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/skill/SkillSelectionAdapter.kt new file mode 100644 index 00000000..56262071 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/skill/SkillSelectionAdapter.kt @@ -0,0 +1,65 @@ +package poke.rogue.helper.presentation.battle.selection.skill + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import poke.rogue.helper.databinding.ItemBattleSkillSelectionBinding +import poke.rogue.helper.presentation.battle.model.SkillSelectionUiModel +import poke.rogue.helper.presentation.battle.selection.BattleSelectionHandler +import poke.rogue.helper.presentation.util.view.ItemDiffCallback + +class SkillSelectionAdapter(private val selectionHandler: BattleSelectionHandler) : + ListAdapter(skillComparator) { + private var selectedSkillId: String? = null + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int, + ): SkillSelectionViewHolder = + SkillSelectionViewHolder( + ItemBattleSkillSelectionBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false, + ), + selectionHandler, + ) + + override fun onBindViewHolder( + viewHolder: SkillSelectionViewHolder, + position: Int, + ) { + val skill = getItem(position) + val isSelected = skill.id == selectedSkillId + viewHolder.bind(skill, isSelected) + } + + fun updateSelectedSkill(selectedId: String) { + var previousSelectedPosition: Int? = null + var newSelectedPosition: Int? = null + + currentList.forEachIndexed { index, skill -> + if (skill.id == selectedSkillId) { + previousSelectedPosition = index + } + if (skill.id == selectedId) { + newSelectedPosition = index + } + if (previousSelectedPosition != null && newSelectedPosition != null) { + return@forEachIndexed + } + } + + selectedSkillId = selectedId + previousSelectedPosition?.let { notifyItemChanged(it) } + newSelectedPosition?.let { notifyItemChanged(it) } + } + + companion object { + private val skillComparator = + ItemDiffCallback( + onItemsTheSame = { oldItem, newItem -> oldItem.id == newItem.id }, + onContentsTheSame = { oldItem, newItem -> oldItem == newItem }, + ) + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/skill/SkillSelectionFragment.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/skill/SkillSelectionFragment.kt new file mode 100644 index 00000000..3591f1a5 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/skill/SkillSelectionFragment.kt @@ -0,0 +1,65 @@ +package poke.rogue.helper.presentation.battle.selection.skill + +import android.os.Bundle +import android.view.View +import androidx.appcompat.widget.Toolbar +import androidx.fragment.app.activityViewModels +import poke.rogue.helper.R +import poke.rogue.helper.databinding.FragmentSkillSelectionBinding +import poke.rogue.helper.presentation.base.error.ErrorHandleFragment +import poke.rogue.helper.presentation.base.error.ErrorHandleViewModel +import poke.rogue.helper.presentation.battle.BattleSelectionUiState +import poke.rogue.helper.presentation.battle.model.SkillSelectionUiModel +import poke.rogue.helper.presentation.battle.selection.BattleSelectionViewModel +import poke.rogue.helper.presentation.util.repeatOnStarted +import poke.rogue.helper.presentation.util.view.LinearSpacingItemDecoration +import poke.rogue.helper.presentation.util.view.dp + +class SkillSelectionFragment : + ErrorHandleFragment(R.layout.fragment_skill_selection) { + private val sharedViewModel: BattleSelectionViewModel by activityViewModels() + private val skillAdapter: SkillSelectionAdapter by lazy { + SkillSelectionAdapter(sharedViewModel) + } + + override val errorViewModel: ErrorHandleViewModel + get() = sharedViewModel + override val toolbar: Toolbar? + get() = null + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { + super.onViewCreated(view, savedInstanceState) + initViews() + initObserver() + skillAdapter.submitList(SkillSelectionUiModel.DUMMY) + } + + private fun initViews() { + with(binding.rvSkills) { + adapter = skillAdapter + addItemDecoration( + LinearSpacingItemDecoration(spacing = 4.dp, false), + ) + } + } + + private fun initObserver() { + repeatOnStarted { + sharedViewModel.skills.collect { + skillAdapter.submitList(it) + } + } + + repeatOnStarted { + sharedViewModel.selectedSkill.collect { selectionState -> + if (selectionState is BattleSelectionUiState.Selected) { + val selected = selectionState.selected + skillAdapter.updateSelectedSkill(selected.id) + } + } + } + } +} diff --git a/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/skill/SkillSelectionViewHolder.kt b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/skill/SkillSelectionViewHolder.kt new file mode 100644 index 00000000..87635ad4 --- /dev/null +++ b/android/app/src/main/java/poke/rogue/helper/presentation/battle/selection/skill/SkillSelectionViewHolder.kt @@ -0,0 +1,20 @@ +package poke.rogue.helper.presentation.battle.selection.skill + +import androidx.recyclerview.widget.RecyclerView +import poke.rogue.helper.databinding.ItemBattleSkillSelectionBinding +import poke.rogue.helper.presentation.battle.model.SkillSelectionUiModel +import poke.rogue.helper.presentation.battle.selection.BattleSelectionHandler + +class SkillSelectionViewHolder( + private val binding: ItemBattleSkillSelectionBinding, + private val selectionHandler: BattleSelectionHandler, +) : RecyclerView.ViewHolder(binding.root) { + fun bind( + skillSelectionUiModel: SkillSelectionUiModel, + isSelected: Boolean, + ) { + binding.skill = skillSelectionUiModel + binding.isSelected = isSelected + binding.selectionHandler = selectionHandler + } +} diff --git a/android/app/src/main/res/drawable/bg_battle_selection.xml b/android/app/src/main/res/drawable/bg_battle_default.xml similarity index 100% rename from android/app/src/main/res/drawable/bg_battle_selection.xml rename to android/app/src/main/res/drawable/bg_battle_default.xml diff --git a/android/app/src/main/res/drawable/bg_battle_selected_border.xml b/android/app/src/main/res/drawable/bg_battle_selected_border.xml new file mode 100644 index 00000000..f8be55a2 --- /dev/null +++ b/android/app/src/main/res/drawable/bg_battle_selected_border.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/android/app/src/main/res/drawable/bg_before_button.xml b/android/app/src/main/res/drawable/bg_before_button.xml new file mode 100644 index 00000000..67c5e19c --- /dev/null +++ b/android/app/src/main/res/drawable/bg_before_button.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/android/app/src/main/res/drawable/bg_button_enabled_selector.xml b/android/app/src/main/res/drawable/bg_button_enabled_selector.xml new file mode 100644 index 00000000..93a5bcc0 --- /dev/null +++ b/android/app/src/main/res/drawable/bg_button_enabled_selector.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/drawable/bg_search_view.xml b/android/app/src/main/res/drawable/bg_search_view.xml index f6a1cfdb..46889101 100644 --- a/android/app/src/main/res/drawable/bg_search_view.xml +++ b/android/app/src/main/res/drawable/bg_search_view.xml @@ -1,7 +1,7 @@ - + diff --git a/android/app/src/main/res/drawable/icon_arrow_right_pixel.png b/android/app/src/main/res/drawable/icon_arrow_right_pixel.png new file mode 100644 index 00000000..940fa98c Binary files /dev/null and b/android/app/src/main/res/drawable/icon_arrow_right_pixel.png differ diff --git a/android/app/src/main/res/layout/activity_battle.xml b/android/app/src/main/res/layout/activity_battle.xml index d98ab243..0ccd83e1 100644 --- a/android/app/src/main/res/layout/activity_battle.xml +++ b/android/app/src/main/res/layout/activity_battle.xml @@ -157,7 +157,7 @@ style="@style/TextAppearance.Poke.TitleBold" android:layout_width="0dp" android:layout_height="wrap_content" - android:background="@drawable/bg_battle_selection" + android:background="@drawable/bg_battle_default" android:paddingHorizontal="4dp" android:paddingVertical="8dp" android:text="@string/battle_move_selection" @@ -183,7 +183,7 @@ android:layout_width="2dp" android:layout_height="0dp" android:layout_margin="24dp" - android:background="@drawable/bg_battle_selection" + android:background="@drawable/bg_battle_default" app:layout_constraintBottom_toBottomOf="@id/gl_bottom" app:layout_constraintEnd_toEndOf="@id/gl_end" app:layout_constraintStart_toStartOf="@id/gl_start" diff --git a/android/app/src/main/res/layout/activity_battle_selection.xml b/android/app/src/main/res/layout/activity_battle_selection.xml new file mode 100644 index 00000000..23babf3d --- /dev/null +++ b/android/app/src/main/res/layout/activity_battle_selection.xml @@ -0,0 +1,116 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/fragment_pokemon_selection.xml b/android/app/src/main/res/layout/fragment_pokemon_selection.xml new file mode 100644 index 00000000..86c2fc40 --- /dev/null +++ b/android/app/src/main/res/layout/fragment_pokemon_selection.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/fragment_skill_selection.xml b/android/app/src/main/res/layout/fragment_skill_selection.xml new file mode 100644 index 00000000..7ac09d40 --- /dev/null +++ b/android/app/src/main/res/layout/fragment_skill_selection.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/item_battle_pokemon_selection.xml b/android/app/src/main/res/layout/item_battle_pokemon_selection.xml new file mode 100644 index 00000000..57c08717 --- /dev/null +++ b/android/app/src/main/res/layout/item_battle_pokemon_selection.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/layout/item_battle_skill_selection.xml b/android/app/src/main/res/layout/item_battle_skill_selection.xml new file mode 100644 index 00000000..8ff2db60 --- /dev/null +++ b/android/app/src/main/res/layout/item_battle_skill_selection.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index cee8c27c..c16cb314 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -78,6 +78,10 @@ 내 포켓몬을 선택해주세요! 상대 포켓몬을 선택해주세요! 기술을 선택해주세요! + 선택 완료 + 기술을 검색하세요 + 포켓몬을 검색하세요 + 포켓몬을 선택하세요 알수없는 문제가 발생했습니다..