Skip to content
Merged
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,8 @@ import com.afternote.core.ui.mvi.MviIntent
import com.afternote.core.ui.mvi.ReducerEvent

internal sealed interface ConnectedAccountsIntent : MviIntent {
data object RefreshOnReturn : ConnectedAccountsIntent

data object RetryLoad : ConnectedAccountsIntent

data class ConsumeError(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.credentials.CredentialManager
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.afternote.core.common.auth.requestKakaoAccessToken
import com.afternote.core.domain.error.CoreAuthFailure
Expand All @@ -32,6 +34,7 @@ internal fun ConnectedAccountsScreen(
viewModel: ConnectedAccountsViewModel = hiltViewModel(),
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { viewModel.onIntent(ConnectedAccountsIntent.RefreshOnReturn) }
val context = LocalContext.current
val credentialManager = remember(context) { CredentialManager.create(context) }
val snackbarHostState = remember { SnackbarHostState() }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,18 @@ internal class ConnectedAccountsViewModel
ConnectedAccountsUiState(isLoading = true),
) {
private var loadJob: Job? = null
private var isFirstResume = true

init {
loadConnectedAccounts()
}

override fun onIntent(intent: ConnectedAccountsIntent) {
when (intent) {
ConnectedAccountsIntent.RefreshOnReturn -> {
refreshOnReturn()
}

ConnectedAccountsIntent.RetryLoad -> {
if (currentState.errorMessage != null) loadConnectedAccounts()
}
Expand Down Expand Up @@ -100,16 +105,26 @@ internal class ConnectedAccountsViewModel
}
}

private fun loadConnectedAccounts() {
if (loadJob?.isActive == true) return
dispatch(ConnectedAccountsReducerEvent.Loading)
private fun refreshOnReturn() {
if (isFirstResume) {
isFirstResume = false
return
}
loadConnectedAccounts(isAutomatic = true)
}

private fun loadConnectedAccounts(isAutomatic: Boolean = false) {
if (loadJob?.isActive == true || currentState.isUpdating || currentState.pendingLinkProvider != null) return
if (!isAutomatic) dispatch(ConnectedAccountsReducerEvent.Loading)
loadJob =
viewModelScope.launch {
runCatchingCancellable { accountRepository.getConnectedAccounts() }
.onSuccess { accounts ->
dispatch(ConnectedAccountsReducerEvent.Loaded(accounts.toStateList()))
}.onFailure {
dispatch(ConnectedAccountsReducerEvent.LoadFailed("계정 정보를 불러올 수 없습니다."))
if (!isAutomatic || currentState.accounts.isEmpty()) {
dispatch(ConnectedAccountsReducerEvent.LoadFailed("계정 정보를 불러올 수 없습니다."))
}
}
}
}
Expand All @@ -131,6 +146,7 @@ internal class ConnectedAccountsViewModel
accessToken: String,
) {
if (currentState.isLoading || currentState.errorMessage != null || currentState.isUpdating) return
loadJob?.cancel()
dispatch(ConnectedAccountsReducerEvent.Changing)
viewModelScope.launch {
runCatchingCancellable { accountRepository.linkConnectedAccount(provider, accessToken) }
Expand All @@ -140,6 +156,7 @@ internal class ConnectedAccountsViewModel
}

private fun unlink(provider: String) {
loadJob?.cancel()
dispatch(ConnectedAccountsReducerEvent.Changing)
viewModelScope.launch {
runCatchingCancellable { accountRepository.unlinkConnectedAccount(provider) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.afternote.core.ui.mvi.MviIntent
import com.afternote.core.ui.mvi.ReducerEvent

internal sealed interface DeliveryConditionIntent : MviIntent {
data object RefreshOnReturn : DeliveryConditionIntent

data class SelectConditionType(
val type: DeliveryConditionType,
) : DeliveryConditionIntent
Expand All @@ -31,6 +33,7 @@ internal sealed interface DeliveryConditionReducerEvent : ReducerEvent {
data object Saving : DeliveryConditionReducerEvent

data class Saved(
val revision: Int,
val conditions: List<DeliveryConditionItem>,
) : DeliveryConditionReducerEvent

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.afternote.core.model.delivery.DeliveryConditionType
import com.afternote.core.ui.mvi.ObserveFlag
Expand All @@ -41,6 +43,7 @@ internal fun DeliveryConditionScreen(
viewModel: DeliveryConditionViewModel,
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { viewModel.onIntent(DeliveryConditionIntent.RefreshOnReturn) }
ObserveFlag(
raised = uiState.isSaveSuccessPending,
consumed = DeliveryConditionIntent.ConsumeSuccess,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import com.afternote.core.model.delivery.InactivityPeriod
import com.afternote.core.ui.mvi.UiState

internal data class DeliveryConditionUiState(
val conditionEditRevision: Int = 0,
val savedConditionRevision: Int = 0,
val isLoading: Boolean = false,
val isInitialized: Boolean = false,
val conditionType: DeliveryConditionType = DeliveryConditionType.INACTIVITY,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

/**
Expand All @@ -28,9 +29,12 @@ internal class DeliveryConditionViewModel
private val updateTimeLetterDeliveryCondition: UpdateTimeLetterDeliveryConditionUseCase,
) : MviViewModel<DeliveryConditionIntent, DeliveryConditionUiState, DeliveryConditionReducerEvent>(DeliveryConditionUiState()) {
private val receiverId = route.receiverId
private var loadJob: Job? = null
private var isFirstResume = true

override fun onIntent(intent: DeliveryConditionIntent) {
when (intent) {
DeliveryConditionIntent.RefreshOnReturn -> refreshOnReturn()
is DeliveryConditionIntent.SelectConditionType -> dispatch(DeliveryConditionReducerEvent.ConditionSelected(intent.type))
DeliveryConditionIntent.Save -> onSave()
DeliveryConditionIntent.ConsumeSuccess -> dispatch(DeliveryConditionReducerEvent.SuccessConsumed)
Expand All @@ -48,12 +52,26 @@ internal class DeliveryConditionViewModel

is DeliveryConditionReducerEvent.Loaded -> {
val representative = event.conditions.firstOrNull { it.contentType == DeliveryContentType.TIME_LETTER }
val hasEdits = state.conditionEditRevision != state.savedConditionRevision
state.copy(
isLoading = false,
isInitialized = true,
conditionType = representative?.conditionType ?: DeliveryConditionType.INACTIVITY,
inactivityPeriod = representative?.inactivityPeriod ?: InactivityPeriod.ONE_YEAR,
conditionType =
if (hasEdits) {
state.conditionType
} else {
representative?.conditionType
?: DeliveryConditionType.INACTIVITY
},
inactivityPeriod =
if (hasEdits) {
state.inactivityPeriod
} else {
representative?.inactivityPeriod
?: InactivityPeriod.ONE_YEAR
},
conditions = event.conditions,
error = state.error.takeUnless { it == DeliveryConditionError.LOAD_FAILED },
)
}

Expand All @@ -62,15 +80,22 @@ internal class DeliveryConditionViewModel
}

is DeliveryConditionReducerEvent.ConditionSelected -> {
state.copy(conditionType = event.type)
state.copy(conditionType = event.type, conditionEditRevision = state.conditionEditRevision + 1)
}

DeliveryConditionReducerEvent.Saving -> {
state.copy(isSaving = true)
}

is DeliveryConditionReducerEvent.Saved -> {
state.copy(isSaving = false, conditions = event.conditions, isSaved = true, isSaveSuccessPending = true)
state.copy(
isSaving = false,
conditions = event.conditions,
isSaved = true,
isSaveSuccessPending = true,
savedConditionRevision = event.revision,
error = null,
)
}

DeliveryConditionReducerEvent.SaveFailed -> {
Expand All @@ -86,22 +111,34 @@ internal class DeliveryConditionViewModel
loadDeliveryConditions()
}

private fun loadDeliveryConditions() {
viewModelScope.launch {
dispatch(DeliveryConditionReducerEvent.Loading)
runCatchingCancellable { receiverRepository.getReceiverDeliveryConditions(receiverId) }
.onSuccess { response ->
dispatch(DeliveryConditionReducerEvent.Loaded(response.conditions))
}.onFailure {
dispatch(DeliveryConditionReducerEvent.LoadFailed)
}
private fun refreshOnReturn() {
if (isFirstResume) {
isFirstResume = false
return
}
loadDeliveryConditions(isAutomatic = true)
}

private fun loadDeliveryConditions(isAutomatic: Boolean = false) {
if (loadJob?.isActive == true || currentState.isSaving) return
loadJob =
viewModelScope.launch {
if (!isAutomatic) dispatch(DeliveryConditionReducerEvent.Loading)
runCatchingCancellable { receiverRepository.getReceiverDeliveryConditions(receiverId) }
.onSuccess { response ->
dispatch(DeliveryConditionReducerEvent.Loaded(response.conditions))
}.onFailure {
if (!isAutomatic || !currentState.isInitialized) dispatch(DeliveryConditionReducerEvent.LoadFailed)
}
}
}

private fun onSave() {
val state = currentState
if (!state.isInitialized || state.isSaveLocked) return

loadJob?.cancel()
val savingRevision = state.conditionEditRevision
dispatch(DeliveryConditionReducerEvent.Saving)
viewModelScope.launch {
runCatchingCancellable {
Expand All @@ -112,7 +149,7 @@ internal class DeliveryConditionViewModel
inactivityPeriod = state.inactivityPeriod,
)
}.onSuccess { response ->
dispatch(DeliveryConditionReducerEvent.Saved(response.conditions))
dispatch(DeliveryConditionReducerEvent.Saved(savingRevision, response.conditions))
}.onFailure {
dispatch(DeliveryConditionReducerEvent.SaveFailed)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ internal fun NotificationSettingScreen(
}

LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
viewModel.onIntent(PushNotificationIntent.RefreshDeviceAlarmStatus)
viewModel.onIntent(PushNotificationIntent.RefreshOnReturn)
}

// 소비가 ObserveFlag 의 effect 를 다시 시작시켜도 스낵바가 끊기지 않게 화면 코루틴에 띄운다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import com.afternote.core.ui.mvi.MviIntent
import com.afternote.core.ui.mvi.ReducerEvent

internal sealed interface PushNotificationIntent : MviIntent {
data object RefreshOnReturn : PushNotificationIntent

data object RetryLoad : PushNotificationIntent

/** 기기 알림 허용 여부를 다시 읽는다. 화면 재개와 권한 요청 결과 때 보낸다. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.afternote.feature.setting.presentation.notification
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.afternote.core.ui.popup.NetworkErrorPopup
import com.afternote.core.ui.popup.ServerErrorPopup
Expand All @@ -13,6 +15,7 @@ internal fun PushNotificationScreen(
viewModel: PushNotificationViewModel = hiltViewModel(),
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { viewModel.onIntent(PushNotificationIntent.RefreshOnReturn) }

PushNotificationContent(
uiState = uiState,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.afternote.core.ui.mvi.UiState

internal data class PushNotificationUiState(
val isLoading: Boolean = false,
val hasLoadedPushSettings: Boolean = false,
val errorMessage: UiText? = null,
val isDeviceAlarmOn: Boolean = false,
// 마케팅 알림 (기기 알림 꺼졌을 때)
Expand Down
Loading
Loading