букинг

This commit is contained in:
2025-12-03 17:49:30 +03:00
parent fea1cbeaca
commit 9617ba31c3
8 changed files with 220 additions and 150 deletions

View File

@@ -38,7 +38,7 @@ fun AppNavHost(
composable<BookScreenDestination> {
BookScreen(
onBack = { navController.popBackStack() },
onBookingSuccess = {
onBookSuccess = {
// Возвращаемся на главный экран и обновляем его
navController.popBackStack()
}

View File

@@ -0,0 +1,6 @@
package ru.myitschool.work.ui.screen.book
sealed interface BookAction {
data class ShowError(val message: String?) : BookAction
object BookSuccess : BookAction
}

View File

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

View File

@@ -1,39 +1,74 @@
package ru.myitschool.work.ui.screen.book
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
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
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.selection.selectable
import androidx.compose.material3.Button
import androidx.compose.material3.RadioButton
import androidx.compose.material3.ScrollableTabRow
import androidx.compose.material3.Tab
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.lifecycle.viewmodel.compose.viewModel
import ru.myitschool.work.core.TestIds
@Composable
fun BookingScreen(
uiState: BookingState, // состояние интерфейса
onSelectDate: (LocalDate) -> Unit, // callback при выборе даты
onSelectPlace: (String) -> Unit, // callback при выборе места
onBook: () -> Unit, // callback при бронировании
onBack: () -> Unit, // callback при нажатии "Назад"
onRefresh: () -> Unit // callback при обновлении
fun BookScreen(
onBack: () -> Unit,
onBookSuccess: () -> Unit
) {
val viewModel: BookViewModel = viewModel()
val uiState by viewModel.uiState.collectAsState()
// Обработка действий
val event = viewModel.actionFlow.collectAsState(initial = null)
LaunchedEffect(event.value) {
when (event.value) {
is BookAction.BookSuccess -> {
onBookSuccess()
}
else -> {}
}
}
// Загрузка начальных данных
LaunchedEffect(Unit) {
viewModel.onIntent(BookIntent.LoadData)
}
when (uiState) {
is BookState.Loading -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
}
is BookState.Data -> {
BookContentScreen(
uiState = uiState as BookState.Data,
onSelectDate = { date -> viewModel.onIntent(BookIntent.SelectDate(date)) },
onSelectPlace = { place -> viewModel.onIntent(BookIntent.SelectPlace(place)) },
onBook = { viewModel.onIntent(BookIntent.BookPlace) },
onBack = onBack,
onRefresh = { viewModel.onIntent(BookIntent.Refresh) }
)
}
}
}
@Composable
fun BookContentScreen(
uiState: BookState.Data,
onSelectDate: (LocalDate) -> Unit,
onSelectPlace: (String) -> Unit,
onBook: () -> Unit,
onBack: () -> Unit,
onRefresh: () -> Unit
) {
// Сортировка дат по порядку
val sortedDates = uiState.dates.sorted()
@@ -45,7 +80,7 @@ fun BookingScreen(
if (availableDates.isNotEmpty()) {
ScrollableTabRow(
selectedTabIndex = availableDates.indexOf(uiState.selectedDate),
) {
) {
availableDates.forEachIndexed { index, date ->
Tab(
selected = date == uiState.selectedDate,
@@ -138,27 +173,3 @@ fun BookingScreen(
}
}
}
@Composable
fun BookScreen(
onBack: () -> Unit, // callback при возврате назад
onBookingSuccess: () -> Unit // callback при успешном бронировании
) {
val viewModel: BookingViewModel = BookingViewModel()
val uiState by viewModel.uiState.collectAsState()
BookingScreen(
uiState = uiState,
onSelectDate = { date -> viewModel.selectDate(date) },
onSelectPlace = { place -> viewModel.selectPlace(place) },
onBook = {
viewModel.bookPlace()
onBookingSuccess()
},
onBack = onBack,
onRefresh = { viewModel.refresh() }
)
}

View File

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

View File

@@ -0,0 +1,126 @@
package ru.myitschool.work.ui.screen.book
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import java.time.LocalDate
class BookViewModel : ViewModel() {
private val _uiState = MutableStateFlow<BookState>(BookState.Loading)
val uiState: StateFlow<BookState> = _uiState.asStateFlow()
private val _actionFlow = MutableSharedFlow<BookAction>()
val actionFlow: SharedFlow<BookAction> = _actionFlow
init {
loadBookData()
}
fun onIntent(intent: BookIntent) {
when (intent) {
is BookIntent.LoadData -> loadBookData()
is BookIntent.Refresh -> refresh()
is BookIntent.BookPlace -> bookPlace()
is BookIntent.SelectDate -> selectDate(intent.date)
is BookIntent.SelectPlace -> selectPlace(intent.place)
}
}
private fun loadBookData() {
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
)
}
} 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(
selectedDate = date,
selectedPlace = null
)
else -> currentState
}
}
}
private fun selectPlace(place: String) {
_uiState.update { currentState ->
when (currentState) {
is BookState.Data -> currentState.copy(selectedPlace = place)
else -> currentState
}
}
}
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
}
}
_actionFlow.emit(BookAction.ShowError("Ошибка бронирования"))
}
}
}
private fun refresh() {
loadBookData()
}
}

View File

@@ -1,12 +0,0 @@
package ru.myitschool.work.ui.screen.book
import java.time.LocalDate
data class BookingState(
val dates: List<LocalDate> = emptyList(), // список доступных дат
val places: Map<LocalDate, List<String>> = emptyMap(), // места по датам
val selectedDate: LocalDate? = null, // выбранная дата
val selectedPlace: String? = null, // выбранное место
val isError: Boolean = false, // флаг ошибки
val errorMessage: String? = null // сообщение об ошибке
)

View File

@@ -1,87 +0,0 @@
package ru.myitschool.work.ui.screen.book
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import java.time.LocalDate
class BookingViewModel : ViewModel() {
private val _uiState = MutableStateFlow(BookingState())
val uiState: StateFlow<BookingState> = _uiState.asStateFlow()
init {
loadBookingData()
}
fun loadBookingData() {
viewModelScope.launch {
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.value = _uiState.value.copy(
dates = sortedDates,
places = mockPlaces,
selectedDate = defaultDate,
selectedPlace = null,
isError = false,
errorMessage = null
)
} catch (e: Exception) {
_uiState.value = _uiState.value.copy(
isError = true,
errorMessage = "Ошибка загрузки данных"
)
}
}
}
fun selectDate(date: LocalDate) {
_uiState.value = _uiState.value.copy(
selectedDate = date,
selectedPlace = null
)
}
fun selectPlace(place: String) {
_uiState.value = _uiState.value.copy(
selectedPlace = place
)
}
fun bookPlace() {
viewModelScope.launch {
try {
//вызов API для бронирования
} catch (e: Exception) {
_uiState.value = _uiState.value.copy(
isError = true,
errorMessage = "Ошибка бронирования"
)
}
}
}
fun refresh() {
loadBookingData()
}
}