diff --git a/README.md b/README.md index 4c7212b..b01b659 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# НТО 2025. II отборочный этап. Командные задания — Android + # НТО 2025. II отборочный этап. Командные задания — Android ## 📖 Предыстория diff --git a/app/src/main/java/ru/myitschool/work/data/repo/AuthDataStoreExt.kt b/app/src/main/java/ru/myitschool/work/data/repo/AuthDataStoreExt.kt index 904c1a1..c35982a 100644 --- a/app/src/main/java/ru/myitschool/work/data/repo/AuthDataStoreExt.kt +++ b/app/src/main/java/ru/myitschool/work/data/repo/AuthDataStoreExt.kt @@ -1,4 +1,4 @@ -package ru.myitschool.work.data.repo + package ru.myitschool.work.data.repo import android.content.Context import androidx.datastore.preferences.preferencesDataStore 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..d373d0c 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 @@ -15,6 +15,7 @@ class RootActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() + actionBar?.hide() setContent { WorkTheme { Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> 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 9746f58..678e30b 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 @@ -2,6 +2,7 @@ package ru.myitschool.work.ui.screen.auth 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 @@ -9,7 +10,9 @@ 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.Icon import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.material3.TextField import androidx.compose.runtime.Composable @@ -20,11 +23,14 @@ 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.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController +import io.ktor.websocket.Frame import ru.myitschool.work.R import ru.myitschool.work.core.TestIds import ru.myitschool.work.ui.nav.MainScreenDestination @@ -50,8 +56,20 @@ fun AuthScreen( .fillMaxSize() .padding(all = 24.dp), horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + verticalArrangement = Arrangement.Top ) { + Spacer(modifier = Modifier.size(48.dp)) + + Text( + text = stringResource(R.string.auth_name), + style = MaterialTheme.typography.displaySmall.copy( + fontWeight = FontWeight.Bold + ), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.size(48.dp)) + Text( text = stringResource(R.string.auth_title), style = MaterialTheme.typography.headlineSmall, @@ -60,7 +78,7 @@ fun AuthScreen( when (val currentState = state) { is AuthState.Data -> Content(viewModel, currentState) is AuthState.Loading -> { - Spacer(modifier = Modifier.size(16.dp)) + Spacer(modifier = Modifier.size(24.dp)) CircularProgressIndicator( modifier = Modifier.size(64.dp) ) @@ -76,28 +94,31 @@ private fun Content( ) { Spacer(modifier = Modifier.size(16.dp)) + OutlinedTextField( + modifier = Modifier + .testTag(TestIds.Auth.CODE_INPUT) + .fillMaxWidth(), + value = state.code, + onValueChange = { viewModel.onIntent(AuthIntent.TextInput(it)) }, + label = { Text(stringResource(R.string.auth_label)) }, + placeholder = { Text("Введите код сотрудника") } + ) + + Spacer(modifier = Modifier.size(8.dp)) + if (state.isErrorVisible) { Text( modifier = Modifier .fillMaxWidth() .testTag(TestIds.Auth.ERROR), text = "Неверный код или ошибка сервера", - color = Color.Red, + color = MaterialTheme.colorScheme.error, textAlign = TextAlign.Center, style = MaterialTheme.typography.bodyMedium ) Spacer(modifier = Modifier.size(8.dp)) } - TextField( - modifier = Modifier - .testTag(TestIds.Auth.CODE_INPUT) - .fillMaxWidth(), - value = state.code, - onValueChange = { viewModel.onIntent(AuthIntent.TextInput(it)) }, - label = { Text(stringResource(R.string.auth_label)) } - ) - Spacer(modifier = Modifier.size(16.dp)) Button( @@ -109,6 +130,16 @@ private fun Content( }, enabled = state.isButtonEnabled ) { - Text(stringResource(R.string.auth_sign_in)) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + Text(stringResource(R.string.auth_sign_in)) + Spacer(modifier = Modifier.size(8.dp)) + Icon( + painter = painterResource(R.drawable.ic_arrow_right), + contentDescription = "Войти" + ) + } + } } 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 index c109bc8..95ac428 100644 --- 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 @@ -16,6 +16,7 @@ import androidx.compose.foundation.selection.selectable import androidx.compose.material3.Button import androidx.compose.material3.Card import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButton import androidx.compose.material3.Surface @@ -27,9 +28,12 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource 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 @Composable @@ -112,14 +116,34 @@ private fun BookErrorContent( modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON), onClick = onRefresh ) { - Text("Обновить") + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = stringResource(R.string.main_screen_refresh)) + Spacer(modifier = Modifier.size(8.dp)) + Icon( + painter = painterResource(R.drawable.ic_refresh), + contentDescription = "Обновить" + ) + } } Spacer(modifier = Modifier.size(8.dp)) Button( modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON), onClick = onBack ) { - Text("Назад") + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.ic_back), + contentDescription = "Назад" + ) + Spacer(modifier = Modifier.size(8.dp)) + Text(text = stringResource(R.string.book_screen_back)) + } } } } @@ -145,14 +169,34 @@ private fun BookEmptyContent( modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON), onClick = onRefresh ) { - Text("Обновить") + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = stringResource(R.string.main_screen_refresh)) + Spacer(modifier = Modifier.size(8.dp)) + Icon( + painter = painterResource(R.drawable.ic_refresh), + contentDescription = "Обновить" + ) + } } Spacer(modifier = Modifier.size(8.dp)) Button( modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON), onClick = onBack ) { - Text("Назад") + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.ic_back), + contentDescription = "Назад" + ) + Spacer(modifier = Modifier.size(8.dp)) + Text(text = stringResource(R.string.book_screen_back)) + } } } } @@ -179,19 +223,39 @@ private fun BookDataContent( modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON), onClick = onBack ) { - Text("Назад") + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Icon( + painter = painterResource(R.drawable.ic_back), + contentDescription = "Назад" + ) + Spacer(modifier = Modifier.size(8.dp)) + Text(text = stringResource(R.string.book_screen_back)) + } } Button( modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON), onClick = onRefresh ) { - Text("Обновить") + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = stringResource(R.string.main_screen_refresh)) + Spacer(modifier = Modifier.size(8.dp)) + Icon( + painter = painterResource(R.drawable.ic_refresh), + contentDescription = "Обновить" + ) + } } } Spacer(modifier = Modifier.size(16.dp)) - // Вкладки с датами + LazyRow( horizontalArrangement = Arrangement.spacedBy(8.dp) ) { @@ -214,7 +278,7 @@ private fun BookDataContent( Spacer(modifier = Modifier.size(16.dp)) - // Список мест + LazyColumn( modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(8.dp) @@ -262,7 +326,17 @@ private fun BookDataContent( onClick = onBook, enabled = state.places.any { it.isSelected } ) { - Text("Забронировать") + Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = stringResource(R.string.book_screen_book)) + Spacer(modifier = Modifier.size(8.dp)) + Icon( + painter = painterResource(R.drawable.ic_arrow_right), + contentDescription = "Забронировать" + ) + } } } } 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 284739d..e1d294f 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,10 +16,12 @@ import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Person import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon import androidx.compose.material3.SegmentedButtonDefaults.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -28,12 +30,19 @@ 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.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import coil3.compose.SubcomposeAsyncImage +import org.intellij.lang.annotations.JdkConstants +import ru.myitschool.work.R import ru.myitschool.work.core.TestIds import ru.myitschool.work.ui.nav.AuthScreenDestination import ru.myitschool.work.ui.nav.BookScreenDestination @@ -130,6 +139,8 @@ private fun MainDataContent( .padding(16.dp) ) { + Spacer(modifier = Modifier.size(16.dp)) + Row( verticalAlignment = Alignment.CenterVertically ) { @@ -147,38 +158,47 @@ private fun MainDataContent( } Spacer(modifier = Modifier.size(16.dp)) Text( - modifier = Modifier.testTag(TestIds.Main.PROFILE_NAME), + modifier = Modifier + .testTag(TestIds.Main.PROFILE_NAME) + .weight(1f), text = state.name, style = MaterialTheme.typography.titleMedium ) + + Button( + modifier = Modifier + .testTag(TestIds.Main.LOGOUT_BUTTON), + onClick = onLogout, + colors = ButtonDefaults.buttonColors( + contentColor = MaterialTheme.colorScheme.errorContainer, + containerColor = MaterialTheme.colorScheme.onErrorContainer + + ) + + ) { + Row(verticalAlignment = Alignment.CenterVertically) { + Text("Выход") + Spacer(modifier = Modifier.size(8.dp)) + Icon( + painter = painterResource(R.drawable.ic_logout), + contentDescription = "Выйти" + ) + } + + } + } - Spacer(modifier = Modifier.size(16.dp)) + Spacer(modifier = Modifier.size(24.dp)) - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Button( - modifier = Modifier.testTag(TestIds.Main.LOGOUT_BUTTON), - onClick = onLogout - ) { - Text("Выход") - } - Button( - modifier = Modifier.testTag(TestIds.Main.REFRESH_BUTTON), - onClick = onRefresh - ) { - Text("Обновить") - } - Button( - modifier = Modifier.testTag(TestIds.Main.ADD_BUTTON), - onClick = onAddBooking - ) { - Text("Бронь") - } - } + Text( + text = stringResource(R.string.main_screen_title), + style = MaterialTheme.typography.titleLarge.copy( + fontWeight = FontWeight.Bold + ) + ) - Spacer(modifier = Modifier.size(16.dp)) + Spacer(modifier = Modifier.size(20.dp)) LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp) @@ -192,20 +212,85 @@ private fun MainDataContent( Column( modifier = Modifier.padding(12.dp) ) { - Text( - modifier = Modifier.testTag(TestIds.Main.ITEM_DATE), + Row(modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically) { + Text( + text = stringResource(R.string.main_screen_date), + style = MaterialTheme.typography.bodyLarge.copy( + fontWeight = FontWeight.Bold + ) + ) + + Text( + modifier = Modifier.testTag(TestIds.Main.ITEM_DATE), text = item.dateLabel, - style = MaterialTheme.typography.bodyMedium - ) - Text( - modifier = Modifier.testTag(TestIds.Main.ITEM_PLACE), - text = item.roomName, - style = MaterialTheme.typography.bodySmall - ) + style = MaterialTheme.typography.bodyLarge.copy( + fontWeight = FontWeight.Bold + ) + ) + } + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = stringResource(R.string.main_screen_place), + style = MaterialTheme.typography.bodyMedium + ) + + Text( + modifier = Modifier.testTag(TestIds.Main.ITEM_PLACE), + text = item.roomName, + style = MaterialTheme.typography.bodyMedium + ) + } + } } } } + + Spacer(modifier = Modifier.size(16.dp)) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Button( + modifier = Modifier.testTag(TestIds.Main.REFRESH_BUTTON), + onClick = onRefresh + ) { Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = stringResource(R.string.main_screen_refresh)) + Spacer(modifier = Modifier.size(8.dp)) + Icon( + painter = painterResource(R.drawable.ic_refresh), + contentDescription = "Обновить" + ) + } + + } + Button( + modifier = Modifier.testTag(TestIds.Main.ADD_BUTTON), + onClick = onAddBooking + ) { Row( + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = stringResource(R.string.main_screen_new_book)) + Spacer(modifier = Modifier.size(8.dp)) + Icon( + painter = painterResource(R.drawable.ic_add), + contentDescription = "Новая бронь" + ) + } + + } + } } } @Composable diff --git a/app/src/main/java/ru/myitschool/work/ui/theme/Theme.kt b/app/src/main/java/ru/myitschool/work/ui/theme/Theme.kt index d9cc58f..651f2df 100644 --- a/app/src/main/java/ru/myitschool/work/ui/theme/Theme.kt +++ b/app/src/main/java/ru/myitschool/work/ui/theme/Theme.kt @@ -32,26 +32,26 @@ private val LightColorScheme = lightColorScheme( */ ) -@Composable -fun WorkTheme( - darkTheme: Boolean = isSystemInDarkTheme(), - // Dynamic color is available on Android 12+ - dynamicColor: Boolean = true, - content: @Composable () -> Unit -) { - val colorScheme = when { - dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { - val context = LocalContext.current - if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + @Composable + fun WorkTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit + ) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme } - darkTheme -> DarkColorScheme - else -> LightColorScheme - } - - MaterialTheme( - colorScheme = colorScheme, - typography = Typography, - content = content - ) -} \ No newline at end of file + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) + } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add.xml b/app/src/main/res/drawable/ic_add.xml new file mode 100644 index 0000000..bd91c80 --- /dev/null +++ b/app/src/main/res/drawable/ic_add.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/ic_arrow_right.xml b/app/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 0000000..ac83a31 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_back.xml b/app/src/main/res/drawable/ic_back.xml new file mode 100644 index 0000000..0aa3f30 --- /dev/null +++ b/app/src/main/res/drawable/ic_back.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml new file mode 100644 index 0000000..b7f6207 --- /dev/null +++ b/app/src/main/res/drawable/ic_logout.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 0000000..bf3ba5a --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fa8bda6..16f4a4b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,7 +1,15 @@ Work RootActivity - Привет! Введи код для авторизации + Система бронирования + Вход в систему Код Войти + Дата: + Место: + Мои бронирования + Обновить + Новая бронь + Забронировать + Назад \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3e927b1..551063a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,21 +1,16 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit +## For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html +# # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# Default value: -Xmx1024m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# # When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects # org.gradle.parallel=true -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn +#Thu Dec 04 14:16:52 GMT+07:00 2025 +android.nonTransitiveRClass=true android.useAndroidX=true -# Enables namespacing of each library's R class so that its R class includes only the -# resources declared in the library itself and none from the library's dependencies, -# thereby reducing the size of the R class for that library -android.nonTransitiveRClass=true \ No newline at end of file +org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" -Dfile.encoding\=UTF-8