Fix: Handle missing 'color', 'isArchived' and 'value' fields in DTOs and mappers to prevent JsonDataException

This commit is contained in:
2025-10-06 09:40:47 +03:00
parent 9500d747b1
commit 78b827f29e
31 changed files with 691 additions and 619 deletions

View File

@@ -45,6 +45,10 @@ android {
buildFeatures { buildFeatures {
compose = true compose = true
buildConfig = true buildConfig = true
aidl = false
renderScript = false
resValues = true
shaders = false
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion = Versions.composeCompiler kotlinCompilerExtensionVersion = Versions.composeCompiler
@@ -65,6 +69,8 @@ dependencies {
implementation(project(":data")) implementation(project(":data"))
// [MODULE_DEPENDENCY] Domain module (transitively included via data, but explicit for clarity) // [MODULE_DEPENDENCY] Domain module (transitively included via data, but explicit for clarity)
implementation(project(":domain")) implementation(project(":domain"))
implementation(project(":ui"))
implementation(project(":feature:inventory"))
// [DEPENDENCY] AndroidX // [DEPENDENCY] AndroidX
implementation(Libs.coreKtx) implementation(Libs.coreKtx)

View File

@@ -15,7 +15,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument import androidx.navigation.navArgument
import com.homebox.lens.ui.screen.dashboard.DashboardScreen import com.homebox.lens.ui.screen.dashboard.DashboardScreen
import com.homebox.lens.ui.screen.inventorylist.InventoryListScreen import com.homebox.lens.feature.inventory.ui.InventoryScreen
import com.homebox.lens.ui.screen.itemdetails.ItemDetailsScreen import com.homebox.lens.ui.screen.itemdetails.ItemDetailsScreen
import com.homebox.lens.ui.screen.itemedit.ItemEditScreen import com.homebox.lens.ui.screen.itemedit.ItemEditScreen
import com.homebox.lens.ui.screen.labelslist.LabelsListScreen import com.homebox.lens.ui.screen.labelslist.LabelsListScreen
@@ -26,6 +26,8 @@ import com.homebox.lens.ui.screen.search.SearchScreen
import com.homebox.lens.ui.screen.setup.SetupScreen import com.homebox.lens.ui.screen.setup.SetupScreen
import com.homebox.lens.ui.screen.settings.SettingsScreen import com.homebox.lens.ui.screen.settings.SettingsScreen
import com.homebox.lens.ui.screen.splash.SplashScreen import com.homebox.lens.ui.screen.splash.SplashScreen
import com.homebox.lens.navigation.NavigationActions
import com.homebox.lens.ui.navigation.Screen
// [END_IMPORTS] // [END_IMPORTS]
// [ENTITY: Function('NavGraph')] // [ENTITY: Function('NavGraph')]
@@ -59,7 +61,9 @@ fun NavGraph(
composable(route = Screen.Setup.route) { composable(route = Screen.Setup.route) {
SetupScreen(onSetupComplete = { SetupScreen(onSetupComplete = {
navController.navigate(Screen.Dashboard.route) { navController.navigate(Screen.Dashboard.route) {
popUpTo(Screen.Setup.route) { inclusive = true } popUpTo(Screen.Setup.route) {
inclusive = true
}
} }
}) })
} }
@@ -69,8 +73,8 @@ fun NavGraph(
navigationActions = navigationActions navigationActions = navigationActions
) )
} }
composable(route = Screen.InventoryList.route) { composable(route = Screen.Inventory.route) {
InventoryListScreen( InventoryScreen(
currentRoute = currentRoute, currentRoute = currentRoute,
navigationActions = navigationActions navigationActions = navigationActions
) )
@@ -105,7 +109,7 @@ fun NavGraph(
navigationActions = navigationActions, navigationActions = navigationActions,
onLocationClick = { locationId -> onLocationClick = { locationId ->
// [AI_NOTE]: Navigate to a pre-filtered inventory list screen // [AI_NOTE]: Navigate to a pre-filtered inventory list screen
navController.navigate(Screen.InventoryList.route) navController.navigate(Screen.Inventory.route)
}, },
onAddNewLocationClick = { onAddNewLocationClick = {
navController.navigate(Screen.LocationEdit.createRoute("new")) navController.navigate(Screen.LocationEdit.createRoute("new"))

View File

@@ -1,38 +0,0 @@
// [FILE] InventoryListScreen.kt
// [SEMANTICS] app, ui, screen, list
package com.homebox.lens.ui.screen.inventorylist
// [IMPORTS]
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.homebox.lens.R
import com.homebox.lens.navigation.NavigationActions
import com.homebox.lens.ui.common.MainScaffold
// [END_IMPORTS]
// [ENTITY: Function('InventoryListScreen')]
// [RELATION: Function('InventoryListScreen')] -> [DEPENDS_ON] -> [Class('NavigationActions')]
// [RELATION: Function('InventoryListScreen')] -> [CALLS] -> [Function('MainScaffold')]
/**
* @summary Composable function for the "Inventory List" screen.
* @param currentRoute The current route to highlight the active item in the Drawer.
* @param navigationActions The object with navigation actions.
*/
@Composable
fun InventoryListScreen(
currentRoute: String?,
navigationActions: NavigationActions
) {
MainScaffold(
topBarTitle = stringResource(id = R.string.inventory_list_title),
currentRoute = currentRoute,
navigationActions = navigationActions
) {
// [AI_NOTE]: Implement Inventory List Screen UI
Text(text = "Inventory List Screen")
}
}
// [END_ENTITY: Function('InventoryListScreen')]
// [END_FILE_InventoryListScreen.kt]

View File

@@ -1,20 +0,0 @@
// [FILE] InventoryListViewModel.kt
// [SEMANTICS] app, ui, viewmodel, list
package com.homebox.lens.ui.screen.inventorylist
// [IMPORTS]
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
// [END_IMPORTS]
// [ENTITY: ViewModel('InventoryListViewModel')]
/**
* @summary ViewModel for the inventory list screen.
*/
@HiltViewModel
class InventoryListViewModel @Inject constructor() : ViewModel() {
// [AI_NOTE]: Implement UI state
}
// [END_ENTITY: ViewModel('InventoryListViewModel')]
// [END_FILE_InventoryListViewModel.kt]

View File

@@ -36,7 +36,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
import com.homebox.lens.R import com.homebox.lens.R
import com.homebox.lens.domain.model.Label import com.homebox.lens.domain.model.Label
import com.homebox.lens.navigation.NavigationActions import com.homebox.lens.navigation.NavigationActions
import com.homebox.lens.navigation.Screen import com.homebox.lens.ui.navigation.Screen
import com.homebox.lens.ui.common.MainScaffold import com.homebox.lens.ui.common.MainScaffold
import timber.log.Timber import timber.log.Timber
// [END_IMPORTS] // [END_IMPORTS]

View File

@@ -12,7 +12,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.NavController import androidx.navigation.NavController
import com.homebox.lens.navigation.Screen import com.homebox.lens.ui.navigation.Screen
import com.homebox.lens.ui.screen.setup.SetupViewModel import com.homebox.lens.ui.screen.setup.SetupViewModel
import timber.log.Timber import timber.log.Timber
// [END_IMPORTS] // [END_IMPORTS]
@@ -50,7 +50,9 @@ fun SplashScreen(
} }
navController.navigate(destination) { navController.navigate(destination) {
popUpTo(Screen.Splash.route) { inclusive = true } popUpTo(Screen.Splash.route) {
inclusive = true
}
} }
} }
} }

View File

@@ -1,121 +0,0 @@
<resources>
<string name="app_name">Homebox Lens</string>
<!-- Common -->
<string name="create">Create</string>
<string name="edit">Edit</string>
<string name="delete">Delete</string>
<string name="search">Search</string>
<string name="logout">Logout</string>
<string name="no_location">No location</string>
<string name="items_not_found">Items not found</string>
<string name="error_loading_failed">Failed to load data. Please try again.</string>
<!-- Content Descriptions -->
<string name="cd_open_navigation_drawer">Open navigation drawer</string>
<string name="cd_scan_qr_code">Scan QR code</string>
<string name="cd_navigate_back">Navigate back</string>
<string name="cd_add_new_location">Add new location</string>
<string name="content_desc_add_label">Add new label</string>
<!-- Dashboard Screen -->
<string name="dashboard_title">Dashboard</string>
<string name="dashboard_section_quick_stats">Quick Stats</string>
<string name="dashboard_section_recently_added">Recently Added</string>
<string name="dashboard_section_locations">Locations</string>
<string name="dashboard_section_labels">Labels</string>
<string name="location_chip_label">%1$s (%2$d)</string>
<!-- Dashboard Statistics -->
<string name="dashboard_stat_total_items">Total Items</string>
<string name="dashboard_stat_total_value">Total Value</string>
<string name="dashboard_stat_total_labels">Total Labels</string>
<string name="dashboard_stat_total_locations">Total Locations</string>
<!-- Navigation -->
<string name="nav_locations">Locations</string>
<string name="nav_labels">Labels</string>
<!-- Screen Titles -->
<string name="inventory_list_title">Inventory</string>
<!-- Screen Titles -->
<string name="item_details_title">Details</string>
<string name="item_edit_title">Edit Item</string>
<string name="labels_list_title">Labels</string>
<string name="locations_list_title">Locations</string>
<string name="search_title">Search</string>
<string name="save_item">Save</string>
<string name="item_name">Name</string>
<string name="item_description">Description</string>
<string name="item_quantity">Quantity</string>
<!-- Location Edit Screen -->
<string name="location_edit_title_create">Create Location</string>
<string name="location_edit_title_edit">Edit Location</string>
<!-- Locations List Screen -->
<string name="locations_not_found">Locations not found. Press + to add a new one.</string>
<string name="item_count">Items: %1$d</string>
<string name="cd_more_options">More options</string>
<!-- Setup Screen -->
<string name="setup_title">Server Setup</string>
<string name="setup_server_url_label">Server URL</string>
<string name="setup_username_label">Username</string>
<string name="setup_password_label">Password</string>
<string name="setup_connect_button">Connect</string>
<!-- Labels List Screen -->
<string name="screen_title_labels">Labels</string>
<string name="content_desc_navigate_back">Navigate back</string>
<string name="content_desc_create_label">Create new label</string>
<string name="content_desc_label_icon">Label icon</string>
<string name="no_labels_found">No labels found.</string>
<string name="dialog_title_create_label">Create Label</string>
<string name="dialog_field_label_name">Label Name</string>
<string name="dialog_button_create">Create</string>
<string name="dialog_button_cancel">Cancel</string>
<!-- Inventory List Screen -->
<string name="content_desc_sync_inventory">Sync inventory</string>
<!-- Item Details Screen -->
<string name="content_desc_edit_item">Edit item</string>
<string name="content_desc_delete_item">Delete item</string>
<string name="section_title_description">Description</string>
<string name="placeholder_no_description">No description</string>
<string name="section_title_details">Details</string>
<string name="label_quantity">Quantity</string>
<string name="label_location">Location</string>
<string name="section_title_labels">Labels</string>
<!-- Item Edit Screen -->
<string name="item_edit_title_create">Create item</string>
<string name="content_desc_save_item">Save item</string>
<string name="label_name">Name</string>
<string name="label_description">Description</string>
<!-- Search Screen -->
<string name="placeholder_search_items">Search items...</string>
<!-- Setup Screen -->
<string name="screen_title_setup">Setup</string>
<!-- Label Edit Screen -->
<string name="label_edit_title_create">Create label</string>
<string name="label_edit_title_edit">Edit label</string>
<string name="label_name_edit">Label name</string>
<!-- Common Actions -->
<string name="back">Back</string>
<string name="save">Save</string>
<!-- Color Picker -->
<string name="label_color">Color</string>
<string name="label_hex_color">HEX color code</string>
</resources>

View File

@@ -2,86 +2,112 @@
<string name="app_name">Homebox Lens</string> <string name="app_name">Homebox Lens</string>
<!-- Common --> <!-- Common -->
<string name="create">Создать</string> <string name="create">Create</string>
<string name="edit">Редактировать</string> <string name="edit">Edit</string>
<string name="delete">Удалить</string> <string name="delete">Delete</string>
<string name="search">Поиск</string> <string name="search">Search</string>
<string name="logout">Выйти</string> <string name="logout">Logout</string>
<string name="no_location">Нет локации</string> <string name="no_location">No location</string>
<string name="items_not_found">Элементы не найдены</string> <string name="items_not_found">Items not found</string>
<string name="error_loading_failed">Не удалось загрузить данные. Пожалуйста, попробуйте еще раз.</string> <string name="error_loading_failed">Failed to load data. Please try again.</string>
<!-- Content Descriptions --> <!-- Content Descriptions -->
<string name="cd_open_navigation_drawer">Открыть боковое меню</string> <string name="cd_open_navigation_drawer">Open navigation drawer</string>
<string name="setup_subtitle">Enter your Homebox server details to connect.</string> <string name="cd_scan_qr_code">Scan QR code</string>
<string name="cd_scan_qr_code">Сканировать QR-код</string> <string name="cd_navigate_back">Navigate back</string>
<string name="cd_navigate_back">Вернуться назад</string> <string name="cd_add_new_location">Add new location</string>
<string name="cd_add_new_location">Добавить новую локацию</string> <string name="content_desc_add_label">Add new label</string>
<string name="content_desc_add_label">Добавить новую метку</string>
<!-- Inventory List Screen -->
<string name="content_desc_sync_inventory">Синхронизировать инвентарь</string>
<!-- Item Details Screen -->
<string name="content_desc_edit_item">Редактировать элемент</string>
<string name="content_desc_delete_item">Удалить элемент</string>
<string name="section_title_description">Описание</string>
<string name="placeholder_no_description">Нет описания</string>
<string name="section_title_details">Детали</string>
<string name="label_quantity">Количество</string>
<string name="label_location">Местоположение</string>
<string name="section_title_labels">Метки</string>
<!-- Item Edit Screen -->
<string name="item_edit_title_create">Создать элемент</string>
<string name="content_desc_save_item">Сохранить элемент</string>
<string name="label_name">Название</string>
<!-- Search Screen -->
<string name="placeholder_search_items">Поиск элементов...</string>
<!-- Dashboard Screen --> <!-- Dashboard Screen -->
<string name="dashboard_title">Главная</string> <string name="dashboard_title">Dashboard</string>
<string name="dashboard_section_quick_stats">Быстрая статистика</string> <string name="dashboard_section_quick_stats">Quick Stats</string>
<string name="dashboard_section_recently_added">Недавно добавлено</string> <string name="dashboard_section_recently_added">Recently Added</string>
<string name="dashboard_section_locations">Места хранения</string> <string name="dashboard_section_locations">Locations</string>
<string name="dashboard_section_labels">Метки</string> <string name="dashboard_section_labels">Labels</string>
<string name="location_chip_label">%1$s (%2$d)</string> <string name="location_chip_label">%1$s (%2$d)</string>
<!-- Dashboard Statistics --> <!-- Dashboard Statistics -->
<string name="dashboard_stat_total_items">Всего вещей</string> <string name="dashboard_stat_total_items">Total Items</string>
<string name="dashboard_stat_total_value">Общая стоимость</string> <string name="dashboard_stat_total_value">Total Value</string>
<string name="dashboard_stat_total_labels">Всего меток</string> <string name="dashboard_stat_total_labels">Total Labels</string>
<string name="dashboard_stat_total_locations">Всего локаций</string> <string name="dashboard_stat_total_locations">Total Locations</string>
<!-- Navigation --> <!-- Navigation -->
<string name="nav_locations">Локации</string> <string name="nav_locations">Locations</string>
<string name="nav_labels">Метки</string> <string name="nav_labels">Labels</string>
<!-- Screen Titles --> <!-- Screen Titles -->
<string name="inventory_list_title">Инвентарь</string> <string name="inventory_list_title">Inventory</string>
<string name="item_details_title">Детали</string>
<string name="item_edit_title">Редактирование</string> <!-- Screen Titles -->
<string name="labels_list_title">Метки</string> <string name="item_details_title">Details</string>
<string name="locations_list_title">Места хранения</string> <string name="item_edit_title">Edit Item</string>
<string name="search_title">Поиск</string> <string name="labels_list_title">Labels</string>
<string name="locations_list_title">Locations</string>
<string name="search_title">Search</string>
<string name="save_item">Save</string>
<string name="item_name">Name</string>
<string name="item_description">Description</string>
<string name="item_quantity">Quantity</string>
<!-- Location Edit Screen -->
<string name="location_edit_title_create">Create Location</string>
<string name="location_edit_title_edit">Edit Location</string>
<!-- Locations List Screen -->
<string name="locations_not_found">Locations not found. Press + to add a new one.</string>
<string name="item_count">Items: %1$d</string>
<string name="cd_more_options">More options</string>
<!-- Setup Screen -->
<string name="setup_title">Server Setup</string>
<string name="setup_server_url_label">Server URL</string>
<string name="setup_username_label">Username</string>
<string name="setup_password_label">Password</string>
<string name="setup_connect_button">Connect</string>
<!-- Labels List Screen -->
<string name="screen_title_labels">Labels</string>
<string name="content_desc_navigate_back">Navigate back</string>
<string name="content_desc_create_label">Create new label</string>
<string name="content_desc_label_icon">Label icon</string>
<string name="no_labels_found">No labels found.</string>
<string name="dialog_title_create_label">Create Label</string>
<string name="dialog_field_label_name">Label Name</string>
<string name="dialog_button_create">Create</string>
<string name="dialog_button_cancel">Cancel</string>
<!-- Inventory List Screen -->
<string name="content_desc_sync_inventory">Sync inventory</string>
<!-- Item Details Screen -->
<string name="content_desc_edit_item">Edit item</string>
<string name="content_desc_delete_item">Delete item</string>
<string name="section_title_description">Description</string>
<string name="placeholder_no_description">No description</string>
<string name="section_title_details">Details</string>
<string name="label_quantity">Quantity</string>
<string name="label_location">Location</string>
<string name="section_title_labels">Labels</string>
<!-- Item Edit Screen -->
<string name="item_edit_title_create">Create item</string>
<string name="content_desc_save_item">Save item</string>
<string name="label_name">Name</string>
<string name="label_description">Description</string>
<string name="save_item">Сохранить</string>
<string name="item_name">Название</string>
<string name="item_description">Описание</string>
<string name="item_quantity">Количество</string>
<string name="item_edit_general_information">General Information</string> <string name="item_edit_general_information">General Information</string>
<string name="item_edit_location">Location</string> <string name="item_edit_location">Location</string>
<string name="item_edit_select_location">Select Location</string>
<string name="item_edit_labels">Labels</string> <string name="item_edit_labels">Labels</string>
<string name="item_edit_select_labels">Select Labels</string> <string name="item_edit_select_labels">Select Labels</string>
<string name="dialog_ok">OK</string>
<string name="dialog_cancel">Cancel</string>
<string name="item_edit_purchase_information">Purchase Information</string> <string name="item_edit_purchase_information">Purchase Information</string>
<string name="item_edit_purchase_price">Purchase Price</string> <string name="item_edit_purchase_price">Purchase Price</string>
<string name="item_edit_purchase_from">Purchase From</string> <string name="item_edit_purchase_from">Purchase From</string>
<string name="item_edit_purchase_time">Purchase Date</string> <string name="item_edit_purchase_time">Purchase Time</string>
<string name="item_edit_select_date">Select Date</string> <string name="item_edit_select_date">Select Date</string>
<string name="dialog_ok">OK</string>
<string name="dialog_cancel">Cancel</string>
<string name="item_edit_warranty_information">Warranty Information</string> <string name="item_edit_warranty_information">Warranty Information</string>
<string name="item_edit_lifetime_warranty">Lifetime Warranty</string> <string name="item_edit_lifetime_warranty">Lifetime Warranty</string>
<string name="item_edit_warranty_details">Warranty Details</string> <string name="item_edit_warranty_details">Warranty Details</string>
@@ -99,50 +125,26 @@
<string name="item_edit_sold_price">Sold Price</string> <string name="item_edit_sold_price">Sold Price</string>
<string name="item_edit_sold_to">Sold To</string> <string name="item_edit_sold_to">Sold To</string>
<string name="item_edit_sold_notes">Sold Notes</string> <string name="item_edit_sold_notes">Sold Notes</string>
<string name="item_edit_sold_time">Sold Date</string> <string name="item_edit_sold_time">Sold Time</string>
<!-- Location Edit Screen --> <!-- Search Screen -->
<string name="location_edit_title_create">Создать локацию</string> <string name="placeholder_search_items">Search items...</string>
<string name="location_edit_title_edit">Редактировать локацию</string>
<!-- Locations List Screen -->
<string name="locations_not_found">Местоположения не найдены. Нажмите +, чтобы добавить новое.</string>
<string name="item_count">Предметов: %1$d</string>
<string name="cd_more_options">Больше опций</string>
<!-- Setup Screen --> <!-- Setup Screen -->
<string name="screen_title_setup">Настройка</string> <string name="screen_title_setup">Setup</string>
<string name="setup_title">Настройка сервера</string> <string name="screen_title_settings">Settings</string>
<string name="setup_server_url_label">URL сервера</string>
<string name="setup_username_label">Имя пользователя</string>
<string name="setup_password_label">Пароль</string>
<string name="setup_connect_button">Подключиться</string>
<!-- Labels List Screen -->
<string name="screen_title_labels">Метки</string>
<!-- Settings Screen -->
<string name="screen_title_settings">Настройки</string>
<string name="content_desc_navigate_back" translatable="false">Вернуться назад</string>
<string name="content_desc_create_label">Создать новую метку</string>
<string name="content_desc_label_icon">Иконка метки</string>
<string name="no_labels_found">Метки не найдены.</string>
<string name="dialog_title_create_label">Создать метку</string>
<string name="dialog_field_label_name">Название метки</string>
<string name="dialog_button_create">Создать</string>
<string name="dialog_button_cancel">Отмена</string>
<!-- Label Edit Screen --> <!-- Label Edit Screen -->
<string name="label_edit_title_create">Создать метку</string> <string name="label_edit_title_create">Create label</string>
<string name="label_edit_title_edit">Редактировать метку</string> <string name="label_edit_title_edit">Edit label</string>
<string name="label_name_edit">Название метки</string> <string name="label_name_edit">Label name</string>
<string name="label_description">Описание</string>
<!-- Common Actions --> <!-- Common Actions -->
<string name="back">Назад</string> <string name="back">Back</string>
<string name="save">Сохранить</string> <string name="save">Save</string>
<!-- Common Actions -->
<!-- Color Picker --> <!-- Color Picker -->
<string name="label_color">Цвет</string> <string name="label_color">Color</string>
<string name="label_hex_color">HEX-код цвета</string> <string name="label_hex_color">HEX color code</string>
</resources> </resources>

View File

@@ -19,10 +19,10 @@ data class ItemSummaryDto(
@Json(name = "name") val name: String, @Json(name = "name") val name: String,
@Json(name = "assetId") val assetId: String?, @Json(name = "assetId") val assetId: String?,
@Json(name = "image") val image: ImageDto?, @Json(name = "image") val image: ImageDto?,
@Json(name = "isArchived") val isArchived: Boolean, @Json(name = "isArchived") val isArchived: Boolean? = false,
@Json(name = "labels") val labels: List<LabelOutDto>, @Json(name = "labels") val labels: List<LabelOutDto>,
@Json(name = "location") val location: LocationOutDto?, @Json(name = "location") val location: LocationOutDto?,
@Json(name = "value") val value: Double, @Json(name = "value") val value: Double? = 0.0,
@Json(name = "createdAt") val createdAt: String, @Json(name = "createdAt") val createdAt: String,
@Json(name = "updatedAt") val updatedAt: String @Json(name = "updatedAt") val updatedAt: String
) )
@@ -39,10 +39,10 @@ fun ItemSummaryDto.toDomain(): ItemSummary {
name = this.name, name = this.name,
assetId = this.assetId, assetId = this.assetId,
image = this.image?.toDomain(), image = this.image?.toDomain(),
isArchived = this.isArchived, isArchived = this.isArchived ?: false,
labels = this.labels.map { it.toDomain() }, labels = this.labels.map { it.toDomain() },
location = this.location?.toDomain(), location = this.location?.toDomain(),
value = this.value, value = this.value ?: 0.0,
createdAt = this.createdAt, createdAt = this.createdAt,
updatedAt = this.updatedAt updatedAt = this.updatedAt
) )

