add full logic to BookScreen
Some checks failed
Android Test / validate-and-test (pull_request) Has been cancelled

This commit is contained in:
2025-12-03 22:52:16 +03:00
parent 4e45459af2
commit 75ffc79666
9 changed files with 577 additions and 316 deletions

View File

@@ -9,4 +9,13 @@ object BookRepository {
suspend fun loadBooking(text: String): Result<BookingEntity> {
return NetworkDataSource.loadBooking(text)
}
suspend fun bookPlace(
userCode: String,
date: String,
placeId: Int,
placeName: String
): Result<Unit> {
return NetworkDataSource.bookPlace(userCode, date, placeId, placeName)
}
}

View File

@@ -1,10 +1,13 @@
package ru.myitschool.work.data.source
import android.util.Log
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpStatusCode
import io.ktor.serialization.kotlinx.json.json
@@ -53,47 +56,72 @@ object NetworkDataSource {
}
}
suspend fun bookPlace(
userCode: String,
date: String,
placeId: Int,
placeName: String
): Result<Unit> = withContext(Dispatchers.IO) {
return@withContext runCatching {
// Log.i("aaa", "Booking: userCode=$userCode, date=$date, placeId=$placeId, placeName=$placeName")
// println("Booking: userCode=$userCode, date=$date, placeId=$placeId, placeName=$placeName")
val response = client.post(getUrl(userCode, Constants.BOOK_URL)) {
setBody(mapOf(
"date" to date,
"placeId" to placeId,
"placeName" to placeName
))
}
when (response.status) {
HttpStatusCode.OK -> Unit
else -> error(response.bodyAsText())
}
}
}
suspend fun checkAuth(code: String): Result<Boolean> = withContext(Dispatchers.IO) {
return@withContext runCatching {
true // удалить при проверке
// true // удалить при проверке
// val response = client.get(getUrl(code, Constants.AUTH_URL))
// response.status
// when (response.status) {
// HttpStatusCode.OK -> true
// else -> error(response.bodyAsText())
// }
val response = client.get(getUrl(code, Constants.AUTH_URL))
response.status
when (response.status) {
HttpStatusCode.OK -> true
else -> error(response.bodyAsText())
}
}
}
suspend fun loadData(code: String): Result<UserEntity> = withContext(Dispatchers.IO) {
return@withContext runCatching {
Json.decodeFromString<UserEntity>(testJson) // удалить при проверке
// Json.decodeFromString<UserEntity>(testJson) // удалить при проверке
// val response = client.get(getUrl(code, Constants.INFO_URL))
// when (response.status) {
// HttpStatusCode.OK -> {
// response.body<UserEntity>()
// }
// else -> error(response.bodyAsText())
// }
val response = client.get(getUrl(code, Constants.INFO_URL))
when (response.status) {
HttpStatusCode.OK -> {
response.body<UserEntity>()
}
else -> error(response.bodyAsText())
}
}
}
suspend fun loadBooking(code: String): Result<BookingEntity> = withContext(Dispatchers.IO) {
return@withContext runCatching {
BookingEntity(Json.decodeFromString<Map<String, List<PlaceInfo>>>(testBookingJson)) // удалить при проверке
// BookingEntity(Json.decodeFromString<Map<String, List<PlaceInfo>>>(testBookingJson)) // удалить при проверке
// val response = client.get(getUrl(code, Constants.BOOKING_URL))
// when (response.status) {
// HttpStatusCode.OK -> {
// BookingEntity(response.body<Map<String, List<PlaceInfo>>>())
// }
// else -> error(response.bodyAsText())
// }
val response = client.get(getUrl(code, Constants.BOOKING_URL))
when (response.status) {
HttpStatusCode.OK -> {
BookingEntity(response.body<Map<String, List<PlaceInfo>>>())
}
else -> error(response.bodyAsText())
}
}
}

View File

@@ -0,0 +1,16 @@
package ru.myitschool.work.domain.book
import ru.myitschool.work.data.repo.BookRepository
class BookingUseCase(
private val repository: BookRepository
) {
suspend operator fun invoke(
userCode: String,
date: String,
placeId: Int,
placeName: String
): Result<Unit> {
return repository.bookPlace(userCode, date, placeId, placeName)
}
}

View File

