Skip to content

Commit

Permalink
refactor/#4 : 화면 이동 버튼 동시 터치시 최초 터치 버튼의 화면 이동만 실행
Browse files Browse the repository at this point in the history
- 동시에 두 화면 이동 버튼을 누르면 네비게이션 스택에 두 화면이 쌓이는 문제를 커스텀 flow 확장함수로 처리.
- 최초 로딩 흐름 수정
  • Loading branch information
TaewoongR committed Jan 23, 2025
1 parent cb34fdc commit c711757
Show file tree
Hide file tree
Showing 5 changed files with 94 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import com.boostcamp.mapisode.ui.base.UiIntent
@Immutable
sealed class GroupDetailIntent : UiIntent {
data class InitializeGroupDetail(val groupId: String) : GroupDetailIntent()
data class TryGetGroup(val groupId: String) : GroupDetailIntent()
data object TryGetUserInfo : GroupDetailIntent()
data object TryGetGroupEpisodes : GroupDetailIntent()
data object OnEditClick : GroupDetailIntent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,13 @@ fun GroupDetailScreen(
)
}

LaunchedEffect(Unit) {
viewModel.sendIntent(GroupDetailIntent.InitializeGroupDetail(detail.groupId))
}

LaunchedEffect(uiState.value) {
with(uiState.value) {
if (isGroupIdCaching) {
viewModel.sendIntent(GroupDetailIntent.InitializeGroupDetail(detail.groupId))
}
if (isGroupLoading) {
viewModel.sendIntent(GroupDetailIntent.TryGetGroup(detail.groupId))
}
if (!isGroupIdCaching && !isGroupLoading && membersInfo.isEmpty()) {
if (isGroupLoaded) {
viewModel.sendIntent(GroupDetailIntent.TryGetUserInfo)
}
// 최초 진입 시 보이지 않는 탭, 후순위 로딩
Expand Down Expand Up @@ -209,32 +207,34 @@ fun GroupDetailContent(
isStatusBarPaddingExist = true,
isNavigationBarPaddingExist = true,
topBar = {
TopAppBar(
title = uiState.group?.name ?: "",
navigationIcon = {
MapisodeIconButton(
onClick = { onBackClick() },
) {
MapisodeIcon(
id = R.drawable.ic_arrow_back_ios,
)
}
},

actions = {
if (uiState.isGroupOwner) {
uiState.group.name.let {
TopAppBar(
title = it,
navigationIcon = {
MapisodeIconButton(
onClick = {
onEditClick()
},
onClick = { onBackClick() },
) {
MapisodeIcon(
id = R.drawable.ic_edit,
id = R.drawable.ic_arrow_back_ios,
)
}
}
},
)
},

actions = {
if (uiState.isGroupOwner) {
MapisodeIconButton(
onClick = {
onEditClick()
},
) {
MapisodeIcon(
id = R.drawable.ic_edit,
)
}
}
},
)
}
},
) {
Column(
Expand Down Expand Up @@ -264,14 +264,12 @@ fun GroupDetailContent(
HorizontalPager(state = pagerState) { page ->
when (page) {
0 -> {
if (uiState.group != null) {
GroupDetailContent(
group = uiState.group.toGroupModel(),
members = uiState.membersInfo,
onIssueCodeClick = onIssueCodeClick,
onGroupOutClick = onGroupOutClick,
)
}
GroupDetailContent(
group = uiState.group.toGroupModel(),
members = uiState.membersInfo,
onIssueCodeClick = onIssueCodeClick,
onGroupOutClick = onGroupOutClick,
)
}

1 -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.boostcamp.mapisode.ui.base.UiState
@Immutable
data class GroupCreationState(
val isInitializing: Boolean = false,
val isNavigatedToGroupScreen: Boolean = false,
val isSelectingGroupImage: Boolean = false,
val group: GroupCreationModel = GroupCreationModel(),
) : UiState
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,22 @@ import com.boostcamp.mapisode.mygroup.model.GroupUiModel
import com.boostcamp.mapisode.ui.base.UiState
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import java.util.Date

@Immutable
data class GroupDetailState(
val isGroupIdCaching: Boolean = true,
val isGroupLoading: Boolean = false,
val isGroupLoaded: Boolean = false,
val isNavigated: Boolean = false,
val isGroupOwner: Boolean = false,
val group: GroupUiModel? = null,
val group: GroupUiModel = GroupUiModel(
id = "",
adminUser = "",
description = "",
imageUrl = "",
name = "",
members = persistentListOf(),
createdAt = Date(),
),
val membersInfo: ImmutableList<GroupUiMemberModel> = persistentListOf(),
val episodes: ImmutableList<GroupUiEpisodeModel> = persistentListOf(),
) : UiState
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.boostcamp.mapisode.mygroup.viewmodel

import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.viewModelScope
import com.boostcamp.mapisode.datastore.UserPreferenceDataStore
import com.boostcamp.mapisode.episode.EpisodeRepository
Expand All @@ -14,15 +13,13 @@ import com.boostcamp.mapisode.mygroup.sideeffect.GroupDetailSideEffect
import com.boostcamp.mapisode.mygroup.state.GroupDetailState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject

Expand All @@ -32,41 +29,29 @@ class GroupDetailViewModel @Inject constructor(
private val episodeRepository: EpisodeRepository,
private val userPreferenceDataStore: UserPreferenceDataStore,
) : GroupBaseViewModel<GroupDetailIntent, GroupDetailState, GroupDetailSideEffect>(GroupDetailState()) {
private val groupId = mutableStateOf("")

@OptIn(FlowPreview::class)
override suspend fun reducer(intent: SharedFlow<GroupDetailIntent>) {
intent
.debounce(100L)
.flatMapLatest { value ->
flowOf(value).onEach { delay(300) }
}
.collectLatest { uiIntent ->
intent.retainFirstIfNavigating()
.collect { uiIntent ->
when (uiIntent) {
is GroupDetailIntent.InitializeGroupDetail -> {
getGroupDetail(uiIntent.groupId)
}

is GroupDetailIntent.TryGetGroup -> {
tryGetGroup()
tryGetGroup(uiIntent.groupId)
}

is GroupDetailIntent.TryGetUserInfo -> {
setGroupMembersInfo()
}

is GroupDetailIntent.OnEditClick -> {
sendEffect { GroupDetailSideEffect.NavigateToGroupEditScreen(groupId.value) }
delay(100)
sendState { copy(isGroupLoading = true) }
navigateToGroupEditScreen(currentState.group.id)
}

is GroupDetailIntent.OnBackClick -> {
sendEffect { GroupDetailSideEffect.NavigateToGroupScreen }
}

is GroupDetailIntent.OnEpisodeClick -> {
sendEffect { GroupDetailSideEffect.NavigateToEpisode(uiIntent.episodeId) }
navigateToEpisode(uiIntent.episodeId)
}

is GroupDetailIntent.OnIssueCodeClick -> {
Expand Down Expand Up @@ -94,30 +79,32 @@ class GroupDetailViewModel @Inject constructor(
}
}

private fun getGroupDetail(groupId: String) {
this.groupId.value = groupId
sendState {
copy(
isGroupIdCaching = false,
isGroupLoading = true,
)
}
private fun navigateToGroupEditScreen(groupId: String) {
sendEffect { GroupDetailSideEffect.NavigateToGroupEditScreen(groupId) }
}

private fun tryGetGroup() {
private fun navigateToEpisode(episodeId: String) {
sendEffect { GroupDetailSideEffect.NavigateToEpisode(episodeId) }
}

private fun tryGetGroup(groupId: String) {
viewModelScope.launch {
try {
val group = groupRepository.getGroupByGroupId(groupId.value)
sendState {
copy(
isGroupLoading = false,
group = group.toGroupUiModel(),
)
}
val group = groupRepository.getGroupByGroupId(groupId)

if (group.adminUser == userPreferenceDataStore.getUserId().first()) {
sendState {
copy(
isGroupOwner = true,
group = group.toGroupUiModel(),
isGroupLoaded = true,
)
}
} else {
sendState {
copy(
group = group.toGroupUiModel(),
isGroupLoaded = true,
)
}
}
Expand All @@ -130,7 +117,7 @@ class GroupDetailViewModel @Inject constructor(
private fun issueInvitationCode() {
viewModelScope.launch {
try {
val code = groupRepository.issueInvitationCode(groupId.value)
val code = groupRepository.issueInvitationCode(currentState.group.id)
sendEffect { GroupDetailSideEffect.IssueInvitationCode(code) }
} catch (e: Exception) {
sendEffect { GroupDetailSideEffect.ShowToast(R.string.message_issue_code_fail) }
Expand All @@ -139,15 +126,15 @@ class GroupDetailViewModel @Inject constructor(
}

private fun setGroupMembersInfo() {
val group = currentState.group ?: throw Exception()
val group = currentState.group
val members = group.members

viewModelScope.launch {
val memberInfo = mutableListOf<GroupUiMemberModel>()
members.forEach { member ->
val userModel = groupRepository.getUserInfoByUserId(member)
val userEpisodeModel = groupRepository.getEpisodesByGroupIdAndUserId(
groupId = groupId.value,
groupId = currentState.group.id,
userId = member,
)
val latestCreatedAt = userEpisodeModel.maxByOrNull { it.createdAt.time }?.createdAt
Expand Down Expand Up @@ -177,7 +164,7 @@ class GroupDetailViewModel @Inject constructor(
private fun leaveGroup() {
viewModelScope.launch {
val userId = userPreferenceDataStore.getUserId().first() ?: throw Exception()
val groupId = groupId.value
val groupId = currentState.group.id
try {
groupRepository.leaveGroup(userId, groupId)
sendEffect { GroupDetailSideEffect.ShowToast(R.string.message_group_out_success) }
Expand All @@ -192,7 +179,7 @@ class GroupDetailViewModel @Inject constructor(
private fun getGroupEpisodes() {
viewModelScope.launch {
try {
val episodes = episodeRepository.getEpisodesByGroup(groupId.value)
val episodes = episodeRepository.getEpisodesByGroup(currentState.group.id)
sendState {
copy(
episodes = episodes.map {
Expand All @@ -209,3 +196,19 @@ class GroupDetailViewModel @Inject constructor(
}
}
}

fun Flow<GroupDetailIntent>.retainFirstIfNavigating() = channelFlow {
var isFirst = true
collectLatest { value ->
if (isFirst) {
launch(Dispatchers.IO) {
isFirst = false
send(value)
if (value is GroupDetailIntent.OnEditClick || value is GroupDetailIntent.OnEpisodeClick) {
delay(300)
}
isFirst = true
}
}
}
}

0 comments on commit c711757

Please sign in to comment.