View File

@@ -16,9 +16,9 @@ data class LocationOutDto(
@Json(name = "name") @Json(name = "name")
val name: String, val name: String,
@Json(name = "color") @Json(name = "color")
val color: String?, val color: String? = "#000000",
@Json(name = "isArchived") @Json(name = "isArchived")
val isArchived: Boolean?, val isArchived: Boolean? = false,
@Json(name = "createdAt") @Json(name = "createdAt")
val createdAt: String, val createdAt: String,
@Json(name = "updatedAt") @Json(name = "updatedAt")

View File

@@ -103,10 +103,10 @@ fun ItemSummaryDto.toDomain(): DomainItemSummary {
name = this.name, name = this.name,
assetId = this.assetId, assetId = this.assetId,
image = this.image?.toDomain(), image = this.image?.toDomain(),
isArchived = this.isArchived, isArchived = this.isArchived ?: false,
labels = this.labels.map { it.toDomain() }, labels = this.labels.map { it.toDomain() },
location = this.location?.toDomain(), location = this.location?.toDomain(),
value = this.value, value = this.value ?: 0.0,
createdAt = this.createdAt, createdAt = this.createdAt,
updatedAt = this.updatedAt updatedAt = this.updatedAt
) )

View File

@@ -21,7 +21,7 @@ sealed class Result<out T> {
* @summary Представляет собой неуспешный результат операции. * @summary Представляет собой неуспешный результат операции.
* @param exception Исключение, которое произошло во время операции. * @param exception Исключение, которое произошло во время операции.
*/ */
data class Error(val exception: Exception) : Result<Nothing>() data class Error(val exception: kotlin.Exception) : Result<Nothing>()
// [END_ENTITY: DataClass('Error')] // [END_ENTITY: DataClass('Error')]
} }
// [END_ENTITY: SealedClass('Result')] // [END_ENTITY: SealedClass('Result')]

