forked from melod1n/fast-messenger
release/0.1.6 (#103)
* Bump com.google.guava:guava from 33.3.1-jre to 33.4.0-jre (#97) * Bump coroutines from 1.9.0 to 1.10.1 (#100) * some improvements + loading conversation on new message if it is not already in the list * Bump koin from 4.0.0 to 4.0.1 (#101) * minor update --------- Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -26,8 +26,8 @@ import dev.meloda.fast.provider.ApiLanguageProvider
|
|||||||
import dev.meloda.fast.service.longpolling.di.longPollModule
|
import dev.meloda.fast.service.longpolling.di.longPollModule
|
||||||
import dev.meloda.fast.settings.di.settingsModule
|
import dev.meloda.fast.settings.di.settingsModule
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
import org.koin.core.module.dsl.viewModelOf
|
||||||
import org.koin.core.qualifier.qualifier
|
import org.koin.core.qualifier.qualifier
|
||||||
import org.koin.dsl.bind
|
import org.koin.dsl.bind
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
@@ -61,7 +61,7 @@ val applicationModule = module {
|
|||||||
qualifier = qualifier("main")
|
qualifier = qualifier("main")
|
||||||
}
|
}
|
||||||
|
|
||||||
single {
|
single<ImageLoader> {
|
||||||
ImageLoader.Builder(get())
|
ImageLoader.Builder(get())
|
||||||
.crossfade(true)
|
.crossfade(true)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package dev.meloda.fast.common.extensions
|
package dev.meloda.fast.common.extensions
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|||||||
@@ -20,18 +20,20 @@ sealed class State<out T> {
|
|||||||
|
|
||||||
data object ConnectionError : Error()
|
data object ConnectionError : Error()
|
||||||
|
|
||||||
data object Unknown : Error()
|
data object UnknownError : Error()
|
||||||
|
|
||||||
data object InternalError : Error()
|
data object InternalError : Error()
|
||||||
|
|
||||||
data class OAuthError(val error: OAuthErrorDomain) : Error()
|
data class OAuthError(val error: OAuthErrorDomain) : Error()
|
||||||
|
|
||||||
|
data class TestError(val message: String) : Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isLoading(): Boolean = this is Loading
|
fun isLoading(): Boolean = this is Loading
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
val UNKNOWN_ERROR = Error.Unknown
|
val UNKNOWN_ERROR = Error.UnknownError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,7 +75,8 @@ fun <T : Any> ApiResult<T, RestApiErrorDomain>.mapToState() = when (this) {
|
|||||||
is ApiResult.Failure.ApiFailure -> this.error.toStateApiError()
|
is ApiResult.Failure.ApiFailure -> this.error.toStateApiError()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <T : Any, N> ApiResult<T, RestApiErrorDomain>.mapToState(successMapper: (T) -> N) = when (this) {
|
fun <T : Any, N> ApiResult<T, RestApiErrorDomain>.mapToState(successMapper: (T) -> N) =
|
||||||
|
when (this) {
|
||||||
is ApiResult.Success -> State.Success(successMapper(this.value))
|
is ApiResult.Success -> State.Success(successMapper(this.value))
|
||||||
|
|
||||||
is ApiResult.Failure.NetworkFailure -> State.Error.ConnectionError
|
is ApiResult.Failure.NetworkFailure -> State.Error.ConnectionError
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class OAuthUseCaseImpl(
|
|||||||
forceSms = forceSms
|
forceSms = forceSms
|
||||||
)
|
)
|
||||||
|
|
||||||
|
kotlin.runCatching {
|
||||||
val error = response.error?.let(VkOAuthError::parse)
|
val error = response.error?.let(VkOAuthError::parse)
|
||||||
val errorType = response.errorType?.let(VkOAuthErrorType::parse)
|
val errorType = response.errorType?.let(VkOAuthErrorType::parse)
|
||||||
|
|
||||||
@@ -120,5 +121,12 @@ class OAuthUseCaseImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
emit(newState)
|
emit(newState)
|
||||||
|
}.fold(
|
||||||
|
onSuccess = {
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
emit(State.Error.TestError(it.stackTraceToString()))
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ enum class ValidationType(val value: String) {
|
|||||||
SMS("2fa_sms");
|
SMS("2fa_sms");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun parse(value: String): ValidationType = entries.first { it.value == value }
|
fun parse(value: String): ValidationType =
|
||||||
|
entries.firstOrNull { it.value == value }
|
||||||
|
?: throw IllegalArgumentException("Unknown validation type $value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -345,6 +345,13 @@ class LoginViewModelImpl(
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is State.Error.TestError -> {
|
||||||
|
val message = stateError.message
|
||||||
|
val error = LoginError.SimpleError(message = message)
|
||||||
|
loginError.update { error }
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,5 @@ sealed class LoginError {
|
|||||||
data object TooManyTries : LoginError()
|
data object TooManyTries : LoginError()
|
||||||
data object WrongValidationCode : LoginError()
|
data object WrongValidationCode : LoginError()
|
||||||
data object WrongValidationCodeFormat : LoginError()
|
data object WrongValidationCodeFormat : LoginError()
|
||||||
|
data class SimpleError(val message: String): LoginError()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,8 +50,6 @@ import androidx.compose.ui.text.input.TextFieldValue
|
|||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import dev.meloda.fast.auth.login.LoginViewModel
|
|
||||||
import dev.meloda.fast.auth.login.LoginViewModelImpl
|
|
||||||
import dev.meloda.fast.auth.login.model.CaptchaArguments
|
import dev.meloda.fast.auth.login.model.CaptchaArguments
|
||||||
import dev.meloda.fast.auth.login.model.LoginError
|
import dev.meloda.fast.auth.login.model.LoginError
|
||||||
import dev.meloda.fast.auth.login.model.LoginScreenState
|
import dev.meloda.fast.auth.login.model.LoginScreenState
|
||||||
@@ -441,5 +439,14 @@ fun HandleError(
|
|||||||
confirmText = stringResource(id = UiR.string.ok)
|
confirmText = stringResource(id = UiR.string.ok)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is LoginError.SimpleError -> {
|
||||||
|
MaterialDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = "Error",
|
||||||
|
text = error.message,
|
||||||
|
confirmText = stringResource(id = UiR.string.ok)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+126
-20
@@ -1,8 +1,11 @@
|
|||||||
package dev.meloda.fast.conversations
|
package dev.meloda.fast.conversations
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.request.ImageRequest
|
||||||
import com.conena.nanokt.collections.indexOfFirstOrNull
|
import com.conena.nanokt.collections.indexOfFirstOrNull
|
||||||
import dev.meloda.fast.common.extensions.createTimerFlow
|
import dev.meloda.fast.common.extensions.createTimerFlow
|
||||||
import dev.meloda.fast.common.extensions.findWithIndex
|
import dev.meloda.fast.common.extensions.findWithIndex
|
||||||
@@ -18,6 +21,7 @@ import dev.meloda.fast.data.State
|
|||||||
import dev.meloda.fast.data.processState
|
import dev.meloda.fast.data.processState
|
||||||
import dev.meloda.fast.datastore.UserSettings
|
import dev.meloda.fast.datastore.UserSettings
|
||||||
import dev.meloda.fast.domain.ConversationsUseCase
|
import dev.meloda.fast.domain.ConversationsUseCase
|
||||||
|
import dev.meloda.fast.domain.LoadConversationsByIdUseCase
|
||||||
import dev.meloda.fast.domain.LongPollUpdatesParser
|
import dev.meloda.fast.domain.LongPollUpdatesParser
|
||||||
import dev.meloda.fast.domain.MessagesUseCase
|
import dev.meloda.fast.domain.MessagesUseCase
|
||||||
import dev.meloda.fast.model.BaseError
|
import dev.meloda.fast.model.BaseError
|
||||||
@@ -43,7 +47,6 @@ interface ConversationsViewModel {
|
|||||||
|
|
||||||
val screenState: StateFlow<ConversationsScreenState>
|
val screenState: StateFlow<ConversationsScreenState>
|
||||||
val baseError: StateFlow<BaseError?>
|
val baseError: StateFlow<BaseError?>
|
||||||
val imagesToPreload: StateFlow<List<String>>
|
|
||||||
val currentOffset: StateFlow<Int>
|
val currentOffset: StateFlow<Int>
|
||||||
val canPaginate: StateFlow<Boolean>
|
val canPaginate: StateFlow<Boolean>
|
||||||
val scrollToTop: StateFlow<Boolean>
|
val scrollToTop: StateFlow<Boolean>
|
||||||
@@ -78,16 +81,23 @@ class ConversationsViewModelImpl(
|
|||||||
private val conversationsUseCase: ConversationsUseCase,
|
private val conversationsUseCase: ConversationsUseCase,
|
||||||
private val messagesUseCase: MessagesUseCase,
|
private val messagesUseCase: MessagesUseCase,
|
||||||
private val resources: Resources,
|
private val resources: Resources,
|
||||||
private val userSettings: UserSettings
|
private val userSettings: UserSettings,
|
||||||
|
private val imageLoader: ImageLoader,
|
||||||
|
private val applicationContext: Context,
|
||||||
|
private val loadConversationsByIdUseCase: LoadConversationsByIdUseCase
|
||||||
) : ConversationsViewModel, ViewModel() {
|
) : ConversationsViewModel, ViewModel() {
|
||||||
|
|
||||||
override val screenState = MutableStateFlow(ConversationsScreenState.EMPTY)
|
override val screenState = MutableStateFlow(ConversationsScreenState.EMPTY)
|
||||||
override val baseError = MutableStateFlow<BaseError?>(null)
|
override val baseError = MutableStateFlow<BaseError?>(null)
|
||||||
override val imagesToPreload = MutableStateFlow<List<String>>(emptyList())
|
|
||||||
override val currentOffset = MutableStateFlow(0)
|
override val currentOffset = MutableStateFlow(0)
|
||||||
override val canPaginate = MutableStateFlow(false)
|
override val canPaginate = MutableStateFlow(false)
|
||||||
override val scrollToTop = MutableStateFlow(false)
|
override val scrollToTop = MutableStateFlow(false)
|
||||||
|
|
||||||
|
// TODO: 22-Dec-24, Danil Nikolaev: rewrite
|
||||||
|
private val useContactNames = {
|
||||||
|
userSettings.useContactNames.value
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPaginationConditionsMet() {
|
override fun onPaginationConditionsMet() {
|
||||||
currentOffset.update { screenState.value.conversations.size }
|
currentOffset.update { screenState.value.conversations.size }
|
||||||
loadConversations()
|
loadConversations()
|
||||||
@@ -281,9 +291,17 @@ class ConversationsViewModelImpl(
|
|||||||
val paginationExhausted = !itemsCountSufficient &&
|
val paginationExhausted = !itemsCountSufficient &&
|
||||||
screenState.value.conversations.isNotEmpty()
|
screenState.value.conversations.isNotEmpty()
|
||||||
|
|
||||||
imagesToPreload.setValue {
|
val imagesToPreload =
|
||||||
response.mapNotNull { it.extractAvatar().extractUrl() }
|
response.mapNotNull { it.extractAvatar().extractUrl() }
|
||||||
|
|
||||||
|
imagesToPreload.forEach { url ->
|
||||||
|
imageLoader.enqueue(
|
||||||
|
ImageRequest.Builder(applicationContext)
|
||||||
|
.data(url)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
conversationsUseCase.storeConversations(response)
|
conversationsUseCase.storeConversations(response)
|
||||||
|
|
||||||
val loadedConversations = response.map {
|
val loadedConversations = response.map {
|
||||||
@@ -337,7 +355,12 @@ class ConversationsViewModelImpl(
|
|||||||
conversations.update { newConversations }
|
conversations.update { newConversations }
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
conversations = newConversations.map { it.asPresentation(resources) }
|
conversations = newConversations.map {
|
||||||
|
it.asPresentation(
|
||||||
|
resources = resources,
|
||||||
|
useContactName = useContactNames()
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -373,13 +396,40 @@ class ConversationsViewModelImpl(
|
|||||||
|
|
||||||
private fun handleNewMessage(event: LongPollEvent.VkMessageNewEvent) {
|
private fun handleNewMessage(event: LongPollEvent.VkMessageNewEvent) {
|
||||||
val message = event.message
|
val message = event.message
|
||||||
|
|
||||||
val newConversations = conversations.value.toMutableList()
|
val newConversations = conversations.value.toMutableList()
|
||||||
val conversationIndex =
|
val conversationIndex =
|
||||||
newConversations.indexOfFirstOrNull { it.id == message.peerId }
|
newConversations.indexOfFirstOrNull { it.id == message.peerId }
|
||||||
|
|
||||||
if (conversationIndex == null) { // диалога нет в списке
|
if (conversationIndex == null) {
|
||||||
// pizdets
|
loadConversationsByIdUseCase(peerIds = listOf(message.peerId))
|
||||||
// TODO: 04/07/2024, Danil Nikolaev: load conversation and store info
|
.listenValue(viewModelScope) { state ->
|
||||||
|
state.processState(
|
||||||
|
error = { error ->
|
||||||
|
|
||||||
|
},
|
||||||
|
success = { response ->
|
||||||
|
val conversation = (response.firstOrNull() ?: return@listenValue)
|
||||||
|
.copy(lastMessage = message)
|
||||||
|
|
||||||
|
// TODO: 22-Dec-24, Danil Nikolaev: handle interactions and pinned state
|
||||||
|
|
||||||
|
newConversations.add(pinnedConversationsCount.value, conversation)
|
||||||
|
conversations.update { newConversations }
|
||||||
|
|
||||||
|
screenState.setValue { old ->
|
||||||
|
old.copy(
|
||||||
|
conversations = newConversations.map {
|
||||||
|
it.asPresentation(
|
||||||
|
resources = resources,
|
||||||
|
useContactName = useContactNames()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val conversation = newConversations[conversationIndex]
|
val conversation = newConversations[conversationIndex]
|
||||||
var newConversation = conversation.copy(
|
var newConversation = conversation.copy(
|
||||||
@@ -420,7 +470,12 @@ class ConversationsViewModelImpl(
|
|||||||
|
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
conversations = newConversations.map { it.asPresentation(resources) }
|
conversations = newConversations.map {
|
||||||
|
it.asPresentation(
|
||||||
|
resources = resources,
|
||||||
|
useContactName = useContactNames()
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -444,7 +499,12 @@ class ConversationsViewModelImpl(
|
|||||||
|
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
conversations = newConversations.map { it.asPresentation(resources) }
|
conversations = newConversations.map {
|
||||||
|
it.asPresentation(
|
||||||
|
resources = resources,
|
||||||
|
useContactName = useContactNames()
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -454,8 +514,11 @@ class ConversationsViewModelImpl(
|
|||||||
val newConversations = conversations.value.toMutableList()
|
val newConversations = conversations.value.toMutableList()
|
||||||
|
|
||||||
val conversationIndex =
|
val conversationIndex =
|
||||||
newConversations.indexOfFirstOrNull { it.id == event.peerId } ?: return
|
newConversations.indexOfFirstOrNull { it.id == event.peerId }
|
||||||
|
|
||||||
|
if (conversationIndex == null) { // диалога нет в списке
|
||||||
|
// pizdets
|
||||||
|
} else {
|
||||||
newConversations[conversationIndex] =
|
newConversations[conversationIndex] =
|
||||||
newConversations[conversationIndex].copy(
|
newConversations[conversationIndex].copy(
|
||||||
inRead = event.messageId,
|
inRead = event.messageId,
|
||||||
@@ -466,17 +529,26 @@ class ConversationsViewModelImpl(
|
|||||||
|
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
conversations = newConversations.map { it.asPresentation(resources) }
|
conversations = newConversations.map {
|
||||||
|
it.asPresentation(
|
||||||
|
resources = resources,
|
||||||
|
useContactName = useContactNames()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleReadOutgoingMessage(event: LongPollEvent.VkMessageReadOutgoingEvent) {
|
private fun handleReadOutgoingMessage(event: LongPollEvent.VkMessageReadOutgoingEvent) {
|
||||||
val newConversations = conversations.value.toMutableList()
|
val newConversations = conversations.value.toMutableList()
|
||||||
|
|
||||||
val conversationIndex =
|
val conversationIndex =
|
||||||
newConversations.indexOfFirstOrNull { it.id == event.peerId } ?: return
|
newConversations.indexOfFirstOrNull { it.id == event.peerId }
|
||||||
|
|
||||||
|
if (conversationIndex == null) { // диалога нет в списке
|
||||||
|
// pizdets
|
||||||
|
} else {
|
||||||
newConversations[conversationIndex] =
|
newConversations[conversationIndex] =
|
||||||
newConversations[conversationIndex].copy(
|
newConversations[conversationIndex].copy(
|
||||||
outRead = event.messageId,
|
outRead = event.messageId,
|
||||||
@@ -486,9 +558,15 @@ class ConversationsViewModelImpl(
|
|||||||
conversations.update { newConversations }
|
conversations.update { newConversations }
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
conversations = newConversations.map { it.asPresentation(resources) }
|
conversations = newConversations.map {
|
||||||
|
it.asPresentation(
|
||||||
|
resources = resources,
|
||||||
|
useContactName = useContactNames()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handlePinStateChanged(event: LongPollEvent.VkConversationPinStateChangedEvent) {
|
private fun handlePinStateChanged(event: LongPollEvent.VkConversationPinStateChangedEvent) {
|
||||||
@@ -496,8 +574,11 @@ class ConversationsViewModelImpl(
|
|||||||
val newConversations = conversations.value.toMutableList()
|
val newConversations = conversations.value.toMutableList()
|
||||||
|
|
||||||
val conversationIndex =
|
val conversationIndex =
|
||||||
newConversations.indexOfFirstOrNull { it.id == event.peerId } ?: return
|
newConversations.indexOfFirstOrNull { it.id == event.peerId }
|
||||||
|
|
||||||
|
if (conversationIndex == null) { // диалога нет в списке
|
||||||
|
// pizdets
|
||||||
|
} else {
|
||||||
val pin = event.majorId > 0
|
val pin = event.majorId > 0
|
||||||
|
|
||||||
val conversation = newConversations[conversationIndex].copy(majorId = event.majorId)
|
val conversation = newConversations[conversationIndex].copy(majorId = event.majorId)
|
||||||
@@ -523,7 +604,13 @@ class ConversationsViewModelImpl(
|
|||||||
conversations.update { newConversations }
|
conversations.update { newConversations }
|
||||||
|
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(conversations = newConversations.map { it.asPresentation(resources) })
|
old.copy(conversations = newConversations.map {
|
||||||
|
it.asPresentation(
|
||||||
|
resources = resources,
|
||||||
|
useContactName = useContactNames()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,8 +630,11 @@ class ConversationsViewModelImpl(
|
|||||||
|
|
||||||
val newConversations = conversations.value.toMutableList()
|
val newConversations = conversations.value.toMutableList()
|
||||||
val conversationAndIndex =
|
val conversationAndIndex =
|
||||||
newConversations.findWithIndex { it.id == peerId } ?: return
|
newConversations.findWithIndex { it.id == peerId }
|
||||||
|
|
||||||
|
if (conversationAndIndex == null) { // диалога нет в списке
|
||||||
|
// pizdets
|
||||||
|
} else {
|
||||||
newConversations[conversationAndIndex.first] =
|
newConversations[conversationAndIndex.first] =
|
||||||
conversationAndIndex.second.copy(
|
conversationAndIndex.second.copy(
|
||||||
interactionType = interactionType.value,
|
interactionType = interactionType.value,
|
||||||
@@ -555,7 +645,12 @@ class ConversationsViewModelImpl(
|
|||||||
|
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
conversations = newConversations.map { it.asPresentation(resources) }
|
conversations = newConversations.map {
|
||||||
|
it.asPresentation(
|
||||||
|
resources = resources,
|
||||||
|
useContactName = useContactNames()
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,6 +678,7 @@ class ConversationsViewModelImpl(
|
|||||||
stopInteraction(peerId, newInteractionJob)
|
stopInteraction(peerId, newInteractionJob)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun stopInteraction(peerId: Int, interactionJob: InteractionJob) {
|
private fun stopInteraction(peerId: Int, interactionJob: InteractionJob) {
|
||||||
interactionsTimers[peerId] ?: return
|
interactionsTimers[peerId] ?: return
|
||||||
@@ -600,7 +696,12 @@ class ConversationsViewModelImpl(
|
|||||||
conversations.update { newConversations }
|
conversations.update { newConversations }
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
conversations = newConversations.map { it.asPresentation(resources) }
|
conversations = newConversations.map {
|
||||||
|
it.asPresentation(
|
||||||
|
resources = resources,
|
||||||
|
useContactName = useContactNames()
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -629,7 +730,12 @@ class ConversationsViewModelImpl(
|
|||||||
conversations.update { newConversations }
|
conversations.update { newConversations }
|
||||||
screenState.setValue { old ->
|
screenState.setValue { old ->
|
||||||
old.copy(
|
old.copy(
|
||||||
conversations = newConversations.map { it.asPresentation(resources) }
|
conversations = newConversations.map {
|
||||||
|
it.asPresentation(
|
||||||
|
resources = resources,
|
||||||
|
useContactName = useContactNames()
|
||||||
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-16
@@ -54,7 +54,6 @@ import androidx.compose.runtime.snapshotFlow
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -65,8 +64,6 @@ import androidx.compose.ui.unit.LayoutDirection
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.view.HapticFeedbackConstantsCompat
|
import androidx.core.view.HapticFeedbackConstantsCompat
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import coil.imageLoader
|
|
||||||
import coil.request.ImageRequest
|
|
||||||
import dev.chrisbanes.haze.haze
|
import dev.chrisbanes.haze.haze
|
||||||
import dev.chrisbanes.haze.hazeChild
|
import dev.chrisbanes.haze.hazeChild
|
||||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||||
@@ -96,24 +93,11 @@ fun ConversationsRoute(
|
|||||||
onConversationPhotoClicked: (url: String) -> Unit,
|
onConversationPhotoClicked: (url: String) -> Unit,
|
||||||
viewModel: ConversationsViewModel
|
viewModel: ConversationsViewModel
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||||
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
|
||||||
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
|
||||||
val isNeedToScrollToTop by viewModel.scrollToTop.collectAsStateWithLifecycle()
|
val isNeedToScrollToTop by viewModel.scrollToTop.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
val imagesToPreload by viewModel.imagesToPreload.collectAsStateWithLifecycle()
|
|
||||||
LaunchedEffect(imagesToPreload) {
|
|
||||||
imagesToPreload.forEach { url ->
|
|
||||||
context.imageLoader.enqueue(
|
|
||||||
ImageRequest.Builder(context)
|
|
||||||
.data(url)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ConversationsScreen(
|
ConversationsScreen(
|
||||||
screenState = screenState,
|
screenState = screenState,
|
||||||
baseError = baseError,
|
baseError = baseError,
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ import dev.meloda.fast.ui.R as UiR
|
|||||||
|
|
||||||
fun VkConversation.asPresentation(
|
fun VkConversation.asPresentation(
|
||||||
resources: Resources,
|
resources: Resources,
|
||||||
useContactName: Boolean = false
|
useContactName: Boolean
|
||||||
): UiConversation = UiConversation(
|
): UiConversation = UiConversation(
|
||||||
id = id,
|
id = id,
|
||||||
lastMessageId = lastMessageId,
|
lastMessageId = lastMessageId,
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
minSdk = "23"
|
minSdk = "23"
|
||||||
targetSdk = "35"
|
targetSdk = "35"
|
||||||
compileSdk = "35"
|
compileSdk = "35"
|
||||||
versionCode = "8"
|
versionCode = "9"
|
||||||
versionName = "0.1.5"
|
versionName = "0.1.6"
|
||||||
|
|
||||||
agp = "8.7.3"
|
agp = "8.7.3"
|
||||||
converterMoshi = "2.11.0"
|
converterMoshi = "2.11.0"
|
||||||
@@ -13,14 +13,14 @@ kotlin = "2.1.0"
|
|||||||
ksp = "2.1.0-1.0.29"
|
ksp = "2.1.0-1.0.29"
|
||||||
|
|
||||||
compose-bom = "2024.12.01"
|
compose-bom = "2024.12.01"
|
||||||
koin = "4.0.0"
|
koin = "4.0.1"
|
||||||
|
|
||||||
accompanist = "0.37.0"
|
accompanist = "0.37.0"
|
||||||
coil = "2.7.0"
|
coil = "2.7.0"
|
||||||
coroutines = "1.9.0"
|
coroutines = "1.10.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
chucker = "4.1.0"
|
chucker = "4.1.0"
|
||||||
guava = "33.3.1-jre"
|
guava = "33.4.0-jre"
|
||||||
lifecycle = "2.8.7"
|
lifecycle = "2.8.7"
|
||||||
core-ktx = "1.15.0"
|
core-ktx = "1.15.0"
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user