FIRST SUCCESS RUN
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.homebox.lens">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
// [PACKAGE] com.homebox.lens.di
|
||||
// [FILE] AppModule.kt
|
||||
// [SEMANTICS] dependency_injection, hilt, configuration
|
||||
|
||||
// [IMPORTS]
|
||||
import com.homebox.lens.data.api.HomeboxApiService
|
||||
import com.homebox.lens.data.repository.ItemRepositoryImpl
|
||||
import com.homebox.lens.domain.repository.ItemRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import javax.inject.Singleton
|
||||
|
||||
// [CORE-LOGIC]
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object AppModule {
|
||||
|
||||
private const val BASE_URL = "https://homebox.fly.dev/api/" // Заглушка, заменить на реальный URL
|
||||
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Предоставляет синглтон-экземпляр Retrofit.
|
||||
*/
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideRetrofit(): Retrofit {
|
||||
// [PRECONDITION] BASE_URL должен быть валидным URL.
|
||||
require(BASE_URL.startsWith("https://") || BASE_URL.startsWith("http://")) {
|
||||
"[PRECONDITION_FAILED] BASE_URL must be a valid URL."
|
||||
}
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Предоставляет синглтон-экземпляр HomeboxApiService.
|
||||
* @param retrofit Экземпляр Retrofit.
|
||||
*/
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideHomeboxApiService(retrofit: Retrofit): HomeboxApiService {
|
||||
return retrofit.create(HomeboxApiService::class.java)
|
||||
}
|
||||
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Предоставляет реализацию ItemRepository.
|
||||
* @param apiService Экземпляр HomeboxApiService.
|
||||
*/
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideItemRepository(apiService: HomeboxApiService): ItemRepository {
|
||||
return ItemRepositoryImpl(apiService)
|
||||
}
|
||||
}
|
||||
// [END_FILE_AppModule.kt]
|
||||
@@ -1,7 +1,7 @@
|
||||
// [PACKAGE] com.homebox.lens.navigation
|
||||
// [FILE] NavGraph.kt
|
||||
// [SEMANTICS] navigation, compose, nav_host
|
||||
|
||||
package com.homebox.lens.navigation
|
||||
// [IMPORTS]
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.compose.NavHost
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
// [PACKAGE] com.homebox.lens.navigation
|
||||
// [FILE] Screen.kt
|
||||
// [SEMANTICS] navigation, routes, constants
|
||||
// [FILE] app/src/main/java/com/homebox/lens/navigation/Screen.kt
|
||||
// [SEMANTICS] navigation, routes, sealed_class
|
||||
|
||||
package com.homebox.lens.navigation
|
||||
|
||||
// [CORE-LOGIC]
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Запечатанный класс для определения навигационных маршрутов в приложении.
|
||||
* Запечатанный класс для определения маршрутов навигации в приложении.
|
||||
* Обеспечивает типобезопасность при навигации.
|
||||
* @property route Строковый идентификатор маршрута.
|
||||
*/
|
||||
sealed class Screen(val route: String) {
|
||||
object Dashboard : Screen("dashboard_screen")
|
||||
// TODO: Добавить остальные экраны по мере их создания
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Представляет экран "Дэшборд".
|
||||
*/
|
||||
data object Dashboard : Screen("dashboard_screen")
|
||||
|
||||
// TODO: Добавить объекты для остальных экранов:
|
||||
// data object ItemDetails : Screen("item_details_screen")
|
||||
// data object Search : Screen("search_screen")
|
||||
}
|
||||
// [END_FILE_Screen.kt]
|
||||
// [END_FILE_Screen.kt]
|
||||
@@ -1,8 +1,10 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.dashboard
|
||||
// [FILE] DashboardScreen.kt
|
||||
// [FILE] app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardScreen.kt
|
||||
// [SEMANTICS] ui, screen, dashboard, compose
|
||||
|
||||
// [IMPORTS]
|
||||
package com.homebox.lens.ui.screen.dashboard
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -18,12 +20,13 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import timber.log.Timber
|
||||
|
||||
// [CORE-LOGIC]
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Главный Composable для экрана "Дэшборд".
|
||||
* @param viewModel ViewModel для этого экрана.
|
||||
* @param viewModel ViewModel для этого экрана, предоставляемая Hilt.
|
||||
*/
|
||||
@Composable
|
||||
fun DashboardScreen(
|
||||
@@ -44,13 +47,16 @@ fun DashboardScreen(
|
||||
}
|
||||
is DashboardUiState.Error -> {
|
||||
// [UI-ACTION] Показываем сообщение об ошибке
|
||||
val errorMessage = "Error: ${state.message}"
|
||||
Text(
|
||||
text = "Error: ${state.message}",
|
||||
text = errorMessage,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
Timber.w("[UI-STATE] Displaying Error: $errorMessage")
|
||||
}
|
||||
is DashboardUiState.Success -> {
|
||||
// [UI-ACTION] Отображаем основной контент
|
||||
Timber.d("[UI-STATE] Displaying Success")
|
||||
DashboardContent(state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.dashboard
|
||||
// [FILE] app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardUiState.kt
|
||||
// [SEMANTICS] ui, state, dashboard
|
||||
|
||||
// [IMPORTS]
|
||||
package com.homebox.lens.ui.screen.dashboard
|
||||
|
||||
import com.homebox.lens.domain.model.GroupStatistics
|
||||
import com.homebox.lens.domain.model.LabelOut
|
||||
import com.homebox.lens.domain.model.LocationOutCount
|
||||
|
||||
// [CORE-LOGIC]
|
||||
// [ENTITY: SealedInterface('DashboardUiState')]
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Определяет все возможные состояния для экрана "Дэшборд".
|
||||
* @invariant В любой момент времени экран может находиться только в одном из этих состояний.
|
||||
*/
|
||||
sealed interface DashboardUiState {
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Состояние успешной загрузки данных.
|
||||
* @property statistics Статистика по инвентарю.
|
||||
* @property locations Список локаций со счетчиками.
|
||||
* @property labels Список всех меток.
|
||||
*/
|
||||
data class Success(
|
||||
val statistics: GroupStatistics,
|
||||
val locations: List<LocationOutCount>,
|
||||
val labels: List<LabelOut>
|
||||
) : DashboardUiState
|
||||
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Состояние ошибки во время загрузки данных.
|
||||
* @property message Человекочитаемое сообщение об ошибке.
|
||||
*/
|
||||
data class Error(val message: String) : DashboardUiState
|
||||
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Состояние, когда данные для экрана загружаются.
|
||||
*/
|
||||
data object Loading : DashboardUiState
|
||||
}
|
||||
// [END_FILE_DashboardUiState.kt]
|
||||
@@ -1,31 +1,25 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.dashboard
|
||||
// [FILE] DashboardViewModel.kt
|
||||
// [SEMANTICS] view_model, dashboard, state_management
|
||||
// [FILE] app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardViewModel.kt
|
||||
// [SEMANTICS] ui, viewmodel, dashboard, hilt
|
||||
|
||||
// [IMPORTS]
|
||||
package com.homebox.lens.ui.screen.dashboard
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.homebox.lens.domain.model.GroupStatistics
|
||||
import com.homebox.lens.domain.model.LabelOut
|
||||
import com.homebox.lens.domain.model.LocationOutCount
|
||||
import com.homebox.lens.domain.usecase.GetAllLabelsUseCase
|
||||
import com.homebox.lens.domain.usecase.GetAllLocationsUseCase
|
||||
import com.homebox.lens.domain.usecase.GetStatisticsUseCase
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber // [FIX] Логирование происходит здесь
|
||||
import javax.inject.Inject
|
||||
|
||||
// [CORE-LOGIC]
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* ViewModel для экрана "Дэшборд".
|
||||
* @param getStatisticsUseCase Use case для получения статистики.
|
||||
* @param getAllLocationsUseCase Use case для получения местоположений.
|
||||
* @param getAllLabelsUseCase Use case для получения меток.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class DashboardViewModel @Inject constructor(
|
||||
private val getStatisticsUseCase: GetStatisticsUseCase,
|
||||
@@ -33,51 +27,51 @@ class DashboardViewModel @Inject constructor(
|
||||
private val getAllLabelsUseCase: GetAllLabelsUseCase
|
||||
) : ViewModel() {
|
||||
|
||||
// [STATE] UI State
|
||||
private val _uiState = MutableStateFlow<DashboardUiState>(DashboardUiState.Loading)
|
||||
val uiState: StateFlow<DashboardUiState> = _uiState.asStateFlow()
|
||||
|
||||
init {
|
||||
// [ACTION] Загрузка всех данных при инициализации.
|
||||
loadDashboardData()
|
||||
}
|
||||
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Загружает все необходимые для экрана данные.
|
||||
* @sideeffect Обновляет _uiState.
|
||||
*/
|
||||
fun loadDashboardData() {
|
||||
viewModelScope.launch {
|
||||
_uiState.value = DashboardUiState.Loading
|
||||
try {
|
||||
val statistics = getStatisticsUseCase()
|
||||
val locations = getAllLocationsUseCase()
|
||||
val labels = getAllLabelsUseCase()
|
||||
private fun loadDashboardData() {
|
||||
Timber.i("[ACTION] Starting dashboard data load.")
|
||||
_uiState.value = DashboardUiState.Loading
|
||||
|
||||
_uiState.value = DashboardUiState.Success(
|
||||
statistics = statistics,
|
||||
locations = locations,
|
||||
labels = labels
|
||||
)
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
// Параллельно запрашиваем все данные
|
||||
val statsDeferred = async { getStatisticsUseCase() }
|
||||
val locationsDeferred = async { getAllLocationsUseCase() }
|
||||
val labelsDeferred = async { getAllLabelsUseCase() }
|
||||
|
||||
val stats = statsDeferred.await()
|
||||
val locations = locationsDeferred.await()
|
||||
val labels = labelsDeferred.await()
|
||||
|
||||
// [ACTION] Логируем результат здесь, во ViewModel
|
||||
if (stats != null && locations != null && labels != null) {
|
||||
_uiState.value = DashboardUiState.Success(
|
||||
statistics = stats,
|
||||
locations = locations,
|
||||
labels = labels
|
||||
)
|
||||
Timber.i("[COHERENCE_CHECK_PASSED] Dashboard data loaded successfully.")
|
||||
} else {
|
||||
// Одна из операций вернула null
|
||||
val errorMessage = "Failed to load dashboard data: " +
|
||||
"stats is ${if(stats==null) "null" else "ok"}, " +
|
||||
"locations is ${if(locations==null) "null" else "ok"}, " +
|
||||
"labels is ${if(labels==null) "null" else "ok"}"
|
||||
Timber.e(errorMessage)
|
||||
_uiState.value = DashboardUiState.Error("Could not load all dashboard data.")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
_uiState.value = DashboardUiState.Error(e.message ?: "Unknown error")
|
||||
// [ERROR_HANDLER] Эта ошибка будет отловлена, если сама корутина `launch` упадет
|
||||
Timber.e(e, "[ERROR] Critical failure in loadDashboardData coroutine.")
|
||||
_uiState.value = DashboardUiState.Error(e.message ?: "An unknown critical error occurred")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Запечатанный интерфейс для представления состояний UI дэшборда.
|
||||
*/
|
||||
sealed interface DashboardUiState {
|
||||
data class Success(
|
||||
val statistics: GroupStatistics,
|
||||
val locations: List<LocationOutCount>,
|
||||
val labels: List<LabelOut>
|
||||
) : DashboardUiState
|
||||
data class Error(val message: String) : DashboardUiState
|
||||
object Loading : DashboardUiState
|
||||
}
|
||||
// [END_FILE_DashboardViewModel.kt]
|
||||
Reference in New Issue
Block a user