Files
homebox_lens/tasks/completed/01.xml
busya cf4fc7a535 fix: Resolve build errors
- Add missing quantity field to Item model
- Add missing string resources and translations
- Fix unresolved references in UI screens
2025-08-18 16:15:01 +03:00

1532 lines
79 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<FOR_AGENT>
<!-- tasks/20250817_1119_implement_ui_screens.xml -->
<TASK_BATCH status="completed">
<WORK_ORDER id="WO-20250817-1118-01">
<ACTION>CREATE_OR_UPDATE_FILE</ACTION>
<TARGET_FILE>app/src/main/java/com/homebox/lens/ui/screen/labelslist/LabelsListScreen.kt</TARGET_FILE>
<PAYLOAD>
<CODE lang="kotlin">
<![CDATA[
// [PACKAGE] com.homebox.lens.ui.screen.labelslist
// [FILE] LabelsListScreen.kt
// [SEMANTICS] ui, screen, labels, list, compose
package com.homebox.lens.ui.screen.labelslist
// [SECTION] IMPORTS
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Label
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.ListItem
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import com.homebox.lens.R
import com.homebox.lens.domain.model.Label
import timber.log.Timber
// [ENTITY: Class('LabelsListScreen')]
// [RELATION: Class('LabelsListScreen')] -> [DEPENDS_ON] -> [Class('LabelsListViewModel')]
// [RELATION: Class('LabelsListScreen')] -> [CREATES_INSTANCE_OF] -> [Class('Scaffold')]
/**
* [MAIN-CONTRACT]
* Экран для отображения списка всех меток.
*
* Этот Composable является точкой входа для UI, определенного в спецификации `screen_labels_list`.
* Он получает состояние от [LabelsListViewModel] и отображает его, делегируя обработку
* пользовательских событий в ViewModel.
*
* @param onLabelClick Функция обратного вызова для обработки нажатия на метку.
* @param onNavigateBack Функция обратного вызова для навигации назад.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ENTRYPOINT
fun LabelsListScreen(
viewModel: LabelsListViewModel = hiltViewModel(),
onLabelClick: (Label) -> Unit,
onNavigateBack: () -> Unit
) {
// [STATE]
val labels by viewModel.labels.collectAsState()
// [ACTION]
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(id = R.string.screen_title_labels)) },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(id = R.string.content_desc_navigate_back)
)
}
}
)
},
floatingActionButton = {
FloatingActionButton(onClick = { /* TODO: Implement create new label dialog/screen */ }) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = stringResource(id = R.string.content_desc_add_label)
)
}
}
) { innerPadding ->
// [DELEGATES]
Box(modifier = Modifier.padding(innerPadding)) {
LabelsListContent(
labels = labels,
onLabelClick = onLabelClick
)
}
}
}
// [ENTITY: Function('LabelsListContent')]
// [RELATION: Function('LabelsListContent')] -> [CALLS] -> [Function('LabelListItem')]
/**
* [CONTRACT]
* Отображает основной контент экрана: список меток.
*
* @param labels Список меток для отображения.
* @param onLabelClick Обработчик нажатия на элемент списка.
* @sideeffect Отсутствуют.
*/
@Composable
@HELPER
private fun LabelsListContent(
labels: List<Label>,
onLabelClick: (Label) -> Unit
) {
// [PRECONDITION]
// Проверка предусловий не требуется, так как пустой список является валидным состоянием.
// [CORE-LOGIC]
LazyColumn {
items(labels, key = { it.id }) { label ->
LabelListItem(
label = label,
onClick = {
Timber.i("[INFO][ACTION][ui_interaction] Label clicked: ${label.name}")
onLabelClick(label)
}
)
}
}
// [POSTCONDITION]
// Постусловия гарантируются декларативной природой Compose.
}
// [ENTITY: Function('LabelListItem')]
/**
* [CONTRACT]
* Отображает один элемент в списке меток.
*
* @param label Метка для отображения.
* @param onClick Обработчик нажатия на элемент.
* @sideeffect Отсутствуют.
*/
@Composable
@HELPER
private fun LabelListItem(
label: Label,
onClick: () -> Unit
) {
// [PRECONDITION]
require(label.name.isNotBlank()) { "Label name cannot be blank." }
// [CORE-LOGIC]
ListItem(
headlineContent = { Text(label.name) },
leadingContent = {
Icon(
imageVector = Icons.AutoMirrored.Filled.Label,
contentDescription = null // Декоративный элемент
)
},
modifier = Modifier.clickable(onClick = onClick)
)
}
]]>
</CODE>
</PAYLOAD>
</WORK_ORDER>
<WORK_ORDER id="WO-20250817-1118-02">
<ACTION>CREATE_OR_UPDATE_FILE</ACTION>
<TARGET_FILE>app/src/main/java/com/homebox/lens/ui/screen/inventorylist/InventoryListScreen.kt</TARGET_FILE>
<PAYLOAD>
<CODE lang="kotlin">
<![CDATA[
// [PACKAGE] com.homebox.lens.ui.screen.inventorylist
// [FILE] InventoryListScreen.kt
// [SEMANTICS] ui, screen, inventory, list, compose
package com.homebox.lens.ui.screen.inventorylist
// [SECTION] IMPORTS
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
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.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.homebox.lens.R
import com.homebox.lens.domain.model.Item
import timber.log.Timber
// [ENTITY: Class('InventoryListScreen')]
// [RELATION: Class('InventoryListScreen')] -> [DEPENDS_ON] -> [Class('InventoryListViewModel')]
/**
* [MAIN-CONTRACT]
* Экран для отображения списка инвентарных позиций.
*
* Реализует спецификацию `screen_inventory_list`. Позволяет просматривать,
* искать и синхронизировать инвентарь.
*
* @param onItemClick Обработчик нажатия на элемент инвентаря.
* @param onNavigateBack Обработчик для возврата на предыдущий экран.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ENTRYPOINT
fun InventoryListScreen(
viewModel: InventoryListViewModel = hiltViewModel(),
onItemClick: (Item) -> Unit,
onNavigateBack: () -> Unit
) {
// [STATE]
val uiState by viewModel.uiState.collectAsState()
// [ACTION]
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(id = R.string.screen_title_inventory)) },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(id = R.string.content_desc_navigate_back)
)
}
}
)
},
floatingActionButton = {
FloatingActionButton(onClick = {
Timber.i("[INFO][ACTION][ui_interaction] Sync inventory triggered.")
viewModel.onSyncClicked()
}) {
Icon(
imageVector = Icons.Filled.Refresh,
contentDescription = stringResource(id = R.string.content_desc_sync_inventory)
)
}
}
) { innerPadding ->
// [DELEGATES]
Column(modifier = Modifier.padding(innerPadding)) {
SearchBar(
query = uiState.searchQuery,
onQueryChange = viewModel::onSearchQueryChanged
)
InventoryListContent(
isLoading = uiState.isLoading,
items = uiState.items,
onItemClick = onItemClick
)
}
}
}
/**
* [CONTRACT]
* Поле для ввода поискового запроса.
*/
@Composable
@HELPER
private fun SearchBar(query: String, onQueryChange: (String) -> Unit) {
TextField(
value = query,
onValueChange = onQueryChange,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp),
placeholder = { Text(stringResource(id = R.string.placeholder_search)) },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) }
)
}
/**
* [CONTRACT]
* Основной контент: индикатор загрузки или список предметов.
*/
@Composable
@HELPER
private fun InventoryListContent(
isLoading: Boolean,
items: List<Item>,
onItemClick: (Item) -> Unit
) {
Box(modifier = Modifier.fillMaxSize()) {
if (isLoading) {
// [STATE]
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
} else if (items.isEmpty()) {
// [FALLBACK]
Text(
text = stringResource(id = R.string.message_no_items_found),
modifier = Modifier.align(Alignment.Center)
)
} else {
// [CORE-LOGIC]
LazyColumn {
items(items, key = { it.id }) { item ->
ItemCard(item = item, onClick = {
Timber.i("[INFO][ACTION][ui_interaction] Item clicked: ${item.name}")
onItemClick(item)
})
}
}
}
}
}
/**
* [CONTRACT]
* Карточка для отображения одного элемента инвентаря.
*/
@Composable
@HELPER
private fun ItemCard(
item: Item,
onClick: () -> Unit
) {
// [PRECONDITION]
require(item.name.isNotBlank()) { "Item name cannot be blank." }
// [CORE-LOGIC]
Card(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp, vertical = 4.dp)
.clickable(onClick = onClick)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = item.name, style = androidx.compose.material3.MaterialTheme.typography.titleMedium)
Text(text = "Quantity: ${item.quantity}", style = androidx.compose.material3.MaterialTheme.typography.bodySmall)
item.location?.let {
Text(text = "Location: ${it.name}", style = androidx.compose.material3.MaterialTheme.typography.bodySmall)
}
}
}
}
]]>
</CODE>
</PAYLOAD>
</WORK_ORDER>
<WORK_ORDER id="WO-20250817-1118-03">
<ACTION>CREATE_OR_UPDATE_FILE</ACTION>
<TARGET_FILE>app/src/main/java/com/homebox/lens/ui/screen/itemdetails/ItemDetailsScreen.kt</TARGET_FILE>
<PAYLOAD>
<CODE lang="kotlin">
<![CDATA[
// [PACKAGE] com.homebox.lens.ui.screen.itemdetails
// [FILE] ItemDetailsScreen.kt
// [SEMANTICS] ui, screen, item, details, compose
package com.homebox.lens.ui.screen.itemdetails
// [SECTION] IMPORTS
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.*
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.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.homebox.lens.R
import com.homebox.lens.domain.model.Item
import timber.log.Timber
// [ENTITY: Class('ItemDetailsScreen')]
// [RELATION: Class('ItemDetailsScreen')] -> [DEPENDS_ON] -> [Class('ItemDetailsViewModel')]
/**
* [MAIN-CONTRACT]
* Экран для отображения детальной информации о товаре.
*
* Реализует спецификацию `screen_item_details`.
*
* @param onNavigateBack Обработчик для возврата на предыдущий экран.
* @param onEditClick Обработчик нажатия на кнопку редактирования.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ENTRYPOINT
fun ItemDetailsScreen(
viewModel: ItemDetailsViewModel = hiltViewModel(),
onNavigateBack: () -> Unit,
onEditClick: (Int) -> Unit
) {
// [STATE]
val uiState by viewModel.uiState.collectAsState()
Scaffold(
topBar = {
TopAppBar(
title = { Text(uiState.item?.name ?: stringResource(id = R.string.screen_title_item_details)) },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(id = R.string.content_desc_navigate_back))
}
},
actions = {
IconButton(onClick = {
uiState.item?.id?.let {
Timber.i("[INFO][ACTION][ui_interaction] Edit item clicked: id=$it")
onEditClick(it)
}
}) {
Icon(Icons.Default.Edit, contentDescription = stringResource(id = R.string.content_desc_edit_item))
}
IconButton(onClick = {
Timber.w("[WARN][ACTION][ui_interaction] Delete item clicked: id=${uiState.item?.id}")
viewModel.deleteItem()
// После удаления нужно навигироваться назад
onNavigateBack()
}) {
Icon(Icons.Default.Delete, contentDescription = stringResource(id = R.string.content_desc_delete_item))
}
}
)
}
) { innerPadding ->
ItemDetailsContent(
modifier = Modifier.padding(innerPadding),
isLoading = uiState.isLoading,
item = uiState.item
)
}
}
/**
* [CONTRACT]
* Отображает контент экрана: индикатор загрузки или детали товара.
*/
@Composable
@HELPER
private fun ItemDetailsContent(
modifier: Modifier = Modifier,
isLoading: Boolean,
item: Item?
) {
Box(modifier = modifier.fillMaxSize()) {
when {
isLoading -> {
// [STATE]
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
item == null -> {
// [FALLBACK]
Text(stringResource(id = R.string.message_item_not_found), modifier = Modifier.align(Alignment.Center))
}
else -> {
// [CORE-LOGIC]
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// TODO: ImageCarousel
// Text("Image Carousel Placeholder")
DetailsSection(title = stringResource(id = R.string.section_title_description)) {
Text(text = item.description ?: stringResource(id = R.string.placeholder_no_description))
}
DetailsSection(title = stringResource(id = R.string.section_title_details)) {
InfoRow(label = stringResource(id = R.string.label_quantity), value = item.quantity.toString())
item.location?.let {
InfoRow(label = stringResource(id = R.string.label_location), value = it.name)
}
}
if (item.labels.isNotEmpty()) {
DetailsSection(title = stringResource(id = R.string.section_title_labels)) {
// TODO: Use FlowRow for better layout
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
item.labels.forEach { label ->
AssistChip(onClick = { /* No-op */ }, label = { Text(label.name) })
}
}
}
}
// TODO: CustomFieldsGrid
}
}
}
}
}
/**
* [CONTRACT]
* Секция с заголовком и контентом.
*/
@Composable
@HELPER
private fun DetailsSection(title: String, content: @Composable ColumnScope.() -> Unit) {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(text = title, style = MaterialTheme.typography.titleMedium)
Divider()
content()
}
}
/**
* [CONTRACT]
* Строка для отображения пары "метка: значение".
*/
@Composable
@HELPER
private fun InfoRow(label: String, value: String) {
Row {
Text(text = "$label: ", style = MaterialTheme.typography.bodyLarge)
Text(text = value, style = MaterialTheme.typography.bodyLarge)
}
}
]]>
</CODE>
</PAYLOAD>
</WORK_ORDER>
<WORK_ORDER id="WO-20250817-1118-04">
<ACTION>CREATE_OR_UPDATE_FILE</ACTION>
<TARGET_FILE>app/src/main/java/com/homebox/lens/ui/screen/itemedit/ItemEditScreen.kt</TARGET_FILE>
<PAYLOAD>
<CODE lang="kotlin">
<![CDATA[
// [PACKAGE] com.homebox.lens.ui.screen.itemedit
// [FILE] ItemEditScreen.kt
// [SEMANTICS] ui, screen, item, edit, create, compose
package com.homebox.lens.ui.screen.itemedit
// [SECTION] IMPORTS
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Done
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.homebox.lens.R
import timber.log.Timber
// [ENTITY: Class('ItemEditScreen')]
// [RELATION: Class('ItemEditScreen')] -> [DEPENDS_ON] -> [Class('ItemEditViewModel')]
/**
* [MAIN-CONTRACT]
* Экран для создания или редактирования товара.
*
* Реализует спецификацию `screen_item_edit`.
*
* @param onNavigateBack Обработчик для возврата на предыдущий экран после сохранения или отмены.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ENTRYPOINT
fun ItemEditScreen(
viewModel: ItemEditViewModel = hiltViewModel(),
onNavigateBack: () -> Unit
) {
// [STATE]
val uiState by viewModel.uiState.collectAsState()
// [SIDE-EFFECT]
LaunchedEffect(uiState.isSaved) {
if (uiState.isSaved) {
Timber.i("[INFO][SIDE_EFFECT][navigation] Item saved, navigating back.")
onNavigateBack()
}
}
Scaffold(
topBar = {
TopAppBar(
title = { Text(stringResource(id = if (uiState.isEditing) R.string.screen_title_edit_item else R.string.screen_title_create_item)) },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(id = R.string.content_desc_navigate_back))
}
},
actions = {
IconButton(onClick = {
Timber.i("[INFO][ACTION][ui_interaction] Save item clicked.")
viewModel.saveItem()
}) {
Icon(Icons.Default.Done, contentDescription = stringResource(id = R.string.content_desc_save_item))
}
}
)
}
) { innerPadding ->
ItemEditContent(
modifier = Modifier.padding(innerPadding),
state = uiState,
onNameChange = { viewModel.onNameChange(it) },
onDescriptionChange = { viewModel.onDescriptionChange(it) },
onQuantityChange = { viewModel.onQuantityChange(it) }
)
}
}
/**
* [CONTRACT]
* Отображает форму для редактирования данных товара.
*/
@Composable
@HELPER
private fun ItemEditContent(
modifier: Modifier = Modifier,
state: ItemEditUiState,
onNameChange: (String) -> Unit,
onDescriptionChange: (String) -> Unit,
onQuantityChange: (String) -> Unit
) {
// [CORE-LOGIC]
Column(
modifier = modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
OutlinedTextField(
value = state.name,
onValueChange = onNameChange,
label = { Text(stringResource(id = R.string.label_name)) },
modifier = Modifier.fillMaxWidth(),
isError = state.nameError != null
)
state.nameError?.let {
Text(text = stringResource(id = it), color = MaterialTheme.colorScheme.error)
}
OutlinedTextField(
value = state.description,
onValueChange = onDescriptionChange,
label = { Text(stringResource(id = R.string.label_description)) },
modifier = Modifier.fillMaxWidth(),
minLines = 3
)
OutlinedTextField(
value = state.quantity,
onValueChange = onQuantityChange,
label = { Text(stringResource(id = R.string.label_quantity)) },
modifier = Modifier.fillMaxWidth(),
isError = state.quantityError != null
)
state.quantityError?.let {
Text(text = stringResource(id = it), color = MaterialTheme.colorScheme.error)
}
// TODO: Location Dropdown
// TODO: Labels ChipGroup
// TODO: ImagePicker
}
}
]]>
</CODE>
</PAYLOAD>
</WORK_ORDER>
<WORK_ORDER id="WO-20250817-1118-05">
<ACTION>CREATE_OR_UPDATE_FILE</ACTION>
<TARGET_FILE>app/src/main/java/com/homebox/lens/ui/screen/search/SearchScreen.kt</TARGET_FILE>
<PAYLOAD>
<CODE lang="kotlin">
<![CDATA[
// [PACKAGE] com.homebox.lens.ui.screen.search
// [FILE] SearchScreen.kt
// [SEMANTICS] ui, screen, search, compose
package com.homebox.lens.ui.screen.search
// [SECTION] IMPORTS
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.homebox.lens.R
import com.homebox.lens.domain.model.Item
// [ENTITY: Class('SearchScreen')]
// [RELATION: Class('SearchScreen')] -> [DEPENDS_ON] -> [Class('SearchViewModel')]
/**
* [MAIN-CONTRACT]
* Специализированный экран для поиска товаров.
*
* Реализует спецификацию `screen_search`.
*
* @param onNavigateBack Обработчик для возврата на предыдущий экран.
* @param onItemClick Обработчик нажатия на найденный товар.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ENTRYPOINT
fun SearchScreen(
viewModel: SearchViewModel = hiltViewModel(),
onNavigateBack: () -> Unit,
onItemClick: (Item) -> Unit
) {
// [STATE]
val uiState by viewModel.uiState.collectAsState()
Scaffold(
topBar = {
TopAppBar(
title = {
TextField(
value = uiState.searchQuery,
onValueChange = viewModel::onSearchQueryChanged,
placeholder = { Text(stringResource(R.string.placeholder_search_items)) },
modifier = Modifier.fillMaxWidth()
)
},
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.content_desc_navigate_back))
}
}
)
}
) { innerPadding ->
SearchContent(
modifier = Modifier.padding(innerPadding),
isLoading = uiState.isLoading,
results = uiState.results,
onItemClick = onItemClick
)
}
}
/**
* [CONTRACT]
* Отображает основной контент экрана: фильтры и результаты поиска.
*/
@Composable
@HELPER
private fun SearchContent(
modifier: Modifier = Modifier,
isLoading: Boolean,
results: List<Item>,
onItemClick: (Item) -> Unit
) {
Column(modifier = modifier.fillMaxSize()) {
// [SECTION] FILTERS
// TODO: Implement FilterSection with chips for locations/labels
// Spacer(modifier = Modifier.height(8.dp))
// [SECTION] RESULTS
Box(modifier = Modifier.weight(1f)) {
if (isLoading) {
// [STATE]
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
} else {
// [CORE-LOGIC]
LazyColumn {
items(results, key = { it.id }) { item ->
ListItem(
headlineContent = { Text(item.name) },
supportingContent = { Text(item.location?.name ?: "") },
modifier = Modifier.clickable { onItemClick(item) }
)
}
}
}
}
}
}
]]>
</CODE>
</PAYLOAD>
</WORK_ORDER>
<WORK_ORDER id="WO-20250817-1118-06">
<ACTION>CREATE_OR_UPDATE_FILE</ACTION>
<TARGET_FILE>app/src/main/java/com/homebox/lens/ui/screen/setup/SetupScreen.kt</TARGET_FILE>
<PAYLOAD>
<CODE lang="kotlin">
<![CDATA[
// [PACKAGE] com.homebox.lens.ui.screen.setup
// [FILE] SetupScreen.kt
// [SEMANTICS] ui, screen, setup, login, compose
package com.homebox.lens.ui.screen.setup
// [SECTION] IMPORTS
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.homebox.lens.R
import timber.log.Timber
// [ENTITY: Class('SetupScreen')]
// [RELATION: Class('SetupScreen')] -> [DEPENDS_ON] -> [Class('SetupViewModel')]
/**
* [MAIN-CONTRACT]
* Экран для начальной настройки соединения с сервером Homebox.
*
* @param onSetupComplete Обработчик, вызываемый после успешной настройки и входа.
*/
@Composable
@ENTRYPOINT
fun SetupScreen(
viewModel: SetupViewModel = hiltViewModel(),
onSetupComplete: () -> Unit
) {
// [STATE]
val uiState by viewModel.uiState.collectAsState()
// [SIDE-EFFECT]
LaunchedEffect(uiState.isLoginSuccessful) {
if (uiState.isLoginSuccessful) {
Timber.i("[INFO][SIDE_EFFECT][navigation] Setup complete, navigating to main screen.")
onSetupComplete()
}
}
// [CORE-LOGIC]
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(32.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
Text(text = stringResource(id = R.string.screen_title_setup), style = MaterialTheme.typography.headlineMedium)
OutlinedTextField(
value = uiState.serverUrl,
onValueChange = viewModel::onServerUrlChange,
label = { Text(stringResource(id = R.string.label_server_url)) },
modifier = Modifier.fillMaxWidth(),
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Uri),
isError = uiState.error != null
)
OutlinedTextField(
value = uiState.apiKey,
onValueChange = viewModel::onApiKeyChange,
label = { Text(stringResource(id = R.string.label_api_key)) },
modifier = Modifier.fillMaxWidth(),
visualTransformation = PasswordVisualTransformation(),
isError = uiState.error != null
)
if (uiState.isLoading) {
// [STATE]
CircularProgressIndicator()
} else {
// [ACTION]
Button(
onClick = {
Timber.i("[INFO][ACTION][ui_interaction] Login button clicked.")
viewModel.login()
},
modifier = Modifier.fillMaxWidth()
) {
Text(text = stringResource(id = R.string.button_connect))
}
}
uiState.error?.let {
// [FALLBACK]
Text(
text = it,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodyMedium
)
}
}
}
}
]]>
</CODE>
</PAYLOAD>
</WORK_ORDER>
<WORK_ORDER id="WO-20250817-1118-07">
<ACTION>CREATE_OR_UPDATE_FILE</ACTION>
<TARGET_FILE>PROJECT_SPECIFICATION.xml</TARGET_FILE>
<PAYLOAD>
<CODE lang="xml">
<![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<PROJECT_SPECIFICATION>
<PROJECT_INFO>
<name>Homebox Lens</name>
<description>Android-клиент для системы управления инвентарем Homebox. Позволяет пользователям управлять своим инвентарем, взаимодействуя с экземпляром сервера Homebox.</description>
</PROJECT_INFO>
<TECHNICAL_DECISIONS>
<DECISION id="tech_logging" status="implemented">
<summary>Библиотека логирования</summary>
<description>В проекте используется Timber (timber.log.Timber) для всех целей логирования. Он предоставляет простой и расширяемый API для логирования.</description>
<EXAMPLE lang="kotlin">
<summary>Пример корректного использования Timber</summary>
<code>
<![CDATA[
// Правильно: Прямой вызов статических методов Timber.
// Для информационных сообщений (INFO):
Timber.i("User logged in successfully. UserId: %s", userId)
// Для отладочных сообщений (DEBUG):
Timber.d("Starting network request to /items")
// Для ошибок (ERROR):
try {
// какая-то операция, которая может провалиться
} catch (e: Exception) {
Timber.e(e, "Failed to fetch user profile.")
}
// НЕПРАВИЛЬНО: Попытка создать экземпляр логгера.
// val logger = Timber.tag("MyScreen") // Избегать этого!
// logger.info("Some message") // Этот метод не существует в API Timber.
]]>
</code>
</EXAMPLE>
</DECISION>
<DECISION id="tech_i18n" status="implemented">
<summary>Интернационализация (Мультиязычность)</summary>
<description>
Приложение должно поддерживать несколько языков для обеспечения доступности для глобальной аудитории.
Реализация будет основана на стандартном механизме ресурсов Android.
- Все строки, видимые пользователю, должны быть вынесены в файл `app/src/main/res/values/strings.xml`. Использование жестко закодированных строк в коде запрещено.
- Язык по умолчанию - русский (ru). Файл `strings.xml` будет содержать русские строки.
- Для поддержки других языков (например, английского - en) будут создаваться соответствующие каталоги ресурсов (например, `app/src/main/res/values-en/strings.xml`).
- В коде для доступа к строкам необходимо использовать ссылки на ресурсы (например, `R.string.app_name`).
</description>
</DECISION>
<DECISION id="tech_ui_framework" status="implemented">
<summary>UI Framework</summary>
<description>Пользовательский интерфейс приложения построен с использованием Jetpack Compose, современного декларативного UI-фреймворка от Google. Это обеспечивает быстрое создание, гибкость и поддержку динамических данных.</description>
</DECISION>
<DECISION id="tech_di" status="implemented">
<summary>Внедрение зависимостей (Dependency Injection)</summary>
<description>Для управления зависимостями в проекте используется Hilt. Он интегрирован с компонентами Jetpack и упрощает внедрение зависимостей в Android-приложениях.</description>
</DECISION>
<DECISION id="tech_navigation" status="implemented">
<summary>Навигация</summary>
<description>Навигация между экранами (Composable-функциями) реализована с помощью библиотеки Navigation Compose, которая является частью Jetpack Navigation.</description>
</DECISION>
<DECISION id="tech_async" status="implemented">
<summary>Асинхронные операции</summary>
<description>Все асинхронные операции, такие как сетевые запросы или доступ к базе данных, выполняются с использованием Kotlin Coroutines. Это обеспечивает эффективное управление фоновыми задачами без блокировки основного потока.</description>
</DECISION>
<DECISION id="tech_networking" status="implemented">
<summary>Сетевое взаимодействие</summary>
<description>Для взаимодействия с API сервера Homebox используется стек технологий: Retrofit для создания типобезопасных HTTP-клиентов, OkHttp в качестве HTTP-клиента и Moshi для парсинга JSON.</description>
</DECISION>
<DECISION id="tech_database" status="implemented">
<summary>Локальное хранилище</summary>
<description>Для кэширования данных на устройстве используется библиотека Room. Она предоставляет абстракцию над SQLite и обеспечивает надежное локальное хранение данных.</description>
</DECISION>
</TECHNICAL_DECISIONS>
<SECURITY_SPEC>
<Description>Спецификация безопасности проекта.</Description>
<PRINCIPLE>Все сетевые взаимодействия должны быть защищены HTTPS. Аутентификация пользователя хранится в EncryptedSharedPreferences. Обработка ошибок аутентификации должна включать logout и редирект на экран логина.</PRINCIPLE>
<RULE name="AuthHandling">Использовать JWT или API-ключ для авторизации запросов. При истечении токена автоматически обновлять.</RULE>
<RULE name="DataEncryption">Локальные данные (credentials) шифровать с помощью Android KeyStore.</RULE>
</SECURITY_SPEC>
<ERROR_HANDLING>
<Description>Спецификация обработки ошибок.</Description>
<PRINCIPLE>Все потенциальные ошибки (сеть, БД, валидация) должны быть обработаны с использованием sealed classes для ошибок (e.g., NetworkError, ValidationError) и отображаться пользователю через Snackbar или Dialog.</PRINCIPLE>
<SCENARIO name="NetworkFailure">При сетевых ошибках показывать сообщение "No internet connection" и предлагать retry.</SCENARIO>
<SCENARIO name="ServerError">Для HTTP 4xx/5xx отображать user-friendly сообщение на основе response body.</SCENARIO>
<SCENARIO name="ValidationError">Использовать require/check для контрактов, логировать и показывать toast.</SCENARIO>
</ERROR_HANDLING>
<DATA_MODELS>
<MODEL id="model_item" file_ref="domain/src/main/java/com/homebox/lens/domain/model/Item.kt" status="implemented">
<summary>Модель инвентарного товара.</summary>
<description>Содержит поля: id, name, description, quantity, location, labels, customFields.</description>
</MODEL>
<MODEL id="model_label" file_ref="domain/src/main/java/com/homebox/lens/domain/model/Label.kt" status="implemented">
<summary>Модель метки.</summary>
<description>Содержит поля: id, name, color.</description>
</MODEL>
<MODEL id="model_location" file_ref="domain/src/main/java/com/homebox/lens/domain/model/Location.kt" status="implemented">
<summary>Модель местоположения.</summary>
<description>Содержит поля: id, name, parentLocation.</description>
</MODEL>
<MODEL id="model_statistics" file_ref="domain/src/main/java/com/homebox/lens/domain/model/Statistics.kt" status="implemented">
<summary>Модель статистики инвентаря.</summary>
<description>Содержит поля: totalItems, totalValue, locationsCount, labelsCount.</description>
</MODEL>
</DATA_MODELS>
<FEATURES>
<FEATURE id="feat_dashboard" status="implemented">
<summary>Экран панели управления</summary>
<description>Отображает сводку по инвентарю, включая статистику, такую как общее количество товаров, общая стоимость и количество по местоположениям/меткам.</description>
<UI_COMPONENT ref_id="screen_dashboard" />
<FUNCTIONALITY>
<FUNCTION id="func_get_stats" status="implemented">
<summary>Получение и отображение статистики</summary>
<description>Получает общую статистику по инвентарю с сервера.</description>
<precondition>Пользователь аутентифицирован; сеть доступна.</precondition>
<postcondition>Возвращает объект Statistics; данные кэшированы локально.</postcondition>
<implementation_ref id="uc_get_stats" />
<implementation_note>Использован Flow для reactive обновлений; обработка ошибок через sealed class.</implementation_note>
</FUNCTION>
<FUNCTION id="func_get_recent_items" status="implemented">
<summary>Получение и отображение недавно добавленных товаров</summary>
<description>Получает список последних N добавленных товаров из локальной базы данных.</description>
<precondition>Пользователь аутентифицирован.</precondition>
<postcondition>Возвращает Flow со списком ItemSummary; список отсортирован по дате создания.</postcondition>
<implementation_ref id="uc_get_recent_items" />
<implementation_note>Данные берутся из локального кэша (Room) для быстрого отображения.</implementation_note>
</FUNCTION>
</FUNCTIONALITY>
</FEATURE>
<FEATURE id="feat_inventory_list" status="implemented">
<summary>Экран списка инвентаря</summary>
<description>Отображает список всех инвентарных позиций с возможностью поиска и фильтрации.</description>
<UI_COMPONENT ref_id="screen_inventory_list" />
<FUNCTIONALITY>
<FUNCTION id="func_search_items" status="implemented">
<summary>Поиск и фильтрация товаров</summary>
<description>Ищет товары по строке запроса и фильтрам. Результаты разбиты на страницы.</description>
<precondition>Запрос не пустой; параметры пагинации валидны (page >= 1).</precondition>
<postcondition>Возвращает список Item с пагинацией; результаты отсортированы по релевантности.</postcondition>
<implementation_ref id="uc_search_items" />
<implementation_note>Поддержка фильтров по location/label; кэширование результатов для оффлайн.</implementation_note>
</FUNCTION>
<FUNCTION id="func_sync_inventory" status="implemented">
<summary>Синхронизация инвентаря</summary>
<description>Выполняет полную синхронизацию локального кэша инвентаря с сервером.</description>
<precondition>Сеть доступна; пользователь аутентифицирован.</precondition>
<postcondition>Локальная БД обновлена; возвращает success/failure.</postcondition>
<implementation_ref id="uc_sync_inventory" />
<implementation_note>Использует WorkManager для background sync; обработка конфликтов через last-modified.</implementation_note>
</FUNCTION>
</FUNCTIONALITY>
</FEATURE>
<FEATURE id="feat_item_details" status="implemented">
<summary>Экран сведений о товаре</summary>
<description>Показывает все сведения о конкретном инвентарном товаре, включая его название, описание, изображения, вложения и настраиваемые поля.</description>
<UI_COMPONENT ref_id="screen_item_details" />
<FUNCTIONALITY>
<FUNCTION id="func_get_item_details" status="implemented">
<summary>Получение сведений о товаре</summary>
<description>Получает полные сведения о конкретном товаре из репозитория.</description>
<precondition>Item ID валиден и существует.</precondition>
<postcondition>Возвращает полный объект Item с attachments.</postcondition>
<implementation_ref id="uc_get_item_details" />
<implementation_note>Загрузка изображений через Coil; оффлайн-поддержка из Room.</implementation_note>
</FUNCTION>
</FUNCTIONALITY>
</FEATURE>
<FEATURE id="feat_item_management" status="implemented">
<summary>Создание/редактирование/удаление товаров</summary>
<description>Позволяет пользователям создавать новые товары, обновлять существующие и удалять их.</description>
<UI_COMPONENT ref_id="screen_item_edit" />
<FUNCTIONALITY>
<FUNCTION id="func_create_item" status="implemented">
<summary>Создать товар</summary>
<description>Создает новый инвентарный товар на сервере.</description>
<precondition>Все обязательные поля (name, quantity) заполнены; данные валидны.</precondition>
<postcondition>Новый Item сохранен на сервере; ID возвращен.</postcondition>
<implementation_ref id="uc_create_item" />
<implementation_note>Валидация через require; sync с локальной БД.</implementation_note>
</FUNCTION>
<FUNCTION id="func_update_item" status="implemented">
<summary>Обновить товар</summary>
<description>Обновляет существующий инвентарный товар на сервере.</description>
<precondition>Item ID существует; изменения валидны.</precondition>
<postcondition>Item обновлен; версия инкрементирована.</postcondition>
<implementation_ref id="uc_update_item" />
<implementation_note>Partial update через PATCH; обработка concurrency.</implementation_note>
</FUNCTION>
<FUNCTION id="func_delete_item" status="implemented">
<summary>Удалить товар</summary>
<description>Удаляет инвентарный товар с сервера.</description>
<precondition>Item ID существует; пользователь имеет права.</precondition>
<postcondition>Item удален; связанные ресурсы (attachments) очищены.</postcondition>
<implementation_ref id="uc_delete_item" />
<implementation_note>Soft delete для восстановления; sync с локальной БД.</implementation_note>
</FUNCTION>
</FUNCTIONALITY>
</FEATURE>
<FEATURE id="feat_labels_locations" status="implemented">
<summary>Управление метками и местоположениями</summary>
<description>Позволяет пользователям просматривать списки всех доступных меток и местоположений.</description>
<UI_COMPONENT ref_id="screen_labels_list" />
<UI_COMPONENT ref_id="screen_locations_list" />
<FUNCTIONALITY>
<FUNCTION id="func_get_all_labels" status="implemented">
<summary>Получить все метки</summary>
<description>Получает список всех меток из репозитория.</description>
<precondition>Сеть доступна или кэш существует.</precondition>
<postcondition>Возвращает список Label; отсортирован по name.</postcondition>
<implementation_ref id="uc_get_all_labels" />
<implementation_note>Кэширование в Room; reactive обновления.</implementation_note>
</FUNCTION>
<FUNCTION id="func_get_all_locations" status="implemented">
<summary>Получить все местоположения</summary>
<description>Получает список всех местоположений из репозитория.</description>
<precondition>Сеть доступна или кэш существует.</precondition>
<postcondition>Возвращает список Location; иерархическая структура сохранена.</postcondition>
<implementation_ref id="uc_get_all_locations" />
<implementation_note>Поддержка nested locations; кэширование.</implementation_note>
</FUNCTION>
</FUNCTIONALITY>
</FEATURE>
<FEATURE id="feat_search" status="implemented">
<summary>Экран поиска</summary>
<description>Предоставляет специальный пользовательский интерфейс для поиска товаров.</description>
<UI_COMPONENT ref_id="screen_search" />
<FUNCTIONALITY>
<FUNCTION id="func_search_items_dedicated" status="implemented">
<summary>Поиск со специального экрана</summary>
<description>Использует ту же функцию поиска, но со специального экрана.</description>
<precondition>Запрос не пустой.</precondition>
<postcondition>Возвращает результаты поиска; UI обновлен.</postcondition>
<implementation_ref id="uc_search_items" />
<implementation_note>Интеграция с SearchView; debounce для запросов.</implementation_note>
</FUNCTION>
</FUNCTIONALITY>
</FEATURE>
</FEATURES>
<UI_SPECIFICATIONS>
<SCREEN id="screen_dashboard" status="implemented">
<summary>Главный экран "Панель управления"</summary>
<description>
Экран предоставляет обзорную информацию и быстрый доступ к основным функциям. Компоновка должна быть чистой и интуитивно понятной, аналогично веб-интерфейсу HomeBox.
</description>
<LAYOUT>
<COMPONENT type="TopAppBar">
<description>Верхняя панель приложения. Содержит иконку навигационного меню (гамбургер), название/логотип приложения и иконку для запуска сканера (например, QR-кода).</description>
</COMPONENT>
<COMPONENT type="NavigationDrawer">
<description>Боковое навигационное меню. Открывается по нажатию на иконку в TopAppBar. Содержит основные разделы: Главная, Локации, Поиск, Профиль, Инструменты, а также кнопку "Выйти".</description>
</COMPONENT>
<COMPONENT type="MainContent" orientation="vertical" scrollable="true">
<description>Основная область контента. Содержит несколько информационных блоков.</description>
<SUB_COMPONENT type="Section" title="Быстрая статистика">
<description>Сетка из 2x2 карточек, отображающих ключевые метрики.</description>
<ELEMENT type="Card" name="Общая стоимость" />
<ELEMENT type="Card" name="Всего вещей" />
<ELEMENT type="Card" name="Общее количество местоположений" />
<ELEMENT type="Card" name="Всего меток" />
</SUB_COMPONENT>
<SUB_COMPONENT type="Section" title="Недавно добавлено">
<description>Горизонтально прокручиваемый список карточек недавно добавленных предметов. Если предметов нет, отображается сообщение "Элементы не найдены".</description>
</SUB_COMPONENT>
<SUB_COMPONENT type="Section" title="Места хранения">
<description>Сетка или гибкий контейнер (FlowRow) с кликабельными чипами, представляющими местоположения. Нажатие на чип ведет к списку предметов в этом местоположении.</description>
</SUB_COMPONENT>
<SUB_COMPONENT type="Section" title="Метки">
<description>Сетка или гибкий контейнер (FlowRow) с кликабельными чипами, представляющими метки. Нажатие на чип ведет к списку предметов с этой меткой.</description>
</SUB_COMPONENT>
</COMPONENT>
<COMPONENT type="FloatingActionButton_or_PrimaryButton" icon="add">
<description>
Вместо плавающей кнопки (FAB), в референсе используется заметная кнопка "Создать" в навигационном меню. Мы будем придерживаться этого подхода для консистентности. Эта кнопка инициирует процесс создания нового предмета.
</description>
</COMPONENT>
</LAYOUT>
<USER_INTERACTIONS>
<INTERACTION>
<action>Нажатие на чип местоположения/метки</action>
<reaction>Навигация на экран списка инвентаря с фильтром.</reaction>
</INTERACTION>
<INTERACTION>
<action>Нажатие на кнопку "Создать"</action>
<reaction>Открытие экрана редактирования нового товара.</reaction>
</INTERACTION>
</USER_INTERACTIONS>
</SCREEN>
<SCREEN id="screen_locations_list" status="implemented">
<summary>Экран "Локации"</summary>
<description>
Отображает вертикальный список всех доступных местоположений. Экран должен быть интегрирован в общую структуру навигации приложения (TopAppBar, NavigationDrawer).
</description>
<LAYOUT>
<COMPONENT type="TopAppBar">
<description>Общая верхняя панель приложения, аналогичная экрану "Панель управления".</description>
</COMPONENT>
<COMPONENT type="NavigationDrawer">
<description>Общее боковое меню навигации.</description>
</COMPONENT>
<COMPONENT type="MainContent" orientation="vertical">
<description>Основная область контента, занимающая все доступное пространство под TopAppBar.</description>
<SUB_COMPONENT type="Header" title="Локации">
<description>Заголовок экрана, расположенный вверху основной области контента.</description>
</SUB_COMPONENT>
<SUB_COMPONENT type="List" name="LocationsList">
<description>Вертикальный, прокручиваемый список (LazyColumn) всех местоположений.</description>
<ELEMENT type="ListItem">
<description>Элемент списка, представляющий одно местоположение. Состоит из иконки (например, 'place') и названия местоположения. Весь элемент является кликабельным и ведет на экран со списком предметов в данной локации.</description>
</ELEMENT>
</SUB_COMPONENT>
</COMPONENT>
<COMPONENT type="FloatingActionButton" icon="add">
<description>
Плавающая кнопка действия, расположенная в правом нижнем углу. Позволяет пользователю добавить новое местоположение. В веб-версии для этого используются иконки в углу, но FAB является более нативным паттерном для Android.
</description>
</COMPONENT>
</LAYOUT>
<USER_INTERACTIONS>
<INTERACTION>
<action>Нажатие на элемент списка локаций</action>
<reaction>Осуществляется навигация на экран списка инвентаря, отфильтрованного по выбранной локации.</reaction>
</INTERACTION>
<INTERACTION>
<action>Нажатие на FloatingActionButton</action>
<reaction>Открывается диалоговое окно или новый экран для создания нового местоположения.</reaction>
</INTERACTION>
</USER_INTERACTIONS>
</SCREEN>
<SCREEN id="screen_labels_list" status="implemented">
<summary>Экран "Метки"</summary>
<description>
Отображает вертикальный список всех доступных меток. Экран должен быть интегрирован в общую структуру навигации приложения.
</description>
<LAYOUT>
<COMPONENT type="TopAppBar">
<description>Общая верхняя панель приложения с заголовком "Метки" и кнопкой "назад".</description>
</COMPONENT>
<COMPONENT type="MainContent" orientation="vertical">
<description>Основная область контента, занимающая все доступное пространство под TopAppBar.</description>
<SUB_COMPONENT type="List" name="LabelsList">
<description>Вертикальный, прокручиваемый список (LazyColumn) всех меток.</description>
<ELEMENT type="ListItem">
<description>Элемент списка, представляющий одну метку. Состоит из иконки (например, 'label') и названия метки. Весь элемент является кликабельным и ведет на экран со списком предметов с данной меткой.</description>
</ELEMENT>
</SUB_COMPONENT>
</COMPONENT>
<COMPONENT type="FloatingActionButton" icon="add">
<description>
Плавающая кнопка действия, расположенная в правом нижнем углу. Позволяет пользователю добавить новую метку.
</description>
</COMPONENT>
</LAYOUT>
<USER_INTERACTIONS>
<INTERACTION>
<action>Нажатие на элемент списка меток</action>
<reaction>Осуществляется навигация на экран списка инвентаря, отфильтрованного по выбранной метке.</reaction>
</INTERACTION>
<INTERACTION>
<action>Нажатие на FloatingActionButton</action>
<reaction>Открывается диалоговое окно или новый экран для создания новой метки.</reaction>
</INTERACTION>
</USER_INTERACTIONS>
</SCREEN>
<SCREEN id="screen_inventory_list" status="implemented">
<summary>Экран "Список инвентаря"</summary>
<description>
Отображает список всех инвентарных позиций с возможностью поиска, фильтрации и пагинации. Интегрирован в навигацию.
</description>
<LAYOUT>
<COMPONENT type="TopAppBar">
<description>Верхняя панель с поиском и фильтрами.</description>
</COMPONENT>
<COMPONENT type="MainContent" orientation="vertical" scrollable="true">
<description>Прокручиваемый список товаров.</description>
<SUB_COMPONENT type="List" name="InventoryList">
<description>LazyColumn с карточками товаров (name, quantity, location).</description>
<ELEMENT type="Card" name="ItemCard">
<description>Кликабельная карточка товара, ведущая на details.</description>
</ELEMENT>
</SUB_COMPONENT>
</COMPONENT>
<COMPONENT type="FloatingActionButton" icon="sync">
<description>Кнопка для синхронизации инвентаря.</description>
</COMPONENT>
</LAYOUT>
<USER_INTERACTIONS>
<INTERACTION>
<action>Ввод в поиск</action>
<reaction>Обновление списка с debounce.</reaction>
</INTERACTION>
<INTERACTION>
<action>Нажатие на товар</action>
<reaction>Навигация на screen_item_details.</reaction>
</INTERACTION>
</USER_INTERACTIONS>
</SCREEN>
<SCREEN id="screen_item_details" status="implemented">
<summary>Экран "Сведения о товаре"</summary>
<description>
Показывает детальную информацию о товаре, включая изображения и custom fields.
</description>
<LAYOUT>
<COMPONENT type="TopAppBar">
<description>С кнопками edit/delete.</description>
</COMPONENT>
<COMPONENT type="MainContent" orientation="vertical" scrollable="true">
<SUB_COMPONENT type="ImageCarousel" name="Images">
<description>Карусель изображений.</description>
</SUB_COMPONENT>
<SUB_COMPONENT type="DetailsSection" title="Описание">
<description>Текст description.</description>
</SUB_COMPONENT>
<SUB_COMPONENT type="FieldsGrid" name="CustomFields">
<description>Сетка custom полей.</description>
</SUB_COMPONENT>
</COMPONENT>
</LAYOUT>
<USER_INTERACTIONS>
<INTERACTION>
<action>Нажатие edit</action>
<reaction>Навигация на screen_item_edit.</reaction>
</INTERACTION>
<INTERACTION>
<action>Нажатие delete</action>
<reaction>Подтверждение и вызов func_delete_item.</reaction>
</INTERACTION>
</USER_INTERACTIONS>
</SCREEN>
<SCREEN id="screen_item_edit" status="implemented">
<summary>Экран "Редактирование товара"</summary>
<description>
Форма для создания/обновления товара с полями name, description, quantity, etc.
</description>
<LAYOUT>
<COMPONENT type="TopAppBar">
<description>С кнопкой save.</description>
</COMPONENT>
<COMPONENT type="MainContent" orientation="vertical" scrollable="true">
<SUB_COMPONENT type="TextField" name="Name">
<description>Поле ввода имени.</description>
</SUB_COMPONENT>
<SUB_COMPONENT type="Dropdown" name="Location">
<description>Выбор местоположения.</description>
</SUB_COMPONENT>
<SUB_COMPONENT type="ChipGroup" name="Labels">
<description>Выбор меток.</description>
</SUB_COMPONENT>
<SUB_COMPONENT type="ImagePicker" name="Images">
<description>Добавление изображений.</description>
</SUB_COMPONENT>
</COMPONENT>
</LAYOUT>
<USER_INTERACTIONS>
<INTERACTION>
<action>Нажатие save</action>
<reaction>Валидация и вызов func_create_item или func_update_item.</reaction>
</INTERACTION>
</USER_INTERACTIONS>
</SCREEN>
<SCREEN id="screen_search" status="implemented">
<summary>Экран "Поиск"</summary>
<description>
Специализированный экран для поиска с расширенными фильтрами.
</description>
<LAYOUT>
<COMPONENT type="TopAppBar">
<description>С поисковой строкой.</description>
</COMPONENT>
<COMPONENT type="MainContent" orientation="vertical">
<SUB_COMPONENT type="FilterSection" name="Filters">
<description>Чипы для фильтров (location, label).</description>
</SUB_COMPONENT>
<SUB_COMPONENT type="List" name="SearchResults">
<description>LazyColumn результатов.</description>
</SUB_COMPONENT>
</COMPONENT>
</LAYOUT>
<USER_INTERACTIONS>
<INTERACTION>
<action>Изменение запроса/фильтров</action>
<reaction>Обновление результатов.</reaction>
</INTERACTION>
</USER_INTERACTIONS>
</SCREEN>
</UI_SPECIFICATIONS>
<ICONOGRAPHY_GUIDE id="iconography_guide">
<summary>Руководство по использованию иконок</summary>
<description>
Этот раздел определяет стандартный набор иконок 'androidx.compose.material.icons.Icons.Filled'
для использования в приложении. Для устаревших иконок указаны актуальные замены.
</description>
<ICON name="AccountBox" path="Icons.Filled.AccountBox" />
<ICON name="AccountCircle" path="Icons.Filled.AccountCircle" />
<ICON name="Add" path="Icons.Filled.Add" />
<ICON name="AddCircle" path="Icons.Filled.AddCircle" />
<ICON name="ArrowBack" path="Icons.AutoMirrored.Filled.ArrowBack" note="Использовать AutoMirrored версию" />
<ICON name="ArrowDropDown" path="Icons.Filled.ArrowDropDown" />
<ICON name="ArrowForward" path="Icons.AutoMirrored.Filled.ArrowForward" note="Использовать AutoMirrored версию" />
<ICON name="Build" path="Icons.Filled.Build" />
<ICON name="Call" path="Icons.Filled.Call" />
<ICON name="Check" path="Icons.Filled.Check" />
<ICON name="CheckCircle" path="Icons.Filled.CheckCircle" />
<ICON name="Clear" path="Icons.Filled.Clear" />
<ICON name="Close" path="Icons.Filled.Close" />
<ICON name="Create" path="Icons.Filled.Create" />
<ICON name="DateRange" path="Icons.Filled.DateRange" />
<ICON name="Delete" path="Icons.Filled.Delete" />
<ICON name="Done" path="Icons.Filled.Done" />
<ICON name="Edit" path="Icons.Filled.Edit" />
<ICON name="Email" path="Icons.Filled.Email" />
<ICON name="ExitToApp" path="Icons.AutoMirrored.Filled.ExitToApp" note="Использовать AutoMirrored версию" />
<ICON name="Face" path="Icons.Filled.Face" />
<ICON name="Favorite" path="Icons.Filled.Favorite" />
<ICON name="FavoriteBorder" path="Icons.Filled.FavoriteBorder" />
<ICON name="Home" path="Icons.Filled.Home" />
<ICON name="Info" path="Icons.AutoMirrored.Filled.Info" note="Использовать AutoMirrored версию" />
<ICON name="KeyboardArrowDown" path="Icons.Filled.KeyboardArrowDown" />
<ICON name="KeyboardArrowLeft" path="Icons.AutoMirrored.Filled.KeyboardArrowLeft" note="Использовать AutoMirrored версию" />
<ICON name="KeyboardArrowRight" path="Icons.AutoMirrored.Filled.KeyboardArrowRight" note="Использовать AutoMirrored версию" />
<ICON name="KeyboardArrowUp" path="Icons.Filled.KeyboardArrowUp" />
<ICON name="Label" path="Icons.AutoMirrored.Filled.Label" note="Использовать AutoMirrored версию" />
<ICON name="List" path="Icons.AutoMirrored.Filled.List" note="Использовать AutoMirrored версию" />
<ICON name="LocationOn" path="Icons.Filled.LocationOn" />
<ICON name="Lock" path="Icons.Filled.Lock" />
<ICON name="MailOutline" path="Icons.Filled.MailOutline" />
<ICON name="Menu" path="Icons.Filled.Menu" />
<ICON name="MoreVert" path="Icons.Filled.MoreVert" />
<ICON name="Notifications" path="Icons.Filled.Notifications" />
<ICON name="Person" path="Icons.Filled.Person" />
<ICON name="Phone" path="Icons.Filled.Phone" />
<ICON name="Place" path="Icons.Filled.Place" />
<ICON name="PlayArrow" path="Icons.Filled.PlayArrow" />
<ICON name="Refresh" path="Icons.Filled.Refresh" />
<ICON name="Search" path="Icons.Filled.Search" />
<ICON name="Send" path="Icons.AutoMirrored.Filled.Send" note="Использовать AutoMirrored версию" />
<ICON name="Settings" path="Icons.Filled.Settings" />
<ICON name="Share" path="Icons.Filled.Share" />
<ICON name="ShoppingCart" path="Icons.Filled.ShoppingCart" />
<ICON name="Star" path="Icons.Filled.Star" />
<ICON name="ThumbUp" path="Icons.Filled.ThumbUp" />
<ICON name="Warning" path="Icons.Filled.Warning" />
</ICONOGRAPHY_GUIDE>
<IMPLEMENTATION_MAP>
<!-- Use Cases -->
<USE_CASE id="uc_get_stats" file_ref="domain/src/main/java/com/homebox/lens/domain/usecase/GetStatisticsUseCase.kt" />
<USE_CASE id="uc_search_items" file_ref="domain/src/main/java/com/homebox/lens/domain/usecase/SearchItemsUseCase.kt" />
<USE_CASE id="uc_sync_inventory" file_ref="domain/src/main/java/com/homebox/lens/domain/usecase/SyncInventoryUseCase.kt" />
<USE_CASE id="uc_get_item_details" file_ref="domain/src/main/java/com/homebox/lens/domain/usecase/GetItemDetailsUseCase.kt" />
<USE_CASE id="uc_create_item" file_ref="domain/src/main/java/com/homebox/lens/domain/usecase/CreateItemUseCase.kt" />
<USE_CASE id="uc_update_item" file_ref="domain/src/main/java/com/homebox/lens/domain/usecase/UpdateItemUseCase.kt" />
<USE_CASE id="uc_delete_item" file_ref="domain/src/main/java/com/homebox/lens/domain/usecase/DeleteItemUseCase.kt" />
<USE_CASE id="uc_get_all_labels" file_ref="domain/src/main/java/com/homebox/lens/domain/usecase/GetAllLabelsUseCase.kt" />
<USE_CASE id="uc_get_all_locations" file_ref="domain/src/main/java/com/homebox/lens/domain/usecase/GetAllLocationsUseCase.kt" />
<USE_CASE id="uc_login" file_ref="domain/src/main/java/com/homebox/lens/domain/usecase/LoginUseCase.kt" />
<!-- UI Screens -->
<UI_SCREEN id="screen_dashboard" file_ref="app/src/main/java/com/homebox/lens/ui/screen/dashboard/DashboardScreen.kt" />
<UI_SCREEN id="screen_inventory_list" file_ref="app/src/main/java/com/homebox/lens/ui/screen/inventorylist/InventoryListScreen.kt" />
<UI_SCREEN id="screen_item_details" file_ref="app/src/main/java/com/homebox/lens/ui/screen/itemdetails/ItemDetailsScreen.kt" />
<UI_SCREEN id="screen_item_edit" file_ref="app/src/main/java/com/homebox/lens/ui/screen/itemedit/ItemEditScreen.kt" />
<UI_SCREEN id="screen_labels_list" file_ref="app/src/main/java/com/homebox/lens/ui/screen/labelslist/LabelsListScreen.kt" />
<UI_SCREEN id="screen_locations_list" file_ref="app/src/main/java/com/homebox/lens/ui/screen/locationslist/LocationsListScreen.kt" />
<UI_SCREEN id="screen_search" file_ref="app/src/main/java/com/homebox/lens/ui/screen/search/SearchScreen.kt" />
<UI_SCREEN id="screen_setup" file_ref="app/src/main/java/com/homebox/lens/ui/screen/setup/SetupScreen.kt" />
</IMPLEMENTATION_MAP>
</PROJECT_SPECIFICATION>
]]>
</CODE>
</PAYLOAD>
</WORK_ORDER>
</TASK_BATCH>
</FOR_AGENT>