diff --git a/app/src/main/java/ru/myitschool/work/data/repo/BookingRepository.kt b/app/src/main/java/ru/myitschool/work/data/repo/BookingRepository.kt new file mode 100644 index 0000000..b16e8c4 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/data/repo/BookingRepository.kt @@ -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>> { + val code = DataStoreDataSource.getAuthCode() + if (code.isEmpty() || code == "0") { + return Result.failure(Exception("Auth code not found")) + } + return NetworkDataSource.getAvailableBookings(code) + } +} diff --git a/app/src/main/java/ru/myitschool/work/data/source/NetworkDataSource.kt b/app/src/main/java/ru/myitschool/work/data/source/NetworkDataSource.kt index 66c3dcf..2127f9d 100644 --- a/app/src/main/java/ru/myitschool/work/data/source/NetworkDataSource.kt +++ b/app/src/main/java/ru/myitschool/work/data/source/NetworkDataSource.kt @@ -109,5 +109,37 @@ object NetworkDataSource { } } } + + suspend fun getAvailableBookings(code: String): Result>> = 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>() + + 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" } \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/domain/book/GetAvailableBookingsUseCase.kt b/app/src/main/java/ru/myitschool/work/domain/book/GetAvailableBookingsUseCase.kt new file mode 100644 index 0000000..cc894c7 --- /dev/null +++ b/app/src/main/java/ru/myitschool/work/domain/book/GetAvailableBookingsUseCase.kt @@ -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>> { + return repository.getAvailableBookings() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookIntent.kt b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookIntent.kt index 4630d54..62744be 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookIntent.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookIntent.kt @@ -1,5 +1,6 @@ package ru.myitschool.work.ui.screen.book +import ru.myitschool.work.data.entity.Place import java.time.LocalDate sealed interface BookIntent { @@ -7,5 +8,5 @@ sealed interface BookIntent { object Refresh : BookIntent object BookPlace : BookIntent data class SelectDate(val date: LocalDate) : BookIntent - data class SelectPlace(val place: String) : BookIntent + data class SelectPlace(val place: Place) : BookIntent } \ No newline at end of file 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 25213fb..71c38ca 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 @@ -13,6 +13,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import java.time.LocalDate import java.time.format.DateTimeFormatter import ru.myitschool.work.core.TestIds +import ru.myitschool.work.data.entity.Place @Composable fun BookScreen( @@ -65,7 +66,7 @@ fun BookScreen( fun BookContentScreen( uiState: BookState.Data, onSelectDate: (LocalDate) -> Unit, - onSelectPlace: (String) -> Unit, + onSelectPlace: (Place) -> Unit, onBook: () -> Unit, onBack: () -> Unit, onRefresh: () -> Unit @@ -117,7 +118,7 @@ fun BookContentScreen( verticalAlignment = Alignment.CenterVertically ) { Text( - text = place, + text = place.place, modifier = Modifier.weight(1f).testTag(TestIds.Book.ITEM_PLACE_TEXT) ) RadioButton( diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookState.kt b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookState.kt index 5677a72..8163b48 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookState.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookState.kt @@ -1,14 +1,15 @@ package ru.myitschool.work.ui.screen.book +import ru.myitschool.work.data.entity.Place import java.time.LocalDate sealed interface BookState { object Loading : BookState data class Data( val dates: List = emptyList(), - val places: Map> = emptyMap(), + val places: Map> = emptyMap(), val selectedDate: LocalDate? = null, - val selectedPlace: String? = null, + val selectedPlace: Place? = null, val isError: Boolean = false, val errorMessage: String? = null ) : BookState diff --git a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookViewModel.kt b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookViewModel.kt index 81ac5e2..e248b35 100644 --- a/app/src/main/java/ru/myitschool/work/ui/screen/book/BookViewModel.kt +++ b/app/src/main/java/ru/myitschool/work/ui/screen/book/BookViewModel.kt @@ -7,12 +7,21 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update 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 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.Loading) val uiState: StateFlow = _uiState.asStateFlow() @@ -20,6 +29,8 @@ class BookViewModel : ViewModel() { private val _actionFlow = MutableSharedFlow() val actionFlow: SharedFlow = _actionFlow + private var selectedPlaceId: Long? = null + init { loadBookData() } @@ -38,89 +49,85 @@ class BookViewModel : ViewModel() { viewModelScope.launch(Dispatchers.IO) { _uiState.update { BookState.Loading } - try { - // Временные mock данные - val mockDates = listOf( - LocalDate.now().plusDays(1), - LocalDate.now().plusDays(2), - LocalDate.now().plusDays(3) - ) - - val mockPlaces = mapOf( - mockDates[0] to listOf("Место 1", "Место 2", "Место 3"), - mockDates[1] to listOf("Место 1", "Место 2"), - mockDates[2] to listOf("Место 1") - ) - - val sortedDates = mockDates.sorted() - val availableDates = sortedDates.filter { mockPlaces[it]?.isNotEmpty() == true } - val defaultDate = availableDates.firstOrNull() - - _uiState.update { - BookState.Data( - dates = sortedDates, - places = mockPlaces, - selectedDate = defaultDate, - selectedPlace = null, - isError = false, - errorMessage = null - ) + getAvailableBookingsUseCase().fold( + onSuccess = { bookings -> + if (bookings.isEmpty()) { + _uiState.update { + BookState.Data( + isError = true, + errorMessage = "Нет доступных дат для бронирования" + ) + } + } else { + val dates = bookings.keys.toList() + _uiState.update { + BookState.Data( + dates = dates, + places = bookings, + selectedDate = dates.first(), + selectedPlace = null, + isError = false, + errorMessage = null + ) + } + } + }, + onFailure = { error -> + error.printStackTrace() + _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) { _uiState.update { currentState -> - when (currentState) { - is BookState.Data -> currentState.copy( + if (currentState is BookState.Data) { + currentState.copy( selectedDate = date, selectedPlace = null ) - else -> currentState + } else { + currentState } } + selectedPlaceId = null } - private fun selectPlace(place: String) { + private fun selectPlace(place: Place) { _uiState.update { currentState -> - when (currentState) { - is BookState.Data -> currentState.copy(selectedPlace = place) - else -> currentState + if (currentState is BookState.Data) { + currentState.copy(selectedPlace = place) + } else { + currentState } } + selectedPlaceId = place.id } private fun bookPlace() { - viewModelScope.launch(Dispatchers.IO) { - try { - // вызов API для бронирования - // временная имитация успеха - _actionFlow.emit(BookAction.BookSuccess) - } catch (e: Exception) { - _uiState.update { currentState -> - when (currentState) { - is BookState.Data -> currentState.copy( - isError = true, - errorMessage = "Ошибка бронирования" - ) - else -> currentState + /* + selectedPlaceId?.let { placeId -> + viewModelScope.launch(Dispatchers.IO) { + createBookingUseCase(placeId).fold( + onSuccess = { + _actionFlow.emit(BookAction.BookSuccess) + }, + onFailure = { error -> + error.printStackTrace() + _actionFlow.emit(BookAction.ShowError(error.message ?: "Ошибка бронирования")) } - } - _actionFlow.emit(BookAction.ShowError("Ошибка бронирования")) + ) } - } + }*/ } private fun refresh() { loadBookData() } -} \ No newline at end of file +}