View File

@@ -0,0 +1,23 @@
// [FILE] ResultExtensions.kt
// [SEMANTICS] domain, model, result, extensions
package com.homebox.lens.domain.model
// [ENTITY: Function('fold')]
/**
* @summary Extension function to handle success and failure cases of a Result.
* @param onSuccess The function to be called if the Result is Success.
* @param onFailure The function to be called if the Result is Error.
* @return The result of either onSuccess or onFailure.
* @param R The return type of the fold operation.
*/
inline fun <T, R> Result<T>.fold(
onSuccess: (value: T) -> R,
onFailure: (exception: Exception) -> R
): R {
return when (this) {
is Result.Success -> onSuccess(data)
is Result.Error -> onFailure(exception)
}
}
// [END_ENTITY: Function('fold')]
// [END_FILE_ResultExtensions.kt]

View File

@@ -6,6 +6,7 @@ package com.homebox.lens.domain.usecase
// [IMPORTS] // [IMPORTS]
import com.homebox.lens.domain.model.ItemSummary import com.homebox.lens.domain.model.ItemSummary
import com.homebox.lens.domain.model.PaginationResult import com.homebox.lens.domain.model.PaginationResult
import com.homebox.lens.domain.model.Result
import com.homebox.lens.domain.repository.ItemRepository import com.homebox.lens.domain.repository.ItemRepository
import javax.inject.Inject import javax.inject.Inject
// [END_IMPORTS] // [END_IMPORTS]
@@ -23,10 +24,14 @@ class SearchItemsUseCase @Inject constructor(
/** /**
* @summary Executes the search operation. * @summary Executes the search operation.
* @param query The search query. * @param query The search query.
* @return A pagination result object. * @return A Result object encapsulating either a successful PaginationResult or an Error.
*/ */
suspend operator fun invoke(query: String): PaginationResult<ItemSummary> { suspend operator fun invoke(query: String): Result<PaginationResult<ItemSummary>> {
return itemRepository.searchItems(query) return try {
Result.Success(itemRepository.searchItems(query))
} catch (e: Exception) {
Result.Error(e)
}
} }
// [END_ENTITY: Function('invoke')] // [END_ENTITY: Function('invoke')]
} }

