Finished book push functions

This commit is contained in:
2025-12-03 21:21:52 +03:00
parent 7adb05efb9
commit c53bd718c0
8 changed files with 89 additions and 18 deletions

View File

@@ -0,0 +1,9 @@
package ru.myitschool.work.data.models
import kotlinx.serialization.Serializable
@Serializable
data class BookBody (
val date: String,
val placeId: Int
)

View File

@@ -6,6 +6,10 @@ import ru.myitschool.work.data.source.NetworkDataSource
object BookRepository { object BookRepository {
suspend fun fetch(): Result<BookingInfo> { suspend fun fetch(): Result<BookingInfo> {
return NetworkDataSource.book(getCode()).onSuccess { data -> data } return NetworkDataSource.bookInfo(getCode()).onSuccess { data -> data }
}
suspend fun book(date: String, placeId: Int): Result<Boolean> {
return NetworkDataSource.book(getCode(), date, placeId).onSuccess { success -> success }
} }
} }

View File

@@ -27,5 +27,5 @@ object LocalDataSource {
appContext.dataStore.edit { it[Keys.CODE] = code } appContext.dataStore.edit { it[Keys.CODE] = code }
} }
val isCodePresentFlow: Flow<Boolean> = appContext.dataStore.data.map { it[Keys.CODE] != "" } val isCodePresentFlow: Flow<Boolean> = appContext.dataStore.data.map { it[Keys.CODE] != "" || it[Keys.CODE] != null }
} }

View File

@@ -5,13 +5,18 @@ import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO import io.ktor.client.engine.cio.CIO
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.request.post
import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode import io.ktor.http.HttpStatusCode
import io.ktor.http.contentType
import io.ktor.serialization.kotlinx.json.json import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import ru.myitschool.work.core.Constants import ru.myitschool.work.core.Constants
import ru.myitschool.work.data.models.BookBody
import ru.myitschool.work.data.models.BookingInfo import ru.myitschool.work.data.models.BookingInfo
import ru.myitschool.work.data.models.UserInfo import ru.myitschool.work.data.models.UserInfo
@@ -41,8 +46,6 @@ object NetworkDataSource {
} }
} }
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl"
suspend fun info(code: String): Result<UserInfo> = withContext(Dispatchers.IO) { suspend fun info(code: String): Result<UserInfo> = withContext(Dispatchers.IO) {
return@withContext runCatching { return@withContext runCatching {
val response = client.get(getUrl(code, Constants.INFO_URL)) val response = client.get(getUrl(code, Constants.INFO_URL))
@@ -53,7 +56,7 @@ object NetworkDataSource {
} }
} }
suspend fun book(code: String): Result<BookingInfo> = withContext(Dispatchers.IO) { suspend fun bookInfo(code: String): Result<BookingInfo> = withContext(Dispatchers.IO) {
return@withContext runCatching { return@withContext runCatching {
val response = client.get(getUrl(code, Constants.BOOKING_URL)) val response = client.get(getUrl(code, Constants.BOOKING_URL))
when (response.status) { when (response.status) {
@@ -62,4 +65,20 @@ object NetworkDataSource {
} }
} }
} }
suspend fun book(code: String, date: String, placeId: Int): Result<Boolean> = withContext(Dispatchers.IO) {
return@withContext runCatching {
// val requestBodyString = Json.encodeToString(BookBody(date, placeId))
val response = client.post((getUrl(code, Constants.BOOK_URL))) {
contentType(ContentType.Application.Json)
setBody(BookBody(date, placeId))
}
when(response.status) {
HttpStatusCode.OK -> true
else -> error(response.bodyAsText())
}
}
}
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl"
} }

View File

@@ -1,4 +1,16 @@
package ru.myitschool.work.domain.book package ru.myitschool.work.domain.book
class Book { import ru.myitschool.work.data.repo.BookRepository
class Book (
private val repository: BookRepository
) {
suspend operator fun invoke(
date: String,
placeId: Int
): Result<Unit> {
return repository.book(date, placeId).mapCatching { success ->
if (!success) error("Code is incorrect")
}
}
} }

View File

@@ -2,6 +2,6 @@ package ru.myitschool.work.ui.screen.book
sealed interface BookIntent { sealed interface BookIntent {
data object Fetch: BookIntent data object Fetch: BookIntent
data object Book: BookIntent data class Book(val date: String, val placeId: Int): BookIntent
data object GoBack: BookIntent data object GoBack: BookIntent
} }

View File

