From ee8cf619f8da8226342aebbc9ea9a7a63eb9ac88 Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Wed, 14 Aug 2024 12:04:04 +0300 Subject: [PATCH] remove usage of context-receivers --- .../kotlin/dev/meloda/fast/MainViewModel.kt | 2 +- .../kotlin/dev/meloda/fast/KotlinAndroid.kt | 3 +- .../fast/common/extensions/Extensions.kt | 6 - .../fast/auth/captcha/CaptchaViewModel.kt | 5 +- .../meloda/fast/auth/login/LoginViewModel.kt | 13 +- .../auth/validation/ValidationViewModel.kt | 25 ++-- .../chatmaterials/ChatMaterialsViewModel.kt | 3 +- .../conversations/ConversationsViewModel.kt | 10 +- .../meloda/fast/friends/FriendsViewModel.kt | 124 +++++++++--------- .../MessagesHistoryViewModel.kt | 14 +- .../meloda/fast/profile/ProfileViewModel.kt | 5 +- 11 files changed, 98 insertions(+), 112 deletions(-) diff --git a/app/src/main/kotlin/dev/meloda/fast/MainViewModel.kt b/app/src/main/kotlin/dev/meloda/fast/MainViewModel.kt index 4f8ad0c9..4175b3ea 100644 --- a/app/src/main/kotlin/dev/meloda/fast/MainViewModel.kt +++ b/app/src/main/kotlin/dev/meloda/fast/MainViewModel.kt @@ -152,7 +152,7 @@ class MainViewModelImpl( } private fun listenLongPollState() { - longPollController.stateToApply.listenValue { newState -> + longPollController.stateToApply.listenValue(viewModelScope) { newState -> if (newState == LongPollState.Background) { isNeedToCheckNotificationsPermission.update { true } } diff --git a/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt b/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt index 20e36b56..85193c88 100644 --- a/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt +++ b/build-logic/convention/src/main/kotlin/dev/meloda/fast/KotlinAndroid.kt @@ -55,8 +55,7 @@ private inline fun Project.configureKotlin "-opt-in=kotlin.RequiresOptIn", // Enable experimental coroutines APIs, including Flow "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-opt-in=kotlinx.coroutines.FlowPreview", - "-Xcontext-receivers" + "-opt-in=kotlinx.coroutines.FlowPreview" ) } } diff --git a/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt b/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt index 627acb51..b85befec 100644 --- a/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt +++ b/core/common/src/main/kotlin/dev/meloda/fast/common/extensions/Extensions.kt @@ -25,9 +25,6 @@ fun MutableList.addIf(element: T, condition: () -> Boolean) { if (condition.invoke()) add(element) } -context(ViewModel) -fun Flow.listenValue(action: suspend (T) -> Unit) = listenValue(viewModelScope, action) - fun Flow.listenValue( coroutineScope: CoroutineScope, action: suspend (T) -> Unit @@ -75,9 +72,6 @@ fun createTimerFlow( } } -context(ViewModel) -fun MutableStateFlow.updateValue(newValue: T) = this.update { newValue } - fun MutableStateFlow.setValue(function: (T) -> T) { val newValue = function(value) update { newValue } diff --git a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/captcha/CaptchaViewModel.kt b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/captcha/CaptchaViewModel.kt index e48c54c9..fa5188f8 100644 --- a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/captcha/CaptchaViewModel.kt +++ b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/captcha/CaptchaViewModel.kt @@ -6,7 +6,6 @@ import dev.meloda.fast.auth.captcha.model.CaptchaScreenState import dev.meloda.fast.auth.captcha.navigation.Captcha import dev.meloda.fast.auth.captcha.validation.CaptchaValidator import dev.meloda.fast.common.extensions.setValue -import dev.meloda.fast.common.extensions.updateValue import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update @@ -58,13 +57,13 @@ class CaptchaViewModelImpl( } override fun onNavigatedToLogin() { - screenState.updateValue(CaptchaScreenState.EMPTY) + screenState.update { CaptchaScreenState.EMPTY } isNeedToOpenLogin.update { false } } private fun processValidation(): Boolean { val isValid = validator.validate(screenState.value).isValid() - screenState.updateValue(screenState.value.copy(codeError = !isValid)) + screenState.setValue { old -> old.copy(codeError = !isValid) } return isValid } } diff --git a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt index 321d9b39..c57aaf4d 100644 --- a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt +++ b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/login/LoginViewModel.kt @@ -14,7 +14,6 @@ import dev.meloda.fast.common.LongPollController import dev.meloda.fast.common.VkConstants import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.common.extensions.setValue -import dev.meloda.fast.common.extensions.updateValue import dev.meloda.fast.common.model.LongPollState import dev.meloda.fast.data.State import dev.meloda.fast.data.UserConfig @@ -102,7 +101,7 @@ class LoginViewModelImpl( login = newLogin.trim(), loginError = false ) - screenState.updateValue(newState) + screenState.setValue { newState } } override fun onPasswordInputChanged(newPassword: String) { @@ -110,7 +109,7 @@ class LoginViewModelImpl( password = newPassword.trim(), passwordError = false ) - screenState.updateValue(newState) + screenState.setValue { newState } } override fun onSignInButtonClicked() { @@ -176,7 +175,7 @@ class LoginViewModelImpl( userIds = null, fields = VkConstants.USER_FIELDS, nomCase = null - ).listenValue { state -> + ).listenValue(viewModelScope) { state -> state.processState( error = { error -> UserConfig.currentUserId = -1 @@ -227,7 +226,7 @@ class LoginViewModelImpl( validationCode = validationCode.value, captchaSid = captchaArguments.value?.captchaSid, captchaKey = captchaCode.value - ).listenValue { state -> + ).listenValue(viewModelScope) { state -> state.processState( error = { error -> Log.d("LoginViewModelImpl", "login: error: $error") @@ -354,11 +353,11 @@ class LoginViewModelImpl( validationState.value.forEach { result -> when (result) { LoginValidationResult.LoginEmpty -> { - screenState.updateValue(screenState.value.copy(loginError = true)) + screenState.setValue { old -> old.copy(loginError = true) } } LoginValidationResult.PasswordEmpty -> { - screenState.updateValue(screenState.value.copy(passwordError = true)) + screenState.setValue { old -> old.copy(passwordError = true) } } LoginValidationResult.Empty -> Unit diff --git a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/validation/ValidationViewModel.kt b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/validation/ValidationViewModel.kt index ce7e257c..16e64e00 100644 --- a/feature/auth/src/main/kotlin/dev/meloda/fast/auth/validation/ValidationViewModel.kt +++ b/feature/auth/src/main/kotlin/dev/meloda/fast/auth/validation/ValidationViewModel.kt @@ -10,7 +10,6 @@ import dev.meloda.fast.auth.validation.validation.ValidationValidator import dev.meloda.fast.common.extensions.createTimerFlow import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.common.extensions.setValue -import dev.meloda.fast.common.extensions.updateValue import dev.meloda.fast.data.processState import dev.meloda.fast.domain.AuthUseCase import kotlinx.coroutines.Job @@ -77,12 +76,12 @@ class ValidationViewModelImpl( } override fun onCodeInputChanged(newCode: String) { - screenState.updateValue( - screenState.value.copy( + screenState.setValue { old -> + old.copy( code = newCode.trim(), codeError = false ) - ) + } if (newCode.length == 6) { viewModelScope.launch { @@ -116,7 +115,7 @@ class ValidationViewModelImpl( } override fun onNavigatedToLogin() { - screenState.updateValue(ValidationScreenState.EMPTY) + screenState.update { ValidationScreenState.EMPTY } isNeedToOpenLogin.update { false } } @@ -132,7 +131,7 @@ class ValidationViewModelImpl( val sid = validationSid ?: return authUseCase.validatePhone(sid) - .listenValue { state -> + .listenValue(viewModelScope) { state -> state.processState( error = { error -> @@ -164,21 +163,13 @@ class ValidationViewModelImpl( delayJob = createTimerFlow( time = delay, onStartAction = { - screenState.updateValue( - screenState.value.copy(isSmsButtonVisible = false) - ) + screenState.setValue { old -> old.copy(isSmsButtonVisible = false) } }, onTickAction = { remainedTime -> - screenState.updateValue( - screenState.value.copy(delayTime = remainedTime) - ) + screenState.setValue { old -> old.copy(delayTime = remainedTime) } }, onTimeoutAction = { - screenState.updateValue( - screenState.value.copy( - isSmsButtonVisible = true - ) - ) + screenState.setValue { old -> old.copy(isSmsButtonVisible = true) } }, ).launchIn(viewModelScope) } diff --git a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/ChatMaterialsViewModel.kt b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/ChatMaterialsViewModel.kt index b3ff3460..12638fff 100644 --- a/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/ChatMaterialsViewModel.kt +++ b/feature/chatmaterials/src/main/kotlin/dev/meloda/fast/chatmaterials/ChatMaterialsViewModel.kt @@ -2,6 +2,7 @@ package dev.meloda.fast.chatmaterials import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dev.meloda.fast.chatmaterials.model.ChatMaterialsScreenState import dev.meloda.fast.chatmaterials.navigation.ChatMaterials import dev.meloda.fast.chatmaterials.util.asPresentation @@ -83,7 +84,7 @@ class ChatMaterialsViewModelImpl( offset = offset, attachmentTypes = listOf(screenState.value.attachmentType), conversationMessageId = screenState.value.conversationMessageId - ).listenValue { state -> + ).listenValue(viewModelScope) { state -> state.processState( error = { error -> diff --git a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/ConversationsViewModel.kt b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/ConversationsViewModel.kt index c25506fd..78cc31d3 100644 --- a/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/ConversationsViewModel.kt +++ b/feature/conversations/src/main/kotlin/dev/meloda/fast/conversations/ConversationsViewModel.kt @@ -88,7 +88,7 @@ class ConversationsViewModelImpl( }.stateIn(viewModelScope, SharingStarted.Eagerly, 0) init { - userSettings.useContactNames.listenValue(::updateConversationsNames) + userSettings.useContactNames.listenValue(viewModelScope, ::updateConversationsNames) updatesParser.onNewMessage(::handleNewMessage) updatesParser.onMessageEdited(::handleEditedMessage) @@ -227,7 +227,7 @@ class ConversationsViewModelImpl( offset: Int = currentOffset.value ) { conversationsUseCase.getConversations(count = LOAD_COUNT, offset = offset) - .listenValue { state -> + .listenValue(viewModelScope) { state -> state.processState( error = { error -> if (error is State.Error.ApiError) { @@ -288,7 +288,7 @@ class ConversationsViewModelImpl( } private fun deleteConversation(peerId: Int) { - conversationsUseCase.delete(peerId).listenValue { state -> + conversationsUseCase.delete(peerId).listenValue(viewModelScope) { state -> state.processState( error = { error -> @@ -314,7 +314,7 @@ class ConversationsViewModelImpl( private fun pinConversation(peerId: Int, pin: Boolean) { conversationsUseCase.changePinState(peerId, pin) - .listenValue { state -> + .listenValue(viewModelScope) { state -> state.processState( error = { error -> @@ -578,7 +578,7 @@ class ConversationsViewModelImpl( messagesUseCase.markAsRead( peerId = peerId, startMessageId = startMessageId - ).listenValue { state -> + ).listenValue(viewModelScope) { state -> state.processState( error = { error -> diff --git a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/FriendsViewModel.kt b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/FriendsViewModel.kt index e5464c43..2dcbeb75 100644 --- a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/FriendsViewModel.kt +++ b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/FriendsViewModel.kt @@ -1,12 +1,13 @@ package dev.meloda.fast.friends import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.common.extensions.setValue import dev.meloda.fast.data.State -import dev.meloda.fast.domain.FriendsUseCase import dev.meloda.fast.data.processState import dev.meloda.fast.datastore.UserSettings +import dev.meloda.fast.domain.FriendsUseCase import dev.meloda.fast.friends.model.FriendsScreenState import dev.meloda.fast.friends.util.asPresentation import dev.meloda.fast.model.BaseError @@ -47,7 +48,7 @@ class FriendsViewModelImpl( private val friends = MutableStateFlow>(emptyList()) init { - userSettings.useContactNames.listenValue(::updateFriendsNames) + userSettings.useContactNames.listenValue(viewModelScope, ::updateFriendsNames) loadFriends() } @@ -66,71 +67,72 @@ class FriendsViewModelImpl( } private fun loadFriends(offset: Int = currentOffset.value) { - friendsUseCase.getAllFriends(count = LOAD_COUNT, offset = offset).listenValue { state -> - state.processState( - error = { error -> - if (error is State.Error.ApiError) { - when (error.errorCode) { - VkErrorCode.USER_AUTHORIZATION_FAILED -> { - baseError.setValue { BaseError.SessionExpired } + friendsUseCase.getAllFriends(count = LOAD_COUNT, offset = offset) + .listenValue(viewModelScope) { state -> + state.processState( + error = { error -> + if (error is State.Error.ApiError) { + when (error.errorCode) { + VkErrorCode.USER_AUTHORIZATION_FAILED -> { + baseError.setValue { BaseError.SessionExpired } + } + + else -> Unit } + } + }, + success = { info -> + val response = info.friends + val itemsCountSufficient = response.size == LOAD_COUNT + canPaginate.setValue { itemsCountSufficient } - else -> Unit + val paginationExhausted = !itemsCountSufficient && + screenState.value.friends.size >= LOAD_COUNT + + imagesToPreload.setValue { + response.mapNotNull(VkUser::photo100) + } + + friendsUseCase.storeUsers(response) + + val loadedFriends = response.map { + it.asPresentation(userSettings.useContactNames.value) + } + val loadedOnlineFriends = info.onlineFriends.map { + it.asPresentation(userSettings.useContactNames.value) + } + + val newState = screenState.value.copy( + isPaginationExhausted = paginationExhausted + ) + + if (offset == 0) { + friends.emit(response) + screenState.setValue { + newState.copy( + friends = loadedFriends, + onlineFriends = loadedOnlineFriends + ) + } + } else { + friends.emit(friends.value.plus(response)) + screenState.setValue { + newState.copy( + friends = newState.friends.plus(loadedFriends), + onlineFriends = newState.onlineFriends.plus(loadedOnlineFriends) + ) + } } } - }, - success = { info -> - val response = info.friends - val itemsCountSufficient = response.size == LOAD_COUNT - canPaginate.setValue { itemsCountSufficient } - - val paginationExhausted = !itemsCountSufficient && - screenState.value.friends.size >= LOAD_COUNT - - imagesToPreload.setValue { - response.mapNotNull(VkUser::photo100) - } - - friendsUseCase.storeUsers(response) - - val loadedFriends = response.map { - it.asPresentation(userSettings.useContactNames.value) - } - val loadedOnlineFriends = info.onlineFriends.map { - it.asPresentation(userSettings.useContactNames.value) - } - - val newState = screenState.value.copy( - isPaginationExhausted = paginationExhausted - ) - - if (offset == 0) { - friends.emit(response) - screenState.setValue { - newState.copy( - friends = loadedFriends, - onlineFriends = loadedOnlineFriends - ) - } - } else { - friends.emit(friends.value.plus(response)) - screenState.setValue { - newState.copy( - friends = newState.friends.plus(loadedFriends), - onlineFriends = newState.onlineFriends.plus(loadedOnlineFriends) - ) - } - } - } - ) - - screenState.setValue { old -> - old.copy( - isLoading = offset == 0 && state.isLoading(), - isPaginating = offset > 0 && state.isLoading() ) + + screenState.setValue { old -> + old.copy( + isLoading = offset == 0 && state.isLoading(), + isPaginating = offset > 0 && state.isLoading() + ) + } } - } } private fun updateFriendsNames(useContactNames: Boolean) { diff --git a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt index dc9bf628..0fa197f6 100644 --- a/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt +++ b/feature/messageshistory/src/main/kotlin/dev/meloda/fast/messageshistory/MessagesHistoryViewModel.kt @@ -11,7 +11,6 @@ import com.conena.nanokt.collections.indexOfOrNull import com.conena.nanokt.text.isEmptyOrBlank import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.common.extensions.setValue -import dev.meloda.fast.common.extensions.updateValue import dev.meloda.fast.common.provider.ResourceProvider import dev.meloda.fast.data.UserConfig import dev.meloda.fast.data.VkMemoryCache @@ -97,7 +96,10 @@ class MessagesHistoryViewModelImpl( updatesParser.onMessageIncomingRead(::handleReadIncomingEvent) updatesParser.onMessageOutgoingRead(::handleReadOutgoingEvent) - userSettings.showTimeInActionMessages.listenValue(::toggleShowTimeInActionMessages) + userSettings.showTimeInActionMessages.listenValue( + viewModelScope, + ::toggleShowTimeInActionMessages + ) } override fun onRefresh() { @@ -117,9 +119,7 @@ class MessagesHistoryViewModelImpl( ) } - screenState.value.copy(message = newText).let { newValue -> - screenState.updateValue(newValue) - } + screenState.setValue { old -> old.copy(message = newText) } } override fun onEmojiButtonClicked() { @@ -236,7 +236,7 @@ class MessagesHistoryViewModelImpl( conversationId = screenState.value.conversationId, count = MESSAGES_LOAD_COUNT, offset = offset, - ).listenValue { state -> + ).listenValue(viewModelScope) { state -> state.processState( error = { error -> @@ -375,7 +375,7 @@ class MessagesHistoryViewModelImpl( message = newMessage.text, replyTo = null, attachments = null - ).listenValue { state -> + ).listenValue(viewModelScope) { state -> state.processState( error = { error -> sendingMessages -= newMessage diff --git a/feature/profile/src/main/kotlin/dev/meloda/fast/profile/ProfileViewModel.kt b/feature/profile/src/main/kotlin/dev/meloda/fast/profile/ProfileViewModel.kt index 45bfd335..1e345cb9 100644 --- a/feature/profile/src/main/kotlin/dev/meloda/fast/profile/ProfileViewModel.kt +++ b/feature/profile/src/main/kotlin/dev/meloda/fast/profile/ProfileViewModel.kt @@ -1,6 +1,7 @@ package dev.meloda.fast.profile import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import dev.meloda.fast.common.VkConstants import dev.meloda.fast.common.extensions.listenValue import dev.meloda.fast.common.extensions.setValue @@ -32,7 +33,7 @@ class ProfileViewModelImpl( private fun getLocalAccountInfo() { usersUseCase.getLocalUser(UserConfig.userId) - .listenValue { state -> + .listenValue(viewModelScope) { state -> state.processState( error = { error -> if (error is State.Error.ApiError) { @@ -70,7 +71,7 @@ class ProfileViewModelImpl( userIds = null, fields = VkConstants.USER_FIELDS, nomCase = null - ).listenValue { state -> + ).listenValue(viewModelScope) { state -> state.processState( error = { error -> // TODO: 12/07/2024, Danil Nikolaev: if local info is null then show error view