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

View File

@@ -15,7 +15,7 @@ import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
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.itemedit.ItemEditScreen
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.settings.SettingsScreen
import com.homebox.lens.ui.screen.splash.SplashScreen
import com.homebox.lens.navigation.NavigationActions
import com.homebox.lens.ui.navigation.Screen
// [END_IMPORTS]
// [ENTITY: Function('NavGraph')]
@@ -59,7 +61,9 @@ fun NavGraph(
composable(route = Screen.Setup.route) {
SetupScreen(onSetupComplete = {
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
)
}
composable(route = Screen.InventoryList.route) {
InventoryListScreen(
composable(route = Screen.Inventory.route) {
InventoryScreen(
currentRoute = currentRoute,
navigationActions = navigationActions
)
@@ -105,7 +109,7 @@ fun NavGraph(
navigationActions = navigationActions,
onLocationClick = { locationId ->
// [AI_NOTE]: Navigate to a pre-filtered inventory list screen
navController.navigate(Screen.InventoryList.route)
navController.navigate(Screen.Inventory.route)
},
onAddNewLocationClick = {
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.domain.model.Label
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 timber.log.Timber
// [END_IMPORTS]

View File

@@ -12,7 +12,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
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 timber.log.Timber
// [END_IMPORTS]
@@ -50,7 +50,9 @@ fun SplashScreen(
}
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>
<!-- Common -->
<string name="create">Создать</string>
<string name="edit">Редактировать</string>
<string name="delete">Удалить</string>
<string name="search">Поиск</string>
<string name="logout">Выйти</string>
<string name="no_location">Нет локации</string>
<string name="items_not_found">Элементы не найдены</string>
<string name="error_loading_failed">Не удалось загрузить данные. Пожалуйста, попробуйте еще раз.</string>
<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">Открыть боковое меню</string>
<string name="setup_subtitle">Enter your Homebox server details to connect.</string>
<string name="cd_scan_qr_code">Сканировать QR-код</string>
<string name="cd_navigate_back">Вернуться назад</string>
<string name="cd_add_new_location">Добавить новую локацию</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>
<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">Главная</string>
<string name="dashboard_section_quick_stats">Быстрая статистика</string>
<string name="dashboard_section_recently_added">Недавно добавлено</string>
<string name="dashboard_section_locations">Места хранения</string>
<string name="dashboard_section_labels">Метки</string>
<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">Всего вещей</string>
<string name="dashboard_stat_total_value">Общая стоимость</string>
<string name="dashboard_stat_total_labels">Всего меток</string>
<string name="dashboard_stat_total_locations">Всего локаций</string>
<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">Локации</string>
<string name="nav_labels">Метки</string>
<string name="nav_locations">Locations</string>
<string name="nav_labels">Labels</string>
<!-- Screen Titles -->
<string name="inventory_list_title">Инвентарь</string>
<string name="item_details_title">Детали</string>
<string name="item_edit_title">Редактирование</string>
<string name="labels_list_title">Метки</string>
<string name="locations_list_title">Места хранения</string>
<string name="search_title">Поиск</string>
<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>
<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_location">Location</string>
<string name="item_edit_select_location">Select Location</string>
<string name="item_edit_labels">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_price">Purchase Price</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="dialog_ok">OK</string>
<string name="dialog_cancel">Cancel</string>
<string name="item_edit_warranty_information">Warranty Information</string>
<string name="item_edit_lifetime_warranty">Lifetime Warranty</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_to">Sold To</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 -->
<string name="location_edit_title_create">Создать локацию</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>
<!-- Search Screen -->
<string name="placeholder_search_items">Search items...</string>
<!-- Setup Screen -->
<string name="screen_title_setup">Настройка</string>
<string name="setup_title">Настройка сервера</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>
<string name="screen_title_setup">Setup</string>
<string name="screen_title_settings">Settings</string>
<!-- Label Edit Screen -->
<string name="label_edit_title_create">Создать метку</string>
<string name="label_edit_title_edit">Редактировать метку</string>
<string name="label_name_edit">Название метки</string>
<string name="label_description">Описание</string>
<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">Назад</string>
<string name="save">Сохранить</string>
<!-- Common Actions -->
<string name="back">Back</string>
<string name="save">Save</string>
<!-- Color Picker -->
<string name="label_color">Цвет</string>
<string name="label_hex_color">HEX-код цвета</string>
</resources>
<string name="label_color">Color</string>
<string name="label_hex_color">HEX color code</string>
</resources>

View File

@@ -19,10 +19,10 @@ data class ItemSummaryDto(
@Json(name = "name") val name: String,
@Json(name = "assetId") val assetId: String?,
@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 = "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 = "updatedAt") val updatedAt: String
)
@@ -39,10 +39,10 @@ fun ItemSummaryDto.toDomain(): ItemSummary {
name = this.name,
assetId = this.assetId,
image = this.image?.toDomain(),
isArchived = this.isArchived,
isArchived = this.isArchived ?: false,
labels = this.labels.map { it.toDomain() },
location = this.location?.toDomain(),
value = this.value,
value = this.value ?: 0.0,
createdAt = this.createdAt,
updatedAt = this.updatedAt
)

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ sealed class Result<out T> {
* @summary Представляет собой неуспешный результат операции.
* @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: 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]
import com.homebox.lens.domain.model.ItemSummary
import com.homebox.lens.domain.model.PaginationResult
import com.homebox.lens.domain.model.Result
import com.homebox.lens.domain.repository.ItemRepository
import javax.inject.Inject
// [END_IMPORTS]
@@ -23,10 +24,14 @@ class SearchItemsUseCase @Inject constructor(
/**
* @summary Executes the search operation.
* @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> {
return itemRepository.searchItems(query)
suspend operator fun invoke(query: String): Result<PaginationResult<ItemSummary>> {
return try {
Result.Success(itemRepository.searchItems(query))
} catch (e: Exception) {
Result.Error(e)
}
}
// [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(":data")
include(":domain")
include(":ui")
include(":feature:inventory")
include(":data:semantic-ktlint-rules")
// [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
// [SEMANTICS] app, ui, navigation, actions
// [SEMANTICS] ui, navigation, actions
package com.homebox.lens.navigation
// [IMPORTS]
import androidx.navigation.NavHostController
import timber.log.Timber
import com.homebox.lens.ui.navigation.Screen
// [END_IMPORTS]
// [ENTITY: Class('NavigationActions')]
@@ -64,10 +65,19 @@ class NavigationActions(private val navController: NavHostController) {
}
// [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')]
fun navigateToInventoryListWithLabel(labelId: String) {
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)
}
// [END_ENTITY: Function('navigateToInventoryListWithLabel')]
@@ -75,7 +85,7 @@ class NavigationActions(private val navController: NavHostController) {
// [ENTITY: Function('navigateToInventoryListWithLocation')]
fun navigateToInventoryListWithLocation(locationId: String) {
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)
}
// [END_ENTITY: Function('navigateToInventoryListWithLocation')]
@@ -87,6 +97,13 @@ class NavigationActions(private val navController: NavHostController) {
}
// [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')]
fun navigateToLogout() {
Timber.i("[INFO][ACTION][navigate_to_logout] Navigating to Logout.")
@@ -104,4 +121,4 @@ class NavigationActions(private val navController: NavHostController) {
// [END_ENTITY: Function('navigateBack')]
}
// [END_ENTITY: Class('NavigationActions')]
// [END_FILE_NavigationActions.kt]
// [END_FILE_NavigationActions.kt]

View File

@@ -1,6 +1,6 @@
// [FILE] Screen.kt
// [SEMANTICS] app, ui, navigation, routes
package com.homebox.lens.navigation
// [SEMANTICS] ui, navigation, routes
package com.homebox.lens.ui.navigation
// [ENTITY: SealedClass('Screen')]
/**
@@ -21,11 +21,11 @@ sealed class Screen(val route: String) {
data object Dashboard : Screen("dashboard_screen")
// [END_ENTITY: Object('Dashboard')]
// [ENTITY: Object('InventoryList')]
data object InventoryList : Screen("inventory_list_screen") {
// [ENTITY: Object('Inventory')]
data object Inventory : Screen("inventory_screen") {
// [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 value The filter value (e.g., the ID of the label or location).
* @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 {
require(key.isNotBlank()) { "Filter key 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." }
return constructedRoute
}
// [END_ENTITY: Function('withFilter')]
}
// [END_ENTITY: Object('InventoryList')]
// [END_ENTITY: Object('Inventory')]
// [ENTITY: Object('ItemDetails')]
data object ItemDetails : Screen("item_details_screen/{itemId}") {
@@ -127,4 +127,4 @@ sealed class Screen(val route: String) {
// [END_ENTITY: Object('Settings')]
}
// [END_ENTITY: SealedClass('Screen')]
// [END_FILE_Screen.kt]
// [END_FILE_Screen.kt]

View File

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

View File

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