main #6
@@ -38,7 +38,7 @@ fun AppNavHost(
|
||||
composable<BookScreenDestination> {
|
||||
BookScreen(
|
||||
onBack = { navController.popBackStack() },
|
||||
onBookingSuccess = {
|
||||
onBookSuccess = {
|
||||
// Возвращаемся на главный экран и обновляем его
|
||||
navController.popBackStack()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package ru.myitschool.work.ui.screen.book
|
||||
|
||||
sealed interface BookAction {
|
||||
data class ShowError(val message: String?) : BookAction
|
||||
object BookSuccess : BookAction
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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() }
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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 // сообщение об ошибке
|
||||
)
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user