View File

@@ -0,0 +1,59 @@
// [FILE] feature/inventory/build.gradle.kts
// [SEMANTICS] build, dependencies
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt")
id("dagger.hilt.android.plugin")
}
android {
namespace = "com.homebox.lens.feature.inventory"
compileSdk = Versions.compileSdk
defaultConfig {
minSdk = Versions.minSdk
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = Versions.composeCompiler
}
}
dependencies {
implementation(project(":domain"))
implementation(project(":ui"))
// AndroidX & Lifecycle
implementation(Libs.coreKtx)
implementation(Libs.lifecycleRuntime)
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
// Compose
implementation(platform(Libs.composeBom))
implementation(Libs.composeUi)
implementation(Libs.composeUiGraphics)
implementation(Libs.composeUiToolingPreview)
implementation(Libs.composeMaterial3)
implementation(Libs.navigationCompose)
implementation(Libs.hiltNavigationCompose)
// Hilt
implementation(Libs.hiltAndroid)
kapt(Libs.hiltCompiler)
// Other
implementation(Libs.timber)
implementation("io.coil-kt:coil-compose:2.4.0")
}
// [END_FILE_feature/inventory/build.gradle.kts]

View File

@@ -0,0 +1,160 @@
// [FILE] InventoryScreen.kt
// [SEMANTICS] ui, screen, list, compose
package com.homebox.lens.feature.inventory.ui
// [IMPORTS]
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.Card
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
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.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import coil.compose.AsyncImage
import coil.request.ImageRequest
import com.homebox.lens.domain.model.ItemSummary
import com.homebox.lens.navigation.NavigationActions
import com.homebox.lens.ui.navigation.Screen
import com.homebox.lens.ui.R // Import R from ui module
import com.homebox.lens.ui.common.MainScaffold
// [END_IMPORTS]
// [ENTITY: Function('InventoryScreen')]
// [RELATION: Function('InventoryScreen')] -> [DEPENDS_ON] -> [Class('NavigationActions')]
// [RELATION: Function('InventoryScreen')] -> [CALLS] -> [Function('MainScaffold')]
// [RELATION: Function('InventoryScreen')] -> [CONSUMES_STATE] -> [ViewModel('InventoryViewModel')]
/**
* @summary Composable function for the "Inventory" screen.
* @param currentRoute The current route to highlight the active item in the Drawer.
* @param navigationActions The object with navigation actions.
* @param viewModel The ViewModel for this screen.
*/
@Composable
fun InventoryScreen(
currentRoute: String?,
navigationActions: NavigationActions,
viewModel: InventoryViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsState()
MainScaffold(
topBarTitle = stringResource(id = R.string.inventory_list_title),
currentRoute = currentRoute,
navigationActions = navigationActions
) { innerPadding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(innerPadding),
contentAlignment = Alignment.Center
) {
when (val state = uiState) {
is InventoryUiState.Loading -> CircularProgressIndicator()
is InventoryUiState.Success -> InventoryList(
items = state.items,
onItemClick = { itemId ->
navigationActions.navigateToItemDetails(itemId)
}
)
is InventoryUiState.Error -> Text(text = state.message)
}
}
}
}
// [END_ENTITY: Function('InventoryScreen')]
// [ENTITY: Function('InventoryList')]
// [RELATION: Function('InventoryList')] -> [DEPENDS_ON] -> [DataClass('ItemSummary')]
// [RELATION: Function('InventoryList')] -> [CALLS] -> [Function('ItemRow')]
/**
* @summary Displays a list of inventory items.
* @param items The list of items to display.
* @param onItemClick Callback invoked when an item is clicked.
*/
@Composable
private fun InventoryList(
items: List<ItemSummary>,
onItemClick: (String) -> Unit
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(8.dp)
) {
items(items) { item ->
ItemRow(item = item, onItemClick = onItemClick)
}
}
}
// [END_ENTITY: Function('InventoryList')]
// [ENTITY: Function('ItemRow')]
// [RELATION: Function('ItemRow')] -> [DEPENDS_ON] -> [DataClass('ItemSummary')]
/**
* @summary Displays a single row in the inventory list.
* @param item The item to display.
* @param onItemClick Callback invoked when the row is clicked.
*/
@Composable
private fun ItemRow(
item: ItemSummary,
onItemClick: (String) -> Unit
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
.clickable { onItemClick(item.id) },
) {
Row(
modifier = Modifier.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data(item.image?.path)
.crossfade(true)
.build(),
placeholder = painterResource(R.drawable.ic_placeholder),
error = painterResource(R.drawable.ic_placeholder),
contentDescription = item.name,
contentScale = ContentScale.Crop,
modifier = Modifier.size(64.dp)
)
Spacer(modifier = Modifier.width(16.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = item.name,
style = MaterialTheme.typography.titleMedium,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
item.location?.let {
Text(
text = it.name,
style = MaterialTheme.typography.bodySmall,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
// [AI_NOTE]: Quantity is not present in ItemSummary, so it's omitted for now.
// Placeholder: quantity will be added later when ItemSummary is updated to include it.
}
}
}
// [END_ENTITY: Function('ItemRow')]
// [END_FILE_InventoryScreen.kt]

View File

@@ -0,0 +1,40 @@
// [FILE] InventoryUiState.kt
// [SEMANTICS] ui, state_management, sealed_state
package com.homebox.lens.feature.inventory.ui
// [IMPORTS]
import com.homebox.lens.domain.model.ItemSummary
// [END_IMPORTS]
// [ENTITY: SealedInterface('InventoryUiState')]
/**
* @summary Represents the different states of the Inventory screen.
* @invariant Only one state can be active at a time.
*/
sealed interface InventoryUiState {
// [END_ENTITY: SealedInterface('InventoryUiState')]
// [ENTITY: Object('Loading')]
/**
* @summary Represents the loading state, where the list of items is being fetched.
*/
object Loading : InventoryUiState
// [END_ENTITY: Object('Loading')]
// [ENTITY: DataClass('Success')]
/**
* @summary Represents the success state, where the list of items is available.
* @param items The list of inventory items.
*/
data class Success(val items: List<ItemSummary>) : InventoryUiState
// [END_ENTITY: DataClass('Success')]
// [ENTITY: DataClass('Error')]
/**
* @summary Represents the error state, where an error occurred while fetching items.
* @param message The error message.
*/
data class Error(val message: String) : InventoryUiState
// [END_ENTITY: DataClass('Error')]
}
// [END_FILE_InventoryUiState.kt]

View File

@@ -0,0 +1,77 @@
// [FILE] InventoryViewModel.kt
// [SEMANTICS] ui, viewmodel, state_management
package com.homebox.lens.feature.inventory.ui
// [IMPORTS]
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.homebox.lens.domain.usecase.SearchItemsUseCase
import com.homebox.lens.domain.model.PaginationResult
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 timber.log.Timber
import com.homebox.lens.domain.model.fold
import javax.inject.Inject
// [END_IMPORTS]
// [ENTITY: ViewModel('InventoryViewModel')]
// [RELATION: ViewModel('InventoryViewModel')] -> [DEPENDS_ON] -> [UseCase('SearchItemsUseCase')]
// [RELATION: ViewModel('InventoryViewModel')] -> [EMITS_STATE] -> [SealedInterface('InventoryUiState')]
/**
* @summary ViewModel for the Inventory screen.
* @param searchItemsUseCase The use case to search for inventory items.
* @invariant The ViewModel manages the UI state and business logic for the inventory screen.
*/
@HiltViewModel
class InventoryViewModel @Inject constructor(
private val searchItemsUseCase: SearchItemsUseCase
) : ViewModel() {
// [ENTITY: DataStructure('uiState')]
private val _uiState = MutableStateFlow<InventoryUiState>(InventoryUiState.Loading)
val uiState: StateFlow<InventoryUiState> = _uiState.asStateFlow()
// [END_ENTITY: DataStructure('uiState')]
init {
fetchInventoryItems()
}
// [ENTITY: Function('fetchInventoryItems')]
// [RELATION: Function('fetchInventoryItems')] -> [CALLS] -> [UseCase('SearchItemsUseCase')]
// [RELATION: Function('fetchInventoryItems')] -> [MODIFIES_STATE_OF] -> [DataStructure('uiState')]
/**
* @summary Fetches the list of inventory items.
* @sideeffect Updates the `uiState` with the result of the operation.
*/
private fun fetchInventoryItems() {
viewModelScope.launch {
_uiState.value = InventoryUiState.Loading
Timber.d("[DEBUG][INVENTORY_VIEW_MODEL][FETCH_START] Fetching inventory items.")
try {
val result = searchItemsUseCase(query = "")
result.fold(
onSuccess = { paginationResult ->
@Suppress("UNCHECKED_CAST")
val items = paginationResult.items as List<com.homebox.lens.domain.model.ItemSummary>
_uiState.value = InventoryUiState.Success(items)
Timber.i("[INFO][INVENTORY_VIEW_MODEL][FETCH_SUCCESS] Successfully fetched %d items.", items.size)
},
onFailure = { throwable: Exception ->
_uiState.value = InventoryUiState.Error(throwable.message ?: "Unknown error")
Timber.e(throwable, "[ERROR][INVENTORY_VIEW_MODEL][FETCH_FAILURE] Error fetching inventory items: %s", throwable.message)
}
)
} catch (e: Exception) {
_uiState.value = InventoryUiState.Error(e.message ?: "An unexpected error occurred")
Timber.e(e, "[ERROR][INVENTORY_VIEW_MODEL][FETCH_UNEXPECTED] Unexpected error fetching inventory items: %s", e.message)
}
}
}
// [END_ENTITY: Function('fetchInventoryItems')]
}
// [END_ENTITY: ViewModel('InventoryViewModel')]
// [END_FILE_InventoryViewModel.kt]

View File

@@ -21,6 +21,8 @@ rootProject.name = "HomeboxLens"
include(":app") include(":app")
include(":data") include(":data")
include(":domain") include(":domain")
include(":ui")
include(":feature:inventory")
include(":data:semantic-ktlint-rules")
// [END_FILE_settings.gradle.kts] // [END_FILE_settings.gradle.kts]
include(":data:semantic-ktlint-rules")

View File

@@ -1,84 +0,0 @@
<![CDATA[
<work_order>
<title>Миграция Moshi с Kapt на KSP</title>
<description>
Устранить предупреждение "Kapt support in Moshi Kotlin Code Gen is deprecated".
Это требует миграции генерации кода Moshi с `kapt` на `KSP` (Kotlin Symbol Processing).
</description>
<steps>
<step>
<description>Добавить плагин KSP в корневой файл `build.gradle.kts`.</description>
<tool>
<name>apply_diff</name>
<args>
<file>
<path>build.gradle.kts</path>
<diff>
<content>
<![CDATA[
<<<<<<< SEARCH
// [PLUGIN] Hilt Android plugin
id("com.google.dagger.hilt.android") version "2.48.1" apply false
}
=======
// [PLUGIN] Hilt Android plugin
id("com.google.dagger.hilt.android") version "2.48.1" apply false
// [PLUGIN] KSP plugin
id("com.google.devtools.ksp") version "1.9.23-1.0.19" apply false
}
>>>>>>> REPLACE
]]>
</content>
<start_line>9</start_line>
</diff>
</file>
</args>
</tool>
</step>
<step>
<description>Применить плагин KSP и заменить зависимость `kapt` на `ksp` для Moshi в файле `data/build.gradle.kts`.</description>
<tool>
<name>apply_diff</name>
<args>
<file>
<path>data/build.gradle.kts</path>
<diff>
<content>
<![CDATA[
<<<<<<< SEARCH
id("org.jetbrains.kotlin.android")
id("com.google.dagger.hilt.android")
id("kotlin-kapt")
}
=======
id("org.jetbrains.kotlin.android")
id("com.google.dagger.hilt.android")
id("kotlin-kapt")
id("com.google.devtools.ksp")
}
>>>>>>> REPLACE
]]>
</content>
<start_line>6</start_line>
</diff>
<diff>
<content>
<![CDATA[
<<<<<<< SEARCH
implementation(Libs.moshiKotlin)
kapt(Libs.moshiCodegen)
=======
implementation(Libs.moshiKotlin)
ksp(Libs.moshiCodegen)
>>>>>>> REPLACE
]]>
</content>
<start_line>53</start_line>
</diff>
</file>
</args>
</tool>
</step>
</steps>
</work_order>
]]>

