diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a5ccda1..9b849bb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -36,7 +36,7 @@ android { dependencies { defaultComposeLibrary() - implementation("androidx.datastore:datastore-preferences:1.1.7") + implementation("androidx.datastore:datastore-preferences:1.2.0") implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0") implementation("androidx.navigation:navigation-compose:2.9.6") val coil = "3.3.0" diff --git a/app/src/main/java/ru/myitschool/work/App.kt b/app/src/main/java/ru/myitschool/work/App.kt index aa33483..fcf320f 100644 --- a/app/src/main/java/ru/myitschool/work/App.kt +++ b/app/src/main/java/ru/myitschool/work/App.kt @@ -2,11 +2,21 @@ package ru.myitschool.work import android.app.Application import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.preferencesDataStore +import ru.myitschool.work.data.datastore.DataStoreManager + +val Context.dataStore: DataStore by preferencesDataStore(name = "datastore") class App: Application() { + + lateinit var dataStoreManager: DataStoreManager + override fun onCreate() { super.onCreate() context = this + dataStoreManager = DataStoreManager(dataStore) } companion object { diff --git a/app/src/main/java/ru/myitschool/work/data/datastore/DataStoreManager.kt b/app/src/main/java/ru/myitschool/work/data/datastore/DataStoreManager.kt new file mode 100644 index 0000000..5ce914b --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/datastore/DataStoreManager.kt @@ -0,0 +1,29 @@ +package ru.myitschool.work.data.datastore + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class DataStoreManager( + private val dataStore: DataStore +) { + + companion object { + private val USER_CODE_KEY = stringPreferencesKey("user_code") + } + + suspend fun saveUserCode(userCode: UserCode) { + dataStore.edit { preferences -> + preferences[USER_CODE_KEY] = userCode.code + } + } + + fun getUserCode(): Flow = dataStore.data.map { preferences -> + UserCode( + code = preferences[USER_CODE_KEY] ?: "" + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/data/datastore/UserCode.kt b/app/src/main/java/ru/myitschool/work/data/datastore/UserCode.kt new file mode 100644 index 0000000..e408283 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/datastore/UserCode.kt @@ -0,0 +1,5 @@ +package ru.myitschool.work.data.datastore + +data class UserCode( + val code: String +) diff --git a/app/src/main/java/ru/myitschool/work/ui/Composables.kt b/app/src/main/java/ru/myitschool/work/ui/Composables.kt new file mode 100644 index 0000000..e8855fc --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/Composables.kt @@ -0,0 +1,236 @@ +package ru.myitschool.work.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.RowScope +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.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import ru.myitschool.work.R +import ru.myitschool.work.core.TestIds.Main +import ru.myitschool.work.ui.theme.Black +import ru.myitschool.work.ui.theme.Blue +import ru.myitschool.work.ui.theme.Gray +import ru.myitschool.work.ui.theme.LightBlue +import ru.myitschool.work.ui.theme.LightGray +import ru.myitschool.work.ui.theme.Typography +import ru.myitschool.work.ui.theme.White + +@Composable +fun BaseText24( + text: String, + modifier: Modifier = Modifier, + color: Color = Black, + textAlign: TextAlign = TextAlign.Left +) { + Text( + text = text, + fontSize = 24.sp, + style = Typography.bodyLarge, + modifier = modifier, + color = color, + textAlign = textAlign + ) +} + +@Composable +fun BaseNoBackgroundButton( + text: String, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Button( + onClick = onClick, + modifier = modifier, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = White, + disabledContainerColor = Color.Transparent, + disabledContentColor = White + ) + ) { + BaseText16(text = text, color = White) + } +} + +@Composable +fun Logo() { + Image( + painter = painterResource(R.drawable.logo), + contentDescription = "Logo", + modifier = Modifier.padding(top = 40.dp, bottom = 60.dp) + ) +} + +@Composable +fun BaseText16( + text: String, + modifier: Modifier = Modifier, + color: Color = Black, +) { + Text( + text = text, + style = Typography.bodySmall, + fontSize = 16.sp, + color = color, + modifier = modifier + ) +} + +@Composable +fun BaseText12( + modifier: Modifier = Modifier, + text: String, + color: Color = Black, +) { + Text( + text = text, + style = Typography.bodySmall, + fontSize = 12.sp, + color = color, + modifier = modifier, + ) +} + +@Composable +fun BaseText14( + modifier: Modifier = Modifier, + text: String, + color: Color = Black, +) { + Text( + text = text, + style = Typography.bodySmall, + fontSize = 14.sp, + color = color, + modifier = modifier, + ) +} + +@Composable +fun BaseInputText( + placeholder: String= "", + modifier: Modifier = Modifier, + onValueChange: (String) -> Unit, + value: String +) { + + TextField( + value = value, + onValueChange = onValueChange, + shape = RoundedCornerShape(16.dp), + placeholder = { + BaseText16( + text = placeholder, + color = Gray + ) + }, + textStyle = Typography.bodySmall.copy(fontSize = 16.sp), + colors = TextFieldDefaults.colors( + focusedContainerColor = LightBlue, + unfocusedContainerColor = LightBlue, + focusedIndicatorColor = Color.Transparent, + unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, + errorIndicatorColor = Color.Transparent + ), + singleLine = true, + modifier = modifier + ) +} + +@Composable +fun BaseText20( + text: String, + color: Color = Color.Unspecified, + style: TextStyle = Typography.bodySmall, + modifier: Modifier = Modifier.padding(7.dp), + textAlign: TextAlign = TextAlign.Unspecified +) { + Text( + text = text, + style = style, + fontSize = 20.sp, + modifier = modifier, + color = color, + textAlign = textAlign + ) +} + +@Composable +fun BaseButton( + enable: Boolean = true, + text: String, + btnColor: Color, + btnContentColor: Color, + onClick: () -> Unit, + icon: @Composable RowScope.() -> Unit = {}, + modifier: Modifier = Modifier.fillMaxWidth() +) { + Button( + enabled = enable, + onClick = onClick, + colors = ButtonDefaults.buttonColors( + containerColor = btnColor, + contentColor = btnContentColor, + disabledContainerColor = LightGray, + disabledContentColor = Gray + ), + modifier = modifier, + shape = RoundedCornerShape(16.dp), + ) { + icon() + BaseText20(text = text) + } +} + +@Composable +fun ErrorScreen() { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(horizontal = 20.dp, vertical = 40.dp) + ) { + + Spacer(modifier = Modifier.height(80.dp)) + + BaseText24( + text = "Ошибка загрузки данных", + textAlign = TextAlign.Center, + modifier = Modifier + .testTag(Main.ERROR) + .width(250.dp) + ) + + Spacer(modifier = Modifier.height(30.dp)) + + BaseButton( + modifier = Modifier.testTag(Main.REFRESH_BUTTON), + text = "Обновить", + btnColor = Blue, + btnContentColor = White, + onClick = {} + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/nav/SplashScreenDestination.kt b/app/src/main/java/ru/myitschool/work/ui/nav/SplashScreenDestination.kt new file mode 100644 index 0000000..4fc41c7 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/nav/SplashScreenDestination.kt @@ -0,0 +1,6 @@ +package ru.myitschool.work.ui.nav + +import kotlinx.serialization.Serializable + +@Serializable +data object SplashScreenDestination: AppDestination \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/NavigationGraph.kt b/app/src/main/java/ru/myitschool/work/ui/screen/NavigationGraph.kt index 01b0f32..3b58440 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/NavigationGraph.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/NavigationGraph.kt @@ -14,7 +14,10 @@ import androidx.navigation.compose.rememberNavController import ru.myitschool.work.ui.nav.AuthScreenDestination import ru.myitschool.work.ui.nav.BookScreenDestination import ru.myitschool.work.ui.nav.MainScreenDestination +import ru.myitschool.work.ui.nav.SplashScreenDestination import ru.myitschool.work.ui.screen.auth.AuthScreen +import ru.myitschool.work.ui.screen.main.MainScreen +import ru.myitschool.work.ui.screen.splash.SplashScreen @Composable fun AppNavHost( @@ -26,17 +29,16 @@ fun AppNavHost( enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None }, navController = navController, - startDestination = AuthScreenDestination, + startDestination = SplashScreenDestination, ) { + composable { + SplashScreen(navController = navController) + } composable { AuthScreen(navController = navController) } composable { - Box( - contentAlignment = Alignment.Center - ) { - Text(text = "Hello") - } + MainScreen(navController = navController) } composable { Box( diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthScreen.kt b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthScreen.kt index f99978e..8e15357 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthScreen.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthScreen.kt @@ -1,27 +1,20 @@ package ru.myitschool.work.ui.screen.auth -import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size -import androidx.compose.material3.Button +import androidx.compose.foundation.layout.width import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.material3.TextField import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -30,12 +23,20 @@ import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import ru.myitschool.work.R import ru.myitschool.work.core.TestIds +import ru.myitschool.work.ui.BaseButton +import ru.myitschool.work.ui.BaseInputText +import ru.myitschool.work.ui.BaseText12 +import ru.myitschool.work.ui.BaseText24 +import ru.myitschool.work.ui.Logo import ru.myitschool.work.ui.nav.MainScreenDestination +import ru.myitschool.work.ui.theme.Blue +import ru.myitschool.work.ui.theme.Red +import ru.myitschool.work.ui.theme.White @Composable fun AuthScreen( viewModel: AuthViewModel = viewModel(), - navController: NavController + navController: NavController, ) { val state by viewModel.uiState.collectAsState() @@ -45,24 +46,34 @@ fun AuthScreen( } } - Column( - modifier = Modifier - .fillMaxSize() - .padding(all = 24.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxSize() ) { - Text( - text = stringResource(R.string.auth_title), - style = MaterialTheme.typography.headlineSmall, - textAlign = TextAlign.Center - ) - when (val currentState = state) { - is AuthState.Data -> Content(viewModel, currentState) - is AuthState.Loading -> { - CircularProgressIndicator( - modifier = Modifier.size(64.dp) - ) + Column( + modifier = Modifier + .width(400.dp) + .fillMaxHeight() + .padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + + Logo() + + BaseText24( + text = stringResource(R.string.auth_title), + textAlign = TextAlign.Center + ) + + when (state) { + is AuthState.Data -> Content(viewModel) + is AuthState.Loading -> { + CircularProgressIndicator( + modifier = Modifier + .padding(top = 40.dp) + .size(64.dp) + ) + } } } } @@ -70,28 +81,50 @@ fun AuthScreen( @Composable private fun Content( - viewModel: AuthViewModel, - state: AuthState.Data + viewModel: AuthViewModel ) { - var inputText by remember { mutableStateOf("") } - Spacer(modifier = Modifier.size(16.dp)) - TextField( - modifier = Modifier.testTag(TestIds.Auth.CODE_INPUT).fillMaxWidth(), - value = inputText, - onValueChange = { - inputText = it - viewModel.onIntent(AuthIntent.TextInput(it)) - }, - label = { Text(stringResource(R.string.auth_label)) } - ) - Spacer(modifier = Modifier.size(16.dp)) - Button( - modifier = Modifier.testTag(TestIds.Auth.SIGN_BUTTON).fillMaxWidth(), - onClick = { - viewModel.onIntent(AuthIntent.Send(inputText)) - }, - enabled = true + + val isButtonEnabled by viewModel.isButtonEnabled.collectAsState() + val errorStateValue by viewModel.errorStateValue.collectAsState() + val textState by viewModel.textState.collectAsState() + + Column( + modifier = Modifier.padding(vertical = 20.dp) ) { - Text(stringResource(R.string.auth_sign_in)) + + BaseInputText( + value = textState, + placeholder = stringResource(R.string.auth_label), + modifier = Modifier + .testTag(TestIds.Auth.CODE_INPUT) + .fillMaxWidth(), + onValueChange = { viewModel.onIntent(AuthIntent.TextInput(it)) } + ) + + if (errorStateValue != "") { + BaseText12( + text = errorStateValue, + color = Red, + modifier = Modifier + .testTag(TestIds.Auth.ERROR) + .padding( + start = 10.dp, + top = 5.dp, + bottom = 0.dp + ) + .fillMaxWidth() + ) + } } + + BaseButton( + text = stringResource(R.string.auth_sign_in), + onClick = { viewModel.onIntent(AuthIntent.Send(textState)) }, + btnColor = Blue, + enable = isButtonEnabled, + btnContentColor = White, + modifier = Modifier + .testTag(TestIds.Auth.SIGN_BUTTON) + .fillMaxWidth() + ) } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt index 3153640..4fc9a70 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/auth/AuthViewModel.kt @@ -1,6 +1,7 @@ package ru.myitschool.work.ui.screen.auth -import androidx.lifecycle.ViewModel +import android.app.Application +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow @@ -10,34 +11,52 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch +import ru.myitschool.work.App +import ru.myitschool.work.data.datastore.UserCode import ru.myitschool.work.data.repo.AuthRepository import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase -class AuthViewModel : ViewModel() { +class AuthViewModel(application: Application) : AndroidViewModel(application) { + + private val dataStoreManager by lazy { + (getApplication() as App).dataStoreManager + } private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) } private val _uiState = MutableStateFlow(AuthState.Data) val uiState: StateFlow = _uiState.asStateFlow() - private val _actionFlow: MutableSharedFlow = MutableSharedFlow() val actionFlow: SharedFlow = _actionFlow + private val _errorStateValue = MutableStateFlow("") + val errorStateValue: StateFlow = _errorStateValue.asStateFlow() + private val _isButtonEnabled = MutableStateFlow(false) + val isButtonEnabled: StateFlow = _isButtonEnabled.asStateFlow() + private val _textState = MutableStateFlow("") + val textState: StateFlow = _textState.asStateFlow() fun onIntent(intent: AuthIntent) { when (intent) { is AuthIntent.Send -> { viewModelScope.launch(Dispatchers.Default) { _uiState.update { AuthState.Loading } - checkAndSaveAuthCodeUseCase.invoke("9999").fold( + checkAndSaveAuthCodeUseCase.invoke(intent.text).fold( onSuccess = { + dataStoreManager.saveUserCode(UserCode(code = intent.text)) _actionFlow.emit(Unit) }, onFailure = { error -> error.printStackTrace() - _actionFlow.emit(Unit) + _uiState.update { AuthState.Data } + _errorStateValue.value = "Неизвестная ошибка" } ) } } - is AuthIntent.TextInput -> Unit + is AuthIntent.TextInput -> { + _textState.value = intent.text + _errorStateValue.value = "" + _isButtonEnabled.value = if (intent.text.length == 4 && intent.text.matches(Regex("^[a-zA-Z0-9]*\$"))) + true else false + } } } } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/main/MainScreen.kt b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainScreen.kt new file mode 100644 index 0000000..fee50ff --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainScreen.kt @@ -0,0 +1,203 @@ +package ru.myitschool.work.ui.screen.main + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +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 +import androidx.compose.foundation.layout.fillMaxSize +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.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +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.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import ru.myitschool.work.R +import ru.myitschool.work.core.TestIds.Main +import ru.myitschool.work.ui.BaseButton +import ru.myitschool.work.ui.BaseNoBackgroundButton +import ru.myitschool.work.ui.BaseText14 +import ru.myitschool.work.ui.BaseText20 +import ru.myitschool.work.ui.theme.Black +import ru.myitschool.work.ui.theme.Blue +import ru.myitschool.work.ui.theme.LightGray +import ru.myitschool.work.ui.theme.Typography +import ru.myitschool.work.ui.theme.White + +@Composable +fun MainScreen( + navController: NavController, + viewModel: MainViewModel = viewModel() +) { + + val state by viewModel.uiState.collectAsState() + + when(state) { + is MainState.Loading -> { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier.fillMaxSize() + ) { + CircularProgressIndicator( + modifier = Modifier.size(64.dp) + ) + } + } + is MainState.Error -> { + + } + is MainState.Data -> { + Content(viewModel) + } + } +} + +@Composable +fun Content(viewModel: MainViewModel) { + Column ( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .background(LightGray) + .fillMaxSize() + .width(400.dp) + + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .clip(RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp)) + .background(Blue) + .fillMaxWidth() + .padding(10.dp) + ) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + BaseNoBackgroundButton( + text = "Обновить", + onClick = { }, + modifier = Modifier.testTag(Main.REFRESH_BUTTON) + ) + BaseNoBackgroundButton( + text = "Выйти", + onClick = { }, + modifier = Modifier.testTag(Main.LOGOUT_BUTTON) + ) + } + + Image( + painter = painterResource(R.drawable.avatar), + contentDescription = "User avatar", + modifier = Modifier + .testTag(Main.PROFILE_IMAGE) + .padding(20.dp) + ) + + BaseText20( + text = "Артемий Артемиев Иванович", + color = White, + textAlign = TextAlign.Center, + modifier = Modifier + .testTag(Main.PROFILE_NAME) + .width(250.dp), + style = Typography.bodyLarge + ) + + Spacer(modifier = Modifier.height(20.dp)) + } + Column( + verticalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxSize() + .padding(20.dp) + .clip(RoundedCornerShape(16.dp)) + .background(White) + ) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = "Ваши забронированные места", + style = Typography.bodyMedium, + color = Black, + fontSize = 16.sp, + modifier = Modifier.padding( + horizontal = 10.dp, + vertical = 20.dp + ) + ) + BookList() + } + BaseButton( + text = "Бронировать", + btnColor = Blue, + btnContentColor = White, + onClick = {}, + modifier = Modifier + .testTag(Main.ADD_BUTTON) + .padding(horizontal = 10.dp, vertical = 15.dp) + .fillMaxWidth(), + icon = {Image( + painter = painterResource(R.drawable.add_icon), + contentDescription = "plus Icon" + )} + + ) + } + } +} + +val bookListData = listOf( + "Конгресс Холл", + "Конгресс Холл", + "Конгресс Холл" +) + +@Composable +fun BookList() { + Column( + modifier = Modifier.padding(horizontal = 20.dp) + ) { + for ((index, book) in bookListData.withIndex()) { + BookListElement(index) + } + } +} + +@Composable +fun BookListElement(index: Int) { + Row( + modifier = Modifier + .testTag(Main.getIdItemByPosition(index)) + .fillMaxWidth() + .padding(vertical = 20.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + BaseText14( + text = "Конгресс Холл", + modifier = Modifier.testTag(Main.ITEM_PLACE) + ) + BaseText14( + text = "16.02.3026", + modifier = Modifier.testTag(Main.ITEM_DATE) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/main/MainState.kt b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainState.kt new file mode 100644 index 0000000..067cf84 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainState.kt @@ -0,0 +1,7 @@ +package ru.myitschool.work.ui.screen.main + +sealed interface MainState { + object Data: MainState + object Loading: MainState + object Error: MainState +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/main/MainViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainViewModel.kt new file mode 100644 index 0000000..76869d7 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainViewModel.kt @@ -0,0 +1,15 @@ +package ru.myitschool.work.ui.screen.main + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class MainViewModel: ViewModel() { + + private val _uiState = MutableStateFlow(MainState.Loading) + val uiState: StateFlow = _uiState.asStateFlow() + + + +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/splash/SplashScreen.kt b/app/src/main/java/ru/myitschool/work/ui/screen/splash/SplashScreen.kt new file mode 100644 index 0000000..2b995ce --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/splash/SplashScreen.kt @@ -0,0 +1,49 @@ +package ru.myitschool.work.ui.screen.splash + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.Alignment +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import ru.myitschool.work.ui.nav.AuthScreenDestination +import ru.myitschool.work.ui.nav.MainScreenDestination + +@Composable +fun SplashScreen( + navController: NavController, + viewModel: SplashViewModel = viewModel() + ) { + + val splashState by viewModel.splashState.collectAsState() + + LaunchedEffect(splashState) { + when (splashState) { + is SplashState.Authenticated -> { + navController.navigate(MainScreenDestination) + } + is SplashState.UnAuthenticated -> { + navController.navigate(AuthScreenDestination) + } + is SplashState.Error -> { + navController.navigate(AuthScreenDestination) + } + SplashState.Loading -> { + } + } + } + + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator(modifier = Modifier.size(64.dp)) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/splash/SplashState.kt b/app/src/main/java/ru/myitschool/work/ui/screen/splash/SplashState.kt new file mode 100644 index 0000000..9920f30 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/splash/SplashState.kt @@ -0,0 +1,10 @@ +package ru.myitschool.work.ui.screen.splash + +import android.os.Message + +sealed interface SplashState { + object Loading: SplashState + object Authenticated: SplashState + object UnAuthenticated: SplashState + class Error(message: String): SplashState +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/splash/SplashViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/screen/splash/SplashViewModel.kt new file mode 100644 index 0000000..4b419b4 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/splash/SplashViewModel.kt @@ -0,0 +1,48 @@ +package ru.myitschool.work.ui.screen.splash + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import ru.myitschool.work.App + +class SplashViewModel(application: Application) : AndroidViewModel(application) { + + private val dataStoreManager by lazy { + (getApplication() as App).dataStoreManager + } + + private val _splashState = MutableStateFlow(SplashState.Loading) + val splashState: StateFlow = _splashState.asStateFlow() + + init { + checkAuthStatus() + } + + private fun checkAuthStatus() { + viewModelScope.launch { + try { + val userCode = dataStoreManager.getUserCode().first() + + val isAuthenticated = when { + userCode == null -> false + userCode.code is String -> (userCode.code as String).isNotEmpty() + userCode.code is Int -> (userCode.code as Int) != -1 + else -> false + } + + _splashState.value = if (isAuthenticated) { + SplashState.Authenticated + } else { + SplashState.UnAuthenticated + } + } catch (e: Exception) { + _splashState.value = SplashState.Error(e.message ?: "Unknown error") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/theme/Color.kt b/app/src/main/java/ru/myitschool/work/ui/theme/Color.kt index 22226f4..81685a2 100644 --- a/app/src/main/java/ru/myitschool/work/ui/theme/Color.kt +++ b/app/src/main/java/ru/myitschool/work/ui/theme/Color.kt @@ -8,4 +8,18 @@ val Pink80 = Color(0xFFEFB8C8) val Purple40 = Color(0xFF6650a4) val PurpleGrey40 = Color(0xFF625b71) -val Pink40 = Color(0xFF7D5260) \ No newline at end of file +val Pink40 = Color(0xFF7D5260) + +val Blue = Color(0xFF004BFF) + +val Gray = Color(0xFF777777) + +val LightBlue = Color(0xFFF2EFFF) + +val White = Color(0xFFFFFFFF) + +val Red = Color(0xFFFF4D4D) + +val LightGray = Color(0xFFF2F1F7) + +val Black = Color(0xFF000000) \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/theme/Type.kt b/app/src/main/java/ru/myitschool/work/ui/theme/Type.kt index 61b2923..4ef268d 100644 --- a/app/src/main/java/ru/myitschool/work/ui/theme/Type.kt +++ b/app/src/main/java/ru/myitschool/work/ui/theme/Type.kt @@ -2,19 +2,34 @@ package ru.myitschool.work.ui.theme import androidx.compose.material3.Typography import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.sp +import ru.myitschool.work.R // Set of Material typography styles to start with + +val MontserratFontFamily = FontFamily( + Font(R.font.montserrat_bold, FontWeight.Bold), + Font(R.font.montserrat_medium, FontWeight.Medium), + Font(R.font.montserrat_semibold, weight = FontWeight.SemiBold) +) + val Typography = Typography( + bodySmall = TextStyle( + fontFamily = MontserratFontFamily, + fontWeight = FontWeight.Medium, + ), + bodyMedium = TextStyle( + fontFamily = MontserratFontFamily, + fontWeight = FontWeight.SemiBold, + ), bodyLarge = TextStyle( - fontFamily = FontFamily.Default, - fontWeight = FontWeight.Normal, - fontSize = 16.sp, - lineHeight = 24.sp, - letterSpacing = 0.5.sp - ) + fontFamily = MontserratFontFamily, + fontWeight = FontWeight.Bold, + ), + /* Other default text styles to override titleLarge = TextStyle( fontFamily = FontFamily.Default, diff --git a/app/src/main/res/drawable/add_icon.xml b/app/src/main/res/drawable/add_icon.xml new file mode 100644 index 0000000..3a2acfa --- /dev/null +++ b/app/src/main/res/drawable/add_icon.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/avatar.xml b/app/src/main/res/drawable/avatar.xml new file mode 100644 index 0000000..98811f3 --- /dev/null +++ b/app/src/main/res/drawable/avatar.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/logo.xml b/app/src/main/res/drawable/logo.xml new file mode 100644 index 0000000..1f187f8 --- /dev/null +++ b/app/src/main/res/drawable/logo.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/font/montserrat_bold.ttf b/app/src/main/res/font/montserrat_bold.ttf new file mode 100644 index 0000000..4033587 Binary files /dev/null and b/app/src/main/res/font/montserrat_bold.ttf differ diff --git a/app/src/main/res/font/montserrat_medium.ttf b/app/src/main/res/font/montserrat_medium.ttf new file mode 100644 index 0000000..c9a39ea Binary files /dev/null and b/app/src/main/res/font/montserrat_medium.ttf differ diff --git a/app/src/main/res/font/montserrat_semibold.ttf b/app/src/main/res/font/montserrat_semibold.ttf new file mode 100644 index 0000000..161477a Binary files /dev/null and b/app/src/main/res/font/montserrat_semibold.ttf differ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fa8bda6..8d9daf4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,7 @@ Work RootActivity - Привет! Введи код для авторизации + Введите код для авторизации Код Войти \ No newline at end of file