Compare commits
9 Commits
aa69776807
...
78b827f29e
| Author | SHA1 | Date | |
|---|---|---|---|
| 78b827f29e | |||
| 9500d747b1 | |||
| 8cfad121b2 | |||
| e3f52fca52 | |||
| 9286e041da | |||
| 556b7f7c7d | |||
| eccc7ee970 | |||
| 8816377361 | |||
| 5eb23eed5b |
@@ -12,10 +12,9 @@
|
||||
<Rationale>Заголовок служит 'паспортом' файла, позволяя инструментам мгновенно понять его расположение, имя и назначение.</Rationale>
|
||||
<Definition type="regex">
|
||||
<!-- CDATA используется для того, чтобы символы вроде '<' или '>' не были интерпретированы как XML -->
|
||||
<Pattern><![CDATA[^\s*//\s*\[PACKAGE\]\s*(?P<package>.*?)\n//\s*\[FILE\]\s*(?P<file>.*?)\n//\s*\[SEMANTICS\]\s*(?P<semantics>.*)]]></Pattern>
|
||||
<Pattern><![CDATA[^\s*//\s*\[FILE\]\s*(?P<file>.*?)\n//\s*\[SEMANTICS\]\s*(?P<semantics>.*)]]></Pattern>
|
||||
</Definition>
|
||||
<Example><![CDATA[
|
||||
// [PACKAGE] com.example.your.package.name
|
||||
// [FILE] YourFileName.kt
|
||||
// [SEMANTICS] ui, viewmodel, state_management
|
||||
package com.example.your.package.name
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
</META>
|
||||
<INCLUDES>
|
||||
<INCLUDE from="../knowledge_base/semantic_linting.xml"/>
|
||||
<INCLUDE from="../knowledge_base/graphrag_optimization.md"/>
|
||||
<INCLUDE from="../knowledge_base/design_by_contract.md"/>
|
||||
<INCLUDE from="../knowledge_base/ai_friendly_logging.md"/>
|
||||
<INCLUDE from="../knowledge_base/graphrag_optimization.xml"/>
|
||||
<INCLUDE from="../knowledge_base/design_by_contract.xml"/>
|
||||
<INCLUDE from="../knowledge_base/ai_friendly_logging.xml"/>
|
||||
</INCLUDES>
|
||||
</SEMANTIC_ENRICHMENT_PROTOCOL>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</META>
|
||||
|
||||
<ROLE_DEFINITION>
|
||||
<SPECIALIZATION>При исполнении этой роли, я, Gemini, действую как стратегический интерфейс между человеком-архитектором и автоматизированной системой разработки. Моя задача — вести итеративный диалог для уточнения целей, анализировать кодовую базу и, после получения одобрения, инициировать производственную цепочку через выбранный канал задач.</SPECIALIZATION>
|
||||
<SPECIALIZATION>При исполнении этой роли, я действую как стратегический интерфейс между человеком-архитектором и автоматизированной системой разработки. Моя задача — вести итеративный диалог для уточнения целей, анализировать кодовую базу и, после получения одобрения, инициировать производственную цепочку через выбранный канал задач.</SPECIALIZATION>
|
||||
<CORE_GOAL>Основная цель этой роли — трансформировать неструктурированный человеческий диалог в структурированный, машиночитаемый и полностью готовый к исполнению `Work Order` для роли 'Агента-Разработчика'.</CORE_GOAL>
|
||||
</ROLE_DEFINITION>
|
||||
|
||||
|
||||
105
agent_promts/roles/semantic_enrichment_agent.xml
Normal file
105
agent_promts/roles/semantic_enrichment_agent.xml
Normal file
@@ -0,0 +1,105 @@
|
||||
<![CDATA[
|
||||
<AI_AGENT_SEMANTIC_ENRICHMENT_PROTOCOL>
|
||||
<EXTENDS from="base_role.xml"/>
|
||||
|
||||
<META>
|
||||
<PURPOSE>Этот документ определяет операционный протокол для **исполнения роли 'Агента Семантического Обогащения'**. Главная задача — обогащение кодовой базы семантической информацией согласно `SEMANTIC_ENRICHMENT_PROTOCOL`.</PURPOSE>
|
||||
<VERSION>1.0</VERSION>
|
||||
|
||||
<METRICS_TO_COLLECT>
|
||||
<COLLECTS group_id="core_metrics"/>
|
||||
<COLLECTS group_id="enrichment_specific"/>
|
||||
</METRICS_TO_COLLECT>
|
||||
|
||||
<DEPENDS_ON>
|
||||
- ..agent_promts/interfaces/task_channel_interface.xml
|
||||
- ..agent_promts/protocols/semantic_enrichment_protocol.xml
|
||||
</DEPENDS_ON>
|
||||
</META>
|
||||
|
||||
<ROLE_DEFINITION>
|
||||
<SPECIALIZATION>При исполнении этой роли, я действую как агент семантического обогащения. Моя задача - находить и размечать код, добавляя ему семантическую ценность в соответствии с протоколом.</SPECIALIZATION>
|
||||
<CORE_GOAL>Проактивно обогащать кодовую базу семантической разметкой для улучшения машиночитаемости и анализа.</CORE_GOAL>
|
||||
</ROLE_DEFINITION>
|
||||
|
||||
<CORE_PHILOSOPHY>
|
||||
<PHILOSOPHY_PRINCIPLE name="Enrich_Dont_Change_Logic">
|
||||
<DESCRIPTION>Моя работа заключается в добавлении семантических комментариев и аннотаций, не изменяя логику существующего кода.</DESCRIPTION>
|
||||
</PHILOSOPHY_PRINCIPLE>
|
||||
<PHILOSOPHY_PRINCIPLE name="Traceable_And_Reviewable">
|
||||
<DESCRIPTION>Все изменения должны быть доступны для просмотра, например, через Pull Request.</DESCRIPTION>
|
||||
</PHILOSOPHY_PRINCIPLE>
|
||||
</CORE_PHILOSOPHY>
|
||||
|
||||
<BOOTSTRAP_PROTOCOL name="Initialization">
|
||||
<ACTION>Загрузить и полностью проанализировать `agent_promts/protocols/semantic_enrichment_protocol.xml`, включая все вложенные `INCLUDE` файлы, для построения полного набора правил в памяти.</ACTION>
|
||||
</BOOTSTRAP_PROTOCOL>
|
||||
|
||||
<TASK_SPECIFICATION name="Enrichment_Task">
|
||||
<DESCRIPTION>Задачи для этой роли определяют, какие части кодовой базы нужно обогатить.</DESCRIPTION>
|
||||
<STRUCTURE>
|
||||
<![CDATA[
|
||||
<ENRICHMENT_TASK>
|
||||
<SCOPE>full_project | directory | file_list</SCOPE>
|
||||
<TARGET>
|
||||
<!-- Для directory: path/to/dir -->
|
||||
<!-- Для file_list: список файлов -->
|
||||
</TARGET>
|
||||
</ENRICHMENT_TASK>
|
||||
]]>
|
||||
</STRUCTURE>
|
||||
</TASK_SPECIFICATION>
|
||||
|
||||
<MASTER_WORKFLOW name="Enrich_Code_And_Create_PR">
|
||||
<WORKFLOW_STEP id="1" name="Acknowledge_Task">
|
||||
<LET name="WorkOrder" value="CALL MyTaskChannel.FindNextTask(RoleName='agent-enrichment', TaskType='type::enrichment')"/>
|
||||
<IF condition="WorkOrder IS NULL">
|
||||
<TERMINATE/>
|
||||
</IF>
|
||||
<ACTION>CALL MyTaskChannel.UpdateTaskStatus(IssueID={WorkOrder.ID}, NewStatus='status::in-progress')</ACTION>
|
||||
</WORKFLOW_STEP>
|
||||
|
||||
<WORKFLOW_STEP id="2" name="Execute_Enrichment">
|
||||
<ACTION>Извлечь `<ENRICHMENT_TASK>` из `WorkOrder`.</ACTION>
|
||||
<LET name="BranchName">feature/{WorkOrder.ID}/semantic-enrichment</LET>
|
||||
<ACTION>CALL MyTaskChannel.CreateBranch(BranchName={BranchName})</ACTION>
|
||||
<ACTION>Определить `files_to_process` на основе `SCOPE` и `TARGET`.</ACTION>
|
||||
<ACTION>Для каждого файла в `files_to_process` применить правила из `SEMANTIC_ENRICHMENT_PROTOCOL`.</ACTION>
|
||||
</WORKFLOW_STEP>
|
||||
|
||||
<WORKFLOW_STEP id="3" name="Commit_And_PR">
|
||||
<IF condition="есть_изменения">
|
||||
<ACTION>Сделать коммит с сообщением: `feat(enrichment): apply semantic markup`.</ACTION>
|
||||
<ACTION>CALL MyTaskChannel.CommitChanges(...)</ACTION>
|
||||
<LET name="PrID" value="CALL MyTaskChannel.CreatePullRequest(Title='feat(enrichment): Semantic Markup', Body='Closes #{WorkOrder.ID}', HeadBranch={BranchName}, BaseBranch='main')"/>
|
||||
<ACTION>CALL MyTaskChannel.AddComment(IssueID={WorkOrder.ID}, CommentBody='Enrichment complete. PR #{PrID} is ready for review.')</ACTION>
|
||||
</IF>
|
||||
<ELSE>
|
||||
<ACTION>CALL MyTaskChannel.AddComment(IssueID={WorkOrder.ID}, CommentBody='Enrichment complete. No new semantic markup was added.')</ACTION>
|
||||
</ELSE>
|
||||
</WORKFLOW_STEP>
|
||||
|
||||
<WORKFLOW_STEP id="4" name="Finalize">
|
||||
<ACTION>CALL MyTaskChannel.UpdateTaskStatus(IssueID={WorkOrder.ID}, NewStatus='status::completed')</ACTION>
|
||||
</WORKFLOW_STEP>
|
||||
|
||||
<WORKFLOW_STEP id="5" name="Log_Metrics">
|
||||
<ACTION>Отправить метрики через `MyMetricsSink`.</ACTION>
|
||||
</WORKFLOW_STEP>
|
||||
|
||||
<WORKFLOW_STEP id="6" name="Log_Completion">
|
||||
<REQUIRES_CHANNEL type="LogSink" as="MyLogSink"/>
|
||||
<LET name="EnrichmentMetrics" value="CALL MyMetricsSink.GetMetrics(group_id='enrichment_specific')"/>
|
||||
<LET name="LogMessage">
|
||||
`WorkOrder {WorkOrder.ID} completed.
|
||||
- Files Processed: {EnrichmentMetrics.files_processed}
|
||||
- Entities Enriched: {EnrichmentMetrics.entities_enriched}
|
||||
- Relations Added: {EnrichmentMetrics.relations_added}
|
||||
- Contracts Added: {EnrichmentMetrics.contracts_added}
|
||||
- Logs Added: {EnrichmentMetrics.logs_added}`
|
||||
</LET>
|
||||
<ACTION>CALL MyLogSink.Log(FileName="logs/enrichment_agent_log.txt", Content={LogMessage})</ACTION>
|
||||
</WORKFLOW_STEP>
|
||||
</MASTER_WORKFLOW>
|
||||
</AI_AGENT_SEMANTIC_ENRICHMENT_PROTOCOL>
|
||||
]]>
|
||||
@@ -11,13 +11,13 @@
|
||||
</METRICS_TO_COLLECT>
|
||||
|
||||
<DEPENDS_ON>
|
||||
- ../interfaces/task_channel_interface.xml
|
||||
- ../protocols/semantic_enrichment_protocol.xml
|
||||
- ..agent_promts/interfaces/task_channel_interface.xml
|
||||
- ..agent_promts/protocols/semantic_enrichment_protocol.xml
|
||||
</DEPENDS_ON>
|
||||
</META>
|
||||
|
||||
<ROLE_DEFINITION>
|
||||
<SPECIALIZATION>При исполнении этой роли, я, Gemini, действую как автоматизированный хранитель чистоты кода. Моя единственная задача — обеспечить, чтобы каждый файл в указанной области соответствовал `SEMANTIC_ENRICHMENT_PROTOCOL`.</SPECIALIZATION>
|
||||
<SPECIALIZATION>При исполнении этой роли, я, действую как автоматизированный хранитель чистоты кода. Моя единственная задача — обеспечить, чтобы каждый файл в указанной области соответствовал `SEMANTIC_ENRICHMENT_PROTOCOL`.</SPECIALIZATION>
|
||||
<CORE_GOAL>Поддерживать 100% семантическую чистоту и машиночитаемость кодовой базы, делая все изменения отслеживаемыми через систему контроля версий.</CORE_GOAL>
|
||||
</ROLE_DEFINITION>
|
||||
|
||||
@@ -30,33 +30,6 @@
|
||||
</PHILOSOPHY_PRINCIPLE>
|
||||
</CORE_PHILOSOPHY>
|
||||
|
||||
<TOOLS_FOR_ROLE>
|
||||
<TOOL name="CodeEditor">
|
||||
<COMMANDS><COMMAND name="ReadFile"/><COMMAND name="WriteFile"/></COMMANDS>
|
||||
</TOOL>
|
||||
<TOOL name="Shell">
|
||||
<ALLOWED_COMMANDS>
|
||||
<COMMAND>find . -name "*.kt"</COMMAND>
|
||||
<COMMAND>git diff --name-only {commit_range}</COMMAND>
|
||||
</ALLOWED_COMMANDS>
|
||||
</TOOL>
|
||||
</TOOLS_FOR_ROLE>
|
||||
|
||||
<ISSUE_BODY_FORMAT name="Linting_Task_Specification">
|
||||
<DESCRIPTION>Задачи для этой роли должны содержать XML-блок, определяющий режим работы.</DESCRIPTION>
|
||||
<STRUCTURE>
|
||||
<![CDATA[
|
||||
<LINTING_TASK>
|
||||
<MODE>full_project | recent_changes | single_file</MODE>
|
||||
<TARGET>
|
||||
<!-- Для recent_changes: commit range, e.g., HEAD~1..HEAD -->
|
||||
<!-- Для single_file: path/to/file.kt -->
|
||||
</TARGET>
|
||||
</LINTING_TASK>
|
||||
]]>
|
||||
</STRUCTURE>
|
||||
</ISSUE_BODY_FORMAT>
|
||||
|
||||
<MASTER_WORKFLOW name="Lint_And_Create_Pull_Request_Cycle">
|
||||
<WORKFLOW_STEP id="1" name="Find_And_Acknowledge_Task">
|
||||
<LET name="WorkOrder" value="CALL MyTaskChannel.FindNextTask(RoleName='agent-linter', TaskType='type::linting')"/>
|
||||
|
||||
@@ -44,4 +44,12 @@
|
||||
<METRIC id="manual_verification_time_min" type="integer" description="Время, затраченное на ручную проверку, в минутах."/>
|
||||
</METRIC_GROUP>
|
||||
|
||||
<METRIC_GROUP id="enrichment_specific">
|
||||
<METRIC name="files_processed" type="integer" unit="files">Количество обработанных файлов.</METRIC>
|
||||
<METRIC name="entities_enriched" type="integer" unit="entities">Количество обогащенных сущностей (добавлены якоря ENTITY).</METRIC>
|
||||
<METRIC name="relations_added" type="integer" unit="relations">Количество добавленных семантических связей (якоря RELATION).</METRIC>
|
||||
<METRIC name="contracts_added" type="integer" unit="contracts">Количество добавленных KDoc-контрактов.</METRIC>
|
||||
<METRIC name="logs_added" type="integer" unit="logs">Количество добавленных структурированных логов.</METRIC>
|
||||
</METRIC_GROUP>
|
||||
|
||||
</METRICS_CATALOG>
|
||||
|
||||
@@ -36,15 +36,19 @@ android {
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
aidl = false
|
||||
renderScript = false
|
||||
resValues = true
|
||||
shaders = false
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = Versions.composeCompiler
|
||||
@@ -54,6 +58,10 @@ android {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||
}
|
||||
}
|
||||
lint {
|
||||
checkReleaseBuilds = false
|
||||
abortOnError = false
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -61,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)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.homebox.lens">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens
|
||||
// [FILE] MainActivity.kt
|
||||
// [SEMANTICS] ui, activity, entrypoint
|
||||
// [SEMANTICS] app, ui, activity, entrypoint
|
||||
package com.homebox.lens
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -22,7 +21,7 @@ import timber.log.Timber
|
||||
|
||||
// [ENTITY: Activity('MainActivity')]
|
||||
/**
|
||||
* @summary Главная и единственная Activity в приложении.
|
||||
* @summary The main and only Activity in the application.
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens
|
||||
// [FILE] MainApplication.kt
|
||||
// [SEMANTICS] application, hilt, timber
|
||||
// [SEMANTICS] app, hilt, timber, entrypoint
|
||||
package com.homebox.lens
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -11,7 +10,7 @@ import timber.log.Timber
|
||||
|
||||
// [ENTITY: Application('MainApplication')]
|
||||
/**
|
||||
* @summary Точка входа в приложение. Инициализирует Hilt и Timber.
|
||||
* @summary The entry point of the application. Initializes Hilt and Timber.
|
||||
*/
|
||||
@HiltAndroidApp
|
||||
class MainApplication : Application() {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.navigation
|
||||
// [FILE] NavGraph.kt
|
||||
// [SEMANTICS] navigation, compose, nav_host
|
||||
// [SEMANTICS] app, ui, navigation
|
||||
|
||||
package com.homebox.lens.navigation
|
||||
|
||||
@@ -16,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
|
||||
@@ -25,17 +24,22 @@ import com.homebox.lens.ui.screen.locationedit.LocationEditScreen
|
||||
import com.homebox.lens.ui.screen.locationslist.LocationsListScreen
|
||||
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')]
|
||||
// [RELATION: Function('NavGraph')] -> [DEPENDS_ON] -> [Framework('NavHostController')]
|
||||
// [RELATION: Function('NavGraph')] -> [CREATES_INSTANCE_OF] -> [Class('NavigationActions')]
|
||||
// [RELATION: Function('NavGraph')] -> [USES] -> [Screen('SplashScreen')]
|
||||
/**
|
||||
* @summary Определяет граф навигации для всего приложения с использованием Jetpack Compose Navigation.
|
||||
* @param navController Контроллер навигации.
|
||||
* @summary Defines the navigation graph for the entire application using Jetpack Compose Navigation.
|
||||
* @param navController The navigation controller.
|
||||
* @see Screen
|
||||
* @sideeffect Регистрирует все экраны и управляет состоянием навигации.
|
||||
* @invariant Стартовый экран - `Screen.Setup`.
|
||||
* @sideeffect Registers all screens and manages the navigation state.
|
||||
* @invariant The start screen is `Screen.Splash`.
|
||||
*/
|
||||
@Composable
|
||||
fun NavGraph(
|
||||
@@ -47,15 +51,19 @@ fun NavGraph(
|
||||
val navigationActions = remember(navController) {
|
||||
NavigationActions(navController)
|
||||
}
|
||||
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = Screen.Setup.route
|
||||
startDestination = Screen.Splash.route
|
||||
) {
|
||||
composable(route = Screen.Splash.route) {
|
||||
SplashScreen(navController = navController)
|
||||
}
|
||||
composable(route = Screen.Setup.route) {
|
||||
SetupScreen(onSetupComplete = {
|
||||
navController.navigate(Screen.Dashboard.route) {
|
||||
popUpTo(Screen.Setup.route) { inclusive = true }
|
||||
popUpTo(Screen.Setup.route) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -65,8 +73,8 @@ fun NavGraph(
|
||||
navigationActions = navigationActions
|
||||
)
|
||||
}
|
||||
composable(route = Screen.InventoryList.route) {
|
||||
InventoryListScreen(
|
||||
composable(route = Screen.Inventory.route) {
|
||||
InventoryScreen(
|
||||
currentRoute = currentRoute,
|
||||
navigationActions = navigationActions
|
||||
)
|
||||
@@ -101,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"))
|
||||
@@ -137,6 +145,12 @@ fun NavGraph(
|
||||
navigationActions = navigationActions
|
||||
)
|
||||
}
|
||||
composable(route = Screen.Settings.route) {
|
||||
SettingsScreen(
|
||||
currentRoute = currentRoute,
|
||||
navigationActions = navigationActions
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('NavGraph')]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.components
|
||||
// [FILE] ColorPicker.kt
|
||||
// [SEMANTICS] ui, component, color_selection
|
||||
// [SEMANTICS] app, ui, component, color
|
||||
|
||||
package com.homebox.lens.ui.components
|
||||
|
||||
@@ -25,10 +24,10 @@ import com.homebox.lens.R
|
||||
|
||||
// [ENTITY: Function('ColorPicker')]
|
||||
/**
|
||||
* @summary Компонент для выбора цвета.
|
||||
* @param selectedColor Текущий выбранный цвет в формате HEX строки (например, "#FFFFFF").
|
||||
* @param onColorSelected Лямбда-функция, вызываемая при выборе нового цвета.
|
||||
* @param modifier Модификатор для настройки внешнего вида.
|
||||
* @summary A component for color selection.
|
||||
* @param selectedColor The currently selected color in HEX string format (e.g., "#FFFFFF").
|
||||
* @param onColorSelected A lambda function called when a new color is selected.
|
||||
* @param modifier A modifier for customizing the appearance.
|
||||
*/
|
||||
@Composable
|
||||
fun ColorPicker(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.components
|
||||
// [FILE] LoadingOverlay.kt
|
||||
// [SEMANTICS] ui, component, loading
|
||||
// [SEMANTICS] app, ui, component, loading
|
||||
|
||||
package com.homebox.lens.ui.components
|
||||
|
||||
@@ -18,7 +17,7 @@ import androidx.compose.ui.graphics.Color
|
||||
|
||||
// [ENTITY: Function('LoadingOverlay')]
|
||||
/**
|
||||
* @summary Полноэкранный оверлей с индикатором загрузки.
|
||||
* @summary A full-screen overlay with a loading indicator.
|
||||
*/
|
||||
@Composable
|
||||
fun LoadingOverlay() {
|
||||
|
||||
62
app/src/main/java/com/homebox/lens/ui/mapper/ItemMapper.kt
Normal file
62
app/src/main/java/com/homebox/lens/ui/mapper/ItemMapper.kt
Normal file
@@ -0,0 +1,62 @@
|
||||
// [FILE] ItemMapper.kt
|
||||
// [SEMANTICS] app, ui, mapper, item
|
||||
package com.homebox.lens.ui.mapper
|
||||
|
||||
import com.homebox.lens.domain.model.Item
|
||||
import com.homebox.lens.domain.model.ItemOut
|
||||
import com.homebox.lens.domain.model.Label
|
||||
import com.homebox.lens.domain.model.Location
|
||||
import javax.inject.Inject
|
||||
|
||||
// [ENTITY: Class('ItemMapper')]
|
||||
/**
|
||||
* @summary Maps Item data between domain and UI layers.
|
||||
* @invariant This class is stateless and its methods are pure functions.
|
||||
*/
|
||||
class ItemMapper @Inject constructor() {
|
||||
|
||||
// [ENTITY: Function('toItem')]
|
||||
// [RELATION: Function('toItem')] -> [CREATES_INSTANCE_OF] -> [DataClass('Item')]
|
||||
/**
|
||||
* @summary Converts a detailed [ItemOut] from the domain layer to a simplified [Item] for the UI layer.
|
||||
* @param itemOut The [ItemOut] object to convert.
|
||||
* @return The resulting [Item] object.
|
||||
* @precondition itemOut MUST NOT be null.
|
||||
* @postcondition The returned Item will be a valid representation for the UI.
|
||||
*/
|
||||
fun toItem(itemOut: ItemOut): Item {
|
||||
return Item(
|
||||
id = itemOut.id,
|
||||
name = itemOut.name,
|
||||
description = itemOut.description,
|
||||
quantity = itemOut.quantity,
|
||||
image = itemOut.images.firstOrNull { it.isPrimary }?.path,
|
||||
location = itemOut.location?.let { Location(it.id, it.name) },
|
||||
labels = itemOut.labels.map { Label(it.id, it.name) },
|
||||
purchasePrice = itemOut.purchasePrice,
|
||||
createdAt = itemOut.createdAt,
|
||||
archived = itemOut.isArchived,
|
||||
assetId = itemOut.assetId,
|
||||
fields = itemOut.fields.map { com.homebox.lens.domain.model.CustomField(it.name, it.value, it.type) },
|
||||
insured = itemOut.insured ?: false,
|
||||
lifetimeWarranty = itemOut.lifetimeWarranty ?: false,
|
||||
manufacturer = itemOut.manufacturer,
|
||||
modelNumber = itemOut.modelNumber,
|
||||
notes = itemOut.notes,
|
||||
parentId = itemOut.parent?.id,
|
||||
purchaseFrom = itemOut.purchaseFrom,
|
||||
purchaseTime = itemOut.purchaseTime,
|
||||
serialNumber = itemOut.serialNumber,
|
||||
soldNotes = itemOut.soldNotes,
|
||||
soldPrice = itemOut.soldPrice,
|
||||
soldTime = itemOut.soldTime,
|
||||
soldTo = itemOut.soldTo,
|
||||
syncChildItemsLocations = itemOut.syncChildItemsLocations ?: false,
|
||||
warrantyDetails = itemOut.warrantyDetails,
|
||||
warrantyExpires = itemOut.warrantyExpires
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('toItem')]
|
||||
}
|
||||
// [END_ENTITY: Class('ItemMapper')]
|
||||
// [END_FILE_ItemMapper.kt]
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.dashboard
|
||||
// [FILE] DashboardScreen.kt
|
||||
// [SEMANTICS] ui, screen, dashboard, compose, navigation
|
||||
// [SEMANTICS] app, ui, screen, dashboard
|
||||
package com.homebox.lens.ui.screen.dashboard
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -37,11 +36,11 @@ import timber.log.Timber
|
||||
// [RELATION: Function('DashboardScreen')] -> [DEPENDS_ON] -> [Class('NavigationActions')]
|
||||
// [RELATION: Function('DashboardScreen')] -> [CALLS] -> [Function('MainScaffold')]
|
||||
/**
|
||||
* @summary Главная Composable-функция для экрана "Панель управления".
|
||||
* @param viewModel ViewModel для этого экрана, предоставляется через Hilt.
|
||||
* @param currentRoute Текущий маршрут для подсветки активного элемента в Drawer.
|
||||
* @param navigationActions Объект с навигационными действиями.
|
||||
* @sideeffect Вызывает навигационные лямбды при взаимодействии с UI.
|
||||
* @summary The main Composable function for the "Dashboard" screen.
|
||||
* @param viewModel The ViewModel for this screen, provided by Hilt.
|
||||
* @param currentRoute The current route to highlight the active item in the Drawer.
|
||||
* @param navigationActions The object with navigation actions.
|
||||
* @sideeffect Calls navigation lambdas upon UI interaction.
|
||||
*/
|
||||
@Composable
|
||||
fun DashboardScreen(
|
||||
@@ -82,11 +81,11 @@ fun DashboardScreen(
|
||||
// [ENTITY: Function('DashboardContent')]
|
||||
// [RELATION: Function('DashboardContent')] -> [CONSUMES_STATE] -> [SealedInterface('DashboardUiState')]
|
||||
/**
|
||||
* @summary Отображает основной контент экрана в зависимости от uiState.
|
||||
* @param modifier Модификатор для стилизации.
|
||||
* @param uiState Текущее состояние UI экрана.
|
||||
* @param onLocationClick Лямбда-обработчик нажатия на местоположение.
|
||||
* @param onLabelClick Лямбда-обработчик нажатия на метку.
|
||||
* @summary Displays the main content of the screen depending on the uiState.
|
||||
* @param modifier A modifier for styling.
|
||||
* @param uiState The current UI state of the screen.
|
||||
* @param onLocationClick A lambda handler for clicking on a location.
|
||||
* @param onLabelClick A lambda handler for clicking on a label.
|
||||
*/
|
||||
@Composable
|
||||
private fun DashboardContent(
|
||||
@@ -132,8 +131,8 @@ private fun DashboardContent(
|
||||
// [ENTITY: Function('StatisticsSection')]
|
||||
// [RELATION: Function('StatisticsSection')] -> [DEPENDS_ON] -> [DataClass('GroupStatistics')]
|
||||
/**
|
||||
* @summary Секция для отображения общей статистики.
|
||||
* @param statistics Объект со статистическими данными.
|
||||
* @summary Section for displaying general statistics.
|
||||
* @param statistics The object with statistical data.
|
||||
*/
|
||||
@Composable
|
||||
private fun StatisticsSection(statistics: GroupStatistics) {
|
||||
@@ -164,9 +163,9 @@ private fun StatisticsSection(statistics: GroupStatistics) {
|
||||
|
||||
// [ENTITY: Function('StatisticCard')]
|
||||
/**
|
||||
* @summary Карточка для отображения одного статистического показателя.
|
||||
* @param title Название показателя.
|
||||
* @param value Значение показателя.
|
||||
* @summary Card for displaying a single statistical indicator.
|
||||
* @param title The name of the indicator.
|
||||
* @param value The value of the indicator.
|
||||
*/
|
||||
@Composable
|
||||
private fun StatisticCard(title: String, value: String) {
|
||||
@@ -180,8 +179,8 @@ private fun StatisticCard(title: String, value: String) {
|
||||
// [ENTITY: Function('RecentlyAddedSection')]
|
||||
// [RELATION: Function('RecentlyAddedSection')] -> [DEPENDS_ON] -> [DataClass('ItemSummary')]
|
||||
/**
|
||||
* @summary Секция для отображения недавно добавленных элементов.
|
||||
* @param items Список элементов для отображения.
|
||||
* @summary Section for displaying recently added items.
|
||||
* @param items The list of items to display.
|
||||
*/
|
||||
@Composable
|
||||
private fun RecentlyAddedSection(items: List<ItemSummary>) {
|
||||
@@ -213,8 +212,8 @@ private fun RecentlyAddedSection(items: List<ItemSummary>) {
|
||||
// [ENTITY: Function('ItemCard')]
|
||||
// [RELATION: Function('ItemCard')] -> [DEPENDS_ON] -> [DataClass('ItemSummary')]
|
||||
/**
|
||||
* @summary Карточка для отображения краткой информации об элементе.
|
||||
* @param item Элемент для отображения.
|
||||
* @summary Card for displaying brief information about an item.
|
||||
* @param item The item to display.
|
||||
*/
|
||||
@Composable
|
||||
private fun ItemCard(item: ItemSummary) {
|
||||
@@ -236,9 +235,9 @@ private fun ItemCard(item: ItemSummary) {
|
||||
// [ENTITY: Function('LocationsSection')]
|
||||
// [RELATION: Function('LocationsSection')] -> [DEPENDS_ON] -> [DataClass('LocationOutCount')]
|
||||
/**
|
||||
* @summary Секция для отображения местоположений в виде чипсов.
|
||||
* @param locations Список местоположений.
|
||||
* @param onLocationClick Лямбда-обработчик нажатия на местоположение.
|
||||
* @summary Section for displaying locations as chips.
|
||||
* @param locations The list of locations.
|
||||
* @param onLocationClick A lambda handler for clicking on a location.
|
||||
*/
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
@@ -265,9 +264,9 @@ private fun LocationsSection(locations: List<LocationOutCount>, onLocationClick:
|
||||
// [ENTITY: Function('LabelsSection')]
|
||||
// [RELATION: Function('LabelsSection')] -> [DEPENDS_ON] -> [DataClass('LabelOut')]
|
||||
/**
|
||||
* @summary Секция для отображения меток в виде чипсов.
|
||||
* @param labels Список меток.
|
||||
* @param onLabelClick Лямбда-обработчик нажатия на метку.
|
||||
* @summary Section for displaying labels as chips.
|
||||
* @param labels The list of labels.
|
||||
* @param onLabelClick A lambda handler for clicking on a label.
|
||||
*/
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
@@ -310,10 +309,10 @@ fun DashboardContentSuccessPreview() {
|
||||
LocationOutCount(id="5",name="Basement", color = "#00FFFF", isArchived = false, itemCount = 3, createdAt = "", updatedAt = "")
|
||||
),
|
||||
labels = listOf(
|
||||
LabelOut(id="1", name="electronics", color = "#FF0000", isArchived = false, createdAt = "", updatedAt = ""),
|
||||
LabelOut(id="2", name="important", color = "#00FF00", isArchived = false, createdAt = "", updatedAt = ""),
|
||||
LabelOut(id="3", name="seasonal", color = "#0000FF", isArchived = false, createdAt = "", updatedAt = ""),
|
||||
LabelOut(id="4", name="hobby", color = "#FFFF00", isArchived = false, createdAt = "", updatedAt = "")
|
||||
LabelOut(id="1", name="electronics", description = null, color = "#FF0000", isArchived = false, createdAt = "", updatedAt = ""),
|
||||
LabelOut(id="2", name="important", description = null, color = "#00FF00", isArchived = false, createdAt = "", updatedAt = ""),
|
||||
LabelOut(id="3", name="seasonal", description = null, color = "#0000FF", isArchived = false, createdAt = "", updatedAt = ""),
|
||||
LabelOut(id="4", name="hobby", description = null, color = "#FFFF00", isArchived = false, createdAt = "", updatedAt = "")
|
||||
),
|
||||
recentlyAddedItems = emptyList()
|
||||
)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.dashboard
|
||||
// [FILE] DashboardUiState.kt
|
||||
// [SEMANTICS] ui, state, dashboard
|
||||
// [SEMANTICS] app, ui, state, dashboard
|
||||
package com.homebox.lens.ui.screen.dashboard
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -12,8 +11,8 @@ import com.homebox.lens.domain.model.LocationOutCount
|
||||
|
||||
// [ENTITY: SealedInterface('DashboardUiState')]
|
||||
/**
|
||||
* @summary Определяет все возможные состояния для экрана "Дэшборд".
|
||||
* @invariant В любой момент времени экран может находиться только в одном из этих состояний.
|
||||
* @summary Defines all possible states for the "Dashboard" screen.
|
||||
* @invariant At any given time, the screen can only be in one of these states.
|
||||
*/
|
||||
sealed interface DashboardUiState {
|
||||
// [ENTITY: DataClass('Success')]
|
||||
@@ -22,11 +21,11 @@ sealed interface DashboardUiState {
|
||||
// [RELATION: DataClass('Success')] -> [DEPENDS_ON] -> [DataClass('LabelOut')]
|
||||
// [RELATION: DataClass('Success')] -> [DEPENDS_ON] -> [DataClass('ItemSummary')]
|
||||
/**
|
||||
* @summary Состояние успешной загрузки данных.
|
||||
* @param statistics Статистика по инвентарю.
|
||||
* @param locations Список локаций со счетчиками.
|
||||
* @param labels Список всех меток.
|
||||
* @param recentlyAddedItems Список недавно добавленных товаров.
|
||||
* @summary The state of a successful data load.
|
||||
* @param statistics The inventory statistics.
|
||||
* @param locations The list of locations with counters.
|
||||
* @param labels The list of all labels.
|
||||
* @param recentlyAddedItems The list of recently added items.
|
||||
*/
|
||||
data class Success(
|
||||
val statistics: GroupStatistics,
|
||||
@@ -38,15 +37,15 @@ sealed interface DashboardUiState {
|
||||
|
||||
// [ENTITY: DataClass('Error')]
|
||||
/**
|
||||
* @summary Состояние ошибки во время загрузки данных.
|
||||
* @param message Человекочитаемое сообщение об ошибке.
|
||||
* @summary The state of an error during data loading.
|
||||
* @param message A human-readable error message.
|
||||
*/
|
||||
data class Error(val message: String) : DashboardUiState
|
||||
// [END_ENTITY: DataClass('Error')]
|
||||
|
||||
// [ENTITY: Object('Loading')]
|
||||
/**
|
||||
* @summary Состояние, когда данные для экрана загружаются.
|
||||
* @summary The state when data for the screen is being loaded.
|
||||
*/
|
||||
data object Loading : DashboardUiState
|
||||
// [END_ENTITY: Object('Loading')]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.dashboard
|
||||
// [FILE] DashboardViewModel.kt
|
||||
// [SEMANTICS] ui_logic, dashboard, state_management, sealed_state, parallel_data_loading, timber_logging
|
||||
// [SEMANTICS] app, ui, viewmodel, dashboard
|
||||
package com.homebox.lens.ui.screen.dashboard
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -24,10 +23,10 @@ import javax.inject.Inject
|
||||
// [RELATION: ViewModel('DashboardViewModel')] -> [DEPENDS_ON] -> [UseCase('GetRecentlyAddedItemsUseCase')]
|
||||
// [RELATION: ViewModel('DashboardViewModel')] -> [EMITS_STATE] -> [SealedInterface('DashboardUiState')]
|
||||
/**
|
||||
* @summary ViewModel для главного экрана (Dashboard).
|
||||
* @description Оркестрирует загрузку данных для Dashboard, используя строгую модель состояний
|
||||
* (`DashboardUiState`), и обрабатывает параллельные запросы без состояний гонки.
|
||||
* @invariant `uiState` всегда является одним из состояний, определенных в `DashboardUiState`.
|
||||
* @summary ViewModel for the main screen (Dashboard).
|
||||
* @description Orchestrates the loading of data for the Dashboard, using a strict state model
|
||||
* (`DashboardUiState`), and handles parallel requests without race conditions.
|
||||
* @invariant `uiState` is always one of the states defined in `DashboardUiState`.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class DashboardViewModel @Inject constructor(
|
||||
@@ -46,10 +45,10 @@ class DashboardViewModel @Inject constructor(
|
||||
|
||||
// [ENTITY: Function('loadDashboardData')]
|
||||
/**
|
||||
* @summary Загружает все необходимые данные для экрана Dashboard.
|
||||
* @description Выполняет UseCase'ы параллельно и обновляет UI, переключая его
|
||||
* между состояниями `Loading`, `Success` и `Error` из `DashboardUiState`.
|
||||
* @sideeffect Асинхронно обновляет `_uiState` одним из состояний `DashboardUiState`.
|
||||
* @summary Loads all necessary data for the Dashboard screen.
|
||||
* @description Executes UseCases in parallel and updates the UI by switching it
|
||||
* between the `Loading`, `Success`, and `Error` states from `DashboardUiState`.
|
||||
* @sideeffect Asynchronously updates `_uiState` with one of the `DashboardUiState` states.
|
||||
*/
|
||||
fun loadDashboardData() {
|
||||
viewModelScope.launch {
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.inventorylist
|
||||
// [FILE] InventoryListScreen.kt
|
||||
// [SEMANTICS] ui, screen, inventory, 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-функция для экрана "Список инвентаря".
|
||||
* @param currentRoute Текущий маршрут для подсветки активного элемента в Drawer.
|
||||
* @param navigationActions Объект с навигационными действиями.
|
||||
*/
|
||||
@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]
|
||||
@@ -1,21 +0,0 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.inventorylist
|
||||
// [FILE] InventoryListViewModel.kt
|
||||
// [SEMANTICS] ui, viewmodel, inventory_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]
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.itemdetails
|
||||
// [FILE] ItemDetailsScreen.kt
|
||||
// [SEMANTICS] ui, screen, item, details
|
||||
// [SEMANTICS] app, ui, screen, details
|
||||
|
||||
package com.homebox.lens.ui.screen.itemdetails
|
||||
|
||||
@@ -17,9 +16,9 @@ import com.homebox.lens.ui.common.MainScaffold
|
||||
// [RELATION: Function('ItemDetailsScreen')] -> [DEPENDS_ON] -> [Class('NavigationActions')]
|
||||
// [RELATION: Function('ItemDetailsScreen')] -> [CALLS] -> [Function('MainScaffold')]
|
||||
/**
|
||||
* @summary Composable-функция для экрана "Детали элемента".
|
||||
* @param currentRoute Текущий маршрут для подсветки активного элемента в Drawer.
|
||||
* @param navigationActions Объект с навигационными действиями.
|
||||
* @summary Composable function for the "Item Details" screen.
|
||||
* @param currentRoute The current route to highlight the active item in the Drawer.
|
||||
* @param navigationActions The object with navigation actions.
|
||||
*/
|
||||
@Composable
|
||||
fun ItemDetailsScreen(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.itemdetails
|
||||
// [FILE] ItemDetailsViewModel.kt
|
||||
// [SEMANTICS] ui, viewmodel, item_details
|
||||
// [SEMANTICS] app, ui, viewmodel, details
|
||||
package com.homebox.lens.ui.screen.itemdetails
|
||||
|
||||
// [IMPORTS]
|
||||
|
||||
@@ -1,32 +1,56 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.itemedit
|
||||
// [FILE] ItemEditScreen.kt
|
||||
// [SEMANTICS] ui, screen, item, edit
|
||||
// [SEMANTICS] app, ui, screen, edit
|
||||
|
||||
package com.homebox.lens.ui.screen.itemedit
|
||||
|
||||
// [IMPORTS]
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||
import androidx.compose.material.icons.filled.DateRange
|
||||
import androidx.compose.material.icons.filled.Save
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.DatePicker
|
||||
import androidx.compose.material3.DatePickerDialog
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExposedDropdownMenuBox
|
||||
import androidx.compose.material3.ExposedDropdownMenuDefaults
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberDatePickerState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
@@ -36,21 +60,25 @@ import com.homebox.lens.R
|
||||
import com.homebox.lens.navigation.NavigationActions
|
||||
import com.homebox.lens.ui.common.MainScaffold
|
||||
import timber.log.Timber
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: Function('ItemEditScreen')]
|
||||
// [RELATION: Function('ItemEditScreen')] -> [DEPENDS_ON] -> [Class('NavigationActions')]
|
||||
// [RELATION: Function('ItemEditScreen')] -> [DEPENDS_ON] -> [ViewModel('ItemEditViewModel')]
|
||||
// [RELATION: Function('ItemEditScreen')] -> [CONSUMES_STATE] -> [DataClass('ItemEditUiState')]
|
||||
// [RELATION: Function('ItemEditScreen')] -> [CALLS] -> [Function('MainScaffold')]
|
||||
// [ENTITY: Composable('ItemEditScreen')]
|
||||
// [RELATION: Composable('ItemEditScreen')] -> [DEPENDS_ON] -> [Class('NavigationActions')]
|
||||
// [RELATION: Composable('ItemEditScreen')] -> [DEPENDS_ON] -> [ViewModel('ItemEditViewModel')]
|
||||
// [RELATION: Composable('ItemEditScreen')] -> [CONSUMES_STATE] -> [DataClass('ItemEditUiState')]
|
||||
// [RELATION: Composable('ItemEditScreen')] -> [CALLS] -> [Composable('MainScaffold')]
|
||||
/**
|
||||
* @summary Composable-функция для экрана "Редактирование элемента".
|
||||
* @param currentRoute Текущий маршрут для подсветки активного элемента в Drawer.
|
||||
* @param navigationActions Объект с навигационными действиями.
|
||||
* @param itemId ID элемента для редактирования. Null, если создается новый элемент.
|
||||
* @param viewModel ViewModel для управления состоянием экрана.
|
||||
* @param onSaveSuccess Callback, вызываемый после успешного сохранения товара.
|
||||
* @summary Composable function for the "Edit Item" screen.
|
||||
* @param currentRoute The current route to highlight the active item in the Drawer.
|
||||
* @param navigationActions The object with navigation actions.
|
||||
* @param itemId The ID of the item to edit. Null if a new item is being created.
|
||||
* @param viewModel The ViewModel for managing the screen's state.
|
||||
* @param onSaveSuccess A callback invoked after the item is successfully saved.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ItemEditScreen(
|
||||
currentRoute: String?,
|
||||
@@ -75,7 +103,7 @@ fun ItemEditScreen(
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.saveCompleted.collect {
|
||||
viewModel.saveCompleted.collect {
|
||||
Timber.i("[INFO][ACTION][save_completed_callback] Item save completed. Triggering onSaveSuccess.")
|
||||
onSaveSuccess()
|
||||
}
|
||||
@@ -85,7 +113,7 @@ fun ItemEditScreen(
|
||||
topBarTitle = stringResource(id = R.string.item_edit_title),
|
||||
currentRoute = currentRoute,
|
||||
navigationActions = navigationActions
|
||||
) {
|
||||
) { paddingValues ->
|
||||
Scaffold(
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
floatingActionButton = {
|
||||
@@ -100,40 +128,479 @@ fun ItemEditScreen(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(it)
|
||||
.padding(paddingValues)
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
if (uiState.isLoading) {
|
||||
CircularProgressIndicator(modifier = Modifier.fillMaxWidth())
|
||||
} else {
|
||||
uiState.item?.let { item ->
|
||||
OutlinedTextField(
|
||||
value = item.name,
|
||||
onValueChange = { viewModel.updateName(it) },
|
||||
label = { Text(stringResource(R.string.item_name)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.description ?: "",
|
||||
onValueChange = { viewModel.updateDescription(it) },
|
||||
label = { Text(stringResource(R.string.item_description)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.quantity.toString(),
|
||||
onValueChange = { viewModel.updateQuantity(it.toIntOrNull() ?: 0) },
|
||||
label = { Text(stringResource(R.string.item_quantity)) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
// Add more fields as needed
|
||||
// [AI_NOTE]: General Information section for basic item details.
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(4.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.item_edit_general_information),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
OutlinedTextField(
|
||||
value = item.name,
|
||||
onValueChange = { viewModel.updateName(it) },
|
||||
label = { Text(stringResource(R.string.item_name)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.description ?: "",
|
||||
onValueChange = { viewModel.updateDescription(it) },
|
||||
label = { Text(stringResource(R.string.item_description)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.quantity.toString(),
|
||||
onValueChange = { viewModel.updateQuantity(it.toIntOrNull() ?: 0) },
|
||||
label = { Text(stringResource(R.string.item_quantity)) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Location Dropdown
|
||||
var locationExpanded by remember { mutableStateOf(false) }
|
||||
ExposedDropdownMenuBox(
|
||||
expanded = locationExpanded,
|
||||
onExpandedChange = { locationExpanded = !locationExpanded }
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = item.location?.name ?: "",
|
||||
onValueChange = { },
|
||||
label = { Text(stringResource(R.string.item_edit_location)) },
|
||||
readOnly = true,
|
||||
trailingIcon = {
|
||||
ExposedDropdownMenuDefaults.TrailingIcon(expanded = locationExpanded)
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.menuAnchor()
|
||||
)
|
||||
ExposedDropdownMenu(
|
||||
expanded = locationExpanded,
|
||||
onDismissRequest = { locationExpanded = false }
|
||||
) {
|
||||
uiState.allLocations.forEach { location ->
|
||||
DropdownMenuItem(
|
||||
text = { Text(location.name) },
|
||||
onClick = {
|
||||
viewModel.updateLocation(location)
|
||||
locationExpanded = false
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// Labels Dialog
|
||||
var showLabelsDialog by remember { mutableStateOf(false) }
|
||||
|
||||
OutlinedTextField(
|
||||
value = item.labels.joinToString { it.name },
|
||||
onValueChange = { },
|
||||
label = { Text(stringResource(R.string.item_edit_labels)) },
|
||||
readOnly = true,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { showLabelsDialog = true },
|
||||
trailingIcon = {
|
||||
Icon(Icons.Filled.ArrowDropDown, contentDescription = stringResource(R.string.item_edit_select_labels))
|
||||
}
|
||||
)
|
||||
|
||||
if (showLabelsDialog) {
|
||||
// This state will hold the temporary selections within the dialog
|
||||
val tempSelectedLabels = remember { mutableStateOf(item.labels.toSet()) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = { showLabelsDialog = false },
|
||||
title = { Text(stringResource(R.string.item_edit_select_labels)) },
|
||||
text = {
|
||||
Column {
|
||||
uiState.allLabels.forEach { label ->
|
||||
val isChecked = tempSelectedLabels.value.contains(label)
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable {
|
||||
val currentSelection = tempSelectedLabels.value.toMutableSet()
|
||||
if (isChecked) {
|
||||
currentSelection.remove(label)
|
||||
} else {
|
||||
currentSelection.add(label)
|
||||
}
|
||||
tempSelectedLabels.value = currentSelection
|
||||
}
|
||||
.padding(vertical = 8.dp)
|
||||
) {
|
||||
Checkbox(
|
||||
checked = isChecked,
|
||||
onCheckedChange = {
|
||||
val currentSelection = tempSelectedLabels.value.toMutableSet()
|
||||
if (it) {
|
||||
currentSelection.add(label)
|
||||
} else {
|
||||
currentSelection.remove(label)
|
||||
}
|
||||
tempSelectedLabels.value = currentSelection
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = label.name,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
// Update the ViewModel with the final selection
|
||||
viewModel.updateLabels(tempSelectedLabels.value.toList())
|
||||
showLabelsDialog = false
|
||||
}
|
||||
) {
|
||||
Text(stringResource(R.string.dialog_ok))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showLabelsDialog = false }) {
|
||||
Text(stringResource(R.string.dialog_cancel))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// [AI_NOTE]: Purchase Information section.
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(4.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.item_edit_purchase_information),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
OutlinedTextField(
|
||||
value = item.purchasePrice?.toString() ?: "",
|
||||
onValueChange = { viewModel.updatePurchasePrice(it.toDoubleOrNull()) },
|
||||
label = { Text(stringResource(R.string.item_edit_purchase_price)) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.purchaseFrom ?: "",
|
||||
onValueChange = { viewModel.updatePurchaseFrom(it) },
|
||||
label = { Text(stringResource(R.string.item_edit_purchase_from)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
// [AI_NOTE]: Date picker for purchase time.
|
||||
var showPurchaseDatePicker by remember { mutableStateOf(false) }
|
||||
val purchaseDateState = rememberDatePickerState()
|
||||
OutlinedTextField(
|
||||
value = item.purchaseTime ?: "",
|
||||
onValueChange = { }, // Read-only, handled by date picker
|
||||
label = { Text(stringResource(R.string.item_edit_purchase_time)) },
|
||||
readOnly = true,
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { showPurchaseDatePicker = true }) {
|
||||
Icon(Icons.Filled.DateRange, contentDescription = stringResource(R.string.item_edit_select_date))
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { showPurchaseDatePicker = true }
|
||||
)
|
||||
if (showPurchaseDatePicker) {
|
||||
DatePickerDialog(
|
||||
onDismissRequest = { showPurchaseDatePicker = false },
|
||||
confirmButton = {
|
||||
Text(stringResource(R.string.dialog_ok), modifier = Modifier.clickable {
|
||||
val selectedDate = purchaseDateState.selectedDateMillis?.let {
|
||||
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(it))
|
||||
}
|
||||
if (selectedDate != null) {
|
||||
viewModel.updatePurchaseTime(selectedDate)
|
||||
}
|
||||
showPurchaseDatePicker = false
|
||||
})
|
||||
},
|
||||
dismissButton = {
|
||||
Text(stringResource(R.string.dialog_cancel), modifier = Modifier.clickable { showPurchaseDatePicker = false })
|
||||
}
|
||||
) {
|
||||
DatePicker(state = purchaseDateState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// [AI_NOTE]: Warranty Information section.
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(4.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.item_edit_warranty_information),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(stringResource(R.string.item_edit_lifetime_warranty))
|
||||
Switch(
|
||||
checked = item.lifetimeWarranty,
|
||||
onCheckedChange = { viewModel.updateLifetimeWarranty(it) }
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.warrantyDetails ?: "",
|
||||
onValueChange = { viewModel.updateWarrantyDetails(it) },
|
||||
label = { Text(stringResource(R.string.item_edit_warranty_details)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
// [AI_NOTE]: Date picker for warranty expiration.
|
||||
var showWarrantyDatePicker by remember { mutableStateOf(false) }
|
||||
val warrantyDateState = rememberDatePickerState()
|
||||
OutlinedTextField(
|
||||
value = item.warrantyExpires ?: "",
|
||||
onValueChange = { }, // Read-only, handled by date picker
|
||||
label = { Text(stringResource(R.string.item_edit_warranty_expires)) },
|
||||
readOnly = true,
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { showWarrantyDatePicker = true }) {
|
||||
Icon(Icons.Filled.DateRange, contentDescription = stringResource(R.string.item_edit_select_date))
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { showWarrantyDatePicker = true }
|
||||
)
|
||||
if (showWarrantyDatePicker) {
|
||||
DatePickerDialog(
|
||||
onDismissRequest = { showWarrantyDatePicker = false },
|
||||
confirmButton = {
|
||||
Text(stringResource(R.string.dialog_ok), modifier = Modifier.clickable {
|
||||
val selectedDate = warrantyDateState.selectedDateMillis?.let {
|
||||
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(it))
|
||||
}
|
||||
if (selectedDate != null) {
|
||||
viewModel.updateWarrantyExpires(selectedDate)
|
||||
}
|
||||
showWarrantyDatePicker = false
|
||||
})
|
||||
},
|
||||
dismissButton = {
|
||||
Text(stringResource(R.string.dialog_cancel), modifier = Modifier.clickable { showWarrantyDatePicker = false })
|
||||
}
|
||||
) {
|
||||
DatePicker(state = warrantyDateState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// [AI_NOTE]: Identification section.
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(4.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.item_edit_identification),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
OutlinedTextField(
|
||||
value = item.assetId ?: "",
|
||||
onValueChange = { viewModel.updateAssetId(it) },
|
||||
label = { Text(stringResource(R.string.item_edit_asset_id)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.serialNumber ?: "",
|
||||
onValueChange = { viewModel.updateSerialNumber(it) },
|
||||
label = { Text(stringResource(R.string.item_edit_serial_number)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.manufacturer ?: "",
|
||||
onValueChange = { viewModel.updateManufacturer(it) },
|
||||
label = { Text(stringResource(R.string.item_edit_manufacturer)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.modelNumber ?: "",
|
||||
onValueChange = { viewModel.updateModelNumber(it) },
|
||||
label = { Text(stringResource(R.string.item_edit_model_number)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// [AI_NOTE]: Status & Notes section.
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(4.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.item_edit_status_notes),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(stringResource(R.string.item_edit_archived))
|
||||
Switch(
|
||||
checked = item.archived,
|
||||
onCheckedChange = { viewModel.updateArchived(it) }
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(stringResource(R.string.item_edit_insured))
|
||||
Switch(
|
||||
checked = item.insured,
|
||||
onCheckedChange = { viewModel.updateInsured(it) }
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.notes ?: "",
|
||||
onValueChange = { viewModel.updateNotes(it) },
|
||||
label = { Text(stringResource(R.string.item_edit_notes)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// [AI_NOTE]: Sold Information section (conditionally displayed).
|
||||
if (item.soldTime != null || item.soldPrice != null || item.soldTo != null || item.soldNotes != null) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(4.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = stringResource(R.string.item_edit_sold_information),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
OutlinedTextField(
|
||||
value = item.soldPrice?.toString() ?: "",
|
||||
onValueChange = { viewModel.updateSoldPrice(it.toDoubleOrNull()) },
|
||||
label = { Text(stringResource(R.string.item_edit_sold_price)) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.soldTo ?: "",
|
||||
onValueChange = { viewModel.updateSoldTo(it) },
|
||||
label = { Text(stringResource(R.string.item_edit_sold_to)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = item.soldNotes ?: "",
|
||||
onValueChange = { viewModel.updateSoldNotes(it) },
|
||||
label = { Text(stringResource(R.string.item_edit_sold_notes)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
// [AI_NOTE]: Date picker for sold time.
|
||||
var showSoldDatePicker by remember { mutableStateOf(false) }
|
||||
val soldDateState = rememberDatePickerState()
|
||||
OutlinedTextField(
|
||||
value = item.soldTime ?: "",
|
||||
onValueChange = { }, // Read-only, handled by date picker
|
||||
label = { Text(stringResource(R.string.item_edit_sold_time)) },
|
||||
readOnly = true,
|
||||
trailingIcon = {
|
||||
IconButton(onClick = { showSoldDatePicker = true }) {
|
||||
Icon(Icons.Filled.DateRange, contentDescription = stringResource(R.string.item_edit_select_date))
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable { showSoldDatePicker = true }
|
||||
)
|
||||
if (showSoldDatePicker) {
|
||||
DatePickerDialog(
|
||||
onDismissRequest = { showSoldDatePicker = false },
|
||||
confirmButton = {
|
||||
Text(stringResource(R.string.dialog_ok), modifier = Modifier.clickable {
|
||||
val selectedDate = soldDateState.selectedDateMillis?.let {
|
||||
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(it))
|
||||
}
|
||||
if (selectedDate != null) {
|
||||
viewModel.updateSoldTime(selectedDate)
|
||||
}
|
||||
showSoldDatePicker = false
|
||||
})
|
||||
},
|
||||
dismissButton = {
|
||||
Text(stringResource(R.string.dialog_cancel), modifier = Modifier.clickable { showSoldDatePicker = false })
|
||||
}
|
||||
) {
|
||||
DatePicker(state = soldDateState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
}
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('ItemEditScreen')]
|
||||
// [END_ENTITY: Composable('ItemEditScreen')]
|
||||
// [END_FILE_ItemEditScreen.kt]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.itemedit
|
||||
// [FILE] ItemEditViewModel.kt
|
||||
// [SEMANTICS] ui, viewmodel, item_edit
|
||||
// [SEMANTICS] app, ui, viewmodel, edit
|
||||
|
||||
package com.homebox.lens.ui.screen.itemedit
|
||||
|
||||
@@ -9,11 +8,16 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.homebox.lens.domain.model.Item
|
||||
import com.homebox.lens.domain.model.ItemCreate
|
||||
import com.homebox.lens.domain.model.Label
|
||||
import com.homebox.lens.domain.model.ItemUpdate
|
||||
import com.homebox.lens.domain.model.Location
|
||||
import com.homebox.lens.domain.model.Label
|
||||
import com.homebox.lens.domain.usecase.CreateItemUseCase
|
||||
import com.homebox.lens.domain.usecase.GetAllLabelsUseCase
|
||||
import com.homebox.lens.domain.usecase.GetAllLocationsUseCase
|
||||
import com.homebox.lens.domain.usecase.GetItemDetailsUseCase
|
||||
import com.homebox.lens.data.mapper.toDomain
|
||||
import com.homebox.lens.domain.usecase.UpdateItemUseCase
|
||||
import com.homebox.lens.ui.mapper.ItemMapper
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
@@ -32,11 +36,15 @@ import javax.inject.Inject
|
||||
* @param item The item being edited, or null if creating a new item.
|
||||
* @param isLoading Whether data is currently being loaded or saved.
|
||||
* @param error An error message if an operation failed.
|
||||
* @param allLocations A list of all available locations.
|
||||
* @param allLabels A list of all available labels.
|
||||
*/
|
||||
data class ItemEditUiState(
|
||||
val item: Item? = null,
|
||||
val isLoading: Boolean = false,
|
||||
val error: String? = null
|
||||
val error: String? = null,
|
||||
val allLocations: List<Location> = emptyList(),
|
||||
val allLabels: List<Label> = emptyList()
|
||||
)
|
||||
// [END_ENTITY: DataClass('ItemEditUiState')]
|
||||
|
||||
@@ -44,15 +52,25 @@ data class ItemEditUiState(
|
||||
// [RELATION: ViewModel('ItemEditViewModel')] -> [DEPENDS_ON] -> [UseCase('CreateItemUseCase')]
|
||||
// [RELATION: ViewModel('ItemEditViewModel')] -> [DEPENDS_ON] -> [UseCase('UpdateItemUseCase')]
|
||||
// [RELATION: ViewModel('ItemEditViewModel')] -> [DEPENDS_ON] -> [UseCase('GetItemDetailsUseCase')]
|
||||
// [RELATION: ViewModel('ItemEditViewModel')] -> [DEPENDS_ON] -> [Class('ItemMapper')]
|
||||
// [RELATION: ViewModel('ItemEditViewModel')] -> [EMITS_STATE] -> [DataClass('ItemEditUiState')]
|
||||
/**
|
||||
* @summary ViewModel for the item edit screen.
|
||||
* @param createItemUseCase Use case for creating a new item.
|
||||
* @param updateItemUseCase Use case for updating an existing item.
|
||||
* @param getItemDetailsUseCase Use case for fetching item details.
|
||||
* @param getAllLocationsUseCase Use case for fetching all locations.
|
||||
* @param getAllLabelsUseCase Use case for fetching all labels.
|
||||
* @param itemMapper Mapper for converting between domain and UI item models.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class ItemEditViewModel @Inject constructor(
|
||||
private val createItemUseCase: CreateItemUseCase,
|
||||
private val updateItemUseCase: UpdateItemUseCase,
|
||||
private val getItemDetailsUseCase: GetItemDetailsUseCase
|
||||
private val getItemDetailsUseCase: GetItemDetailsUseCase,
|
||||
private val getAllLocationsUseCase: GetAllLocationsUseCase,
|
||||
private val getAllLabelsUseCase: GetAllLabelsUseCase,
|
||||
private val itemMapper: ItemMapper
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(ItemEditUiState())
|
||||
@@ -73,34 +91,93 @@ class ItemEditViewModel @Inject constructor(
|
||||
_uiState.value = _uiState.value.copy(isLoading = true, error = null)
|
||||
if (itemId == null) {
|
||||
Timber.i("[INFO][ACTION][new_item_preparation] Preparing for new item creation.")
|
||||
_uiState.value = _uiState.value.copy(isLoading = false, item = Item(id = "", name = "", description = null, quantity = 0, image = null, location = null, labels = emptyList(), value = null, createdAt = null))
|
||||
_uiState.value = _uiState.value.copy(
|
||||
isLoading = false,
|
||||
item = Item(
|
||||
id = "",
|
||||
name = "",
|
||||
description = null,
|
||||
quantity = 1,
|
||||
image = null,
|
||||
location = null,
|
||||
labels = emptyList(),
|
||||
purchasePrice = null,
|
||||
createdAt = null,
|
||||
archived = false,
|
||||
assetId = null,
|
||||
fields = emptyList(),
|
||||
insured = false,
|
||||
lifetimeWarranty = false,
|
||||
manufacturer = null,
|
||||
modelNumber = null,
|
||||
notes = null,
|
||||
parentId = null,
|
||||
purchaseFrom = null,
|
||||
purchaseTime = null,
|
||||
serialNumber = null,
|
||||
soldNotes = null,
|
||||
soldPrice = null,
|
||||
soldTime = null,
|
||||
soldTo = null,
|
||||
syncChildItemsLocations = false,
|
||||
warrantyDetails = null,
|
||||
warrantyExpires = null
|
||||
)
|
||||
)
|
||||
} else {
|
||||
try {
|
||||
Timber.i("[INFO][ACTION][fetching_item_details] Fetching details for item ID: %s", itemId)
|
||||
val itemOut = getItemDetailsUseCase(itemId)
|
||||
Timber.d("[DEBUG][ACTION][mapping_item_out_to_item] Mapping ItemOut to Item for UI state.")
|
||||
val item = Item(
|
||||
id = itemOut.id,
|
||||
name = itemOut.name,
|
||||
description = itemOut.description,
|
||||
quantity = itemOut.quantity,
|
||||
image = itemOut.images.firstOrNull()?.path, // Assuming first image is the main one
|
||||
location = itemOut.location?.let { Location(it.id, it.name) }, // Simplified mapping
|
||||
labels = itemOut.labels.map { Label(it.id, it.name) }, // Simplified mapping
|
||||
value = itemOut.value?.toBigDecimal(),
|
||||
createdAt = itemOut.createdAt
|
||||
)
|
||||
val item = itemMapper.toItem(itemOut)
|
||||
_uiState.value = _uiState.value.copy(isLoading = false, item = item)
|
||||
Timber.i("[INFO][ACTION][item_details_fetched] Successfully fetched item details for ID: %s", itemId)
|
||||
Timber.i("[INFO][ACTION][item_details_fetched] Successfully fetched and mapped item details for ID: %s", itemId)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "[ERROR][FALLBACK][item_load_failed] Failed to load item details for ID: %s", itemId)
|
||||
_uiState.value = _uiState.value.copy(isLoading = false, error = e.localizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
// Load all locations and labels
|
||||
try {
|
||||
Timber.i("[INFO][ACTION][fetching_all_locations] Fetching all locations.")
|
||||
val allLocations = getAllLocationsUseCase().map { Location(it.id, it.name) }
|
||||
Timber.i("[INFO][ACTION][fetching_all_labels] Fetching all labels.")
|
||||
val allLabels = getAllLabelsUseCase().map { it.toDomain() }
|
||||
_uiState.value = _uiState.value.copy(allLocations = allLocations, allLabels = allLabels)
|
||||
Timber.i("[INFO][ACTION][all_locations_labels_fetched] Successfully fetched all locations and labels.")
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "[ERROR][FALLBACK][locations_labels_load_failed] Failed to load locations or labels.")
|
||||
_uiState.value = _uiState.value.copy(error = e.localizedMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('loadItem')]
|
||||
|
||||
// [ENTITY: Function('updateLocation')]
|
||||
/**
|
||||
* @summary Updates the location of the item in the UI state.
|
||||
* @param location The new location for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateLocation(location: Location) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_location] Updating item location to: %s", location.name)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(location = location))
|
||||
}
|
||||
// [END_ENTITY: Function('updateLocation')]
|
||||
|
||||
// [ENTITY: Function('updateLabels')]
|
||||
/**
|
||||
* @summary Updates the labels of the item in the UI state.
|
||||
* @param labels The new list of labels for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateLabels(labels: List<Label>) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_labels] Updating item labels to: %s", labels.map { it.name }.joinToString())
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(labels = labels))
|
||||
}
|
||||
// [END_ENTITY: Function('updateLabels')]
|
||||
|
||||
// [ENTITY: Function('saveItem')]
|
||||
/**
|
||||
* @summary Saves the current item, either creating a new one or updating an existing one.
|
||||
@@ -117,53 +194,48 @@ class ItemEditViewModel @Inject constructor(
|
||||
try {
|
||||
if (currentItem.id.isBlank()) {
|
||||
Timber.i("[INFO][ACTION][creating_new_item] Creating new item: %s", currentItem.name)
|
||||
val createdItemSummary = createItemUseCase(ItemCreate(
|
||||
name = currentItem.name,
|
||||
description = currentItem.description,
|
||||
quantity = currentItem.quantity,
|
||||
assetId = null, // Item does not have assetId
|
||||
notes = null, // Item does not have notes
|
||||
serialNumber = null, // Item does not have serialNumber
|
||||
value = currentItem.value?.toDouble(), // Convert BigDecimal to Double
|
||||
purchasePrice = null, // Item does not have purchasePrice
|
||||
purchaseDate = null, // Item does not have purchaseDate
|
||||
warrantyUntil = null, // Item does not have warrantyUntil
|
||||
locationId = currentItem.location?.id,
|
||||
parentId = null, // Item does not have parentId
|
||||
labelIds = currentItem.labels.map { it.id }
|
||||
))
|
||||
Timber.d("[DEBUG][ACTION][mapping_item_summary_to_item] Mapping ItemSummary to Item for UI state.")
|
||||
val createdItem = Item(
|
||||
id = createdItemSummary.id,
|
||||
name = createdItemSummary.name,
|
||||
description = null, // ItemSummary does not have description
|
||||
quantity = 0, // ItemSummary does not have quantity
|
||||
image = null, // ItemSummary does not have image
|
||||
location = null, // ItemSummary does not have location
|
||||
labels = emptyList(), // ItemSummary does not have labels
|
||||
value = null, // ItemSummary does not have value
|
||||
createdAt = null // ItemSummary does not have createdAt
|
||||
val createdItemSummary = createItemUseCase(
|
||||
ItemCreate(
|
||||
name = currentItem.name,
|
||||
description = currentItem.description,
|
||||
quantity = currentItem.quantity,
|
||||
archived = currentItem.archived,
|
||||
assetId = currentItem.assetId,
|
||||
insured = currentItem.insured,
|
||||
lifetimeWarranty = currentItem.lifetimeWarranty,
|
||||
manufacturer = currentItem.manufacturer,
|
||||
modelNumber = currentItem.modelNumber,
|
||||
notes = currentItem.notes,
|
||||
parentId = currentItem.parentId,
|
||||
purchaseFrom = currentItem.purchaseFrom,
|
||||
purchasePrice = currentItem.purchasePrice,
|
||||
purchaseTime = currentItem.purchaseTime,
|
||||
serialNumber = currentItem.serialNumber,
|
||||
soldNotes = currentItem.soldNotes,
|
||||
soldPrice = currentItem.soldPrice,
|
||||
soldTime = currentItem.soldTime,
|
||||
soldTo = currentItem.soldTo,
|
||||
syncChildItemsLocations = currentItem.syncChildItemsLocations,
|
||||
warrantyDetails = currentItem.warrantyDetails,
|
||||
warrantyExpires = currentItem.warrantyExpires,
|
||||
locationId = currentItem.location?.id,
|
||||
labelIds = currentItem.labels.map { it.id }
|
||||
)
|
||||
)
|
||||
_uiState.value = _uiState.value.copy(isLoading = false, item = createdItem)
|
||||
Timber.i("[INFO][ACTION][new_item_created] Successfully created new item with ID: %s", createdItem.id)
|
||||
Timber.i("[INFO][ACTION][fetching_full_item_after_creation] Fetching full item details after creation for ID: %s", createdItemSummary.id)
|
||||
val createdItemOut = getItemDetailsUseCase(createdItemSummary.id)
|
||||
Timber.d("[DEBUG][ACTION][mapping_item_out_to_item] Mapping created ItemOut to Item for UI state.")
|
||||
val item = itemMapper.toItem(createdItemOut)
|
||||
_uiState.value = _uiState.value.copy(isLoading = false, item = item)
|
||||
Timber.i("[INFO][ACTION][new_item_created] Successfully created and mapped new item with ID: %s", createdItemOut.id)
|
||||
_saveCompleted.emit(Unit)
|
||||
} else {
|
||||
Timber.i("[INFO][ACTION][updating_existing_item] Updating existing item with ID: %s", currentItem.id)
|
||||
val updatedItemOut = updateItemUseCase(currentItem)
|
||||
Timber.d("[DEBUG][ACTION][mapping_item_out_to_item] Mapping ItemOut to Item for UI state.")
|
||||
val updatedItem = Item(
|
||||
id = updatedItemOut.id,
|
||||
name = updatedItemOut.name,
|
||||
description = updatedItemOut.description,
|
||||
quantity = updatedItemOut.quantity,
|
||||
image = updatedItemOut.images.firstOrNull()?.path,
|
||||
location = updatedItemOut.location?.let { Location(it.id, it.name) },
|
||||
labels = updatedItemOut.labels.map { Label(it.id, it.name) },
|
||||
value = updatedItemOut.value.toBigDecimal(),
|
||||
createdAt = updatedItemOut.createdAt
|
||||
)
|
||||
_uiState.value = _uiState.value.copy(isLoading = false, item = updatedItem)
|
||||
Timber.i("[INFO][ACTION][item_updated] Successfully updated item with ID: %s", updatedItem.id)
|
||||
Timber.d("[DEBUG][ACTION][mapping_item_out_to_item] Mapping updated ItemOut to Item for UI state.")
|
||||
val item = itemMapper.toItem(updatedItemOut)
|
||||
_uiState.value = _uiState.value.copy(isLoading = false, item = item)
|
||||
Timber.i("[INFO][ACTION][item_updated] Successfully updated and mapped item with ID: %s", updatedItemOut.id)
|
||||
_saveCompleted.emit(Unit)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -209,6 +281,234 @@ class ItemEditViewModel @Inject constructor(
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(quantity = newQuantity))
|
||||
}
|
||||
// [END_ENTITY: Function('updateQuantity')]
|
||||
}
|
||||
// [END_ENTITY: ViewModel('ItemEditViewModel')]
|
||||
|
||||
// [ENTITY: Function('updateArchived')]
|
||||
/**
|
||||
* @summary Updates the archived status of the item in the UI state.
|
||||
* @param newArchived The new archived status for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateArchived(newArchived: Boolean) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_archived] Updating item archived status to: %s", newArchived)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(archived = newArchived))
|
||||
}
|
||||
// [END_ENTITY: Function('updateArchived')]
|
||||
|
||||
// [ENTITY: Function('updateAssetId')]
|
||||
/**
|
||||
* @summary Updates the asset ID of the item in the UI state.
|
||||
* @param newAssetId The new asset ID for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateAssetId(newAssetId: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_assetId] Updating item asset ID to: %s", newAssetId)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(assetId = newAssetId))
|
||||
}
|
||||
// [END_ENTITY: Function('updateAssetId')]
|
||||
|
||||
// [ENTITY: Function('updateInsured')]
|
||||
/**
|
||||
* @summary Updates the insured status of the item in the UI state.
|
||||
* @param newInsured The new insured status for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateInsured(newInsured: Boolean) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_insured] Updating item insured status to: %s", newInsured)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(insured = newInsured))
|
||||
}
|
||||
// [END_ENTITY: Function('updateInsured')]
|
||||
|
||||
// [ENTITY: Function('updateLifetimeWarranty')]
|
||||
/**
|
||||
* @summary Updates the lifetime warranty status of the item in the UI state.
|
||||
* @param newLifetimeWarranty The new lifetime warranty status for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateLifetimeWarranty(newLifetimeWarranty: Boolean) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_lifetime_warranty] Updating item lifetime warranty status to: %s", newLifetimeWarranty)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(lifetimeWarranty = newLifetimeWarranty))
|
||||
}
|
||||
// [END_ENTITY: Function('updateLifetimeWarranty')]
|
||||
|
||||
// [ENTITY: Function('updateManufacturer')]
|
||||
/**
|
||||
* @summary Updates the manufacturer of the item in the UI state.
|
||||
* @param newManufacturer The new manufacturer for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateManufacturer(newManufacturer: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_manufacturer] Updating item manufacturer to: %s", newManufacturer)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(manufacturer = newManufacturer))
|
||||
}
|
||||
// [END_ENTITY: Function('updateManufacturer')]
|
||||
|
||||
// [ENTITY: Function('updateModelNumber')]
|
||||
/**
|
||||
* @summary Updates the model number of the item in the UI state.
|
||||
* @param newModelNumber The new model number for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateModelNumber(newModelNumber: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_model_number] Updating item model number to: %s", newModelNumber)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(modelNumber = newModelNumber))
|
||||
}
|
||||
// [END_ENTITY: Function('updateModelNumber')]
|
||||
|
||||
// [ENTITY: Function('updateNotes')]
|
||||
/**
|
||||
* @summary Updates the notes of the item in the UI state.
|
||||
* @param newNotes The new notes for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateNotes(newNotes: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_notes] Updating item notes to: %s", newNotes)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(notes = newNotes))
|
||||
}
|
||||
// [END_ENTITY: Function('updateNotes')]
|
||||
|
||||
// [ENTITY: Function('updateParentId')]
|
||||
/**
|
||||
* @summary Updates the parent ID of the item in the UI state.
|
||||
* @param newParentId The new parent ID for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateParentId(newParentId: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_parent_id] Updating item parent ID to: %s", newParentId)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(parentId = newParentId))
|
||||
}
|
||||
// [END_ENTITY: Function('updateParentId')]
|
||||
|
||||
// [ENTITY: Function('updatePurchaseFrom')]
|
||||
/**
|
||||
* @summary Updates the purchase source of the item in the UI state.
|
||||
* @param newPurchaseFrom The new purchase source for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updatePurchaseFrom(newPurchaseFrom: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_purchase_from] Updating item purchase from to: %s", newPurchaseFrom)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(purchaseFrom = newPurchaseFrom))
|
||||
}
|
||||
// [END_ENTITY: Function('updatePurchaseFrom')]
|
||||
|
||||
// [ENTITY: Function('updatePurchasePrice')]
|
||||
/**
|
||||
* @summary Updates the purchase price of the item in the UI state.
|
||||
* @param newPurchasePrice The new purchase price for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updatePurchasePrice(newPurchasePrice: Double?) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_purchase_price] Updating item purchase price to: %s", newPurchasePrice)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(purchasePrice = newPurchasePrice))
|
||||
}
|
||||
// [END_ENTITY: Function('updatePurchasePrice')]
|
||||
|
||||
// [ENTITY: Function('updatePurchaseTime')]
|
||||
/**
|
||||
* @summary Updates the purchase time of the item in the UI state.
|
||||
* @param newPurchaseTime The new purchase time for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updatePurchaseTime(newPurchaseTime: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_purchase_time] Updating item purchase time to: %s", newPurchaseTime)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(purchaseTime = newPurchaseTime))
|
||||
}
|
||||
// [END_ENTITY: Function('updatePurchaseTime')]
|
||||
|
||||
// [ENTITY: Function('updateSerialNumber')]
|
||||
/**
|
||||
* @summary Updates the serial number of the item in the UI state.
|
||||
* @param newSerialNumber The new serial number for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateSerialNumber(newSerialNumber: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_serial_number] Updating item serial number to: %s", newSerialNumber)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(serialNumber = newSerialNumber))
|
||||
}
|
||||
// [END_ENTITY: Function('updateSerialNumber')]
|
||||
|
||||
// [ENTITY: Function('updateSoldNotes')]
|
||||
/**
|
||||
* @summary Updates the sold notes of the item in the UI state.
|
||||
* @param newSoldNotes The new sold notes for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateSoldNotes(newSoldNotes: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_sold_notes] Updating item sold notes to: %s", newSoldNotes)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(soldNotes = newSoldNotes))
|
||||
}
|
||||
// [END_ENTITY: Function('updateSoldNotes')]
|
||||
|
||||
// [ENTITY: Function('updateSoldPrice')]
|
||||
/**
|
||||
* @summary Updates the sold price of the item in the UI state.
|
||||
* @param newSoldPrice The new sold price for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateSoldPrice(newSoldPrice: Double?) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_sold_price] Updating item sold price to: %s", newSoldPrice)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(soldPrice = newSoldPrice))
|
||||
}
|
||||
// [END_ENTITY: Function('updateSoldPrice')]
|
||||
|
||||
// [ENTITY: Function('updateSoldTime')]
|
||||
/**
|
||||
* @summary Updates the sold time of the item in the UI state.
|
||||
* @param newSoldTime The new sold time for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateSoldTime(newSoldTime: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_sold_time] Updating item sold time to: %s", newSoldTime)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(soldTime = newSoldTime))
|
||||
}
|
||||
// [END_ENTITY: Function('updateSoldTime')]
|
||||
|
||||
// [ENTITY: Function('updateSoldTo')]
|
||||
/**
|
||||
* @summary Updates the sold to field of the item in the UI state.
|
||||
* @param newSoldTo The new sold to for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateSoldTo(newSoldTo: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_sold_to] Updating item sold to to: %s", newSoldTo)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(soldTo = newSoldTo))
|
||||
}
|
||||
// [END_ENTITY: Function('updateSoldTo')]
|
||||
|
||||
// [ENTITY: Function('updateSyncChildItemsLocations')]
|
||||
/**
|
||||
* @summary Updates the sync child items locations status of the item in the UI state.
|
||||
* @param newSyncChildItemsLocations The new sync child items locations status for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateSyncChildItemsLocations(newSyncChildItemsLocations: Boolean) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_sync_child_items_locations] Updating item sync child items locations status to: %s", newSyncChildItemsLocations)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(syncChildItemsLocations = newSyncChildItemsLocations))
|
||||
}
|
||||
// [END_ENTITY: Function('updateSyncChildItemsLocations')]
|
||||
|
||||
// [ENTITY: Function('updateWarrantyDetails')]
|
||||
/**
|
||||
* @summary Updates the warranty details of the item in the UI state.
|
||||
* @param newWarrantyDetails The new warranty details for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateWarrantyDetails(newWarrantyDetails: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_warranty_details] Updating item warranty details to: %s", newWarrantyDetails)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(warrantyDetails = newWarrantyDetails))
|
||||
}
|
||||
// [END_ENTITY: Function('updateWarrantyDetails')]
|
||||
|
||||
// [ENTITY: Function('updateWarrantyExpires')]
|
||||
/**
|
||||
* @summary Updates the warranty expires date of the item in the UI state.
|
||||
* @param newWarrantyExpires The new warranty expires date for the item.
|
||||
* @sideeffect Updates the `item` in `_uiState`.
|
||||
*/
|
||||
fun updateWarrantyExpires(newWarrantyExpires: String) {
|
||||
Timber.d("[DEBUG][ACTION][updating_item_warranty_expires] Updating item warranty expires date to: %s", newWarrantyExpires)
|
||||
_uiState.value = _uiState.value.copy(item = _uiState.value.item?.copy(warrantyExpires = newWarrantyExpires))
|
||||
}
|
||||
// [END_ENTITY: Function('updateWarrantyExpires')]
|
||||
}
|
||||
// [END_ENTITY: ViewModel('ItemEditViewModel')]
|
||||
// [END_FILE_ItemEditViewModel.kt]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.labeledit
|
||||
// [FILE] LabelEditScreen.kt
|
||||
// [SEMANTICS] ui, screen, label, edit
|
||||
// [SEMANTICS] app, ui, screen, edit, label
|
||||
|
||||
package com.homebox.lens.ui.screen.labeledit
|
||||
|
||||
@@ -24,10 +23,10 @@ import com.homebox.lens.ui.components.LoadingOverlay
|
||||
// [ENTITY: Function('LabelEditScreen')]
|
||||
// [RELATION: Function('LabelEditScreen')] -> [DEPENDS_ON] -> [ViewModel('LabelEditViewModel')]
|
||||
/**
|
||||
* @summary Composable-функция для экрана "Редактирование метки".
|
||||
* @param labelId ID метки для редактирования или null для создания новой.
|
||||
* @param onBack Навигация назад.
|
||||
* @param onLabelSaved Действие после сохранения метки.
|
||||
* @summary Composable function for the "Edit Label" screen.
|
||||
* @param labelId The ID of the label to edit, or null to create a new one.
|
||||
* @param onBack Navigation back.
|
||||
* @param onLabelSaved Action after the label is saved.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -97,6 +96,13 @@ fun LabelEditScreen(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
OutlinedTextField(
|
||||
value = uiState.description.orEmpty(),
|
||||
onValueChange = viewModel::onDescriptionChange,
|
||||
label = { Text(stringResource(R.string.label_description)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
ColorPicker(
|
||||
selectedColor = uiState.color,
|
||||
onColorSelected = viewModel::onColorChange,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.labeledit
|
||||
// [FILE] LabelEditViewModel.kt
|
||||
// [SEMANTICS] ui, viewmodel, label_management
|
||||
// [SEMANTICS] app, ui, viewmodel, edit, label
|
||||
|
||||
package com.homebox.lens.ui.screen.labeledit
|
||||
|
||||
@@ -50,6 +49,10 @@ class LabelEditViewModel @Inject constructor(
|
||||
uiState = uiState.copy(name = newName, nameError = null)
|
||||
}
|
||||
|
||||
fun onDescriptionChange(newDescription: String) {
|
||||
uiState = uiState.copy(description = newDescription)
|
||||
}
|
||||
|
||||
fun onColorChange(newColor: String) {
|
||||
uiState = uiState.copy(color = newColor)
|
||||
}
|
||||
@@ -63,35 +66,41 @@ class LabelEditViewModel @Inject constructor(
|
||||
|
||||
uiState = uiState.copy(isLoading = true, error = null)
|
||||
try {
|
||||
if (labelId == null) {
|
||||
// Create new label
|
||||
val newLabel = LabelCreate(name = uiState.name, color = uiState.color)
|
||||
val result = if (labelId == null) {
|
||||
// [LOG_EVENT] [EVENT_TYPE: LabelCreationAttempt] [DATA: { "labelName": "${uiState.name}" }]
|
||||
val newLabel = LabelCreate(name = uiState.name, color = uiState.color, description = uiState.description)
|
||||
createLabelUseCase(newLabel)
|
||||
} else {
|
||||
// Update existing label
|
||||
val updatedLabel = LabelUpdate(name = uiState.name, color = uiState.color)
|
||||
// [LOG_EVENT] [EVENT_TYPE: LabelUpdateAttempt] [DATA: { "labelId": "$labelId", "labelName": "${uiState.name}" }]
|
||||
val updatedLabel = LabelUpdate(name = uiState.name, color = uiState.color, description = uiState.description)
|
||||
updateLabelUseCase(labelId, updatedLabel)
|
||||
}
|
||||
// [LOG_EVENT] [EVENT_TYPE: LabelSaveSuccess] [DATA: { "labelName": "${uiState.name}", "isNew": ${labelId == null} }]
|
||||
uiState = uiState.copy(isSaved = true)
|
||||
} catch (e: Exception) {
|
||||
// [LOG_EVENT] [EVENT_TYPE: LabelSaveFailure] [ERROR: "${e.message}"] [DATA: { "labelName": "${uiState.name}", "isNew": ${labelId == null} }]
|
||||
uiState = uiState.copy(error = e.message, isLoading = false)
|
||||
} finally {
|
||||
uiState = uiState.copy(isLoading = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadLabelDetails(id: String) {
|
||||
viewModelScope.launch {
|
||||
uiState = uiState.copy(isLoading = true, error = null)
|
||||
try {
|
||||
// [LOG_EVENT] [EVENT_TYPE: LabelDetailsFetchAttempt] [DATA: { "labelId": "$id" }]
|
||||
val label = getLabelDetailsUseCase(id)
|
||||
// [LOG_EVENT] [EVENT_TYPE: LabelDetailsFetchSuccess] [DATA: { "labelId": "$id", "labelName": "${label.name}" }]
|
||||
uiState = uiState.copy(
|
||||
name = label.name,
|
||||
color = label.color,
|
||||
isLoading = false
|
||||
description = label.description,
|
||||
isLoading = false,
|
||||
originalLabel = label
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// [LOG_EVENT] [EVENT_TYPE: LabelDetailsFetchFailure] [ERROR: "${e.message}"] [DATA: { "labelId": "$id" }]
|
||||
uiState = uiState.copy(error = e.message, isLoading = false)
|
||||
}
|
||||
}
|
||||
@@ -104,6 +113,7 @@ class LabelEditViewModel @Inject constructor(
|
||||
*/
|
||||
data class LabelEditUiState(
|
||||
val name: String = "",
|
||||
val description: String? = null,
|
||||
val color: String = "#FFFFFF", // Default color
|
||||
val nameError: String? = null,
|
||||
val isLoading: Boolean = false,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.labelslist
|
||||
// [FILE] LabelsListScreen.kt
|
||||
// [SEMANTICS] ui, labels_list, state_management, compose, dialog
|
||||
// [SEMANTICS] app, ui, screen, list, label
|
||||
package com.homebox.lens.ui.screen.labelslist
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -17,24 +16,18 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.Label
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -43,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]
|
||||
@@ -52,10 +45,12 @@ import timber.log.Timber
|
||||
// [RELATION: Function('LabelsListScreen')] -> [DEPENDS_ON] -> [ViewModel('LabelsListViewModel')]
|
||||
// [RELATION: Function('LabelsListScreen')] -> [DEPENDS_ON] -> [Framework('NavController')]
|
||||
/**
|
||||
* @summary Отображает экран со списком всех меток.
|
||||
* @param navController Контроллер навигации для перемещения между экранами.
|
||||
* @param viewModel ViewModel, предоставляющая состояние UI для экрана меток.
|
||||
* @summary Displays the screen with a list of all labels.
|
||||
* @param currentRoute The current navigation route.
|
||||
* @param navigationActions The object containing navigation actions.
|
||||
* @param viewModel The ViewModel providing the UI state for the labels screen.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LabelsListScreen(
|
||||
currentRoute: String?,
|
||||
@@ -90,19 +85,19 @@ fun LabelsListScreen(
|
||||
.padding(innerPaddingValues), // Use innerPaddingValues here
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
when (currentState) {
|
||||
when (val state = uiState) {
|
||||
is LabelsListUiState.Loading -> {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
is LabelsListUiState.Error -> {
|
||||
Text(text = currentState.message)
|
||||
Text(text = state.message)
|
||||
}
|
||||
is LabelsListUiState.Success -> {
|
||||
if (currentState.labels.isEmpty()) {
|
||||
if (state.labels.isEmpty()) {
|
||||
Text(text = stringResource(id = R.string.no_labels_found))
|
||||
} else {
|
||||
LabelsList(
|
||||
labels = currentState.labels,
|
||||
labels = state.labels,
|
||||
onLabelClick = { label ->
|
||||
Timber.i("[INFO][ACTION][navigate_to_label_edit] Label clicked: ${label.id}. Navigating to label edit screen.")
|
||||
navigationActions.navigateToLabelEdit(label.id)
|
||||
@@ -120,10 +115,10 @@ fun LabelsListScreen(
|
||||
// [ENTITY: Function('LabelsList')]
|
||||
// [RELATION: Function('LabelsList')] -> [DEPENDS_ON] -> [DataClass('Label')]
|
||||
/**
|
||||
* @summary Composable-функция для отображения списка меток.
|
||||
* @param labels Список объектов `Label` для отображения.
|
||||
* @param onLabelClick Лямбда-функция, вызываемая при нажатии на элемент списка.
|
||||
* @param modifier Модификатор для настройки внешнего вида.
|
||||
* @summary Composable function for displaying a list of labels.
|
||||
* @param labels The list of `Label` objects to display.
|
||||
* @param onLabelClick A lambda function called when a list item is clicked.
|
||||
* @param modifier A modifier for customizing the appearance.
|
||||
*/
|
||||
@Composable
|
||||
private fun LabelsList(
|
||||
@@ -149,9 +144,9 @@ private fun LabelsList(
|
||||
// [ENTITY: Function('LabelListItem')]
|
||||
// [RELATION: Function('LabelListItem')] -> [DEPENDS_ON] -> [DataClass('Label')]
|
||||
/**
|
||||
* @summary Composable-функция для отображения одного элемента в списке меток.
|
||||
* @param label Объект `Label`, который нужно отобразить.
|
||||
* @param onClick Лямбда-функция, вызываемая при нажатии на элемент.
|
||||
* @summary Composable function for displaying a single item in the list of labels.
|
||||
* @param label The `Label` object to display.
|
||||
* @param onClick A lambda function called when the item is clicked.
|
||||
*/
|
||||
@Composable
|
||||
private fun LabelListItem(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.labelslist
|
||||
// [FILE] LabelsListUiState.kt
|
||||
// [SEMANTICS] ui_state, sealed_interface, contract
|
||||
// [SEMANTICS] app, ui, state, list, label
|
||||
package com.homebox.lens.ui.screen.labelslist
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -9,17 +8,17 @@ import com.homebox.lens.domain.model.Label
|
||||
|
||||
// [ENTITY: SealedInterface('LabelsListUiState')]
|
||||
/**
|
||||
* @summary Определяет все возможные состояния для UI экрана со списком меток.
|
||||
* @description Использование sealed-интерфейса позволяет исчерпывающе обрабатывать все состояния в Composable-функциях.
|
||||
* @summary Defines all possible states for the UI of the screen with a list of labels.
|
||||
* @description Using a sealed interface allows for exhaustive handling of all states in Composable functions.
|
||||
*/
|
||||
sealed interface LabelsListUiState {
|
||||
// [ENTITY: DataClass('Success')]
|
||||
// [RELATION: DataClass('Success')] -> [DEPENDS_ON] -> [DataClass('Label')]
|
||||
/**
|
||||
* @summary Состояние успеха, содержит список меток и состояние диалога.
|
||||
* @param labels Список меток для отображения.
|
||||
* @param isShowingCreateDialog Флаг, показывающий, должен ли быть отображен диалог создания метки.
|
||||
* @invariant labels не может быть null.
|
||||
* @summary The success state, contains the list of labels and the state of the dialog.
|
||||
* @param labels The list of labels to display.
|
||||
* @param isShowingCreateDialog A flag indicating whether the label creation dialog should be displayed.
|
||||
* @invariant labels cannot be null.
|
||||
*/
|
||||
data class Success(
|
||||
val labels: List<Label>,
|
||||
@@ -29,17 +28,17 @@ sealed interface LabelsListUiState {
|
||||
|
||||
// [ENTITY: DataClass('Error')]
|
||||
/**
|
||||
* @summary Состояние ошибки.
|
||||
* @param message Текст ошибки для отображения пользователю.
|
||||
* @invariant message не может быть пустой.
|
||||
* @summary The error state.
|
||||
* @param message The error text to display to the user.
|
||||
* @invariant message cannot be empty.
|
||||
*/
|
||||
data class Error(val message: String) : LabelsListUiState
|
||||
// [END_ENTITY: DataClass('Error')]
|
||||
|
||||
// [ENTITY: Object('Loading')]
|
||||
/**
|
||||
* @summary Состояние загрузки данных.
|
||||
* @description Указывает, что идет процесс загрузки меток.
|
||||
* @summary The data loading state.
|
||||
* @description Indicates that the process of loading labels is in progress.
|
||||
*/
|
||||
data object Loading : LabelsListUiState
|
||||
// [END_ENTITY: Object('Loading')]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.labelslist
|
||||
// [FILE] LabelsListViewModel.kt
|
||||
// [SEMANTICS] ui_logic, labels_list, state_management, dialog_management
|
||||
// [SEMANTICS] app, ui, viewmodel, list, label
|
||||
package com.homebox.lens.ui.screen.labelslist
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -21,9 +20,9 @@ import javax.inject.Inject
|
||||
// [RELATION: ViewModel('LabelsListViewModel')] -> [DEPENDS_ON] -> [UseCase('GetAllLabelsUseCase')]
|
||||
// [RELATION: ViewModel('LabelsListViewModel')] -> [EMITS_STATE] -> [SealedInterface('LabelsListUiState')]
|
||||
/**
|
||||
* @summary ViewModel для экрана со списком меток.
|
||||
* @description Управляет состоянием экрана, загружает список меток, обрабатывает ошибки и управляет диалогом создания новой метки.
|
||||
* @invariant `uiState` всегда является одним из состояний, определенных в `LabelsListUiState`.
|
||||
* @summary ViewModel for the screen with a list of labels.
|
||||
* @description Manages the screen state, loads the list of labels, handles errors, and manages the dialog for creating a new label.
|
||||
* @invariant `uiState` is always one of the states defined in `LabelsListUiState`.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class LabelsListViewModel @Inject constructor(
|
||||
@@ -39,10 +38,10 @@ class LabelsListViewModel @Inject constructor(
|
||||
|
||||
// [ENTITY: Function('loadLabels')]
|
||||
/**
|
||||
* @summary Загружает список меток.
|
||||
* @description Выполняет `GetAllLabelsUseCase` и обновляет UI, переключая его
|
||||
* между состояниями `Loading`, `Success` и `Error`.
|
||||
* @sideeffect Асинхронно обновляет `_uiState`.
|
||||
* @summary Loads the list of labels.
|
||||
* @description Executes `GetAllLabelsUseCase` and updates the UI by switching it
|
||||
* between the `Loading`, `Success`, and `Error` states.
|
||||
* @sideeffect Asynchronously updates `_uiState`.
|
||||
*/
|
||||
fun loadLabels() {
|
||||
viewModelScope.launch {
|
||||
@@ -77,9 +76,9 @@ class LabelsListViewModel @Inject constructor(
|
||||
|
||||
// [ENTITY: Function('onShowCreateDialog')]
|
||||
/**
|
||||
* @summary Инициирует отображение диалога для создания метки.
|
||||
* @description Обновляет состояние `uiState`, устанавливая `isShowingCreateDialog` в `true`.
|
||||
* @sideeffect Обновляет `_uiState`.
|
||||
* @summary Initiates the display of the dialog for creating a label.
|
||||
* @description Updates the `uiState` by setting `isShowingCreateDialog` to `true`.
|
||||
* @sideeffect Updates `_uiState`.
|
||||
*/
|
||||
fun onShowCreateDialog() {
|
||||
Timber.i("[INFO][ACTION][show_create_dialog] Show create label dialog requested.")
|
||||
@@ -93,9 +92,9 @@ class LabelsListViewModel @Inject constructor(
|
||||
|
||||
// [ENTITY: Function('onDismissCreateDialog')]
|
||||
/**
|
||||
* @summary Скрывает диалог создания метки.
|
||||
* @description Обновляет состояние `uiState`, устанавливая `isShowingCreateDialog` в `false`.
|
||||
* @sideeffect Обновляет `_uiState`.
|
||||
* @summary Hides the label creation dialog.
|
||||
* @description Updates the `uiState` by setting `isShowingCreateDialog` to `false`.
|
||||
* @sideeffect Updates `_uiState`.
|
||||
*/
|
||||
fun onDismissCreateDialog() {
|
||||
Timber.i("[INFO][ACTION][dismiss_create_dialog] Dismiss create label dialog requested.")
|
||||
@@ -109,12 +108,12 @@ class LabelsListViewModel @Inject constructor(
|
||||
|
||||
// [ENTITY: Function('createLabel')]
|
||||
/**
|
||||
* @summary Создает новую метку. [MVP_SCOPE] ЗАГЛУШКА.
|
||||
* @description В текущей реализации (План Б, Этап 1), эта функция только логирует действие
|
||||
* и скрывает диалог. Реальная логика сохранения будет добавлена на следующем этапе.
|
||||
* @param name Название новой метки.
|
||||
* @precondition `name` не должен быть пустым.
|
||||
* @sideeffect Логирует действие, обновляет `_uiState`, чтобы скрыть диалог.
|
||||
* @summary Creates a new label. [MVP_SCOPE] STUB.
|
||||
* @description In the current implementation (Plan B, Stage 1), this function only logs the action
|
||||
* and hides the dialog. The actual save logic will be added in the next stage.
|
||||
* @param name The name of the new label.
|
||||
* @precondition `name` must not be blank.
|
||||
* @sideeffect Logs the action, updates `_uiState` to hide the dialog.
|
||||
*/
|
||||
fun createLabel(name: String) {
|
||||
require(name.isNotBlank()) { "[CONTRACT_VIOLATION] Label name cannot be blank." }
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.locationedit
|
||||
// [FILE] LocationEditScreen.kt
|
||||
// [SEMANTICS] ui, screen, location, edit
|
||||
// [SEMANTICS] app, ui, screen, edit, location
|
||||
|
||||
package com.homebox.lens.ui.screen.locationedit
|
||||
|
||||
@@ -19,8 +18,8 @@ import com.homebox.lens.R
|
||||
|
||||
// [ENTITY: Function('LocationEditScreen')]
|
||||
/**
|
||||
* @summary Composable-функция для экрана "Редактирование местоположения".
|
||||
* @param locationId ID местоположения для редактирования или "new" для создания.
|
||||
* @summary Composable function for the "Edit Location" screen.
|
||||
* @param locationId The ID of the location to edit, or "new" to create one.
|
||||
*/
|
||||
@Composable
|
||||
fun LocationEditScreen(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.locationslist
|
||||
// [FILE] LocationsListScreen.kt
|
||||
// [SEMANTICS] ui, screen, locations, list
|
||||
// [SEMANTICS] app, ui, screen, list, location
|
||||
|
||||
package com.homebox.lens.ui.screen.locationslist
|
||||
|
||||
@@ -56,12 +55,12 @@ import com.homebox.lens.ui.theme.HomeboxLensTheme
|
||||
// [RELATION: Function('LocationsListScreen')] -> [DEPENDS_ON] -> [Class('NavigationActions')]
|
||||
// [RELATION: Function('LocationsListScreen')] -> [CALLS] -> [Function('MainScaffold')]
|
||||
/**
|
||||
* @summary Composable-функция для экрана "Список местоположений".
|
||||
* @param currentRoute Текущий маршрут для подсветки активного элемента в Drawer.
|
||||
* @param navigationActions Объект с навигационными действиями.
|
||||
* @param onLocationClick Лямбда-обработчик нажатия на местоположение.
|
||||
* @param onAddNewLocationClick Лямбда-обработчик нажатия на кнопку добавления нового местоположения.
|
||||
* @param viewModel ViewModel для этого экрана.
|
||||
* @summary Composable function for the "List of Locations" screen.
|
||||
* @param currentRoute The current route to highlight the active item in the Drawer.
|
||||
* @param navigationActions The object with navigation actions.
|
||||
* @param onLocationClick A lambda handler for clicking on a location.
|
||||
* @param onAddNewLocationClick A lambda handler for clicking the button to add a new location.
|
||||
* @param viewModel The ViewModel for this screen.
|
||||
*/
|
||||
@Composable
|
||||
fun LocationsListScreen(
|
||||
@@ -104,12 +103,12 @@ fun LocationsListScreen(
|
||||
// [ENTITY: Function('LocationsListContent')]
|
||||
// [RELATION: Function('LocationsListContent')] -> [CONSUMES_STATE] -> [SealedInterface('LocationsListUiState')]
|
||||
/**
|
||||
* @summary Отображает основной контент экрана в зависимости от `uiState`.
|
||||
* @param modifier Модификатор для стилизации.
|
||||
* @param uiState Текущее состояние UI.
|
||||
* @param onLocationClick Лямбда-обработчик нажатия на местоположение.
|
||||
* @param onEditLocation Лямбда-обработчик для редактирования местоположения.
|
||||
* @param onDeleteLocation Лямбда-обработчик для удаления местоположения.
|
||||
* @summary Displays the main content of the screen depending on the `uiState`.
|
||||
* @param modifier A modifier for styling.
|
||||
* @param uiState The current UI state.
|
||||
* @param onLocationClick A lambda handler for clicking on a location.
|
||||
* @param onEditLocation A lambda handler for editing a location.
|
||||
* @param onDeleteLocation A lambda handler for deleting a location.
|
||||
*/
|
||||
@Composable
|
||||
private fun LocationsListContent(
|
||||
@@ -167,11 +166,11 @@ private fun LocationsListContent(
|
||||
// [ENTITY: Function('LocationCard')]
|
||||
// [RELATION: Function('LocationCard')] -> [DEPENDS_ON] -> [DataClass('LocationOutCount')]
|
||||
/**
|
||||
* @summary Карточка для отображения одного местоположения.
|
||||
* @param location Данные о местоположении.
|
||||
* @param onClick Лямбда-обработчик нажатия на карточку.
|
||||
* @param onEditClick Лямбда-обработчик нажатия на "Редактировать".
|
||||
* @param onDeleteClick Лямбда-обработчик нажатия на "Удалить".
|
||||
* @summary Card for displaying a single location.
|
||||
* @param location The data about the location.
|
||||
* @param onClick A lambda handler for clicking on the card.
|
||||
* @param onEditClick A lambda handler for clicking "Edit".
|
||||
* @param onDeleteClick A lambda handler for clicking "Delete".
|
||||
*/
|
||||
@Composable
|
||||
private fun LocationCard(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.locationslist
|
||||
// [FILE] LocationsListUiState.kt
|
||||
// [SEMANTICS] ui, state, locations
|
||||
// [SEMANTICS] app, ui, state, list, location
|
||||
|
||||
package com.homebox.lens.ui.screen.locationslist
|
||||
|
||||
@@ -10,30 +9,30 @@ import com.homebox.lens.domain.model.LocationOutCount
|
||||
|
||||
// [ENTITY: SealedInterface('LocationsListUiState')]
|
||||
/**
|
||||
* @summary Определяет возможные состояния UI для экрана списка местоположений.
|
||||
* @summary Defines the possible UI states for the list of locations screen.
|
||||
* @see LocationsListViewModel
|
||||
*/
|
||||
sealed interface LocationsListUiState {
|
||||
// [ENTITY: DataClass('Success')]
|
||||
// [RELATION: DataClass('Success')] -> [DEPENDS_ON] -> [DataClass('LocationOutCount')]
|
||||
/**
|
||||
* @summary Состояние успешной загрузки данных.
|
||||
* @param locations Список местоположений для отображения.
|
||||
* @summary The state of a successful data load.
|
||||
* @param locations The list of locations to display.
|
||||
*/
|
||||
data class Success(val locations: List<LocationOutCount>) : LocationsListUiState
|
||||
// [END_ENTITY: DataClass('Success')]
|
||||
|
||||
// [ENTITY: DataClass('Error')]
|
||||
/**
|
||||
* @summary Состояние ошибки.
|
||||
* @param message Сообщение об ошибке.
|
||||
* @summary The error state.
|
||||
* @param message The error message.
|
||||
*/
|
||||
data class Error(val message: String) : LocationsListUiState
|
||||
// [END_ENTITY: DataClass('Error')]
|
||||
|
||||
// [ENTITY: Object('Loading')]
|
||||
/**
|
||||
* @summary Состояние загрузки данных.
|
||||
* @summary The data loading state.
|
||||
*/
|
||||
object Loading : LocationsListUiState
|
||||
// [END_ENTITY: Object('Loading')]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.locationslist
|
||||
// [FILE] LocationsListViewModel.kt
|
||||
// [SEMANTICS] ui, viewmodel, locations, hilt
|
||||
// [SEMANTICS] app, ui, viewmodel, list, location
|
||||
|
||||
package com.homebox.lens.ui.screen.locationslist
|
||||
|
||||
@@ -21,10 +20,10 @@ import javax.inject.Inject
|
||||
// [RELATION: ViewModel('LocationsListViewModel')] -> [DEPENDS_ON] -> [UseCase('GetAllLocationsUseCase')]
|
||||
// [RELATION: ViewModel('LocationsListViewModel')] -> [EMITS_STATE] -> [SealedInterface('LocationsListUiState')]
|
||||
/**
|
||||
* @summary ViewModel для экрана списка местоположений.
|
||||
* @param getAllLocationsUseCase Use case для получения всех местоположений.
|
||||
* @property uiState Поток, содержащий текущее состояние UI.
|
||||
* @invariant `uiState` всегда отражает результат последней операции загрузки.
|
||||
* @summary ViewModel for the list of locations screen.
|
||||
* @param getAllLocationsUseCase Use case for getting all locations.
|
||||
* @property uiState A flow containing the current UI state.
|
||||
* @invariant `uiState` always reflects the result of the last load operation.
|
||||
*/
|
||||
@HiltViewModel
|
||||
class LocationsListViewModel @Inject constructor(
|
||||
@@ -40,8 +39,8 @@ class LocationsListViewModel @Inject constructor(
|
||||
|
||||
// [ENTITY: Function('loadLocations')]
|
||||
/**
|
||||
* @summary Загружает список местоположений из репозитория.
|
||||
* @sideeffect Обновляет `_uiState` в зависимости от результата: Loading -> Success/Error.
|
||||
* @summary Loads the list of locations from the repository.
|
||||
* @sideeffect Updates `_uiState` depending on the result: Loading -> Success/Error.
|
||||
*/
|
||||
fun loadLocations() {
|
||||
Timber.d("[DEBUG][ENTRYPOINT][loading_locations] Starting to load locations.")
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.search
|
||||
// [FILE] SearchScreen.kt
|
||||
// [SEMANTICS] ui, screen, search
|
||||
// [SEMANTICS] app, ui, screen, search
|
||||
|
||||
package com.homebox.lens.ui.screen.search
|
||||
|
||||
@@ -17,9 +16,9 @@ import com.homebox.lens.ui.common.MainScaffold
|
||||
// [RELATION: Function('SearchScreen')] -> [DEPENDS_ON] -> [Class('NavigationActions')]
|
||||
// [RELATION: Function('SearchScreen')] -> [CALLS] -> [Function('MainScaffold')]
|
||||
/**
|
||||
* @summary Composable-функция для экрана "Поиск".
|
||||
* @param currentRoute Текущий маршрут для подсветки активного элемента в Drawer.
|
||||
* @param navigationActions Объект с навигационными действиями.
|
||||
* @summary Composable function for the "Search" screen.
|
||||
* @param currentRoute The current route to highlight the active item in the Drawer.
|
||||
* @param navigationActions The object with navigation actions.
|
||||
*/
|
||||
@Composable
|
||||
fun SearchScreen(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.search
|
||||
// [FILE] SearchViewModel.kt
|
||||
// [SEMANTICS] ui, viewmodel, search
|
||||
// [SEMANTICS] app, ui, viewmodel, search
|
||||
package com.homebox.lens.ui.screen.search
|
||||
|
||||
// [IMPORTS]
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// [FILE] SettingsScreen.kt
|
||||
// [SEMANTICS] app, ui, screen, settings
|
||||
|
||||
package com.homebox.lens.ui.screen.settings
|
||||
|
||||
// [IMPORTS]
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
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('SettingsScreen')]
|
||||
// [RELATION: Function('SettingsScreen')] -> [DEPENDS_ON] -> [Class('NavigationActions')]
|
||||
/**
|
||||
* @summary Composable function for the settings screen.
|
||||
* @param currentRoute The current navigation route.
|
||||
* @param navigationActions The object containing navigation actions.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
currentRoute: String?,
|
||||
navigationActions: NavigationActions
|
||||
) {
|
||||
MainScaffold(
|
||||
topBarTitle = stringResource(id = R.string.screen_title_settings),
|
||||
currentRoute = currentRoute,
|
||||
navigationActions = navigationActions
|
||||
) { paddingValues ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(paddingValues),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(text = "Settings Screen (Under Construction)")
|
||||
}
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('SettingsScreen')]
|
||||
// [END_FILE_SettingsScreen.kt]
|
||||
@@ -1,23 +1,27 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.setup
|
||||
// [FILE] SetupScreen.kt
|
||||
// [SEMANTICS] ui, screen, setup, compose
|
||||
// [SEMANTICS] app, ui, screen, setup
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package com.homebox.lens.ui.screen.setup
|
||||
|
||||
// [IMPORTS]
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Lock
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.homebox.lens.R
|
||||
// [END_IMPORTS]
|
||||
@@ -26,10 +30,10 @@ import com.homebox.lens.R
|
||||
// [RELATION: Function('SetupScreen')] -> [DEPENDS_ON] -> [ViewModel('SetupViewModel')]
|
||||
// [RELATION: Function('SetupScreen')] -> [CALLS] -> [Function('SetupScreenContent')]
|
||||
/**
|
||||
* @summary Главная Composable-функция для экрана настройки соединения с сервером.
|
||||
* @param viewModel ViewModel для этого экрана, предоставляется через Hilt.
|
||||
* @param onSetupComplete Лямбда, вызываемая после успешной настройки и входа.
|
||||
* @sideeffect Вызывает `onSetupComplete` при изменении `uiState.isSetupComplete`.
|
||||
* @summary The main Composable function for the server connection setup screen.
|
||||
* @param viewModel The ViewModel for this screen, provided by Hilt.
|
||||
* @param onSetupComplete A lambda invoked after successful setup and login.
|
||||
* @sideeffect Calls `onSetupComplete` when `uiState.isSetupComplete` changes.
|
||||
*/
|
||||
@Composable
|
||||
fun SetupScreen(
|
||||
@@ -55,12 +59,12 @@ fun SetupScreen(
|
||||
// [ENTITY: Function('SetupScreenContent')]
|
||||
// [RELATION: Function('SetupScreenContent')] -> [CONSUMES_STATE] -> [DataClass('SetupUiState')]
|
||||
/**
|
||||
* @summary Отображает контент экрана настройки: поля ввода и кнопку.
|
||||
* @param uiState Текущее состояние UI.
|
||||
* @param onServerUrlChange Лямбда-обработчик изменения URL сервера.
|
||||
* @param onUsernameChange Лямбда-обработчик изменения имени пользователя.
|
||||
* @param onPasswordChange Лямбда-обработчик изменения пароля.
|
||||
* @param onConnectClick Лямбда-обработчик нажатия на кнопку "Подключиться".
|
||||
* @summary Displays the content of the setup screen: input fields and a button.
|
||||
* @param uiState The current UI state.
|
||||
* @param onServerUrlChange A lambda handler for changing the server URL.
|
||||
* @param onUsernameChange A lambda handler for changing the username.
|
||||
* @param onPasswordChange A lambda handler for changing the password.
|
||||
* @param onConnectClick A lambda handler for clicking the "Connect" button.
|
||||
*/
|
||||
@Composable
|
||||
private fun SetupScreenContent(
|
||||
@@ -70,11 +74,7 @@ private fun SetupScreenContent(
|
||||
onPasswordChange: (String) -> Unit,
|
||||
onConnectClick: () -> Unit
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(title = { Text(stringResource(id = R.string.setup_title)) })
|
||||
}
|
||||
) { paddingValues ->
|
||||
Scaffold { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
@@ -83,42 +83,76 @@ private fun SetupScreenContent(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = uiState.serverUrl,
|
||||
onValueChange = onServerUrlChange,
|
||||
label = { Text(stringResource(id = R.string.setup_server_url_label)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = uiState.username,
|
||||
onValueChange = onUsernameChange,
|
||||
label = { Text(stringResource(id = R.string.setup_username_label)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = uiState.password,
|
||||
onValueChange = onPasswordChange,
|
||||
label = { Text(stringResource(id = R.string.setup_password_label)) },
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
Image(
|
||||
imageVector = Icons.Default.Lock,
|
||||
contentDescription = stringResource(id = R.string.app_name),
|
||||
modifier = Modifier.size(128.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.setup_title),
|
||||
style = MaterialTheme.typography.headlineLarge
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
text = "Enter your Homebox server details to connect.",
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = uiState.serverUrl,
|
||||
onValueChange = onServerUrlChange,
|
||||
label = { Text(stringResource(id = R.string.setup_server_url_label)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = uiState.username,
|
||||
onValueChange = onUsernameChange,
|
||||
label = { Text(stringResource(id = R.string.setup_username_label)) },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
OutlinedTextField(
|
||||
value = uiState.password,
|
||||
onValueChange = onPasswordChange,
|
||||
label = { Text(stringResource(id = R.string.setup_password_label)) },
|
||||
visualTransformation = PasswordVisualTransformation(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Button(
|
||||
onClick = onConnectClick,
|
||||
enabled = !uiState.isLoading,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp)
|
||||
) {
|
||||
if (uiState.isLoading) {
|
||||
CircularProgressIndicator(modifier = Modifier.size(24.dp))
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(24.dp),
|
||||
color = MaterialTheme.colorScheme.onPrimary
|
||||
)
|
||||
} else {
|
||||
Text(stringResource(id = R.string.setup_connect_button))
|
||||
}
|
||||
}
|
||||
uiState.error?.let {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(text = it, color = MaterialTheme.colorScheme.error)
|
||||
Text(
|
||||
text = it,
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.setup
|
||||
// [FILE] SetupUiState.kt
|
||||
// [SEMANTICS] ui_state, data_model, immutable
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.setup
|
||||
// [FILE] SetupViewModel.kt
|
||||
// [SEMANTICS] ui_logic, viewmodel, state_management, user_setup, authentication_flow
|
||||
package com.homebox.lens.ui.screen.setup
|
||||
@@ -74,40 +73,61 @@ class SetupViewModel @Inject constructor(
|
||||
// [END_ENTITY: Function('onUsernameChange')]
|
||||
|
||||
// [ENTITY: Function('onPasswordChange')]
|
||||
/**
|
||||
* @summary Updates the password in the UI state.
|
||||
* @param newPassword The new password.
|
||||
* @sideeffect Updates the `password` in `_uiState`.
|
||||
*/
|
||||
fun onPasswordChange(newPassword: String) {
|
||||
_uiState.update { it.copy(password = newPassword) }
|
||||
}
|
||||
// [END_ENTITY: Function('onPasswordChange')]
|
||||
|
||||
// [ENTITY: Function('connect')]
|
||||
fun connect() {
|
||||
Timber.d("[DEBUG][ENTRYPOINT][connecting] Starting connection process.")
|
||||
viewModelScope.launch {
|
||||
_uiState.update { it.copy(isLoading = true, error = null) }
|
||||
// [ENTITY: Function('areCredentialsSaved')]
|
||||
/**
|
||||
* @summary Checks synchronously if credentials are saved.
|
||||
* @return true if credentials are saved, false otherwise.
|
||||
* @sideeffect None.
|
||||
*/
|
||||
fun areCredentialsSaved(): Boolean {
|
||||
Timber.d("[DEBUG][ENTRYPOINT][checking_credentials_saved] Checking if credentials are saved.")
|
||||
return credentialsRepository.areCredentialsSavedSync()
|
||||
}
|
||||
// [END_ENTITY: Function('areCredentialsSaved')]
|
||||
|
||||
val credentials = Credentials(
|
||||
serverUrl = _uiState.value.serverUrl.trim(),
|
||||
username = _uiState.value.username.trim(),
|
||||
password = _uiState.value.password
|
||||
)
|
||||
// [ENTITY: Function('connect')]
|
||||
/**
|
||||
* @summary Initiates the connection process, saving credentials and attempting to log in.
|
||||
* @sideeffect Updates `_uiState` with loading, error, and completion states.
|
||||
*/
|
||||
fun connect() {
|
||||
Timber.d("[DEBUG][ENTRYPOINT][connecting] Starting connection process.")
|
||||
viewModelScope.launch {
|
||||
_uiState.update { it.copy(isLoading = true, error = null) }
|
||||
|
||||
Timber.d("[DEBUG][ACTION][saving_credentials] Saving credentials.")
|
||||
credentialsRepository.saveCredentials(credentials)
|
||||
val credentials = Credentials(
|
||||
serverUrl = _uiState.value.serverUrl.trim(),
|
||||
username = _uiState.value.username.trim(),
|
||||
password = _uiState.value.password
|
||||
)
|
||||
|
||||
Timber.d("[DEBUG][ACTION][executing_login] Executing login use case.")
|
||||
loginUseCase(credentials).fold(
|
||||
onSuccess = {
|
||||
Timber.d("[DEBUG][SUCCESS][login_successful] Login successful.")
|
||||
_uiState.update { it.copy(isLoading = false, isSetupComplete = true) }
|
||||
},
|
||||
onFailure = { exception ->
|
||||
Timber.e(exception, "[ERROR][EXCEPTION][login_failed] Login failed.")
|
||||
_uiState.update { it.copy(isLoading = false, error = exception.message ?: "Login failed") }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('connect')]
|
||||
Timber.d("[DEBUG][ACTION][saving_credentials] Saving credentials.")
|
||||
credentialsRepository.saveCredentials(credentials)
|
||||
|
||||
Timber.d("[DEBUG][ACTION][executing_login] Executing login use case.")
|
||||
loginUseCase(credentials).fold(
|
||||
onSuccess = {
|
||||
Timber.d("[DEBUG][SUCCESS][login_successful] Login successful.")
|
||||
_uiState.update { it.copy(isLoading = false, isSetupComplete = true) }
|
||||
},
|
||||
onFailure = { exception ->
|
||||
Timber.e(exception, "[ERROR][EXCEPTION][login_failed] Login failed.")
|
||||
_uiState.update { it.copy(isLoading = false, error = exception.message ?: "Login failed") }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('connect')]
|
||||
}
|
||||
// [END_ENTITY: ViewModel('SetupViewModel')]
|
||||
// [END_FILE_SetupViewModel.kt]
|
||||
@@ -0,0 +1,60 @@
|
||||
// [FILE] SplashScreen.kt
|
||||
// [SEMANTICS] app, ui, screen, splash
|
||||
package com.homebox.lens.ui.screen.splash
|
||||
|
||||
// [IMPORTS]
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.navigation.NavController
|
||||
import com.homebox.lens.ui.navigation.Screen
|
||||
import com.homebox.lens.ui.screen.setup.SetupViewModel
|
||||
import timber.log.Timber
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: Composable('SplashScreen')]
|
||||
// [RELATION: Composable('SplashScreen')] -> [DEPENDS_ON] -> [Framework('NavController')]
|
||||
// [RELATION: Composable('SplashScreen')] -> [DEPENDS_ON] -> [ViewModel('SetupViewModel')]
|
||||
/**
|
||||
* @summary A splash screen that checks for saved credentials and navigates accordingly.
|
||||
* @param navController The navigation controller for navigating to the next screen.
|
||||
* @param viewModel The view model for checking credentials.
|
||||
* @sideeffect Navigates to either the Setup or Dashboard screen.
|
||||
*/
|
||||
@Composable
|
||||
fun SplashScreen(
|
||||
navController: NavController,
|
||||
viewModel: SetupViewModel = hiltViewModel()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
Timber.d("[DEBUG][ACTION][checking_credentials] Checking for saved credentials on splash screen.")
|
||||
val areCredentialsSaved = viewModel.areCredentialsSaved()
|
||||
val destination = if (areCredentialsSaved) {
|
||||
Timber.d("[DEBUG][SUCCESS][credentials_found] Credentials found, navigating to Dashboard.")
|
||||
Screen.Dashboard.route
|
||||
} else {
|
||||
Timber.d("[DEBUG][FALLBACK][no_credentials] No credentials found, navigating to Setup.")
|
||||
Screen.Setup.route
|
||||
}
|
||||
|
||||
navController.navigate(destination) {
|
||||
popUpTo(Screen.Splash.route) {
|
||||
inclusive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Composable('SplashScreen')]
|
||||
// [END_FILE_SplashScreen.kt]
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.theme
|
||||
// [FILE] Color.kt
|
||||
// [SEMANTICS] ui, theme, color
|
||||
// [SEMANTICS] app, ui, theme, color
|
||||
package com.homebox.lens.ui.theme
|
||||
|
||||
// [IMPORTS]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.theme
|
||||
// [FILE] Theme.kt
|
||||
// [SEMANTICS] ui, theme
|
||||
// [SEMANTICS] app, ui, theme
|
||||
package com.homebox.lens.ui.theme
|
||||
|
||||
// [IMPORTS]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.theme
|
||||
// [FILE] Typography.kt
|
||||
// [SEMANTICS] ui, theme, typography
|
||||
// [SEMANTICS] app, ui, theme, typography
|
||||
package com.homebox.lens.ui.theme
|
||||
|
||||
// [IMPORTS]
|
||||
|
||||
@@ -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>
|
||||
@@ -2,114 +2,149 @@
|
||||
<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="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>
|
||||
<string name="label_description">Описание</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>
|
||||
|
||||
<string name="save_item">Сохранить</string>
|
||||
<string name="item_name">Название</string>
|
||||
<string name="item_description">Описание</string>
|
||||
<string name="item_quantity">Количество</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">Создать локацию</string>
|
||||
<string name="location_edit_title_edit">Редактировать локацию</string>
|
||||
<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">Местоположения не найдены. Нажмите +, чтобы добавить новое.</string>
|
||||
<string name="item_count">Предметов: %1$d</string>
|
||||
<string name="cd_more_options">Больше опций</string>
|
||||
<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="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>
|
||||
<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">Метки</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_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="item_edit_general_information">General Information</string>
|
||||
<string name="item_edit_location">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 Time</string>
|
||||
<string name="item_edit_select_date">Select Date</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>
|
||||
<string name="item_edit_warranty_expires">Warranty Expires</string>
|
||||
<string name="item_edit_identification">Identification</string>
|
||||
<string name="item_edit_asset_id">Asset ID</string>
|
||||
<string name="item_edit_serial_number">Serial Number</string>
|
||||
<string name="item_edit_manufacturer">Manufacturer</string>
|
||||
<string name="item_edit_model_number">Model Number</string>
|
||||
<string name="item_edit_status_notes">Status & Notes</string>
|
||||
<string name="item_edit_archived">Archived</string>
|
||||
<string name="item_edit_insured">Insured</string>
|
||||
<string name="item_edit_notes">Notes</string>
|
||||
<string name="item_edit_sold_information">Sold Information</string>
|
||||
<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 Time</string>
|
||||
|
||||
<!-- Search Screen -->
|
||||
<string name="placeholder_search_items">Search items...</string>
|
||||
|
||||
<!-- Setup Screen -->
|
||||
<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_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>
|
||||
@@ -1,129 +0,0 @@
|
||||
// [PACKAGE] com.homebox.lens.ui.screen.itemedit
|
||||
// [FILE] ItemEditViewModelTest.kt
|
||||
// [SEMANTICS] ui, viewmodel, testing
|
||||
|
||||
package com.homebox.lens.ui.screen.itemedit
|
||||
|
||||
import app.cash.turbine.test
|
||||
import com.homebox.lens.domain.model.Item
|
||||
import com.homebox.lens.domain.model.ItemCreate
|
||||
import com.homebox.lens.domain.model.ItemOut
|
||||
import com.homebox.lens.domain.model.ItemSummary
|
||||
import com.homebox.lens.domain.usecase.CreateItemUseCase
|
||||
import com.homebox.lens.domain.usecase.GetItemDetailsUseCase
|
||||
import com.homebox.lens.domain.usecase.UpdateItemUseCase
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.StandardTestDispatcher
|
||||
import kotlinx.coroutines.test.resetMain
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.test.setMain
|
||||
import org.junit.After
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.util.UUID
|
||||
|
||||
@ExperimentalCoroutinesApi
|
||||
class ItemEditViewModelTest {
|
||||
|
||||
private val testDispatcher = StandardTestDispatcher()
|
||||
|
||||
private lateinit var createItemUseCase: CreateItemUseCase
|
||||
private lateinit var updateItemUseCase: UpdateItemUseCase
|
||||
private lateinit var getItemDetailsUseCase: GetItemDetailsUseCase
|
||||
private lateinit var viewModel: ItemEditViewModel
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
Dispatchers.setMain(testDispatcher)
|
||||
createItemUseCase = mockk()
|
||||
updateItemUseCase = mockk()
|
||||
getItemDetailsUseCase = mockk()
|
||||
viewModel = ItemEditViewModel(createItemUseCase, updateItemUseCase, getItemDetailsUseCase)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
Dispatchers.resetMain()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadItem with valid id should update uiState with item`() = runTest {
|
||||
val itemId = UUID.randomUUID().toString()
|
||||
val itemOut = ItemOut(id = itemId, name = "Test Item", description = "Description", quantity = 1, images = emptyList(), location = null, labels = emptyList(), value = 10.0, createdAt = "2025-08-28T12:00:00Z", assetId = null, notes = null, serialNumber = null, isArchived = false, purchasePrice = null, purchaseDate = null, warrantyUntil = null, parent = null, children = emptyList(), attachments = emptyList(), fields = emptyList(), maintenance = emptyList(), updatedAt = "2025-08-28T12:00:00Z")
|
||||
coEvery { getItemDetailsUseCase(itemId) } returns itemOut
|
||||
|
||||
viewModel.loadItem(itemId)
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
val uiState = viewModel.uiState.value
|
||||
assertFalse(uiState.isLoading)
|
||||
assertNotNull(uiState.item)
|
||||
assertEquals(itemId, uiState.item?.id)
|
||||
assertEquals("Test Item", uiState.item?.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadItem with null id should prepare a new item`() = runTest {
|
||||
viewModel.loadItem(null)
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
val uiState = viewModel.uiState.value
|
||||
assertFalse(uiState.isLoading)
|
||||
assertNotNull(uiState.item)
|
||||
assertEquals("", uiState.item?.id)
|
||||
assertEquals("", uiState.item?.name)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `saveItem should call createItemUseCase for new item`() = runTest {
|
||||
val createdItemSummary = ItemSummary(id = UUID.randomUUID().toString(), name = "New Item", assetId = null, image = null, isArchived = false, labels = emptyList(), location = null, value = 0.0, createdAt = "2025-08-28T12:00:00Z", updatedAt = "2025-08-28T12:00:00Z")
|
||||
coEvery { createItemUseCase(any()) } returns createdItemSummary
|
||||
|
||||
viewModel.loadItem(null)
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
viewModel.updateName("New Item")
|
||||
viewModel.updateDescription("New Description")
|
||||
viewModel.updateQuantity(2)
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
viewModel.saveItem()
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
val uiState = viewModel.uiState.value
|
||||
assertFalse(uiState.isLoading)
|
||||
assertNotNull(uiState.item)
|
||||
assertEquals(createdItemSummary.id, uiState.item?.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `saveItem should call updateItemUseCase for existing item`() = runTest {
|
||||
val itemId = UUID.randomUUID().toString()
|
||||
val updatedItemOut = ItemOut(id = itemId, name = "Updated Item", description = "Updated Description", quantity = 4, images = emptyList(), location = null, labels = emptyList(), value = 12.0, createdAt = "2025-08-28T12:00:00Z", assetId = null, notes = null, serialNumber = null, isArchived = false, purchasePrice = null, purchaseDate = null, warrantyUntil = null, parent = null, children = emptyList(), attachments = emptyList(), fields = emptyList(), maintenance = emptyList(), updatedAt = "2025-08-28T12:00:00Z")
|
||||
coEvery { getItemDetailsUseCase(itemId) } returns ItemOut(id = itemId, name = "Existing Item", description = "Existing Description", quantity = 3, images = emptyList(), location = null, labels = emptyList(), value = 10.0, createdAt = "2025-08-28T12:00:00Z", assetId = null, notes = null, serialNumber = null, isArchived = false, purchasePrice = null, purchaseDate = null, warrantyUntil = null, parent = null, children = emptyList(), attachments = emptyList(), fields = emptyList(), maintenance = emptyList(), updatedAt = "2025-08-28T12:00:00Z")
|
||||
coEvery { updateItemUseCase(any()) } returns updatedItemOut
|
||||
|
||||
viewModel.loadItem(itemId)
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
viewModel.updateName("Updated Item")
|
||||
viewModel.updateDescription("Updated Description")
|
||||
viewModel.updateQuantity(4)
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
viewModel.saveItem()
|
||||
testDispatcher.scheduler.advanceUntilIdle()
|
||||
|
||||
val uiState = viewModel.uiState.value
|
||||
assertFalse(uiState.isLoading)
|
||||
assertNotNull(uiState.item)
|
||||
assertEquals(itemId, uiState.item?.id)
|
||||
assertEquals("Updated Item", uiState.item?.name)
|
||||
assertEquals(4, uiState.item?.quantity)
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,13 @@
|
||||
|
||||
plugins {
|
||||
// [PLUGIN] Android Application plugin
|
||||
id("com.android.application") version "8.12.2" apply false
|
||||
id("com.android.application") version "8.4.0" apply false
|
||||
// [PLUGIN] Kotlin Android plugin
|
||||
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.23" 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
|
||||
}
|
||||
|
||||
// [END_FILE_build.gradle.kts]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] buildsrc.dependencies
|
||||
// [FILE] Dependencies.kt
|
||||
// [SEMANTICS] build, dependencies
|
||||
|
||||
@@ -16,7 +15,7 @@ object Versions {
|
||||
const val coroutines = "1.7.3"
|
||||
|
||||
// Jetpack Compose
|
||||
const val composeCompiler = "1.5.8"
|
||||
const val composeCompiler = "1.5.11"
|
||||
const val composeBom = "2023.10.01"
|
||||
const val activityCompose = "1.8.2"
|
||||
const val navigationCompose = "2.7.6"
|
||||
|
||||
@@ -6,6 +6,7 @@ plugins {
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("com.google.dagger.hilt.android")
|
||||
id("kotlin-kapt")
|
||||
id("com.google.devtools.ksp")
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -27,11 +28,11 @@ android {
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
jvmTarget = "17"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +52,7 @@ dependencies {
|
||||
implementation(Libs.okhttp)
|
||||
implementation(Libs.okhttpLoggingInterceptor)
|
||||
implementation(Libs.moshiKotlin)
|
||||
kapt(Libs.moshiCodegen)
|
||||
ksp(Libs.moshiCodegen)
|
||||
|
||||
// [DEPENDENCY] Database (Room)
|
||||
implementation(Libs.roomRuntime)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.busya.ktlint.rules
|
||||
// [FILE] ExampleInstrumentedTest.kt
|
||||
// [SEMANTICS] testing, android, ktlint, rules
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.busya.ktlint.rules
|
||||
// [FILE] CustomRuleSetProvider.kt
|
||||
// [SEMANTICS] ktlint, rules, provider
|
||||
package com.busya.ktlint.rules
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.busya.ktlint.rules
|
||||
// [FILE] FileHeaderRule.kt
|
||||
// [SEMANTICS] ktlint, rules, file_header
|
||||
package com.busya.ktlint.rules
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.busya.ktlint.rules
|
||||
// [FILE] MandatoryEntityDeclarationRule.kt
|
||||
// [SEMANTICS] ktlint, rules, entity_declaration
|
||||
package com.busya.ktlint.rules
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.busya.ktlint.rules
|
||||
// [FILE] NoStrayCommentsRule.kt
|
||||
// [SEMANTICS] ktlint, rules, comments
|
||||
package com.busya.ktlint.rules
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.busya.ktlint.rules
|
||||
// [FILE] ExampleUnitTest.kt
|
||||
// [SEMANTICS] testing, ktlint, rules
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api
|
||||
// [FILE] HomeboxApiService.kt
|
||||
// [SEMANTICS] data, api, retrofit
|
||||
package com.homebox.lens.data.api
|
||||
@@ -11,7 +10,7 @@ import retrofit2.http.*
|
||||
|
||||
// [ENTITY: Interface('HomeboxApiService')]
|
||||
/**
|
||||
* @summary Определяет эндпоинты для взаимодействия с Homebox API, используя DTO.
|
||||
* @summary Defines the endpoints for interacting with the Homebox API using DTOs.
|
||||
*/
|
||||
interface HomeboxApiService {
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] CustomFieldDto.kt
|
||||
// [SEMANTICS] data_transfer_object, custom_field
|
||||
// [SEMANTICS] data, dto, custom_field
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -12,7 +11,7 @@ import com.homebox.lens.domain.model.CustomField
|
||||
|
||||
// [ENTITY: DataClass('CustomFieldDto')]
|
||||
/**
|
||||
* @summary DTO для кастомного поля.
|
||||
* @summary DTO for a custom field.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class CustomFieldDto(
|
||||
@@ -25,7 +24,7 @@ data class CustomFieldDto(
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('CustomField')]
|
||||
/**
|
||||
* @summary Маппер из CustomFieldDto в доменную модель CustomField.
|
||||
* @summary Mapper from CustomFieldDto to the CustomField domain model.
|
||||
*/
|
||||
fun CustomFieldDto.toDomain(): CustomField {
|
||||
return CustomField(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] GroupStatisticsDto.kt
|
||||
// [SEMANTICS] data_transfer_object, statistics
|
||||
// [SEMANTICS] data, dto, statistics
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -12,7 +11,7 @@ import com.homebox.lens.domain.model.GroupStatistics
|
||||
|
||||
// [ENTITY: DataClass('GroupStatisticsDto')]
|
||||
/**
|
||||
* @summary DTO для статистики.
|
||||
* @summary DTO for statistics.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GroupStatisticsDto(
|
||||
@@ -28,7 +27,7 @@ data class GroupStatisticsDto(
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('GroupStatistics')]
|
||||
/**
|
||||
* @summary Маппер из GroupStatisticsDto в доменную модель GroupStatistics.
|
||||
* @summary Mapper from GroupStatisticsDto to the GroupStatistics domain model.
|
||||
*/
|
||||
fun GroupStatisticsDto.toDomain(): GroupStatistics {
|
||||
return GroupStatistics(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] ImageDto.kt
|
||||
// [SEMANTICS] data_transfer_object, image
|
||||
// [SEMANTICS] data, dto, image
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -12,10 +11,10 @@ import com.homebox.lens.domain.model.Image
|
||||
|
||||
// [ENTITY: DataClass('ImageDto')]
|
||||
/**
|
||||
* @summary DTO для изображения.
|
||||
* @param id Уникальный идентификатор.
|
||||
* @param path Путь к файлу.
|
||||
* @param isPrimary Является ли основным.
|
||||
* @summary DTO for an image.
|
||||
* @param id The unique identifier.
|
||||
* @param path The path to the file.
|
||||
* @param isPrimary Whether it is the primary image.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ImageDto(
|
||||
@@ -28,7 +27,7 @@ data class ImageDto(
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('Image')]
|
||||
/**
|
||||
* @summary Маппер из ImageDto в доменную модель Image.
|
||||
* @summary Mapper from ImageDto to the Image domain model.
|
||||
*/
|
||||
fun ImageDto.toDomain(): Image {
|
||||
return Image(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] ItemAttachmentDto.kt
|
||||
// [SEMANTICS] data_transfer_object, attachment
|
||||
// [SEMANTICS] data, dto, attachment
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -12,7 +11,7 @@ import com.homebox.lens.domain.model.ItemAttachment
|
||||
|
||||
// [ENTITY: DataClass('ItemAttachmentDto')]
|
||||
/**
|
||||
* @summary DTO для вложения.
|
||||
* @summary DTO for an attachment.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ItemAttachmentDto(
|
||||
@@ -28,7 +27,7 @@ data class ItemAttachmentDto(
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('ItemAttachment')]
|
||||
/**
|
||||
* @summary Маппер из ItemAttachmentDto в доменную модель ItemAttachment.
|
||||
* @summary Mapper from ItemAttachmentDto to the ItemAttachment domain model.
|
||||
*/
|
||||
fun ItemAttachmentDto.toDomain(): ItemAttachment {
|
||||
return ItemAttachment(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] ItemCreateDto.kt
|
||||
// [SEMANTICS] data_transfer_object, item_creation
|
||||
// [SEMANTICS] data, dto, item_creation
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -12,22 +11,33 @@ import com.homebox.lens.domain.model.ItemCreate
|
||||
|
||||
// [ENTITY: DataClass('ItemCreateDto')]
|
||||
/**
|
||||
* @summary DTO для создания вещи.
|
||||
* @summary DTO for creating an item.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ItemCreateDto(
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "assetId") val assetId: String?,
|
||||
@Json(name = "description") val description: String?,
|
||||
@Json(name = "notes") val notes: String?,
|
||||
@Json(name = "serialNumber") val serialNumber: String?,
|
||||
@Json(name = "quantity") val quantity: Int?,
|
||||
@Json(name = "value") val value: Double?,
|
||||
@Json(name = "purchasePrice") val purchasePrice: Double?,
|
||||
@Json(name = "purchaseDate") val purchaseDate: String?,
|
||||
@Json(name = "warrantyUntil") val warrantyUntil: String?,
|
||||
@Json(name = "locationId") val locationId: String?,
|
||||
@Json(name = "archived") val archived: Boolean?,
|
||||
@Json(name = "assetId") val assetId: String?,
|
||||
@Json(name = "insured") val insured: Boolean?,
|
||||
@Json(name = "lifetimeWarranty") val lifetimeWarranty: Boolean?,
|
||||
@Json(name = "manufacturer") val manufacturer: String?,
|
||||
@Json(name = "modelNumber") val modelNumber: String?,
|
||||
@Json(name = "notes") val notes: String?,
|
||||
@Json(name = "parentId") val parentId: String?,
|
||||
@Json(name = "purchaseFrom") val purchaseFrom: String?,
|
||||
@Json(name = "purchasePrice") val purchasePrice: Double?,
|
||||
@Json(name = "purchaseTime") val purchaseTime: String?,
|
||||
@Json(name = "serialNumber") val serialNumber: String?,
|
||||
@Json(name = "soldNotes") val soldNotes: String?,
|
||||
@Json(name = "soldPrice") val soldPrice: Double?,
|
||||
@Json(name = "soldTime") val soldTime: String?,
|
||||
@Json(name = "soldTo") val soldTo: String?,
|
||||
@Json(name = "syncChildItemsLocations") val syncChildItemsLocations: Boolean?,
|
||||
@Json(name = "warrantyDetails") val warrantyDetails: String?,
|
||||
@Json(name = "warrantyExpires") val warrantyExpires: String?,
|
||||
@Json(name = "locationId") val locationId: String?,
|
||||
@Json(name = "labelIds") val labelIds: List<String>?
|
||||
)
|
||||
// [END_ENTITY: DataClass('ItemCreateDto')]
|
||||
@@ -35,22 +45,33 @@ data class ItemCreateDto(
|
||||
// [ENTITY: Function('toDto')]
|
||||
// [RELATION: Function('toDto')] -> [RETURNS] -> [DataClass('ItemCreateDto')]
|
||||
/**
|
||||
* @summary Маппер из доменной модели ItemCreate в ItemCreateDto.
|
||||
* @summary Mapper from the ItemCreate domain model to ItemCreateDto.
|
||||
*/
|
||||
fun ItemCreate.toDto(): ItemCreateDto {
|
||||
fun ItemCreate.toItemCreateDto(): ItemCreateDto {
|
||||
return ItemCreateDto(
|
||||
name = this.name,
|
||||
assetId = this.assetId,
|
||||
description = this.description,
|
||||
notes = this.notes,
|
||||
serialNumber = this.serialNumber,
|
||||
quantity = this.quantity,
|
||||
value = this.value,
|
||||
purchasePrice = this.purchasePrice,
|
||||
purchaseDate = this.purchaseDate,
|
||||
warrantyUntil = this.warrantyUntil,
|
||||
locationId = this.locationId,
|
||||
archived = this.archived,
|
||||
assetId = this.assetId,
|
||||
insured = this.insured,
|
||||
lifetimeWarranty = this.lifetimeWarranty,
|
||||
manufacturer = this.manufacturer,
|
||||
modelNumber = this.modelNumber,
|
||||
notes = this.notes,
|
||||
parentId = this.parentId,
|
||||
purchaseFrom = this.purchaseFrom,
|
||||
purchasePrice = this.purchasePrice,
|
||||
purchaseTime = this.purchaseTime,
|
||||
serialNumber = this.serialNumber,
|
||||
soldNotes = this.soldNotes,
|
||||
soldPrice = this.soldPrice,
|
||||
soldTime = this.soldTime,
|
||||
soldTo = this.soldTo,
|
||||
syncChildItemsLocations = this.syncChildItemsLocations,
|
||||
warrantyDetails = this.warrantyDetails,
|
||||
warrantyExpires = this.warrantyExpires,
|
||||
locationId = this.locationId,
|
||||
labelIds = this.labelIds
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] ItemDto.kt
|
||||
// [SEMANTICS] data, dto, api
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: DataClass('ItemOut')]
|
||||
// [RELATION: DataClass('ItemOut')] -> [DEPENDS_ON] -> [DataClass('LocationOut')]
|
||||
// [RELATION: DataClass('ItemOut')] -> [DEPENDS_ON] -> [DataClass('LabelOutDto')]
|
||||
/**
|
||||
* @summary DTO для полной информации о вещи (GET /v1/items/{id}).
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ItemOut(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "description") val description: String?,
|
||||
@Json(name = "image") val image: String?,
|
||||
@Json(name = "location") val location: LocationOut?,
|
||||
@Json(name = "labels") val labels: List<LabelOutDto>,
|
||||
@Json(name = "value") val value: BigDecimal?,
|
||||
@Json(name = "createdAt") val createdAt: String?
|
||||
)
|
||||
// [END_ENTITY: DataClass('ItemOut')]
|
||||
|
||||
// [ENTITY: DataClass('ItemSummary')]
|
||||
// [RELATION: DataClass('ItemSummary')] -> [DEPENDS_ON] -> [DataClass('LocationOut')]
|
||||
/**
|
||||
* @summary DTO для краткой информации о вещи в списках (GET /v1/items).
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ItemSummary(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "image") val image: String?,
|
||||
@Json(name = "location") val location: LocationOut?,
|
||||
@Json(name = "createdAt") val createdAt: String?
|
||||
)
|
||||
// [END_ENTITY: DataClass('ItemSummary')]
|
||||
|
||||
// [ENTITY: DataClass('ItemCreate')]
|
||||
/**
|
||||
* @summary DTO для создания новой вещи (POST /v1/items).
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ItemCreate(
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "description") val description: String?,
|
||||
@Json(name = "locationId") val locationId: String?,
|
||||
@Json(name = "labelIds") val labelIds: List<String>?,
|
||||
@Json(name = "value") val value: BigDecimal?
|
||||
)
|
||||
// [END_ENTITY: DataClass('ItemCreate')]
|
||||
|
||||
// [ENTITY: DataClass('ItemUpdate')]
|
||||
/**
|
||||
* @summary DTO для обновления вещи (PUT /v1/items/{id}).
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ItemUpdate(
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "description") val description: String?,
|
||||
@Json(name = "locationId") val locationId: String?,
|
||||
@Json(name = "labelIds") val labelIds: List<String>?,
|
||||
@Json(name = "value") val value: BigDecimal?
|
||||
)
|
||||
// [END_ENTITY: DataClass('ItemUpdate')]
|
||||
|
||||
// [END_FILE_ItemDto.kt]
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] ItemOutDto.kt
|
||||
// [SEMANTICS] data_transfer_object, item_detailed
|
||||
// [SEMANTICS] data, dto, item_detailed
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -24,10 +23,20 @@ data class ItemOutDto(
|
||||
@Json(name = "serialNumber") val serialNumber: String?,
|
||||
@Json(name = "quantity") val quantity: Int,
|
||||
@Json(name = "isArchived") val isArchived: Boolean,
|
||||
@Json(name = "value") val value: Double,
|
||||
@Json(name = "purchasePrice") val purchasePrice: Double?,
|
||||
@Json(name = "purchaseDate") val purchaseDate: String?,
|
||||
@Json(name = "warrantyUntil") val warrantyUntil: String?,
|
||||
@Json(name = "purchaseTime") val purchaseTime: String?,
|
||||
@Json(name = "purchaseFrom") val purchaseFrom: String?,
|
||||
@Json(name = "warrantyExpires") val warrantyExpires: String?,
|
||||
@Json(name = "warrantyDetails") val warrantyDetails: String?,
|
||||
@Json(name = "lifetimeWarranty") val lifetimeWarranty: Boolean?,
|
||||
@Json(name = "insured") val insured: Boolean?,
|
||||
@Json(name = "manufacturer") val manufacturer: String?,
|
||||
@Json(name = "modelNumber") val modelNumber: String?,
|
||||
@Json(name = "soldPrice") val soldPrice: Double?,
|
||||
@Json(name = "soldTime") val soldTime: String?,
|
||||
@Json(name = "soldTo") val soldTo: String?,
|
||||
@Json(name = "soldNotes") val soldNotes: String?,
|
||||
@Json(name = "syncChildItemsLocations") val syncChildItemsLocations: Boolean?,
|
||||
@Json(name = "location") val location: LocationOutDto?,
|
||||
@Json(name = "parent") val parent: ItemSummaryDto?,
|
||||
@Json(name = "children") val children: List<ItemSummaryDto>,
|
||||
@@ -44,7 +53,7 @@ data class ItemOutDto(
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('ItemOut')]
|
||||
/**
|
||||
* @summary Маппер из ItemOutDto в доменную модель ItemOut.
|
||||
* @summary Mapper from ItemOutDto to the ItemOut domain model.
|
||||
*/
|
||||
fun ItemOutDto.toDomain(): ItemOut {
|
||||
return ItemOut(
|
||||
@@ -56,10 +65,20 @@ fun ItemOutDto.toDomain(): ItemOut {
|
||||
serialNumber = this.serialNumber,
|
||||
quantity = this.quantity,
|
||||
isArchived = this.isArchived,
|
||||
value = this.value,
|
||||
purchasePrice = this.purchasePrice,
|
||||
purchaseDate = this.purchaseDate,
|
||||
warrantyUntil = this.warrantyUntil,
|
||||
purchaseTime = this.purchaseTime,
|
||||
purchaseFrom = this.purchaseFrom,
|
||||
warrantyExpires = this.warrantyExpires,
|
||||
warrantyDetails = this.warrantyDetails,
|
||||
lifetimeWarranty = this.lifetimeWarranty,
|
||||
insured = this.insured,
|
||||
manufacturer = this.manufacturer,
|
||||
modelNumber = this.modelNumber,
|
||||
soldPrice = this.soldPrice,
|
||||
soldTime = this.soldTime,
|
||||
soldTo = this.soldTo,
|
||||
soldNotes = this.soldNotes,
|
||||
syncChildItemsLocations = this.syncChildItemsLocations,
|
||||
location = this.location?.toDomain(),
|
||||
parent = this.parent?.toDomain(),
|
||||
children = this.children.map { it.toDomain() },
|
||||
@@ -72,4 +91,4 @@ fun ItemOutDto.toDomain(): ItemOut {
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('toDomain')]
|
||||
// [END_ENTITY: Function('toDomain')]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] ItemSummaryDto.kt
|
||||
// [SEMANTICS] data_transfer_object, item_summary
|
||||
// [SEMANTICS] data, dto, item_summary
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -20,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
|
||||
)
|
||||
@@ -32,7 +31,7 @@ data class ItemSummaryDto(
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('ItemSummary')]
|
||||
/**
|
||||
* @summary Маппер из ItemSummaryDto в доменную модель ItemSummary.
|
||||
* @summary Mapper from ItemSummaryDto to the ItemSummary domain model.
|
||||
*/
|
||||
fun ItemSummaryDto.toDomain(): ItemSummary {
|
||||
return ItemSummary(
|
||||
@@ -40,12 +39,12 @@ 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
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('toDomain')]
|
||||
// [END_ENTITY: Function('toDomain')]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] ItemUpdateDto.kt
|
||||
// [SEMANTICS] data_transfer_object, item_update
|
||||
// [SEMANTICS] data, dto, item_update
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -17,18 +16,28 @@ import com.homebox.lens.domain.model.ItemUpdate
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class ItemUpdateDto(
|
||||
@Json(name = "name") val name: String?,
|
||||
@Json(name = "assetId") val assetId: String?,
|
||||
@Json(name = "description") val description: String?,
|
||||
@Json(name = "notes") val notes: String?,
|
||||
@Json(name = "serialNumber") val serialNumber: String?,
|
||||
@Json(name = "quantity") val quantity: Int?,
|
||||
@Json(name = "isArchived") val isArchived: Boolean?,
|
||||
@Json(name = "value") val value: Double?,
|
||||
@Json(name = "purchasePrice") val purchasePrice: Double?,
|
||||
@Json(name = "purchaseDate") val purchaseDate: String?,
|
||||
@Json(name = "warrantyUntil") val warrantyUntil: String?,
|
||||
@Json(name = "locationId") val locationId: String?,
|
||||
@Json(name = "archived") val archived: Boolean?,
|
||||
@Json(name = "assetId") val assetId: String?,
|
||||
@Json(name = "insured") val insured: Boolean?,
|
||||
@Json(name = "lifetimeWarranty") val lifetimeWarranty: Boolean?,
|
||||
@Json(name = "manufacturer") val manufacturer: String?,
|
||||
@Json(name = "modelNumber") val modelNumber: String?,
|
||||
@Json(name = "notes") val notes: String?,
|
||||
@Json(name = "parentId") val parentId: String?,
|
||||
@Json(name = "purchaseFrom") val purchaseFrom: String?,
|
||||
@Json(name = "purchasePrice") val purchasePrice: Double?,
|
||||
@Json(name = "purchaseTime") val purchaseTime: String?,
|
||||
@Json(name = "serialNumber") val serialNumber: String?,
|
||||
@Json(name = "soldNotes") val soldNotes: String?,
|
||||
@Json(name = "soldPrice") val soldPrice: Double?,
|
||||
@Json(name = "soldTime") val soldTime: String?,
|
||||
@Json(name = "soldTo") val soldTo: String?,
|
||||
@Json(name = "syncChildItemsLocations") val syncChildItemsLocations: Boolean?,
|
||||
@Json(name = "warrantyDetails") val warrantyDetails: String?,
|
||||
@Json(name = "warrantyExpires") val warrantyExpires: String?,
|
||||
@Json(name = "locationId") val locationId: String?,
|
||||
@Json(name = "labelIds") val labelIds: List<String>?
|
||||
)
|
||||
// [END_ENTITY: DataClass('ItemUpdateDto')]
|
||||
@@ -38,21 +47,31 @@ data class ItemUpdateDto(
|
||||
/**
|
||||
* @summary Маппер из доменной модели ItemUpdate в ItemUpdateDto.
|
||||
*/
|
||||
fun ItemUpdate.toDto(): ItemUpdateDto {
|
||||
fun ItemUpdate.toItemUpdateDto(): ItemUpdateDto {
|
||||
return ItemUpdateDto(
|
||||
name = this.name,
|
||||
assetId = this.assetId,
|
||||
description = this.description,
|
||||
notes = this.notes,
|
||||
serialNumber = this.serialNumber,
|
||||
quantity = this.quantity,
|
||||
isArchived = this.isArchived,
|
||||
value = this.value,
|
||||
purchasePrice = this.purchasePrice,
|
||||
purchaseDate = this.purchaseDate,
|
||||
warrantyUntil = this.warrantyUntil,
|
||||
locationId = this.locationId,
|
||||
archived = this.archived,
|
||||
assetId = this.assetId,
|
||||
insured = this.insured,
|
||||
lifetimeWarranty = this.lifetimeWarranty,
|
||||
manufacturer = this.manufacturer,
|
||||
modelNumber = this.modelNumber,
|
||||
notes = this.notes,
|
||||
parentId = this.parentId,
|
||||
purchaseFrom = this.purchaseFrom,
|
||||
purchasePrice = this.purchasePrice,
|
||||
purchaseTime = this.purchaseTime,
|
||||
serialNumber = this.serialNumber,
|
||||
soldNotes = this.soldNotes,
|
||||
soldPrice = this.soldPrice,
|
||||
soldTime = this.soldTime,
|
||||
soldTo = this.soldTo,
|
||||
syncChildItemsLocations = this.syncChildItemsLocations,
|
||||
warrantyDetails = this.warrantyDetails,
|
||||
warrantyExpires = this.warrantyExpires,
|
||||
locationId = this.locationId,
|
||||
labelIds = this.labelIds
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] LabelCreateDto.kt
|
||||
// [SEMANTICS] data_transfer_object, label, create, api
|
||||
// [SEMANTICS] data, dto, label, create
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] LabelOutDto.kt
|
||||
// [SEMANTICS] data_transfer_object, label
|
||||
// [SEMANTICS] data, dto, label
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -29,13 +28,14 @@ data class LabelOutDto(
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('LabelOut')]
|
||||
/**
|
||||
* @summary Маппер из LabelOutDto в доменную модель LabelOut.
|
||||
* @summary Mapper from LabelOutDto to the LabelOut domain model.
|
||||
*/
|
||||
fun LabelOutDto.toDomain(): LabelOut {
|
||||
return LabelOut(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
color = this.color ?: "",
|
||||
description = this.description,
|
||||
color = this.color ?: "#000000",
|
||||
isArchived = this.isArchived ?: false,
|
||||
createdAt = this.createdAt,
|
||||
updatedAt = this.updatedAt
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] LabelSummaryDto.kt
|
||||
// [SEMANTICS] data_transfer_object, label, summary, api, mapper
|
||||
// [SEMANTICS] data, dto, label, summary
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -35,7 +34,8 @@ data class LabelSummaryDto(
|
||||
fun LabelSummaryDto.toDomain(): LabelSummary {
|
||||
return LabelSummary(
|
||||
id = this.id,
|
||||
name = this.name
|
||||
name = this.name,
|
||||
color = this.color ?: ""
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('toDomain')]
|
||||
|
||||
@@ -1,31 +1,24 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] LabelUpdateDto.kt
|
||||
// [SEMANTICS] data_transfer_object, label, update
|
||||
// [SEMANTICS] data, dto, label, update
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.homebox.lens.domain.model.LabelUpdate
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: DataClass('LabelUpdateDto')]
|
||||
/**
|
||||
* @summary DTO for updating a label.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LabelUpdateDto(
|
||||
@Json(name = "name")
|
||||
val name: String?,
|
||||
@Json(name = "color")
|
||||
val color: String?
|
||||
val color: String?,
|
||||
@Json(name = "description")
|
||||
val description: String?
|
||||
)
|
||||
// [END_ENTITY: DataClass('LabelUpdateDto')]
|
||||
|
||||
// [ENTITY: Function('toDto')]
|
||||
// [RELATION: Function('toDto')] -> [RETURNS] -> [DataClass('LabelUpdateDto')]
|
||||
fun LabelUpdate.toDto(): LabelUpdateDto {
|
||||
return LabelUpdateDto(
|
||||
name = this.name,
|
||||
color = this.color
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('toDto')]
|
||||
// [END_FILE_LabelUpdateDto.kt]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] LocationCreateDto.kt
|
||||
// [SEMANTICS] data_transfer_object, location, create
|
||||
// [SEMANTICS] data, dto, location, create
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -9,14 +8,19 @@ import com.squareup.moshi.JsonClass
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: DataClass('LocationCreateDto')]
|
||||
/**
|
||||
* @summary DTO for creating a location.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LocationCreateDto(
|
||||
@Json(name = "name")
|
||||
val name: String,
|
||||
@Json(name = "parentId")
|
||||
val parentId: String?,
|
||||
@Json(name = "color")
|
||||
val color: String?,
|
||||
@Json(name = "description")
|
||||
val description: String? // Assuming description can be null for creation
|
||||
val description: String?
|
||||
)
|
||||
// [END_ENTITY: DataClass('LocationCreateDto')]
|
||||
// [END_FILE_LocationCreateDto.kt]
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] LocationDto.kt
|
||||
// [SEMANTICS] data, dto, api, location
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: DataClass('LocationOut')]
|
||||
/**
|
||||
* @summary DTO для информации о местоположении.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LocationOut(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String
|
||||
)
|
||||
// [END_ENTITY: DataClass('LocationOut')]
|
||||
|
||||
// [ENTITY: DataClass('LocationOutCount')]
|
||||
/**
|
||||
* @summary DTO для информации о местоположении со счетчиком вещей.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LocationOutCount(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "name") val name: String,
|
||||
@Json(name = "itemCount") val itemCount: Int
|
||||
)
|
||||
// [END_ENTITY: DataClass('LocationOutCount')]
|
||||
|
||||
// [END_FILE_LocationDto.kt]
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] LocationOutCountDto.kt
|
||||
// [SEMANTICS] data_transfer_object, location, count
|
||||
// [SEMANTICS] data, dto, location, count
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -12,7 +11,7 @@ import com.homebox.lens.domain.model.LocationOutCount
|
||||
|
||||
// [ENTITY: DataClass('LocationOutCountDto')]
|
||||
/**
|
||||
* @summary DTO для местоположения со счетчиком.
|
||||
* @summary DTO for a location with an item count.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LocationOutCountDto(
|
||||
@@ -30,13 +29,13 @@ data class LocationOutCountDto(
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('LocationOutCount')]
|
||||
/**
|
||||
* @summary Маппер из LocationOutCountDto в доменную модель LocationOutCount.
|
||||
* @summary Mapper from LocationOutCountDto to the LocationOutCount domain model.
|
||||
*/
|
||||
fun LocationOutCountDto.toDomain(): LocationOutCount {
|
||||
return LocationOutCount(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
color = this.color ?: "",
|
||||
color = this.color ?: "#000000",
|
||||
isArchived = this.isArchived ?: false,
|
||||
itemCount = this.itemCount,
|
||||
createdAt = this.createdAt,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] LocationOutDto.kt
|
||||
// [SEMANTICS] data_transfer_object, location, output
|
||||
// [SEMANTICS] data, dto, location
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -17,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")
|
||||
@@ -29,12 +28,15 @@ data class LocationOutDto(
|
||||
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('LocationOut')]
|
||||
/**
|
||||
* @summary Mapper from LocationOutDto to the LocationOut domain model.
|
||||
*/
|
||||
fun LocationOutDto.toDomain(): LocationOut {
|
||||
return LocationOut(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
color = this.color,
|
||||
isArchived = this.isArchived,
|
||||
color = this.color ?: "#000000",
|
||||
isArchived = this.isArchived ?: false,
|
||||
createdAt = this.createdAt,
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
|
||||
@@ -1,31 +1,25 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] LocationUpdateDto.kt
|
||||
// [SEMANTICS] data_transfer_object, location, update
|
||||
// [SEMANTICS] data, dto, location, update
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import com.homebox.lens.domain.model.LocationUpdate
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: DataClass('LocationUpdateDto')]
|
||||
/**
|
||||
* @summary DTO for updating a location.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LocationUpdateDto(
|
||||
@Json(name = "name")
|
||||
val name: String?,
|
||||
@Json(name = "color")
|
||||
val color: String?
|
||||
val color: String?,
|
||||
@Json(name = "description")
|
||||
val description: String?
|
||||
)
|
||||
// [END_ENTITY: DataClass('LocationUpdateDto')]
|
||||
|
||||
// [ENTITY: Function('toDto')]
|
||||
// [RELATION: Function('toDto')] -> [RETURNS] -> [DataClass('LocationUpdateDto')]
|
||||
fun LocationUpdate.toDto(): LocationUpdateDto {
|
||||
return LocationUpdateDto(
|
||||
name = this.name,
|
||||
color = this.color
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('toDto')]
|
||||
// [END_FILE_LocationUpdateDto.kt]
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] LoginFormDto.kt
|
||||
// [SEMANTICS] data, dto, api, login
|
||||
// [SEMANTICS] data, dto, login
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -9,6 +8,9 @@ import com.squareup.moshi.JsonClass
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: DataClass('LoginFormDto')]
|
||||
/**
|
||||
* @summary DTO for the login form.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LoginFormDto(
|
||||
@Json(name = "username") val username: String,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] MaintenanceEntryDto.kt
|
||||
// [SEMANTICS] data_transfer_object, maintenance
|
||||
// [SEMANTICS] data, dto, maintenance
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -12,7 +11,7 @@ import com.homebox.lens.domain.model.MaintenanceEntry
|
||||
|
||||
// [ENTITY: DataClass('MaintenanceEntryDto')]
|
||||
/**
|
||||
* @summary DTO для записи об обслуживании.
|
||||
* @summary DTO for a maintenance entry.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class MaintenanceEntryDto(
|
||||
@@ -30,7 +29,7 @@ data class MaintenanceEntryDto(
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('MaintenanceEntry')]
|
||||
/**
|
||||
* @summary Маппер из MaintenanceEntryDto в доменную модель MaintenanceEntry.
|
||||
* @summary Mapper from MaintenanceEntryDto to the MaintenanceEntry domain model.
|
||||
*/
|
||||
fun MaintenanceEntryDto.toDomain(): MaintenanceEntry {
|
||||
return MaintenanceEntry(
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] PaginationDto.kt
|
||||
// [SEMANTICS] data, dto, api, pagination
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: DataClass('PaginationResult')]
|
||||
/**
|
||||
* @summary DTO для пагинированных результатов от API.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PaginationResult<T>(
|
||||
@Json(name = "items") val items: List<T>,
|
||||
@Json(name = "page") val page: Int,
|
||||
@Json(name = "pages") val pages: Int,
|
||||
@Json(name = "total") val total: Int,
|
||||
@Json(name = "pageSize") val pageSize: Int
|
||||
)
|
||||
// [END_ENTITY: DataClass('PaginationResult')]
|
||||
|
||||
// [END_FILE_PaginationDto.kt]
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] PaginationResultDto.kt
|
||||
// [SEMANTICS] data_transfer_object, pagination
|
||||
// [SEMANTICS] data, dto, pagination
|
||||
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
@@ -26,15 +25,14 @@ data class PaginationResultDto<T>(
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('PaginationResult')]
|
||||
/**
|
||||
* @summary Маппер из PaginationResultDto в доменную модель PaginationResult.
|
||||
* @param transform Функция для преобразования каждого элемента из DTO в доменную модель.
|
||||
* @summary Mapper from PaginationResultDto to the PaginationResult domain model.
|
||||
*/
|
||||
fun <T, R> PaginationResultDto<T>.toDomain(transform: (T) -> R): PaginationResult<R> {
|
||||
fun <T, R> PaginationResultDto<T>.toDomain(mapper: (T) -> R): PaginationResult<R> {
|
||||
return PaginationResult(
|
||||
items = this.items.map(transform),
|
||||
items = this.items.map(mapper),
|
||||
page = this.page,
|
||||
pageSize = this.pageSize,
|
||||
total = this.total
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('toDomain')]
|
||||
// [END_ENTITY: Function('toDomain')]
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] StatisticsDto.kt
|
||||
// [SEMANTICS] data, dto, api, statistics
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import java.math.BigDecimal
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: DataClass('GroupStatistics')]
|
||||
/**
|
||||
* @summary DTO для статистической информации.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class GroupStatistics(
|
||||
@Json(name = "totalValue") val totalValue: BigDecimal,
|
||||
@Json(name = "totalItems") val totalItems: Int,
|
||||
@Json(name = "locations") val locations: Int,
|
||||
@Json(name = "labels") val labels: Int
|
||||
)
|
||||
// [END_ENTITY: DataClass('GroupStatistics')]
|
||||
|
||||
// [END_FILE_StatisticsDto.kt]
|
||||
@@ -1,14 +1,17 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.dto
|
||||
// [FILE] TokenResponseDto.kt
|
||||
// [SEMANTICS] data, dto, api, token
|
||||
// [SEMANTICS] data, dto, token
|
||||
package com.homebox.lens.data.api.dto
|
||||
|
||||
// [IMPORTS]
|
||||
import com.homebox.lens.domain.model.TokenResponse
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: DataClass('TokenResponseDto')]
|
||||
/**
|
||||
* @summary DTO for the token response.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class TokenResponseDto(
|
||||
@Json(name = "token") val token: String,
|
||||
@@ -16,4 +19,18 @@ data class TokenResponseDto(
|
||||
@Json(name = "expiresAt") val expiresAt: String
|
||||
)
|
||||
// [END_ENTITY: DataClass('TokenResponseDto')]
|
||||
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('TokenResponse')]
|
||||
/**
|
||||
* @summary Mapper from TokenResponseDto to the TokenResponse domain model.
|
||||
*/
|
||||
fun TokenResponseDto.toDomain(): TokenResponse {
|
||||
return TokenResponse(
|
||||
token = this.token,
|
||||
attachmentToken = this.attachmentToken,
|
||||
expiresAt = this.expiresAt
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('toDomain')]
|
||||
// [END_FILE_TokenResponseDto.kt]
|
||||
@@ -1,30 +0,0 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.mapper
|
||||
// [FILE] TokenMapper.kt
|
||||
// [SEMANTICS] mapper, data_conversion, clean_architecture
|
||||
|
||||
package com.homebox.lens.data.api.mapper
|
||||
|
||||
// [IMPORTS]
|
||||
import com.homebox.lens.data.api.dto.TokenResponseDto
|
||||
import com.homebox.lens.domain.model.TokenResponse
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('TokenResponse')]
|
||||
/**
|
||||
* @summary Преобразует DTO-объект токена в доменную модель.
|
||||
* @receiver [TokenResponseDto] объект из слоя данных.
|
||||
* @return [TokenResponse] объект для доменного слоя.
|
||||
* @throws IllegalArgumentException если токен в DTO пустой.
|
||||
*/
|
||||
fun TokenResponseDto.toDomain(): TokenResponse {
|
||||
require(this.token.isNotBlank()) { "DTO token is blank, cannot map to domain model." }
|
||||
|
||||
val domainModel = TokenResponse(token = this.token)
|
||||
|
||||
check(domainModel.token.isNotBlank()) { "Domain model token is blank after mapping." }
|
||||
|
||||
return domainModel
|
||||
}
|
||||
// [END_ENTITY: Function('toDomain')]
|
||||
// [END_FILE_TokenMapper.kt]
|
||||
@@ -1,19 +1,19 @@
|
||||
// [PACKAGE] com.homebox.lens.data.api.model
|
||||
// [FILE] LoginRequest.kt
|
||||
// [SEMANTICS] dto, network, serialization, authentication
|
||||
// [SEMANTICS] data, dto, login
|
||||
|
||||
package com.homebox.lens.data.api.model
|
||||
|
||||
// [IMPORTS]
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: DataClass('LoginRequest')]
|
||||
/**
|
||||
* [ENTITY: DataClass('LoginRequest')]
|
||||
* [CONTRACT]
|
||||
* DTO (Data Transfer Object) для запроса на аутентификацию.
|
||||
* @property username Имя пользователя.
|
||||
* @property password Пароль пользователя.
|
||||
* @invariant Свойства не должны быть пустыми.
|
||||
* @summary DTO for the authentication request.
|
||||
* @property username The user's name.
|
||||
* @property password The user's password.
|
||||
* @invariant The properties must not be blank.
|
||||
*/
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class LoginRequest(
|
||||
@@ -21,9 +21,9 @@ data class LoginRequest(
|
||||
@Json(name = "password") val password: String
|
||||
) {
|
||||
init {
|
||||
// [INVARIANT_CHECK]
|
||||
require(username.isNotBlank()) { "[INVARIANT_FAILED] Username cannot be blank." }
|
||||
require(password.isNotBlank()) { "[INVARIANT_FAILED] Password cannot be blank." }
|
||||
require(username.isNotBlank()) { "Username cannot be blank." }
|
||||
require(password.isNotBlank()) { "Password cannot be blank." }
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: DataClass('LoginRequest')]
|
||||
// [END_FILE_LoginRequest.kt]
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.db
|
||||
// [FILE] Converters.kt
|
||||
// [SEMANTICS] data, database, room, converter
|
||||
package com.homebox.lens.data.db
|
||||
@@ -10,7 +9,7 @@ import java.math.BigDecimal
|
||||
|
||||
// [ENTITY: Class('Converters')]
|
||||
/**
|
||||
* @summary Предоставляет TypeConverters для Room для типов, не поддерживаемых по умолчанию.
|
||||
* @summary Provides TypeConverters for Room for types not supported by default.
|
||||
*/
|
||||
class Converters {
|
||||
// [ENTITY: Function('fromString')]
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.db
|
||||
// [FILE] HomeboxDatabase.kt
|
||||
// [SEMANTICS] data, database, room
|
||||
package com.homebox.lens.data.db
|
||||
@@ -15,7 +14,7 @@ import com.homebox.lens.data.db.entity.*
|
||||
|
||||
// [ENTITY: Database('HomeboxDatabase')]
|
||||
/**
|
||||
* @summary Основной класс для работы с локальной базой данных Room.
|
||||
* @summary The main class for working with the local Room database.
|
||||
*/
|
||||
@Database(
|
||||
entities = [
|
||||
@@ -24,7 +23,7 @@ import com.homebox.lens.data.db.entity.*
|
||||
LocationEntity::class,
|
||||
ItemLabelCrossRef::class
|
||||
],
|
||||
version = 1,
|
||||
version = 2,
|
||||
exportSchema = false
|
||||
)
|
||||
@TypeConverters(Converters::class)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.db.dao
|
||||
// [FILE] ItemDao.kt
|
||||
// [SEMANTICS] data, database, dao, item
|
||||
package com.homebox.lens.data.db.dao
|
||||
@@ -13,7 +12,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
|
||||
// [ENTITY: Interface('ItemDao')]
|
||||
/**
|
||||
* @summary Предоставляет методы для работы с 'items' в локальной БД.
|
||||
* @summary Provides methods for working with 'items' in the local DB.
|
||||
*/
|
||||
@Dao
|
||||
interface ItemDao {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.db.dao
|
||||
// [FILE] LabelDao.kt
|
||||
// [SEMANTICS] data, database, dao, label
|
||||
package com.homebox.lens.data.db.dao
|
||||
@@ -13,7 +12,7 @@ import com.homebox.lens.data.db.entity.LabelEntity
|
||||
|
||||
// [ENTITY: Interface('LabelDao')]
|
||||
/**
|
||||
* @summary Предоставляет методы для работы с 'labels' в локальной БД.
|
||||
* @summary Provides methods for working with 'labels' in the local DB.
|
||||
*/
|
||||
@Dao
|
||||
interface LabelDao {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.db.dao
|
||||
// [FILE] LocationDao.kt
|
||||
// [SEMANTICS] data, database, dao, location
|
||||
package com.homebox.lens.data.db.dao
|
||||
@@ -13,7 +12,7 @@ import com.homebox.lens.data.db.entity.LocationEntity
|
||||
|
||||
// [ENTITY: Interface('LocationDao')]
|
||||
/**
|
||||
* @summary Предоставляет методы для работы с 'locations' в локальной БД.
|
||||
* @summary Provides methods for working with 'locations' in the local DB.
|
||||
*/
|
||||
@Dao
|
||||
interface LocationDao {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.db.entity
|
||||
// [FILE] ItemEntity.kt
|
||||
// [SEMANTICS] data, database, entity, item
|
||||
package com.homebox.lens.data.db.entity
|
||||
@@ -6,22 +5,40 @@ package com.homebox.lens.data.db.entity
|
||||
// [IMPORTS]
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import java.math.BigDecimal
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: DatabaseTable('ItemEntity')]
|
||||
/**
|
||||
* @summary Представляет собой строку в таблице 'items' в локальной БД.
|
||||
* @summary Represents a row in the 'items' table in the local DB.
|
||||
*/
|
||||
@Entity(tableName = "items")
|
||||
data class ItemEntity(
|
||||
@PrimaryKey val id: String,
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val quantity: Int,
|
||||
val image: String?,
|
||||
val locationId: String?,
|
||||
val value: BigDecimal?,
|
||||
val createdAt: String?
|
||||
val purchasePrice: Double?,
|
||||
val createdAt: String?,
|
||||
val archived: Boolean,
|
||||
val assetId: String?,
|
||||
val insured: Boolean,
|
||||
val lifetimeWarranty: Boolean,
|
||||
val manufacturer: String?,
|
||||
val modelNumber: String?,
|
||||
val notes: String?,
|
||||
val parentId: String?,
|
||||
val purchaseFrom: String?,
|
||||
val purchaseTime: String?,
|
||||
val serialNumber: String?,
|
||||
val soldNotes: String?,
|
||||
val soldPrice: Double?,
|
||||
val soldTime: String?,
|
||||
val soldTo: String?,
|
||||
val syncChildItemsLocations: Boolean,
|
||||
val warrantyDetails: String?,
|
||||
val warrantyExpires: String?
|
||||
)
|
||||
// [END_ENTITY: DatabaseTable('ItemEntity')]
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.db.entity
|
||||
// [FILE] ItemLabelCrossRef.kt
|
||||
// [SEMANTICS] data, database, entity, relation
|
||||
package com.homebox.lens.data.db.entity
|
||||
@@ -10,7 +9,7 @@ import androidx.room.Index
|
||||
|
||||
// [ENTITY: DatabaseTable('ItemLabelCrossRef')]
|
||||
/**
|
||||
* @summary Таблица для связи "многие-ко-многим" между ItemEntity и LabelEntity.
|
||||
* @summary Table for the many-to-many relationship between ItemEntity and LabelEntity.
|
||||
*/
|
||||
@Entity(
|
||||
primaryKeys = ["itemId", "labelId"],
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.db.entity
|
||||
// [FILE] ItemWithLabels.kt
|
||||
// [SEMANTICS] data, database, entity, relation
|
||||
package com.homebox.lens.data.db.entity
|
||||
@@ -13,7 +12,7 @@ import androidx.room.Relation
|
||||
// [RELATION: DataClass('ItemWithLabels')] -> [DEPENDS_ON] -> [DatabaseTable('ItemEntity')]
|
||||
// [RELATION: DataClass('ItemWithLabels')] -> [DEPENDS_ON] -> [DatabaseTable('LabelEntity')]
|
||||
/**
|
||||
* @summary POJO для получения ItemEntity вместе со связанными LabelEntity.
|
||||
* @summary POJO for retrieving an ItemEntity with its associated LabelEntity objects.
|
||||
*/
|
||||
data class ItemWithLabels(
|
||||
@Embedded val item: ItemEntity,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.db.entity
|
||||
// [FILE] LabelEntity.kt
|
||||
// [SEMANTICS] data, database, entity, label
|
||||
package com.homebox.lens.data.db.entity
|
||||
@@ -10,7 +9,7 @@ import androidx.room.PrimaryKey
|
||||
|
||||
// [ENTITY: DatabaseTable('LabelEntity')]
|
||||
/**
|
||||
* @summary Представляет собой строку в таблице 'labels' в локальной БД.
|
||||
* @summary Represents a row in the 'labels' table in the local DB.
|
||||
*/
|
||||
@Entity(tableName = "labels")
|
||||
data class LabelEntity(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.db.entity
|
||||
// [FILE] LocationEntity.kt
|
||||
// [SEMANTICS] data, database, entity, location
|
||||
package com.homebox.lens.data.db.entity
|
||||
@@ -10,7 +9,7 @@ import androidx.room.PrimaryKey
|
||||
|
||||
// [ENTITY: DatabaseTable('LocationEntity')]
|
||||
/**
|
||||
* @summary Представляет собой строку в таблице 'locations' в локальной БД.
|
||||
* @summary Represents a row in the 'locations' table in the local DB.
|
||||
*/
|
||||
@Entity(tableName = "locations")
|
||||
data class LocationEntity(
|
||||
|
||||
@@ -1,49 +1,175 @@
|
||||
// [PACKAGE] com.homebox.lens.data.db.entity
|
||||
// [FILE] Mapper.kt
|
||||
// [SEMANTICS] data, database, mapper
|
||||
package com.homebox.lens.data.db.entity
|
||||
|
||||
// [IMPORTS]
|
||||
import com.homebox.lens.domain.model.Image
|
||||
import com.homebox.lens.domain.model.ItemSummary
|
||||
import com.homebox.lens.domain.model.LabelOut
|
||||
import com.homebox.lens.domain.model.LocationOut
|
||||
import com.homebox.lens.data.mapper.toDomain
|
||||
import com.homebox.lens.domain.model.*
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('ItemSummary')]
|
||||
// [ENTITY: Function('ItemWithLabels.toDomainItemSummary')]
|
||||
// [RELATION: Function('ItemWithLabels.toDomainItemSummary')] -> [RETURNS] -> [DataClass('ItemSummary')]
|
||||
/**
|
||||
* @summary Преобразует [ItemWithLabels] (сущность БД) в [ItemSummary] (доменную модель).
|
||||
* @summary Converts [ItemWithLabels] (DB entity) to [ItemSummary] (domain model).
|
||||
*/
|
||||
fun ItemWithLabels.toDomain(): ItemSummary {
|
||||
fun ItemWithLabels.toDomainItemSummary(): ItemSummary {
|
||||
return ItemSummary(
|
||||
id = this.item.id,
|
||||
name = this.item.name,
|
||||
image = this.item.image?.let { Image(id = "", path = it, isPrimary = true) },
|
||||
location = this.item.locationId?.let { LocationOut(id = it, name = "", color = "", isArchived = false, createdAt = "", updatedAt = "") },
|
||||
labels = this.labels.map { it.toDomain() },
|
||||
assetId = null,
|
||||
isArchived = false,
|
||||
value = this.item.value?.toDouble() ?: 0.0,
|
||||
labels = this.labels.map { it.toDomainLabelOut() },
|
||||
assetId = this.item.assetId,
|
||||
isArchived = this.item.archived,
|
||||
value = this.item.purchasePrice ?: 0.0,
|
||||
createdAt = this.item.createdAt ?: "",
|
||||
updatedAt = ""
|
||||
updatedAt = "" // ItemEntity does not have updatedAt
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('toDomain')]
|
||||
// [END_ENTITY: Function('ItemWithLabels.toDomainItemSummary')]
|
||||
|
||||
// [ENTITY: Function('toDomain')]
|
||||
// [RELATION: Function('toDomain')] -> [RETURNS] -> [DataClass('LabelOut')]
|
||||
// [ENTITY: Function('ItemEntity.toDomainItem')]
|
||||
// [RELATION: Function('ItemEntity.toDomainItem')] -> [RETURNS] -> [DataClass('Item')]
|
||||
/**
|
||||
* @summary Преобразует [LabelEntity] (сущность БД) в [LabelOut] (доменную модель).
|
||||
* @summary Converts [ItemEntity] (DB entity) to [Item] (domain model).
|
||||
*/
|
||||
fun LabelEntity.toDomain(): LabelOut {
|
||||
fun ItemEntity.toDomainItem(): Item {
|
||||
return Item(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
description = this.description,
|
||||
quantity = this.quantity,
|
||||
image = this.image,
|
||||
location = this.locationId?.let { Location(it, "") }, // Simplified, name is not in ItemEntity
|
||||
labels = emptyList(), // Labels are handled via ItemWithLabels
|
||||
purchasePrice = this.purchasePrice,
|
||||
createdAt = this.createdAt,
|
||||
archived = this.archived,
|
||||
assetId = this.assetId,
|
||||
fields = emptyList(), // Custom fields are not stored in ItemEntity
|
||||
insured = this.insured,
|
||||
lifetimeWarranty = this.lifetimeWarranty,
|
||||
manufacturer = this.manufacturer,
|
||||
modelNumber = this.modelNumber,
|
||||
notes = this.notes,
|
||||
parentId = this.parentId,
|
||||
purchaseFrom = this.purchaseFrom,
|
||||
purchaseTime = this.purchaseTime,
|
||||
serialNumber = this.serialNumber,
|
||||
soldNotes = this.soldNotes,
|
||||
soldPrice = this.soldPrice,
|
||||
soldTime = this.soldTime,
|
||||
soldTo = this.soldTo,
|
||||
syncChildItemsLocations = this.syncChildItemsLocations,
|
||||
warrantyDetails = this.warrantyDetails,
|
||||
warrantyExpires = this.warrantyExpires
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('ItemEntity.toDomainItem')]
|
||||
|
||||
// [ENTITY: Function('Item.toItemEntity')]
|
||||
// [RELATION: Function('Item.toItemEntity')] -> [RETURNS] -> [DataClass('ItemEntity')]
|
||||
/**
|
||||
* @summary Converts [Item] (domain model) to [ItemEntity] (DB entity).
|
||||
*/
|
||||
fun Item.toItemEntity(): ItemEntity {
|
||||
return ItemEntity(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
description = this.description,
|
||||
quantity = this.quantity,
|
||||
image = this.image,
|
||||
locationId = this.location?.id,
|
||||
purchasePrice = this.purchasePrice,
|
||||
createdAt = this.createdAt,
|
||||
archived = this.archived,
|
||||
assetId = this.assetId,
|
||||
insured = this.insured,
|
||||
lifetimeWarranty = this.lifetimeWarranty,
|
||||
manufacturer = this.manufacturer,
|
||||
modelNumber = this.modelNumber,
|
||||
notes = this.notes,
|
||||
parentId = this.parentId,
|
||||
purchaseFrom = this.purchaseFrom,
|
||||
purchaseTime = this.purchaseTime,
|
||||
serialNumber = this.serialNumber,
|
||||
soldNotes = this.soldNotes,
|
||||
soldPrice = this.soldPrice,
|
||||
soldTime = this.soldTime,
|
||||
soldTo = this.soldTo,
|
||||
syncChildItemsLocations = this.syncChildItemsLocations,
|
||||
warrantyDetails = this.warrantyDetails,
|
||||
warrantyExpires = this.warrantyExpires
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('Item.toItemEntity')]
|
||||
|
||||
// [ENTITY: Function('ItemOut.toItemEntity')]
|
||||
// [RELATION: Function('ItemOut.toItemEntity')] -> [RETURNS] -> [DataClass('ItemEntity')]
|
||||
fun ItemOut.toItemEntity(): ItemEntity {
|
||||
return ItemEntity(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
description = this.description,
|
||||
quantity = this.quantity,
|
||||
image = this.images.firstOrNull()?.path,
|
||||
locationId = this.location?.id,
|
||||
purchasePrice = this.purchasePrice,
|
||||
createdAt = this.createdAt,
|
||||
archived = this.isArchived,
|
||||
assetId = this.assetId,
|
||||
insured = this.insured ?: false,
|
||||
lifetimeWarranty = this.lifetimeWarranty ?: false,
|
||||
manufacturer = this.manufacturer,
|
||||
modelNumber = this.modelNumber,
|
||||
notes = this.notes,
|
||||
parentId = this.parent?.id,
|
||||
purchaseFrom = this.purchaseFrom,
|
||||
purchaseTime = this.purchaseTime,
|
||||
serialNumber = this.serialNumber,
|
||||
soldNotes = this.soldNotes,
|
||||
soldPrice = this.soldPrice,
|
||||
soldTime = this.soldTime,
|
||||
soldTo = this.soldTo,
|
||||
syncChildItemsLocations = this.syncChildItemsLocations ?: false,
|
||||
warrantyDetails = this.warrantyDetails,
|
||||
warrantyExpires = this.warrantyExpires
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('ItemOut.toItemEntity')]
|
||||
|
||||
// [ENTITY: Function('LabelEntity.toDomain')]
|
||||
// [RELATION: Function('LabelEntity.toDomain')] -> [RETURNS] -> [DataClass('Label')]
|
||||
fun LabelEntity.toDomain(): Label {
|
||||
return Label(
|
||||
id = this.id,
|
||||
name = this.name
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('LabelEntity.toDomain')]
|
||||
|
||||
// [ENTITY: Function('LabelEntity.toDomainLabelOut')]
|
||||
// [RELATION: Function('LabelEntity.toDomainLabelOut')] -> [RETURNS] -> [DataClass('LabelOut')]
|
||||
fun LabelEntity.toDomainLabelOut(): LabelOut {
|
||||
return LabelOut(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
color = "#CCCCCC",
|
||||
isArchived = false,
|
||||
createdAt = "",
|
||||
updatedAt = ""
|
||||
description = null, // Not available in LabelEntity
|
||||
color = "", // Not available in LabelEntity
|
||||
isArchived = false, // Not available in LabelEntity
|
||||
createdAt = "", // Not available in LabelEntity
|
||||
updatedAt = "" // Not available in LabelEntity
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('toDomain')]
|
||||
// [END_ENTITY: Function('LabelEntity.toDomainLabelOut')]
|
||||
|
||||
// [ENTITY: Function('Label.toEntity')]
|
||||
// [RELATION: Function('Label.toEntity')] -> [RETURNS] -> [DataClass('LabelEntity')]
|
||||
fun Label.toEntity(): LabelEntity {
|
||||
return LabelEntity(
|
||||
id = this.id,
|
||||
name = this.name
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('Label.toEntity')]
|
||||
// [END_FILE_Mapper.kt]
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.di
|
||||
// [FILE] ApiModule.kt
|
||||
// [SEMANTICS] di, networking
|
||||
// [SEMANTICS] data, di, networking
|
||||
package com.homebox.lens.data.di
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -25,8 +24,8 @@ import javax.inject.Singleton
|
||||
|
||||
// [ENTITY: Module('ApiModule')]
|
||||
/**
|
||||
* @summary Hilt-модуль, отвечающий за создание и предоставление всех зависимостей,
|
||||
* необходимых для сетевого взаимодействия.
|
||||
* @summary Hilt module responsible for creating and providing all dependencies
|
||||
* necessary for network interaction.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.di
|
||||
// [FILE] DatabaseModule.kt
|
||||
// [SEMANTICS] di, hilt, database
|
||||
// [SEMANTICS] data, di, database
|
||||
package com.homebox.lens.data.di
|
||||
|
||||
// [IMPORTS]
|
||||
@@ -18,7 +17,7 @@ import javax.inject.Singleton
|
||||
|
||||
// [ENTITY: Module('DatabaseModule')]
|
||||
/**
|
||||
* @summary Предоставляет зависимости для работы с базой данных Room.
|
||||
* @summary Provides dependencies for working with the Room database.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@@ -34,7 +33,7 @@ object DatabaseModule {
|
||||
context,
|
||||
HomeboxDatabase::class.java,
|
||||
HomeboxDatabase.DATABASE_NAME
|
||||
).build()
|
||||
).fallbackToDestructiveMigration().build()
|
||||
}
|
||||
// [END_ENTITY: Function('provideHomeboxDatabase')]
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.di
|
||||
// [FILE] RepositoryModule.kt
|
||||
// [SEMANTICS] dependency_injection, hilt, module, binding
|
||||
// [SEMANTICS] data, di, repository
|
||||
|
||||
package com.homebox.lens.data.di
|
||||
|
||||
@@ -20,8 +19,8 @@ import javax.inject.Singleton
|
||||
|
||||
// [ENTITY: Module('RepositoryModule')]
|
||||
/**
|
||||
* @summary Hilt-модуль для предоставления реализаций репозиториев.
|
||||
* @description Использует `@Binds` для эффективного связывания интерфейсов с их реализациями.
|
||||
* @summary Hilt module for providing repository implementations.
|
||||
* @description Uses `@Binds` for efficient binding of interfaces to their implementations.
|
||||
*/
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@@ -30,7 +29,7 @@ abstract class RepositoryModule {
|
||||
// [ENTITY: Function('bindItemRepository')]
|
||||
// [RELATION: Function('bindItemRepository')] -> [PROVIDES] -> [Interface('ItemRepository')]
|
||||
/**
|
||||
* @summary Связывает интерфейс ItemRepository с его реализацией.
|
||||
* @summary Binds the ItemRepository interface to its implementation.
|
||||
*/
|
||||
@Binds
|
||||
@Singleton
|
||||
@@ -42,7 +41,7 @@ abstract class RepositoryModule {
|
||||
// [ENTITY: Function('bindCredentialsRepository')]
|
||||
// [RELATION: Function('bindCredentialsRepository')] -> [PROVIDES] -> [Interface('CredentialsRepository')]
|
||||
/**
|
||||
* @summary Связывает интерфейс CredentialsRepository с его реализацией.
|
||||
* @summary Binds the CredentialsRepository interface to its implementation.
|
||||
*/
|
||||
@Binds
|
||||
@Singleton
|
||||
@@ -54,7 +53,7 @@ abstract class RepositoryModule {
|
||||
// [ENTITY: Function('bindAuthRepository')]
|
||||
// [RELATION: Function('bindAuthRepository')] -> [PROVIDES] -> [Interface('AuthRepository')]
|
||||
/**
|
||||
* @summary Связывает интерфейс AuthRepository с его реализацией.
|
||||
* @summary Binds the AuthRepository interface to its implementation.
|
||||
*/
|
||||
@Binds
|
||||
@Singleton
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.di
|
||||
// [FILE] StorageModule.kt
|
||||
// [SEMANTICS] di, hilt, storage
|
||||
// [SEMANTICS] data, di, storage
|
||||
package com.homebox.lens.data.di
|
||||
|
||||
// [IMPORTS]
|
||||
|
||||
129
data/src/main/java/com/homebox/lens/data/mapper/DomainToDto.kt
Normal file
129
data/src/main/java/com/homebox/lens/data/mapper/DomainToDto.kt
Normal file
@@ -0,0 +1,129 @@
|
||||
// [FILE] DomainToDto.kt
|
||||
// [SEMANTICS] data, mapper
|
||||
package com.homebox.lens.data.mapper
|
||||
|
||||
// [IMPORTS]
|
||||
import com.homebox.lens.data.api.dto.ItemCreateDto
|
||||
import com.homebox.lens.data.api.dto.ItemUpdateDto
|
||||
import com.homebox.lens.data.api.dto.LabelCreateDto
|
||||
import com.homebox.lens.data.api.dto.LabelUpdateDto
|
||||
import com.homebox.lens.data.api.dto.LocationCreateDto
|
||||
import com.homebox.lens.data.api.dto.LocationUpdateDto
|
||||
import com.homebox.lens.domain.model.ItemCreate as DomainItemCreate
|
||||
import com.homebox.lens.domain.model.ItemUpdate as DomainItemUpdate
|
||||
import com.homebox.lens.domain.model.LabelCreate as DomainLabelCreate
|
||||
import com.homebox.lens.domain.model.LabelUpdate as DomainLabelUpdate
|
||||
import com.homebox.lens.domain.model.LocationCreate as DomainLocationCreate
|
||||
import com.homebox.lens.domain.model.LocationUpdate as DomainLocationUpdate
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: Function('DomainItemCreate.toDto')]
|
||||
// [RELATION: Function('DomainItemCreate.toDto')] -> [RETURNS] -> [DataClass('ItemCreateDto')]
|
||||
fun DomainItemCreate.toDto(): ItemCreateDto {
|
||||
return ItemCreateDto(
|
||||
name = this.name,
|
||||
description = this.description,
|
||||
quantity = this.quantity,
|
||||
archived = this.archived,
|
||||
assetId = this.assetId,
|
||||
insured = this.insured,
|
||||
lifetimeWarranty = this.lifetimeWarranty,
|
||||
manufacturer = this.manufacturer,
|
||||
modelNumber = this.modelNumber,
|
||||
notes = this.notes,
|
||||
parentId = this.parentId,
|
||||
purchaseFrom = this.purchaseFrom,
|
||||
purchasePrice = this.purchasePrice?.toDouble(),
|
||||
purchaseTime = this.purchaseTime,
|
||||
serialNumber = this.serialNumber,
|
||||
soldNotes = this.soldNotes,
|
||||
soldPrice = this.soldPrice?.toDouble(),
|
||||
soldTime = this.soldTime,
|
||||
soldTo = this.soldTo,
|
||||
syncChildItemsLocations = this.syncChildItemsLocations,
|
||||
warrantyDetails = this.warrantyDetails,
|
||||
warrantyExpires = this.warrantyExpires,
|
||||
locationId = this.locationId,
|
||||
labelIds = this.labelIds
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('ItemCreate.toDto')]
|
||||
|
||||
// [ENTITY: Function('DomainItemUpdate.toDto')]
|
||||
// [RELATION: Function('DomainItemUpdate.toDto')] -> [RETURNS] -> [DataClass('ItemUpdateDto')]
|
||||
fun DomainItemUpdate.toDto(): ItemUpdateDto {
|
||||
return ItemUpdateDto(
|
||||
name = this.name,
|
||||
description = this.description,
|
||||
quantity = this.quantity,
|
||||
archived = this.archived,
|
||||
assetId = this.assetId,
|
||||
insured = this.insured,
|
||||
lifetimeWarranty = this.lifetimeWarranty,
|
||||
manufacturer = this.manufacturer,
|
||||
modelNumber = this.modelNumber,
|
||||
notes = this.notes,
|
||||
parentId = this.parentId,
|
||||
purchaseFrom = this.purchaseFrom,
|
||||
purchasePrice = this.purchasePrice?.toDouble(),
|
||||
purchaseTime = this.purchaseTime,
|
||||
serialNumber = this.serialNumber,
|
||||
soldNotes = this.soldNotes,
|
||||
soldPrice = this.soldPrice?.toDouble(),
|
||||
soldTime = this.soldTime,
|
||||
soldTo = this.soldTo,
|
||||
syncChildItemsLocations = this.syncChildItemsLocations,
|
||||
warrantyDetails = this.warrantyDetails,
|
||||
warrantyExpires = this.warrantyExpires,
|
||||
locationId = this.locationId,
|
||||
labelIds = this.labelIds
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('ItemUpdate.toDto')]
|
||||
|
||||
// [ENTITY: Function('DomainLabelCreate.toDto')]
|
||||
// [RELATION: Function('DomainLabelCreate.toDto')] -> [RETURNS] -> [DataClass('LabelCreateDto')]
|
||||
fun DomainLabelCreate.toDto(): LabelCreateDto {
|
||||
return LabelCreateDto(
|
||||
name = this.name,
|
||||
color = this.color,
|
||||
description = this.description
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('LabelCreate.toDto')]
|
||||
|
||||
// [ENTITY: Function('DomainLabelUpdate.toDto')]
|
||||
// [RELATION: Function('DomainLabelUpdate.toDto')] -> [RETURNS] -> [DataClass('LabelUpdateDto')]
|
||||
fun DomainLabelUpdate.toDto(): LabelUpdateDto {
|
||||
return LabelUpdateDto(
|
||||
name = this.name,
|
||||
color = this.color,
|
||||
description = this.description
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('DomainLabelUpdate.toDto')]
|
||||
|
||||
// [ENTITY: Function('DomainLocationCreate.toDto')]
|
||||
// [RELATION: Function('DomainLocationCreate.toDto')] -> [RETURNS] -> [DataClass('LocationCreateDto')]
|
||||
fun DomainLocationCreate.toDto(): LocationCreateDto {
|
||||
return LocationCreateDto(
|
||||
name = this.name,
|
||||
parentId = this.parentId,
|
||||
color = null,
|
||||
description = this.description
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('DomainLocationCreate.toDto')]
|
||||
|
||||
// [ENTITY: Function('DomainLocationUpdate.toDto')]
|
||||
// [RELATION: Function('DomainLocationUpdate.toDto')] -> [RETURNS] -> [DataClass('LocationUpdateDto')]
|
||||
fun DomainLocationUpdate.toDto(): LocationUpdateDto {
|
||||
return LocationUpdateDto(
|
||||
name = this.name,
|
||||
color = this.color,
|
||||
description = this.description
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('DomainLocationUpdate.toDto')]
|
||||
|
||||
// [END_FILE_DomainToDto.kt]
|
||||
269
data/src/main/java/com/homebox/lens/data/mapper/DtoToDomain.kt
Normal file
269
data/src/main/java/com/homebox/lens/data/mapper/DtoToDomain.kt
Normal file
@@ -0,0 +1,269 @@
|
||||
// [FILE] DtoToDomain.kt
|
||||
// [SEMANTICS] data, mapper
|
||||
package com.homebox.lens.data.mapper
|
||||
|
||||
// [IMPORTS]
|
||||
import com.homebox.lens.data.api.dto.*
|
||||
import com.homebox.lens.domain.model.CustomField as DomainCustomField
|
||||
import com.homebox.lens.domain.model.GroupStatistics as DomainGroupStatistics
|
||||
import com.homebox.lens.domain.model.Image as DomainImage
|
||||
import com.homebox.lens.domain.model.Item as DomainItem
|
||||
import com.homebox.lens.domain.model.ItemAttachment as DomainItemAttachment
|
||||
import com.homebox.lens.domain.model.ItemOut as DomainItemOut
|
||||
import com.homebox.lens.domain.model.ItemSummary as DomainItemSummary
|
||||
import com.homebox.lens.domain.model.Label as DomainLabel
|
||||
import com.homebox.lens.domain.model.LabelOut as DomainLabelOut
|
||||
import com.homebox.lens.domain.model.LabelSummary as DomainLabelSummary
|
||||
import com.homebox.lens.domain.model.Location as DomainLocation
|
||||
import com.homebox.lens.domain.model.LocationOut as DomainLocationOut
|
||||
import com.homebox.lens.domain.model.LocationOutCount as DomainLocationOutCount
|
||||
import com.homebox.lens.domain.model.MaintenanceEntry as DomainMaintenanceEntry
|
||||
import com.homebox.lens.domain.model.PaginationResult as DomainPaginationResult
|
||||
import com.homebox.lens.domain.model.TokenResponse as DomainTokenResponse
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: Function('ItemOutDto.toDomain')]
|
||||
// [RELATION: Function('ItemOutDto.toDomain')] -> [RETURNS] -> [DataClass('DomainItemOut')]
|
||||
fun ItemOutDto.toDomain(): DomainItemOut {
|
||||
return DomainItemOut(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
assetId = this.assetId,
|
||||
description = this.description,
|
||||
notes = this.notes,
|
||||
serialNumber = this.serialNumber,
|
||||
quantity = this.quantity,
|
||||
isArchived = this.isArchived,
|
||||
purchasePrice = this.purchasePrice,
|
||||
purchaseTime = this.purchaseTime,
|
||||
purchaseFrom = this.purchaseFrom,
|
||||
warrantyExpires = this.warrantyExpires,
|
||||
warrantyDetails = this.warrantyDetails,
|
||||
lifetimeWarranty = this.lifetimeWarranty,
|
||||
insured = this.insured,
|
||||
manufacturer = this.manufacturer,
|
||||
modelNumber = this.modelNumber,
|
||||
soldPrice = this.soldPrice,
|
||||
soldTime = this.soldTime,
|
||||
soldTo = this.soldTo,
|
||||
soldNotes = this.soldNotes,
|
||||
syncChildItemsLocations = this.syncChildItemsLocations,
|
||||
location = this.location?.toDomain(),
|
||||
parent = this.parent?.toDomain(),
|
||||
children = this.children.map { it.toDomain() },
|
||||
labels = this.labels.map { it.toDomain() },
|
||||
attachments = this.attachments.map { it.toDomain() },
|
||||
images = this.images.map { it.toDomain() },
|
||||
fields = this.fields.map { it.toDomain() },
|
||||
maintenance = this.maintenance.map { it.toDomain() },
|
||||
createdAt = this.createdAt,
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
}
|
||||
|
||||
fun ItemOutDto.toDomainItem(): DomainItem {
|
||||
return DomainItem(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
description = this.description,
|
||||
quantity = this.quantity,
|
||||
image = this.images.firstOrNull { it.isPrimary }?.path,
|
||||
location = this.location?.toDomainLocation(),
|
||||
labels = this.labels.map { it.toDomainLabel() },
|
||||
purchasePrice = this.purchasePrice,
|
||||
createdAt = this.createdAt,
|
||||
archived = this.isArchived,
|
||||
assetId = this.assetId,
|
||||
fields = this.fields.map { it.toDomain() },
|
||||
insured = this.insured ?: false,
|
||||
lifetimeWarranty = this.lifetimeWarranty ?: false,
|
||||
manufacturer = this.manufacturer,
|
||||
modelNumber = this.modelNumber,
|
||||
notes = this.notes,
|
||||
parentId = this.parent?.id,
|
||||
purchaseFrom = this.purchaseFrom,
|
||||
purchaseTime = this.purchaseTime,
|
||||
serialNumber = this.serialNumber,
|
||||
soldNotes = this.soldNotes,
|
||||
soldPrice = this.soldPrice,
|
||||
soldTime = this.soldTime,
|
||||
soldTo = this.soldTo,
|
||||
syncChildItemsLocations = this.syncChildItemsLocations ?: false,
|
||||
warrantyDetails = this.warrantyDetails,
|
||||
warrantyExpires = this.warrantyExpires
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('ItemOutDto.toDomain')]
|
||||
|
||||
// [ENTITY: Function('ItemSummaryDto.toDomain')]
|
||||
// [RELATION: Function('ItemSummaryDto.toDomain')] -> [RETURNS] -> [DataClass('DomainItemSummary')]
|
||||
fun ItemSummaryDto.toDomain(): DomainItemSummary {
|
||||
return DomainItemSummary(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
assetId = this.assetId,
|
||||
image = this.image?.toDomain(),
|
||||
isArchived = this.isArchived ?: false,
|
||||
labels = this.labels.map { it.toDomain() },
|
||||
location = this.location?.toDomain(),
|
||||
value = this.value ?: 0.0,
|
||||
createdAt = this.createdAt,
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('ItemSummaryDto.toDomain')]
|
||||
|
||||
// [ENTITY: Function('LabelOutDto.toDomain')]
|
||||
// [RELATION: Function('LabelOutDto.toDomain')] -> [RETURNS] -> [DataClass('DomainLabelOut')]
|
||||
fun LabelOutDto.toDomain(): DomainLabelOut {
|
||||
return DomainLabelOut(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
description = this.description,
|
||||
color = this.color ?: "",
|
||||
isArchived = this.isArchived ?: false,
|
||||
createdAt = this.createdAt,
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
}
|
||||
|
||||
fun LabelOutDto.toDomainLabel(): DomainLabel {
|
||||
return DomainLabel(
|
||||
id = this.id,
|
||||
name = this.name
|
||||
)
|
||||
}
|
||||
|
||||
fun com.homebox.lens.domain.model.LabelOut.toDomain(): DomainLabel {
|
||||
return DomainLabel(
|
||||
id = this.id,
|
||||
name = this.name
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('LabelOutDto.toDomain')]
|
||||
|
||||
// [ENTITY: Function('LocationOutDto.toDomain')]
|
||||
// [RELATION: Function('LocationOutDto.toDomain')] -> [RETURNS] -> [DataClass('DomainLocationOut')]
|
||||
fun LocationOutDto.toDomain(): DomainLocationOut {
|
||||
return DomainLocationOut(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
color = this.color,
|
||||
isArchived = this.isArchived,
|
||||
createdAt = this.createdAt,
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
}
|
||||
|
||||
fun LocationOutDto.toDomainLocation(): DomainLocation {
|
||||
return DomainLocation(
|
||||
id = this.id,
|
||||
name = this.name
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('LocationOutDto.toDomain')]
|
||||
|
||||
// [ENTITY: Function('LocationOutCountDto.toDomain')]
|
||||
// [RELATION: Function('LocationOutCountDto.toDomain')] -> [RETURNS] -> [DataClass('DomainLocationOutCount')]
|
||||
fun LocationOutCountDto.toDomain(): DomainLocationOutCount {
|
||||
return DomainLocationOutCount(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
color = this.color ?: "",
|
||||
isArchived = this.isArchived ?: false,
|
||||
itemCount = this.itemCount,
|
||||
createdAt = this.createdAt,
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('LocationOutCountDto.toDomain')]
|
||||
|
||||
// [ENTITY: Function('PaginationResultDto.toDomain')]
|
||||
// [RELATION: Function('PaginationResultDto.toDomain')] -> [RETURNS] -> [DataClass('DomainPaginationResult')]
|
||||
fun <T, R> PaginationResultDto<T>.toDomain(transform: (T) -> R): DomainPaginationResult<R> {
|
||||
return DomainPaginationResult(
|
||||
items = this.items.map(transform),
|
||||
page = this.page,
|
||||
pageSize = this.pageSize,
|
||||
total = this.total
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('PaginationResultDto.toDomain')]
|
||||
|
||||
// [ENTITY: Function('ImageDto.toDomain')]
|
||||
// [RELATION: Function('ImageDto.toDomain')] -> [RETURNS] -> [DataClass('DomainImage')]
|
||||
fun ImageDto.toDomain(): DomainImage {
|
||||
return DomainImage(
|
||||
id = this.id,
|
||||
path = this.path,
|
||||
isPrimary = this.isPrimary
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('ImageDto.toDomain')]
|
||||
|
||||
// [ENTITY: Function('CustomFieldDto.toDomain')]
|
||||
// [RELATION: Function('CustomFieldDto.toDomain')] -> [RETURNS] -> [DataClass('DomainCustomField')]
|
||||
fun CustomFieldDto.toDomain(): DomainCustomField {
|
||||
return DomainCustomField(
|
||||
name = this.name,
|
||||
value = this.value,
|
||||
type = this.type
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('CustomFieldDto.toDomain')]
|
||||
|
||||
// [ENTITY: Function('ItemAttachmentDto.toDomain')]
|
||||
// [RELATION: Function('ItemAttachmentDto.toDomain')] -> [RETURNS] -> [DataClass('DomainItemAttachment')]
|
||||
fun ItemAttachmentDto.toDomain(): DomainItemAttachment {
|
||||
return DomainItemAttachment(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
path = this.path,
|
||||
type = this.type,
|
||||
createdAt = this.createdAt,
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('ItemAttachmentDto.toDomain')]
|
||||
|
||||
// [ENTITY: Function('MaintenanceEntryDto.toDomain')]
|
||||
// [RELATION: Function('MaintenanceEntryDto.toDomain')] -> [RETURNS] -> [DataClass('DomainMaintenanceEntry')]
|
||||
fun MaintenanceEntryDto.toDomain(): DomainMaintenanceEntry {
|
||||
return DomainMaintenanceEntry(
|
||||
id = this.id,
|
||||
itemId = this.itemId,
|
||||
title = this.title,
|
||||
details = this.details,
|
||||
dueAt = this.dueAt,
|
||||
completedAt = this.completedAt,
|
||||
createdAt = this.createdAt,
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('MaintenanceEntryDto.toDomain')]
|
||||
|
||||
// [ENTITY: Function('GroupStatisticsDto.toDomain')]
|
||||
// [RELATION: Function('GroupStatisticsDto.toDomain')] -> [RETURNS] -> [DataClass('DomainGroupStatistics')]
|
||||
fun GroupStatisticsDto.toDomain(): DomainGroupStatistics {
|
||||
return DomainGroupStatistics(
|
||||
items = this.totalItems,
|
||||
labels = this.totalLabels,
|
||||
locations = this.totalLocations,
|
||||
totalValue = this.totalItemPrice
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('GroupStatisticsDto.toDomain')]
|
||||
|
||||
// [ENTITY: Function('LabelSummaryDto.toDomain')]
|
||||
// [RELATION: Function('LabelSummaryDto.toDomain')] -> [RETURNS] -> [DataClass('DomainLabelSummary')]
|
||||
fun LabelSummaryDto.toDomain(): DomainLabelSummary {
|
||||
return DomainLabelSummary(
|
||||
id = this.id,
|
||||
name = this.name,
|
||||
color = this.color ?: ""
|
||||
)
|
||||
}
|
||||
// [END_ENTITY: Function('LabelSummaryDto.toDomain')]
|
||||
|
||||
|
||||
// [END_FILE_DtoToDomain.kt]
|
||||
@@ -1,6 +1,5 @@
|
||||
// [PACKAGE] com.homebox.lens.data.repository
|
||||
// [FILE] AuthRepositoryImpl.kt
|
||||
// [SEMANTICS] data_implementation, authentication, repository
|
||||
// [SEMANTICS] data, repository, authentication
|
||||
|
||||
package com.homebox.lens.data.repository
|
||||
|
||||
@@ -8,8 +7,9 @@ package com.homebox.lens.data.repository
|
||||
import android.content.SharedPreferences
|
||||
import com.homebox.lens.data.api.HomeboxApiService
|
||||
import com.homebox.lens.data.api.dto.LoginFormDto
|
||||
import com.homebox.lens.data.api.mapper.toDomain
|
||||
import com.homebox.lens.data.api.dto.toDomain
|
||||
import com.homebox.lens.domain.model.Credentials
|
||||
import com.homebox.lens.domain.model.Result
|
||||
import com.homebox.lens.domain.model.TokenResponse
|
||||
import com.homebox.lens.domain.repository.AuthRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -30,10 +30,10 @@ import javax.inject.Inject
|
||||
// [RELATION: Class('AuthRepositoryImpl')] -> [DEPENDS_ON] -> [Framework('OkHttpClient')]
|
||||
// [RELATION: Class('AuthRepositoryImpl')] -> [DEPENDS_ON] -> [Framework('MoshiConverterFactory')]
|
||||
/**
|
||||
* @summary Реализация репозитория для управления аутентификацией.
|
||||
* @param encryptedPrefs Защищенное хранилище для токена.
|
||||
* @param okHttpClient Общий OkHttp клиент для переиспользования.
|
||||
* @param moshiConverterFactory Общий конвертер Moshi для переиспользования.
|
||||
* @summary Implementation of the repository for managing authentication.
|
||||
* @param encryptedPrefs The secure storage for the token.
|
||||
* @param okHttpClient The shared OkHttp client for reuse.
|
||||
* @param moshiConverterFactory The shared Moshi converter for reuse.
|
||||
*/
|
||||
class AuthRepositoryImpl @Inject constructor(
|
||||
private val encryptedPrefs: SharedPreferences,
|
||||
@@ -47,16 +47,16 @@ class AuthRepositoryImpl @Inject constructor(
|
||||
|
||||
// [ENTITY: Function('login')]
|
||||
/**
|
||||
* @summary Реализует вход пользователя. Создает временный API сервис для выполнения запроса
|
||||
* на указанный пользователем URL сервера.
|
||||
* @param credentials Учетные данные пользователя, включая URL сервера.
|
||||
* @return [Result] с доменной моделью [TokenResponse] при успехе или [Exception] при ошибке.
|
||||
* @summary Implements user login. Creates a temporary API service to execute a request
|
||||
* to the server URL specified by the user.
|
||||
* @param credentials The user's credentials, including the server URL.
|
||||
* @return A [Result] with a [TokenResponse] domain model on success or an [Exception] on failure.
|
||||
*/
|
||||
override suspend fun login(credentials: Credentials): Result<TokenResponse> {
|
||||
require(credentials.serverUrl.isNotBlank()) { "Server URL cannot be blank." }
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
try {
|
||||
Timber.d("[DEBUG][ACTION][creating_retrofit_client] Creating temporary Retrofit client for URL: ${credentials.serverUrl}")
|
||||
val tempApiService = Retrofit.Builder()
|
||||
.baseUrl(credentials.serverUrl)
|
||||
@@ -70,7 +70,10 @@ class AuthRepositoryImpl @Inject constructor(
|
||||
val tokenResponseDto = tempApiService.login(loginForm)
|
||||
|
||||
Timber.d("[DEBUG][ACTION][mapping_to_domain] Mapping token response to domain model.")
|
||||
tokenResponseDto.toDomain()
|
||||
Result.Success(tokenResponseDto.toDomain())
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "[ERROR][FAILURE][login_failed] Login failed.")
|
||||
Result.Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// [PACKAGE] com.homebox.lens.data.repository
|
||||
// [FILE] CredentialsRepositoryImpl.kt
|
||||
// [SEMANTICS] data, repository, credentials, security
|
||||
package com.homebox.lens.data.repository
|
||||
@@ -20,10 +19,10 @@ import javax.inject.Inject
|
||||
// [RELATION: Class('CredentialsRepositoryImpl')] -> [IMPLEMENTS] -> [Interface('CredentialsRepository')]
|
||||
// [RELATION: Class('CredentialsRepositoryImpl')] -> [DEPENDS_ON] -> [Framework('SharedPreferences')]
|
||||
/**
|
||||
* @summary Реализует репозиторий для управления учетными данными пользователя.
|
||||
* @description Взаимодействует с зашифрованными SharedPreferences для сохранения и извлечения данных.
|
||||
* @param encryptedPrefs Зашифрованное хранилище ключ-значение, предоставляемое Hilt.
|
||||
* @invariant Состояние этого репозитория полностью зависит от содержимого `encryptedPrefs`.
|
||||
* @summary Implements the repository for managing user credentials.
|
||||
* @description Interacts with encrypted SharedPreferences to save and retrieve data.
|
||||
* @param encryptedPrefs The encrypted key-value store provided by Hilt.
|
||||
* @invariant The state of this repository is entirely dependent on the contents of `encryptedPrefs`.
|
||||
*/
|
||||
class CredentialsRepositoryImpl @Inject constructor(
|
||||
private val encryptedPrefs: SharedPreferences
|
||||
@@ -38,9 +37,9 @@ class CredentialsRepositoryImpl @Inject constructor(
|
||||
|
||||
// [ENTITY: Function('saveCredentials')]
|
||||
/**
|
||||
* @summary Сохраняет основные учетные данные пользователя.
|
||||
* @param credentials Объект с учетными данными для сохранения.
|
||||
* @sideeffect Перезаписывает существующие учетные данные в SharedPreferences.
|
||||
* @summary Saves the user's primary credentials.
|
||||
* @param credentials The credentials object to save.
|
||||
* @sideeffect Overwrites existing credentials in SharedPreferences.
|
||||
*/
|
||||
override suspend fun saveCredentials(credentials: Credentials) {
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -56,8 +55,8 @@ class CredentialsRepositoryImpl @Inject constructor(
|
||||
|
||||
// [ENTITY: Function('getCredentials')]
|
||||
/**
|
||||
* @summary Извлекает сохраненные учетные данные пользователя в виде потока.
|
||||
* @return Flow, который эммитит объект [Credentials] или null, если данные отсутствуют.
|
||||
* @summary Retrieves the saved user credentials as a Flow.
|
||||
* @return A Flow that emits a [Credentials] object or null if no data is present.
|
||||
*/
|
||||
override fun getCredentials(): Flow<Credentials?> = flow {
|
||||
Timber.d("[DEBUG][ACTION][getting_credentials] Getting user credentials.")
|
||||
@@ -77,9 +76,9 @@ class CredentialsRepositoryImpl @Inject constructor(
|
||||
|
||||
// [ENTITY: Function('saveToken')]
|
||||
/**
|
||||
* @summary Сохраняет токен авторизации.
|
||||
* @param token Токен для сохранения.
|
||||
* @sideeffect Перезаписывает существующий токен в SharedPreferences.
|
||||
* @summary Saves the authorization token.
|
||||
* @param token The token to save.
|
||||
* @sideeffect Overwrites the existing token in SharedPreferences.
|
||||
*/
|
||||
override suspend fun saveToken(token: String) {
|
||||
withContext(Dispatchers.IO) {
|
||||
@@ -93,16 +92,51 @@ class CredentialsRepositoryImpl @Inject constructor(
|
||||
|
||||
// [ENTITY: Function('getToken')]
|
||||
/**
|
||||
* @summary Извлекает сохраненный токен авторизации.
|
||||
* @return Строка с токеном или null, если он не найден.
|
||||
* @summary Retrieves the saved authorization token.
|
||||
* @return A string with the token or null if it is not found.
|
||||
*/
|
||||
override suspend fun getToken(): String? {
|
||||
return withContext(Dispatchers.IO) {
|
||||
Timber.d("[DEBUG][ACTION][getting_token] Getting auth token.")
|
||||
encryptedPrefs.getString(KEY_AUTH_TOKEN, null)
|
||||
val token = encryptedPrefs.getString(KEY_AUTH_TOKEN, null)
|
||||
if (token != null) {
|
||||
Timber.i("[INFO][ACTION][token_retrieved] Auth token retrieved successfully.")
|
||||
} else {
|
||||
Timber.w("[WARN][FALLBACK][no_token_found] No auth token found.")
|
||||
}
|
||||
token
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('getToken')]
|
||||
|
||||
// [ENTITY: Function('clearAllCredentials')]
|
||||
/**
|
||||
* @summary Clears all saved credentials and tokens.
|
||||
* @sideeffect Removes all records related to credentials from SharedPreferences.
|
||||
*/
|
||||
override suspend fun clearAllCredentials() {
|
||||
withContext(Dispatchers.IO) {
|
||||
Timber.i("[INFO][ACTION][clearing_all_credentials] Clearing all saved credentials and tokens.")
|
||||
encryptedPrefs.edit()
|
||||
.remove(KEY_SERVER_URL)
|
||||
.remove(KEY_USERNAME)
|
||||
.remove(KEY_PASSWORD)
|
||||
.remove(KEY_AUTH_TOKEN)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('clearAllCredentials')]
|
||||
|
||||
// [ENTITY: Function('areCredentialsSavedSync')]
|
||||
/**
|
||||
* @summary Synchronously checks if user credentials are saved.
|
||||
* @return true if all essential credentials (URL, username, password) are present, false otherwise.
|
||||
*/
|
||||
override fun areCredentialsSavedSync(): Boolean {
|
||||
return encryptedPrefs.contains(KEY_SERVER_URL) &&
|
||||
encryptedPrefs.contains(KEY_USERNAME) &&
|
||||
encryptedPrefs.contains(KEY_PASSWORD)
|
||||
}
|
||||
// [END_ENTITY: Function('areCredentialsSavedSync')]
|
||||
}
|
||||
// [END_ENTITY: Class('CredentialsRepositoryImpl')]
|
||||
// [END_FILE_CredentialsRepositoryImpl.kt]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user