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/composable/Navigation.kt b/presentation/src/main/java/com/nto/presentation/composable/Navigation.kt index 1bbb8f3..12b4235 100644 --- a/presentation/src/main/java/com/nto/presentation/composable/Navigation.kt +++ b/presentation/src/main/java/com/nto/presentation/composable/Navigation.kt @@ -6,6 +6,7 @@ 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 @@ -15,7 +16,7 @@ fun Navigation(navController: NavHostController, modifier: Modifier = Modifier) NavHost( navController = navController, modifier = modifier, - startDestination = Destinations.Login + startDestination = Destinations.Profile ) { composable { LoginScreen(navController) @@ -27,7 +28,7 @@ fun Navigation(navController: NavHostController, modifier: Modifier = Modifier) ScanResultScreen(navController) } composable { - //TODO + AdminScreen(navController) } composable { //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/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(), ) { 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/ProfileViewModel.kt b/presentation/src/main/java/com/nto/presentation/screens/profileScreen/ProfileViewModel.kt index 12bc576..2ec5a5f 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,32 +27,19 @@ 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 get() = _state.asStateFlow() fun updateInfo() { viewModelScope.launch(Dispatchers.IO) { + _isLoading = true val result = useCase.getInfo() _state.tryEmit(ProfileState().apply { deserialize(result, context) }) + _isLoading = false } } 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 @@ + + + diff --git a/presentation/src/main/res/values-en/strings.xml b/presentation/src/main/res/values-en/strings.xml index ab71b0f..2e23e39 100644 --- a/presentation/src/main/res/values-en/strings.xml +++ b/presentation/src/main/res/values-en/strings.xml @@ -22,4 +22,5 @@ Enter was cancelled Something went wrong Close + Admin \ 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..68cf196 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -23,4 +23,5 @@ Вход был отменён Что-то пошло не так Закрыть + Управление \ No newline at end of file