Use MutableSharedFlow

This commit is contained in:
Andrew 2024-06-18 12:12:25 +09:30
parent 71e7dfb131
commit 90ddc9805a
10 changed files with 59 additions and 58 deletions

View File

@ -18,7 +18,8 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class LandingActivity: BaseActionBarActivity() { class LandingActivity: BaseActionBarActivity() {
@Inject lateinit var prefs: TextSecurePreferences @Inject
internal lateinit var prefs: TextSecurePreferences
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@ -20,8 +20,10 @@ import javax.inject.Inject
@androidx.annotation.OptIn(ExperimentalGetImage::class) @androidx.annotation.OptIn(ExperimentalGetImage::class)
class LoadAccountActivity : BaseActionBarActivity() { class LoadAccountActivity : BaseActionBarActivity() {
@Inject lateinit var prefs: TextSecurePreferences @Inject
@Inject lateinit var loadingManager: LoadingManager internal lateinit var prefs: TextSecurePreferences
@Inject
internal lateinit var loadingManager: LoadingManager
private val viewModel: LinkDeviceViewModel by viewModels() private val viewModel: LinkDeviceViewModel by viewModels()
@ -34,7 +36,7 @@ class LoadAccountActivity : BaseActionBarActivity() {
prefs.setLastProfileUpdateTime(0) prefs.setLastProfileUpdateTime(0)
lifecycleScope.launch { lifecycleScope.launch {
viewModel.eventFlow.collect { viewModel.events.collect {
loadingManager.load(it.mnemonic) loadingManager.load(it.mnemonic)
startMessageNotificationsActivity() startMessageNotificationsActivity()
finish() finish()
@ -43,7 +45,7 @@ class LoadAccountActivity : BaseActionBarActivity() {
setComposeContent { setComposeContent {
val state by viewModel.stateFlow.collectAsState() val state by viewModel.stateFlow.collectAsState()
LoadAccountScreen(state, viewModel.qrErrorsFlow, viewModel::onChange, viewModel::onContinue, viewModel::onScanQrCode) LoadAccountScreen(state, viewModel.qrErrors, viewModel::onChange, viewModel::onContinue, viewModel::onScanQrCode)
} }
} }
} }

View File

@ -4,12 +4,11 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
@ -20,7 +19,7 @@ import org.session.libsignal.utilities.Hex
import org.thoughtcrime.securesms.crypto.MnemonicUtilities import org.thoughtcrime.securesms.crypto.MnemonicUtilities
import javax.inject.Inject import javax.inject.Inject
class LinkDeviceEvent(val mnemonic: ByteArray) class LoadAccountEvent(val mnemonic: ByteArray)
internal data class State( internal data class State(
val recoveryPhrase: String = "", val recoveryPhrase: String = "",
@ -34,12 +33,11 @@ internal class LinkDeviceViewModel @Inject constructor(
private val state = MutableStateFlow(State()) private val state = MutableStateFlow(State())
val stateFlow = state.asStateFlow() val stateFlow = state.asStateFlow()
private val event = Channel<LinkDeviceEvent>() private val _events = MutableSharedFlow<LoadAccountEvent>()
val eventFlow = event.receiveAsFlow().take(1) val events = _events.asSharedFlow()
private val qrErrors = Channel<Throwable>() private val _qrErrors = MutableSharedFlow<Throwable>()
val qrErrors = _qrErrors.asSharedFlow()
val qrErrorsFlow = qrErrors.receiveAsFlow()
.mapNotNull { application.getString(R.string.qrNotRecoveryPassword) } .mapNotNull { application.getString(R.string.qrNotRecoveryPassword) }
private val codec by lazy { MnemonicCodec { MnemonicUtilities.loadFileContents(getApplication(), it) } } private val codec by lazy { MnemonicCodec { MnemonicUtilities.loadFileContents(getApplication(), it) } }
@ -64,7 +62,7 @@ internal class LinkDeviceViewModel @Inject constructor(
state.value = State(recoveryPhrase) state.value = State(recoveryPhrase)
} }
private fun onSuccess(seed: ByteArray) { private fun onSuccess(seed: ByteArray) {
viewModelScope.launch { event.send(LinkDeviceEvent(seed)) } viewModelScope.launch { _events.emit(LoadAccountEvent(seed)) }
} }
private fun onFailure(error: Throwable) { private fun onFailure(error: Throwable) {
@ -80,7 +78,7 @@ internal class LinkDeviceViewModel @Inject constructor(
} }
private fun onQrCodeScanFailure(error: Throwable) { private fun onQrCodeScanFailure(error: Throwable) {
viewModelScope.launch { qrErrors.send(error) } viewModelScope.launch { _qrErrors.emit(error) }
} }
private fun runDecodeCatching(mnemonic: String) = runCatching { private fun runDecodeCatching(mnemonic: String) = runCatching {

View File

@ -22,10 +22,10 @@ import javax.inject.Inject
class LoadingActivity: BaseActionBarActivity() { class LoadingActivity: BaseActionBarActivity() {
@Inject @Inject
lateinit var configFactory: ConfigFactory internal lateinit var configFactory: ConfigFactory
@Inject @Inject
lateinit var prefs: TextSecurePreferences internal lateinit var prefs: TextSecurePreferences
private val viewModel: LoadingViewModel by viewModels() private val viewModel: LoadingViewModel by viewModels()
@ -52,14 +52,14 @@ class LoadingActivity: BaseActionBarActivity() {
ApplicationContext.getInstance(this).newAccount = false ApplicationContext.getInstance(this).newAccount = false
setComposeContent { setComposeContent {
val state by viewModel.stateFlow.collectAsState() val state by viewModel.states.collectAsState()
LoadingScreen(state) LoadingScreen(state)
} }
setUpActionBarSessionLogo(true) setUpActionBarSessionLogo(true)
lifecycleScope.launch { lifecycleScope.launch {
viewModel.eventFlow.collect { viewModel.events.collect {
when (it) { when (it) {
Event.TIMEOUT -> register(loadFailed = true) Event.TIMEOUT -> register(loadFailed = true)
Event.SUCCESS -> register(loadFailed = false) Event.SUCCESS -> register(loadFailed = false)

View File

@ -17,12 +17,10 @@ import javax.inject.Singleton
@Singleton @Singleton
class LoadingManager @Inject constructor( class LoadingManager @Inject constructor(
@dagger.hilt.android.qualifiers.ApplicationContext val context: Context, @dagger.hilt.android.qualifiers.ApplicationContext private val context: Context,
val configFactory: ConfigFactory, private val configFactory: ConfigFactory,
val prefs: TextSecurePreferences private val prefs: TextSecurePreferences
) { ) {
val isLoading: Boolean get() = restoreJob?.isActive == true
private val database: LokiAPIDatabaseProtocol private val database: LokiAPIDatabaseProtocol
get() = SnodeModule.shared.storage get() = SnodeModule.shared.storage

View File

@ -5,14 +5,14 @@ import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.timeout import kotlinx.coroutines.flow.timeout
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.TextSecurePreferences
@ -29,15 +29,15 @@ private val TIMEOUT_TIME = 15.seconds
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
@HiltViewModel @HiltViewModel
class LoadingViewModel @Inject constructor( internal class LoadingViewModel @Inject constructor(
val prefs: TextSecurePreferences val prefs: TextSecurePreferences
): ViewModel() { ): ViewModel() {
private val state = MutableStateFlow(State(TIMEOUT_TIME)) private val _states = MutableStateFlow(State(TIMEOUT_TIME))
val stateFlow = state.asStateFlow() val states = _states.asStateFlow()
private val event = Channel<Event>() private val _events = MutableSharedFlow<Event>()
val eventFlow = event.receiveAsFlow() val events = _events.asSharedFlow()
init { init {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
@ -56,13 +56,13 @@ class LoadingViewModel @Inject constructor(
} }
private suspend fun onSuccess() { private suspend fun onSuccess() {
state.value = State(ANIMATE_TO_DONE_TIME) _states.value = State(ANIMATE_TO_DONE_TIME)
delay(IDLE_DONE_TIME) delay(IDLE_DONE_TIME)
event.send(Event.SUCCESS) _events.emit(Event.SUCCESS)
} }
private fun onFail() { private fun onFail() {
event.trySend(Event.TIMEOUT) _events.tryEmit(Event.TIMEOUT)
} }
} }

View File

@ -39,12 +39,12 @@ class MessageNotificationsActivity : BaseActionBarActivity() {
@Composable @Composable
private fun MessageNotificationsScreen() { private fun MessageNotificationsScreen() {
val state by viewModel.stateFlow.collectAsState() val state by viewModel.states.collectAsState()
MessageNotificationsScreen(state, viewModel::setEnabled, ::register) MessageNotificationsScreen(state, viewModel::setEnabled, ::register)
} }
private fun register() { private fun register() {
prefs.setPushEnabled(viewModel.stateFlow.value.pushEnabled) prefs.setPushEnabled(viewModel.states.value.pushEnabled)
ApplicationContext.getInstance(this).startPollingIfNeeded() ApplicationContext.getInstance(this).startPollingIfNeeded()
pushRegistry.refresh(true) pushRegistry.refresh(true)

View File

@ -8,12 +8,12 @@ import kotlinx.coroutines.flow.update
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class MessageNotificationsViewModel @Inject constructor(): ViewModel() { internal class MessageNotificationsViewModel @Inject constructor(): ViewModel() {
private val state = MutableStateFlow(MessageNotificationsState()) private val _states = MutableStateFlow(MessageNotificationsState())
val stateFlow = state.asStateFlow() val states = _states.asStateFlow()
fun setEnabled(enabled: Boolean) { fun setEnabled(enabled: Boolean) {
state.update { MessageNotificationsState(pushEnabled = enabled) } _states.update { MessageNotificationsState(pushEnabled = enabled) }
} }
} }

View File

@ -23,8 +23,10 @@ private const val EXTRA_LOAD_FAILED = "extra_load_failed"
@AndroidEntryPoint @AndroidEntryPoint
class PickDisplayNameActivity : BaseActionBarActivity() { class PickDisplayNameActivity : BaseActionBarActivity() {
@Inject lateinit var viewModelFactory: PickDisplayNameViewModel.AssistedFactory @Inject
@Inject lateinit var prefs: TextSecurePreferences internal lateinit var viewModelFactory: PickDisplayNameViewModel.AssistedFactory
@Inject
internal lateinit var prefs: TextSecurePreferences
private val loadFailed get() = intent.getBooleanExtra(EXTRA_LOAD_FAILED, false) private val loadFailed get() = intent.getBooleanExtra(EXTRA_LOAD_FAILED, false)
@ -41,7 +43,7 @@ class PickDisplayNameActivity : BaseActionBarActivity() {
if (!loadFailed) prefs.setHasViewedSeed(false) if (!loadFailed) prefs.setHasViewedSeed(false)
lifecycleScope.launch { lifecycleScope.launch {
viewModel.eventFlow.collect { viewModel.events.collect {
if (loadFailed) startHomeActivity() else startMessageNotificationsActivity() if (loadFailed) startHomeActivity() else startMessageNotificationsActivity()
} }
} }
@ -49,7 +51,7 @@ class PickDisplayNameActivity : BaseActionBarActivity() {
@Composable @Composable
private fun DisplayNameScreen(viewModel: PickDisplayNameViewModel) { private fun DisplayNameScreen(viewModel: PickDisplayNameViewModel) {
val state = viewModel.stateFlow.collectAsState() val state = viewModel.states.collectAsState()
DisplayName(state.value, viewModel::onChange) { viewModel.onContinue(this) } DisplayName(state.value, viewModel::onChange) { viewModel.onContinue(this) }
} }
} }

View File

@ -7,10 +7,10 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import network.loki.messenger.R import network.loki.messenger.R
@ -23,28 +23,28 @@ import org.session.libsignal.utilities.hexEncodedPublicKey
import org.thoughtcrime.securesms.crypto.KeyPairUtilities import org.thoughtcrime.securesms.crypto.KeyPairUtilities
import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.dependencies.ConfigFactory
class PickDisplayNameViewModel( internal class PickDisplayNameViewModel(
pickNewName: Boolean, pickNewName: Boolean,
private val prefs: TextSecurePreferences, private val prefs: TextSecurePreferences,
private val configFactory: ConfigFactory private val configFactory: ConfigFactory
): ViewModel() { ): ViewModel() {
private val state = MutableStateFlow(if (pickNewName) pickNewNameState() else State()) private val _states = MutableStateFlow(if (pickNewName) pickNewNameState() else State())
val stateFlow = state.asStateFlow() val states = _states.asStateFlow()
private val event = Channel<Event>() private val _events = MutableSharedFlow<Event>()
val eventFlow = event.receiveAsFlow() val events = _events.asSharedFlow()
private val database: LokiAPIDatabaseProtocol private val database: LokiAPIDatabaseProtocol
get() = SnodeModule.shared.storage get() = SnodeModule.shared.storage
fun onContinue(context: Context) { fun onContinue(context: Context) {
state.update { it.copy(displayName = it.displayName.trim()) } _states.update { it.copy(displayName = it.displayName.trim()) }
val displayName = state.value.displayName val displayName = _states.value.displayName
when { when {
displayName.isEmpty() -> { state.update { it.copy(error = R.string.displayNameErrorDescription) } } displayName.isEmpty() -> { _states.update { it.copy(error = R.string.displayNameErrorDescription) } }
displayName.length > NAME_PADDED_LENGTH -> { state.update { it.copy(error = R.string.displayNameErrorDescriptionShorter) } } displayName.length > NAME_PADDED_LENGTH -> { _states.update { it.copy(error = R.string.displayNameErrorDescriptionShorter) } }
else -> { else -> {
prefs.setProfileName(displayName) prefs.setProfileName(displayName)
@ -66,13 +66,13 @@ class PickDisplayNameViewModel(
prefs.setLocalNumber(userHexEncodedPublicKey) prefs.setLocalNumber(userHexEncodedPublicKey)
prefs.setRestorationTime(0) prefs.setRestorationTime(0)
viewModelScope.launch { event.send(Event.DONE) } viewModelScope.launch { _events.emit(Event.DONE) }
} }
} }
} }
fun onChange(value: String) { fun onChange(value: String) {
state.update { state -> _states.update { state ->
state.copy( state.copy(
displayName = value, displayName = value,
error = value.takeIf { it.length > NAME_PADDED_LENGTH }?.let { R.string.displayNameErrorDescriptionShorter } error = value.takeIf { it.length > NAME_PADDED_LENGTH }?.let { R.string.displayNameErrorDescriptionShorter }