Book page fetch implemented

This commit is contained in:
2025-12-01 22:47:22 +03:00
parent 3e404ed765
commit 7adb05efb9
11 changed files with 89 additions and 39 deletions

View File

@@ -0,0 +1,3 @@
package ru.myitschool.work.data.models
typealias BookingInfo = Map<String, List<Booking>>

View File

@@ -3,7 +3,7 @@ package ru.myitschool.work.data.models
import kotlinx.serialization.Serializable
@Serializable
data class UserData(
data class UserInfo(
val name: String,
val photoUrl: String,
val booking: Map<String, Booking>

View File

@@ -1,4 +1,11 @@
package ru.myitschool.work.data.repo
import ru.myitschool.work.data.models.BookingInfo
import ru.myitschool.work.data.repo.AuthRepository.getCode
import ru.myitschool.work.data.source.NetworkDataSource
object BookRepository {
suspend fun fetch(): Result<BookingInfo> {
return NetworkDataSource.book(getCode()).onSuccess { data -> data }
}
}

View File

@@ -1,14 +1,11 @@
package ru.myitschool.work.data.repo
import android.util.Log
import ru.myitschool.work.data.models.Booking
import ru.myitschool.work.data.models.UserData
import ru.myitschool.work.data.models.UserInfo
import ru.myitschool.work.data.repo.AuthRepository.getCode
import ru.myitschool.work.data.source.LocalDataSource
import ru.myitschool.work.data.source.NetworkDataSource
object MainRepository {
suspend fun fetch(): Result<UserData> {
return NetworkDataSource.Info(getCode()).onSuccess { data -> data.name }
suspend fun fetch(): Result<UserInfo> {
return NetworkDataSource.info(getCode()).onSuccess { data -> data }
}
}

View File

@@ -1,6 +1,5 @@
package ru.myitschool.work.data.source
import android.util.Log
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO
@@ -13,8 +12,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import ru.myitschool.work.core.Constants
import ru.myitschool.work.data.models.Booking
import ru.myitschool.work.data.models.UserData
import ru.myitschool.work.data.models.BookingInfo
import ru.myitschool.work.data.models.UserInfo
object NetworkDataSource {
private val client by lazy {
@@ -44,11 +43,21 @@ object NetworkDataSource {
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl"
suspend fun Info(code: String): Result<UserData> = withContext(Dispatchers.IO) {
suspend fun info(code: String): Result<UserInfo> = withContext(Dispatchers.IO) {
return@withContext runCatching {
val response = client.get(getUrl(code, Constants.INFO_URL))
when (response.status) {
HttpStatusCode.OK -> Json.decodeFromString(response.body())
HttpStatusCode.OK -> Json.decodeFromString<UserInfo>(response.body())
else -> error(response.bodyAsText())
}
}
}
suspend fun book(code: String): Result<BookingInfo> = withContext(Dispatchers.IO) {
return@withContext runCatching {
val response = client.get(getUrl(code, Constants.BOOKING_URL))
when (response.status) {
HttpStatusCode.OK -> Json.decodeFromString<BookingInfo>(response.body())
else -> error(response.bodyAsText())
}
}

View File

@@ -0,0 +1,4 @@
package ru.myitschool.work.domain.book
class Book {
}

View File

@@ -0,0 +1,12 @@
package ru.myitschool.work.domain.book
import ru.myitschool.work.data.models.BookingInfo
import ru.myitschool.work.data.repo.BookRepository
class Fetch (
private val repository: BookRepository
) {
suspend operator fun invoke(): Result<BookingInfo> {
return repository.fetch().mapCatching { success -> success.filter { it.value.isNotEmpty() } as BookingInfo }
}
}

View File

@@ -1,12 +1,12 @@
package ru.myitschool.work.domain.main
import ru.myitschool.work.data.models.UserData
import ru.myitschool.work.data.models.UserInfo
import ru.myitschool.work.data.repo.MainRepository
class Fetch(
private val repository: MainRepository
) {
suspend operator fun invoke(): Result<UserData> {
suspend operator fun invoke(): Result<UserInfo> {
return repository.fetch().mapCatching { success -> success }
}
}

View File

@@ -41,6 +41,7 @@ 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.data.models.Booking
import ru.myitschool.work.ui.nav.MainScreenDestination
@Composable
@@ -49,7 +50,8 @@ fun BookScreen(
viewModel: BookViewModel = viewModel()
) {
val state by viewModel.uiState.collectAsState()
val dates = remember { bookingsByDate.keys.sorted() }
val info by viewModel.infoFlow.collectAsState()
val err by viewModel.errorFlow.collectAsState()
var selectedTabIndex by remember { mutableStateOf(0) }
Box(
@@ -73,7 +75,7 @@ fun BookScreen(
modifier = Modifier.fillMaxSize()
) {
Text(
text = "TEST_ERROR",
text = err,
modifier = Modifier.testTag(TestIds.Book.ERROR),
color = MaterialTheme.colorScheme.error
)
@@ -100,13 +102,13 @@ fun BookScreen(
containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.primary,
) {
dates.forEachIndexed { index, date ->
info!!.entries.toList().forEachIndexed { index, entry ->
Tab(
selected = selectedTabIndex == index,
onClick = { selectedTabIndex = index },
text = {
Text(
text = date,
text = entry.key,
modifier = Modifier.testTag(TestIds.Book.ITEM_DATE)
)
},
@@ -115,13 +117,10 @@ fun BookScreen(
}
}
val selectedDate = dates[selectedTabIndex]
val bookings = bookingsByDate[selectedDate] ?: emptyList()
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
itemsIndexed(bookings) { index, booking ->
itemsIndexed(info!!.entries.toList()[selectedTabIndex].value) { index, booking ->
Booking(booking, index)
}
}
@@ -169,6 +168,7 @@ fun BookScreen(
}
LaunchedEffect(Unit) {
viewModel.onIntent(BookIntent.Fetch)
viewModel.actionFlow.collect {
navController.navigate(MainScreenDestination)
}
@@ -209,13 +209,3 @@ private fun Booking(booking: Booking, index: Int){
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"))
)

View File

@@ -1,5 +1,6 @@
package ru.myitschool.work.ui.screen.book
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -7,17 +8,44 @@ 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 ru.myitschool.work.data.models.BookingInfo
import ru.myitschool.work.data.repo.BookRepository
import ru.myitschool.work.domain.book.Fetch
import ru.myitschool.work.ui.screen.main.MainState
class BookViewModel(): ViewModel() {
private val _uiState = MutableStateFlow<BookState>(BookState.DataPresent)
private val fetch by lazy { Fetch(BookRepository) }
private val _uiState = MutableStateFlow<BookState>(BookState.Loading)
val uiState: StateFlow<BookState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
val actionFlow: SharedFlow<Unit> = _actionFlow
private val _infoFlow: MutableStateFlow<BookingInfo?> = MutableStateFlow(null)
val infoFlow: StateFlow<BookingInfo?> = _infoFlow.asStateFlow()
private val _errorFlow = MutableStateFlow<String>("")
val errorFlow: StateFlow<String> = _errorFlow.asStateFlow()
fun onIntent(intent: BookIntent) {
when(intent) {
is BookIntent.Fetch -> Unit
is BookIntent.Fetch -> viewModelScope.launch {
_uiState.update { BookState.Loading }
fetch.invoke().fold(
onSuccess = { success ->
if (success.isEmpty()) {
_uiState.update { BookState.DataAbsent }
} else {
_infoFlow.update { success }
_uiState.update { BookState.DataPresent }
}
},
onFailure = { failure ->
Log.d(failure.message, "failure")
_uiState.update { BookState.Error }
_errorFlow.update { failure.message.toString() }
}
)
}
is BookIntent.Book -> Unit
is BookIntent.GoBack -> viewModelScope.launch {
_actionFlow.emit(Unit)

View File

@@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import ru.myitschool.work.data.models.UserData
import ru.myitschool.work.data.models.UserInfo
import ru.myitschool.work.data.repo.AuthRepository
import ru.myitschool.work.data.repo.MainRepository
import ru.myitschool.work.domain.main.Fetch
@@ -23,8 +23,8 @@ class MainViewModel(): ViewModel() {
val uiState: StateFlow<MainState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
val actionFlow: SharedFlow<Unit> = _actionFlow
private val _infoFlow: MutableStateFlow<UserData?> = MutableStateFlow(null)
val infoFlow: StateFlow<UserData?> = _infoFlow.asStateFlow()
private val _infoFlow: MutableStateFlow<UserInfo?> = MutableStateFlow(null)
val infoFlow: StateFlow<UserInfo?> = _infoFlow.asStateFlow()
private val _errorFlow = MutableStateFlow<String>("")
val errorFlow: StateFlow<String> = _errorFlow.asStateFlow()
@@ -38,7 +38,7 @@ class MainViewModel(): ViewModel() {
_uiState.update { MainState.Data }
},
onFailure = { failure ->
Log.d(failure.message, "")
Log.d(failure.message, "failure")
_uiState.update { MainState.Error }
_errorFlow.update { failure.message.toString() }
}