211 lines
9.5 KiB
XML
211 lines
9.5 KiB
XML
<!-- 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> |