initial commit
This commit is contained in:
62
app/src/main/java/com/homebox/lens/MainActivity.kt
Normal file
62
app/src/main/java/com/homebox/lens/MainActivity.kt
Normal file
@@ -0,0 +1,62 @@
|
||||
// [PACKAGE] com.homebox.lens
|
||||
// [FILE] MainActivity.kt
|
||||
|
||||
package com.homebox.lens
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.homebox.lens.navigation.NavGraph
|
||||
import com.homebox.lens.ui.theme.HomeboxLensTheme
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
// [CONTRACT]
|
||||
/**
|
||||
* [ENTITY: Activity('MainActivity')]
|
||||
* [PURPOSE] Главная и единственная Activity в приложении.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
// [LIFECYCLE]
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
HomeboxLensTheme {
|
||||
// A surface container using the 'background' color from the theme
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = MaterialTheme.colorScheme.background
|
||||
) {
|
||||
NavGraph()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// [HELPER]
|
||||
@Composable
|
||||
fun Greeting(name: String, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
text = "Hello $name!",
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
// [PREVIEW]
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun GreetingPreview() {
|
||||
HomeboxLensTheme {
|
||||
Greeting("Android")
|
||||
}
|
||||
}
|
||||
|
||||
// [END_FILE_MainActivity.kt]
|
||||
28
app/src/main/java/com/homebox/lens/MainApplication.kt
Normal file
28
app/src/main/java/com/homebox/lens/MainApplication.kt
Normal file
@@ -0,0 +1,28 @@
|
||||
// [PACKAGE] com.homebox.lens
|
||||
// [FILE] MainApplication.kt
|
||||
|
||||
package com.homebox.lens
|
||||
|
||||
import android.app.Application
|
||||
import com.homebox.lens.BuildConfig
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import timber.log.Timber
|
||||
|
||||
// [CONTRACT]
|
||||
/**
|
||||
* [ENTITY: Application('MainApplication')]
|
||||
* [PURPOSE] Точка входа в приложение. Инициализирует Hilt и Timber.
|
||||
*/
|
||||
@HiltAndroidApp
|
||||
class MainApplication : Application() {
|
||||
// [LIFECYCLE]
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
// [ACTION] Initialize Timber for logging
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(Timber.DebugTree())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// [END_FILE_MainApplication.kt]
|
||||
63
app/src/main/java/com/homebox/lens/di/AppModule.kt
Normal file
63
app/src/main/java/com/homebox/lens/di/AppModule.kt
Normal file
@@ -0,0 +1,63 @@
|
||||
// [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]
|
||||
30
app/src/main/java/com/homebox/lens/navigation/NavGraph.kt
Normal file
30
app/src/main/java/com/homebox/lens/navigation/NavGraph.kt
Normal file
@@ -0,0 +1,30 @@
|
||||
// [PACKAGE] com.homebox.lens.navigation
|
||||
// [FILE] NavGraph.kt
|
||||
// [SEMANTICS] navigation, compose, nav_host
|
||||
|
||||
// [IMPORTS]
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.homebox.lens.ui.screen.dashboard.DashboardScreen
|
||||
|
||||
// [CORE-LOGIC]
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Определяет граф навигации для приложения.
|
||||
*/
|
||||
@Composable
|
||||
fun NavGraph() {
|
||||
val navController = rememberNavController()
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.Dashboard.route
|
||||
) {
|
||||
composable(route = Screen.Dashboard.route) {
|
||||
DashboardScreen()
|
||||
}
|
||||
// TODO: Добавить остальные экраны в граф навигации
|
||||
}
|
||||
}
|
||||
// [END_FILE_NavGraph.kt]
|
||||
15
app/src/main/java/com/homebox/lens/navigation/Screen.kt
Normal file
15
app/src/main/java/com/homebox/lens/navigation/Screen.kt
Normal file
@@ -0,0 +1,15 @@
|
||||
// [PACKAGE] com.homebox.lens.navigation
|
||||
// [FILE] Screen.kt
|
||||
// [SEMANTICS] navigation, routes, constants
|
||||
|
||||
// [CORE-LOGIC]
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Запечатанный класс для определения навигационных маршрутов в приложении.
|
||||
* @property route Строковый идентификатор маршрута.
|
||||
*/
|
||||
sealed class Screen(val route: String) {
|
||||
object Dashboard : Screen("dashboard_screen")
|
||||
// TODO: Добавить остальные экраны по мере их создания
|
||||
}
|
||||
// [END_FILE_Screen.kt]
|
||||
@@ -0,0 +1,94 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.dashboard
|
||||
// [FILE] DashboardScreen.kt
|
||||
// [SEMANTICS] ui, screen, dashboard, compose
|
||||
|
||||
// [IMPORTS]
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
|
||||
// [CORE-LOGIC]
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Главный Composable для экрана "Дэшборд".
|
||||
* @param viewModel ViewModel для этого экрана.
|
||||
*/
|
||||
@Composable
|
||||
fun DashboardScreen(
|
||||
viewModel: DashboardViewModel = hiltViewModel()
|
||||
) {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
|
||||
Scaffold { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
when (val state = uiState) {
|
||||
is DashboardUiState.Loading -> {
|
||||
// [UI-ACTION] Показываем индикатор загрузки
|
||||
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
|
||||
}
|
||||
is DashboardUiState.Error -> {
|
||||
// [UI-ACTION] Показываем сообщение об ошибке
|
||||
Text(
|
||||
text = "Error: ${state.message}",
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
is DashboardUiState.Success -> {
|
||||
// [UI-ACTION] Отображаем основной контент
|
||||
DashboardContent(state)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [CONTRACT]
|
||||
* Composable для отображения успешного состояния дэшборда.
|
||||
* @param state Состояние UI с данными.
|
||||
*/
|
||||
@Composable
|
||||
fun DashboardContent(state: DashboardUiState.Success) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
// [UI-COMPONENT] Статистика
|
||||
Text(text = "Statistics:")
|
||||
Text(text = " Items: ${state.statistics.items}")
|
||||
Text(text = " Locations: ${state.statistics.locations}")
|
||||
Text(text = " Labels: ${state.statistics.labels}")
|
||||
Text(text = " Total Value: ${state.statistics.totalValue}")
|
||||
|
||||
// [UI-COMPONENT] Локации
|
||||
Text(text = "Locations:")
|
||||
state.locations.forEach { location ->
|
||||
Text(text = " - ${location.name} (${location.itemCount})")
|
||||
}
|
||||
|
||||
// [UI-COMPONENT] Метки
|
||||
Text(text = "Labels:")
|
||||
state.labels.forEach { label ->
|
||||
Text(text = " - ${label.name}")
|
||||
}
|
||||
}
|
||||
}
|
||||
// [END_FILE_DashboardScreen.kt]
|
||||
@@ -0,0 +1,83 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.dashboard
|
||||
// [FILE] DashboardViewModel.kt
|
||||
// [SEMANTICS] view_model, dashboard, state_management
|
||||
|
||||
// [IMPORTS]
|
||||
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.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
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,
|
||||
private val getAllLocationsUseCase: GetAllLocationsUseCase,
|
||||
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()
|
||||
|
||||
_uiState.value = DashboardUiState.Success(
|
||||
statistics = statistics,
|
||||
locations = locations,
|
||||
labels = labels
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
_uiState.value = DashboardUiState.Error(e.message ?: "Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [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]
|
||||
16
app/src/main/java/com/homebox/lens/ui/theme/Color.kt
Normal file
16
app/src/main/java/com/homebox/lens/ui/theme/Color.kt
Normal file
@@ -0,0 +1,16 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.theme
|
||||
// [FILE] Color.kt
|
||||
|
||||
package com.homebox.lens.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Purple80 = Color(0xFFD0BCFF)
|
||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||
val Pink80 = Color(0xFFEFB8C8)
|
||||
|
||||
val Purple40 = Color(0xFF6650a4)
|
||||
val PurpleGrey40 = Color(0xFF625b71)
|
||||
val Pink40 = Color(0xFF7D5260)
|
||||
|
||||
// [END_FILE_Color.kt]
|
||||
64
app/src/main/java/com/homebox/lens/ui/theme/Theme.kt
Normal file
64
app/src/main/java/com/homebox/lens/ui/theme/Theme.kt
Normal file
@@ -0,0 +1,64 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.theme
|
||||
// [FILE] Theme.kt
|
||||
|
||||
package com.homebox.lens.ui.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Purple80,
|
||||
secondary = PurpleGrey80,
|
||||
tertiary = Pink80
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme(
|
||||
primary = Purple40,
|
||||
secondary = PurpleGrey40,
|
||||
tertiary = Pink40
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun HomeboxLensTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
// Dynamic color is available on Android 12+
|
||||
dynamicColor: Boolean = true,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
window.statusBarColor = colorScheme.primary.toArgb()
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
// [END_FILE_Theme.kt]
|
||||
23
app/src/main/java/com/homebox/lens/ui/theme/Typography.kt
Normal file
23
app/src/main/java/com/homebox/lens/ui/theme/Typography.kt
Normal file
@@ -0,0 +1,23 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.theme
|
||||
// [FILE] Typography.kt
|
||||
|
||||
package com.homebox.lens.ui.theme
|
||||
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
bodyLarge = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
)
|
||||
)
|
||||
|
||||
// [END_FILE_Typography.kt]
|
||||
Reference in New Issue
Block a user