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 import kotlinx.serialization.Serializable
@Serializable @Serializable
data class UserData( data class UserInfo(
val name: String, val name: String,
val photoUrl: String, val photoUrl: String,
val booking: Map<String, Booking> val booking: Map<String, Booking>

View File

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

View File

@@ -1,6 +1,5 @@
package ru.myitschool.work.data.source package ru.myitschool.work.data.source
import android.util.Log
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.call.body import io.ktor.client.call.body
import io.ktor.client.engine.cio.CIO import io.ktor.client.engine.cio.CIO
@@ -13,8 +12,8 @@ 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.Booking import ru.myitschool.work.data.models.BookingInfo
import ru.myitschool.work.data.models.UserData import ru.myitschool.work.data.models.UserInfo
object NetworkDataSource { object NetworkDataSource {
private val client by lazy { private val client by lazy {
@@ -44,11 +43,21 @@ object NetworkDataSource {
private fun getUrl(code: String, targetUrl: String) = "${Constants.HOST}/api/$code$targetUrl" 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 { return@withContext runCatching {
val response = client.get(getUrl(code, Constants.INFO_URL)) val response = client.get(getUrl(code, Constants.INFO_URL))
when (response.status) { 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()) 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 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 import ru.myitschool.work.data.repo.MainRepository
class Fetch( class Fetch(
private val repository: MainRepository private val repository: MainRepository
) { ) {
suspend operator fun invoke(): Result<UserData> { suspend operator fun invoke(): Result<UserInfo> {
return repository.fetch().mapCatching { success -> success } 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.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import ru.myitschool.work.core.TestIds import ru.myitschool.work.core.TestIds
import ru.myitschool.work.data.models.Booking
import ru.myitschool.work.ui.nav.MainScreenDestination import ru.myitschool.work.ui.nav.MainScreenDestination
@Composable @Composable
@@ -49,7 +50,8 @@ fun BookScreen(
viewModel: BookViewModel = viewModel() viewModel: BookViewModel = viewModel()
) { ) {
val state by viewModel.uiState.collectAsState() 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) } var selectedTabIndex by remember { mutableStateOf(0) }
Box( Box(
@@ -73,7 +75,7 @@ fun BookScreen(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Text( Text(
text = "TEST_ERROR", text = err,
modifier = Modifier.testTag(TestIds.Book.ERROR), modifier = Modifier.testTag(TestIds.Book.ERROR),
color = MaterialTheme.colorScheme.error color = MaterialTheme.colorScheme.error
) )
@@ -100,13 +102,13 @@ fun BookScreen(
containerColor = MaterialTheme.colorScheme.surface, containerColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.primary, contentColor = MaterialTheme.colorScheme.primary,
) { ) {
dates.forEachIndexed { index, date -> info!!.entries.toList().forEachIndexed { index, entry ->
Tab( Tab(
selected = selectedTabIndex == index, selected = selectedTabIndex == index,
onClick = { selectedTabIndex = index }, onClick = { selectedTabIndex = index },
text = { text = {
Text( Text(
text = date, text = entry.key,
modifier = Modifier.testTag(TestIds.Book.ITEM_DATE) modifier = Modifier.testTag(TestIds.Book.ITEM_DATE)
) )
}, },
@@ -115,13 +117,10 @@ fun BookScreen(
} }
} }
val selectedDate = dates[selectedTabIndex]
val bookings = bookingsByDate[selectedDate] ?: emptyList()
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) { ) {
itemsIndexed(bookings) { index, booking -> itemsIndexed(info!!.entries.toList()[selectedTabIndex].value) { index, booking ->
Booking(booking, index) Booking(booking, index)
} }
} }
@@ -169,6 +168,7 @@ fun BookScreen(
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.onIntent(BookIntent.Fetch)
viewModel.actionFlow.collect { viewModel.actionFlow.collect {
navController.navigate(MainScreenDestination) navController.navigate(MainScreenDestination)
} }
@@ -208,14 +208,4 @@ private fun Booking(booking: Booking, index: Int){
thickness = 1.dp, thickness = 1.dp,
modifier = Modifier.padding(start = 16.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"))
)

View File

@@ -1,5 +1,6 @@
package ru.myitschool.work.ui.screen.book package ru.myitschool.work.ui.screen.book
import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
@@ -7,17 +8,44 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
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.repo.BookRepository
import ru.myitschool.work.domain.book.Fetch
import ru.myitschool.work.ui.screen.main.MainState
class BookViewModel(): ViewModel() { 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() val uiState: StateFlow<BookState> = _uiState.asStateFlow()
private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow() private val _actionFlow: MutableSharedFlow<Unit> = MutableSharedFlow()
val actionFlow: SharedFlow<Unit> = _actionFlow 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) { fun onIntent(intent: BookIntent) {
when(intent) { 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.Book -> Unit
is BookIntent.GoBack -> viewModelScope.launch { is BookIntent.GoBack -> viewModelScope.launch {
_actionFlow.emit(Unit) _actionFlow.emit(Unit)

View File

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