View File

@@ -1,35 +0,0 @@
<![CDATA[
<work_order>
<title>Удаление атрибута package из AndroidManifest.xml</title>
<description>
Устранить предупреждение "Setting the namespace via the package attribute in the source AndroidManifest.xml is no longer supported".
Это требует удаления устаревшего атрибута `package` из файла `app/src/main/AndroidManifest.xml`.
</description>
<steps>
<step>
<description>Удалить атрибут `package` из тега `manifest` в файле `app/src/main/AndroidManifest.xml`.</description>
<tool>
<name>apply_diff</name>
<args>
<file>
<path>app/src/main/AndroidManifest.xml</path>
<diff>
<content>
<![CDATA[
<<<<<<< SEARCH
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.homebox.lens">
=======
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
>>>>>>> REPLACE
]]>
</content>
<start_line>2</start_line>
</diff>
</file>
</args>
</tool>
</step>
</steps>
</work_order>
]]>

View File

@@ -1,86 +0,0 @@
<![CDATA[
<work_order>
<title>Устранение предупреждений сборки Gradle</title>
<description>
Необходимо устранить два типа предупреждений, возникающих при сборке проекта:
1. Использование устаревшего метода `Configuration.fileCollection(Spec)` в плагине `org.jetbrains.kotlin.android`.
2. Использование устаревшей версии Java 8 для компиляции.
</description>
<steps>
<step>
<description>Обновить версию Android Gradle Plugin (AGP) и плагина Kotlin в корневом файле `build.gradle.kts`.</description>
<tool>
<name>apply_diff</name>
<args>
<file>
<path>build.gradle.kts</path>
<diff>
<content>
<![CDATA[
<<<<<<< SEARCH
// [PLUGIN] Android Application plugin
id("com.android.application") version "8.13.0" apply false
// [PLUGIN] Kotlin Android plugin
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
=======
// [PLUGIN] Android Application plugin
id("com.android.application") version "8.4.0" apply false
// [PLUGIN] Kotlin Android plugin
id("org.jetbrains.kotlin.android") version "1.9.23" apply false
>>>>>>> REPLACE
]]>
</content>
<start_line>5</start_line>
</diff>
</file>
</args>
</tool>
</step>
<step>
<description>Обновить версию Java до 17 в файлах сборки всех модулей. Начать с `data/build.gradle.kts`.</description>
<tool>
<name>apply_diff</name>
<args>
<file>
<path>data/build.gradle.kts</path>
<diff>
<content>
<![CDATA[
<<<<<<< SEARCH
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
=======
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
>>>>>>> REPLACE
]]>
</content>
<start_line>29</start_line>
</diff>
</file>
</args>
</tool>
</step>
<step>
<description>Применить аналогичные изменения для `app/build.gradle.kts` и других модулей. (Примечание: этот шаг потребует поиска и изменения всех `build.gradle.kts` файлов в проекте).</description>
<tool>
<name>search_files</name>
<args>
<path>.</path>
<regex>build\.gradle\.kts</regex>
</args>
</tool>
</step>
</steps>
</work_order>
]]>

