main #15
@@ -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<Boolean> {
|
||||
return NetworkDataSource.checkAuth(text).onSuccess { success ->
|
||||
|
||||
@@ -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/<CODE>/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/<CODE>/info.
|
||||
При нажатии на кнопку для выхода, все сохранённые данные пользователя должны быть очищены, а приложение должно открыть экран авторизации.
|
||||
При нажатии кнопки бронирования необходимо открыть экран бронирования.
|
||||
При нажатии на кнопку обновления данных — необходимо повторно вызывать сетевой запрос для получения актуальных данных.
|
||||
Список бронирований должен быть отсортирован в порядке увеличения даты (например, 5 января -> 6 января -> 9 января).
|
||||
*/
|
||||
|
||||
val formattedDate = remember(date) {
|
||||
try {
|
||||
val parts = date.split("-")
|
||||
@@ -107,133 +244,3 @@ 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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user