This commit is contained in:
2025-08-14 15:34:05 +03:00
parent ecf614e4c2
commit 7816bb3464
27 changed files with 1795 additions and 335 deletions

View File

@@ -0,0 +1,211 @@
<!-- tasks/003_implement_labels_screen_ui.xml -->
<TASK status="completed">
<WORK_ORDER id="task-20250812-115003">
<ACTION>MODIFY_CODE</ACTION>
<TARGET_FILE>app/src/main/java/com/homebox/lens/ui/screen/labelslist/LabelsListScreen.kt</TARGET_FILE>
<GOAL>
Реализовать UI для экрана "Метки" (LabelsListScreen), заменив заглушку.
Экран должен получать данные от LabelsListViewModel, отображать список меток в LazyColumn,
а также содержать TopAppBar с кнопкой "назад" и FloatingActionButton для добавления новой метки,
в полном соответствии со спецификацией screen_labels_list.
</GOAL>
<CONTEXT_FILES>
<FILE>tech_spec.txt</FILE>
<FILE>project_structure.txt</FILE>
<FILE>app/src/main/java/com/homebox/lens/ui/screen/labelslist/LabelsListViewModel.kt</FILE>
</CONTEXT_FILES>
<CONTRACT>
<CONSTRAINTS>
<CONSTRAINT>Полностью заменить содержимое файла `LabelsListScreen.kt`.</CONSTRAINT>
<CONSTRAINT>Главная функция `LabelsListScreen` должна получать `LabelsListViewModel` через `hiltViewModel()`.</CONSTRAINT>
<CONSTRAINT>Состояние UI должно собираться из `viewModel.uiState` с использованием `collectAsStateWithLifecycle`.</CONSTRAINT>
<CONSTRAINT>Для отображения списка должен использоваться `LazyColumn`.</CONSTRAINT>
<CONSTRAINT>Каждый элемент списка должен быть реализован в отдельном Composable `LabelListItem`.</CONSTRAINT>
<CONSTRAINT>`TopAppBar` должен содержать `IconButton` для навигации назад.</CONSTRAINT>
<CONSTRAINT>`Scaffold` должен содержать `FloatingActionButton`.</CONSTRAINT>
</CONSTRAINTS>
</CONTRACT>
<PAYLOAD mode="FULL_CONTENT">
<![CDATA[
// [FILE] app/src/main/java/com/homebox/lens/ui/screen/labelslist/LabelsListScreen.kt
// [PACKAGE]
package com.homebox.lens.ui.screen.labelslist
// [IMPORTS]
import androidx.compose.foundation.clickable
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.filled.Add
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Label
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.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.homebox.lens.domain.model.Label
import com.homebox.lens.ui.theme.HomeboxLensTheme
// [COMPOSABLE_FUNCTION] LabelsListScreen (Stateful)
/**
* [CONTRACT]
* Контейнерный Composable для экрана "Метки", управляющий состоянием.
* Он получает данные от ViewModel и передает их в state-less Composable `LabelsListContent`.
*
* @param viewModel ViewModel, предоставляемая Hilt, для доступа к бизнес-логике и состоянию UI.
* @param onNavigateBack Лямбда для обработки действия "назад".
* @param onLabelClick Лямбда для обработки нажатия на метку, передает ID метки.
* @param onAddLabelClick Лямбда для обработки нажатия на FAB.
* @sideeffect Получает состояние UI (`uiState`) из `viewModel`.
* @sideeffect Вызывает навигационные лямбды в ответ на действия пользователя.
*/
@Composable
fun LabelsListScreen(
viewModel: LabelsListViewModel = hiltViewModel(),
onNavigateBack: () -> Unit,
onLabelClick: (String) -> Unit,
onAddLabelClick: () -> Unit
) {
// [ACTION] Сбор состояния из ViewModel
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LabelsListContent(
labels = uiState.labels,
onNavigateBack = onNavigateBack,
onLabelClick = onLabelClick,
onAddLabelClick = onAddLabelClick
)
// [COHERENCE_CHECK_PASSED] Состояние передается в stateless composable.
}
// [END_FUNCTION]
// [COMPOSABLE_FUNCTION] LabelsListContent (Stateless)
/**
* [CONTRACT]
* Отображает UI для экрана "Метки". Этот Composable не имеет своего состояния (stateless).
*
* @param labels Список объектов `Label` для отображения.
* @param onNavigateBack Лямбда для обработки действия "назад".
* @param onLabelClick Лямбда для обработки нажатия на метку.
* @param onAddLabelClick Лямбда для обработки нажатия на FAB.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun LabelsListContent(
labels: List<Label>,
onNavigateBack: () -> Unit,
onLabelClick: (String) -> Unit,
onAddLabelClick: () -> Unit
) {
// [CORE-LOGIC] Основная разметка экрана
Scaffold(
topBar = {
TopAppBar(
title = { Text("Метки") },
navigationIcon = {
IconButton(onClick = onNavigateBack) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = "Назад" // TODO: Заменить на stringResource
)
}
}
)
},
floatingActionButton = {
FloatingActionButton(onClick = onAddLabelClick) {
Icon(
imageVector = Icons.Filled.Add,
contentDescription = "Добавить метку" // TODO: Заменить на stringResource
)
}
}
) { innerPadding ->
// [CORE-LOGIC] Список меток
LazyColumn(modifier = Modifier.padding(innerPadding)) {
items(items = labels, key = { it.id }) { label ->
LabelListItem(
label = label,
onClick = { onLabelClick(label.id) }
)
}
}
}
}
// [END_FUNCTION]
// [HELPER] LabelListItem
/**
* [CONTRACT]
* Отображает один элемент списка для метки.
*
* @param label Объект `Label`, данные которого нужно отобразить.
* @param onClick Лямбда, вызываемая при нажатии на элемент.
*/
@Composable
private fun LabelListItem(
label: Label,
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
ListItem(
headlineContent = { Text(label.name) },
leadingContent = {
Icon(
imageVector = Icons.Filled.Label,
contentDescription = null
)
},
modifier = modifier.clickable(onClick = onClick)
)
}
// [END_FUNCTION]
// [PREVIEW]
@Preview(showBackground = true)
@Composable
private fun LabelsListContentPreview() {
val sampleLabels = listOf(
Label(id = "1", name = "Электроника", color = "#FF0000"),
Label(id = "2", name = "Книги", color = "#00FF00"),
Label(id = "3", name = "Инструменты", color = "#0000FF")
)
HomeboxLensTheme {
LabelsListContent(
labels = sampleLabels,
onNavigateBack = {},
onLabelClick = {},
onAddLabelClick = {}
)
}
}
// [END_PREVIEW]
// [END_FILE]
]]>
</PAYLOAD>
<IMPLEMENTATION_HINTS>
<HINT>Это задание заменяет весь контент файла `app/src/main/java/com/homebox/lens/ui/screen/labelslist/LabelsListScreen.kt`.</HINT>
<HINT>Основная логика разделена на два Composable: `LabelsListScreen` (stateful) и `LabelsListContent` (stateless), что является хорошей практикой.</HINT>
<HINT>Функция `LabelsListScreen` отвечает за взаимодействие с ViewModel.</HINT>
<HINT>Функция `LabelsListContent` отвечает исключительно за отображение UI на основе переданных данных.</HINT>
<HINT>Убедись, что все импорты, указанные в секции [IMPORTS], добавлены корректно. Особенно важны `hiltViewModel` и `collectAsStateWithLifecycle`.</HINT>
</IMPLEMENTATION_HINTS>
</WORK_ORDER>
</TASK>