First request #5
@@ -35,6 +35,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.compose.material3:material3:1.4.0")
|
||||
defaultComposeLibrary()
|
||||
implementation("androidx.datastore:datastore-preferences:1.1.7")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-collections-immutable:0.4.0")
|
||||
|
||||
@@ -23,6 +23,7 @@ import ru.myitschool.work.ui.nav.SplashScreenDestination
|
||||
import ru.myitschool.work.ui.root.RootState
|
||||
import ru.myitschool.work.ui.screen.auth.AuthIntent
|
||||
import ru.myitschool.work.ui.screen.auth.AuthScreen
|
||||
import ru.myitschool.work.ui.screen.book.BookScreen
|
||||
import ru.myitschool.work.ui.screen.main.MainScreen
|
||||
import ru.myitschool.work.ui.screen.splash.SplashScreen
|
||||
|
||||
@@ -38,8 +39,8 @@ fun AppNavHost(
|
||||
|
||||
NavHost(
|
||||
modifier = modifier,
|
||||
enterTransition = { EnterTransition.None },
|
||||
exitTransition = { ExitTransition.None },
|
||||
// enterTransition = { EnterTransition.None },
|
||||
// exitTransition = { ExitTransition.None },
|
||||
navController = navController,
|
||||
startDestination = startDestination,
|
||||
) {
|
||||
@@ -53,11 +54,7 @@ fun AppNavHost(
|
||||
MainScreen(navController = navController)
|
||||
}
|
||||
composable<BookScreenDestination> {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(text = "BOOK")
|
||||
}
|
||||
BookScreen(navController = navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,5 @@ package ru.myitschool.work.ui.screen.auth
|
||||
sealed interface AuthState {
|
||||
object Loading: AuthState
|
||||
object Data: AuthState
|
||||
|
||||
object Error: AuthState
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package ru.myitschool.work.ui.screen.book
|
||||
|
||||
sealed interface BookIntent {
|
||||
data object Fetch: BookIntent
|
||||
data object Book: BookIntent
|
||||
data object GoBack: BookIntent
|
||||
}
|
||||
@@ -0,0 +1,221 @@
|
||||
package ru.myitschool.work.ui.screen.book
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBackIosNew
|
||||
import androidx.compose.material.icons.filled.BookmarkAdd
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.PrimaryScrollableTabRow
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import ru.myitschool.work.core.TestIds
|
||||
import ru.myitschool.work.ui.nav.MainScreenDestination
|
||||
|
||||
@Composable
|
||||
fun BookScreen(
|
||||
navController: NavController,
|
||||
viewModel: BookViewModel = viewModel()
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsState()
|
||||
val dates = remember { bookingsByDate.keys.sorted() }
|
||||
var selectedTabIndex by remember { mutableStateOf(0) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
){
|
||||
when(val currentState = state) {
|
||||
is BookState.Loading -> {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
is BookState.Error -> {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Text(
|
||||
text = "TEST_ERROR",
|
||||
modifier = Modifier.testTag(TestIds.Book.ERROR),
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
|
||||
FloatingActionButton(
|
||||
onClick = { viewModel.onIntent(BookIntent.Fetch) },
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.offset(x = -16.dp, y = -16.dp)
|
||||
.testTag(TestIds.Book.REFRESH_BUTTON)
|
||||
) {
|
||||
Icon(Icons.Default.Refresh, contentDescription = "Обновить")
|
||||
}
|
||||
}
|
||||
|
||||
is BookState.DataPresent -> {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
PrimaryScrollableTabRow(
|
||||
selectedTabIndex = selectedTabIndex,
|
||||
edgePadding = 16.dp,
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
contentColor = MaterialTheme.colorScheme.primary,
|
||||
) {
|
||||
dates.forEachIndexed { index, date ->
|
||||
Tab(
|
||||
selected = selectedTabIndex == index,
|
||||
onClick = { selectedTabIndex = index },
|
||||
text = {
|
||||
Text(
|
||||
text = date,
|
||||
modifier = Modifier.testTag(TestIds.Book.ITEM_DATE)
|
||||
)
|
||||
},
|
||||
modifier = Modifier.testTag(TestIds.Book.getIdDateItemByPosition(index))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val selectedDate = dates[selectedTabIndex]
|
||||
val bookings = bookingsByDate[selectedDate] ?: emptyList()
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
itemsIndexed(bookings) { index, booking ->
|
||||
Booking(booking, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = { viewModel.onIntent(BookIntent.Book) },
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
text = {
|
||||
Text("Бронировать")
|
||||
},
|
||||
icon = {
|
||||
Icon(Icons.Default.BookmarkAdd, contentDescription = "Бронировать")
|
||||
},
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.offset(x = -16.dp, y = -16.dp)
|
||||
.testTag(TestIds.Book.BOOK_BUTTON)
|
||||
)
|
||||
}
|
||||
|
||||
is BookState.DataAbsent -> {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Text(text = "Всё забронировано", modifier = Modifier.testTag(TestIds.Book.EMPTY))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FloatingActionButton(
|
||||
onClick = { viewModel.onIntent(BookIntent.GoBack) },
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.offset(x = 16.dp, y = -16.dp)
|
||||
.testTag(TestIds.Book.BACK_BUTTON)
|
||||
) {
|
||||
Icon(Icons.Default.ArrowBackIosNew, contentDescription = "Назад")
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.actionFlow.collect {
|
||||
navController.navigate(MainScreenDestination)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Booking(booking: Booking, index: Int){
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
// .clickable { }
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
.testTag(TestIds.Book.getIdPlaceItemByPosition(index)),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
RadioButton(
|
||||
selected = false,
|
||||
onClick = {},
|
||||
modifier = Modifier
|
||||
.testTag(TestIds.Book.ITEM_PLACE_SELECTOR)
|
||||
// .selectable(
|
||||
// selected = false,
|
||||
// onClick = {}
|
||||
// )
|
||||
)
|
||||
Text(
|
||||
text = booking.place,
|
||||
style = MaterialTheme.typography.titleMedium,
|
||||
fontWeight = FontWeight.Medium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
modifier = Modifier.testTag(TestIds.Book.ITEM_PLACE_TEXT)
|
||||
)
|
||||
}
|
||||
HorizontalDivider(
|
||||
color = MaterialTheme.colorScheme.outlineVariant,
|
||||
thickness = 1.dp,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
data class Booking(val id: Int, val place: String)
|
||||
typealias BookingsByDate = Map<String, List<Booking>>
|
||||
|
||||
val bookingsByDate: BookingsByDate = mapOf(
|
||||
"2025-01-05" to listOf(Booking(1, "102"), Booking(2, "209.13")),
|
||||
"2025-01-06" to listOf(Booking(3, "Зона 51. 50")),
|
||||
"2025-01-07" to listOf(Booking(1, "102"), Booking(2, "209.13")),
|
||||
"2025-01-08" to listOf(Booking(2, "209.13"))
|
||||
)
|
||||
@@ -0,0 +1,8 @@
|
||||
package ru.myitschool.work.ui.screen.book
|
||||
|
||||
sealed interface BookState {
|
||||
data object Loading: BookState
|
||||
data object DataPresent: BookState
|
||||
data object DataAbsent: BookState
|
||||
data object Error: BookState
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package ru.myitschool.work.ui.screen.book
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.launch
|
||||
|
||||
class BookViewModel(): ViewModel() {
|
||||
private val _uiState = MutableStateFlow<BookState>(BookState.DataPresent)
|
||||
val uiState: StateFlow<BookState> = _uiState.asStateFlow()
|
||||
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
|
||||
val actionFlow: SharedFlow<Unit> = _actionFlow
|
||||
|
||||
fun onIntent(intent: BookIntent) {
|
||||
when(intent) {
|
||||
is BookIntent.Fetch -> Unit
|
||||
is BookIntent.Book -> Unit
|
||||
is BookIntent.GoBack -> viewModelScope.launch {
|
||||
_actionFlow.emit(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,5 @@ package ru.myitschool.work.ui.screen.main
|
||||
sealed interface MainIntent {
|
||||
data object Fetch: MainIntent
|
||||
data object Logout: MainIntent
|
||||
data object NewBooking: MainIntent
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Logout
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.BookmarkBorder
|
||||
import androidx.compose.material.icons.filled.Bookmarks
|
||||
import androidx.compose.material.icons.filled.ChevronRight
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
@@ -49,6 +50,8 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import coil3.compose.rememberAsyncImagePainter
|
||||
import ru.myitschool.work.core.TestIds
|
||||
import ru.myitschool.work.ui.nav.BookScreenDestination
|
||||
import ru.myitschool.work.ui.nav.MainScreenDestination
|
||||
|
||||
@Composable
|
||||
fun MainScreen(
|
||||
@@ -82,7 +85,26 @@ fun MainScreen(
|
||||
|
||||
when (val currentState = state) {
|
||||
is MainState.Error -> {
|
||||
Text("TEST_ERROR", modifier = Modifier.testTag(TestIds.Main.ERROR))
|
||||
Text(
|
||||
text = "TEST_ERROR",
|
||||
modifier = Modifier.testTag(TestIds.Main.ERROR),
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
IconButton(
|
||||
onClick = { viewModel.onIntent(MainIntent.Fetch) },
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.aspectRatio(1f)
|
||||
.testTag(TestIds.Main.REFRESH_BUTTON),
|
||||
enabled = true,
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Refresh,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is MainState.Loading -> {
|
||||
@@ -176,7 +198,6 @@ fun MainScreen(
|
||||
Icon(
|
||||
imageVector = Icons.Default.BookmarkBorder,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondaryFixed
|
||||
)
|
||||
Text(
|
||||
text = "Бронирования",
|
||||
@@ -213,7 +234,7 @@ fun MainScreen(
|
||||
}
|
||||
|
||||
FloatingActionButton(
|
||||
onClick = { },
|
||||
onClick = { viewModel.onIntent(MainIntent.NewBooking) },
|
||||
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
modifier = Modifier
|
||||
@@ -223,7 +244,6 @@ fun MainScreen(
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = "Добавить")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,6 +251,9 @@ fun MainScreen(
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.onIntent(MainIntent.Fetch)
|
||||
viewModel.actionFlow.collect {
|
||||
navController.navigate(BookScreenDestination)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package ru.myitschool.work.ui.screen.main
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.launch
|
||||
@@ -13,6 +15,8 @@ class MainViewModel(): ViewModel() {
|
||||
private val logout by lazy { Logout(AuthRepository) }
|
||||
private val _uiState = MutableStateFlow<MainState>(MainState.Data)
|
||||
val uiState: StateFlow<MainState> = _uiState.asStateFlow()
|
||||
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
|
||||
val actionFlow: SharedFlow<Unit> = _actionFlow
|
||||
|
||||
fun onIntent(intent: MainIntent) {
|
||||
when (intent) {
|
||||
@@ -22,6 +26,11 @@ class MainViewModel(): ViewModel() {
|
||||
logout.invoke()
|
||||
}
|
||||
}
|
||||
is MainIntent.NewBooking -> {
|
||||
viewModelScope.launch {
|
||||
_actionFlow.emit(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user