diff --git a/data/src/main/java/com/nto/data/models/UserDTO.kt b/data/src/main/java/com/nto/data/models/UserDTO.kt index 81a3652..299d36c 100644 --- a/data/src/main/java/com/nto/data/models/UserDTO.kt +++ b/data/src/main/java/com/nto/data/models/UserDTO.kt @@ -8,8 +8,8 @@ enum class Position { data class UserDTO( val firstName: String = "", - val secondName: String = "", - val thirdName: String = "", + val lastName: String = "", + val patronymic: String = "", val position: Position = Position.DEVELOPER, val lastVisit: LocalDateTime = LocalDateTime.now(), val isError: Boolean = false, diff --git a/data/src/main/java/com/nto/data/repository/DataRepository.kt b/data/src/main/java/com/nto/data/repository/DataRepository.kt index 28bf478..5fad021 100644 --- a/data/src/main/java/com/nto/data/repository/DataRepository.kt +++ b/data/src/main/java/com/nto/data/repository/DataRepository.kt @@ -10,7 +10,7 @@ interface DataRepository { suspend fun saveToken(token: String, login: String) suspend fun getToken(): String suspend fun getLogin(): String - suspend fun getInfo(): UserDTO + suspend fun getInfo(login: String): UserDTO suspend fun getVisits(id: String): VisitCardWrapper suspend fun open(): RequestResult suspend fun logout() diff --git a/data/src/main/java/com/nto/data/repository/DataRepositoryImpl.kt b/data/src/main/java/com/nto/data/repository/DataRepositoryImpl.kt index 4ae984a..29a0656 100644 --- a/data/src/main/java/com/nto/data/repository/DataRepositoryImpl.kt +++ b/data/src/main/java/com/nto/data/repository/DataRepositoryImpl.kt @@ -35,8 +35,8 @@ class DataRepositoryImpl @Inject constructor(@ApplicationContext private val con return context.getSharedPreferences("auth", MODE_PRIVATE).getString("login", "")!! } - override suspend fun getInfo(): UserDTO { - val result = Provider.provideRetrofit().getInfo(getToken()).execute() + override suspend fun getInfo(login: String): UserDTO { + val result = Provider.provideRetrofit().getInfo(token = getToken(), login = login).execute() return if (result.isSuccessful) result.body()!! else UserDTO(isError = true, isUnauthorized = result.code() == 403) } diff --git a/data/src/main/java/com/nto/data/utils/RetrofitApi.kt b/data/src/main/java/com/nto/data/utils/RetrofitApi.kt index 1d519ec..d7a677f 100644 --- a/data/src/main/java/com/nto/data/utils/RetrofitApi.kt +++ b/data/src/main/java/com/nto/data/utils/RetrofitApi.kt @@ -12,11 +12,11 @@ import retrofit2.http.Path import retrofit2.http.Query interface RetrofitApi { - @GET("employee/admin/auth") + @GET("employee/auth") fun auth(@Header("Authorization") token: String): Call<ResponseBody> - @GET("employee/info") - fun getInfo(@Header("Authorization") token: String): Call<UserDTO> + @GET("employee/{login}/info") + fun getInfo(@Header("Authorization") token: String, @Path("login") login: String): Call<UserDTO> @GET("visits/{login}/visits") fun getVisits(@Header("Authorization") token: String, @Path("login") login:String): Call<List<VisitCardDTO>> diff --git a/domain/src/main/java/com/nto/domain/repository/DomainRepository.kt b/domain/src/main/java/com/nto/domain/repository/DomainRepository.kt index f4fbd12..5d96ef4 100644 --- a/domain/src/main/java/com/nto/domain/repository/DomainRepository.kt +++ b/domain/src/main/java/com/nto/domain/repository/DomainRepository.kt @@ -9,7 +9,8 @@ interface DomainRepository { suspend fun auth(email: String, password: String): LoginResult suspend fun saveToken(token: String, login: String) suspend fun getToken(): String? - suspend fun getInfo(): UserDTO + suspend fun getLogin(): String + suspend fun getInfo(login: String): UserDTO suspend fun getVisits(id: String): VisitCardWrapper suspend fun open(): RequestResult suspend fun logout() diff --git a/domain/src/main/java/com/nto/domain/repository/DomainRepositoryImpl.kt b/domain/src/main/java/com/nto/domain/repository/DomainRepositoryImpl.kt index f91116f..397c0e8 100644 --- a/domain/src/main/java/com/nto/domain/repository/DomainRepositoryImpl.kt +++ b/domain/src/main/java/com/nto/domain/repository/DomainRepositoryImpl.kt @@ -32,9 +32,13 @@ class DomainRepositoryImpl @Inject constructor(private val dataRepositoryImpl: D } } - override suspend fun getInfo(): UserDTO { + override suspend fun getLogin(): String { + return dataRepositoryImpl.getLogin() + } + + override suspend fun getInfo(login: String): UserDTO { return try { - return dataRepositoryImpl.getInfo() + return dataRepositoryImpl.getInfo(login.ifBlank { getLogin() }) } catch (e: IOException) { UserDTO(isError = true, isUnauthorized = true) } diff --git a/domain/src/main/java/com/nto/domain/usecase/LoginUseCase.kt b/domain/src/main/java/com/nto/domain/usecase/LoginUseCase.kt index 37d4244..41aa8de 100644 --- a/domain/src/main/java/com/nto/domain/usecase/LoginUseCase.kt +++ b/domain/src/main/java/com/nto/domain/usecase/LoginUseCase.kt @@ -12,5 +12,4 @@ class LoginUseCase @Inject constructor(private val domainRepositoryImpl: DomainR suspend fun auth(email: String, password: String): LoginResult { return domainRepositoryImpl.auth(email, password) } - } \ No newline at end of file diff --git a/domain/src/main/java/com/nto/domain/usecase/ProfileUseCase.kt b/domain/src/main/java/com/nto/domain/usecase/ProfileUseCase.kt index 8ef3ae3..7ed91b4 100644 --- a/domain/src/main/java/com/nto/domain/usecase/ProfileUseCase.kt +++ b/domain/src/main/java/com/nto/domain/usecase/ProfileUseCase.kt @@ -7,7 +7,7 @@ import javax.inject.Inject class ProfileUseCase @Inject constructor(private val domainRepositoryImpl: DomainRepositoryImpl) { suspend fun getInfo(): UserDTO{ - return domainRepositoryImpl.getInfo() + return domainRepositoryImpl.getInfo("") } suspend fun logout(){ return domainRepositoryImpl.logout() diff --git a/domain/src/main/java/com/nto/domain/usecase/SplashScreenUseCase.kt b/domain/src/main/java/com/nto/domain/usecase/SplashScreenUseCase.kt new file mode 100644 index 0000000..3a6c0f1 --- /dev/null +++ b/domain/src/main/java/com/nto/domain/usecase/SplashScreenUseCase.kt @@ -0,0 +1,10 @@ +package com.nto.domain.usecase + +import com.nto.domain.repository.DomainRepositoryImpl +import javax.inject.Inject + +class SplashScreenUseCase @Inject constructor(private val domainRepositoryImpl: DomainRepositoryImpl) { + suspend fun getToken(): String? { + return domainRepositoryImpl.getToken() + } +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d70bb2f..1aa93cc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ junitVersion = "1.2.1" espressoCore = "3.6.1" lifecycleRuntimeKtx = "2.8.7" activityCompose = "1.10.0" -composeBom = "2024.04.01" +composeBom = "2024.09.03" kapt = "2.1.10" diff --git a/presentation/src/main/java/com/nto/presentation/MainActivity.kt b/presentation/src/main/java/com/nto/presentation/MainActivity.kt index 7c14f27..3771da5 100644 --- a/presentation/src/main/java/com/nto/presentation/MainActivity.kt +++ b/presentation/src/main/java/com/nto/presentation/MainActivity.kt @@ -1,42 +1,46 @@ package com.nto.presentation -import android.app.Activity -import android.os.Build import android.os.Bundle -import android.view.WindowInsets import androidx.activity.ComponentActivity -import androidx.activity.compose.LocalActivity import androidx.activity.compose.setContent -import androidx.activity.enableEdgeToEdge +import androidx.activity.viewModels import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding -import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Scaffold +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb -import androidx.compose.ui.platform.LocalContext import androidx.navigation.compose.rememberNavController import com.nto.presentation.composable.Navigation +import com.nto.presentation.screens.splashScreen.SplashScreenViewModel import com.nto.presentation.theme.NTOTheme +import com.nto.presentation.theme.TextColor import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class MainActivity : ComponentActivity() { + val viewmodel by viewModels<SplashScreenViewModel>() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContent { - NTOTheme { - //XML SUCKS! We use Jetpack Compose btw :> - this.window.statusBarColor = NTOTheme.colors.primaryText.toArgb() - Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> - val navController = rememberNavController() - Navigation( - navController = navController, - modifier = Modifier - .padding(innerPadding) - .fillMaxSize() - ) + this.window.statusBarColor = TextColor.toArgb() + viewmodel.checkLogin().apply { + setContent { + NTOTheme { + //XML SUCKS! We use Jetpack Compose btw :> + val state = viewmodel.state.collectAsState().value + + Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> + val navController = rememberNavController() + Navigation( + navController = navController, + modifier = Modifier + .padding(innerPadding) + .fillMaxSize(), + skipAuth = state.skipAuth + ) + } } } } diff --git a/presentation/src/main/java/com/nto/presentation/composable/Navigation.kt b/presentation/src/main/java/com/nto/presentation/composable/Navigation.kt index 1bbb8f3..3e86827 100644 --- a/presentation/src/main/java/com/nto/presentation/composable/Navigation.kt +++ b/presentation/src/main/java/com/nto/presentation/composable/Navigation.kt @@ -6,16 +6,17 @@ import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import com.nto.data.utils.Destinations +import com.nto.presentation.screens.admin.AdminScreen import com.nto.presentation.screens.loginScreen.LoginScreen import com.nto.presentation.screens.profileScreen.ProfileScreen import com.nto.presentation.screens.scanResult.ScanResultScreen @Composable -fun Navigation(navController: NavHostController, modifier: Modifier = Modifier) { +fun Navigation(navController: NavHostController, skipAuth: Boolean, modifier: Modifier = Modifier) { NavHost( navController = navController, modifier = modifier, - startDestination = Destinations.Login + startDestination = if (skipAuth) Destinations.Profile else Destinations.Login ) { composable<Destinations.Login> { LoginScreen(navController) @@ -27,7 +28,7 @@ fun Navigation(navController: NavHostController, modifier: Modifier = Modifier) ScanResultScreen(navController) } composable<Destinations.Admin> { - //TODO + AdminScreen(navController) } composable<Destinations.Options> { //TODO diff --git a/presentation/src/main/java/com/nto/presentation/screens/admin/AdminScreen.kt b/presentation/src/main/java/com/nto/presentation/screens/admin/AdminScreen.kt new file mode 100644 index 0000000..544d32e --- /dev/null +++ b/presentation/src/main/java/com/nto/presentation/screens/admin/AdminScreen.kt @@ -0,0 +1,88 @@ +package com.nto.presentation.screens.admin + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.Image +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import com.nto.data.utils.Destinations +import com.nto.presentation.R +import com.nto.presentation.composable.InputField +import com.nto.presentation.theme.NTOTheme +import com.nto.presentation.theme.playfair +import com.nto.presentation.theme.raleway + +@Composable +fun AdminScreen( + navController: NavController, + modifier: Modifier = Modifier, + viewModel: AdminViewModel = hiltViewModel() +) { + Scaffold { innerPaddings -> + Column( + Modifier + .padding(innerPaddings) + .fillMaxWidth() + .padding(horizontal = 24.dp) + ) { + Spacer(Modifier.height(36.dp)) + Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) { + Text( + text = stringResource(R.string.admin_header), + fontFamily = playfair, + color = NTOTheme.colors.primaryText, + fontSize = 32.sp, + fontWeight = FontWeight.Bold + ) + IconButton( + onClick = { + navController.popBackStack() + }, + modifier = Modifier + .size(44.dp) + .border(BorderStroke(2.dp, NTOTheme.colors.buttonDisabled), CircleShape), + ) { + Image( + painter = painterResource(R.drawable.ic_arrow_back), + contentDescription = "" + ) + } + } + Spacer(Modifier.height(20.dp)) + Text( + stringResource(R.string.text_login), + fontFamily = raleway, + fontWeight = FontWeight.Medium, + color = NTOTheme.colors.primaryText, + modifier = Modifier.padding(start = 10.dp), + fontSize = 14.sp + ) + Spacer(modifier = Modifier.height(5.dp)) + InputField( + viewModel.login, + placeholder = stringResource(R.string.placholder_login), + onValueChange = viewModel::setLogin + ) + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/nto/presentation/screens/admin/AdminViewModel.kt b/presentation/src/main/java/com/nto/presentation/screens/admin/AdminViewModel.kt new file mode 100644 index 0000000..9ddd91e --- /dev/null +++ b/presentation/src/main/java/com/nto/presentation/screens/admin/AdminViewModel.kt @@ -0,0 +1,19 @@ +package com.nto.presentation.screens.admin + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class AdminViewModel @Inject constructor(): ViewModel() { + private var _login by mutableStateOf("") + + val login get() = _login + + fun setLogin(value: String) { + _login = value + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/nto/presentation/screens/loginScreen/LoginScreen.kt b/presentation/src/main/java/com/nto/presentation/screens/loginScreen/LoginScreen.kt index 6c1c2c6..044100a 100644 --- a/presentation/src/main/java/com/nto/presentation/screens/loginScreen/LoginScreen.kt +++ b/presentation/src/main/java/com/nto/presentation/screens/loginScreen/LoginScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight diff --git a/presentation/src/main/java/com/nto/presentation/screens/loginScreen/LoginViewModel.kt b/presentation/src/main/java/com/nto/presentation/screens/loginScreen/LoginViewModel.kt index 5c279d3..ef586f5 100644 --- a/presentation/src/main/java/com/nto/presentation/screens/loginScreen/LoginViewModel.kt +++ b/presentation/src/main/java/com/nto/presentation/screens/loginScreen/LoginViewModel.kt @@ -18,7 +18,9 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class LoginViewModel @Inject constructor(private val useCase: LoginUseCase, @ApplicationContext private val context: Context) : ViewModel() { +class LoginViewModel @Inject constructor( + private val useCase: LoginUseCase, @ApplicationContext private val context: Context +) : ViewModel() { private val _state = MutableStateFlow(LoginScreenState()) val state: StateFlow<LoginScreenState> @@ -52,7 +54,7 @@ class LoginViewModel @Inject constructor(private val useCase: LoginUseCase, @App if (result.successful) { Dispatchers.Main { navController.navigate(Destinations.Profile) { - popUpTo<Destinations.Login>() { + popUpTo<Destinations.Login> { inclusive = true } } diff --git a/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileScreen.kt b/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileScreen.kt index 7cdd9f2..b585092 100644 --- a/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileScreen.kt +++ b/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileScreen.kt @@ -1,13 +1,11 @@ package com.nto.presentation.screens.profileScreen -import android.content.ContentValues.TAG -import android.util.Log import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -19,11 +17,16 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton +import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.Text +import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState @@ -41,8 +44,8 @@ import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController import com.journeyapps.barcodescanner.ScanContract import com.journeyapps.barcodescanner.ScanOptions -import com.nto.presentation.CustomCaptureActivity import com.nto.data.utils.Destinations +import com.nto.presentation.CustomCaptureActivity import com.nto.presentation.R import com.nto.presentation.composable.DecoratedButton import com.nto.presentation.composable.cards.VisitCard @@ -51,6 +54,7 @@ import com.nto.presentation.theme.TextGray import com.nto.presentation.theme.playfair import com.nto.presentation.theme.raleway +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ProfileScreen( navController: NavController, @@ -58,7 +62,6 @@ fun ProfileScreen( viewModel: ProfileViewModel = hiltViewModel<ProfileViewModel>(), ) { val state = viewModel.state.collectAsState().value - val scannerLauncher = rememberLauncherForActivityResult( contract = ScanContract(), onResult = { result -> navController.navigate(Destinations.Scan(result.contents)) } @@ -75,199 +78,201 @@ fun ProfileScreen( navController.navigate(Destinations.Login) } } - - Column( - modifier = modifier.background(NTOTheme.colors.primaryBackground) + PullToRefreshBox( + isRefreshing = viewModel.isLoading, + onRefresh = { + viewModel.updateInfo() + } ) { Column( - modifier = Modifier - .fillMaxSize() - .padding(start = 20.dp, end = 20.dp) + modifier = modifier + .background(NTOTheme.colors.primaryBackground) ) { - Spacer(modifier = Modifier.height(10.dp)) - Column(modifier = Modifier.weight(1f)) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - stringResource(R.string.title_profile), - fontFamily = playfair, - color = NTOTheme.colors.primaryText, - fontSize = 32.sp, - fontWeight = FontWeight.Bold + Column( + modifier = Modifier + .fillMaxSize() + .padding(start = 20.dp, end = 20.dp) + .verticalScroll( + rememberScrollState() ) - Row(verticalAlignment = Alignment.CenterVertically) { - Box( - modifier = Modifier - .size(35.dp) - .clip(CircleShape) - .border(2.dp, NTOTheme.colors.buttonAdmin, shape = CircleShape), - contentAlignment = Alignment.Center - ) { - IconButton(modifier = Modifier.size(25.dp), onClick = { - viewModel.admin(navController) - }) { + ) { + Spacer(modifier = Modifier.height(10.dp)) + Column(modifier = Modifier.weight(1f)) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + stringResource(R.string.title_profile), + fontFamily = playfair, + color = NTOTheme.colors.primaryText, + fontSize = 32.sp, + fontWeight = FontWeight.Bold + ) + Row(verticalAlignment = Alignment.CenterVertically) { + IconButton( + onClick = { + navController.navigate(Destinations.Admin) + }, + modifier = Modifier + .size(44.dp) + .border( + BorderStroke(2.dp, NTOTheme.colors.buttonAdmin), + CircleShape + ), + ) { Icon( painter = painterResource(R.drawable.icon_admin), - modifier = Modifier.size(20.dp), contentDescription = null, tint = NTOTheme.colors.buttonAdmin ) } - } - Spacer(modifier = Modifier.width(8.dp)) - Box( - modifier = Modifier - .size(35.dp) - .clip(CircleShape) - .border(2.dp, NTOTheme.colors.buttonDisabled, shape = CircleShape), - contentAlignment = Alignment.Center - ) { - IconButton(modifier = Modifier.size(25.dp), onClick = { - viewModel.option(navController) - }) { + Spacer(modifier = Modifier.width(12.dp)) + IconButton( + onClick = {}, + modifier = Modifier + .size(44.dp) + .border( + BorderStroke(2.dp, NTOTheme.colors.buttonDisabled), + CircleShape + ) + ) { Icon( painter = painterResource(R.drawable.icon_options), - modifier = Modifier.size(20.dp), contentDescription = null, tint = NTOTheme.colors.buttonDisabled ) } - } - Spacer(modifier = Modifier.width(14.dp)) - Box( - modifier = Modifier - .size(35.dp) - .clip(CircleShape) - .background(NTOTheme.colors.buttonDisabled), - contentAlignment = Alignment.Center - ) { - IconButton(modifier = Modifier.size(25.dp), onClick = { - viewModel.logout(navController) - }) { + Spacer(modifier = Modifier.width(16.dp)) + IconButton( + modifier = Modifier.size(44.dp), onClick = { + viewModel.logout(navController) + }, colors = IconButtonDefaults.iconButtonColors( + containerColor = NTOTheme.colors.buttonDisabled + ) + ) { Icon( painter = painterResource(R.drawable.icon_logout), - modifier = Modifier.size(20.dp), contentDescription = null, tint = NTOTheme.colors.primaryBackground ) } } - } - } - Spacer(modifier = Modifier.height(50.dp)) - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - Image( - painter = painterResource(R.drawable.logo_placeholder_user), - modifier = Modifier - .size(100.dp) - .clip( - CircleShape - ), - contentDescription = null - ) - Spacer(modifier = Modifier.width(15.dp)) - Column( + } + Spacer(modifier = Modifier.height(50.dp)) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + Image( + painter = painterResource(R.drawable.logo_placeholder_user), + modifier = Modifier + .size(100.dp) + .clip( + CircleShape + ), + contentDescription = null + ) + Spacer(modifier = Modifier.width(15.dp)) + Column( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(10.dp)) + .background(NTOTheme.colors.inputFieldBackground) + .padding(10.dp) + ) { + Text( + state.secondName, + fontFamily = raleway, + color = NTOTheme.colors.primaryText, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 16.sp + ) + Text( + state.firstName, + fontFamily = raleway, + color = NTOTheme.colors.primaryText, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 16.sp + ) + Text( + state.thirdName, + fontFamily = raleway, + color = NTOTheme.colors.primaryText, + fontSize = 16.sp, + fontWeight = FontWeight.SemiBold, + lineHeight = 16.sp + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + state.job, + fontWeight = FontWeight.Medium, + fontFamily = raleway, + fontSize = 14.sp, + color = TextGray + ) + } + } + Spacer(modifier = Modifier.height(20.dp)) + Row( modifier = Modifier .fillMaxWidth() + .height(35.dp) .clip(RoundedCornerShape(10.dp)) .background(NTOTheme.colors.inputFieldBackground) - .padding(10.dp) + .padding(start = 15.dp, end = 15.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { Text( - state.secondName, - fontFamily = raleway, - color = NTOTheme.colors.primaryText, - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 16.sp - ) - Text( - state.firstName, - fontFamily = raleway, - color = NTOTheme.colors.primaryText, - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 16.sp - ) - Text( - state.thirdName, - fontFamily = raleway, - color = NTOTheme.colors.primaryText, - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - lineHeight = 16.sp - ) - Spacer(modifier = Modifier.height(8.dp)) - Text( - state.job, + stringResource(R.string.label_last_visit), fontWeight = FontWeight.Medium, fontFamily = raleway, - fontSize = 14.sp, - color = TextGray + color = NTOTheme.colors.primaryText, + fontSize = 14.sp + ) + Text( + state.lastOpen, + fontWeight = FontWeight.Normal, + fontFamily = raleway, + color = NTOTheme.colors.primaryText, + fontSize = 14.sp ) } - } - Spacer(modifier = Modifier.height(20.dp)) - Row( - modifier = Modifier - .fillMaxWidth() - .height(35.dp) - .clip(RoundedCornerShape(10.dp)) - .background(NTOTheme.colors.inputFieldBackground) - .padding(start = 15.dp, end = 15.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { + Spacer(modifier = Modifier.height(20.dp)) Text( - stringResource(R.string.label_last_visit), - fontWeight = FontWeight.Medium, + stringResource(R.string.label_visits), + fontWeight = FontWeight.SemiBold, + fontSize = 20.sp, fontFamily = raleway, - color = NTOTheme.colors.primaryText, - fontSize = 14.sp + color = NTOTheme.colors.primaryText ) - Text( - state.lastOpen, - fontWeight = FontWeight.Normal, - fontFamily = raleway, - color = NTOTheme.colors.primaryText, - fontSize = 14.sp - ) - } - Spacer(modifier = Modifier.height(20.dp)) - Text( - stringResource(R.string.label_visits), - fontWeight = FontWeight.SemiBold, - fontSize = 20.sp, - fontFamily = raleway, - color = NTOTheme.colors.primaryText - ) - Spacer(modifier = Modifier.height(10.dp)) + Spacer(modifier = Modifier.height(10.dp)) - LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) { - items(state.visits) { item -> - VisitCard(item) + LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) { + items(state.visits) { item -> + VisitCard(item) + } } } - } - DecoratedButton( - stringResource(R.string.label_scan), - false, - modifier = Modifier - .padding(bottom = 10.dp) - .fillMaxWidth() - .height(62.dp) - ) { - scannerLauncher.launch(scanOptions) + DecoratedButton( + stringResource(R.string.label_scan), + false, + modifier = Modifier + .padding(bottom = 10.dp) + .fillMaxWidth() + .height(62.dp) + ) { + scannerLauncher.launch(scanOptions) + } } } } + } @Preview diff --git a/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileState.kt b/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileState.kt index 9fe77c9..fdfb8ce 100644 --- a/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileState.kt +++ b/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileState.kt @@ -5,7 +5,6 @@ import com.nto.data.models.Position import com.nto.data.models.UserDTO import com.nto.data.models.cards.VisitCardDTO import com.nto.presentation.R -import java.text.SimpleDateFormat import java.time.format.DateTimeFormatter data class ProfileState( @@ -16,18 +15,22 @@ data class ProfileState( var job: String = "", var isUnauthorized: Boolean = false, val visits: List<VisitCardDTO> = listOf() -){ - fun deserialize(o: UserDTO, context: Context){ +) { + fun deserialize(o: UserDTO, context: Context) { val dateFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") this.firstName = o.firstName - this.secondName = o.secondName - this.thirdName = o.thirdName - this.lastOpen = o.lastVisit.format(dateFormat) + this.secondName = o.lastName + this.thirdName = o.patronymic + this.lastOpen = try { + o.lastVisit.format(dateFormat) + } catch (e: NullPointerException) { + context.getString(R.string.label_last_visit_none) + } this.job = translatePosition(o.position, context) this.isUnauthorized = o.isUnauthorized } - private fun translatePosition(position: Position, context:Context): String { + private fun translatePosition(position: Position, context: Context): String { return when (position) { Position.TESTER -> context.getString(R.string.label_tester) Position.DEVELOPER -> context.getString(R.string.label_developer) diff --git a/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileViewModel.kt b/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileViewModel.kt index 3a57e00..f55d1e1 100644 --- a/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileViewModel.kt +++ b/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileViewModel.kt @@ -1,6 +1,9 @@ package com.nto.presentation.screens.profileScreen import android.content.Context +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.NavController @@ -24,33 +27,20 @@ import javax.inject.Inject class ProfileViewModel @Inject constructor( private val useCase: ProfileUseCase, @ApplicationContext private val context: Context ) : ViewModel() { - private val _state = MutableStateFlow( - ProfileState( - "Николай", - "Одегов", - "Алексеевич", - "20 января 2024 20:21", - "Senior UI/UX designer", - visits = listOf( - VisitCardDTO( - "Кабинет 207", - 10020, - LocalDateTime.now(), - VisitType.CARD_ENTRY, - qrCode = QRDTO(12032, "Кобинет 52") - ) - ) - ) - ) + private var _isLoading by mutableStateOf(false) + private val _state = MutableStateFlow(ProfileState()) + val isLoading get() = _isLoading val state: StateFlow<ProfileState> get() = _state.asStateFlow() fun updateInfo() { viewModelScope.launch(Dispatchers.IO) { + _isLoading = true val result = useCase.getInfo() //val visits = useCase.get _state.tryEmit(ProfileState().apply { deserialize(result, context) }) + _isLoading = false } } diff --git a/presentation/src/main/java/com/nto/presentation/screens/splashScreen/SplashScreenState.kt b/presentation/src/main/java/com/nto/presentation/screens/splashScreen/SplashScreenState.kt new file mode 100644 index 0000000..068e4a5 --- /dev/null +++ b/presentation/src/main/java/com/nto/presentation/screens/splashScreen/SplashScreenState.kt @@ -0,0 +1,5 @@ +package com.nto.presentation.screens.splashScreen + +data class SplashScreenState( + val skipAuth: Boolean = false +) diff --git a/presentation/src/main/java/com/nto/presentation/screens/splashScreen/SplashScreenViewModel.kt b/presentation/src/main/java/com/nto/presentation/screens/splashScreen/SplashScreenViewModel.kt new file mode 100644 index 0000000..59d2b31 --- /dev/null +++ b/presentation/src/main/java/com/nto/presentation/screens/splashScreen/SplashScreenViewModel.kt @@ -0,0 +1,31 @@ +package com.nto.presentation.screens.splashScreen + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.nto.domain.usecase.SplashScreenUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SplashScreenViewModel @Inject constructor(private val useCase: SplashScreenUseCase) : ViewModel(){ + private val _state = MutableStateFlow( + SplashScreenState() + ) + + val state: StateFlow<SplashScreenState> + get() = _state.asStateFlow() + + fun checkLogin(){ + viewModelScope.launch(Dispatchers.IO) { + val token = useCase.getToken() + if (!token.isNullOrBlank()){ + _state.tryEmit(_state.value.copy(skipAuth = true)) + } + } + } +} \ No newline at end of file diff --git a/presentation/src/main/java/com/nto/presentation/theme/Color.kt b/presentation/src/main/java/com/nto/presentation/theme/Color.kt index 42b52aa..b3b06a7 100644 --- a/presentation/src/main/java/com/nto/presentation/theme/Color.kt +++ b/presentation/src/main/java/com/nto/presentation/theme/Color.kt @@ -12,6 +12,7 @@ val Green = Color(0xFF738D73) val GreenDisabled = Color(0xFFCAD5CA) val Error = Color(0xFFD28989) val Warning = Color(0xFFCFC37F) +val TextColor = Color(0xFF211A1D) @Immutable data class AppColors( @@ -51,7 +52,7 @@ val extendedColor = AppColors( secondaryBackground = Color.Black, inputFieldBackground = BoxGray, disabledText = TextGray, - primaryText = Color.Black, + primaryText = TextColor, secondaryText = Color.White, button = Green, buttonDisabled = GreenDisabled, diff --git a/presentation/src/main/res/drawable/ic_arrow_back.xml b/presentation/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 0000000..f58534e --- /dev/null +++ b/presentation/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:pathData="M7.825,13L13.425,18.6L12,20L4,12L12,4L13.425,5.4L7.825,11H20V13H7.825Z" + android:fillColor="#CAD5CA"/> +</vector> diff --git a/presentation/src/main/res/values-en/strings.xml b/presentation/src/main/res/values-en/strings.xml index ab71b0f..a1db460 100644 --- a/presentation/src/main/res/values-en/strings.xml +++ b/presentation/src/main/res/values-en/strings.xml @@ -22,4 +22,6 @@ <string name="code_scanned_error">Enter was cancelled</string> <string name="code_scanned_warning">Something went wrong</string> <string name="close">Close</string> + <string name="label_last_visit_none">None</string> + <string name="admin_header">Admin</string> </resources> \ No newline at end of file diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index ac23097..7e37ed6 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -23,4 +23,6 @@ <string name="code_scanned_error">Вход был отменён</string> <string name="code_scanned_warning">Что-то пошло не так</string> <string name="close">Закрыть</string> + <string name="admin_header">Управление</string> + <string name="label_last_visit_none">Нет</string> </resources> \ No newline at end of file