Compare commits

...

4 Commits

Author SHA1 Message Date
b40749faa1 UI/UX solution:
fix POST booking request
2025-12-05 22:19:51 +07:00
dbe735e541 UI/UX solution:
Added clickable date tabs for booking
2025-12-05 21:28:06 +07:00
40a8428a19 Merge remote-tracking branch 'origin/main' 2025-12-05 05:01:00 +07:00
9e603f87e6 UI/UX solution 2025-12-05 05:00:33 +07:00
17 changed files with 393 additions and 131 deletions

View File

@@ -1,4 +1,4 @@
# НТО 2025. II отборочный этап. Командные задания — Android # НТО 2025. II отборочный этап. Командные задания — Android
## 📖 Предыстория ## 📖 Предыстория

View File

@@ -1,4 +1,4 @@
package ru.myitschool.work.data.repo package ru.myitschool.work.data.repo
import android.content.Context import android.content.Context
import androidx.datastore.preferences.preferencesDataStore import androidx.datastore.preferences.preferencesDataStore

View File

@@ -8,7 +8,10 @@ import io.ktor.client.request.get
import io.ktor.client.request.post import io.ktor.client.request.post
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import io.ktor.client.utils.EmptyContent.contentType
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -145,6 +148,7 @@ object NetworkDataSource {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
runCatching { runCatching {
val response = client.post(getUrl(code, Constants.BOOK_URL)) { val response = client.post(getUrl(code, Constants.BOOK_URL)) {
contentType(ContentType.Application.Json)
setBody( setBody(
BookRequestDto( BookRequestDto(
date = date, date = date,

View File

@@ -2,11 +2,12 @@ package ru.myitschool.work.domain.booking
import ru.myitschool.work.data.repo.UserRepository import ru.myitschool.work.data.repo.UserRepository
class BookPlaceUseCase( class BookPlaceUseCase(
private val repository: UserRepository, private val repository: UserRepository,
) { ) {
suspend operator fun invoke(date: String, placeId: Int): Result<Unit> { suspend operator fun invoke(roomName: String, placeId: Int): Result<Unit> {
return repository.book(date, placeId) // Нужно получить дату из какого-то источника
// Либо изменить логику в ViewModel
return repository.book(roomName, placeId) // ← но repository.book ожидает date, а не roomName
} }
} }

View File

@@ -15,6 +15,7 @@ class RootActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
actionBar?.hide()
setContent { setContent {
WorkTheme { WorkTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->

View File

@@ -2,6 +2,7 @@ package ru.myitschool.work.ui.screen.auth
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth 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.foundation.layout.size
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@@ -20,11 +23,14 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import io.ktor.websocket.Frame
import ru.myitschool.work.R import ru.myitschool.work.R
import ru.myitschool.work.core.TestIds import ru.myitschool.work.core.TestIds
import ru.myitschool.work.ui.nav.MainScreenDestination import ru.myitschool.work.ui.nav.MainScreenDestination
@@ -50,8 +56,20 @@ fun AuthScreen(
.fillMaxSize() .fillMaxSize()
.padding(all = 24.dp), .padding(all = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally, 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(
text = stringResource(R.string.auth_title), text = stringResource(R.string.auth_title),
style = MaterialTheme.typography.headlineSmall, style = MaterialTheme.typography.headlineSmall,
@@ -60,7 +78,7 @@ fun AuthScreen(
when (val currentState = state) { when (val currentState = state) {
is AuthState.Data -> Content(viewModel, currentState) is AuthState.Data -> Content(viewModel, currentState)
is AuthState.Loading -> { is AuthState.Loading -> {
Spacer(modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.size(24.dp))
CircularProgressIndicator( CircularProgressIndicator(
modifier = Modifier.size(64.dp) modifier = Modifier.size(64.dp)
) )
@@ -76,28 +94,31 @@ private fun Content(
) { ) {
Spacer(modifier = Modifier.size(16.dp)) 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) { if (state.isErrorVisible) {
Text( Text(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.testTag(TestIds.Auth.ERROR), .testTag(TestIds.Auth.ERROR),
text = "Неверный код или ошибка сервера", text = "Неверный код или ошибка сервера",
color = Color.Red, color = MaterialTheme.colorScheme.error,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyMedium style = MaterialTheme.typography.bodyMedium
) )
Spacer(modifier = Modifier.size(8.dp)) 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)) Spacer(modifier = Modifier.size(16.dp))
Button( Button(
@@ -108,7 +129,17 @@ private fun Content(
viewModel.onIntent(AuthIntent.Send(state.code)) viewModel.onIntent(AuthIntent.Send(state.code))
}, },
enabled = state.isButtonEnabled enabled = state.isButtonEnabled
) {
Row(
verticalAlignment = Alignment.CenterVertically
) { ) {
Text(stringResource(R.string.auth_sign_in)) Text(stringResource(R.string.auth_sign_in))
Spacer(modifier = Modifier.size(8.dp))
Icon(
painter = painterResource(R.drawable.ic_arrow_right),
contentDescription = "Войти"
)
}
} }
} }

View File

@@ -1,5 +1,6 @@
package ru.myitschool.work.ui.screen.book package ru.myitschool.work.ui.screen.book
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -13,9 +14,11 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton import androidx.compose.material3.RadioButton
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@@ -26,10 +29,14 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.testTag 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.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import ru.myitschool.work.R
import ru.myitschool.work.core.TestIds import ru.myitschool.work.core.TestIds
@Composable @Composable
@@ -112,14 +119,34 @@ private fun BookErrorContent(
modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON), modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON),
onClick = onRefresh 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)) Spacer(modifier = Modifier.size(8.dp))
Button( Button(
modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON), modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON),
onClick = onBack 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 +172,34 @@ private fun BookEmptyContent(
modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON), modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON),
onClick = onRefresh 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)) Spacer(modifier = Modifier.size(8.dp))
Button( Button(
modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON), modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON),
onClick = onBack 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,34 +226,31 @@ private fun BookDataContent(
modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON), modifier = Modifier.testTag(TestIds.Book.BACK_BUTTON),
onClick = onBack 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( Button(
modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON), modifier = Modifier.testTag(TestIds.Book.REFRESH_BUTTON),
onClick = onRefresh onClick = onRefresh
) { ) {
Text("Обновить") Row(
} horizontalArrangement = Arrangement.Center,
} verticalAlignment = Alignment.CenterVertically
Spacer(modifier = Modifier.size(16.dp))
// Вкладки с датами
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
itemsIndexed(state.dates) { index, dateLabel -> Text(text = stringResource(R.string.main_screen_refresh))
Surface( Spacer(modifier = Modifier.size(8.dp))
modifier = Modifier Icon(
.testTag(TestIds.Book.getIdDateItemByPosition(index)), painter = painterResource(R.drawable.ic_refresh),
tonalElevation = if (index == state.selectedDateIndex) 4.dp else 0.dp contentDescription = "Обновить"
) {
Text(
modifier = Modifier
.padding(horizontal = 12.dp, vertical = 8.dp)
.testTag(TestIds.Book.ITEM_DATE),
text = dateLabel,
style = MaterialTheme.typography.bodyMedium
) )
} }
} }
@@ -214,7 +258,42 @@ private fun BookDataContent(
Spacer(modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.size(16.dp))
// Список мест
LazyRow(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
itemsIndexed(state.dates) { index, dateLabel ->
Surface(
modifier = Modifier
.testTag(TestIds.Book.getIdDateItemByPosition(index))
.clickable { onSelectDate(index) }
.clip(RoundedCornerShape(8.dp)),
tonalElevation = if (index == state.selectedDateIndex) 4.dp else 0.dp,
color = if (index == state.selectedDateIndex) {
MaterialTheme.colorScheme.primaryContainer
} else {
MaterialTheme.colorScheme.surface
}
) {
Text(
modifier = Modifier
.padding(horizontal = 12.dp, vertical = 8.dp)
.testTag(TestIds.Book.ITEM_DATE),
text = dateLabel,
style = MaterialTheme.typography.bodyMedium,
color = if (index == state.selectedDateIndex) {
MaterialTheme.colorScheme.onPrimaryContainer
} else {
MaterialTheme.colorScheme.onSurface
}
)
}
}
}
Spacer(modifier = Modifier.size(16.dp))
LazyColumn( LazyColumn(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
@@ -262,7 +341,17 @@ private fun BookDataContent(
onClick = onBook, onClick = onBook,
enabled = state.places.any { it.isSelected } 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 = "Забронировать"
)
}
} }
} }
} }

View File

@@ -37,7 +37,7 @@ class BookViewModel : ViewModel() {
private var allGroups: List<DateGroup> = emptyList() private var allGroups: List<DateGroup> = emptyList()
private var selectedDateIndex: Int = 0 private var selectedDateIndex: Int = 0
private var selectedPlaceId: Int? = null private var selectedSlotId: Int? = null
fun onIntent(intent: BookIntent) { fun onIntent(intent: BookIntent) {
when (intent) { when (intent) {
@@ -51,7 +51,7 @@ class BookViewModel : ViewModel() {
} }
is BookIntent.SelectPlace -> { is BookIntent.SelectPlace -> {
selectedPlaceId = intent.id selectedSlotId = intent.id
val current = _uiState.value val current = _uiState.value
if (current is BookState.Data) { if (current is BookState.Data) {
val updatedPlaces = current.places.map { item -> val updatedPlaces = current.places.map { item ->
@@ -77,7 +77,7 @@ class BookViewModel : ViewModel() {
if (bookings.isEmpty()) { if (bookings.isEmpty()) {
_uiState.value = BookState.Empty _uiState.value = BookState.Empty
allGroups = emptyList() allGroups = emptyList()
selectedPlaceId = null selectedSlotId = null
selectedDateIndex = 0 selectedDateIndex = 0
return@fold return@fold
} }
@@ -86,18 +86,18 @@ class BookViewModel : ViewModel() {
if (allGroups.isEmpty()) { if (allGroups.isEmpty()) {
_uiState.value = BookState.Empty _uiState.value = BookState.Empty
selectedPlaceId = null selectedSlotId = null
selectedDateIndex = 0 selectedDateIndex = 0
return@fold return@fold
} }
selectedDateIndex = 0 selectedDateIndex = 0
selectedPlaceId = null selectedSlotId = null
val firstGroup = allGroups[0] val firstGroup = allGroups[0]
val places = firstGroup.slots.mapIndexed { index, slot -> val places = firstGroup.slots.map { slot ->
BookPlaceItem( BookPlaceItem(
id = index, id = slot.id,
roomName = slot.roomName, roomName = slot.roomName,
time = slot.time, time = slot.time,
isSelected = false, isSelected = false,
@@ -124,10 +124,9 @@ class BookViewModel : ViewModel() {
if (index !in groups.indices) return if (index !in groups.indices) return
selectedDateIndex = index selectedDateIndex = index
selectedPlaceId = null selectedSlotId = null
val group = groups[index] val group = groups[index]
val places = group.slots.mapIndexed { idx, slot -> val places = group.slots.map { slot ->
BookPlaceItem( BookPlaceItem(
id = slot.id, id = slot.id,
roomName = slot.roomName, roomName = slot.roomName,
@@ -135,7 +134,6 @@ class BookViewModel : ViewModel() {
isSelected = false, isSelected = false,
) )
} }
val datesLabels = groups.map { it.label } val datesLabels = groups.map { it.label }
val current = _uiState.value val current = _uiState.value
@@ -158,11 +156,19 @@ class BookViewModel : ViewModel() {
val current = _uiState.value val current = _uiState.value
if (current !is BookState.Data) return if (current !is BookState.Data) return
val placeId = selectedPlaceId ?: return val slotId = selectedSlotId ?: return
val place = current.places.firstOrNull { it.id == placeId } ?: return
allGroups
.flatMap { it.slots }
.firstOrNull { it.id == slotId }
?: return
val selectedDate = allGroups[selectedDateIndex].date.toString()
viewModelScope.launch(Dispatchers.Default) { viewModelScope.launch(Dispatchers.Default) {
bookPlaceUseCase(place.roomName, place.id)
bookPlaceUseCase(selectedDate, slotId)
.fold( .fold(
onSuccess = { onSuccess = {
_actionFlow.emit(Action.CloseWithSuccess) _actionFlow.emit(Action.CloseWithSuccess)

View File

@@ -16,10 +16,12 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Person import androidx.compose.material.icons.filled.Person
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.SegmentedButtonDefaults.Icon import androidx.compose.material3.SegmentedButtonDefaults.Icon
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -28,12 +30,19 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.testTag 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.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import coil3.compose.SubcomposeAsyncImage 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.core.TestIds
import ru.myitschool.work.ui.nav.AuthScreenDestination import ru.myitschool.work.ui.nav.AuthScreenDestination
import ru.myitschool.work.ui.nav.BookScreenDestination import ru.myitschool.work.ui.nav.BookScreenDestination
@@ -130,6 +139,8 @@ private fun MainDataContent(
.padding(16.dp) .padding(16.dp)
) { ) {
Spacer(modifier = Modifier.size(16.dp))
Row( Row(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -147,38 +158,47 @@ private fun MainDataContent(
} }
Spacer(modifier = Modifier.size(16.dp)) Spacer(modifier = Modifier.size(16.dp))
Text( Text(
modifier = Modifier.testTag(TestIds.Main.PROFILE_NAME), modifier = Modifier
.testTag(TestIds.Main.PROFILE_NAME)
.weight(1f),
text = state.name, text = state.name,
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
}
Spacer(modifier = Modifier.size(16.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Button( Button(
modifier = Modifier.testTag(TestIds.Main.LOGOUT_BUTTON), modifier = Modifier
onClick = onLogout .testTag(TestIds.Main.LOGOUT_BUTTON),
onClick = onLogout,
colors = ButtonDefaults.buttonColors(
contentColor = MaterialTheme.colorScheme.errorContainer,
containerColor = MaterialTheme.colorScheme.onErrorContainer
)
) { ) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Выход") Text("Выход")
} Spacer(modifier = Modifier.size(8.dp))
Button( Icon(
modifier = Modifier.testTag(TestIds.Main.REFRESH_BUTTON), painter = painterResource(R.drawable.ic_logout),
onClick = onRefresh contentDescription = "Выйти"
) { )
Text("Обновить")
}
Button(
modifier = Modifier.testTag(TestIds.Main.ADD_BUTTON),
onClick = onAddBooking
) {
Text("Бронь")
}
} }
Spacer(modifier = Modifier.size(16.dp)) }
}
Spacer(modifier = Modifier.size(24.dp))
Text(
text = stringResource(R.string.main_screen_title),
style = MaterialTheme.typography.titleLarge.copy(
fontWeight = FontWeight.Bold
)
)
Spacer(modifier = Modifier.size(20.dp))
LazyColumn( LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
@@ -192,21 +212,86 @@ private fun MainDataContent(
Column( Column(
modifier = Modifier.padding(12.dp) modifier = Modifier.padding(12.dp)
) { ) {
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( Text(
modifier = Modifier.testTag(TestIds.Main.ITEM_DATE), modifier = Modifier.testTag(TestIds.Main.ITEM_DATE),
text = item.dateLabel, text = item.dateLabel,
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 style = MaterialTheme.typography.bodyMedium
) )
Text( Text(
modifier = Modifier.testTag(TestIds.Main.ITEM_PLACE), modifier = Modifier.testTag(TestIds.Main.ITEM_PLACE),
text = item.roomName, text = item.roomName,
style = MaterialTheme.typography.bodySmall 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 @Composable
private fun UserAvatar( private fun UserAvatar(

View File

@@ -32,13 +32,13 @@ private val LightColorScheme = lightColorScheme(
*/ */
) )
@Composable @Composable
fun WorkTheme( fun WorkTheme(
darkTheme: Boolean = isSystemInDarkTheme(), darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+ // Dynamic color is available on Android 12+
dynamicColor: Boolean = true, dynamicColor: Boolean = true,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
val colorScheme = when { val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current val context = LocalContext.current
@@ -54,4 +54,4 @@ fun WorkTheme(
typography = Typography, typography = Typography,
content = content content = content
) )
} }

View File

@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#FF000000"
android:pathData="M0.5,8.5v-1h15v1Z"
android:fillType="evenOdd"/>
<path
android:fillColor="#FF000000"
android:pathData="M8.5,15.5h-1V0.5h1Z"
android:fillType="evenOdd"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="16dp" android:viewportHeight="24" android:viewportWidth="24" android:width="16dp">
<path android:fillColor="#FF000000" android:pathData="M11.293,4.707l6.293,6.293l-13.586,0l0,2l13.586,0l-6.293,6.293l1.414,1.414l8.707,-8.707l-8.707,-8.707l-1.414,1.414z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="#FF000000"
android:pathData="M11.62,3.81l-4.19,4.19l4.19,4.19l-1.53,1.52l-5.71,-5.71l5.71,-5.71l1.53,1.52z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="16dp" android:viewportHeight="32" android:viewportWidth="32" android:width="16dp">
<path android:fillColor="#FF000000" android:pathData="M27.9,2.58a0.86,0.86 0,0 0,-0.07 -0.1,0.71 0.71,0 0,0 -0.19,-0.23l0,0 -0.09,0a1.12,1.12 0,0 0,-0.25 -0.11L27.1,2 27,2H12a1,1 0,0 0,-1 1V9a1,1 0,0 0,2 0V4h7.19L16.71,5A1,1 0,0 0,16 6V25H13V22a1,1 0,0 0,-2 0v4a1,1 0,0 0,1 1h4v2a1,1 0,0 0,0.4 0.8,1 1,0 0,0 0.6,0.2 1,1 0,0 0,0.29 0l10,-3A1,1 0,0 0,28 26V3A1,1 0,0 0,27.9 2.58ZM26,25.26l-8,2.4V6.74l8,-2.4Z"/>
<path android:fillColor="#FF000000" android:pathData="M7.41,17H14a1,1 0,0 0,0 -2H7.41l1.3,-1.29a1,1 0,0 0,-1.42 -1.42l-3,3a1,1 0,0 0,-0.21 0.33,1 1,0 0,0 0,0.76 1,1 0,0 0,0.21 0.33l3,3a1,1 0,0 0,1.42 0,1 1,0 0,0 0,-1.42Z"/>
<path android:fillColor="#FF000000" android:pathData="M20,17a1,1 0,0 0,0 -2h0a1,1 0,1 0,0 2Z"/>
</vector>

View File

@@ -0,0 +1,5 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="16dp" android:viewportHeight="24" android:viewportWidth="24" android:width="16dp">
<path android:fillColor="#FF000000" android:pathData="M12,3A8.959,8.959 0,0 0,5 6.339V4H3v6H9V8H6.274A6.982,6.982 0,1 1,5.22 13.751l-1.936,0.5A9,9 0,1 0,12 3Z"/>
</vector>

View File

@@ -1,7 +1,15 @@
<resources> <resources>
<string name="app_name">Work</string> <string name="app_name">Work</string>
<string name="title_activity_root">RootActivity</string> <string name="title_activity_root">RootActivity</string>
<string name="auth_title">Привет! Введи код для авторизации</string> <string name="auth_name">Система бронирования</string>
<string name="auth_title">Вход в систему</string>
<string name="auth_label">Код</string> <string name="auth_label">Код</string>
<string name="auth_sign_in">Войти</string> <string name="auth_sign_in">Войти</string>
<string name="main_screen_date">Дата:</string>
<string name="main_screen_place">Место:</string>
<string name="main_screen_title">Мои бронирования</string>
<string name="main_screen_refresh">Обновить</string>
<string name="main_screen_new_book">Новая бронь</string>
<string name="book_screen_book">Забронировать</string>
<string name="book_screen_back">Назад</string>
</resources> </resources>

View File

@@ -1,21 +1,16 @@
# Project-wide Gradle settings. ## For more details on how to configure your build environment visit
# 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
# http://www.gradle.org/docs/current/userguide/build_environment.html # http://www.gradle.org/docs/current/userguide/build_environment.html
#
# Specifies the JVM arguments used for the daemon process. # Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings. # 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. # When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit # This option should only be used with decoupled projects. For more details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true # org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the #Thu Dec 04 14:16:52 GMT+07:00 2025
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
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 android.nonTransitiveRClass=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx2048M -Dkotlin.daemon.jvm.options\="-Xmx2048M" -Dfile.encoding\=UTF-8