Skip to content

Commit

Permalink
feat/#14: MyPage MVI 적용
Browse files Browse the repository at this point in the history
  • Loading branch information
boiledEgg-s committed Dec 12, 2024
1 parent 6ec409c commit b3118ee
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 93 deletions.
42 changes: 28 additions & 14 deletions app/src/main/java/org/sopt/and/presentation/mypage/MyPageScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ import org.sopt.and.core.designsystem.theme.Grey200
import org.sopt.and.core.designsystem.theme.WavveBackground
import org.sopt.and.core.designsystem.theme.White
import org.sopt.and.core.extension.noRippleClickable
import org.sopt.and.domain.entity.Program
import org.sopt.and.core.preference.PreferenceUtil.Companion.LocalPreference
import org.sopt.and.domain.entity.Program
import org.sopt.and.presentation.mypage.component.ProfileLogGroup
import org.sopt.and.presentation.mypage.component.ProfilePurchaseGroup
import org.sopt.and.presentation.mypage.component.ProfileTopBar
import org.sopt.and.presentation.mypage.state.MyPageUiState
import org.sopt.and.presentation.mypage.contract.MyPageSideEffect
import org.sopt.and.presentation.mypage.contract.MyPageUiEvent
import org.sopt.and.presentation.mypage.contract.MyPageUiState

