diff --git a/app/src/main/java/ru/myitschool/work/ui/root/RootActivity.kt b/app/src/main/java/ru/myitschool/work/ui/root/RootActivity.kt index 54b156d..46b463f 100644 --- a/app/src/main/java/ru/myitschool/work/ui/root/RootActivity.kt +++ b/app/src/main/java/ru/myitschool/work/ui/root/RootActivity.kt @@ -8,7 +8,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.compose.viewModel import ru.myitschool.work.ui.screen.AppNavHost +import ru.myitschool.work.ui.screen.auth.AuthViewModel import ru.myitschool.work.ui.theme.WorkTheme class RootActivity : ComponentActivity() { @@ -18,7 +20,9 @@ class RootActivity : ComponentActivity() { setContent { WorkTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> + val viewModel: AuthViewModel = viewModel() AppNavHost( + viewModel, modifier = Modifier .fillMaxSize() .padding(innerPadding) 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..6d209f4 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 @@ -1,26 +1,63 @@ package ru.myitschool.work.ui.screen +import androidx.compose.animation.AnimatedContentScope import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition +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.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.layout.wrapContentHeight +import androidx.compose.foundation.text.input.TextFieldLineLimits +import androidx.compose.foundation.text.input.rememberTextFieldState +import androidx.compose.material3.Button +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text +import androidx.compose.material3.TextField 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.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController +import ru.myitschool.work.core.TestIds 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.screen.auth.AuthIntent import ru.myitschool.work.ui.screen.auth.AuthScreen +import ru.myitschool.work.ui.screen.auth.AuthViewModel +import ru.myitschool.work.ui.screen.book.BookScreen +import ru.myitschool.work.ui.screen.main.MainScreen + @Composable fun AppNavHost( + viewModel: AuthViewModel, modifier: Modifier = Modifier, - navController: NavHostController = rememberNavController() -) { + navController: NavHostController = rememberNavController(), + + ) { + NavHost( modifier = modifier, enterTransition = { EnterTransition.None }, @@ -32,18 +69,13 @@ fun AppNavHost( AuthScreen(navController = navController) } composable { - Box( - contentAlignment = Alignment.Center - ) { - Text(text = "Hello") - } + MainScreen(navController = navController) } composable { - Box( - contentAlignment = Alignment.Center - ) { - Text(text = "Hello") - } + BookScreen(navController = navController) } } -} \ No newline at end of file +} + + + 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..2ba59ae 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 @@ -76,7 +76,9 @@ private fun Content( var inputText by remember { mutableStateOf("") } Spacer(modifier = Modifier.size(16.dp)) TextField( - modifier = Modifier.testTag(TestIds.Auth.CODE_INPUT).fillMaxWidth(), + modifier = Modifier + .testTag(TestIds.Auth.CODE_INPUT) + .fillMaxWidth(), value = inputText, onValueChange = { inputText = it @@ -86,7 +88,9 @@ private fun Content( ) Spacer(modifier = Modifier.size(16.dp)) Button( - modifier = Modifier.testTag(TestIds.Auth.SIGN_BUTTON).fillMaxWidth(), + modifier = Modifier + .testTag(TestIds.Auth.SIGN_BUTTON) + .fillMaxWidth(), onClick = { viewModel.onIntent(AuthIntent.Send(inputText)) }, diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookIntent.kt b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookIntent.kt new file mode 100644 index 0000000..f5e1590 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookIntent.kt @@ -0,0 +1,7 @@ +package ru.myitschool.work.ui.screen.book + + +interface BookIntent { + data class Send(val text: String): BookIntent + data class TextInput(val text: String): BookIntent +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookScreen.kt b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookScreen.kt new file mode 100644 index 0000000..b6de44e --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookScreen.kt @@ -0,0 +1,132 @@ +package ru.myitschool.work.ui.screen.book + +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.selection.selectable +import androidx.compose.material3.Button +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +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.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import ru.myitschool.work.ui.nav.MainScreenDestination + +@Composable +fun BookScreen( + viewModel: BookViewModel = viewModel(), + navController: NavController +) { + + val state by viewModel.uiState.collectAsState() + + LaunchedEffect(Unit) { + viewModel.actionFlow.collect { + navController.navigate(MainScreenDestination) + } + } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(all = 24.dp), + ) { + when (val currentState = state) { + is BookState.Data -> Content(viewModel, currentState) + is BookState.Loading -> { + CircularProgressIndicator( + modifier = Modifier.size(64.dp) + ) + } + } + } +} + +@Composable +private fun Content( + viewModel: BookViewModel, + state: BookState.Data +) { + val error = false + var selected = false + var input by remember { mutableStateOf("") } + Column() { Row (horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth().padding(8.dp)) { + OutlinedButton(onClick = { }) { + Text("19.04", Modifier.testTag("main_item_place")) + Modifier.semantics { testTag = "main_add_button"}.selectable(selected = selected, onClick = {selected = true }, role = Role.Button) + } + OutlinedButton(onClick = { }) { + Text("20.04", Modifier.testTag("main_item_place")) + Modifier.semantics { testTag = "main_add_button"}.selectable(selected = selected, onClick = {selected = true }, role = Role.Button) + } + OutlinedButton(onClick = { }) { + Text("21.04", Modifier.testTag("main_item_place")) + Modifier.semantics { testTag = "main_add_button"}.selectable(selected = selected, onClick = {selected = true }, role = Role.Button) + } + OutlinedButton(onClick = { }) { + Text("22.04", Modifier.testTag("main_item_place")) + Modifier.semantics { testTag = "main_add_button" }.selectable(selected = selected, onClick = {selected = true }, role = Role.Button) + } + } + Spacer(modifier = Modifier.size(20.dp)) + Text("Рабочее место у окна") + Spacer(modifier = Modifier.size(20.dp)) + Text("Переговорная комната №1") + Spacer(modifier = Modifier.size(20.dp)) + Text("Коворкинг A", Modifier.selectable(selected = selected, onClick = {selected = true}, role = Role.Button)) + Spacer(modifier = Modifier.size(20.dp)) + Row (horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth().padding(8.dp)){ + Button(onClick = { viewModel.onIntent(BookIntent.Send(input)) }) { + Text(text = "Назад") + Modifier + .padding(20.dp) + .fillMaxWidth() + .semantics { testTag = "book_back_button" } + } + Button(onClick = { }) { + Text(text = "Бронировать") + Modifier + .padding(20.dp) + .fillMaxWidth() + .semantics { testTag = "book_book_button" } + } + + } + if (error){ + Spacer(modifier = Modifier.size(20.dp)) + Text("Ошибка", Modifier.testTag("book_error")) + Button(onClick = { }) { + Text(text = "Обновить") + Modifier + .padding(20.dp) + .fillMaxWidth() + .semantics { testTag = "book_refresh_button" } + } + Text("Всё забронировано", Modifier.testTag("book_empty")) + + } + + + } + +} diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookState.kt b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookState.kt new file mode 100644 index 0000000..eb69ccd --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookState.kt @@ -0,0 +1,8 @@ +package ru.myitschool.work.ui.screen.book + +import ru.myitschool.work.ui.screen.auth.AuthState + +interface BookState { + object Loading: BookState + object Data: BookState +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookViewModel.kt new file mode 100644 index 0000000..ff9a04f --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookViewModel.kt @@ -0,0 +1,43 @@ +package ru.myitschool.work.ui.screen.book + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import ru.myitschool.work.data.repo.AuthRepository +import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase + +class BookViewModel: ViewModel() { + private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) } + private val _uiState = MutableStateFlow(BookState.Data) + val uiState: StateFlow = _uiState.asStateFlow() + + private val _actionFlow: MutableSharedFlow = MutableSharedFlow() + val actionFlow: SharedFlow = _actionFlow + + fun onIntent(intent: BookIntent) { + when (intent) { + is BookIntent.Send -> { + viewModelScope.launch(Dispatchers.Default) { + _uiState.update { BookState.Loading } + checkAndSaveAuthCodeUseCase.invoke("9999").fold( + onSuccess = { + _actionFlow.emit(Unit) + }, + onFailure = { error -> + error.printStackTrace() + _actionFlow.emit(Unit) + } + ) + } + } + is BookIntent.TextInput -> Unit + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/book/MainState.kt b/app/src/main/java/ru/myitschool/work/ui/screen/book/MainState.kt new file mode 100644 index 0000000..e12c10b --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/book/MainState.kt @@ -0,0 +1,4 @@ +package ru.myitschool.work.ui.screen.book + +interface MainState { +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/main/MainIntent.kt b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainIntent.kt new file mode 100644 index 0000000..40d5c01 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainIntent.kt @@ -0,0 +1,7 @@ +package ru.myitschool.work.ui.screen.main + + +interface MainIntent { + data class Send(val text: String): MainIntent + data class TextInput(val text: String): MainIntent +} \ 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..f28f1f4 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainScreen.kt @@ -0,0 +1,154 @@ +package ru.myitschool.work.ui.screen.main + + +import androidx.compose.foundation.Image +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.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.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +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.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Color.Companion.Gray +import androidx.compose.ui.graphics.painter.ColorPainter +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTag +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +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.nav.BookScreenDestination +import ru.myitschool.work.ui.nav.MainScreenDestination +import ru.myitschool.work.ui.screen.auth.AuthIntent +import ru.myitschool.work.ui.screen.auth.AuthState +import ru.myitschool.work.ui.screen.auth.AuthViewModel +import ru.myitschool.work.ui.screen.book.BookIntent + +@Composable +fun MainScreen( + viewModel: MainViewModel = viewModel(), + navController: NavController +) { + + val state by viewModel.uiState.collectAsState() + + LaunchedEffect(Unit) { + viewModel.actionFlow.collect { + navController.navigate(BookScreenDestination) + } + } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(all = 24.dp) + ) { + when (val currentState = state) { + is MainState.Data -> Content(viewModel, currentState) + is MainState.Loading -> { + CircularProgressIndicator( + modifier = Modifier.size(64.dp) + ) + } + } + } +} + +@Composable +private fun Content( + viewModel: MainViewModel, + state: MainState.Data +) { + var expanded by remember{ mutableStateOf(false)} + var inputUsername by remember { mutableStateOf("Введите имя") } + var input by remember { mutableStateOf("") } + val date:String = "dd.MM.yyyy" + val username:String = "test" + var place:String = "test" + val error = false + Spacer(modifier = Modifier.size(16.dp)) + Column() { + Spacer(modifier = Modifier.size(16.dp)) + Text( + modifier = Modifier.testTag("main_name").fillMaxWidth(), + text = username + ) + Spacer(modifier = Modifier.size(10.dp)) + Image(painter = ColorPainter(Color.DarkGray), contentDescription = "main_photo", modifier = Modifier.size(50.dp)) + + Spacer(modifier = Modifier.size(10.dp)) + Row (horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth().padding(8.dp)) { + + + Button(onClick = { }) { + Text(text = "Выход") + Modifier + .padding(20.dp) + .fillMaxWidth() + .semantics { testTag = "main_logout_button" } + } + Spacer(modifier = Modifier.size(10.dp)) + Button(onClick = { }) { + Text(text = "Обновить") + Modifier + .padding(20.dp) + .fillMaxWidth() + .semantics { testTag = "main_refresh_button" } + } + Spacer(modifier = Modifier.size(10.dp)) + Button(onClick = { + viewModel.onIntent(MainIntent.Send(input)) + }) { + Text(text = "Бронировать") + Modifier + .padding(20.dp) + .fillMaxWidth() + .semantics { testTag = "main_add_button" } + } + Spacer(modifier = Modifier.size(10.dp)) + } + Spacer(modifier = Modifier.size(10.dp)) + + + Text( + modifier = Modifier.testTag("main_item_date").fillMaxWidth(), + text = date + ) + Spacer(modifier = Modifier.size(10.dp)) + Text( + modifier = Modifier.testTag("main_item_place").fillMaxWidth(), + text = place + ) + Text(text = "ERROR", + modifier = Modifier.testTag("main_error").fillMaxWidth().alpha(0f)) + + + } + var inputText by remember { mutableStateOf("") } + Spacer(modifier = Modifier.size(16.dp)) + + +} \ 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..2692596 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainState.kt @@ -0,0 +1,9 @@ +package ru.myitschool.work.ui.screen.main + +import ru.myitschool.work.ui.screen.auth.AuthState + +interface MainState { + object Loading: MainState + object Data: 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..a69ed85 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/ui/screen/main/MainViewModel.kt @@ -0,0 +1,43 @@ +package ru.myitschool.work.ui.screen.main + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import ru.myitschool.work.data.repo.AuthRepository +import ru.myitschool.work.domain.auth.CheckAndSaveAuthCodeUseCase + +class MainViewModel: ViewModel() { + private val checkAndSaveAuthCodeUseCase by lazy { CheckAndSaveAuthCodeUseCase(AuthRepository) } + private val _uiState = MutableStateFlow(MainState.Data) + val uiState: StateFlow = _uiState.asStateFlow() + private val _actionFlow: MutableSharedFlow = MutableSharedFlow() + val actionFlow: SharedFlow = _actionFlow + + fun onIntent(intent: MainIntent) { + when (intent) { + is MainIntent.Send -> { + viewModelScope.launch(Dispatchers.Default) { + _uiState.update { MainState.Loading } + checkAndSaveAuthCodeUseCase.invoke("9999").fold( + onSuccess = { + _actionFlow.emit(Unit) + }, + onFailure = { error -> + error.printStackTrace() + _actionFlow.emit(Unit) + } + ) + } + } + + is MainIntent.TextInput -> Unit + } + } +} \ No newline at end of file