Files
homebox_lens/tasks/completed/20250813_080300_implement_labels_screen.xml
2025-08-14 15:34:05 +03:00

237 lines
11 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.

<!-- tasks/20250813_080300_implement_labels_screen.xml -->
<TASK status="completed">
<WORK_ORDER id="task-20250813080300-001">
<ACTION>MODIFY_CODE</ACTION>
<TARGET_FILE>app/src/main/java/com/homebox/lens/ui/screen/labelslist/LabelsListScreen.kt</TARGET_FILE>
<GOAL>
Реализовать UI для экрана "Метки" (`LabelsListScreen`) в соответствии со спецификацией `screen_labels_list`.
Это включает в себя создание Composable-функции, которая:
1. Использует `Scaffold` с `TopAppBar` и `FloatingActionButton`.
2. Получает состояние (список меток, статус загрузки, ошибки) от `LabelsListViewModel`.
3. Отображает список меток с помощью `LazyColumn`.
4. Обрабатывает клики по элементам списка для навигации на экран инвентаря с фильтром по метке.
5. Обрабатывает нажатие на FAB для создания новой метки (пока что через лог).
6. Отображает индикатор загрузки и сообщения об ошибках или пустом списке.
7. Строго следует принципам Design by Contract, использует иконки из гайда и строки из ресурсов.
</GOAL>
<CONTEXT_FILES>
<FILE>PROJECT_SPECIFICATION.xml</FILE>
<FILE>PROJECT_STRUCTURE.xml</FILE>
<FILE>app/src/main/java/com/homebox/lens/ui/screen/labelslist/LabelsListViewModel.kt</FILE>
<FILE>domain/src/main/java/com/homebox/lens/domain/model/Label.kt</FILE>
<FILE>app/src/main/java/com/homebox/lens/navigation/Screen.kt</FILE>
</CONTEXT_FILES>
<CONTRACT>
<![CDATA[
// [PACKAGE] com.homebox.lens.ui.screen.labelslist
// [FILE] app/src/main/java/com/homebox/lens/ui/screen/labelslist/LabelsListScreen.kt
// [IMPORTS]
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize
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.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.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.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 androidx.navigation.NavController
import com.homebox.lens.R
import com.homebox.lens.domain.model.Label
import timber.log.Timber
// [CONTRACT]
/**
* [CONTRACT]
* Отображает экран со списком всех меток.
*
* @param navController Контроллер навигации для перемещения между экранами.
* @param viewModel ViewModel, предоставляющая состояние UI для экрана меток.
*
* @precondition `navController` должен быть корректно инициализирован и способен обрабатывать навигационные события.
* @precondition `viewModel` должен быть доступен через Hilt.
* @postcondition Экран отображает список меток или соответствующее состояние (загрузка, ошибка, пустой список).
* @sideeffect Пользовательские действия (клики) инициируют навигационные команды через `navController` или логируются.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun LabelsListScreen(
navController: NavController,
viewModel: LabelsListViewModel = hiltViewModel()
) {
// [ACTION]
val uiState by viewModel.uiState.collectAsState()
val logger = Timber.tag("LabelsListScreen")
Scaffold(
topBar = {
TopAppBar(
title = { Text(text = stringResource(id = R.string.screen_title_labels)) },
navigationIcon = {
IconButton(onClick = {
logger.info { "[ACTION] Navigate up initiated." }
navController.navigateUp()
}) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
contentDescription = stringResource(id = R.string.content_desc_navigate_back)
)
}
}
)
},
floatingActionButton = {
FloatingActionButton(onClick = {
// [ACTION]
// TODO: Открыть диалог или экран создания метки
logger.info { "[ACTION] FAB clicked: Initiate create new label flow." }
}) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = stringResource(id = R.string.content_desc_create_label)
)
}
}
) { paddingValues ->
// [CORE-LOGIC]
Box(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues),
contentAlignment = Alignment.Center
) {
when {
uiState.isLoading -> {
// [STATE_BRANCH] Loading
CircularProgressIndicator()
}
uiState.error != null -> {
// [STATE_BRANCH] Error
Text(text = uiState.error ?: stringResource(id = R.string.error_unknown))
}
uiState.labels.isEmpty() -> {
// [STATE_BRANCH] Empty
Text(text = stringResource(id = R.string.labels_list_empty))
}
else -> {
// [STATE_BRANCH] Success
LabelsList(
labels = uiState.labels,
onLabelClick = { label ->
// [ACTION]
// TODO: Реализовать навигацию на экран инвентаря с фильтром
logger.info { "[ACTION] Label clicked: ${label.id}. Navigating to inventory list." }
// navController.navigate(Screen.InventoryList.withFilter("label", label.id))
}
)
}
}
}
}
// [COHERENCE_CHECK_PASSED]
}
// [END_FUNCTION] LabelsListScreen
// [HELPER]
/**
* [CONTRACT]
* Composable-функция для отображения списка меток.
*
* @param labels Список объектов `Label` для отображения.
* @param onLabelClick Лямбда-функция, вызываемая при нажатии на элемент списка.
*
* @precondition `labels` не должен быть null.
* @postcondition Отображается вертикальный прокручиваемый список.
*/
@Composable
private fun LabelsList(
labels: List<Label>,
onLabelClick: (Label) -> Unit,
modifier: Modifier = Modifier
) {
// [CORE-LOGIC]
LazyColumn(
modifier = modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
items(labels) { label ->
LabelListItem(
label = label,
onClick = { onLabelClick(label) }
)
}
}
}
// [END_FUNCTION] LabelsList
// [HELPER]
/**
* [CONTRACT]
* Composable-функция для отображения одного элемента в списке меток.
*
* @param label Объект `Label`, который нужно отобразить.
* @param onClick Лямбда-функция, вызываемая при нажатии на элемент.
*
* @precondition `label` не должен быть null.
* @postcondition Отображается кликабельный элемент списка с иконкой и названием метки.
*/
@Composable
private fun LabelListItem(
label: Label,
onClick: () -> Unit
) {
// [CORE-LOGIC]
ListItem(
headlineContent = { Text(text = label.name) },
leadingContent = {
Icon(
imageVector = Icons.AutoMirrored.Filled.Label,
contentDescription = stringResource(id = R.string.content_desc_label_icon)
)
},
modifier = Modifier.clickable(onClick = onClick)
)
}
// [END_FUNCTION] LabelListItem
// [END_FILE] LabelsListScreen.kt
]]>
</CONTRACT>
<IMPLEMENTATION_HINTS>
<HINT>Используйте `@HiltViewModel` для получения экземпляра `LabelsListViewModel`.</HINT>
<HINT>Собирайте `uiState` из ViewModel с помощью `collectAsState()` для автоматического обновления UI при изменении состояния.</HINT>
<HINT>Используйте `Scaffold` для базовой структуры экрана (TopAppBar, FAB, основное содержимое).</HINT>
<HINT>Для навигации назад используйте `navController.navigateUp()`.</HINT>
<HINT>Используйте `LazyColumn` для эффективного отображения потенциально длинных списков меток.</HINT>
<HINT>Обязательно добавьте новые строковые ресурсы (`screen_title_labels`, `content_desc_navigate_back`, `content_desc_create_label`, `labels_list_empty`, `content_desc_label_icon`) в `strings.xml`.</HINT>
<HINT>Иконки должны браться из `androidx.compose.material.icons` в соответствии с `ICONOGRAPHY_GUIDE`.</HINT>
</IMPLEMENTATION_HINTS>
</WORK_ORDER>
</TASK>