Skip to content

Commit

Permalink
[1.1.1/AN-FEAT] 포켓몬 상세에서 배틀 도우미로 가는 floating 버튼 추가(Revert 된 것 다시) (#413)
Browse files Browse the repository at this point in the history
* feat(activity_pokemon_detail2): first coordinatorlayout

* feat(activity_pokemon_detail2): title

* chore: PokemonDetailActivity 안 쓰는 부분 일단 주석

* feat(PokemonDetail2Activity): 샘플 코드 가져와보기

* feat: activity_pokemon_detail2.xml 타입만 해결하면 되는데

* feat: OuterEvolutionAdapter, OuterEvolutionViewHolder

* feat: 진화 프래그먼트 outer evolutions 로 변경

issue: 화면이 안나옴..

* fix: feat: 진화 프래그먼트 outer evolutions 로 , 화면 나옴

* fix: activity_pokemon_detail2 의 nestedScrollview 를 각 프래그먼트로 이동

* chore: ktlint, 사용하지 않는 파일 제거

* chore: ktlint, 사용하지 않는 파일 제거

* refactor(EvolutionsUiModel): ui 에서 사용하는 리스트로 변환

* refactor(EvolutionStageAdapter): 네이밍

* fix: activity_pokemon_detail.xml 최상단일 때만 상단이 가려진다

fix: #369 (review)

* chore: 로그 삭제

* fix(activity_pokemon_detail): progress indicator 위치 조정

* feat: activity_pokemon_detail floating 버튼

* feat: floating 버튼에 쓰일 아이콘

* feat: floating shape_red_20_fill_20_rect 텍스트 배경

* feat: 포켓몬 상세에서 배틀로 가는 팝업 아이템

* feat: PokemonDetailBattlePopupAdapter

* feat: PokemonDetailBattlePopupAdapter

* feat: floating 버튼으로 배틀 이동

* chore: ktlint

* feat: 배틀로 이동 시 로깅

* feat: viewModel loading

* [1.1.1/AN-UI, AN-REFACTOR] 포켓몬 상세 화면 스크롤 시 애니메이션 (#369)

* feat(activity_pokemon_detail2): first coordinatorlayout

* feat(activity_pokemon_detail2): title

* chore: PokemonDetailActivity 안 쓰는 부분 일단 주석

* feat(PokemonDetail2Activity): 샘플 코드 가져와보기

* feat: activity_pokemon_detail2.xml 타입만 해결하면 되는데

* feat: OuterEvolutionAdapter, OuterEvolutionViewHolder

* feat: 진화 프래그먼트 outer evolutions 로 변경

issue: 화면이 안나옴..

* fix: feat: 진화 프래그먼트 outer evolutions 로 , 화면 나옴

* fix: activity_pokemon_detail2 의 nestedScrollview 를 각 프래그먼트로 이동

* chore: ktlint, 사용하지 않는 파일 제거

* chore: ktlint, 사용하지 않는 파일 제거

* refactor(EvolutionsUiModel): ui 에서 사용하는 리스트로 변환

* refactor(EvolutionStageAdapter): 네이밍

* fix: activity_pokemon_detail.xml 최상단일 때만 상단이 가려진다

fix: #369 (review)

* chore: 로그 삭제

* fix(activity_pokemon_detail): progress indicator 위치 조정

* feat: floating button 을 배틀 아이콘 실루엣으로 설정

* refactor: battle pop up extended floating action button 으로

* refactor: battle 플로팅 액션 버튼 정적으로 넣는다

* refactor: battle floating action bar 정적으로

* refactor: FloatingButtonHandler

* refactor: 네비게이션, 기존 동적 플로팅 버튼 삭제

* refactor(PokemonDetailViewModel): eventflow

* fix(AnalyticsExtensions): analytic event

* chore; ktlintformat

* refactor: FloatingButtonHandler 를 클래스가 아닌 함수로

* feat: isExpanded save and restore

* chore: ktlint
  • Loading branch information
sh1mj1 authored Oct 23, 2024
1 parent 3293f8b commit 1aba612
Show file tree
Hide file tree
Showing 16 changed files with 310 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package poke.rogue.helper.presentation.dex

import poke.rogue.helper.analytics.AnalyticsEvent
import poke.rogue.helper.analytics.AnalyticsLogger
import poke.rogue.helper.presentation.dex.detail.NavigateToBattleEvent
import poke.rogue.helper.presentation.dex.filter.PokeFilterUiModel
import poke.rogue.helper.presentation.dex.sort.PokemonSortUiModel

Expand Down Expand Up @@ -36,3 +37,27 @@ private fun PokeFilterUiModel.toParams(): List<AnalyticsEvent.Param> {
AnalyticsEvent.Param(key = "type", value = it.name)
} + AnalyticsEvent.Param(key = "generation", value = selectedGeneration.name)
}

fun AnalyticsLogger.logPokemonDetailToBattle(event: NavigateToBattleEvent) {
val eventType = "pokemon_detail_to_battle_directly"
logEvent(
AnalyticsEvent(
type = eventType,
extras = event.toParams(),
),
)
}

private fun NavigateToBattleEvent.toParams(): List<AnalyticsEvent.Param> {
val (battleRoleValue, pokemon) =
when (this) {
is NavigateToBattleEvent.WithMyPokemon -> Pair("MyPokemon", pokemon)
is NavigateToBattleEvent.WithOpponentPokemon -> Pair("EnemyPokemon", pokemon)
}

return listOf(
AnalyticsEvent.Param(key = "battle_role", value = battleRoleValue),
AnalyticsEvent.Param(key = "pokemon_id", value = pokemon.id),
AnalyticsEvent.Param(key = "pokemon_name", value = pokemon.name),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package poke.rogue.helper.presentation.dex.detail

import poke.rogue.helper.presentation.dex.model.PokemonUiModel

sealed class NavigateToBattleEvent {
data class WithMyPokemon(val pokemon: PokemonUiModel) : NavigateToBattleEvent()

data class WithOpponentPokemon(val pokemon: PokemonUiModel) : NavigateToBattleEvent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package poke.rogue.helper.presentation.dex.detail
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import android.widget.LinearLayout.LayoutParams
import androidx.activity.viewModels
import androidx.appcompat.widget.Toolbar
Expand All @@ -21,6 +24,7 @@ import poke.rogue.helper.presentation.util.context.stringOf
import poke.rogue.helper.presentation.util.repeatOnStarted
import poke.rogue.helper.presentation.util.view.dp
import poke.rogue.helper.presentation.util.view.loadImageWithProgress
import timber.log.Timber

class PokemonDetailActivity :
ToolbarActivity<ActivityPokemonDetailBinding>(R.layout.activity_pokemon_detail) {
Expand All @@ -31,6 +35,8 @@ class PokemonDetailActivity :
private lateinit var pokemonTypesAdapter: PokemonTypesAdapter
private lateinit var pokemonDetailPagerAdapter: PokemonDetailPagerAdapter

private var isExpanded = false

override val toolbar: Toolbar
get() = binding.toolbarPokemonDetail

Expand All @@ -40,9 +46,22 @@ class PokemonDetailActivity :

binding.eventHandler = viewModel
binding.lifecycleOwner = this
binding.vm = viewModel

initAdapter()
initObservers()
initFloatingButtonsHandler()
}

override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(IS_EXPANDED, isExpanded)
super.onSaveInstanceState(outState)
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
isExpanded = savedInstanceState.getBoolean(IS_EXPANDED)
super.onRestoreInstanceState(savedInstanceState)
updateFloatingButtonsState()
}

private fun initAdapter() {
Expand All @@ -69,6 +88,13 @@ class PokemonDetailActivity :
observeNavigateToAbilityDetailEvent()
observeNavigateToBiomeDetailEvent()
observeNavigateToPokemonDetailEvent()
observeNavigateToBattleEvent()
}

private fun initFloatingButtonsHandler() {
binding.fabPokemonDetailBattle.setOnClickListener {
toggleFloatingButtons()
}
}

private fun observePokemonDetailUi() {
Expand Down Expand Up @@ -118,6 +144,24 @@ class PokemonDetailActivity :
}
}

// TODO: 예니 여기서 하면 될 Battle Activity 로 이동하면 될 것 같아요
private fun observeNavigateToBattleEvent() {
repeatOnStarted {
viewModel.navigateToBattleEvent.collect { battleEvent ->
when (battleEvent) {
is NavigateToBattleEvent.WithMyPokemon -> {
Timber.d("내 포켓몬으로 배틀 액티비티로 이동 pokemon: ${battleEvent.pokemon}")
}

is NavigateToBattleEvent.WithOpponentPokemon -> {
Timber.d("상대 포켓몬으로 배틀 액티비티로 이동 pokemon: ${battleEvent.pokemon}")
// TODO()
}
}
}
}
}

private fun bindPokemonDetail(pokemonDetail: PokemonDetailUiState.Success) {
with(binding) {
ivPokemonDetailPokemon.loadImageWithProgress(
Expand All @@ -140,8 +184,44 @@ class PokemonDetailActivity :
)
}

private fun toggleFloatingButtons() {
val rotateOpen: Animation = AnimationUtils.loadAnimation(this, R.anim.rotate_open)
val rotateClose: Animation = AnimationUtils.loadAnimation(this, R.anim.rotate_close)
val fromBottom: Animation = AnimationUtils.loadAnimation(this, R.anim.from_bottom)
val toBottom: Animation = AnimationUtils.loadAnimation(this, R.anim.to_bottom)

updateFloatingButtonsState()
with(binding) {
if (!isExpanded) {
fabPokemonDetailBattle.startAnimation(rotateOpen)
efabPokemonDetailBattleWithMine.startAnimation(fromBottom)
efabPokemonDetailBattleWithOpponent.startAnimation(fromBottom)
} else {
fabPokemonDetailBattle.startAnimation(rotateClose)
efabPokemonDetailBattleWithMine.startAnimation(toBottom)
efabPokemonDetailBattleWithOpponent.startAnimation(toBottom)
}
}

isExpanded = !isExpanded
}

private fun updateFloatingButtonsState() {
with(binding) {
if (isExpanded) {
efabPokemonDetailBattleWithMine.visibility = View.VISIBLE
efabPokemonDetailBattleWithOpponent.visibility = View.VISIBLE
} else {
efabPokemonDetailBattleWithMine.visibility = View.INVISIBLE
efabPokemonDetailBattleWithOpponent.visibility = View.INVISIBLE
}
}
}

companion object {
private const val POKEMON_ID = "pokemonId"
private const val IS_EXPANDED = "isExpanded"

val TAG: String = PokemonDetailActivity::class.java.simpleName

private val typesUiConfig =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@ interface PokemonDetailNavigateHandler {
fun navigateToHome()

fun navigateToPokemonDetail(pokemonId: String)

fun navigateToBattleWithMine()

fun navigateToBattleWithOpponent()
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ package poke.rogue.helper.presentation.dex.detail

import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
Expand All @@ -17,31 +16,37 @@ import poke.rogue.helper.analytics.analyticsLogger
import poke.rogue.helper.data.repository.DexRepository
import poke.rogue.helper.presentation.base.BaseViewModelFactory
import poke.rogue.helper.presentation.base.error.ErrorHandleViewModel
import poke.rogue.helper.presentation.dex.logPokemonDetailToBattle
import poke.rogue.helper.presentation.util.event.MutableEventFlow
import poke.rogue.helper.presentation.util.event.asEventFlow

class PokemonDetailViewModel(
private val dexRepository: DexRepository,
logger: AnalyticsLogger = analyticsLogger(),
private val logger: AnalyticsLogger = analyticsLogger(),
) :
ErrorHandleViewModel(logger),
PokemonDetailNavigateHandler {
private val _uiState: MutableStateFlow<PokemonDetailUiState> = MutableStateFlow(PokemonDetailUiState.IsLoading)
val uiState = _uiState.asStateFlow()

val isEmpty: StateFlow<Boolean> =
val isLoading: StateFlow<Boolean> =
uiState.map { it is PokemonDetailUiState.IsLoading }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), true)

private val _navigationToAbilityDetailEvent = MutableSharedFlow<String>()
val navigationToAbilityDetailEvent: SharedFlow<String> = _navigationToAbilityDetailEvent.asSharedFlow()
private val _navigationToAbilityDetailEvent = MutableEventFlow<String>()
val navigationToAbilityDetailEvent = _navigationToAbilityDetailEvent.asEventFlow()

private val _navigationToBiomeDetailEvent = MutableSharedFlow<String>()
val navigationToBiomeDetailEvent: SharedFlow<String> = _navigationToBiomeDetailEvent.asSharedFlow()
private val _navigationToBiomeDetailEvent = MutableEventFlow<String>()
val navigationToBiomeDetailEvent = _navigationToBiomeDetailEvent.asEventFlow()

private val _navigateToHomeEvent = MutableSharedFlow<Boolean>()
val navigateToHomeEvent = _navigateToHomeEvent.asSharedFlow()
private val _navigateToHomeEvent = MutableEventFlow<Boolean>()
val navigateToHomeEvent = _navigateToHomeEvent.asEventFlow()

private val _navigateToPokemonDetailEvent = MutableSharedFlow<String>()
val navigateToPokemonDetailEvent = _navigateToPokemonDetailEvent.asSharedFlow()
private val _navigateToPokemonDetailEvent = MutableEventFlow<String>()
val navigateToPokemonDetailEvent = _navigateToPokemonDetailEvent.asEventFlow()

private val _navigateToBattleEvent = MutableEventFlow<NavigateToBattleEvent>()
val navigateToBattleEvent = _navigateToBattleEvent.asEventFlow()

fun updatePokemonDetail(pokemonId: String?) {
requireNotNull(pokemonId) { "Pokemon ID must not be null" }
Expand Down Expand Up @@ -74,6 +79,27 @@ class PokemonDetailViewModel(
}
}

override fun navigateToBattleWithMine() {
viewModelScope.launch {
val navigation = NavigateToBattleEvent.WithMyPokemon(pokemonUiModel())
_navigateToBattleEvent.emit(navigation)
logger.logPokemonDetailToBattle(navigation)
}
}

override fun navigateToBattleWithOpponent() {
viewModelScope.launch {
val navigation = NavigateToBattleEvent.WithOpponentPokemon(pokemonUiModel())
_navigateToBattleEvent.emit(navigation)
logger.logPokemonDetailToBattle(navigation)
}
}

private suspend fun pokemonUiModel() =
uiState
.filterIsInstance<PokemonDetailUiState.Success>()
.first().pokemon

companion object {
fun factory(dexRepository: DexRepository): ViewModelProvider.Factory =
BaseViewModelFactory { PokemonDetailViewModel(dexRepository) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ fun Context.stringOf(

fun Context.stringArrayOf(
@ArrayRes resId: Int,
) = resources.getStringArray(resId)
): Array<String> = resources.getStringArray(resId)

fun Context.colorOf(
@ColorRes resId: Int,
Expand Down
20 changes: 20 additions & 0 deletions android/app/src/main/res/anim/from_bottom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<translate
android:duration="300"
android:fromXDelta="100%"
android:toXDelta="0%" />

<scale
android:toXScale="0.8"
android:toYScale="0.8"
android:pivotX="50%"
android:pivotY="50%" />

<alpha
android:duration="800"
android:fromAlpha="0"
android:toAlpha="1" />

</set>
11 changes: 11 additions & 0 deletions android/app/src/main/res/anim/rotate_close.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<rotate
android:duration="300"
android:fromDegrees="45"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="0" />

</set>
10 changes: 10 additions & 0 deletions android/app/src/main/res/anim/rotate_open.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">
<rotate
android:duration="300"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="45" />
</set>
23 changes: 23 additions & 0 deletions android/app/src/main/res/anim/to_bottom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true">

<translate
android:duration="300"
android:fromXDelta="0%"
android:toXDelta="100%" />
<scale
android:fromXScale="0.8"
android:fromYScale="0.8"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="0.8"
android:toYScale="0.8" />

<alpha
android:duration="150"
android:fromAlpha="1"
android:toAlpha="0" />


</set>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions android/app/src/main/res/drawable/shape_red_20_fill_20_rect.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<solid android:color="@color/poke_red_20" />

<corners
android:topLeftRadius="12dp"
android:topRightRadius="12dp"
android:bottomLeftRadius="12dp"
android:bottomRightRadius="12dp" />

</shape>

Loading

0 comments on commit 1aba612

Please sign in to comment.