-
Notifications
You must be signed in to change notification settings - Fork 4
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
feat: 모험 기록 상세 페이지 뷰 구현 #494
The head ref may contain hidden characters: "feat/#489_\uBAA8\uD5D8_\uAE30\uB85D_\uC0C1\uC138_\uD398\uC774\uC9C0_\uBDF0_\uAD6C\uD604"
Changes from all commits
520a766
85b8ee8
a8912df
04cc1a9
82c3344
81c7c24
96c762f
f05ca63
aa544f9
3e30f2b
33f7425
22781e5
97e8478
f81a799
4c5d696
01c0d09
4df8471
94fced6
0c29e97
9b2c5fd
c7e7589
0e9a035
3fac1bc
6d0c438
5d6abee
8c8c88c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.now.naaga.data.mapper | ||
|
||
import com.now.domain.model.letter.OpenLetter | ||
import com.now.naaga.data.remote.dto.OpenLetterDto | ||
|
||
fun OpenLetterDto.toDomain(): OpenLetter { | ||
return OpenLetter( | ||
id = id, | ||
player = player.toDomain(), | ||
coordinate = coordinateDto.toDomain(), | ||
message = message, | ||
registerDate = registerDate, | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package com.now.naaga.presentation.adventuredetail | ||
|
||
import android.content.Context | ||
import android.content.Intent | ||
import android.os.Bundle | ||
import androidx.activity.viewModels | ||
import androidx.appcompat.app.AppCompatActivity | ||
import com.bumptech.glide.Glide | ||
import com.google.android.material.tabs.TabLayoutMediator | ||
import com.now.domain.model.AdventureResult | ||
import com.now.naaga.R | ||
import com.now.naaga.data.firebase.analytics.AnalyticsDelegate | ||
import com.now.naaga.data.firebase.analytics.DefaultAnalyticsDelegate | ||
import com.now.naaga.databinding.ActivityAdventureDetailBinding | ||
import com.now.naaga.presentation.adventuredetail.viewpager.ViewPagerAdapter | ||
import com.now.naaga.presentation.uimodel.model.OpenLetterUiModel | ||
import com.now.naaga.util.extension.repeatOnStarted | ||
import com.now.naaga.util.extension.showSnackbarWithEvent | ||
import dagger.hilt.android.AndroidEntryPoint | ||
|
||
@AndroidEntryPoint | ||
class AdventureDetailActivity : AppCompatActivity(), AnalyticsDelegate by DefaultAnalyticsDelegate() { | ||
private lateinit var binding: ActivityAdventureDetailBinding | ||
private val viewModel: AdventureDetailViewModel by viewModels() | ||
|
||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
binding = ActivityAdventureDetailBinding.inflate(layoutInflater) | ||
setContentView(binding.root) | ||
|
||
val gameId = intent.getLongExtra(KET_GAME_ID, 0L) | ||
|
||
initView(gameId) | ||
setClickListeners() | ||
subscribe() | ||
} | ||
|
||
private fun initView(gameId: Long) { | ||
viewModel.fetchReadLetter(gameId) | ||
viewModel.fetchWriteLetter(gameId) | ||
viewModel.fetchAdventureResult(gameId) | ||
} | ||
|
||
private fun setClickListeners() { | ||
binding.ivAdventureDetailBack.setOnClickListener { finish() } | ||
} | ||
|
||
private fun subscribe() { | ||
repeatOnStarted { | ||
viewModel.uiState.collect { adventureDetailUiState -> | ||
when (adventureDetailUiState) { | ||
is AdventureDetailUiState.Loading, is AdventureDetailUiState.Error -> Unit | ||
is AdventureDetailUiState.Success -> initView(adventureDetailUiState) | ||
} | ||
} | ||
} | ||
repeatOnStarted { | ||
viewModel.throwableFlow.collect { event -> | ||
when (event) { | ||
is AdventureDetailViewModel.Event.NetworkExceptionEvent -> showReRequestSnackbar() | ||
is AdventureDetailViewModel.Event.LetterExceptionEvent -> showReRequestSnackbar() | ||
is AdventureDetailViewModel.Event.GameExceptionEvent -> showReRequestSnackbar() | ||
} | ||
} | ||
} | ||
} | ||
|
||
private fun showReRequestSnackbar() { | ||
binding.root.showSnackbarWithEvent( | ||
message = getString(R.string.snackbar_action_re_request_message), | ||
actionTitle = getString(R.string.snackbar_action__re_request_title), | ||
) { finish() } | ||
} | ||
|
||
private fun initView(adventureDetailUiState: AdventureDetailUiState.Success) { | ||
initViewPager(adventureDetailUiState.readLetters, adventureDetailUiState.writeLetters) | ||
initImage(adventureDetailUiState.adventureResult) | ||
} | ||
|
||
private fun initViewPager(readLetters: List<OpenLetterUiModel>, writeLetters: List<OpenLetterUiModel>) { | ||
binding.vpAdventureDetail.adapter = ViewPagerAdapter(listOf(readLetters, writeLetters)) | ||
|
||
TabLayoutMediator(binding.tlAdventureDetail, binding.vpAdventureDetail) { tab, position -> | ||
when (position) { | ||
0 -> tab.text = getString(R.string.adventure_detail_read_letter) | ||
1 -> tab.text = getString(R.string.adventure_detail_write_letter) | ||
} | ||
}.attach() | ||
} | ||
|
||
private fun initImage(adventureResult: AdventureResult) { | ||
Glide.with(binding.ivAdventureDetailPhoto) | ||
.load(adventureResult.destination.image) | ||
.error(R.drawable.ic_none_photo) | ||
.into(binding.ivAdventureDetailPhoto) | ||
} | ||
|
||
companion object { | ||
private const val KET_GAME_ID = "GAME_ID" | ||
|
||
fun getIntentWithId(context: Context, gameId: Long): Intent { | ||
return Intent(context, AdventureDetailActivity::class.java).apply { | ||
putExtra(KET_GAME_ID, gameId) | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.now.naaga.presentation.adventuredetail | ||
|
||
import com.now.domain.model.AdventureResult | ||
import com.now.naaga.presentation.uimodel.model.OpenLetterUiModel | ||
|
||
sealed interface AdventureDetailUiState { | ||
object Loading : AdventureDetailUiState | ||
|
||
data class Success( | ||
val readLetters: List<OpenLetterUiModel>, | ||
val writeLetters: List<OpenLetterUiModel>, | ||
val adventureResult: AdventureResult, | ||
) : AdventureDetailUiState | ||
|
||
object Error : AdventureDetailUiState | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package com.now.naaga.presentation.adventuredetail | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import com.now.domain.model.AdventureResult | ||
import com.now.domain.model.letter.OpenLetter | ||
import com.now.domain.model.type.LogType | ||
import com.now.domain.repository.AdventureRepository | ||
import com.now.domain.repository.LetterRepository | ||
import com.now.naaga.data.throwable.DataThrowable | ||
import com.now.naaga.presentation.uimodel.mapper.toUiModel | ||
import com.now.naaga.presentation.uimodel.model.OpenLetterUiModel | ||
import dagger.hilt.android.lifecycle.HiltViewModel | ||
import kotlinx.coroutines.flow.MutableSharedFlow | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.SharedFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asSharedFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.flow.collectLatest | ||
import kotlinx.coroutines.flow.combine | ||
import kotlinx.coroutines.launch | ||
import java.io.IOException | ||
import javax.inject.Inject | ||
|
||
@HiltViewModel | ||
class AdventureDetailViewModel @Inject constructor( | ||
private val letterRepository: LetterRepository, | ||
private val adventureRepository: AdventureRepository, | ||
) : ViewModel() { | ||
private val readLettersFlow = MutableSharedFlow<List<OpenLetter>>() | ||
|
||
private val writeLettersFlow = MutableSharedFlow<List<OpenLetter>>() | ||
|
||
private val adventureFlow = MutableSharedFlow<AdventureResult>() | ||
|
||
private val _uiState: MutableStateFlow<AdventureDetailUiState> = MutableStateFlow(AdventureDetailUiState.Loading) | ||
val uiState: StateFlow<AdventureDetailUiState> = _uiState.asStateFlow() | ||
|
||
private val _throwableFlow = MutableSharedFlow<Event>() | ||
val throwableFlow: SharedFlow<Event> = _throwableFlow.asSharedFlow() | ||
|
||
init { | ||
viewModelScope.launch { | ||
combine(readLettersFlow, writeLettersFlow, adventureFlow) { readLetters, writeLetters, adventureResult -> | ||
AdventureDetailUiState.Success( | ||
readLetters = getOpenLetterUiModels(readLetters), | ||
writeLetters = getOpenLetterUiModels(writeLetters), | ||
adventureResult = adventureResult, | ||
) | ||
}.collectLatest { _uiState.value = it } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. collect를 통해 emit한 값을 수집하여 사용할 수 있습니다. fun main() = runBlocking {
val flow = flow {
emit(1)
delay(1000)
emit(2)
delay(1000)
emit(3)
}
flow.collectLatest { value ->
println("Received: $value")
delay(3000)
}
}
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호... 예제 코드까지 주시다니.. 👍 |
||
} | ||
} | ||
|
||
private fun getOpenLetterUiModels(letters: List<OpenLetter>): List<OpenLetterUiModel> { | ||
if (letters.isEmpty()) return listOf(OpenLetterUiModel.DEFAULT_OPEN_LETTER) | ||
return letters.map { it.toUiModel() } | ||
} | ||
|
||
fun fetchReadLetter(gameId: Long) { | ||
viewModelScope.launch { | ||
runCatching { | ||
letterRepository.fetchLetterLogs(gameId, LogType.READ) | ||
}.onSuccess { | ||
readLettersFlow.emit(it) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호..... init 에서 컴바인을 해주면 여기선 success가 될 때 emit만 해주면 되는군요..? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
emit을 해야 collect가 가능합니다! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 아 ~~~~~~~~~ combine된 애들이 emit을 통해 값이 불러와지고 이 아이들이 다 들어오게 되면 collectLatest를 통해 받아오는거군요 |
||
}.onFailure { | ||
setThrowable(it) | ||
} | ||
} | ||
} | ||
|
||
fun fetchWriteLetter(gameId: Long) { | ||
viewModelScope.launch { | ||
runCatching { | ||
letterRepository.fetchLetterLogs(gameId, LogType.WRITE) | ||
}.onSuccess { | ||
writeLettersFlow.emit(it) | ||
}.onFailure { | ||
setThrowable(it) | ||
} | ||
} | ||
} | ||
|
||
fun fetchAdventureResult(gameId: Long) { | ||
viewModelScope.launch { | ||
runCatching { | ||
adventureRepository.fetchAdventureResult(gameId) | ||
}.onSuccess { | ||
adventureFlow.emit(it) | ||
}.onFailure { | ||
setThrowable(it) | ||
} | ||
} | ||
} | ||
|
||
private fun setThrowable(throwable: Throwable) { | ||
when (throwable) { | ||
is IOException -> throwable(Event.NetworkExceptionEvent(throwable)) | ||
is DataThrowable.LetterThrowable -> throwable(Event.LetterExceptionEvent(throwable)) | ||
is DataThrowable.GameThrowable -> throwable(Event.GameExceptionEvent(throwable)) | ||
} | ||
} | ||
|
||
private fun throwable(event: Event) { | ||
viewModelScope.launch { | ||
_throwableFlow.emit(event) | ||
} | ||
} | ||
|
||
sealed class Event { | ||
data class NetworkExceptionEvent(val throwable: Throwable) : Event() | ||
data class LetterExceptionEvent(val throwable: Throwable) : Event() | ||
data class GameExceptionEvent(val throwable: Throwable) : Event() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.now.naaga.presentation.adventuredetail.recyclerview | ||
|
||
import android.view.ViewGroup | ||
import androidx.recyclerview.widget.RecyclerView | ||
import com.now.naaga.presentation.uimodel.model.OpenLetterUiModel | ||
|
||
class LetterAdapter(private val letters: List<OpenLetterUiModel>) : RecyclerView.Adapter<LetterViewHolder>() { | ||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LetterViewHolder { | ||
return LetterViewHolder(parent) | ||
} | ||
|
||
override fun onBindViewHolder(holder: LetterViewHolder, position: Int) { | ||
holder.bind(letters[position]) | ||
} | ||
|
||
override fun getItemCount(): Int { | ||
return letters.size | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.now.naaga.presentation.adventuredetail.recyclerview | ||
|
||
import android.view.LayoutInflater | ||
import android.view.ViewGroup | ||
import androidx.recyclerview.widget.RecyclerView | ||
import com.now.naaga.R | ||
import com.now.naaga.databinding.ItemLetterBinding | ||
import com.now.naaga.presentation.uimodel.model.OpenLetterUiModel | ||
|
||
class LetterViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder( | ||
LayoutInflater.from(parent.context).inflate(R.layout.item_letter, parent, false), | ||
) { | ||
private val binding: ItemLetterBinding = ItemLetterBinding.bind(itemView) | ||
|
||
fun bind(letter: OpenLetterUiModel) { | ||
binding.tvItemLetterNickname.text = letter.nickname | ||
binding.tvItemLetterRegisterDate.text = letter.registerDate | ||
binding.tvItemLetter.text = letter.message | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.now.naaga.presentation.adventuredetail.viewpager | ||
|
||
import android.view.ViewGroup | ||
import androidx.recyclerview.widget.RecyclerView | ||
import com.now.naaga.presentation.uimodel.model.OpenLetterUiModel | ||
|
||
class ViewPagerAdapter(private val data: List<List<OpenLetterUiModel>>) : RecyclerView.Adapter<ViewPagerHolder>() { | ||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewPagerHolder { | ||
return ViewPagerHolder(parent) | ||
} | ||
|
||
override fun onBindViewHolder(holder: ViewPagerHolder, position: Int) { | ||
holder.bind(data[position]) | ||
} | ||
|
||
override fun getItemCount(): Int { | ||
return data.size | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.now.naaga.presentation.adventuredetail.viewpager | ||
|
||
import android.view.LayoutInflater | ||
import android.view.ViewGroup | ||
import androidx.recyclerview.widget.RecyclerView | ||
import com.now.naaga.R | ||
import com.now.naaga.databinding.ItemViewPagerBinding | ||
import com.now.naaga.presentation.adventuredetail.recyclerview.LetterAdapter | ||
import com.now.naaga.presentation.uimodel.model.OpenLetterUiModel | ||
|
||
class ViewPagerHolder(parent: ViewGroup) : RecyclerView.ViewHolder( | ||
LayoutInflater.from(parent.context).inflate(R.layout.item_view_pager, parent, false), | ||
) { | ||
private val binding: ItemViewPagerBinding = ItemViewPagerBinding.bind(itemView) | ||
|
||
fun bind(data: List<OpenLetterUiModel>) { | ||
binding.rvItemViewPager.adapter = LetterAdapter(data) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2]
오타가 있어요!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
오타...는 아닙니다..🦖
재요청 == Re(재)Request(요청)
의 느낌으로 작성한건데...
showRequestSnackbar
로 변경할까요?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ㅋㅋㅋㅋㅋㅋ오타가 아니군요!? 앗차차~~~
showRetrySnackbar
는 어떤가요?ㅋㅋㅋㅋ구리면 바로showReRequestSnackbar
로 유지합시다!ㅋㅋㅋ크롱 말 듣고 다시 보니 showReRequestSnackbar 네이밍도 메서드가 머선일을 하는지 잘 보이는 것 같고 좋네요👍