@Composable
fun MyPageRoute(
Expand All @@ -56,6 +58,7 @@ fun MyPageRoute(
viewModel: MyPageViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val starredProgram by viewModel.starredState.collectAsStateWithLifecycle()

val snackBarHost = remember { SnackbarHostState() }
val lifecycleOwner = LocalLifecycleOwner.current
Expand Down Expand Up @@ -83,22 +86,24 @@ fun MyPageRoute(
MyPageScreen(
hobby = uiState.hobby,
snackBarHost = snackBarHost,
onLogoutButtonClick = viewModel::onLogoutButtonClick,
onLogoutButtonClick = {
viewModel.setEvent(MyPageUiEvent.OnLogoutButtonClick)
},
onProgramPress = { program ->
with(viewModel) {
updatePressedProgram(program)
updateDeleteDialogVisibility(visibility = true)
}
viewModel.setEvent(MyPageUiEvent.OnStarredProgramPressed(program))
},
uiState = uiState
uiState = uiState,
starredPrograms = starredProgram
)


FloatingActionButton(
onClick = { viewModel.updateSearchDialogVisibility(true) },
shape = CircleShape,
containerColor = Color.Blue,
contentColor = White,
onClick = {
viewModel.setEvent(MyPageUiEvent.OnFAButtonClick)
},
modifier = Modifier
.wrapContentSize()
.align(Alignment.BottomEnd)
Expand All @@ -115,8 +120,12 @@ fun MyPageRoute(

if (uiState.searchDialogVisibility) {
SearchDialog(
onDismissRequest = { viewModel.updateSearchDialogVisibility(false) },
onItemSelect = viewModel::onInsertProgram,
onDismissRequest = {
viewModel.setEvent(MyPageUiEvent.OnSearchDialogDismissed)
},
onItemSelect = { program ->
viewModel.setEvent(MyPageUiEvent.OnSearchProgramSelected(program))
},
)
}
if (uiState.deleteDialogVisibility) {
Expand All @@ -126,8 +135,12 @@ fun MyPageRoute(
R.string.dialog_delete_content,
uiState.pressedProgram?.title.orEmpty()
),
onDismissRequest = { viewModel.updateDeleteDialogVisibility(visibility = false) },
onConfirm = viewModel::onConfirmDelete
onDismissRequest = {
viewModel.setEvent(MyPageUiEvent.OnDeleteDialogDismissed)
},
onConfirm = {
viewModel.setEvent(MyPageUiEvent.OnDeleteProgramConfirmed)
}
)
}

Expand All @@ -137,6 +150,7 @@ fun MyPageRoute(
private fun MyPageScreen(
hobby: String,
uiState: MyPageUiState,
starredPrograms: List<Program>,
snackBarHost: SnackbarHostState,
onLogoutButtonClick: () -> Unit,
onProgramPress: (Program) -> Unit,
Expand Down Expand Up @@ -179,7 +193,7 @@ private fun MyPageScreen(
title = stringResource(R.string.mypage_content_title2),
subTitle = stringResource(R.string.mypage_content_empty2),
onItemPress = onProgramPress,
list = uiState.starredProgram,
list = starredPrograms,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 10.dp)
Expand Down

This file was deleted.

115 changes: 55 additions & 60 deletions app/src/main/java/org/sopt/and/presentation/mypage/MyPageViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,99 +1,94 @@
package org.sopt.and.presentation.mypage

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.sopt.and.core.viewmodel.BaseViewModel
import org.sopt.and.domain.entity.Program
import org.sopt.and.domain.repository.MyHobbyRepository
import org.sopt.and.domain.repository.StarredProgramRepository
import org.sopt.and.presentation.mypage.state.MyPageInteractionState
import org.sopt.and.presentation.mypage.state.MyPageUiState
import org.sopt.and.presentation.mypage.contract.MyPageSideEffect
import org.sopt.and.presentation.mypage.contract.MyPageUiEvent
import org.sopt.and.presentation.mypage.contract.MyPageUiState
import javax.inject.Inject

@HiltViewModel
class MyPageViewModel @Inject constructor(
private val starredProgramRepository: StarredProgramRepository,
private val myHobbyRepository: MyHobbyRepository
) : ViewModel() {
private var interactionState = MutableStateFlow(MyPageInteractionState())
private val starredState: StateFlow<List<Program>> =
) : BaseViewModel<MyPageUiState, MyPageSideEffect, MyPageUiEvent>() {

val starredState: StateFlow<List<Program>> =
starredProgramRepository.getStarredPrograms()
.map { it.map { entity -> Program(title = entity.programName, imgFile = entity.programImage) } }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(1000),
initialValue = emptyList()
)
private val currentStarredState: List<Program>
get() = starredState.value

val uiState: StateFlow<MyPageUiState> = combine(
interactionState, starredState
) { uiState, starredState ->
MyPageUiState().copy(
hobby = uiState.hobby,
searchDialogVisibility = uiState.searchDialogVisibility,
deleteDialogVisibility = uiState.deleteDialogVisibility,
pressedProgram = uiState.pressedProgram,
starredProgram = starredState
)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.Lazily,
initialValue = MyPageUiState(),
)

private var _sideEffect = MutableSharedFlow<MyPageSideEffect>()
val sideEffect = _sideEffect.asSharedFlow()

fun getMyHobby(token: String) = viewModelScope.launch {
myHobbyRepository.getMyHobby(token)
.onSuccess { hobby ->
interactionState.update { currentState ->
currentState.copy(hobby = hobby.hobby)
override fun createInitialState(): MyPageUiState = MyPageUiState()

override suspend fun handleEvent(event: MyPageUiEvent) {
when (event) {
is MyPageUiEvent.OnLogoutButtonClick -> {
setSideEffect(MyPageSideEffect.OnLogout)
}

is MyPageUiEvent.OnFAButtonClick -> {
setState { copy(searchDialogVisibility = true) }
}

is MyPageUiEvent.OnSearchDialogDismissed -> {
setState { copy(searchDialogVisibility = false) }
}

is MyPageUiEvent.OnSearchProgramSelected -> {
insertProgramToLocal(program = event.program)
}

is MyPageUiEvent.OnStarredProgramPressed -> {
setState {
copy(
pressedProgram = event.program,
deleteDialogVisibility = true
)
}
}
}

fun onLogoutButtonClick() = viewModelScope.launch {
_sideEffect.emit(MyPageSideEffect.OnLogout)
}
is MyPageUiEvent.OnDeleteDialogDismissed -> {
setState { copy(deleteDialogVisibility = false) }
}

fun onConfirmDelete() = viewModelScope.launch {
interactionState.value.pressedProgram?.run {
starredProgramRepository.deletedStarredProgram(this)
}
updateDeleteDialogVisibility(false)
}
is MyPageUiEvent.OnDeleteProgramConfirmed -> {
deleteProgramFromLocal()
setState { copy(deleteDialogVisibility = false) }
}

fun updateSearchDialogVisibility(visibility: Boolean) =
interactionState.update { currentState ->
currentState.copy(searchDialogVisibility = visibility)
}
}

fun updateDeleteDialogVisibility(visibility: Boolean) =
interactionState.update { currentState ->
currentState.copy(
deleteDialogVisibility = visibility
)
}
suspend fun getMyHobby(token: String) {
myHobbyRepository.getMyHobby(token)
.onSuccess { hobby ->
setState { copy(hobby = hobby.hobby) }
}
}

fun updatePressedProgram(program: Program) {
interactionState.update { currentState ->
currentState.copy(pressedProgram = program)
private suspend fun deleteProgramFromLocal() {
currentState.pressedProgram?.run {
starredProgramRepository.deletedStarredProgram(this)
}
}

fun onInsertProgram(program: Program) = viewModelScope.launch {
if (uiState.value.starredProgram.contains(program)) return@launch
private suspend fun insertProgramToLocal(program: Program) {
if (currentStarredState.contains(program))
return

starredProgramRepository.postStarredProgram(program)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.sopt.and.presentation.mypage.contract

import org.sopt.and.core.viewmodel.UiSideEffect

sealed class MyPageSideEffect: UiSideEffect {
data object OnLogout : MyPageSideEffect()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.sopt.and.presentation.mypage.contract

import org.sopt.and.core.viewmodel.UiEvent
import org.sopt.and.domain.entity.Program

sealed class MyPageUiEvent: UiEvent {
data object OnLogoutButtonClick: MyPageUiEvent()
data object OnFAButtonClick: MyPageUiEvent()
data class OnStarredProgramPressed(val program: Program): MyPageUiEvent()
data object OnSearchDialogDismissed: MyPageUiEvent()
data class OnSearchProgramSelected(val program: Program): MyPageUiEvent()
data object OnDeleteDialogDismissed: MyPageUiEvent()
data object OnDeleteProgramConfirmed: MyPageUiEvent()
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package org.sopt.and.presentation.mypage.state
package org.sopt.and.presentation.mypage.contract

import org.sopt.and.core.viewmodel.UiState
import org.sopt.and.domain.entity.Program

data class MyPageUiState(
val hobby: String = "",
val searchDialogVisibility: Boolean = false,
val deleteDialogVisibility: Boolean = false,
val pressedProgram: Program? = null,
val starredProgram: List<Program> = emptyList()

)
): UiState

This file was deleted.

0 comments on commit b3118ee

Please sign in to comment.