booking with server

This commit is contained in:
2025-12-04 11:52:16 +03:00
parent 4f23a2a1bd
commit ab03679731
7 changed files with 137 additions and 65 deletions

View File

@@ -0,0 +1,17 @@
package ru.myitschool.work.data.repo
import ru.myitschool.work.data.entity.Place
import ru.myitschool.work.data.source.DataStoreDataSource
import ru.myitschool.work.data.source.NetworkDataSource
import java.time.LocalDate
class BookingRepository {
suspend fun getAvailableBookings(): Result<Map<LocalDate, List<Place>>> {
val code = DataStoreDataSource.getAuthCode()
if (code.isEmpty() || code == "0") {
return Result.failure(Exception("Auth code not found"))
}
return NetworkDataSource.getAvailableBookings(code)
}
}

View File

@@ -109,5 +109,37 @@ object NetworkDataSource {
} }
} }
} }
suspend fun getAvailableBookings(code: String): Result<Map<LocalDate, List<Place>>> = withContext(Dispatchers.IO) {
return@withContext runCatching {
val response = client.get(getUrl(code, Constants.BOOKING_URL))
when (response.status) {
HttpStatusCode.OK -> {
val json = response.bodyAsText()
val jsonObject = Json.parseToJsonElement(json).jsonObject
val availableBookings = mutableMapOf<LocalDate, List<Place>>()
for ((dateString, placesArray) in jsonObject) {
val date = LocalDate.parse(dateString)
val places = placesArray.jsonArray.map { placeElement ->
val placeObj = placeElement.jsonObject
val id = placeObj["id"]?.jsonPrimitive?.long
?: error("Missing 'id' in place")
val placeName = placeObj["place"]?.jsonPrimitive?.content
?: error("Missing 'place' in place")
Place(id, placeName)
}
if (places.isNotEmpty()) {
availableBookings[date] = places
}
}
availableBookings.toSortedMap()
}
else -> error("Request error: ${response.bodyAsText()}")
}
}
}
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl" private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl"
} }

View File

@@ -0,0 +1,13 @@
package ru.myitschool.work.domain.book
import ru.myitschool.work.data.entity.Place
import ru.myitschool.work.data.repo.BookingRepository
import java.time.LocalDate
class GetAvailableBookingsUseCase(
private val repository: BookingRepository
) {
suspend operator fun invoke(): Result<Map<LocalDate, List<Place>>> {
return repository.getAvailableBookings()
}
}

View File