View File

@@ -0,0 +1,65 @@
<![CDATA[
<?xml version="1.0" encoding="UTF-8"?>
<WorkOrder>
<WO_ID>20251006_add_missing_string_resources</WO_ID>
<Originator>Architect</Originator>
<Created>2025-10-06T06:05:22Z</Created>
<Project>Homebox-Lens</Project>
<Title>Add Missing String Resources to Resolve Build Failure</Title>
<Description>
The project build is currently failing due to multiple 'Unresolved reference' errors in `ItemEditScreen.kt` and `SettingsScreen.kt`. These errors are caused by missing string resources. To fix this, the specified string resources must be added to the `app/src/main/res/values/strings.xml` file.
</Description>
<Scope>
<File>app/src/main/res/values/strings.xml</File>
</Scope>
<AcceptanceCriteria>
- The project must compile successfully without any 'Unresolved reference' errors related to string resources.
- All new strings must be added to `app/src/main/res/values/strings.xml`.
- The new strings should have meaningful English values.
</AcceptanceCriteria>
<Constraints>
- Changes should be limited to the `app/src/main/res/values/strings.xml` file. No other files should be modified unless absolutely necessary to resolve the primary issue.
</Constraints>
<Deliverables>
<File>app/src/main/res/values/strings.xml</File>
</Deliverables>
<ImplementationDetails>
<LinguisticContext>
The new strings are for the item editing screen and settings screen. The text should be clear and concise.
</LinguisticContext>
<TechnicalContext>
The following string resources are missing and must be added to `app/src/main/res/values/strings.xml`:
- `item_edit_general_information`
- `item_edit_location`
- `item_edit_labels`
- `item_edit_select_labels`
- `dialog_ok`
- `dialog_cancel`
- `item_edit_purchase_information`
- `item_edit_purchase_price`
- `item_edit_purchase_from`
- `item_edit_purchase_time`
- `item_edit_select_date`
- `item_edit_warranty_information`
- `item_edit_lifetime_warranty`
- `item_edit_warranty_details`
- `item_edit_warranty_expires`
- `item_edit_identification`
- `item_edit_asset_id`
- `item_edit_serial_number`
- `item_edit_manufacturer`
- `item_edit_model_number`
- `item_edit_status_notes`
- `item_edit_archived`
- `item_edit_insured`
- `item_edit_notes`
- `item_edit_sold_information`
- `item_edit_sold_price`
- `item_edit_sold_to`
- `item_edit_sold_notes`
- `item_edit_sold_time`
- `screen_title_settings`
</TechnicalContext>
</ImplementationDetails>
</WorkOrder>
]]>

