From 8c053905cea365b60ba68f539877da8f958b37a5 Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Sat, 30 May 2026 20:30:55 +0300 Subject: [PATCH] feat: extend friends data support and refactor profile state management --- .../fast/common/extensions/Extensions.kt | 6 ++ .../dev/meloda/fast/data/VkMemoryCache.kt | 2 +- .../data/api/convos/ConvosRepositoryImpl.kt | 8 +-- .../data/api/friends/FriendsRepositoryImpl.kt | 11 +++- .../fast/model/api/requests/FriendsRequest.kt | 4 +- .../model/api/responses/FriendsResponse.kt | 6 +- .../friends/navigation/FriendsNavigation.kt | 8 +-- .../meloda/fast/profile/ProfileViewModel.kt | 59 +++++++++++-------- .../fast/profile/navigation/ProfileRoute.kt | 5 +- 9 files changed, 65 insertions(+), 44 deletions(-) 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 25d1dd1c..fe187b80 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 @@ -2,6 +2,8 @@ package dev.meloda.fast.common.extensions import android.os.Build import android.os.Bundle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -40,6 +42,10 @@ fun MutableList.removeIfCompat(condition: (T) -> Boolean): Boolean { return removed } +context(viewModel: ViewModel) +fun Flow.listenValue(action: suspend (T) -> Unit): Job = + listenValue(viewModel.viewModelScope, action) + fun Flow.listenValue( coroutineScope: CoroutineScope, action: suspend (T) -> Unit diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/VkMemoryCache.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/VkMemoryCache.kt index dfb2f748..146923bf 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/VkMemoryCache.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/VkMemoryCache.kt @@ -35,7 +35,7 @@ object VkMemoryCache { } fun appendContacts(contacts: List) { - contacts.forEach { contact -> VkMemoryCache.contacts[contact.userId] = contact } + contacts.forEach { contact -> VkMemoryCache[contact.userId] = contact } } operator fun set(userid: Long, user: VkUser) { diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/api/convos/ConvosRepositoryImpl.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/api/convos/ConvosRepositoryImpl.kt index 970cbfd6..47fd5b4a 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/api/convos/ConvosRepositoryImpl.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/api/convos/ConvosRepositoryImpl.kt @@ -129,6 +129,10 @@ class ConvosRepositoryImpl( val groupsList = response.groups.orEmpty().map(VkGroupData::mapToDomain) val contactsList = response.contacts.orEmpty().map(VkContactData::mapToDomain) + VkMemoryCache.appendUsers(profilesList) + VkMemoryCache.appendGroups(groupsList) + VkMemoryCache.appendContacts(contactsList) + val usersMap = VkUsersMap.forUsers(profilesList) val groupsMap = VkGroupsMap.forGroups(groupsList) @@ -147,10 +151,6 @@ class ConvosRepositoryImpl( groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity)) } - VkMemoryCache.appendUsers(profilesList) - VkMemoryCache.appendGroups(groupsList) - VkMemoryCache.appendContacts(contactsList) - convos }, errorMapper = { error -> diff --git a/core/data/src/main/kotlin/dev/meloda/fast/data/api/friends/FriendsRepositoryImpl.kt b/core/data/src/main/kotlin/dev/meloda/fast/data/api/friends/FriendsRepositoryImpl.kt index 4719d010..4a13a2c6 100644 --- a/core/data/src/main/kotlin/dev/meloda/fast/data/api/friends/FriendsRepositoryImpl.kt +++ b/core/data/src/main/kotlin/dev/meloda/fast/data/api/friends/FriendsRepositoryImpl.kt @@ -1,9 +1,12 @@ package dev.meloda.fast.data.api.friends +import com.slack.eithernet.ApiResult +import com.slack.eithernet.successOrElse import dev.meloda.fast.common.VkConstants import dev.meloda.fast.data.VkMemoryCache import dev.meloda.fast.database.dao.UserDao import dev.meloda.fast.model.FriendsInfo +import dev.meloda.fast.model.api.data.VkContactData import dev.meloda.fast.model.api.data.VkUserData import dev.meloda.fast.model.api.domain.VkUser import dev.meloda.fast.model.api.domain.asEntity @@ -13,8 +16,6 @@ import dev.meloda.fast.network.RestApiErrorDomain import dev.meloda.fast.network.mapApiDefault import dev.meloda.fast.network.mapApiResult import dev.meloda.fast.network.service.friends.FriendsService -import com.slack.eithernet.ApiResult -import com.slack.eithernet.successOrElse import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.withContext @@ -51,14 +52,18 @@ class FriendsRepositoryImpl( order = order, count = count, offset = offset, - fields = VkConstants.USER_FIELDS + fields = VkConstants.USER_FIELDS, + extended = true ) service.getFriends(requestModel.map).mapApiResult( successMapper = { apiResponse -> val response = apiResponse.requireResponse() + val users = response.items.map(VkUserData::mapToDomain) + val contactsList = response.contacts.orEmpty().map(VkContactData::mapToDomain) VkMemoryCache.appendUsers(users) + VkMemoryCache.appendContacts(contactsList) users }, diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/FriendsRequest.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/FriendsRequest.kt index ac209f69..7e64f5ea 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/FriendsRequest.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/requests/FriendsRequest.kt @@ -4,7 +4,8 @@ data class GetFriendsRequest( val order: String?, val count: Int?, val offset: Int?, - val fields: String? + val fields: String?, + val extended: Boolean? ) { val map @@ -14,6 +15,7 @@ data class GetFriendsRequest( count?.let { this["count"] = it.toString() } offset?.let { this["offset"] = it.toString() } fields?.let { this["fields"] = it } + extended?.let { this["extended"] = it.toString() } } } diff --git a/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/FriendsResponse.kt b/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/FriendsResponse.kt index d9df0555..e0983387 100644 --- a/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/FriendsResponse.kt +++ b/core/model/src/main/kotlin/dev/meloda/fast/model/api/responses/FriendsResponse.kt @@ -1,11 +1,13 @@ package dev.meloda.fast.model.api.responses -import dev.meloda.fast.model.api.data.VkUserData import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import dev.meloda.fast.model.api.data.VkContactData +import dev.meloda.fast.model.api.data.VkUserData @JsonClass(generateAdapter = true) data class GetFriendsResponse( @Json(name = "count") val count: Int, - @Json(name = "items") val items: List + @Json(name = "items") val items: List, + @Json(name = "contacts") val contacts: List? ) diff --git a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/navigation/FriendsNavigation.kt b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/navigation/FriendsNavigation.kt index d204f0bb..497034f2 100644 --- a/feature/friends/src/main/kotlin/dev/meloda/fast/friends/navigation/FriendsNavigation.kt +++ b/feature/friends/src/main/kotlin/dev/meloda/fast/friends/navigation/FriendsNavigation.kt @@ -21,11 +21,11 @@ fun NavGraphBuilder.friendsScreen( onMessageClicked: (userId: Long) -> Unit, onScrolledToTop: () -> Unit ) { - val friendsViewModel: FriendsViewModel = activity.getViewModel() - val onlineFriendsViewModel = - activity.getViewModel() - composable { + val friendsViewModel: FriendsViewModel = activity.getViewModel() + val onlineFriendsViewModel = + activity.getViewModel() + FriendsRoute( friendsViewModel = friendsViewModel, onlineFriendsViewModel = onlineFriendsViewModel, 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 e3de652f..f2feff16 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 @@ -9,47 +9,54 @@ import dev.meloda.fast.data.UserConfig import dev.meloda.fast.data.processState import dev.meloda.fast.domain.GetLocalUserByIdUseCase import dev.meloda.fast.domain.LoadUserByIdUseCase +import dev.meloda.fast.logger.FastLogger import dev.meloda.fast.profile.model.ProfileScreenState import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow class ProfileViewModel( private val getLocalUserByIdUseCase: GetLocalUserByIdUseCase, - private val loadUserByIdUseCase: LoadUserByIdUseCase + private val loadUserByIdUseCase: LoadUserByIdUseCase, + private val logger: FastLogger ) : ViewModel() { private val screenState = MutableStateFlow(ProfileScreenState.EMPTY) + val screenStateFlow get() = screenState.asStateFlow() init { getLocalAccountInfo() } - fun screenStateFlow(): StateFlow = screenState.asStateFlow() - private fun getLocalAccountInfo() { - getLocalUserByIdUseCase(UserConfig.userId) - .listenValue(viewModelScope) { state -> - state.processState( - error = { - screenState.setValue { old -> - old.copy( - avatarUrl = null, - fullName = null - ) - } - }, - success = { user -> - screenState.setValue { old -> - old.copy( - avatarUrl = user?.photo200, - fullName = user?.fullName - ) - } - }, - any = ::loadAccountInfo - ) - } + logger.debug(this@ProfileViewModel::class, "START") + emit(screenState.value.copy(isLoading = true)) + + getLocalUserByIdUseCase(UserConfig.userId).listenValue { state -> + logger.debug(this@ProfileViewModel::class, "LOADED: $state") + + emit(screenState.value.copy(isLoading = false)) + + state.processState( + error = { + logger.debug(this@ProfileViewModel::class, "ERROR") + emit(screenState.value.copy(avatarUrl = null, fullName = null)) + }, + success = { user -> + logger.debug(this@ProfileViewModel::class, "SUCCESS") + emit( + screenState.value.copy( + avatarUrl = user?.photo200, + fullName = user?.fullName + ) + ) + }, + any = ::loadAccountInfo + ) + } + } + + private fun emit(state: ProfileScreenState) { + screenState.setValue { state } } private fun loadAccountInfo() { diff --git a/feature/profile/src/main/kotlin/dev/meloda/fast/profile/navigation/ProfileRoute.kt b/feature/profile/src/main/kotlin/dev/meloda/fast/profile/navigation/ProfileRoute.kt index ff0f3ff2..9740a339 100644 --- a/feature/profile/src/main/kotlin/dev/meloda/fast/profile/navigation/ProfileRoute.kt +++ b/feature/profile/src/main/kotlin/dev/meloda/fast/profile/navigation/ProfileRoute.kt @@ -18,10 +18,9 @@ fun NavGraphBuilder.profileScreen( onSettingsButtonClicked: () -> Unit, onPhotoClicked: (url: String) -> Unit ) { - val viewModel: ProfileViewModel = with(activity) { getViewModel() } - composable { - val screenState by viewModel.screenStateFlow().collectAsStateWithLifecycle() + val viewModel: ProfileViewModel = activity.getViewModel() + val screenState by viewModel.screenStateFlow.collectAsStateWithLifecycle() ProfileRoute( screenState = screenState,