account's info on profile screen; storing users into cache
This commit is contained in:
@@ -7,21 +7,14 @@ import androidx.compose.animation.fadeOut
|
|||||||
import androidx.compose.animation.slideIn
|
import androidx.compose.animation.slideIn
|
||||||
import androidx.compose.animation.slideOut
|
import androidx.compose.animation.slideOut
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.rounded.Settings
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.NavigationBar
|
import androidx.compose.material3.NavigationBar
|
||||||
import androidx.compose.material3.NavigationBarDefaults
|
import androidx.compose.material3.NavigationBarDefaults
|
||||||
import androidx.compose.material3.NavigationBarItem
|
import androidx.compose.material3.NavigationBarItem
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
@@ -31,7 +24,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
@@ -47,6 +39,8 @@ import com.meloda.app.fast.designsystem.LocalTheme
|
|||||||
import com.meloda.app.fast.friends.navigation.Friends
|
import com.meloda.app.fast.friends.navigation.Friends
|
||||||
import com.meloda.app.fast.friends.navigation.friendsRoute
|
import com.meloda.app.fast.friends.navigation.friendsRoute
|
||||||
import com.meloda.app.fast.model.BaseError
|
import com.meloda.app.fast.model.BaseError
|
||||||
|
import com.meloda.app.fast.profile.navigation.Profile
|
||||||
|
import com.meloda.app.fast.profile.navigation.profileRoute
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.hazeChild
|
import dev.chrisbanes.haze.hazeChild
|
||||||
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
|
||||||
@@ -60,9 +54,6 @@ object MainGraph
|
|||||||
@Serializable
|
@Serializable
|
||||||
object Main
|
object Main
|
||||||
|
|
||||||
@Serializable
|
|
||||||
object Profile
|
|
||||||
|
|
||||||
data class BottomNavigationItem(
|
data class BottomNavigationItem(
|
||||||
val titleResId: Int,
|
val titleResId: Int,
|
||||||
val selectedIconResId: Int,
|
val selectedIconResId: Int,
|
||||||
@@ -70,7 +61,7 @@ data class BottomNavigationItem(
|
|||||||
val route: Any,
|
val route: Any,
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
|
@OptIn(ExperimentalHazeMaterialsApi::class)
|
||||||
fun NavGraphBuilder.mainScreen(
|
fun NavGraphBuilder.mainScreen(
|
||||||
onError: (BaseError) -> Unit,
|
onError: (BaseError) -> Unit,
|
||||||
onNavigateToSettings: () -> Unit,
|
onNavigateToSettings: () -> Unit,
|
||||||
@@ -192,36 +183,13 @@ fun NavGraphBuilder.mainScreen(
|
|||||||
// isBottomBarVisible = isScrolling
|
// isBottomBarVisible = isScrolling
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
profileRoute(
|
||||||
composable<Profile> {
|
onError = onError,
|
||||||
Scaffold(
|
onNavigateToSettings = onNavigateToSettings,
|
||||||
topBar = {
|
navController = navController
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Text(text = stringResource(id = UiR.string.title_profile))
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
IconButton(onClick = onNavigateToSettings) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Rounded.Settings,
|
|
||||||
contentDescription = null
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
}
|
|
||||||
) { padding ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(padding)
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,12 +42,22 @@ inline fun <T> State<T>.processState(
|
|||||||
success: (data: T) -> (Unit),
|
success: (data: T) -> (Unit),
|
||||||
idle: (() -> (Unit)) = {},
|
idle: (() -> (Unit)) = {},
|
||||||
loading: (() -> (Unit)) = {},
|
loading: (() -> (Unit)) = {},
|
||||||
|
any: () -> Unit = {}
|
||||||
) {
|
) {
|
||||||
when (this) {
|
when (this) {
|
||||||
is State.Error -> error(this)
|
is State.Error -> {
|
||||||
|
error(this)
|
||||||
|
any()
|
||||||
|
}
|
||||||
|
|
||||||
State.Idle -> idle()
|
State.Idle -> idle()
|
||||||
|
|
||||||
State.Loading -> loading()
|
State.Loading -> loading()
|
||||||
is State.Success -> success(data)
|
|
||||||
|
is State.Success -> {
|
||||||
|
success(data)
|
||||||
|
any()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
package com.meloda.app.fast.data.api.users
|
package com.meloda.app.fast.data.api.users
|
||||||
|
|
||||||
import com.meloda.app.fast.model.api.data.VkUserData
|
import com.meloda.app.fast.model.api.domain.VkUser
|
||||||
import com.meloda.app.fast.model.api.requests.UsersGetRequest
|
import com.meloda.app.fast.network.RestApiErrorDomain
|
||||||
|
import com.slack.eithernet.ApiResult
|
||||||
|
|
||||||
interface UsersRepository {
|
interface UsersRepository {
|
||||||
suspend fun getById(params: UsersGetRequest): List<VkUserData>
|
|
||||||
|
suspend fun get(
|
||||||
|
userIds: List<Int>?,
|
||||||
|
fields: String?,
|
||||||
|
nomCase: String?
|
||||||
|
): ApiResult<List<VkUser>, RestApiErrorDomain>
|
||||||
|
|
||||||
|
suspend fun getLocalUsers(userIds: List<Int>): List<VkUser>
|
||||||
|
|
||||||
|
suspend fun storeUsers(users: List<VkUser>)
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-4
@@ -1,16 +1,62 @@
|
|||||||
package com.meloda.app.fast.data.api.users
|
package com.meloda.app.fast.data.api.users
|
||||||
|
|
||||||
|
import com.meloda.app.fast.data.VkMemoryCache
|
||||||
|
import com.meloda.app.fast.database.dao.UsersDao
|
||||||
import com.meloda.app.fast.model.api.data.VkUserData
|
import com.meloda.app.fast.model.api.data.VkUserData
|
||||||
|
import com.meloda.app.fast.model.api.domain.VkUser
|
||||||
|
import com.meloda.app.fast.model.api.domain.asEntity
|
||||||
import com.meloda.app.fast.model.api.requests.UsersGetRequest
|
import com.meloda.app.fast.model.api.requests.UsersGetRequest
|
||||||
|
import com.meloda.app.fast.model.database.VkUserEntity
|
||||||
|
import com.meloda.app.fast.model.database.asExternalModel
|
||||||
|
import com.meloda.app.fast.network.RestApiErrorDomain
|
||||||
|
import com.meloda.app.fast.network.mapApiResult
|
||||||
import com.meloda.app.fast.network.service.users.UsersService
|
import com.meloda.app.fast.network.service.users.UsersService
|
||||||
|
import com.slack.eithernet.ApiResult
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
class UsersRepositoryImpl(
|
class UsersRepositoryImpl(
|
||||||
private val usersService: UsersService
|
private val service: UsersService,
|
||||||
|
private val dao: UsersDao
|
||||||
) : UsersRepository {
|
) : UsersRepository {
|
||||||
|
|
||||||
override suspend fun getById(params: UsersGetRequest): List<VkUserData> {
|
override suspend fun get(
|
||||||
// TODO: 05/05/2024, Danil Nikolaev: implement
|
userIds: List<Int>?,
|
||||||
|
fields: String?,
|
||||||
|
nomCase: String?
|
||||||
|
): ApiResult<List<VkUser>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
|
||||||
|
val requestModel = UsersGetRequest(
|
||||||
|
userIds = userIds,
|
||||||
|
fields = fields,
|
||||||
|
nomCase = nomCase
|
||||||
|
)
|
||||||
|
|
||||||
return emptyList()
|
service.get(requestModel.map).mapApiResult(
|
||||||
|
successMapper = { apiResponse ->
|
||||||
|
val response = apiResponse.requireResponse()
|
||||||
|
|
||||||
|
val users = response.map(VkUserData::mapToDomain)
|
||||||
|
|
||||||
|
launch { storeUsers(users) }
|
||||||
|
|
||||||
|
VkMemoryCache.appendUsers(users)
|
||||||
|
|
||||||
|
users
|
||||||
|
},
|
||||||
|
errorMapper = { error ->
|
||||||
|
error?.toDomain()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getLocalUsers(
|
||||||
|
userIds: List<Int>
|
||||||
|
): List<VkUser> = withContext(Dispatchers.IO) {
|
||||||
|
dao.getAllByIds(userIds).map(VkUserEntity::asExternalModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun storeUsers(users: List<VkUser>) {
|
||||||
|
dao.insertAll(users.map(VkUser::asEntity))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,14 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
|
|
||||||
interface UsersUseCase {
|
interface UsersUseCase {
|
||||||
|
|
||||||
fun getUserById(
|
fun get(
|
||||||
userId: Int,
|
userIds: List<Int>?,
|
||||||
fields: String?,
|
|
||||||
nomCase: String?
|
|
||||||
): Flow<State<VkUser?>>
|
|
||||||
|
|
||||||
fun getUsersByIds(
|
|
||||||
userIds: List<Int>,
|
|
||||||
fields: String?,
|
fields: String?,
|
||||||
nomCase: String?
|
nomCase: String?
|
||||||
): Flow<State<List<VkUser>>>
|
): Flow<State<List<VkUser>>>
|
||||||
|
|
||||||
|
fun getLocalUser(userId: Int): Flow<State<VkUser?>>
|
||||||
|
|
||||||
suspend fun storeUser(user: VkUser)
|
suspend fun storeUser(user: VkUser)
|
||||||
suspend fun storeUsers(users: List<VkUser>)
|
suspend fun storeUsers(users: List<VkUser>)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,67 +1,39 @@
|
|||||||
package com.meloda.app.fast.data.api.users
|
package com.meloda.app.fast.data.api.users
|
||||||
|
|
||||||
import com.meloda.app.fast.data.State
|
import com.meloda.app.fast.data.State
|
||||||
|
import com.meloda.app.fast.data.mapToState
|
||||||
import com.meloda.app.fast.model.api.domain.VkUser
|
import com.meloda.app.fast.model.api.domain.VkUser
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
|
|
||||||
// TODO: 05/05/2024, Danil Nikolaev: implement
|
|
||||||
class UsersUseCaseImpl(
|
class UsersUseCaseImpl(
|
||||||
private val usersRepository: UsersRepository,
|
private val repository: UsersRepository,
|
||||||
) : UsersUseCase {
|
) : UsersUseCase {
|
||||||
|
|
||||||
override fun getUserById(
|
override fun get(
|
||||||
userId: Int,
|
userIds: List<Int>?,
|
||||||
fields: String?,
|
|
||||||
nomCase: String?
|
|
||||||
): Flow<State<VkUser?>> = flow {
|
|
||||||
// emit(State.Loading)
|
|
||||||
//
|
|
||||||
// val newState = usersRepository.getById(
|
|
||||||
// UsersGetRequest(
|
|
||||||
// userIds = listOf(userId),
|
|
||||||
// fields = fields,
|
|
||||||
// nomCase = nomCase
|
|
||||||
// )
|
|
||||||
// ).fold(
|
|
||||||
// onSuccess = { response -> State.Success(response.singleOrNull()?.mapToDomain()) },
|
|
||||||
// onNetworkFailure = { State.Error.ConnectionError },
|
|
||||||
// onUnknownFailure = { State.UNKNOWN_ERROR },
|
|
||||||
// onHttpFailure = { result -> result.error.toStateApiError() },
|
|
||||||
// onApiFailure = { result -> result.error.toStateApiError() }
|
|
||||||
// )
|
|
||||||
// emit(newState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getUsersByIds(
|
|
||||||
userIds: List<Int>,
|
|
||||||
fields: String?,
|
fields: String?,
|
||||||
nomCase: String?
|
nomCase: String?
|
||||||
): Flow<State<List<VkUser>>> = flow {
|
): Flow<State<List<VkUser>>> = flow {
|
||||||
// emit(State.Loading)
|
emit(State.Loading)
|
||||||
//
|
|
||||||
// val newState = usersRepository.getById(
|
val newState = repository.get(userIds, fields, nomCase).mapToState()
|
||||||
// UsersGetRequest(
|
emit(newState)
|
||||||
// userIds = userIds,
|
|
||||||
// fields = fields,
|
|
||||||
// nomCase = nomCase
|
|
||||||
// )
|
|
||||||
// ).fold(
|
|
||||||
// onSuccess = { response -> State.Success(response.map(VkUserData::mapToDomain)) },
|
|
||||||
// onNetworkFailure = { State.Error.ConnectionError },
|
|
||||||
// onUnknownFailure = { State.UNKNOWN_ERROR },
|
|
||||||
// onHttpFailure = { result -> result.error.toStateApiError() },
|
|
||||||
// onApiFailure = { result -> result.error.toStateApiError() }
|
|
||||||
// )
|
|
||||||
// emit(newState)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun storeUser(user: VkUser) {
|
override fun getLocalUser(userId: Int): Flow<State<VkUser?>> = flow {
|
||||||
|
emit(State.Loading)
|
||||||
|
|
||||||
|
val newState = kotlin.runCatching {
|
||||||
|
repository.getLocalUsers(listOf(userId)).singleOrNull()
|
||||||
|
}.fold(
|
||||||
|
onSuccess = { user -> State.Success(user) },
|
||||||
|
onFailure = { State.Error.InternalError }
|
||||||
|
)
|
||||||
|
|
||||||
|
emit(newState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun storeUsers(users: List<VkUser>) {
|
override suspend fun storeUser(user: VkUser) = repository.storeUsers(listOf(user))
|
||||||
|
override suspend fun storeUsers(users: List<VkUser>) = repository.storeUsers(users)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ data class UsersGetRequest(
|
|||||||
val map
|
val map
|
||||||
get() = mutableMapOf<String, String>()
|
get() = mutableMapOf<String, String>()
|
||||||
.apply {
|
.apply {
|
||||||
userIds?.let { this["user_ids"] = it.joinToString() }
|
userIds?.let { this["user_ids"] = it.joinToString(",") }
|
||||||
fields?.let { this["fields"] = it }
|
fields?.let { this["fields"] = it }
|
||||||
nomCase?.let { this["nom_case"] = it }
|
nomCase?.let { this["nom_case"] = it }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.meloda.app.fast.model.database
|
|||||||
|
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
|
import com.meloda.app.fast.model.api.domain.OnlineStatus
|
||||||
|
import com.meloda.app.fast.model.api.domain.VkUser
|
||||||
|
|
||||||
@Entity(tableName = "users")
|
@Entity(tableName = "users")
|
||||||
data class VkUserEntity(
|
data class VkUserEntity(
|
||||||
@@ -18,3 +20,20 @@ data class VkUserEntity(
|
|||||||
val photo100: String?,
|
val photo100: String?,
|
||||||
val photo200: String?
|
val photo200: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun VkUserEntity.asExternalModel(): VkUser = VkUser(
|
||||||
|
id = id,
|
||||||
|
firstName = firstName,
|
||||||
|
lastName = lastName,
|
||||||
|
onlineStatus = when {
|
||||||
|
!isOnline -> OnlineStatus.Offline
|
||||||
|
!isOnlineMobile -> OnlineStatus.Online(onlineAppId)
|
||||||
|
else -> OnlineStatus.OnlineMobile(onlineAppId)
|
||||||
|
},
|
||||||
|
photo50 = photo50,
|
||||||
|
photo100 = photo100,
|
||||||
|
photo200 = photo200,
|
||||||
|
lastSeen = lastSeen,
|
||||||
|
lastSeenStatus = lastSeenStatus,
|
||||||
|
birthday = birthday
|
||||||
|
)
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ interface UsersService {
|
|||||||
|
|
||||||
@FormUrlEncoded
|
@FormUrlEncoded
|
||||||
@POST(UsersUrls.GET_BY_ID)
|
@POST(UsersUrls.GET_BY_ID)
|
||||||
suspend fun getById(
|
suspend fun get(
|
||||||
@FieldMap params: Map<String, String>?
|
@FieldMap params: Map<String, String>?
|
||||||
): ApiResult<ApiResponse<List<VkUserData>>, RestApiError>
|
): ApiResult<ApiResponse<List<VkUserData>>, RestApiError>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,6 +135,16 @@ class LoginViewModelImpl(
|
|||||||
UserConfig.trustedHash = account.trustedHash
|
UserConfig.trustedHash = account.trustedHash
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usersUseCase.get(
|
||||||
|
userIds = null,
|
||||||
|
fields = VkConstants.USER_FIELDS,
|
||||||
|
nomCase = null
|
||||||
|
).listenValue { state ->
|
||||||
|
state.processState(
|
||||||
|
error = { error ->
|
||||||
|
|
||||||
|
},
|
||||||
|
success = { response ->
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
accountsRepository.storeAccounts(listOf(currentAccount))
|
accountsRepository.storeAccounts(listOf(currentAccount))
|
||||||
|
|
||||||
@@ -142,6 +152,11 @@ class LoginViewModelImpl(
|
|||||||
screenState.setValue { old -> old.copy(isNeedToNavigateToMain = true) }
|
screenState.setValue { old -> old.copy(isNeedToNavigateToMain = true) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
screenState.setValue { old -> old.copy(isLoading = state.isLoading()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun login(forceSms: Boolean = false) {
|
private fun login(forceSms: Boolean = false) {
|
||||||
val currentState = screenState.value.copy()
|
val currentState = screenState.value.copy()
|
||||||
@@ -177,16 +192,11 @@ class LoginViewModelImpl(
|
|||||||
return@processState
|
return@processState
|
||||||
}
|
}
|
||||||
|
|
||||||
usersUseCase.getUserById(
|
usersUseCase.get(
|
||||||
userId = userId,
|
userIds = listOf(userId),
|
||||||
fields = VkConstants.USER_FIELDS,
|
fields = VkConstants.USER_FIELDS,
|
||||||
nomCase = null
|
nomCase = null
|
||||||
).listenValue { state ->
|
|
||||||
state.processState(
|
|
||||||
error = {},
|
|
||||||
success = { user -> user?.let { usersUseCase.storeUser(user) } }
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
val currentAccount = AccountEntity(
|
val currentAccount = AccountEntity(
|
||||||
userId = userId,
|
userId = userId,
|
||||||
|
|||||||
@@ -1,12 +1,78 @@
|
|||||||
package com.meloda.app.fast.profile
|
package com.meloda.app.fast.profile
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.meloda.app.fast.common.UserConfig
|
||||||
|
import com.meloda.app.fast.common.VkConstants
|
||||||
|
import com.meloda.app.fast.common.extensions.listenValue
|
||||||
|
import com.meloda.app.fast.common.extensions.setValue
|
||||||
|
import com.meloda.app.fast.data.api.users.UsersUseCase
|
||||||
|
import com.meloda.app.fast.data.processState
|
||||||
|
import com.meloda.app.fast.profile.model.ProfileScreenState
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
interface ProfileViewModel {
|
interface ProfileViewModel {
|
||||||
|
val screenState: StateFlow<ProfileScreenState>
|
||||||
}
|
}
|
||||||
|
|
||||||
class ProfileViewModelImpl(
|
class ProfileViewModelImpl(
|
||||||
|
private val usersUseCase: UsersUseCase
|
||||||
) : ViewModel(), ProfileViewModel {
|
) : ViewModel(), ProfileViewModel {
|
||||||
|
|
||||||
|
override val screenState = MutableStateFlow(ProfileScreenState.EMPTY)
|
||||||
|
|
||||||
|
init {
|
||||||
|
getLocalAccountInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLocalAccountInfo() {
|
||||||
|
usersUseCase.getLocalUser(UserConfig.userId)
|
||||||
|
.listenValue { state ->
|
||||||
|
state.processState(
|
||||||
|
error = { 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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadAccountInfo() {
|
||||||
|
usersUseCase.get(
|
||||||
|
userIds = null,
|
||||||
|
fields = VkConstants.USER_FIELDS,
|
||||||
|
nomCase = null
|
||||||
|
).listenValue { state ->
|
||||||
|
state.processState(
|
||||||
|
error = { error ->
|
||||||
|
// TODO: 12/07/2024, Danil Nikolaev: if local info is null then show error view
|
||||||
|
},
|
||||||
|
success = { response ->
|
||||||
|
val user = response.single()
|
||||||
|
|
||||||
|
screenState.setValue { old ->
|
||||||
|
old.copy(
|
||||||
|
avatarUrl = user.photo200,
|
||||||
|
fullName = user.fullName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
screenState.setValue { old -> old.copy(isLoading = state.isLoading()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
package com.meloda.app.fast.profile.model
|
||||||
|
|
||||||
|
data class ProfileScreenState(
|
||||||
|
val isLoading: Boolean,
|
||||||
|
val avatarUrl: String?,
|
||||||
|
val fullName: String?
|
||||||
|
) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val EMPTY: ProfileScreenState = ProfileScreenState(
|
||||||
|
isLoading = false,
|
||||||
|
avatarUrl = null,
|
||||||
|
fullName = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package com.meloda.app.fast.profile.navigation
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import com.meloda.app.fast.common.extensions.navigation.sharedViewModel
|
||||||
|
import com.meloda.app.fast.model.BaseError
|
||||||
|
import com.meloda.app.fast.profile.ProfileViewModel
|
||||||
|
import com.meloda.app.fast.profile.ProfileViewModelImpl
|
||||||
|
import com.meloda.app.fast.profile.presentation.ProfileScreen
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
object Profile
|
||||||
|
|
||||||
|
fun NavGraphBuilder.profileRoute(
|
||||||
|
onError: (BaseError) -> Unit,
|
||||||
|
onNavigateToSettings: () -> Unit,
|
||||||
|
navController: NavController
|
||||||
|
) {
|
||||||
|
composable<Profile> {
|
||||||
|
val viewModel: ProfileViewModel =
|
||||||
|
it.sharedViewModel<ProfileViewModelImpl>(navController = navController)
|
||||||
|
|
||||||
|
ProfileScreen(
|
||||||
|
onError = onError,
|
||||||
|
onNavigateToSettings = onNavigateToSettings,
|
||||||
|
viewModel = viewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
package com.meloda.app.fast.profile.presentation
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.calculateEndPadding
|
||||||
|
import androidx.compose.foundation.layout.calculateStartPadding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.statusBarsPadding
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Settings
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import com.meloda.app.fast.model.BaseError
|
||||||
|
import com.meloda.app.fast.profile.ProfileViewModel
|
||||||
|
import com.meloda.app.fast.profile.ProfileViewModelImpl
|
||||||
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
|
import com.meloda.app.fast.designsystem.R as UiR
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ProfileScreen(
|
||||||
|
onError: (BaseError) -> Unit,
|
||||||
|
onNavigateToSettings: () -> Unit,
|
||||||
|
viewModel: ProfileViewModel = koinViewModel<ProfileViewModelImpl>()
|
||||||
|
) {
|
||||||
|
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
Log.d("ProfileScreen", "isLoading: ${screenState.isLoading}")
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = onNavigateToSettings) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.Settings,
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { padding ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
|
||||||
|
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr))
|
||||||
|
.padding(bottom = padding.calculateBottomPadding()),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
if (screenState.fullName == null && screenState.isLoading) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.statusBarsPadding())
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
|
AsyncImage(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(120.dp)
|
||||||
|
.clip(CircleShape),
|
||||||
|
model = screenState.avatarUrl,
|
||||||
|
contentDescription = null,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
placeholder = painterResource(id = UiR.drawable.ic_account_circle_cut)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(18.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = screenState.fullName.orEmpty(),
|
||||||
|
style = MaterialTheme.typography.headlineLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user