View File

@@ -1,97 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<WorkOrder>
<Configuration>
<MaxConcurrentAgents>1</MaxConcurrentAgents>
<MaxIterations>25</MaxIterations>
<TaskChannel>
<Implementation>
<Plugin>filesystem</Plugin>
<Parameters>
<BasePath>tasks/temp</BasePath>
</Parameters>
</Implementation>
</TaskChannel>
<MetricsSink>
<Implementation>
<Plugin>xml_file</Plugin>
<Parameters>
<OutputPath>logs/metrics</OutputPath>
</Parameters>
</Implementation>
</MetricsSink>
<LogSink>
<Implementation>
<Plugin>xml_file</Plugin>
<Parameters>
<OutputPath>logs/main</OutputPath>
</Parameters>
</Implementation>
</LogSink>
</Configuration>
<Objective>
<HumanReadable>Создать полнофункциональный экран "Inventory", который будет отображать список предметов инвентаря. Экран должен использовать существующую архитектуру и компоненты.</HumanReadable>
</Objective>
<SystemContext>
<System>
<OperatingSystem>Linux</OperatingSystem>
<Shell>/usr/bin/zsh</Shell>
</System>
<Project>
<Name>Homebox Lens</Name>
<Framework>Android (Jetpack Compose)</Framework>
<Language>Kotlin</Language>
<BuildSystem>Gradle</BuildSystem>
<Architecture>MVVM, Clean Architecture</Architecture>
</Project>
</SystemContext>
<InitialContext>
<File>
<Path>domain/src/main/java/com/homebox/lens/domain/model/ItemSummary.kt</Path>
</File>
<File>
<Path>app/src/main/java/com/homebox/lens/ui/screen/inventorylist/InventoryListScreen.kt</Path>
</File>
</InitialContext>
<AcceptanceCriteria>
<Criterion>
<Description>Создан новый Gradle-модуль `feature:inventory`.</Description>
</Criterion>
<Criterion>
<Description>В модуле `feature:inventory` реализован `InventoryScreen`, `InventoryViewModel` и `InventoryUiState`.</Description>
</Criterion>
<Criterion>
<Description>`InventoryViewModel` использует `SearchItemsUseCase` для получения списка предметов.</Description>
</Criterion>
<Criterion>
<Description>`InventoryScreen` отображает список предметов, используя `ItemSummary` в качестве модели представления для каждого элемента списка.</Description>
</Criterion>
<Criterion>
<Description>Новый экран интегрирован в навигацию приложения.</Description>
</Criterion>
<Criterion>
<Description>Весь новый код соответствует стайлгайду проекта и семантически корректен.</Description>
</Criterion>
</AcceptanceCriteria>
<Workflow>
<Step>
<Name>Создание модуля</Name>
<Description>Создать новый feature-модуль с именем `inventory`.</Description>
</Step>
<Step>
<Name>Реализация ViewModel</Name>
<Description>Создать `InventoryViewModel` который будет запрашивать данные из `SearchItemsUseCase` и управлять состоянием экрана `InventoryUiState`.</Description>
</Step>
<Step>
<Name>Реализация UI</Name>
<Description>Создать `InventoryScreen`, который будет отображать список `ItemSummary` из `InventoryUiState`. Для каждого элемента списка создать `ItemRow` composable.</Description>
</Step>
<Step>
<Name>Интеграция навигации</Name>
<Description>Добавить `InventoryScreen` в граф навигации приложения.</Description>
</Step>
<Step>
<Name>Рефакторинг</Name>
<Description>Удалить старый `InventoryListScreen` из `app` модуля, если он больше не нужен.</Description>
</Step>
</Workflow>
</WorkOrder>

55
ui/build.gradle.kts Normal file
View File

@@ -0,0 +1,55 @@
// [FILE] ui/build.gradle.kts
// [SEMANTICS] build, dependencies, ui
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
id("kotlin-kapt") // Add kapt for Hilt
}
android {
namespace = "com.homebox.lens.ui"
compileSdk = Versions.compileSdk
defaultConfig {
minSdk = Versions.minSdk
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = Versions.composeCompiler
}
}
dependencies {
// AndroidX
implementation(Libs.coreKtx)
implementation(Libs.lifecycleRuntime) // For androidx.lifecycle:lifecycle-runtime-ktx
// Compose
implementation(platform(Libs.composeBom))
implementation(Libs.composeUi)
implementation(Libs.composeUiGraphics)
implementation(Libs.composeUiToolingPreview)
implementation(Libs.composeMaterial3)
implementation(Libs.activityCompose)
implementation("androidx.compose.material:material-icons-extended-android:1.6.8")
implementation(Libs.navigationCompose) // For NavigationActions
implementation(Libs.hiltNavigationCompose) // For Hilt with Compose
// Hilt
implementation(Libs.hiltAndroid)
kapt(Libs.hiltCompiler)
// Other
implementation(Libs.timber)
}
// [END_FILE_ui/build.gradle.kts]

View File

