diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a5ccda1..8b61370 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -35,6 +35,7 @@ android {
}
dependencies {
+ implementation("androidx.compose.material3:material3:1.4.0")
defaultComposeLibrary()
implementation("androidx.datastore:datastore-preferences:1.1.7")
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0")
@@ -48,4 +49,5 @@ dependencies {
implementation("io.ktor:ktor-client-content-negotiation:$ktor")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
+ implementation("io.coil-kt:coil-compose:2.6.0")
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a2c02bd..0eb09d5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,10 +19,9 @@
android:name=".ui.root.RootActivity"
android:exported="true"
android:windowSoftInputMode="adjustResize"
- android:label="@string/title_activity_root">
+ android:theme="@style/Theme.Work">
-
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 7890fab..d843e79 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
@@ -28,7 +28,8 @@ fun AppNavHost(
enterTransition = { EnterTransition.None },
exitTransition = { ExitTransition.None },
navController = navController,
- startDestination = AuthScreenDestination,
+// startDestination = AuthScreenDestination,
+ startDestination = MainScreenDestination,
) {
composable {
AuthScreen(navController = navController)
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 1d78716..065a0c2 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
@@ -34,6 +34,7 @@ import ru.myitschool.work.ui.nav.MainScreenDestination
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.layout.imePadding
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
@@ -54,6 +55,7 @@ fun AuthScreen(
Column(
modifier = Modifier
.fillMaxSize()
+ .imePadding()
.padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
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 a4a71c8..0e9895d 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
@@ -2,20 +2,41 @@ package ru.myitschool.work.ui.screen.main
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.imePadding
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.itemsIndexed
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Card
+import androidx.compose.material3.CardDefaults
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.text.style.TextAlign
+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.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.MainScreenDestination
@Composable
@@ -25,27 +46,9 @@ fun MainScreen(
) {
val state by viewModel.uiState.collectAsState()
- LaunchedEffect(Unit) {
- viewModel.actionFlow.collect {
- navController.navigate(MainScreenDestination)
- }
- }
-
/*
- Главный экран
- Данный экран содержит информацию о пользователе и его текущие бронирования. Если пользователь авторизован, данный экран должен быть отображен при запуске приложения.
-
- Элементы, которые должны присутствовать на экране:
-
- Текстовое поле (main_name), в котором написано имя пользователя.
- Изображение (main_photo), на котором отображено фото пользователя.
- Кнопка (main_logout_button) для выхода пользователя из аккаунта.
- Кнопка (main_refresh_button) для принудительного обновления данных.
- Кнопка (main_add_button) для бронирования.
- Список, содержащий однотипные элементы (main_book_pos_{индекс}), со следующим содержимым:
- Текстовое поле (main_item_date) с датой бронирования в формате dd.MM.yyyy.
- Текстовое поле (main_item_place) с местом, которое забронировано.
По умолчанию скрытое текстовое поле с ошибкой (main_error).
+
Требования к компонентам:
В случае любой ошибки необходимо скрыть все элементы, кроме текстового поля с ошибкой и кнопки обновления данных.
@@ -56,20 +59,184 @@ fun MainScreen(
Список бронирований должен быть отсортирован в порядке увеличения даты (например, 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()
- .padding(all = 24.dp),
+ .imePadding()
+ .padding(
+ start = 20.dp,
+ top = 20.dp,
+ end = 20.dp,
+ bottom = 0.dp),
horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.Center
+ 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)
+ )
+ }
- Text(
- text = "главная страница...",
- style = MaterialTheme.typography.headlineSmall,
- textAlign = TextAlign.Center
- )
+ 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 = {
+ },
+ ) {
+ 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 8.dp,
+ bottom = if (index == bookings.lastIndex) 0.dp else 8.dp
+ )
+ )
+ }
+ }
+ }
+ }
+}
+
+data class Booking(
+ val date: String,
+ val place: String
+)
+
+@Composable
+fun BookCard(
+ date: String,
+ place: String,
+ modifier: Modifier = Modifier
+) {
+ val formattedDate = remember(date) {
+ try {
+ val parts = date.split("-")
+ if (parts.size == 3) {
+ "${parts[2]}.${parts[1]}.${parts[0]}"
+ } else {
+ date
+ }
+ } catch (_: Exception) {
+ date
+ }
+ }
+
+ Card(
+ colors = CardDefaults.cardColors(
+ containerColor = MaterialTheme.colorScheme.surfaceVariant,
+ ),
+ modifier = modifier.fillMaxWidth()
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp)
+ ) {
+ Text(
+ text = "Бронь на $formattedDate",
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier
+ .testTag(TestIds.Main.ITEM_DATE)
+ )
+
+ Spacer(modifier = Modifier.size(8.dp))
+
+ Text(
+ text = place,
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ modifier = Modifier
+ .testTag(TestIds.Main.ITEM_PLACE)
+ )
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fa8bda6..1a695a3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,7 +1,9 @@
Work
- RootActivity
Привет! Введи код для авторизации
Код
Войти
+ Выйти
+ Обновить данные
+ Новая бронь
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000..07e5fc2
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,8 @@
+
+
+
+
\ No newline at end of file