feat: extend friends data support and refactor profile state management

This commit is contained in:
2026-05-30 20:30:55 +03:00
parent 2381d4ca0a
commit 8c053905ce
9 changed files with 65 additions and 44 deletions
@@ -2,6 +2,8 @@ package dev.meloda.fast.common.extensions
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
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
@@ -40,6 +42,10 @@ fun <T> MutableList<T>.removeIfCompat(condition: (T) -> Boolean): Boolean {
return removed return removed
} }
context(viewModel: ViewModel)
fun <T> Flow<T>.listenValue(action: suspend (T) -> Unit): Job =
listenValue(viewModel.viewModelScope, action)
fun <T> Flow<T>.listenValue( fun <T> Flow<T>.listenValue(
coroutineScope: CoroutineScope, coroutineScope: CoroutineScope,
action: suspend (T) -> Unit action: suspend (T) -> Unit
@@ -35,7 +35,7 @@ object VkMemoryCache {
} }
fun appendContacts(contacts: List<VkContactDomain>) { fun appendContacts(contacts: List<VkContactDomain>) {
contacts.forEach { contact -> VkMemoryCache.contacts[contact.userId] = contact } contacts.forEach { contact -> VkMemoryCache[contact.userId] = contact }
} }
operator fun set(userid: Long, user: VkUser) { operator fun set(userid: Long, user: VkUser) {
@@ -129,6 +129,10 @@ class ConvosRepositoryImpl(
val groupsList = response.groups.orEmpty().map(VkGroupData::mapToDomain) val groupsList = response.groups.orEmpty().map(VkGroupData::mapToDomain)
val contactsList = response.contacts.orEmpty().map(VkContactData::mapToDomain) val contactsList = response.contacts.orEmpty().map(VkContactData::mapToDomain)
VkMemoryCache.appendUsers(profilesList)
VkMemoryCache.appendGroups(groupsList)
VkMemoryCache.appendContacts(contactsList)
val usersMap = VkUsersMap.forUsers(profilesList) val usersMap = VkUsersMap.forUsers(profilesList)
val groupsMap = VkGroupsMap.forGroups(groupsList) val groupsMap = VkGroupsMap.forGroups(groupsList)
@@ -147,10 +151,6 @@ class ConvosRepositoryImpl(
groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity)) groupDao.insertAll(groupsList.map(VkGroupDomain::asEntity))
} }
VkMemoryCache.appendUsers(profilesList)
VkMemoryCache.appendGroups(groupsList)
VkMemoryCache.appendContacts(contactsList)
convos convos
}, },
errorMapper = { error -> errorMapper = { error ->
@@ -1,9 +1,12 @@
package dev.meloda.fast.data.api.friends 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.common.VkConstants
import dev.meloda.fast.data.VkMemoryCache import dev.meloda.fast.data.VkMemoryCache
import dev.meloda.fast.database.dao.UserDao import dev.meloda.fast.database.dao.UserDao
import dev.meloda.fast.model.FriendsInfo 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.data.VkUserData
import dev.meloda.fast.model.api.domain.VkUser import dev.meloda.fast.model.api.domain.VkUser
import dev.meloda.fast.model.api.domain.asEntity 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.mapApiDefault
import dev.meloda.fast.network.mapApiResult import dev.meloda.fast.network.mapApiResult
import dev.meloda.fast.network.service.friends.FriendsService 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.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -51,14 +52,18 @@ class FriendsRepositoryImpl(
order = order, order = order,
count = count, count = count,
offset = offset, offset = offset,
fields = VkConstants.USER_FIELDS fields = VkConstants.USER_FIELDS,
extended = true
) )
service.getFriends(requestModel.map).mapApiResult( service.getFriends(requestModel.map).mapApiResult(
successMapper = { apiResponse -> successMapper = { apiResponse ->
val response = apiResponse.requireResponse() val response = apiResponse.requireResponse()
val users = response.items.map(VkUserData::mapToDomain) val users = response.items.map(VkUserData::mapToDomain)
val contactsList = response.contacts.orEmpty().map(VkContactData::mapToDomain)
VkMemoryCache.appendUsers(users) VkMemoryCache.appendUsers(users)
VkMemoryCache.appendContacts(contactsList)
users users
}, },
@@ -4,7 +4,8 @@ data class GetFriendsRequest(
val order: String?, val order: String?,
val count: Int?, val count: Int?,
val offset: Int?, val offset: Int?,
val fields: String? val fields: String?,
val extended: Boolean?
) { ) {
val map val map
@@ -14,6 +15,7 @@ data class GetFriendsRequest(
count?.let { this["count"] = it.toString() } count?.let { this["count"] = it.toString() }
offset?.let { this["offset"] = it.toString() } offset?.let { this["offset"] = it.toString() }
fields?.let { this["fields"] = it } fields?.let { this["fields"] = it }
extended?.let { this["extended"] = it.toString() }
} }
} }
@@ -1,11 +1,13 @@
package dev.meloda.fast.model.api.responses package dev.meloda.fast.model.api.responses
import dev.meloda.fast.model.api.data.VkUserData
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import dev.meloda.fast.model.api.data.VkContactData
import dev.meloda.fast.model.api.data.VkUserData
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class GetFriendsResponse( data class GetFriendsResponse(
@Json(name = "count") val count: Int, @Json(name = "count") val count: Int,
@Json(name = "items") val items: List<VkUserData> @Json(name = "items") val items: List<VkUserData>,
@Json(name = "contacts") val contacts: List<VkContactData>?
) )
@@ -21,11 +21,11 @@ fun NavGraphBuilder.friendsScreen(
onMessageClicked: (userId: Long) -> Unit, onMessageClicked: (userId: Long) -> Unit,
onScrolledToTop: () -> Unit onScrolledToTop: () -> Unit
) { ) {
val friendsViewModel: FriendsViewModel = activity.getViewModel<FriendsViewModelImpl>()
val onlineFriendsViewModel =
activity.getViewModel<OnlineFriendsViewModelImpl>()
composable<Friends> { composable<Friends> {
val friendsViewModel: FriendsViewModel = activity.getViewModel<FriendsViewModelImpl>()
val onlineFriendsViewModel =
activity.getViewModel<OnlineFriendsViewModelImpl>()
FriendsRoute( FriendsRoute(
friendsViewModel = friendsViewModel, friendsViewModel = friendsViewModel,
onlineFriendsViewModel = onlineFriendsViewModel, onlineFriendsViewModel = onlineFriendsViewModel,
@@ -9,47 +9,54 @@ import dev.meloda.fast.data.UserConfig
import dev.meloda.fast.data.processState import dev.meloda.fast.data.processState
import dev.meloda.fast.domain.GetLocalUserByIdUseCase import dev.meloda.fast.domain.GetLocalUserByIdUseCase
import dev.meloda.fast.domain.LoadUserByIdUseCase import dev.meloda.fast.domain.LoadUserByIdUseCase
import dev.meloda.fast.logger.FastLogger
import dev.meloda.fast.profile.model.ProfileScreenState import dev.meloda.fast.profile.model.ProfileScreenState
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
class ProfileViewModel( class ProfileViewModel(
private val getLocalUserByIdUseCase: GetLocalUserByIdUseCase, private val getLocalUserByIdUseCase: GetLocalUserByIdUseCase,
private val loadUserByIdUseCase: LoadUserByIdUseCase private val loadUserByIdUseCase: LoadUserByIdUseCase,
private val logger: FastLogger
) : ViewModel() { ) : ViewModel() {
private val screenState = MutableStateFlow(ProfileScreenState.EMPTY) private val screenState = MutableStateFlow(ProfileScreenState.EMPTY)
val screenStateFlow get() = screenState.asStateFlow()
init { init {
getLocalAccountInfo() getLocalAccountInfo()
} }
fun screenStateFlow(): StateFlow<ProfileScreenState> = screenState.asStateFlow()
private fun getLocalAccountInfo() { private fun getLocalAccountInfo() {
getLocalUserByIdUseCase(UserConfig.userId) logger.debug(this@ProfileViewModel::class, "START")
.listenValue(viewModelScope) { state -> emit(screenState.value.copy(isLoading = true))
state.processState(
error = { getLocalUserByIdUseCase(UserConfig.userId).listenValue { state ->
screenState.setValue { old -> logger.debug(this@ProfileViewModel::class, "LOADED: $state")
old.copy(
avatarUrl = null, emit(screenState.value.copy(isLoading = false))
fullName = null
) state.processState(
} error = {
}, logger.debug(this@ProfileViewModel::class, "ERROR")
success = { user -> emit(screenState.value.copy(avatarUrl = null, fullName = null))
screenState.setValue { old -> },
old.copy( success = { user ->
avatarUrl = user?.photo200, logger.debug(this@ProfileViewModel::class, "SUCCESS")
fullName = user?.fullName emit(
) screenState.value.copy(
} avatarUrl = user?.photo200,
}, fullName = user?.fullName
any = ::loadAccountInfo )
) )
} },
any = ::loadAccountInfo
)
}
}
private fun emit(state: ProfileScreenState) {
screenState.setValue { state }
} }
private fun loadAccountInfo() { private fun loadAccountInfo() {
@@ -18,10 +18,9 @@ fun NavGraphBuilder.profileScreen(
onSettingsButtonClicked: () -> Unit, onSettingsButtonClicked: () -> Unit,
onPhotoClicked: (url: String) -> Unit onPhotoClicked: (url: String) -> Unit
) { ) {
val viewModel: ProfileViewModel = with(activity) { getViewModel() }
composable<Profile> { composable<Profile> {
val screenState by viewModel.screenStateFlow().collectAsStateWithLifecycle() val viewModel: ProfileViewModel = activity.getViewModel()
val screenState by viewModel.screenStateFlow.collectAsStateWithLifecycle()
ProfileRoute( ProfileRoute(
screenState = screenState, screenState = screenState,