diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt index b9b183687282..38404ec96b75 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/AccountScreen.kt @@ -33,8 +33,8 @@ import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.navigation.popUpTo import com.ramcosta.composedestinations.result.NavResult import com.ramcosta.composedestinations.result.ResultRecipient +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.R @@ -153,7 +153,7 @@ fun Account( @Composable fun AccountScreen( uiState: AccountUiState, - uiSideEffect: SharedFlow, + uiSideEffect: Flow, onCopyAccountNumber: (String) -> Unit = {}, onRedeemVoucherClick: () -> Unit = {}, onManageAccountClick: () -> Unit = {}, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt index 387559bd376b..eda867480246 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/AccountViewModel.kt @@ -3,11 +3,12 @@ package net.mullvad.mullvadvpn.viewmodel import android.app.Activity import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.state.PaymentState @@ -29,9 +30,8 @@ class AccountViewModel( private val paymentUseCase: PaymentUseCase, deviceRepository: DeviceRepository ) : ViewModel() { - - private val _uiSideEffect = MutableSharedFlow(extraBufferCapacity = 1) - val uiSideEffect = _uiSideEffect.asSharedFlow() + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() val uiState: StateFlow = combine( @@ -57,7 +57,7 @@ class AccountViewModel( fun onManageAccountClick() { viewModelScope.launch { - _uiSideEffect.tryEmit( + _uiSideEffect.send( UiSideEffect.OpenAccountManagementPageInBrowser( serviceConnectionManager.authTokenCache()?.fetchAuthToken() ?: "" ) @@ -67,11 +67,11 @@ class AccountViewModel( fun onLogoutClick() { accountRepository.logout() - viewModelScope.launch { _uiSideEffect.emit(UiSideEffect.NavigateToLogin) } + viewModelScope.launch { _uiSideEffect.send(UiSideEffect.NavigateToLogin) } } fun onCopyAccountNumber(accountNumber: String) { - viewModelScope.launch { _uiSideEffect.emit(UiSideEffect.CopyAccountNumber(accountNumber)) } + viewModelScope.launch { _uiSideEffect.send(UiSideEffect.CopyAccountNumber(accountNumber)) } } fun startBillingPayment(productId: ProductId, activityProvider: () -> Activity) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt index 694c396928d9..976eed1270e2 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ConnectViewModel.kt @@ -3,14 +3,14 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.emptyFlow @@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -55,8 +56,8 @@ class ConnectViewModel( private val outOfTimeUseCase: OutOfTimeUseCase, private val paymentUseCase: PaymentUseCase ) : ViewModel() { - private val _uiSideEffect = MutableSharedFlow(extraBufferCapacity = 1) - val uiSideEffect = _uiSideEffect.asSharedFlow() + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() private val _shared: SharedFlow = serviceConnectionManager.connectionState @@ -138,7 +139,7 @@ class ConnectViewModel( viewModelScope.launch { // This once we get isOutOfTime true we will navigate to OutOfTime view. outOfTimeUseCase.isOutOfTime().first { it == true } - _uiSideEffect.emit(UiSideEffect.OutOfTime) + _uiSideEffect.send(UiSideEffect.OutOfTime) } viewModelScope.launch { @@ -179,7 +180,7 @@ class ConnectViewModel( fun onManageAccountClick() { viewModelScope.launch { - _uiSideEffect.tryEmit( + _uiSideEffect.send( UiSideEffect.OpenAccountManagementPageInBrowser( serviceConnectionManager.authTokenCache()?.fetchAuthToken() ?: "" ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModel.kt index 19a458114abf..48a8782d0493 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DeviceListViewModel.kt @@ -5,14 +5,15 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.onSubscription +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -35,8 +36,8 @@ class DeviceListViewModel( ) : ViewModel() { private val _loadingDevices = MutableStateFlow>(emptyList()) - private val _uiSideEffect: MutableSharedFlow = MutableSharedFlow() - val uiSideEffect: SharedFlow = _uiSideEffect + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() private var cachedDeviceList: List? = null @@ -85,7 +86,7 @@ class DeviceListViewModel( clearLoadingDevice(deviceIdToRemove) if (result == null) { - _uiSideEffect.emit( + _uiSideEffect.send( DeviceListSideEffect.ShowToast( resources.getString(R.string.failed_to_remove_device) ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt index ed3bff396cf0..b931d4a7ba3f 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt @@ -5,14 +5,15 @@ import androidx.lifecycle.viewModelScope import java.net.InetAddress import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.constant.EMPTY_STRING @@ -80,8 +81,8 @@ class DnsDialogViewModel( createViewState(_ipAddressInput.value, vmState.value) ) - private val _uiSideEffect = MutableSharedFlow() - val uiSideEffect: SharedFlow = _uiSideEffect + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() private fun createViewState(ipAddress: String, vmState: DnsDialogViewModelState) = DnsDialogViewState( @@ -126,7 +127,7 @@ class DnsDialogViewModel( } } - _uiSideEffect.emit(DnsDialogSideEffect.Complete) + _uiSideEffect.send(DnsDialogSideEffect.Complete) } fun onRemoveDnsClick() = @@ -134,7 +135,7 @@ class DnsDialogViewModel( repository.updateCustomDnsList { it.filter { it.hostAddress != uiState.value.ipAddress } } - _uiSideEffect.emit(DnsDialogSideEffect.Complete) + _uiSideEffect.send(DnsDialogSideEffect.Complete) } private fun String.isValidIp(): Boolean { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModel.kt index f6a5b516ab91..3f95d7919399 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/FilterViewModel.kt @@ -2,13 +2,14 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel 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.first +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.state.RelayFilterState @@ -23,8 +24,8 @@ import net.mullvad.mullvadvpn.usecase.RelayListFilterUseCase class FilterViewModel( private val relayListFilterUseCase: RelayListFilterUseCase, ) : ViewModel() { - private val _uiSideEffect = MutableSharedFlow() - val uiSideEffect = _uiSideEffect.asSharedFlow() + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() private val selectedOwnership = MutableStateFlow(null) private val selectedProviders = MutableStateFlow>(emptyList()) @@ -101,7 +102,7 @@ class FilterViewModel( newSelectedOwnership, newSelectedProviders ) - _uiSideEffect.emit(FilterScreenSideEffect.CloseScreen) + _uiSideEffect.send(FilterScreenSideEffect.CloseScreen) } } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt index f53076525538..2de5d42a05e3 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/LoginViewModel.kt @@ -5,17 +5,17 @@ import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay -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.filter import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -59,8 +59,8 @@ class LoginViewModel( private val _loginState = MutableStateFlow(LoginUiState.INITIAL.loginState) private val _loginInput = MutableStateFlow(LoginUiState.INITIAL.accountNumberInput) - private val _uiSideEffect = MutableSharedFlow(extraBufferCapacity = 1) - val uiSideEffect = _uiSideEffect.asSharedFlow() + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() private val _uiState = combine( @@ -112,9 +112,9 @@ class LoginViewModel( delay(1000) val isOutOfTime = isOutOfTimeDeferred.getOrDefault(false) if (isOutOfTime) { - _uiSideEffect.emit(LoginUiSideEffect.NavigateToOutOfTime) + _uiSideEffect.send(LoginUiSideEffect.NavigateToOutOfTime) } else { - _uiSideEffect.emit(LoginUiSideEffect.NavigateToConnect) + _uiSideEffect.send(LoginUiSideEffect.NavigateToConnect) } } newDeviceNotificationUseCase.newDeviceCreated() @@ -134,7 +134,7 @@ class LoginViewModel( if (refreshResult.isAvailable()) { // Navigate to device list - _uiSideEffect.emit( + _uiSideEffect.send( LoginUiSideEffect.TooManyDevices(AccountToken(accountToken)) ) Idle() @@ -157,7 +157,7 @@ class LoginViewModel( private suspend fun AccountCreationResult.mapToUiState(): LoginState? { return if (this is AccountCreationResult.Success) { - _uiSideEffect.emit(LoginUiSideEffect.NavigateToWelcome) + _uiSideEffect.send(LoginUiSideEffect.NavigateToWelcome) null } else { Idle(LoginError.UnableToCreateAccount) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/MtuDialogViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/MtuDialogViewModel.kt index 067a26c79fed..db324e0b133e 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/MtuDialogViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/MtuDialogViewModel.kt @@ -4,8 +4,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.repository.SettingsRepository import net.mullvad.mullvadvpn.util.isValidMtu @@ -15,21 +16,21 @@ class MtuDialogViewModel( private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) : ViewModel() { - private val _uiSideEffect = MutableSharedFlow() - val uiSideEffect: SharedFlow = _uiSideEffect + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() fun onSaveClick(mtuValue: Int) = viewModelScope.launch(dispatcher) { if (mtuValue.isValidMtu()) { repository.setWireguardMtu(mtuValue) } - _uiSideEffect.emit(MtuDialogSideEffect.Complete) + _uiSideEffect.send(MtuDialogSideEffect.Complete) } fun onRestoreClick() = viewModelScope.launch(dispatcher) { repository.setWireguardMtu(null) - _uiSideEffect.emit(MtuDialogSideEffect.Complete) + _uiSideEffect.send(MtuDialogSideEffect.Complete) } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt index 87c03ab783e9..89fc7fc34e4a 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/OutOfTimeViewModel.kt @@ -2,6 +2,7 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -39,7 +40,11 @@ class OutOfTimeViewModel( private val pollAccountExpiry: Boolean = true, ) : ViewModel() { - private val _uiSideEffect = MutableSharedFlow(replay = 1) + private val _uiSideEffect = + MutableSharedFlow( + extraBufferCapacity = 2, + onBufferOverflow = BufferOverflow.DROP_OLDEST + ) val uiSideEffect = _uiSideEffect.asSharedFlow() val uiState = diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/PrivacyDisclaimerViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/PrivacyDisclaimerViewModel.kt index 5ddb172d7fb7..f8e6b13f3d02 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/PrivacyDisclaimerViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/PrivacyDisclaimerViewModel.kt @@ -2,21 +2,23 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.repository.PrivacyDisclaimerRepository class PrivacyDisclaimerViewModel( private val privacyDisclaimerRepository: PrivacyDisclaimerRepository ) : ViewModel() { + private val _uiSideEffect = - MutableSharedFlow(extraBufferCapacity = 1) - val uiSideEffect = _uiSideEffect.asSharedFlow() + Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() fun setPrivacyDisclosureAccepted() { privacyDisclaimerRepository.setPrivacyDisclosureAccepted() - viewModelScope.launch { _uiSideEffect.emit(PrivacyDisclaimerUiSideEffect.NavigateToLogin) } + viewModelScope.launch { _uiSideEffect.send(PrivacyDisclaimerUiSideEffect.NavigateToLogin) } } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModel.kt index 0a719b55c7d4..52311f82a099 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/ReportProblemViewModel.kt @@ -3,12 +3,13 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.async +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.constant.MINIMUM_LOADING_TIME_MILLIS @@ -55,15 +56,15 @@ class ReportProblemViewModel( } .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), ReportProblemUiState()) - private val _uiSideEffect = MutableSharedFlow() - val uiSideEffect = _uiSideEffect.asSharedFlow() + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() fun sendReport(email: String, description: String, skipEmptyEmailCheck: Boolean = false) { viewModelScope.launch { val userEmail = email.trim() val nullableEmail = if (email.isEmpty()) null else userEmail if (!skipEmptyEmailCheck && shouldShowConfirmNoEmail(nullableEmail)) { - _uiSideEffect.emit(ReportProblemSideEffect.ShowConfirmNoEmail) + _uiSideEffect.send(ReportProblemSideEffect.ShowConfirmNoEmail) } else { sendingState.emit(SendingReportUiState.Sending) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt index e8d61d149be5..dc9d5e7d6fc2 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SelectLocationViewModel.kt @@ -2,12 +2,13 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.state.SelectLocationUiState @@ -80,13 +81,13 @@ class SelectLocationViewModel( SelectLocationUiState.Loading ) - private val _uiSideEffect = MutableSharedFlow() - val uiSideEffect = _uiSideEffect.asSharedFlow() + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() fun selectRelay(relayItem: RelayItem) { relayListUseCase.updateSelectedRelayLocation(relayItem.location) serviceConnectionManager.connectionProxy()?.connect() - viewModelScope.launch { _uiSideEffect.emit(SelectLocationSideEffect.CloseScreen) } + viewModelScope.launch { _uiSideEffect.send(SelectLocationSideEffect.CloseScreen) } } fun onSearchTermInput(searchTerm: String) { diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SplashViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SplashViewModel.kt index 29b1262a537e..8163fb977069 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SplashViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/SplashViewModel.kt @@ -3,11 +3,12 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.async -import kotlinx.coroutines.flow.MutableSharedFlow -import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch import kotlinx.coroutines.selects.onTimeout import kotlinx.coroutines.selects.select @@ -26,16 +27,15 @@ class SplashViewModel( private val messageHandler: MessageHandler, ) : ViewModel() { - private val _uiSideEffect = - MutableSharedFlow(replay = 1, extraBufferCapacity = 1) - val uiSideEffect = _uiSideEffect.asSharedFlow() + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() fun start() { viewModelScope.launch { if (privacyDisclaimerRepository.hasAcceptedPrivacyDisclosure()) { - _uiSideEffect.emit(getStartDestination()) + _uiSideEffect.send(getStartDestination()) } else { - _uiSideEffect.emit(SplashUiSideEffect.NavigateToPrivacyDisclaimer) + _uiSideEffect.send(SplashUiSideEffect.NavigateToPrivacyDisclaimer) } } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt index eb96589af021..80b51a811c31 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt @@ -7,14 +7,15 @@ import androidx.lifecycle.viewModelScope import java.net.InetAddress import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -50,8 +51,8 @@ class VpnSettingsViewModel( private val dispatcher: CoroutineDispatcher = Dispatchers.IO ) : ViewModel() { - private val _uiSideEffect = MutableSharedFlow(extraBufferCapacity = 1) - val uiSideEffect = _uiSideEffect.asSharedFlow() + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() private val customPort = MutableStateFlow?>(null) @@ -122,7 +123,7 @@ class VpnSettingsViewModel( fun onToggleCustomDns(enable: Boolean) { repository.setDnsState(if (enable) DnsState.Custom else DnsState.Default) if (enable && vmState.value.customDnsList.isEmpty()) { - viewModelScope.launch { _uiSideEffect.emit(VpnSettingsSideEffect.NavigateToDnsDialog) } + viewModelScope.launch { _uiSideEffect.send(VpnSettingsSideEffect.NavigateToDnsDialog) } } else { showApplySettingChangesWarningToast() } @@ -259,7 +260,7 @@ class VpnSettingsViewModel( private fun showApplySettingChangesWarningToast() { viewModelScope.launch { - _uiSideEffect.emit( + _uiSideEffect.send( VpnSettingsSideEffect.ShowToast( resources.getString(R.string.settings_changes_effect_warning_short) ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt index 9eb0e4319123..7c77d183caa1 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/WelcomeViewModel.kt @@ -3,17 +3,18 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.channels.BufferOverflow +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharingStarted -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import net.mullvad.mullvadvpn.compose.state.WelcomeUiState @@ -42,11 +43,8 @@ class WelcomeViewModel( private val outOfTimeUseCase: OutOfTimeUseCase, private val pollAccountExpiry: Boolean = true ) : ViewModel() { - private val _uiSideEffect = - MutableSharedFlow( - replay = 1, - ) - val uiSideEffect = _uiSideEffect.asSharedFlow() + private val _uiSideEffect = Channel(1, BufferOverflow.DROP_OLDEST) + val uiSideEffect = _uiSideEffect.receiveAsFlow() val uiState = serviceConnectionManager.connectionState @@ -86,7 +84,7 @@ class WelcomeViewModel( viewModelScope.launch { outOfTimeUseCase.isOutOfTime().first { it == false } paymentUseCase.resetPurchaseResult() - _uiSideEffect.emit(UiSideEffect.OpenConnectScreen) + _uiSideEffect.send(UiSideEffect.OpenConnectScreen) } verifyPurchases() fetchPaymentAvailability() @@ -97,7 +95,7 @@ class WelcomeViewModel( fun onSitePaymentClick() { viewModelScope.launch { - _uiSideEffect.tryEmit( + _uiSideEffect.send( UiSideEffect.OpenAccountView( serviceConnectionManager.authTokenCache()?.fetchAuthToken() ?: "" ) diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt index f29eec05cb2d..2e227c052c6f 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/usecase/OutOfTimeUseCaseTest.kt @@ -85,7 +85,7 @@ class OutOfTimeUseCaseTest { @Test fun `Account that expires without new expiry event`() = runTest { // Arrange - val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusSeconds(2)) + val expiredAccountExpiry = AccountExpiry.Available(DateTime.now().plusSeconds(62)) // Act, Assert outOfTimeUseCase.isOutOfTime().test {