Impl base schedule navigation; add internal logger

This commit is contained in:
Shcherbatykh Oleg
2026-02-19 13:40:00 +03:00
parent 592d948cd0
commit 6aa4b6d39d
22 changed files with 180 additions and 72 deletions

View File

@@ -8,7 +8,6 @@ import ru.fincode.tsudesk.core.network.model.NetworkResult
import ru.fincode.tsudesk.core.network.model.map
import ru.fincode.tsudesk.feature.schedule.data.datasource.ScheduleLocalDataSource
import ru.fincode.tsudesk.feature.schedule.data.datasource.ScheduleRemoteDataSource
import ru.fincode.tsudesk.feature.schedule.data.local.ScheduleCacheKey
import ru.fincode.tsudesk.feature.schedule.data.remote.ScheduleNetworkMapper
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleEntity
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleType
@@ -23,9 +22,11 @@ class ScheduleRepositoryImpl @Inject constructor(
override fun observeSchedule(type: ScheduleType): Flow<DataResult<ScheduleEntity>> = flow {
val scheduleKey = type.toCacheKey()
val cached = local.observeSchedule(scheduleKey).first()
cached?.let {
emit(DataResult.Data(it, refreshedFromNetwork = false))
if (cached != null) {
emit(DataResult.Data(cached, refreshedFromNetwork = false))
if ((System.currentTimeMillis() - cached.timestamp) < CACHE_TTL_MS) return@flow
}
val networkResult = loadSchedule(type)
@@ -45,11 +46,12 @@ class ScheduleRepositoryImpl @Inject constructor(
private suspend fun loadSchedule(type: ScheduleType): NetworkResult<ScheduleEntity> =
when (type) {
is ScheduleType.Group ->
remote.loadScheduleByGroup(type.number).map(mapper::invoke)
is ScheduleType.Group -> remote.loadScheduleByGroup(type.number).map(mapper::invoke)
is ScheduleType.Teacher ->
remote.loadScheduleByTeacher(type.name).map(mapper::invoke)
is ScheduleType.Teacher -> remote.loadScheduleByTeacher(type.name).map(mapper::invoke)
}
private companion object {
private const val CACHE_TTL_MS: Long = 1;//24L * 60L * 60L * 1000L // 1 day
}
}

View File

@@ -7,7 +7,7 @@ import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleType
import ru.fincode.tsudesk.feature.schedule.domain.repository.ScheduleRepository
import javax.inject.Inject
class GetScheduleUseCase @Inject constructor(
class ObserveScheduleUseCase @Inject constructor(
private val repository: ScheduleRepository
) {
operator fun invoke(type: ScheduleType): Flow<DataResult<ScheduleEntity>> =

View File

@@ -0,0 +1,11 @@
package ru.fincode.tsudesk.feature.schedule.presentation.model
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleEntity
data class ScheduleUiState(
val isLoading: Boolean = false,
val data: ScheduleEntity? = null,
val errorMessage: String? = null,
val title: String = "Schedule",
val refreshedFromNetwork: Boolean = false
)

View File

@@ -0,0 +1,20 @@
package ru.fincode.tsudesk.feature.schedule.presentation.navigation
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import ru.fincode.tsudesk.core.navigation.AppRoute
import ru.fincode.tsudesk.feature.schedule.presentation.screen.ScheduleRoute
fun NavGraphBuilder.scheduleGraph(
navController: NavHostController
) {
composable<AppRoute.Schedule> {
ScheduleRoute()
}
// composable<AppRoute.ScheduleDetails> { backStackEntry ->
// val args = backStackEntry.toRoute<AppRoute.ScheduleDetails>()
// ScheduleDetailsRoute(lessonId = args.lessonId)
// }
}

View File

@@ -0,0 +1,16 @@
package ru.fincode.tsudesk.feature.schedule.presentation.screen
import androidx.compose.runtime.Composable
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ru.fincode.tsudesk.feature.schedule.presentation.screen.ScheduleViewModel
import ru.fincode.tsudesk.feature.schedule.presentation.ui.ScheduleScreen
@Composable
fun ScheduleRoute(
viewModel: ScheduleViewModel = hiltViewModel()
) {
val state = viewModel.state.collectAsStateWithLifecycle().value
ScheduleScreen(state = state)
}

View File

@@ -2,21 +2,26 @@ package ru.fincode.tsudesk.feature.schedule.presentation.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import ru.fincode.tsudesk.feature.schedule.presentation.model.ScheduleUiState
@Composable
fun ScheduleScreen() {
fun ScheduleScreen(
state: ScheduleUiState
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "Schedule",
color = Color.Black
)
if (state.isLoading) {
CircularProgressIndicator()
} else {
Text(text = state.title, color = Color.Black)
}
}
}

View File

@@ -0,0 +1,50 @@
package ru.fincode.tsudesk.feature.schedule.presentation.screen
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import ru.fincode.tsudesk.core.common.log.logD
import ru.fincode.tsudesk.core.common.model.DataResult
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleType
import ru.fincode.tsudesk.feature.schedule.domain.usecase.ObserveScheduleUseCase
import ru.fincode.tsudesk.feature.schedule.presentation.model.ScheduleUiState
import javax.inject.Inject
@HiltViewModel
class ScheduleViewModel @Inject constructor(
private val observeScheduleUseCase: ObserveScheduleUseCase
) : ViewModel() {
private val scheduleType = ScheduleType.Group(number = "220631")
val state: StateFlow<ScheduleUiState> = observeScheduleUseCase(scheduleType).map { result ->
when (result) {
is DataResult.Data -> {
logD("Data loaded from ${if (result.refreshedFromNetwork) "NETWORK" else "CACHE"}")
ScheduleUiState(
isLoading = false,
data = result.data,
refreshedFromNetwork = result.refreshedFromNetwork
)
}
is DataResult.Error -> {
logD("Error loading schedule: ${result.error}")
ScheduleUiState(isLoading = false, errorMessage = result.error.toString())
}
}
}.onStart {
logD("Start collecting schedule flow")
emit(ScheduleUiState(isLoading = true))
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = ScheduleUiState(isLoading = true)
)
}

View File

@@ -1,21 +0,0 @@
package ru.fincode.tsudesk.feature.schedule.ui.navigation
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import ru.fincode.tsudesk.core.navigation.AppRoute
import ru.fincode.tsudesk.feature.schedule.presentation.ui.ScheduleScreen
fun NavGraphBuilder.scheduleGraph(
navController: NavHostController
) {
composable<AppRoute.Schedule> {
ScheduleScreen()
}
// регистрация экрана детальной информации о занятии
// composable<AppRoute.ScheduleDetails> { backStackEntry ->
// // val args = backStackEntry.toRoute<AppRoute.ScheduleDetails>()
// // ScheduleDetailsRoute(lessonId = args.lessonId)
// }
}

View File

@@ -1,5 +1,5 @@
package ru.fincode.tsudesk.feature.splash.ui
package ru.fincode.tsudesk.feature.splash.presentation.model
sealed interface SplashEffect {
data object OpenMain : SplashEffect
}
}

View File

@@ -0,0 +1,7 @@
package ru.fincode.tsudesk.feature.splash.presentation.model
data class SplashUiState(
val title: String = "SPLASH SCREEN",
val isLoading: Boolean = true,
val errorMessage: String? = null
)

View File

@@ -1,10 +1,10 @@
package ru.fincode.tsudesk.feature.splash.ui.navigation
package ru.fincode.tsudesk.feature.splash.presentation.navigation
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
import ru.fincode.tsudesk.core.navigation.AppRoute
import ru.fincode.tsudesk.feature.splash.ui.SplashRoute
import ru.fincode.tsudesk.feature.splash.presentation.screen.SplashRoute
fun NavGraphBuilder.splashGraph(
navController: NavHostController

View File

@@ -1,9 +1,10 @@
package ru.fincode.tsudesk.feature.splash.ui
package ru.fincode.tsudesk.feature.splash.presentation.screen
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import ru.fincode.tsudesk.feature.splash.presentation.model.SplashEffect
@Composable
fun SplashRoute(

View File

@@ -1,4 +1,4 @@
package ru.fincode.tsudesk.feature.splash.ui
package ru.fincode.tsudesk.feature.splash.presentation.screen
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
@@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import ru.fincode.tsudesk.feature.splash.presentation.model.SplashUiState
@Composable
fun SplashScreen(

View File

@@ -1,4 +1,4 @@
package ru.fincode.tsudesk.feature.splash.ui
package ru.fincode.tsudesk.feature.splash.presentation.screen
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
@@ -10,6 +10,8 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import ru.fincode.tsudesk.core.common.model.DataResult
import ru.fincode.tsudesk.core.config.domain.usecase.GetConfigUseCase
import ru.fincode.tsudesk.feature.splash.presentation.model.SplashUiState
import ru.fincode.tsudesk.feature.splash.presentation.model.SplashEffect
import javax.inject.Inject
@HiltViewModel

View File

@@ -1,7 +0,0 @@
package ru.fincode.tsudesk.feature.splash.ui
data class SplashUiState(
val title: String = "TSUDesk",
val isLoading: Boolean = true,
val errorMessage: String? = null
)