@@ -1,10 +1,11 @@
// [FILE] NavigationActions.kt // [FILE] NavigationActions.kt
// [SEMANTICS] app, ui, navigation, actions // [SEMANTICS] ui, navigation, actions
package com.homebox.lens.navigation package com.homebox.lens.navigation
// [IMPORTS] // [IMPORTS]
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import timber.log.Timber import timber.log.Timber
import com.homebox.lens.ui.navigation.Screen
// [END_IMPORTS] // [END_IMPORTS]
// [ENTITY: Class('NavigationActions')] // [ENTITY: Class('NavigationActions')]
@@ -64,10 +65,19 @@ class NavigationActions(private val navController: NavHostController) {
} }
// [END_ENTITY: Function('navigateToSearch')] // [END_ENTITY: Function('navigateToSearch')]
// [ENTITY: Function('navigateToInventory')]
fun navigateToInventory() {
Timber.i("[INFO][ACTION][navigate_to_inventory] Navigating to Inventory.")
navController.navigate(Screen.Inventory.route) {
launchSingleTop = true
}
}
// [END_ENTITY: Function('navigateToInventory')]
// [ENTITY: Function('navigateToInventoryListWithLabel')] // [ENTITY: Function('navigateToInventoryListWithLabel')]
fun navigateToInventoryListWithLabel(labelId: String) { fun navigateToInventoryListWithLabel(labelId: String) {
Timber.i("[INFO][ACTION][navigate_to_inventory_with_label] Navigating to Inventory with label: %s", labelId) Timber.i("[INFO][ACTION][navigate_to_inventory_with_label] Navigating to Inventory with label: %s", labelId)
val route = Screen.InventoryList.withFilter("label", labelId) val route = Screen.Inventory.withFilter("label", labelId)
navController.navigate(route) navController.navigate(route)
} }
// [END_ENTITY: Function('navigateToInventoryListWithLabel')] // [END_ENTITY: Function('navigateToInventoryListWithLabel')]
@@ -75,7 +85,7 @@ class NavigationActions(private val navController: NavHostController) {
// [ENTITY: Function('navigateToInventoryListWithLocation')] // [ENTITY: Function('navigateToInventoryListWithLocation')]
fun navigateToInventoryListWithLocation(locationId: String) { fun navigateToInventoryListWithLocation(locationId: String) {
Timber.i("[INFO][ACTION][navigate_to_inventory_with_location] Navigating to Inventory with location: %s", locationId) Timber.i("[INFO][ACTION][navigate_to_inventory_with_location] Navigating to Inventory with location: %s", locationId)
val route = Screen.InventoryList.withFilter("location", locationId) val route = Screen.Inventory.withFilter("location", locationId)
navController.navigate(route) navController.navigate(route)
} }
// [END_ENTITY: Function('navigateToInventoryListWithLocation')] // [END_ENTITY: Function('navigateToInventoryListWithLocation')]
@@ -87,6 +97,13 @@ class NavigationActions(private val navController: NavHostController) {
} }
// [END_ENTITY: Function('navigateToCreateItem')] // [END_ENTITY: Function('navigateToCreateItem')]
// [ENTITY: Function('navigateToItemDetails')]
fun navigateToItemDetails(itemId: String) {
Timber.i("[INFO][ACTION][navigate_to_item_details] Navigating to Item Details with ID: %s", itemId)
navController.navigate(Screen.ItemDetails.createRoute(itemId))
}
// [END_ENTITY: Function('navigateToItemDetails')]
// [ENTITY: Function('navigateToLogout')] // [ENTITY: Function('navigateToLogout')]
fun navigateToLogout() { fun navigateToLogout() {
Timber.i("[INFO][ACTION][navigate_to_logout] Navigating to Logout.") Timber.i("[INFO][ACTION][navigate_to_logout] Navigating to Logout.")

View File

@@ -1,6 +1,6 @@
// [FILE] Screen.kt // [FILE] Screen.kt
// [SEMANTICS] app, ui, navigation, routes // [SEMANTICS] ui, navigation, routes
package com.homebox.lens.navigation package com.homebox.lens.ui.navigation
// [ENTITY: SealedClass('Screen')] // [ENTITY: SealedClass('Screen')]
/** /**
@@ -21,11 +21,11 @@ sealed class Screen(val route: String) {
data object Dashboard : Screen("dashboard_screen") data object Dashboard : Screen("dashboard_screen")
// [END_ENTITY: Object('Dashboard')] // [END_ENTITY: Object('Dashboard')]
// [ENTITY: Object('InventoryList')] // [ENTITY: Object('Inventory')]
data object InventoryList : Screen("inventory_list_screen") { data object Inventory : Screen("inventory_screen") {
// [ENTITY: Function('withFilter')] // [ENTITY: Function('withFilter')]
/** /**
* @summary Creates a route for the inventory list screen with a filter parameter. * @summary Creates a route for the inventory screen with a filter parameter.
* @param key The filter key (e.g., "label" or "location"). * @param key The filter key (e.g., "label" or "location").
* @param value The filter value (e.g., the ID of the label or location). * @param value The filter value (e.g., the ID of the label or location).
* @return A string of the full route with a query parameter. * @return A string of the full route with a query parameter.
@@ -34,13 +34,13 @@ sealed class Screen(val route: String) {
fun withFilter(key: String, value: String): String { fun withFilter(key: String, value: String): String {
require(key.isNotBlank()) { "Filter key cannot be blank." } require(key.isNotBlank()) { "Filter key cannot be blank." }
require(value.isNotBlank()) { "Filter value cannot be blank." } require(value.isNotBlank()) { "Filter value cannot be blank." }
val constructedRoute = "inventory_list_screen?$key=$value" val constructedRoute = "inventory_screen?$key=$value"
check(constructedRoute.contains("?$key=$value")) { "Route must contain the filter query." } check(constructedRoute.contains("?$key=$value")) { "Route must contain the filter query." }
return constructedRoute return constructedRoute
} }
// [END_ENTITY: Function('withFilter')] // [END_ENTITY: Function('withFilter')]
} }
// [END_ENTITY: Object('InventoryList')] // [END_ENTITY: Object('Inventory')]
// [ENTITY: Object('ItemDetails')] // [ENTITY: Object('ItemDetails')]
data object ItemDetails : Screen("item_details_screen/{itemId}") { data object ItemDetails : Screen("item_details_screen/{itemId}") {

View File

@@ -1,5 +1,5 @@
// [FILE] AppDrawer.kt // [FILE] AppDrawer.kt
// [SEMANTICS] app, ui, common, navigation // [SEMANTICS] ui, common, navigation
package com.homebox.lens.ui.common package com.homebox.lens.ui.common
// [IMPORTS] // [IMPORTS]
@@ -21,9 +21,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.homebox.lens.R import com.homebox.lens.ui.R
import com.homebox.lens.navigation.NavigationActions import com.homebox.lens.navigation.NavigationActions
import com.homebox.lens.navigation.Screen import com.homebox.lens.ui.navigation.Screen
// [END_IMPORTS] // [END_IMPORTS]
// [ENTITY: Function('AppDrawerContent')] // [ENTITY: Function('AppDrawerContent')]
@@ -65,6 +65,14 @@ internal fun AppDrawerContent(
onCloseDrawer() onCloseDrawer()
} }
) )
NavigationDrawerItem(
label = { Text(stringResource(id = R.string.inventory_list_title)) },
selected = currentRoute == Screen.Inventory.route,
onClick = {
navigationActions.navigateToInventory()
onCloseDrawer()
}
)
NavigationDrawerItem( NavigationDrawerItem(
label = { Text(stringResource(id = R.string.nav_locations)) }, label = { Text(stringResource(id = R.string.nav_locations)) },
selected = currentRoute == Screen.LocationsList.route, selected = currentRoute == Screen.LocationsList.route,

View File

@@ -1,6 +1,5 @@
// [FILE] MainScaffold.kt // [FILE] MainScaffold.kt
// [SEMANTICS] app, ui, common, scaffold // [SEMANTICS] ui, common, scaffold
package com.homebox.lens.ui.common package com.homebox.lens.ui.common
// [IMPORTS] // [IMPORTS]
@@ -11,7 +10,7 @@ import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.homebox.lens.R import com.homebox.lens.ui.R
import com.homebox.lens.navigation.NavigationActions import com.homebox.lens.navigation.NavigationActions
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
// [END_IMPORTS] // [END_IMPORTS]

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View File

@@ -0,0 +1,19 @@
<resources>
<!-- Common -->
<string name="create">Создать</string>
<string name="search">Поиск</string>
<string name="logout">Выйти</string>
<!-- Content Descriptions -->
<string name="cd_open_navigation_drawer">Открыть боковое меню</string>
<!-- Dashboard Screen -->
<string name="dashboard_title">Главная</string>
<!-- Navigation -->
<string name="nav_locations">Локации</string>
<string name="nav_labels">Метки</string>
<!-- Screen Titles -->
<string name="inventory_list_title">Инвентарь</string>
</resources>