diff --git a/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt b/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt index 3ef28f1..9ed569a 100644 --- a/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt +++ b/app/src/main/java/ru/myitschool/work/data/repo/AuthRepository.kt @@ -5,6 +5,10 @@ import ru.myitschool.work.data.source.NetworkDataSource object AuthRepository { private var codeCache: String? = null + fun clearCode() { + codeCache = null + } + suspend fun checkAndSave(text: String): Result { return NetworkDataSource.checkAuth(text).onSuccess { success -> 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 index 998160c..1cd5afd 100644 --- 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 @@ -16,28 +16,178 @@ import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold 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.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import coil3.compose.AsyncImage 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.data.repo.AuthRepository +import ru.myitschool.work.ui.nav.AuthScreenDestination + +@Composable +fun MainScreen( + viewModel: MainViewModel = viewModel(), + navController: NavController +) { + val state by viewModel.uiState.collectAsState() + + /* + По умолчанию скрытое текстовое поле с ошибкой (main_error). + + Требования к компонентам: + + В случае любой ошибки необходимо скрыть все элементы, кроме текстового поля с ошибкой и кнопки обновления данных. + Для получения данных необходимо использовать сетевой запрос /api//info. + При нажатии на кнопку для выхода, все сохранённые данные пользователя должны быть очищены, а приложение должно открыть экран авторизации. ГОТОВО + При нажатии кнопки бронирования необходимо открыть экран бронирования. + При нажатии на кнопку обновления данных — необходимо повторно вызывать сетевой запрос для получения актуальных данных. + Список бронирований должен быть отсортирован в порядке увеличения даты (например, 5 января -> 6 января -> 9 января). + */ + + val bookings = listOf( + Booking(date = "2025-12-01", place = "Аудитория 1"), + Booking(date = "2025-12-01", place = "Аудитория 2"), + Booking(date = "2025-12-02", place = "Аудитория 3"), + Booking(date = "2025-12-02", place = "Конференц-зал"), + Booking(date = "2025-12-03", place = "Аудитория с очень длинным названием. Lorem ipsum"), + Booking(date = "2025-12-03", place = "Лаборатория №101"), + Booking(date = "2025-12-04", place = "Переговорная комната"), + Booking(date = "2025-12-04", place = "Спортивный зал"), + ) + + LaunchedEffect(Unit) { + viewModel.actionFlow.collect { + navController.navigate(MainScreenDestination) + } + } + + Column( + modifier = Modifier + .fillMaxSize() + .imePadding() + .padding( + start = 20.dp, + top = 20.dp, + end = 20.dp, + bottom = 0.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Top + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Start, + verticalAlignment = Alignment.CenterVertically + ) { + AsyncImage( + model = "https://palyulin.ru/netcat_files/23/21/rabotnik.jpg", + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .size(60.dp) + .clip(CircleShape) + .testTag(TestIds.Main.PROFILE_IMAGE) + ) + Spacer(modifier = Modifier.size(16.dp)) + Text( + text = "Иванов Иван Иванович", + style = MaterialTheme.typography.titleLarge, + modifier = Modifier + .testTag(TestIds.Main.PROFILE_NAME) + ) + } + + Spacer(modifier = Modifier.size(8.dp)) + + Button( + modifier = Modifier + .fillMaxWidth() + .testTag(TestIds.Main.LOGOUT_BUTTON), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.error, + contentColor = MaterialTheme.colorScheme.onError + ), + onClick = { + AuthRepository.clearCode() + navController.navigate(AuthScreenDestination) + }, + ) { + Text(stringResource(R.string.logout)) + } + + Spacer(modifier = Modifier.size(8.dp)) + + Button( + modifier = Modifier + .fillMaxWidth() + .testTag(TestIds.Main.REFRESH_BUTTON), + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.primary, + contentColor = MaterialTheme.colorScheme.onPrimary + ), + onClick = { + }, + ) { + Text(stringResource(R.string.refresh)) + } + + Spacer(modifier = Modifier.size(8.dp)) + + Button( + modifier = Modifier + .fillMaxWidth() + .testTag(TestIds.Main.ADD_BUTTON), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF2E7D32), + contentColor = Color.White + ), + onClick = { + }, + ) { + Text(stringResource(R.string.book_new)) + } + + Scaffold( + modifier = Modifier.fillMaxSize() + ) { paddingValues -> + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + itemsIndexed( + items = bookings, + key = { index, item -> "main_book_pos_$index" } + ) { index, booking -> + BookCard( + date = booking.date, + place = booking.place, + modifier = Modifier.padding( + top = if (index == 0) 0.dp else 4.dp, + bottom = if (index == bookings.lastIndex) 0.dp else 4.dp + ) + ) + } + } + } + } +} data class Booking( val date: String, @@ -50,19 +200,6 @@ fun BookCard( place: String, modifier: Modifier = Modifier ) { - /* - По умолчанию скрытое текстовое поле с ошибкой (main_error). - - Требования к компонентам: - - В случае любой ошибки необходимо скрыть все элементы, кроме текстового поля с ошибкой и кнопки обновления данных. - Для получения данных необходимо использовать сетевой запрос /api//info. - При нажатии на кнопку для выхода, все сохранённые данные пользователя должны быть очищены, а приложение должно открыть экран авторизации. - При нажатии кнопки бронирования необходимо открыть экран бронирования. - При нажатии на кнопку обновления данных — необходимо повторно вызывать сетевой запрос для получения актуальных данных. - Список бронирований должен быть отсортирован в порядке увеличения даты (например, 5 января -> 6 января -> 9 января). - */ - val formattedDate = remember(date) { try { val parts = date.split("-") @@ -106,134 +243,4 @@ fun BookCard( ) } } -} - -@Composable -fun MainScreen( - viewModel: MainViewModel = viewModel(), - navController: NavController -) { - val state by viewModel.uiState.collectAsState() - - Column( - modifier = Modifier - .fillMaxSize() - .imePadding() - .padding(20.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Top - ) { - Button( - modifier = Modifier - .fillMaxWidth() - .testTag(TestIds.Main.REFRESH_BUTTON), - onClick = { /* обновить данные */ } - ) { - Text(stringResource(R.string.refresh)) - } - - Spacer(modifier = Modifier.size(8.dp)) - - when (state) { - is MainState.Error -> { - Text( - text = (state as MainState.Error).message, - color = MaterialTheme.colorScheme.error, - textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth() - ) - } - is MainState.Loading -> { - CircularProgressIndicator() - } - is MainState.Data -> { - MainContent(navController = navController) - } - } - } -} - -@Composable -fun MainContent(navController: NavController) { - val bookings = listOf( - Booking(date = "2025-12-01", place = "Аудитория 1"), - Booking(date = "2025-12-01", place = "Аудитория 2"), - Booking(date = "2025-12-02", place = "Аудитория 3"), - Booking(date = "2025-12-02", place = "Конференц-зал"), - Booking(date = "2025-12-03", place = "Аудитория с очень длинным названием. Lorem ipsum"), - Booking(date = "2025-12-03", place = "Лаборатория №101"), - Booking(date = "2025-12-04", place = "Переговорная комната"), - Booking(date = "2025-12-04", place = "Спортивный зал"), - ) - - Column( - modifier = Modifier.fillMaxSize() - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically - ) { - AsyncImage( - model = "https://palyulin.ru/netcat_files/23/21/rabotnik.jpg", - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier - .size(60.dp) - .clip(CircleShape) - ) - Spacer(modifier = Modifier.size(16.dp)) - Text( - text = "Иванов Иван Иванович", - style = MaterialTheme.typography.titleLarge - ) - } - - Spacer(modifier = Modifier.size(8.dp)) - - Button( - modifier = Modifier.fillMaxWidth(), - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.error - ), - onClick = { /* выход */ } - ) { - Text(stringResource(R.string.logout)) - } - - Spacer(modifier = Modifier.size(8.dp)) - - Button( - modifier = Modifier.fillMaxWidth(), - onClick = { navController.navigate(BookScreenDestination) } - ) { - Text(stringResource(R.string.book_new)) - } - - Spacer(modifier = Modifier.size(8.dp)) - - Scaffold( - modifier = Modifier.fillMaxSize() - ) { paddingValues -> - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(paddingValues) - ) { - itemsIndexed( - items = bookings, - key = { index, item -> "main_book_pos_$index" } - ) { index, booking -> - BookCard( - date = booking.date, - place = booking.place, - modifier = Modifier.padding( - top = if (index == 0) 0.dp else 4.dp, - bottom = if (index == bookings.lastIndex) 0.dp else 4.dp - ) - ) - } - } - } - } } \ No newline at end of file