main #6
@@ -38,7 +38,7 @@ fun AppNavHost(
|
|||||||
composable<BookScreenDestination> {
|
composable<BookScreenDestination> {
|
||||||
BookScreen(
|
BookScreen(
|
||||||
onBack = { navController.popBackStack() },
|
onBack = { navController.popBackStack() },
|
||||||
onBookingSuccess = {
|
onBookSuccess = {
|
||||||
// Возвращаемся на главный экран и обновляем его
|
// Возвращаемся на главный экран и обновляем его
|
||||||
navController.popBackStack()
|
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
|
package ru.myitschool.work.ui.screen.book
|
||||||
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.foundation.layout.*
|
||||||
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.selection.selectable
|
import androidx.compose.foundation.selection.selectable
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.material3.RadioButton
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.material3.ScrollableTabRow
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.material3.Tab
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.testTag
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
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 androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import ru.myitschool.work.core.TestIds
|
import ru.myitschool.work.core.TestIds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookingScreen(
|
fun BookScreen(
|
||||||
uiState: BookingState, // состояние интерфейса
|
onBack: () -> Unit,
|
||||||
onSelectDate: (LocalDate) -> Unit, // callback при выборе даты
|
onBookSuccess: () -> Unit
|
||||||
onSelectPlace: (String) -> Unit, // callback при выборе места
|
) {
|
||||||
onBook: () -> Unit, // callback при бронировании
|
val viewModel: BookViewModel = viewModel()
|
||||||
onBack: () -> Unit, // callback при нажатии "Назад"
|
val uiState by viewModel.uiState.collectAsState()
|
||||||
onRefresh: () -> Unit // callback при обновлении
|
|
||||||
|
// Обработка действий
|
||||||
|
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()
|
val sortedDates = uiState.dates.sorted()
|
||||||
@@ -45,7 +80,7 @@ fun BookingScreen(
|
|||||||
if (availableDates.isNotEmpty()) {
|
if (availableDates.isNotEmpty()) {
|
||||||
ScrollableTabRow(
|
ScrollableTabRow(
|
||||||
selectedTabIndex = availableDates.indexOf(uiState.selectedDate),
|
selectedTabIndex = availableDates.indexOf(uiState.selectedDate),
|
||||||
) {
|
) {
|
||||||
availableDates.forEachIndexed { index, date ->
|
availableDates.forEachIndexed { index, date ->
|
||||||
Tab(
|
Tab(
|
||||||
selected = date == uiState.selectedDate,
|
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() }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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