update package name (even bigger one)

This commit is contained in:
2024-07-16 07:02:50 +03:00
parent 4f9e49003b
commit c8b1d72f08
367 changed files with 12 additions and 25 deletions
@@ -0,0 +1,93 @@
package dev.meloda.fast.profile
import androidx.lifecycle.ViewModel
import dev.meloda.fast.common.UserConfig
import dev.meloda.fast.common.VkConstants
import dev.meloda.fast.common.extensions.listenValue
import dev.meloda.fast.common.extensions.setValue
import dev.meloda.fast.data.State
import dev.meloda.fast.data.api.users.UsersUseCase
import dev.meloda.fast.data.processState
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.network.VkErrorCode
import dev.meloda.fast.profile.model.ProfileScreenState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
interface ProfileViewModel {
val screenState: StateFlow<ProfileScreenState>
val baseError: StateFlow<BaseError?>
}
class ProfileViewModelImpl(
private val usersUseCase: UsersUseCase
) : ViewModel(), ProfileViewModel {
override val screenState = MutableStateFlow(ProfileScreenState.EMPTY)
override val baseError = MutableStateFlow<BaseError?>(null)
init {
getLocalAccountInfo()
}
private fun getLocalAccountInfo() {
usersUseCase.getLocalUser(UserConfig.userId)
.listenValue { state ->
state.processState(
error = { error ->
if (error is State.Error.ApiError) {
when (error.errorCode) {
VkErrorCode.USER_AUTHORIZATION_FAILED -> {
baseError.setValue { BaseError.SessionExpired }
}
else -> Unit
}
}
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()) }
}
}
}
@@ -0,0 +1,9 @@
package dev.meloda.fast.profile.di
import dev.meloda.fast.profile.ProfileViewModelImpl
import org.koin.androidx.viewmodel.dsl.viewModelOf
import org.koin.dsl.module
val profileModule = module {
viewModelOf(::ProfileViewModelImpl)
}
@@ -0,0 +1,16 @@
package dev.meloda.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
)
}
}
@@ -0,0 +1,31 @@
package dev.meloda.fast.profile.navigation
import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import dev.meloda.fast.common.extensions.navigation.sharedViewModel
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.profile.ProfileViewModel
import dev.meloda.fast.profile.ProfileViewModelImpl
import dev.meloda.fast.profile.presentation.ProfileRoute
import kotlinx.serialization.Serializable
@Serializable
object Profile
fun NavGraphBuilder.profileScreen(
onError: (BaseError) -> Unit,
onSettingsButtonClicked: () -> Unit,
navController: NavController
) {
composable<Profile> {
val viewModel: ProfileViewModel =
it.sharedViewModel<ProfileViewModelImpl>(navController = navController)
ProfileRoute(
onError = onError,
onSettingsButtonClicked = onSettingsButtonClicked,
viewModel = viewModel
)
}
}
@@ -0,0 +1,124 @@
package dev.meloda.fast.profile.presentation
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 dev.meloda.fast.model.BaseError
import dev.meloda.fast.profile.ProfileViewModel
import dev.meloda.fast.profile.ProfileViewModelImpl
import dev.meloda.fast.profile.model.ProfileScreenState
import org.koin.androidx.compose.koinViewModel
import dev.meloda.fast.ui.R as UiR
@Composable
fun ProfileRoute(
onError: (BaseError) -> Unit,
onSettingsButtonClicked: () -> Unit,
viewModel: ProfileViewModel = koinViewModel<ProfileViewModelImpl>()
) {
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
ProfileScreen(
screenState = screenState,
baseError = baseError,
onSettingsButtonClicked = onSettingsButtonClicked
)
}
// TODO: 13/07/2024, Danil Nikolaev: handle expired session
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ProfileScreen(
screenState: ProfileScreenState = ProfileScreenState.EMPTY,
baseError: BaseError? = null,
onSettingsButtonClicked: () -> Unit = {},
) {
Scaffold(
topBar = {
TopAppBar(
title = {},
actions = {
IconButton(onClick = onSettingsButtonClicked) {
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
)
}
}
}
}