@@ -53,6 +53,7 @@ fun BookScreen(
val info by viewModel.infoFlow.collectAsState() val info by viewModel.infoFlow.collectAsState()
val err by viewModel.errorFlow.collectAsState() val err by viewModel.errorFlow.collectAsState()
var selectedTabIndex by remember { mutableStateOf(0) } var selectedTabIndex by remember { mutableStateOf(0) }
var selectedPlaceIndex by remember { mutableStateOf(0) }
Box( Box(
modifier = Modifier modifier = Modifier
@@ -95,17 +96,22 @@ fun BookScreen(
} }
is BookState.DataPresent -> { is BookState.DataPresent -> {
val entriesList = info!!.entries.toList()
Column(modifier = Modifier.fillMaxSize()) { Column(modifier = Modifier.fillMaxSize()) {
PrimaryScrollableTabRow( PrimaryScrollableTabRow(
selectedTabIndex = selectedTabIndex, selectedTabIndex = selectedTabIndex,
edgePadding = 16.dp, edgePadding = 16.dp,
containerColor = MaterialTheme.colorScheme.surface, containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.primary,
) { ) {
info!!.entries.toList().forEachIndexed { index, entry -> entriesList.forEachIndexed { index, entry ->
Tab( Tab(
selected = selectedTabIndex == index, selected = selectedTabIndex == index,
onClick = { selectedTabIndex = index }, onClick = {
selectedTabIndex = index
selectedPlaceIndex = 0
},
text = { text = {
Text( Text(
text = entry.key, text = entry.key,
@@ -120,14 +126,22 @@ fun BookScreen(
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) { ) {
itemsIndexed(info!!.entries.toList()[selectedTabIndex].value) { index, booking -> itemsIndexed(entriesList[selectedTabIndex].value) { index, booking ->
Booking(booking, index) Booking(
booking = booking,
index = index,
selected = selectedPlaceIndex,
onRadioChange = { selectedPlaceIndex = index }
)
} }
} }
} }
ExtendedFloatingActionButton( ExtendedFloatingActionButton(
onClick = { viewModel.onIntent(BookIntent.Book) }, onClick = { viewModel.onIntent(BookIntent.Book(
date = entriesList[selectedTabIndex].key,
placeId = entriesList[selectedTabIndex].value[selectedPlaceIndex].id
)) },
containerColor = MaterialTheme.colorScheme.secondaryContainer, containerColor = MaterialTheme.colorScheme.secondaryContainer,
contentColor = MaterialTheme.colorScheme.onSecondaryContainer, contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
text = { text = {
@@ -170,13 +184,13 @@ fun BookScreen(
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.onIntent(BookIntent.Fetch) viewModel.onIntent(BookIntent.Fetch)
viewModel.actionFlow.collect { viewModel.actionFlow.collect {
navController.navigate(MainScreenDestination) navController.popBackStack()
} }
} }
} }
@Composable @Composable
private fun Booking(booking: Booking, index: Int){ private fun Booking(booking: Booking, index: Int, selected: Int, onRadioChange: Function0<Unit>) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -186,8 +200,8 @@ private fun Booking(booking: Booking, index: Int){
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
RadioButton( RadioButton(
selected = false, selected = selected == index,
onClick = {}, onClick = onRadioChange,
modifier = Modifier modifier = Modifier
.testTag(TestIds.Book.ITEM_PLACE_SELECTOR) .testTag(TestIds.Book.ITEM_PLACE_SELECTOR)
// .selectable( // .selectable(

View File

@@ -12,11 +12,12 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ru.myitschool.work.data.models.BookingInfo import ru.myitschool.work.data.models.BookingInfo
import ru.myitschool.work.data.repo.BookRepository import ru.myitschool.work.data.repo.BookRepository
import ru.myitschool.work.domain.book.Book
import ru.myitschool.work.domain.book.Fetch import ru.myitschool.work.domain.book.Fetch
import ru.myitschool.work.ui.screen.main.MainState
class BookViewModel(): ViewModel() { class BookViewModel(): ViewModel() {
private val fetch by lazy { Fetch(BookRepository) } private val fetch by lazy { Fetch(BookRepository) }
private val book by lazy { Book(BookRepository) }
private val _uiState = MutableStateFlow<BookState>(BookState.Loading) private val _uiState = MutableStateFlow<BookState>(BookState.Loading)
val uiState: StateFlow<BookState> = _uiState.asStateFlow() val uiState: StateFlow<BookState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow() private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
@@ -46,7 +47,19 @@ class BookViewModel(): ViewModel() {
} }
) )
} }
is BookIntent.Book -> Unit is BookIntent.Book -> viewModelScope.launch {
_uiState.update { BookState.Loading }
book.invoke(intent.date, intent.placeId).fold(
onSuccess = { success ->
_actionFlow.emit(Unit)
},
onFailure = { failure ->
Log.d(failure.message, "failure")
_uiState.update { BookState.Error }
_errorFlow.update { failure.message.toString() }
}
)
}
is BookIntent.GoBack -> viewModelScope.launch { is BookIntent.GoBack -> viewModelScope.launch {
_actionFlow.emit(Unit) _actionFlow.emit(Unit)
} }