@@ -46,7 +46,7 @@ class AuthViewModel(application: Application) : AndroidViewModel(application) {
onFailure = { error ->
error.printStackTrace()
_uiState.update { AuthState.Data }
_errorStateValue.value = "Неизвестная ошибка"
_errorStateValue.value = error.message.toString() ?: "Неизвестная ошибка"
}
)
}

View File

@@ -3,4 +3,10 @@ package ru.myitschool.work.ui.screen.book
sealed interface BookIntent {
object Back: BookIntent
object LoadBooking: BookIntent
object Book : BookIntent
data class SelectDate(val date: String) : BookIntent
data class SelectPlace(
val placeId: Int,
val placeName: String
) : BookIntent
}

View File

@@ -1,310 +1,424 @@
package ru.myitschool.work.ui.screen.book
package ru.myitschool.work.ui.screen.book
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.CircularProgressIndicator
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import ru.myitschool.work.R
import ru.myitschool.work.core.TestIds
import ru.myitschool.work.core.TestIds.Book
import ru.myitschool.work.core.TestIds.Main
import ru.myitschool.work.domain.book.entities.BookingEntity
import ru.myitschool.work.formatBookingDate
import ru.myitschool.work.formatDate
import ru.myitschool.work.ui.BaseButton
import ru.myitschool.work.ui.BaseNoBackgroundButton
import ru.myitschool.work.ui.BaseText16
import ru.myitschool.work.ui.BaseText24
import ru.myitschool.work.ui.nav.AuthScreenDestination
import ru.myitschool.work.ui.nav.MainScreenDestination
import ru.myitschool.work.ui.screen.main.MainIntent
import ru.myitschool.work.ui.theme.Black
import ru.myitschool.work.ui.theme.Blue
import ru.myitschool.work.ui.theme.Typography
import ru.myitschool.work.ui.theme.White
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.CircularProgressIndicator
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.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
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.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import ru.myitschool.work.R
import ru.myitschool.work.core.TestIds
import ru.myitschool.work.core.TestIds.Book
import ru.myitschool.work.core.TestIds.Main
import ru.myitschool.work.domain.book.entities.BookingEntity
import ru.myitschool.work.domain.book.entities.PlaceInfo
import ru.myitschool.work.formatBookingDate
import ru.myitschool.work.formatDate
import ru.myitschool.work.ui.BaseButton
import ru.myitschool.work.ui.BaseNoBackgroundButton
import ru.myitschool.work.ui.BaseText16
import ru.myitschool.work.ui.BaseText24
import ru.myitschool.work.ui.nav.AuthScreenDestination
import ru.myitschool.work.ui.nav.MainScreenDestination
import ru.myitschool.work.ui.screen.main.MainIntent
import ru.myitschool.work.ui.theme.Black
import ru.myitschool.work.ui.theme.Blue
import ru.myitschool.work.ui.theme.Typography
import ru.myitschool.work.ui.theme.White
@Composable
fun BookScreen(
navController: NavController,
viewModel: BookViewModel = viewModel(),
) {
val state by viewModel.uiState.collectAsState()
@Composable
fun BookScreen(
navController: NavController,
viewModel: BookViewModel = viewModel(),
) {
val state by viewModel.uiState.collectAsState()
LaunchedEffect(Unit) {
viewModel.actionFlow.collect { action ->
when(action) {
is BookAction.Auth -> navController.navigate(AuthScreenDestination)
LaunchedEffect(Unit) {
viewModel.actionFlow.collect { action ->
when(action) {
is BookAction.Auth -> navController.navigate(AuthScreenDestination)
is BookAction.Main -> navController.navigate(MainScreenDestination)
is BookAction.Main -> navController.navigate(MainScreenDestination)
}
}
}
}
when(state) {
is BookState.Loading -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator(
modifier = Modifier.size(64.dp)
when(state) {
is BookState.Loading -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
CircularProgressIndicator(
modifier = Modifier.size(64.dp)
)
}
}
is BookState.Data -> {
val dataState = state as BookState.Data
DataContent(
viewModel = viewModel,
bookingData = dataState.userBooking,
selectedDate = dataState.selectedDate,
selectedPlaceId = dataState.selectedPlaceId
)
}
is BookState.Error -> ErrorContent(viewModel)
is BookState.Empty -> EmptyContent(viewModel)
}
is BookState.Data -> {
DataContent(
viewModel,
bookingData = (state as? BookState.Data)?.userBooking
)
}
is BookState.Error -> ErrorContent(viewModel)
is BookState.Empty -> EmptyContent(viewModel)
}
}
@Composable
fun EmptyContent(
viewModel: BookViewModel
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
@Composable
fun EmptyContent(
viewModel: BookViewModel
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(15.dp)
.fillMaxHeight()
.width(320.dp)
) {
Spacer(modifier = Modifier.height(80.dp))
BaseText24(
text = stringResource(R.string.book_all_booked),
modifier = Modifier.testTag(Book.EMPTY),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(20.dp))
BaseButton(
text = stringResource(R.string.book_back),
modifier = Modifier
.fillMaxWidth()
.testTag(Book.BACK_BUTTON),
onClick = { viewModel.onIntent(BookIntent.Back) },
btnContentColor = White,
btnColor = Blue
)
}
}
}
@Composable
fun ErrorContent(
viewModel: BookViewModel
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(15.dp)
.fillMaxHeight()
.width(320.dp)
) {
Spacer(modifier = Modifier.height(80.dp))
BaseText24(
text = stringResource(R.string.book_error),
modifier = Modifier.testTag(Book.ERROR),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(20.dp))
BaseButton(
border = BorderStroke(1.dp, Blue),
text = stringResource(R.string.book_back),
modifier = Modifier
.fillMaxWidth()
.testTag(Book.BACK_BUTTON),
onClick = { viewModel.onIntent(BookIntent.Back) },
btnContentColor = Blue,
btnColor = Color.Transparent
)
Spacer(modifier = Modifier.height(15.dp))
BaseButton(
text = stringResource(R.string.main_update),
modifier = Modifier
.fillMaxWidth()
.testTag(Book.REFRESH_BUTTON),
onClick = { viewModel.onIntent(BookIntent.LoadBooking) },
btnContentColor = White,
btnColor = Blue
)
}
}
}
@Composable
fun DataContent(
viewModel: BookViewModel,
bookingData: BookingEntity?
) {
Column {
Row(
modifier = Modifier
.clip(RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp))
.background(Blue)
.fillMaxWidth()
.padding(horizontal = 10.dp, vertical = 15.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
BaseText24(
text = stringResource(R.string.book_new_book),
color = White,
modifier = Modifier.padding(start = 15.dp)
)
BaseNoBackgroundButton(
text = stringResource(R.string.book_back),
modifier = Modifier.testTag(Book.BACK_BUTTON),
onClick = { viewModel.onIntent(BookIntent.Back) }
)
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 20.dp, horizontal = 10.dp)
.clip(RoundedCornerShape(16.dp))
.background(White)
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Column(
verticalArrangement = Arrangement.SpaceBetween,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(13.dp)
.padding(15.dp)
.fillMaxHeight()
.width(320.dp)
) {
Column {
Text(
text = stringResource(R.string.book_available_date),
style = Typography.bodyMedium,
fontSize = 16.sp,
)
BookDateList(bookingData?.bookings?.keys?.toList() ?: emptyList())
Spacer(modifier = Modifier.height(80.dp))
Text(
text = stringResource(R.string.book_choose_place),
style = Typography.bodyMedium,
fontSize = 16.sp,
)
BaseText24(
text = stringResource(R.string.book_all_booked),
modifier = Modifier.testTag(Book.EMPTY),
textAlign = TextAlign.Center
)
BookPlaceList()
}
Spacer(modifier = Modifier.height(20.dp))
BaseButton(
text = stringResource(R.string.booking_button),
btnColor = Blue,
btnContentColor = White,
onClick = { },
text = stringResource(R.string.book_back),
modifier = Modifier
.testTag(Book.BOOK_BUTTON)
.padding(horizontal = 10.dp)
.fillMaxWidth(),
icon = { Image(
painter = painterResource(R.drawable.add_icon),
contentDescription = stringResource(R.string.add_icon_description)
) }
.fillMaxWidth()
.testTag(Book.BACK_BUTTON),
onClick = { viewModel.onIntent(BookIntent.Back) },
btnContentColor = White,
btnColor = Blue
)
}
}
}
}
@Composable
fun BookPlaceList() {
BookPlaceListElement()
}
@Composable
fun BookPlaceListElement() {
}
@Composable
fun BookDateList(dates: List<String>) {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(7.dp),
modifier = Modifier.padding(vertical = 15.dp)
@Composable
fun ErrorContent(
viewModel: BookViewModel
) {
dates.forEach { date ->
BookDateListElement(date = date, onClick = {
// Обработка выбора даты
})
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.fillMaxSize()
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(15.dp)
.fillMaxHeight()
.width(320.dp)
) {
Spacer(modifier = Modifier.height(80.dp))
BaseText24(
text = stringResource(R.string.book_error),
modifier = Modifier.testTag(Book.ERROR),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(20.dp))
BaseButton(
border = BorderStroke(1.dp, Blue),
text = stringResource(R.string.book_back),
modifier = Modifier
.fillMaxWidth()
.testTag(Book.BACK_BUTTON),
onClick = { viewModel.onIntent(BookIntent.Back) },
btnContentColor = Blue,
btnColor = Color.Transparent
)
Spacer(modifier = Modifier.height(15.dp))
BaseButton(
text = stringResource(R.string.main_update),
modifier = Modifier
.fillMaxWidth()
.testTag(Book.REFRESH_BUTTON),
onClick = { viewModel.onIntent(BookIntent.LoadBooking) },
btnContentColor = White,
btnColor = Blue
)
}
}
}
}
@Composable
fun BookDateListElement(date: String, onClick: () -> Unit) {
Button(
contentPadding = PaddingValues(0.dp),
modifier = Modifier
.testTag(Book.ITEM_DATE)
.padding(0.dp),
border = BorderStroke(1.dp, Black),
onClick = onClick,
colors = ButtonColors(
contentColor = Black,
containerColor = Color.Transparent,
disabledContentColor = Black,
disabledContainerColor = Color.Transparent),
@Composable
fun DataContent(
viewModel: BookViewModel,
bookingData: BookingEntity,
selectedDate: String,
selectedPlaceId: Int
) {
val formattedDate = formatBookingDate(date)
BaseText16(text = formattedDate)
val availableDates = bookingData.bookings
.filter { it.value.isNotEmpty() }
.keys
.sorted()
val placesForSelectedDate = bookingData.bookings[selectedDate] ?: emptyList()
Column {
Row(
modifier = Modifier
.clip(RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp))
.background(Blue)
.fillMaxWidth()
.padding(horizontal = 10.dp, vertical = 15.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
BaseText24(
text = stringResource(R.string.book_new_book),
color = White,
modifier = Modifier.padding(start = 15.dp)
)
BaseNoBackgroundButton(
text = stringResource(R.string.book_back),
modifier = Modifier.testTag(Book.BACK_BUTTON),
onClick = { viewModel.onIntent(BookIntent.Back) }
)
}
Box(
modifier = Modifier
.fillMaxSize()
.padding(vertical = 20.dp, horizontal = 10.dp)
.clip(RoundedCornerShape(16.dp))
.background(White)
) {
Column(
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.fillMaxSize()
.padding(13.dp)
) {
Column {
Text(
text = stringResource(R.string.book_available_date),
style = Typography.bodyMedium,
fontSize = 16.sp,
)
BookDateList(
dates = availableDates,
selectedDate = selectedDate,
onDateSelected = { date ->
viewModel.onIntent(BookIntent.SelectDate(date))
}
)
Text(
text = stringResource(R.string.book_choose_place),
style = Typography.bodyMedium,
fontSize = 16.sp,
)
BookPlaceList(
places = placesForSelectedDate,
selectedPlaceId = selectedPlaceId,
onPlaceSelected = { placeId, placeName ->
viewModel.onIntent(BookIntent.SelectPlace(placeId, placeName))
}
)
}
BaseButton(
enable = selectedPlaceId != -1,
text = stringResource(R.string.booking_button),
btnColor = Blue,
btnContentColor = White,
onClick = { viewModel.onIntent(BookIntent.Book) },
modifier = Modifier
.testTag(Book.BOOK_BUTTON)
.padding(horizontal = 10.dp)
.fillMaxWidth(),
icon = { Image(
painter = painterResource(R.drawable.add_icon),
contentDescription = stringResource(R.string.add_icon_description)
) }
)
}
}
}
}
}
@Composable
fun BookPlaceList(
places: List<PlaceInfo>,
selectedPlaceId: Int,
onPlaceSelected: (Int, String) -> Unit
) {
Column(
modifier = Modifier.padding(vertical = 15.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
if (places.isEmpty()) {
Text(
text = "Нет доступных мест для выбранной даты",
color = Color.Gray,
style = Typography.bodyMedium,
modifier = Modifier.padding(vertical = 8.dp)
)
} else {
places.forEachIndexed { index, placeInfo ->
BookPlaceListElement(
placeInfo = placeInfo,
isSelected = placeInfo.id == selectedPlaceId,
onPlaceSelected = { onPlaceSelected(placeInfo.id, placeInfo.place) },
index = index
)
}
}
}
}
@Composable
fun BookPlaceListElement(
placeInfo: PlaceInfo,
isSelected: Boolean,
onPlaceSelected: () -> Unit,
index: Int
) {
Row(
modifier = Modifier
.fillMaxWidth()
.selectable(
selected = isSelected,
onClick = onPlaceSelected
)
.testTag(Book.getIdPlaceItemByPosition(index))
.padding(vertical = 12.dp, horizontal = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
BaseText16(
text = placeInfo.place,
modifier = Modifier.testTag(Book.ITEM_PLACE_TEXT)
)
Box(
modifier = Modifier
.size(24.dp)
.border(
width = 2.dp,
color = if (isSelected) Blue else Color.Gray,
shape = CircleShape
)
.background(
color = if (isSelected) Blue else Color.Transparent,
shape = CircleShape
)
.testTag(Book.ITEM_PLACE_SELECTOR)
) {
if (isSelected) {
Box(
modifier = Modifier
.size(12.dp)
.background(Color.White, CircleShape)
.align(Alignment.Center)
)
}
}
}
}
@Composable
fun BookDateList(
dates: List<String>,
selectedDate: String,
onDateSelected: (String) -> Unit
) {
FlowRow(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(7.dp),
modifier = Modifier.padding(vertical = 15.dp)
) {
dates.forEachIndexed { index, date ->
BookDateListElement(
date = date,
isSelected = date == selectedDate,
onClick = { onDateSelected(date) },
index = index
)
}
}
}
@Composable
fun BookDateListElement(
date: String,
isSelected: Boolean,
onClick: () -> Unit,
index: Int
) {
Button(
contentPadding = PaddingValues(0.dp),
modifier = Modifier
.testTag(Book.getIdDateItemByPosition(index))
.padding(0.dp),
border = BorderStroke(1.dp, if (isSelected) Blue else Black,),
onClick = onClick,
colors = ButtonColors(
contentColor = if (isSelected) White else Black,
containerColor = if (isSelected) Blue else Color.Transparent,
disabledContentColor = Black,
disabledContainerColor = Color.Transparent),
) {
val formattedDate = date.formatBookingDate()
BaseText16(
text = formattedDate,
modifier = Modifier.testTag(Book.ITEM_DATE),
color = if (isSelected) White else Black,
)
}
}

View File

@@ -4,7 +4,12 @@ import ru.myitschool.work.domain.book.entities.BookingEntity
sealed interface BookState {
object Loading: BookState
data class Data(val userBooking: BookingEntity): BookState
data class Data(
val userBooking: BookingEntity,
val selectedDate: String = "",
val selectedPlaceId: Int = -1,
val selectedPlaceName: String = ""
): BookState
object Error: BookState
object Empty: BookState
}

View File

@@ -14,15 +14,21 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.myitschool.work.App
import ru.myitschool.work.data.repo.BookRepository
import ru.myitschool.work.data.repo.MainRepository
import ru.myitschool.work.domain.book.BookingUseCase
import ru.myitschool.work.domain.book.LoadBookingUseCase
import ru.myitschool.work.domain.main.LoadDataUseCase
import ru.myitschool.work.ui.screen.main.MainAction
import ru.myitschool.work.ui.screen.main.MainIntent
import ru.myitschool.work.ui.screen.main.MainState
import kotlin.text.isEmpty
class BookViewModel(application: Application) : AndroidViewModel(application) {
private val loadBookingUseCase by lazy { LoadBookingUseCase(BookRepository) }
private val bookingUseCase by lazy { BookingUseCase (BookRepository) }
private val dataStoreManager by lazy {
(getApplication() as App).dataStoreManager
}
@@ -35,6 +41,35 @@ class BookViewModel(application: Application) : AndroidViewModel(application) {
loadBooking()
}
private fun bookSelectedPlace() {
viewModelScope.launch(Dispatchers.IO) {
try {
val userCode = dataStoreManager.getUserCode().first()
val currentState = _uiState.value
if (currentState is BookState.Data && currentState.selectedPlaceId != -1) {
bookingUseCase.invoke(
userCode = userCode.code,
date = currentState.selectedDate,
placeId = currentState.selectedPlaceId,
placeName = currentState.selectedPlaceName
).fold(
onSuccess = {
_actionFlow.emit(BookAction.Main)
},
onFailure = { error ->
error.printStackTrace()
_uiState.update { BookState.Error }
}
)
}
} catch (error: Exception) {
error.printStackTrace()
_uiState.update { BookState.Error }
}
}
}
private fun loadBooking() {
viewModelScope.launch(Dispatchers.IO) {
_uiState.update { BookState.Loading }
@@ -49,11 +84,27 @@ class BookViewModel(application: Application) : AndroidViewModel(application) {
loadBookingUseCase.invoke(userCode.code).fold(
onSuccess = { data ->
if (data.bookings.isEmpty()) {
val availableDates = data.bookings
.filter { it.value.isNotEmpty() }
.keys
.sorted()
if (availableDates.isEmpty()) {
_uiState.update { BookState.Empty }
}
else {
_uiState.update { BookState.Data(data) }
} else {
val selectedDate = availableDates.first()
val placesForSelectedDate = data.bookings[selectedDate] ?: emptyList()
val selectedPlaceId = placesForSelectedDate.firstOrNull()?.id ?: -1
val selectedPlaceName = placesForSelectedDate.firstOrNull()?.place ?: ""
_uiState.update {
BookState.Data(
userBooking = data,
selectedDate = selectedDate,
selectedPlaceId = selectedPlaceId,
selectedPlaceName = selectedPlaceName
)
}
}
},
onFailure = { error ->
@@ -68,8 +119,8 @@ class BookViewModel(application: Application) : AndroidViewModel(application) {
}
}
fun onIntent( intent: BookIntent) {
when(intent) {
fun onIntent(intent: BookIntent) {
when (intent) {
is BookIntent.LoadBooking -> loadBooking()
is BookIntent.Back -> {
@@ -78,6 +129,37 @@ class BookViewModel(application: Application) : AndroidViewModel(application) {
}
}
is BookIntent.Book -> bookSelectedPlace()
is BookIntent.SelectDate -> {
val currentState = _uiState.value
if (currentState is BookState.Data) {
val placesForDate =
currentState.userBooking.bookings[intent.date] ?: emptyList()
val newSelectedPlaceId = placesForDate.firstOrNull()?.id ?: -1
val newSelectedPlaceName = placesForDate.firstOrNull()?.place ?: ""
_uiState.update {
currentState.copy(
selectedDate = intent.date,
selectedPlaceId = newSelectedPlaceId,
selectedPlaceName = newSelectedPlaceName
)
}
}
}
is BookIntent.SelectPlace -> {
val currentState = _uiState.value
if (currentState is BookState.Data) {
_uiState.update {
currentState.copy(
selectedPlaceId = intent.placeId,
selectedPlaceName = intent.placeName
)
}
}
}
}
}
}

View File

@@ -1,9 +1,12 @@
package ru.myitschool.work
import java.text.SimpleDateFormat
import java.util.Locale
fun String.formatDate(): String {
return try {
val inputFormat = java.text.SimpleDateFormat("yyyy-MM-dd", java.util.Locale.getDefault())
val outputFormat = java.text.SimpleDateFormat("dd.MM.yyyy", java.util.Locale.getDefault())
val inputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val outputFormat = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault())
val date = inputFormat.parse(this)
outputFormat.format(date)
} catch (e: Exception) {
@@ -11,15 +14,13 @@ fun String.formatDate(): String {
}
}
fun formatBookingDate(dateString: String): String {
fun String.formatBookingDate(): String {
return try {
val parts = dateString.split("-")
if (parts.size == 3) {
"${parts[2]}.${parts[1]}"
} else {
dateString
}
val inputFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
val outputFormat = SimpleDateFormat("dd.MM", Locale.getDefault())
val date = inputFormat.parse(this)
outputFormat.format(date)
} catch (e: Exception) {
dateString
this
}
}