main #6

Closed
student-20690 wants to merge 20 commits from (deleted):main into main
8 changed files with 220 additions and 150 deletions
Showing only changes of commit 9617ba31c3 - Show all commits

View File

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