Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.threegap.bitnagil.domain.emotion.model.DailyEmotion
import com.threegap.bitnagil.domain.emotion.model.EmotionMarbleType
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import java.time.LocalDate

@Serializable
data class DailyEmotionResponse(
Expand All @@ -17,10 +18,11 @@ data class DailyEmotionResponse(
val emotionMarbleHomeMessage: String?,
)

fun DailyEmotionResponse.toDomain(): DailyEmotion =
fun DailyEmotionResponse.toDomain(fetchedDate: LocalDate): DailyEmotion =
DailyEmotion(
type = emotionMarbleType,
name = emotionMarbleName,
imageUrl = imageUrl,
homeMessage = emotionMarbleHomeMessage,
fetchedDate = fetchedDate,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,26 @@ import com.threegap.bitnagil.data.emotion.datasource.EmotionDataSource
import com.threegap.bitnagil.data.emotion.model.response.toDomain
import com.threegap.bitnagil.domain.emotion.model.DailyEmotion
import com.threegap.bitnagil.domain.emotion.model.Emotion
import com.threegap.bitnagil.domain.emotion.model.EmotionChangeEvent
import com.threegap.bitnagil.domain.emotion.model.EmotionRecommendRoutine
import com.threegap.bitnagil.domain.emotion.repository.EmotionRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.onSubscription
import java.time.LocalDate
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject

class EmotionRepositoryImpl @Inject constructor(
private val emotionDataSource: EmotionDataSource,
) : EmotionRepository {

private val isFetching = AtomicBoolean(false)
private val _dailyEmotionFlow = MutableStateFlow(DailyEmotion.INIT)
override val dailyEmotionFlow: Flow<DailyEmotion> = _dailyEmotionFlow
.onSubscription {
if (_dailyEmotionFlow.value.isStale(LocalDate.now())) fetchDailyEmotion()
}

override suspend fun getEmotions(): Result<List<Emotion>> {
return emotionDataSource.getEmotions().map { response ->
response.map { it.toDomain() }
Expand All @@ -23,20 +32,23 @@ class EmotionRepositoryImpl @Inject constructor(

override suspend fun registerEmotion(emotionMarbleType: String): Result<List<EmotionRecommendRoutine>> {
return emotionDataSource.registerEmotion(emotionMarbleType).map {
it.recommendedRoutines.map {
emotionRecommendedRoutineDto ->
it.recommendedRoutines.map { emotionRecommendedRoutineDto ->
emotionRecommendedRoutineDto.toEmotionRecommendRoutine()
}
}.also {
if (it.isSuccess) {
_emotionChangeEventFlow.emit(EmotionChangeEvent.ChangeEmotion(emotionMarbleType))
}
if (it.isSuccess) fetchDailyEmotion()
}
}

override suspend fun fetchDailyEmotion(currentDate: String): Result<DailyEmotion> =
emotionDataSource.fetchDailyEmotion(currentDate).map { it.toDomain() }

private val _emotionChangeEventFlow = MutableSharedFlow<EmotionChangeEvent>()
override suspend fun getEmotionChangeEventFlow(): Flow<EmotionChangeEvent> = _emotionChangeEventFlow.asSharedFlow()
override suspend fun fetchDailyEmotion(): Result<Unit> {
if (!isFetching.compareAndSet(false, true)) return Result.success(Unit)
return try {
val today = LocalDate.now()
emotionDataSource.fetchDailyEmotion(today.toString()).map {
_dailyEmotionFlow.value = it.toDomain(today)
}
} finally {
isFetching.set(false)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
package com.threegap.bitnagil.domain.emotion.model

import java.time.LocalDate

data class DailyEmotion(
val type: EmotionMarbleType?,
val name: String?,
val imageUrl: String?,
val homeMessage: String?,
)
val fetchedDate: LocalDate = LocalDate.MIN,
) {
fun isStale(today: LocalDate): Boolean = fetchedDate != today

companion object {
val INIT = DailyEmotion(
type = null,
name = null,
imageUrl = null,
homeMessage = null,
fetchedDate = LocalDate.MIN,
)
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ package com.threegap.bitnagil.domain.emotion.repository

import com.threegap.bitnagil.domain.emotion.model.DailyEmotion
import com.threegap.bitnagil.domain.emotion.model.Emotion
import com.threegap.bitnagil.domain.emotion.model.EmotionChangeEvent
import com.threegap.bitnagil.domain.emotion.model.EmotionRecommendRoutine
import kotlinx.coroutines.flow.Flow

interface EmotionRepository {
val dailyEmotionFlow: Flow<DailyEmotion>
suspend fun getEmotions(): Result<List<Emotion>>
suspend fun registerEmotion(emotionMarbleType: String): Result<List<EmotionRecommendRoutine>>
suspend fun fetchDailyEmotion(currentDate: String): Result<DailyEmotion>
suspend fun getEmotionChangeEventFlow(): Flow<EmotionChangeEvent>
suspend fun fetchDailyEmotion(): Result<Unit>
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,11 @@ package com.threegap.bitnagil.domain.emotion.usecase

import com.threegap.bitnagil.domain.emotion.model.DailyEmotion
import com.threegap.bitnagil.domain.emotion.repository.EmotionRepository
import java.time.LocalDate
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject

class FetchDailyEmotionUseCase @Inject constructor(
class ObserveDailyEmotionUseCase @Inject constructor(
private val emotionRepository: EmotionRepository,
) {
suspend operator fun invoke(): Result<DailyEmotion> {
val currentDate = LocalDate.now().toString()
return emotionRepository.fetchDailyEmotion(currentDate)
}
operator fun invoke(): Flow<DailyEmotion> = emotionRepository.dailyEmotionFlow
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ package com.threegap.bitnagil.presentation.screen.home

import android.util.Log
import androidx.lifecycle.ViewModel
import com.threegap.bitnagil.domain.emotion.usecase.FetchDailyEmotionUseCase
import com.threegap.bitnagil.domain.emotion.usecase.GetEmotionChangeEventFlowUseCase
import com.threegap.bitnagil.domain.emotion.usecase.ObserveDailyEmotionUseCase
import com.threegap.bitnagil.domain.onboarding.usecase.GetOnBoardingRecommendRoutineEventFlowUseCase
import com.threegap.bitnagil.domain.routine.model.RoutineCompletionInfo
import com.threegap.bitnagil.domain.routine.model.RoutineCompletionInfos
Expand Down Expand Up @@ -34,23 +33,37 @@ import javax.inject.Inject

@HiltViewModel
class HomeViewModel @Inject constructor(
private val observeDailyEmotionUseCase: ObserveDailyEmotionUseCase,
private val fetchWeeklyRoutinesUseCase: FetchWeeklyRoutinesUseCase,
private val fetchUserProfileUseCase: FetchUserProfileUseCase,
private val fetchDailyEmotionUseCase: FetchDailyEmotionUseCase,
private val routineCompletionUseCase: RoutineCompletionUseCase,
private val getWriteRoutineEventFlowUseCase: GetWriteRoutineEventFlowUseCase,
private val getEmotionChangeEventFlowUseCase: GetEmotionChangeEventFlowUseCase,
private val getOnBoardingRecommendRoutineEventFlowUseCase: GetOnBoardingRecommendRoutineEventFlowUseCase,
private val toggleRoutineUseCase: ToggleRoutineUseCase,
) : ContainerHost<HomeState, HomeSideEffect>, ViewModel() {

override val container: Container<HomeState, HomeSideEffect> = container(initialState = HomeState.INIT)
override val container: Container<HomeState, HomeSideEffect> =
container(
initialState = HomeState.INIT,
buildSettings = { repeatOnSubscribedStopTimeout = 5_000L },
)

private val pendingChangesByDate = mutableMapOf<String, MutableMap<String, RoutineCompletionInfo>>()
private val routineSyncTrigger = MutableSharedFlow<String>(extraBufferCapacity = 64)

init {
initialize()
observeDailyEmotion()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[2] RecommendRoutineViewModel에서는 container의 onCreate 블록에 observeDailyEmotion을 배치했는데, 여기에서는 init 블록에 배치한 이유가 있으신가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기술적인 이유보단 homeViewmodel에서는 아직 변경하지 않은 로직이 남아있어서 onCreateinit 각각 정의하면 혼동할 수 가능성 때문에 기존 init에 정의하고 추후 다른 변경사항과 함께 onCreate로 변경하고자 했숩니다~!

}

private fun observeDailyEmotion() {
intent {
repeatOnSubscription {
observeDailyEmotionUseCase()
.map { it.toUiModel() }
.collect { reduce { state.copy(dailyEmotion = it) } }
}
}
}

fun selectDate(data: LocalDate) {
Expand Down Expand Up @@ -186,10 +199,8 @@ class HomeViewModel @Inject constructor(
intent {
coroutineScope {
launch { fetchUserProfile() }
launch { fetchDailyEmotion() }
launch { fetchWeeklyRoutines(state.currentWeeks) }
launch { observeWriteRoutineEvent() }
launch { observeEmotionChangeEvent() }
launch { observeRecommendRoutineEvent() }
launch { observeWeekChanges() }
launch { observeRoutineUpdates() }
Expand All @@ -205,14 +216,6 @@ class HomeViewModel @Inject constructor(
}
}

private suspend fun observeEmotionChangeEvent() {
subIntent {
getEmotionChangeEventFlowUseCase().collect {
fetchDailyEmotion()
}
}
}

private suspend fun observeRecommendRoutineEvent() {
subIntent {
getOnBoardingRecommendRoutineEventFlowUseCase().collect {
Expand Down Expand Up @@ -278,21 +281,6 @@ class HomeViewModel @Inject constructor(
}
}

private suspend fun fetchDailyEmotion() {
subIntent {
reduce { state.copy(loadingCount = state.loadingCount + 1) }
fetchDailyEmotionUseCase().fold(
onSuccess = {
reduce { state.copy(dailyEmotion = it.toUiModel(), loadingCount = state.loadingCount - 1) }
},
onFailure = {
Log.e("HomeViewModel", "나의 감정 실패: ${it.message}")
reduce { state.copy(loadingCount = state.loadingCount - 1) }
},
)
}
}

private fun syncRoutineChangesForDate(dateKey: String) {
intent {
val dateChanges = pendingChangesByDate.remove(dateKey)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.threegap.bitnagil.presentation.screen.recommendroutine

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.threegap.bitnagil.domain.emotion.usecase.GetEmotionChangeEventFlowUseCase
import com.threegap.bitnagil.domain.emotion.usecase.ObserveDailyEmotionUseCase
import com.threegap.bitnagil.domain.recommendroutine.model.RecommendCategory
import com.threegap.bitnagil.domain.recommendroutine.model.RecommendLevel
import com.threegap.bitnagil.domain.recommendroutine.usecase.FetchRecommendRoutinesUseCase
Expand All @@ -12,25 +11,32 @@ import com.threegap.bitnagil.presentation.screen.recommendroutine.model.Recommen
import com.threegap.bitnagil.presentation.screen.recommendroutine.model.RecommendRoutinesUiModel
import com.threegap.bitnagil.presentation.screen.recommendroutine.model.toUiModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import org.orbitmvi.orbit.Container
import org.orbitmvi.orbit.ContainerHost
import org.orbitmvi.orbit.viewmodel.container
import javax.inject.Inject

@HiltViewModel
class RecommendRoutineViewModel @Inject constructor(
private val observeDailyEmotionUseCase: ObserveDailyEmotionUseCase,
private val fetchRecommendRoutinesUseCase: FetchRecommendRoutinesUseCase,
private val getEmotionChangeEventFlowUseCase: GetEmotionChangeEventFlowUseCase,
) : ContainerHost<RecommendRoutineState, RecommendRoutineSideEffect>, ViewModel() {

override val container: Container<RecommendRoutineState, RecommendRoutineSideEffect> =
container(initialState = RecommendRoutineState.INIT)

init {
loadRecommendRoutines()
observeEmotionChangeEvent()
}
container(
initialState = RecommendRoutineState.INIT,
buildSettings = { repeatOnSubscribedStopTimeout = 5_000L },
onCreate = {
repeatOnSubscription {
observeDailyEmotionUseCase()
.map { it.type }
.distinctUntilChanged()
.collect { loadRecommendRoutines() }
}
},
)

private var recommendRoutines: RecommendRoutinesUiModel = RecommendRoutinesUiModel.INIT

Expand Down Expand Up @@ -80,14 +86,6 @@ class RecommendRoutineViewModel @Inject constructor(
}
}

private fun observeEmotionChangeEvent() {
viewModelScope.launch {
getEmotionChangeEventFlowUseCase().collect {
loadRecommendRoutines()
}
}
}

private fun loadRecommendRoutines() {
intent {
reduce { state.copy(isLoading = true) }
Expand Down