@@ -1,5 +1,6 @@
package ru.myitschool.work.ui.screen.book package ru.myitschool.work.ui.screen.book
import ru.myitschool.work.data.entity.Place
import java.time.LocalDate import java.time.LocalDate
sealed interface BookIntent { sealed interface BookIntent {
@@ -7,5 +8,5 @@ sealed interface BookIntent {
object Refresh : BookIntent object Refresh : BookIntent
object BookPlace : BookIntent object BookPlace : BookIntent
data class SelectDate(val date: LocalDate) : BookIntent data class SelectDate(val date: LocalDate) : BookIntent
data class SelectPlace(val place: String) : BookIntent data class SelectPlace(val place: Place) : BookIntent
} }

View File

@@ -13,6 +13,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import java.time.LocalDate import java.time.LocalDate
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import ru.myitschool.work.core.TestIds import ru.myitschool.work.core.TestIds
import ru.myitschool.work.data.entity.Place
@Composable @Composable
fun BookScreen( fun BookScreen(
@@ -65,7 +66,7 @@ fun BookScreen(
fun BookContentScreen( fun BookContentScreen(
uiState: BookState.Data, uiState: BookState.Data,
onSelectDate: (LocalDate) -> Unit, onSelectDate: (LocalDate) -> Unit,
onSelectPlace: (String) -> Unit, onSelectPlace: (Place) -> Unit,
onBook: () -> Unit, onBook: () -> Unit,
onBack: () -> Unit, onBack: () -> Unit,
onRefresh: () -> Unit onRefresh: () -> Unit
@@ -117,7 +118,7 @@ fun BookContentScreen(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = place, text = place.place,
modifier = Modifier.weight(1f).testTag(TestIds.Book.ITEM_PLACE_TEXT) modifier = Modifier.weight(1f).testTag(TestIds.Book.ITEM_PLACE_TEXT)
) )
RadioButton( RadioButton(

View File

@@ -1,14 +1,15 @@
package ru.myitschool.work.ui.screen.book package ru.myitschool.work.ui.screen.book
import ru.myitschool.work.data.entity.Place
import java.time.LocalDate import java.time.LocalDate
sealed interface BookState { sealed interface BookState {
object Loading : BookState object Loading : BookState
data class Data( data class Data(
val dates: List<LocalDate> = emptyList(), val dates: List<LocalDate> = emptyList(),
val places: Map<LocalDate, List<String>> = emptyMap(), val places: Map<LocalDate, List<Place>> = emptyMap(),
val selectedDate: LocalDate? = null, val selectedDate: LocalDate? = null,
val selectedPlace: String? = null, val selectedPlace: Place? = null,
val isError: Boolean = false, val isError: Boolean = false,
val errorMessage: String? = null val errorMessage: String? = null
) : BookState ) : BookState

View File

@@ -7,12 +7,21 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.myitschool.work.data.entity.Place
import ru.myitschool.work.data.repo.BookingRepository
// import ru.myitschool.work.domain.book.CreateBookingUseCase
import ru.myitschool.work.domain.book.GetAvailableBookingsUseCase
import java.time.LocalDate import java.time.LocalDate
class BookViewModel : ViewModel() { class BookViewModel : ViewModel() {
private val repository by lazy { BookingRepository() }
private val getAvailableBookingsUseCase by lazy { GetAvailableBookingsUseCase(repository) }
// private val createBookingUseCase by lazy { CreateBookingUseCase(repository) }
private val _uiState = MutableStateFlow<BookState>(BookState.Loading) private val _uiState = MutableStateFlow<BookState>(BookState.Loading)
val uiState: StateFlow<BookState> = _uiState.asStateFlow() val uiState: StateFlow<BookState> = _uiState.asStateFlow()
@@ -20,6 +29,8 @@ class BookViewModel : ViewModel() {
private val _actionFlow = MutableSharedFlow<BookAction>() private val _actionFlow = MutableSharedFlow<BookAction>()
val actionFlow: SharedFlow<BookAction> = _actionFlow val actionFlow: SharedFlow<BookAction> = _actionFlow
private var selectedPlaceId: Long? = null
init { init {
loadBookData() loadBookData()
} }
@@ -38,89 +49,85 @@ class BookViewModel : ViewModel() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
_uiState.update { BookState.Loading } _uiState.update { BookState.Loading }
try { getAvailableBookingsUseCase().fold(
// Временные mock данные onSuccess = { bookings ->
val mockDates = listOf( if (bookings.isEmpty()) {
LocalDate.now().plusDays(1), _uiState.update {
LocalDate.now().plusDays(2), BookState.Data(
LocalDate.now().plusDays(3) isError = true,
) errorMessage = "Нет доступных дат для бронирования"
)
val mockPlaces = mapOf( }
mockDates[0] to listOf("Место 1", "Место 2", "Место 3"), } else {
mockDates[1] to listOf("Место 1", "Место 2"), val dates = bookings.keys.toList()
mockDates[2] to listOf("Место 1") _uiState.update {
) BookState.Data(
dates = dates,
val sortedDates = mockDates.sorted() places = bookings,
val availableDates = sortedDates.filter { mockPlaces[it]?.isNotEmpty() == true } selectedDate = dates.first(),
val defaultDate = availableDates.firstOrNull() selectedPlace = null,
isError = false,
_uiState.update { errorMessage = null
BookState.Data( )
dates = sortedDates, }
places = mockPlaces, }
selectedDate = defaultDate, },
selectedPlace = null, onFailure = { error ->
isError = false, error.printStackTrace()
errorMessage = null _uiState.update {
) BookState.Data(
isError = true,
errorMessage = error.message ?: "Ошибка загрузки данных"
)
}
} }
} catch (e: Exception) { )
_uiState.update {
BookState.Data(
isError = true,
errorMessage = "Ошибка загрузки данных"
)
}
_actionFlow.emit(BookAction.ShowError("Ошибка загрузки данных"))
}
} }
} }
private fun selectDate(date: LocalDate) { private fun selectDate(date: LocalDate) {
_uiState.update { currentState -> _uiState.update { currentState ->
when (currentState) { if (currentState is BookState.Data) {
is BookState.Data -> currentState.copy( currentState.copy(
selectedDate = date, selectedDate = date,
selectedPlace = null selectedPlace = null
) )
else -> currentState } else {
currentState
} }
} }
selectedPlaceId = null
} }
private fun selectPlace(place: String) { private fun selectPlace(place: Place) {
_uiState.update { currentState -> _uiState.update { currentState ->
when (currentState) { if (currentState is BookState.Data) {
is BookState.Data -> currentState.copy(selectedPlace = place) currentState.copy(selectedPlace = place)
else -> currentState } else {
currentState
} }
} }
selectedPlaceId = place.id
} }
private fun bookPlace() { private fun bookPlace() {
viewModelScope.launch(Dispatchers.IO) { /*
try { selectedPlaceId?.let { placeId ->
// вызов API для бронирования viewModelScope.launch(Dispatchers.IO) {
// временная имитация успеха createBookingUseCase(placeId).fold(
_actionFlow.emit(BookAction.BookSuccess) onSuccess = {
} catch (e: Exception) { _actionFlow.emit(BookAction.BookSuccess)
_uiState.update { currentState -> },
when (currentState) { onFailure = { error ->
is BookState.Data -> currentState.copy( error.printStackTrace()
isError = true, _actionFlow.emit(BookAction.ShowError(error.message ?: "Ошибка бронирования"))
errorMessage = "Ошибка бронирования"
)
else -> currentState
} }
} )
_actionFlow.emit(BookAction.ShowError("Ошибка бронирования"))
} }
} }*/
} }
private fun refresh() { private fun refresh() {
loadBookData() loadBookData()
} }
} }