refactor StateFlow exposure in ConvosViewModel

This commit is contained in:
2026-05-30 12:01:06 +03:00
parent d428af4ac4
commit 167f980f29
2 changed files with 79 additions and 78 deletions
@@ -55,33 +55,34 @@ class ConvosViewModel(
private val applicationContext: Context, private val applicationContext: Context,
private val loadConvosByIdUseCase: LoadConvosByIdUseCase private val loadConvosByIdUseCase: LoadConvosByIdUseCase
) : ViewModel() { ) : ViewModel() {
private val _screenState = MutableStateFlow(ConvosScreenState.EMPTY)
val screenState = _screenState.asStateFlow()
private val _navigation = MutableStateFlow<ConvoNavigation?>(null) private val screenState = MutableStateFlow(ConvosScreenState.EMPTY)
val navigation = _navigation.asStateFlow() val screenStateFlow get() = screenState.asStateFlow()
private val _dialog = MutableStateFlow<ConvoDialog?>(null) private val navigation = MutableStateFlow<ConvoNavigation?>(null)
val dialog = _dialog.asStateFlow() val navigationFlow get() = navigation.asStateFlow()
private val _convos = MutableStateFlow<List<VkConvo>>(emptyList()) private val dialog = MutableStateFlow<ConvoDialog?>(null)
val convos = _convos.asStateFlow() val dialogFlow get() = dialog.asStateFlow()
private val _uiConvos = MutableStateFlow<List<UiConvo>>(emptyList()) private val convos = MutableStateFlow<List<VkConvo>>(emptyList())
val uiConvos = _uiConvos.asStateFlow() val convosFlow get() = convos.asStateFlow()
private val pinnedConvosCount = convos.map { convos -> private val uiConvos = MutableStateFlow<List<UiConvo>>(emptyList())
val uiConvosFlow get() = uiConvos.asStateFlow()
private val pinnedConvosCount = convosFlow.map { convos ->
convos.count(VkConvo::isPinned) convos.count(VkConvo::isPinned)
}.stateIn(viewModelScope, SharingStarted.Eagerly, 0) }.stateIn(viewModelScope, SharingStarted.Eagerly, 0)
private val _baseError = MutableStateFlow<BaseError?>(null) private val baseError = MutableStateFlow<BaseError?>(null)
val baseError = _baseError.asStateFlow() val baseErrorFlow get() = baseError.asStateFlow()
private val _currentOffset = MutableStateFlow(0) private val currentOffset = MutableStateFlow(0)
val currentOffset = _currentOffset.asStateFlow() val currentOffsetFlow get() = currentOffset.asStateFlow()
private val _canPaginate = MutableStateFlow(false) private val canPaginate = MutableStateFlow(false)
val canPaginate = _canPaginate.asStateFlow() val canPaginateFlow get() = canPaginate.asStateFlow()
private val expandedConvoId = MutableStateFlow(0L) private val expandedConvoId = MutableStateFlow(0L)
@@ -90,7 +91,7 @@ class ConvosViewModel(
private val interactionsTimers = hashMapOf<Long, InteractionJob?>() private val interactionsTimers = hashMapOf<Long, InteractionJob?>()
init { init {
_screenState.updateValue { copy(isArchive = filter == ConvosFilter.ARCHIVE) } screenState.updateValue { copy(isArchive = filter == ConvosFilter.ARCHIVE) }
loadConvos() loadConvos()
@@ -110,7 +111,7 @@ class ConvosViewModel(
} }
fun onNavigationConsumed() { fun onNavigationConsumed() {
_navigation.setValue { null } navigation.setValue { null }
} }
fun onDialogConfirmed(dialog: ConvoDialog, bundle: Bundle) { fun onDialogConfirmed(dialog: ConvoDialog, bundle: Bundle) {
@@ -143,7 +144,7 @@ class ConvosViewModel(
} }
fun onDialogDismissed(dialog: ConvoDialog) { fun onDialogDismissed(dialog: ConvoDialog) {
_dialog.setValue { null } this.dialog.setValue { null }
} }
fun onDialogItemPicked(dialog: ConvoDialog, bundle: Bundle) { fun onDialogItemPicked(dialog: ConvoDialog, bundle: Bundle) {
@@ -157,7 +158,7 @@ class ConvosViewModel(
} }
fun onErrorButtonClicked() { fun onErrorButtonClicked() {
when (baseError.value) { when (baseErrorFlow.value) {
null -> Unit null -> Unit
is BaseError.ConnectionError, is BaseError.ConnectionError,
@@ -170,7 +171,7 @@ class ConvosViewModel(
} }
fun onPaginationConditionsMet() { fun onPaginationConditionsMet() {
_currentOffset.update { convos.value.size } currentOffset.update { convosFlow.value.size }
loadConvos() loadConvos()
} }
@@ -181,7 +182,7 @@ class ConvosViewModel(
fun onConvoItemClick(convo: UiConvo) { fun onConvoItemClick(convo: UiConvo) {
collapseConvos() collapseConvos()
_navigation.setValue { ConvoNavigation.MessagesHistory(peerId = convo.id) } navigation.setValue { ConvoNavigation.MessagesHistory(peerId = convo.id) }
} }
fun onConvoItemLongClick(convo: UiConvo) { fun onConvoItemLongClick(convo: UiConvo) {
@@ -198,7 +199,7 @@ class ConvosViewModel(
) { ) {
when (option) { when (option) {
ConvoOption.Delete -> { ConvoOption.Delete -> {
_dialog.setValue { ConvoDialog.ConvoDelete(convo.id) } dialog.setValue { ConvoDialog.ConvoDelete(convo.id) }
} }
ConvoOption.MarkAsRead -> { ConvoOption.MarkAsRead -> {
@@ -212,37 +213,37 @@ class ConvosViewModel(
} }
ConvoOption.Pin -> { ConvoOption.Pin -> {
_dialog.setValue { ConvoDialog.ConvoPin(convo.id) } dialog.setValue { ConvoDialog.ConvoPin(convo.id) }
} }
ConvoOption.Unpin -> { ConvoOption.Unpin -> {
_dialog.setValue { ConvoDialog.ConvoUnpin(convo.id) } dialog.setValue { ConvoDialog.ConvoUnpin(convo.id) }
} }
ConvoOption.Archive -> { ConvoOption.Archive -> {
_dialog.setValue { ConvoDialog.ConvoArchive(convo.id) } dialog.setValue { ConvoDialog.ConvoArchive(convo.id) }
} }
ConvoOption.Unarchive -> { ConvoOption.Unarchive -> {
_dialog.setValue { ConvoDialog.ConvoUnarchive(convo.id) } dialog.setValue { ConvoDialog.ConvoUnarchive(convo.id) }
} }
} }
} }
fun onErrorConsumed() { fun onErrorConsumed() {
_baseError.setValue { null } baseError.setValue { null }
} }
fun setScrollIndex(index: Int) { fun setScrollIndex(index: Int) {
_screenState.setValue { old -> old.copy(scrollIndex = index) } screenState.setValue { old -> old.copy(scrollIndex = index) }
} }
fun setScrollOffset(offset: Int) { fun setScrollOffset(offset: Int) {
_screenState.setValue { old -> old.copy(scrollOffset = offset) } screenState.setValue { old -> old.copy(scrollOffset = offset) }
} }
fun onCreateChatButtonClicked() { fun onCreateChatButtonClicked() {
_navigation.setValue { ConvoNavigation.CreateChat } navigation.setValue { ConvoNavigation.CreateChat }
} }
private fun collapseConvos() { private fun collapseConvos() {
@@ -251,7 +252,7 @@ class ConvosViewModel(
} }
private fun loadConvos( private fun loadConvos(
offset: Int = currentOffset.value offset: Int = currentOffsetFlow.value
) { ) {
convoUseCase.getConvos( convoUseCase.getConvos(
count = LOAD_COUNT, count = LOAD_COUNT,
@@ -261,22 +262,22 @@ class ConvosViewModel(
state.processState( state.processState(
error = { error -> error = { error ->
val newBaseError = VkUtils.parseError(error) val newBaseError = VkUtils.parseError(error)
_baseError.update { newBaseError } baseError.update { newBaseError }
}, },
success = { response -> success = { response ->
val convos = response val convos = response
val fullConvos = if (offset == 0) { val fullConvos = if (offset == 0) {
convos convos
} else { } else {
this.convos.value.plus(convos) this.convosFlow.value.plus(convos)
} }
val itemsCountSufficient = response.size == LOAD_COUNT val itemsCountSufficient = response.size == LOAD_COUNT
val paginationExhausted = !itemsCountSufficient && val paginationExhausted = !itemsCountSufficient &&
this.convos.value.isNotEmpty() this.convosFlow.value.isNotEmpty()
_screenState.updateValue { screenState.updateValue {
copy(isPaginationExhausted = paginationExhausted) copy(isPaginationExhausted = paginationExhausted)
} }
@@ -293,13 +294,13 @@ class ConvosViewModel(
convoUseCase.storeConvos(response) convoUseCase.storeConvos(response)
_convos.emit(fullConvos) this.convos.emit(fullConvos)
syncUiConvos() syncUiConvos()
_canPaginate.setValue { itemsCountSufficient } canPaginate.setValue { itemsCountSufficient }
} }
) )
_screenState.setValue { old -> screenState.setValue { old ->
old.copy( old.copy(
isLoading = offset == 0 && state.isLoading(), isLoading = offset == 0 && state.isLoading(),
isPaginating = offset > 0 && state.isLoading() isPaginating = offset > 0 && state.isLoading()
@@ -313,17 +314,17 @@ class ConvosViewModel(
state.processState( state.processState(
error = {}, error = {},
success = { success = {
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
val convoIndex = val convoIndex =
newConvos.indexOfFirstOrNull { it.id == peerId } newConvos.indexOfFirstOrNull { it.id == peerId }
?: return@processState ?: return@processState
newConvos.removeAt(convoIndex) newConvos.removeAt(convoIndex)
_convos.update { newConvos.sorted() } convos.update { newConvos.sorted() }
syncUiConvos() syncUiConvos()
} }
) )
_screenState.emit(screenState.value.copy(isLoading = state.isLoading())) screenState.emit(screenStateFlow.value.copy(isLoading = state.isLoading()))
} }
} }
@@ -346,7 +347,7 @@ class ConvosViewModel(
} }
) )
_screenState.setValue { old -> old.copy(isLoading = state.isLoading()) } screenState.setValue { old -> old.copy(isLoading = state.isLoading()) }
} }
} }
@@ -356,7 +357,7 @@ class ConvosViewModel(
state.processState( state.processState(
error = {}, error = {},
success = { success = {
convos.value.find { it.id == peerId }?.let { convo -> convosFlow.value.find { it.id == peerId }?.let { convo ->
handleChatArchived( handleChatArchived(
LongPollParsedEvent.ChatArchived( LongPollParsedEvent.ChatArchived(
convo = convo, convo = convo,
@@ -373,7 +374,7 @@ class ConvosViewModel(
private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) { private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) {
val message = event.message val message = event.message
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
val convoIndex = val convoIndex =
newConvos.indexOfFirstOrNull { it.id == message.peerId } newConvos.indexOfFirstOrNull { it.id == message.peerId }
@@ -392,7 +393,7 @@ class ConvosViewModel(
.copy(lastMessage = message) .copy(lastMessage = message)
newConvos.add(pinnedConvosCount.value, convo) newConvos.add(pinnedConvosCount.value, convo)
_convos.update { newConvos.sorted() } convos.update { newConvos.sorted() }
syncUiConvos() syncUiConvos()
} }
) )
@@ -433,14 +434,14 @@ class ConvosViewModel(
newConvos.add(toPosition, newConvo) newConvos.add(toPosition, newConvo)
} }
_convos.update { newConvos.sorted() } convos.update { newConvos.sorted() }
syncUiConvos() syncUiConvos()
} }
} }
private fun handleEditedMessage(event: LongPollParsedEvent.MessageEdited) { private fun handleEditedMessage(event: LongPollParsedEvent.MessageEdited) {
val message = event.message val message = event.message
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
val convoIndex = newConvos.indexOfFirstOrNull { it.id == message.peerId } val convoIndex = newConvos.indexOfFirstOrNull { it.id == message.peerId }
if (convoIndex == null) { // диалога нет в списке if (convoIndex == null) { // диалога нет в списке
@@ -452,13 +453,13 @@ class ConvosViewModel(
lastMessageId = message.id, lastMessageId = message.id,
lastCmId = message.cmId lastCmId = message.cmId
) )
_convos.update { newConvos } convos.update { newConvos }
syncUiConvos() syncUiConvos()
} }
} }
private fun handleReadIncomingMessage(event: LongPollParsedEvent.IncomingMessageRead) { private fun handleReadIncomingMessage(event: LongPollParsedEvent.IncomingMessageRead) {
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
val convoIndex = val convoIndex =
newConvos.indexOfFirstOrNull { it.id == event.peerId } newConvos.indexOfFirstOrNull { it.id == event.peerId }
@@ -472,13 +473,13 @@ class ConvosViewModel(
unreadCount = event.unreadCount unreadCount = event.unreadCount
) )
_convos.update { newConvos } convos.update { newConvos }
syncUiConvos() syncUiConvos()
} }
} }
private fun handleReadOutgoingMessage(event: LongPollParsedEvent.OutgoingMessageRead) { private fun handleReadOutgoingMessage(event: LongPollParsedEvent.OutgoingMessageRead) {
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
val convoIndex = val convoIndex =
newConvos.indexOfFirstOrNull { it.id == event.peerId } newConvos.indexOfFirstOrNull { it.id == event.peerId }
@@ -492,7 +493,7 @@ class ConvosViewModel(
unreadCount = event.unreadCount unreadCount = event.unreadCount
) )
_convos.update { newConvos } convos.update { newConvos }
syncUiConvos() syncUiConvos()
} }
} }
@@ -502,7 +503,7 @@ class ConvosViewModel(
val peerId = event.peerId val peerId = event.peerId
val userIds = event.userIds val userIds = event.userIds
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
val convoAndIndex = val convoAndIndex =
newConvos.findWithIndex { it.id == peerId } newConvos.findWithIndex { it.id == peerId }
@@ -513,7 +514,7 @@ class ConvosViewModel(
interactionIds = userIds interactionIds = userIds
) )
_convos.update { newConvos } convos.update { newConvos }
syncUiConvos() syncUiConvos()
interactionsTimers[peerId]?.let { interactionJob -> interactionsTimers[peerId]?.let { interactionJob ->
@@ -545,7 +546,7 @@ class ConvosViewModel(
private fun stopInteraction(peerId: Long, interactionJob: InteractionJob) { private fun stopInteraction(peerId: Long, interactionJob: InteractionJob) {
interactionsTimers[peerId] ?: return interactionsTimers[peerId] ?: return
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
val convoAndIndex = val convoAndIndex =
newConvos.findWithIndex { it.id == peerId } ?: return newConvos.findWithIndex { it.id == peerId } ?: return
@@ -555,7 +556,7 @@ class ConvosViewModel(
interactionIds = emptyList() interactionIds = emptyList()
) )
_convos.update { newConvos } convos.update { newConvos }
syncUiConvos() syncUiConvos()
interactionJob.timerJob.cancel() interactionJob.timerJob.cancel()
@@ -563,7 +564,7 @@ class ConvosViewModel(
} }
private fun handleChatMajorChanged(event: LongPollParsedEvent.ChatMajorChanged) { private fun handleChatMajorChanged(event: LongPollParsedEvent.ChatMajorChanged) {
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
val convoIndex = val convoIndex =
newConvos.indexOfFirstOrNull { it.id == event.peerId } newConvos.indexOfFirstOrNull { it.id == event.peerId }
@@ -573,13 +574,13 @@ class ConvosViewModel(
newConvos[convoIndex] = newConvos[convoIndex] =
newConvos[convoIndex].copy(majorId = event.majorId) newConvos[convoIndex].copy(majorId = event.majorId)
_convos.setValue { newConvos.sorted() } convos.setValue { newConvos.sorted() }
syncUiConvos() syncUiConvos()
} }
} }
private fun handleChatMinorChanged(event: LongPollParsedEvent.ChatMinorChanged) { private fun handleChatMinorChanged(event: LongPollParsedEvent.ChatMinorChanged) {
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
val convoIndex = val convoIndex =
newConvos.indexOfFirstOrNull { it.id == event.peerId } newConvos.indexOfFirstOrNull { it.id == event.peerId }
@@ -589,13 +590,13 @@ class ConvosViewModel(
newConvos[convoIndex] = newConvos[convoIndex] =
newConvos[convoIndex].copy(minorId = event.minorId) newConvos[convoIndex].copy(minorId = event.minorId)
_convos.setValue { newConvos.sorted() } convos.setValue { newConvos.sorted() }
syncUiConvos() syncUiConvos()
} }
} }
private fun handleChatClearing(event: LongPollParsedEvent.ChatCleared) { private fun handleChatClearing(event: LongPollParsedEvent.ChatCleared) {
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
val convoIndex = newConvos.indexOfFirstOrNull { it.id == event.peerId } val convoIndex = newConvos.indexOfFirstOrNull { it.id == event.peerId }
@@ -604,7 +605,7 @@ class ConvosViewModel(
} else { } else {
newConvos.removeAt(convoIndex) newConvos.removeAt(convoIndex)
_convos.setValue { newConvos.sorted() } convos.setValue { newConvos.sorted() }
syncUiConvos() syncUiConvos()
} }
} }
@@ -612,7 +613,7 @@ class ConvosViewModel(
private fun handleChatArchived(event: LongPollParsedEvent.ChatArchived) { private fun handleChatArchived(event: LongPollParsedEvent.ChatArchived) {
val convo = event.convo val convo = event.convo
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
when (filter) { when (filter) {
ConvosFilter.BUSINESS_NOTIFY -> Unit ConvosFilter.BUSINESS_NOTIFY -> Unit
@@ -627,7 +628,7 @@ class ConvosViewModel(
newConvos.removeAt(index) newConvos.removeAt(index)
} }
_convos.update { newConvos } convos.update { newConvos }
syncUiConvos() syncUiConvos()
} }
@@ -641,7 +642,7 @@ class ConvosViewModel(
newConvos.add(pinnedConvosCount.value, convo) newConvos.add(pinnedConvosCount.value, convo)
} }
_convos.update { newConvos.sorted() } convos.update { newConvos.sorted() }
syncUiConvos() syncUiConvos()
} }
} }
@@ -655,7 +656,7 @@ class ConvosViewModel(
state.processState( state.processState(
error = {}, error = {},
success = { success = {
val newConvos = convos.value.toMutableList() val newConvos = convosFlow.value.toMutableList()
val convoIndex = val convoIndex =
newConvos.indexOfFirstOrNull { it.id == peerId } newConvos.indexOfFirstOrNull { it.id == peerId }
?: return@listenValue ?: return@listenValue
@@ -663,7 +664,7 @@ class ConvosViewModel(
newConvos[convoIndex] = newConvos[convoIndex] =
newConvos[convoIndex].copy(inRead = startMessageId) newConvos[convoIndex].copy(inRead = startMessageId)
_convos.update { newConvos } convos.update { newConvos }
syncUiConvos() syncUiConvos()
} }
) )
@@ -695,7 +696,7 @@ class ConvosViewModel(
} }
private fun syncUiConvos(): List<UiConvo> { private fun syncUiConvos(): List<UiConvo> {
val convos = convos.value val convos = convosFlow.value
val newUiConvos = convos.map { convo -> val newUiConvos = convos.map { convo ->
val options = mutableListOf<ConvoOption>() val options = mutableListOf<ConvoOption>()
@@ -705,7 +706,7 @@ class ConvosViewModel(
} }
} }
val convosSize = this.convos.value.size val convosSize = this.convosFlow.value.size
val pinnedCount = pinnedConvosCount.value val pinnedCount = pinnedConvosCount.value
val canPinOneMoreDialog = val canPinOneMoreDialog =
@@ -735,7 +736,7 @@ class ConvosViewModel(
options = options.toImmutableList() options = options.toImmutableList()
) )
} }
_uiConvos.setValue { newUiConvos } uiConvos.setValue { newUiConvos }
return newUiConvos return newUiConvos
} }
@@ -19,12 +19,12 @@ fun ConvosRoute(
onNavigateToArchive: (() -> Unit)? = null, onNavigateToArchive: (() -> Unit)? = null,
onScrolledToTop: () -> Unit, onScrolledToTop: () -> Unit,
) { ) {
val screenState by viewModel.screenState.collectAsStateWithLifecycle() val screenState by viewModel.screenStateFlow.collectAsStateWithLifecycle()
val navigationEvent by viewModel.navigation.collectAsStateWithLifecycle() val navigationEvent by viewModel.navigationFlow.collectAsStateWithLifecycle()
val convos by viewModel.uiConvos.collectAsStateWithLifecycle() val convos by viewModel.uiConvosFlow.collectAsStateWithLifecycle()
val dialog by viewModel.dialog.collectAsStateWithLifecycle() val dialog by viewModel.dialogFlow.collectAsStateWithLifecycle()
val baseError by viewModel.baseError.collectAsStateWithLifecycle() val baseError by viewModel.baseErrorFlow.collectAsStateWithLifecycle()
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle() val canPaginate by viewModel.canPaginateFlow.collectAsStateWithLifecycle()
LaunchedEffect(navigationEvent) { LaunchedEffect(navigationEvent) {
val shouldBeConsumed: Boolean = when (val navigation = navigationEvent) { val shouldBeConsumed: Boolean = when (val navigation = navigationEvent) {