Fix: Handle missing 'color', 'isArchived' and 'value' fields in DTOs and mappers to prevent JsonDataException
This commit is contained in:
55
ui/build.gradle.kts
Normal file
55
ui/build.gradle.kts
Normal file
@@ -0,0 +1,55 @@
|
||||
// [FILE] ui/build.gradle.kts
|
||||
// [SEMANTICS] build, dependencies, ui
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("kotlin-kapt") // Add kapt for Hilt
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.homebox.lens.ui"
|
||||
compileSdk = Versions.compileSdk
|
||||
|
||||
defaultConfig {
|
||||
minSdk = Versions.minSdk
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "17"
|
||||
}
|
||||
buildFeatures {
|
||||
compose = true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = Versions.composeCompiler
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// AndroidX
|
||||
implementation(Libs.coreKtx)
|
||||
implementation(Libs.lifecycleRuntime) // For androidx.lifecycle:lifecycle-runtime-ktx
|
||||
|
||||
// Compose
|
||||
implementation(platform(Libs.composeBom))
|
||||
implementation(Libs.composeUi)
|
||||
implementation(Libs.composeUiGraphics)
|
||||
implementation(Libs.composeUiToolingPreview)
|
||||
implementation(Libs.composeMaterial3)
|
||||
implementation(Libs.activityCompose)
|
||||
implementation("androidx.compose.material:material-icons-extended-android:1.6.8")
|
||||
implementation(Libs.navigationCompose) // For NavigationActions
|
||||
implementation(Libs.hiltNavigationCompose) // For Hilt with Compose
|
||||
|
||||
// Hilt
|
||||
implementation(Libs.hiltAndroid)
|
||||
kapt(Libs.hiltCompiler)
|
||||
|
||||
// Other
|
||||
implementation(Libs.timber)
|
||||
}
|
||||
// [END_FILE_ui/build.gradle.kts]
|
||||
@@ -0,0 +1,124 @@
|
||||
// [FILE] NavigationActions.kt
|
||||
// [SEMANTICS] ui, navigation, actions
|
||||
package com.homebox.lens.navigation
|
||||
|
||||
// [IMPORTS]
|
||||
import androidx.navigation.NavHostController
|
||||
import timber.log.Timber
|
||||
import com.homebox.lens.ui.navigation.Screen
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: Class('NavigationActions')]
|
||||
// [RELATION: Class('NavigationActions')] -> [DEPENDS_ON] -> [Framework('NavHostController')]
|
||||
/**
|
||||
* @summary Wrapper class over NavHostController to provide typed navigation actions.
|
||||
* @param navController The Jetpack Navigation controller.
|
||||
* @invariant All navigation actions must use the provided navController.
|
||||
*/
|
||||
class NavigationActions(private val navController: NavHostController) {
|
||||
|
||||
// [ENTITY: Function('navigateToDashboard')]
|
||||
/**
|
||||
* @summary Navigation to the main screen.
|
||||
* @sideeffect Clears the back stack up to the main screen to avoid cycles.
|
||||
*/
|
||||
fun navigateToDashboard() {
|
||||
Timber.i("[INFO][ACTION][navigate_to_dashboard] Navigating to Dashboard.")
|
||||
navController.navigate(Screen.Dashboard.route) {
|
||||
popUpTo(navController.graph.startDestinationId)
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('navigateToDashboard')]
|
||||
|
||||
// [ENTITY: Function('navigateToLocations')]
|
||||
fun navigateToLocations() {
|
||||
Timber.i("[INFO][ACTION][navigate_to_locations] Navigating to Locations.")
|
||||
navController.navigate(Screen.LocationsList.route) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('navigateToLocations')]
|
||||
|
||||
// [ENTITY: Function('navigateToLabels')]
|
||||
fun navigateToLabels() {
|
||||
Timber.i("[INFO][ACTION][navigate_to_labels] Navigating to Labels.")
|
||||
navController.navigate(Screen.LabelsList.route) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('navigateToLabels')]
|
||||
|
||||
// [ENTITY: Function('navigateToLabelEdit')]
|
||||
fun navigateToLabelEdit(labelId: String? = null) {
|
||||
Timber.i("[INFO][ACTION][navigate_to_label_edit] Navigating to Label Edit with ID: %s", labelId)
|
||||
navController.navigate(Screen.LabelEdit.createRoute(labelId))
|
||||
}
|
||||
// [END_ENTITY: Function('navigateToLabelEdit')]
|
||||
|
||||
// [ENTITY: Function('navigateToSearch')]
|
||||
fun navigateToSearch() {
|
||||
Timber.i("[INFO][ACTION][navigate_to_search] Navigating to Search.")
|
||||
navController.navigate(Screen.Search.route) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('navigateToSearch')]
|
||||
|
||||
// [ENTITY: Function('navigateToInventory')]
|
||||
fun navigateToInventory() {
|
||||
Timber.i("[INFO][ACTION][navigate_to_inventory] Navigating to Inventory.")
|
||||
navController.navigate(Screen.Inventory.route) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('navigateToInventory')]
|
||||
|
||||
// [ENTITY: Function('navigateToInventoryListWithLabel')]
|
||||
fun navigateToInventoryListWithLabel(labelId: String) {
|
||||
Timber.i("[INFO][ACTION][navigate_to_inventory_with_label] Navigating to Inventory with label: %s", labelId)
|
||||
val route = Screen.Inventory.withFilter("label", labelId)
|
||||
navController.navigate(route)
|
||||
}
|
||||
// [END_ENTITY: Function('navigateToInventoryListWithLabel')]
|
||||
|
||||
// [ENTITY: Function('navigateToInventoryListWithLocation')]
|
||||
fun navigateToInventoryListWithLocation(locationId: String) {
|
||||
Timber.i("[INFO][ACTION][navigate_to_inventory_with_location] Navigating to Inventory with location: %s", locationId)
|
||||
val route = Screen.Inventory.withFilter("location", locationId)
|
||||
navController.navigate(route)
|
||||
}
|
||||
// [END_ENTITY: Function('navigateToInventoryListWithLocation')]
|
||||
|
||||
// [ENTITY: Function('navigateToCreateItem')]
|
||||
fun navigateToCreateItem() {
|
||||
Timber.i("[INFO][ACTION][navigate_to_create_item] Navigating to Create Item.")
|
||||
navController.navigate(Screen.ItemEdit.createRoute())
|
||||
}
|
||||
// [END_ENTITY: Function('navigateToCreateItem')]
|
||||
|
||||
// [ENTITY: Function('navigateToItemDetails')]
|
||||
fun navigateToItemDetails(itemId: String) {
|
||||
Timber.i("[INFO][ACTION][navigate_to_item_details] Navigating to Item Details with ID: %s", itemId)
|
||||
navController.navigate(Screen.ItemDetails.createRoute(itemId))
|
||||
}
|
||||
// [END_ENTITY: Function('navigateToItemDetails')]
|
||||
|
||||
// [ENTITY: Function('navigateToLogout')]
|
||||
fun navigateToLogout() {
|
||||
Timber.i("[INFO][ACTION][navigate_to_logout] Navigating to Logout.")
|
||||
navController.navigate(Screen.Setup.route) {
|
||||
popUpTo(Screen.Dashboard.route) { inclusive = true }
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('navigateToLogout')]
|
||||
|
||||
// [ENTITY: Function('navigateBack')]
|
||||
fun navigateBack() {
|
||||
Timber.i("[INFO][ACTION][navigate_back] Navigating back.")
|
||||
navController.popBackStack()
|
||||
}
|
||||
// [END_ENTITY: Function('navigateBack')]
|
||||
}
|
||||
// [END_ENTITY: Class('NavigationActions')]
|
||||
// [END_FILE_NavigationActions.kt]
|
||||
130
ui/src/main/java/com/homebox/lens/navigation/Screen.kt
Normal file
130
ui/src/main/java/com/homebox/lens/navigation/Screen.kt
Normal file
@@ -0,0 +1,130 @@
|
||||
// [FILE] Screen.kt
|
||||
// [SEMANTICS] ui, navigation, routes
|
||||
package com.homebox.lens.ui.navigation
|
||||
|
||||
// [ENTITY: SealedClass('Screen')]
|
||||
/**
|
||||
* @summary Sealed class for defining navigation routes in the application.
|
||||
* @description Provides type safety during navigation.
|
||||
* @param route The string identifier of the route.
|
||||
*/
|
||||
sealed class Screen(val route: String) {
|
||||
// [ENTITY: Object('Splash')]
|
||||
data object Splash : Screen("splash_screen")
|
||||
// [END_ENTITY: Object('Splash')]
|
||||
|
||||
// [ENTITY: Object('Setup')]
|
||||
data object Setup : Screen("setup_screen")
|
||||
// [END_ENTITY: Object('Setup')]
|
||||
|
||||
// [ENTITY: Object('Dashboard')]
|
||||
data object Dashboard : Screen("dashboard_screen")
|
||||
// [END_ENTITY: Object('Dashboard')]
|
||||
|
||||
// [ENTITY: Object('Inventory')]
|
||||
data object Inventory : Screen("inventory_screen") {
|
||||
// [ENTITY: Function('withFilter')]
|
||||
/**
|
||||
* @summary Creates a route for the inventory screen with a filter parameter.
|
||||
* @param key The filter key (e.g., "label" or "location").
|
||||
* @param value The filter value (e.g., the ID of the label or location).
|
||||
* @return A string of the full route with a query parameter.
|
||||
* @throws IllegalArgumentException if the key or value is blank.
|
||||
*/
|
||||
fun withFilter(key: String, value: String): String {
|
||||
require(key.isNotBlank()) { "Filter key cannot be blank." }
|
||||
require(value.isNotBlank()) { "Filter value cannot be blank." }
|
||||
val constructedRoute = "inventory_screen?$key=$value"
|
||||
check(constructedRoute.contains("?$key=$value")) { "Route must contain the filter query." }
|
||||
return constructedRoute
|
||||
}
|
||||
// [END_ENTITY: Function('withFilter')]
|
||||
}
|
||||
// [END_ENTITY: Object('Inventory')]
|
||||
|
||||
// [ENTITY: Object('ItemDetails')]
|
||||
data object ItemDetails : Screen("item_details_screen/{itemId}") {
|
||||
// [ENTITY: Function('createRoute')]
|
||||
/**
|
||||
* @summary Creates a route for the item details screen with the specified ID.
|
||||
* @param itemId The ID of the item to display.
|
||||
* @return A string of the full route.
|
||||
* @throws IllegalArgumentException if itemId is blank.
|
||||
*/
|
||||
fun createRoute(itemId: String): String {
|
||||
require(itemId.isNotBlank()) { "itemId не может быть пустым." }
|
||||
val route = "item_details_screen/$itemId"
|
||||
check(route.endsWith(itemId)) { "Маршрут должен заканчиваться на itemId." }
|
||||
return route
|
||||
}
|
||||
// [END_ENTITY: Function('createRoute')]
|
||||
}
|
||||
// [END_ENTITY: Object('ItemDetails')]
|
||||
|
||||
// [ENTITY: Object('ItemEdit')]
|
||||
data object ItemEdit : Screen("item_edit_screen?itemId={itemId}") {
|
||||
// [ENTITY: Function('createRoute')]
|
||||
/**
|
||||
* @summary Creates a route for the item edit screen with the specified ID.
|
||||
* @param itemId The ID of the item to edit. Null if a new item is being created.
|
||||
* @return A string of the full route.
|
||||
*/
|
||||
fun createRoute(itemId: String? = null): String {
|
||||
return itemId?.let { "item_edit_screen?itemId=$it" } ?: "item_edit_screen"
|
||||
}
|
||||
// [END_ENTITY: Function('createRoute')]
|
||||
}
|
||||
// [END_ENTITY: Object('ItemEdit')]
|
||||
|
||||
// [ENTITY: Object('LabelsList')]
|
||||
data object LabelsList : Screen("labels_list_screen")
|
||||
// [END_ENTITY: Object('LabelsList')]
|
||||
|
||||
// [ENTITY: Object('LabelEdit')]
|
||||
data object LabelEdit : Screen("label_edit_screen?labelId={labelId}") {
|
||||
// [ENTITY: Function('createRoute')]
|
||||
/**
|
||||
* @summary Creates a route for the label edit screen with the specified ID.
|
||||
* @param labelId The ID of the label to edit. Null if a new label is being created.
|
||||
* @return A string of the full route.
|
||||
*/
|
||||
fun createRoute(labelId: String? = null): String {
|
||||
return labelId?.let { "label_edit_screen?labelId=$it" } ?: "label_edit_screen"
|
||||
}
|
||||
// [END_ENTITY: Function('createRoute')]
|
||||
}
|
||||
// [END_ENTITY: Object('LabelEdit')]
|
||||
|
||||
// [ENTITY: Object('LocationsList')]
|
||||
data object LocationsList : Screen("locations_list_screen")
|
||||
// [END_ENTITY: Object('LocationsList')]
|
||||
|
||||
// [ENTITY: Object('LocationEdit')]
|
||||
data object LocationEdit : Screen("location_edit_screen/{locationId}") {
|
||||
// [ENTITY: Function('createRoute')]
|
||||
/**
|
||||
* @summary Creates a route for the location edit screen with the specified ID.
|
||||
* @param locationId The ID of the location to edit.
|
||||
* @return A string of the full route.
|
||||
* @throws IllegalArgumentException if locationId is blank.
|
||||
*/
|
||||
fun createRoute(locationId: String): String {
|
||||
require(locationId.isNotBlank()) { "locationId не может быть пустым." }
|
||||
val route = "location_edit_screen/$locationId"
|
||||
check(route.endsWith(locationId)) { "Маршрут должен заканчиваться на locationId." }
|
||||
return route
|
||||
}
|
||||
// [END_ENTITY: Function('createRoute')]
|
||||
}
|
||||
// [END_ENTITY: Object('LocationEdit')]
|
||||
|
||||
// [ENTITY: Object('Search')]
|
||||
data object Search : Screen("search_screen")
|
||||
// [END_ENTITY: Object('Search')]
|
||||
|
||||
// [ENTITY: Object('Settings')]
|
||||
data object Settings : Screen("settings_screen")
|
||||
// [END_ENTITY: Object('Settings')]
|
||||
}
|
||||
// [END_ENTITY: SealedClass('Screen')]
|
||||
// [END_FILE_Screen.kt]
|
||||
113
ui/src/main/java/com/homebox/lens/ui/common/AppDrawer.kt
Normal file
113
ui/src/main/java/com/homebox/lens/ui/common/AppDrawer.kt
Normal file
@@ -0,0 +1,113 @@
|
||||
// [FILE] AppDrawer.kt
|
||||
// [SEMANTICS] ui, common, navigation
|
||||
package com.homebox.lens.ui.common
|
||||
|
||||
// [IMPORTS]
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ModalDrawerSheet
|
||||
import androidx.compose.material3.NavigationDrawerItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.homebox.lens.ui.R
|
||||
import com.homebox.lens.navigation.NavigationActions
|
||||
import com.homebox.lens.ui.navigation.Screen
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: Function('AppDrawerContent')]
|
||||
// [RELATION: Function('AppDrawerContent')] -> [DEPENDS_ON] -> [Class('NavigationActions')]
|
||||
/**
|
||||
* @summary Content for the side navigation menu (Drawer).
|
||||
* @param currentRoute The current route to highlight the active item.
|
||||
* @param navigationActions The object with navigation actions.
|
||||
* @param onCloseDrawer Lambda to close the side menu.
|
||||
*/
|
||||
@Composable
|
||||
internal fun AppDrawerContent(
|
||||
currentRoute: String?,
|
||||
navigationActions: NavigationActions,
|
||||
onCloseDrawer: () -> Unit
|
||||
) {
|
||||
ModalDrawerSheet {
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
navigationActions.navigateToCreateItem()
|
||||
onCloseDrawer()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = null, modifier = Modifier.size(18.dp))
|
||||
Spacer(Modifier.width(8.dp))
|
||||
Text(stringResource(id = R.string.create))
|
||||
}
|
||||
Spacer(Modifier.height(12.dp))
|
||||
Divider()
|
||||
NavigationDrawerItem(
|
||||
label = { Text(stringResource(id = R.string.dashboard_title)) },
|
||||
selected = currentRoute == Screen.Dashboard.route,
|
||||
onClick = {
|
||||
navigationActions.navigateToDashboard()
|
||||
onCloseDrawer()
|
||||
}
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
label = { Text(stringResource(id = R.string.inventory_list_title)) },
|
||||
selected = currentRoute == Screen.Inventory.route,
|
||||
onClick = {
|
||||
navigationActions.navigateToInventory()
|
||||
onCloseDrawer()
|
||||
}
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
label = { Text(stringResource(id = R.string.nav_locations)) },
|
||||
selected = currentRoute == Screen.LocationsList.route,
|
||||
onClick = {
|
||||
navigationActions.navigateToLocations()
|
||||
onCloseDrawer()
|
||||
}
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
label = { Text(stringResource(id = R.string.nav_labels)) },
|
||||
selected = currentRoute == Screen.LabelsList.route,
|
||||
onClick = {
|
||||
navigationActions.navigateToLabels()
|
||||
onCloseDrawer()
|
||||
}
|
||||
)
|
||||
NavigationDrawerItem(
|
||||
label = { Text(stringResource(id = R.string.search)) },
|
||||
selected = currentRoute == Screen.Search.route,
|
||||
onClick = {
|
||||
navigationActions.navigateToSearch()
|
||||
onCloseDrawer()
|
||||
}
|
||||
)
|
||||
// [AI_NOTE]: Add Profile and Tools items
|
||||
Divider()
|
||||
NavigationDrawerItem(
|
||||
label = { Text(stringResource(id = R.string.logout)) },
|
||||
selected = false,
|
||||
onClick = {
|
||||
navigationActions.navigateToLogout()
|
||||
onCloseDrawer()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('AppDrawerContent')]
|
||||
// [END_FILE_AppDrawer.kt]
|
||||
74
ui/src/main/java/com/homebox/lens/ui/common/MainScaffold.kt
Normal file
74
ui/src/main/java/com/homebox/lens/ui/common/MainScaffold.kt
Normal file
@@ -0,0 +1,74 @@
|
||||
// [FILE] MainScaffold.kt
|
||||
// [SEMANTICS] ui, common, scaffold
|
||||
package com.homebox.lens.ui.common
|
||||
|
||||
// [IMPORTS]
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.homebox.lens.ui.R
|
||||
import com.homebox.lens.navigation.NavigationActions
|
||||
import kotlinx.coroutines.launch
|
||||
// [END_IMPORTS]
|
||||
|
||||
// [ENTITY: Function('MainScaffold')]
|
||||
// [RELATION: Function('MainScaffold')] -> [DEPENDS_ON] -> [Class('NavigationActions')]
|
||||
// [RELATION: Function('MainScaffold')] -> [CALLS] -> [Function('AppDrawerContent')]
|
||||
/**
|
||||
* @summary A common wrapper for screens that includes a Scaffold and Navigation Drawer.
|
||||
* @param topBarTitle The title for the TopAppBar.
|
||||
* @param currentRoute The current route to highlight the active item in the Drawer.
|
||||
* @param navigationActions The object with navigation actions.
|
||||
* @param topBarActions A Composable function to display actions (icons) in the TopAppBar.
|
||||
* @param content The main content of the screen to be displayed inside the Scaffold.
|
||||
* @sideeffect Manages the state (open/closed) of the side menu (ModalNavigationDrawer).
|
||||
* @invariant The TopAppBar is always displayed with a menu icon.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MainScaffold(
|
||||
topBarTitle: String,
|
||||
currentRoute: String?,
|
||||
navigationActions: NavigationActions,
|
||||
topBarActions: @Composable () -> Unit = {},
|
||||
content: @Composable (PaddingValues) -> Unit
|
||||
) {
|
||||
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
ModalNavigationDrawer(
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
AppDrawerContent(
|
||||
currentRoute = currentRoute,
|
||||
navigationActions = navigationActions,
|
||||
onCloseDrawer = { scope.launch { drawerState.close() } }
|
||||
)
|
||||
}
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(topBarTitle) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { scope.launch { drawerState.open() } }) {
|
||||
Icon(
|
||||
Icons.Default.Menu,
|
||||
contentDescription = stringResource(id = R.string.cd_open_navigation_drawer)
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = { topBarActions() }
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
content(paddingValues)
|
||||
}
|
||||
}
|
||||
}
|
||||
// [END_ENTITY: Function('MainScaffold')]
|
||||
// [END_FILE_MainScaffold.kt]
|
||||
10
ui/src/main/res/drawable/ic_placeholder.xml
Normal file
10
ui/src/main/res/drawable/ic_placeholder.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
|
||||
</vector>
|
||||
19
ui/src/main/res/values/strings.xml
Normal file
19
ui/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<resources>
|
||||
<!-- Common -->
|
||||
<string name="create">Создать</string>
|
||||
<string name="search">Поиск</string>
|
||||
<string name="logout">Выйти</string>
|
||||
|
||||
<!-- Content Descriptions -->
|
||||
<string name="cd_open_navigation_drawer">Открыть боковое меню</string>
|
||||
|
||||
<!-- Dashboard Screen -->
|
||||
<string name="dashboard_title">Главная</string>
|
||||
|
||||
<!-- Navigation -->
|
||||
<string name="nav_locations">Локации</string>
|
||||
<string name="nav_labels">Метки</string>
|
||||
|
||||
<!-- Screen Titles -->
|
||||
<string name="inventory_list_title">Инвентарь</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user