remove usage of context-receivers

This commit is contained in:
2024-08-14 12:04:04 +03:00
parent 0500969d81
commit ee8cf619f8
11 changed files with 98 additions and 112 deletions
@@ -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 }
}
@@ -55,8 +55,7 @@ private inline fun <reified T : KotlinTopLevelExtension> 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"
)
}
}
@@ -25,9 +25,6 @@ fun <T> MutableList<T>.addIf(element: T, condition: () -> Boolean) {
if (condition.invoke()) add(element)
}
context(ViewModel)
fun <T> Flow<T>.listenValue(action: suspend (T) -> Unit) = listenValue(viewModelScope, action)
fun <T> Flow<T>.listenValue(
coroutineScope: CoroutineScope,
action: suspend (T) -> Unit
@@ -75,9 +72,6 @@ fun createTimerFlow(
}
}
context(ViewModel)
fun <T> MutableStateFlow<T>.updateValue(newValue: T) = this.update { newValue }
fun <T> MutableStateFlow<T>.setValue(function: (T) -> T) {
val newValue = function(value)
update { newValue }
@@ -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
}
}
@@ -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
@@ -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)
}
@@ -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 ->
@@ -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 ->
@@ -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<List<VkUser>>(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) {
@@ -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
@@ -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