feat: Extract hardcoded strings to string resources

Extracted several hardcoded strings in the UI layer to string resources for improved localization and maintainability.
This commit is contained in:
2025-10-13 19:25:32 +02:00
parent af4cf289f9
commit 2b38853ad2
9 changed files with 77 additions and 40 deletions

View File

@@ -4,7 +4,6 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@@ -33,11 +32,8 @@ import androidx.compose.material.icons.automirrored.filled.PlaylistAddCheck
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.ExposurePlus1
import androidx.compose.material.icons.filled.LooksOne
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Visibility
import androidx.compose.material.icons.filled.VisibilityOff
import androidx.compose.material.icons.filled.MoreVert
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
@@ -74,8 +70,6 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewmodel.compose.viewModel
import de.lxtools.noteshop.ui.notes.NoteDetailScreen
import de.lxtools.noteshop.ui.notes.NotesScreen
@@ -86,7 +80,6 @@ import de.lxtools.noteshop.ui.shopping.ShoppingListsViewModel
import de.lxtools.noteshop.ui.theme.NoteshopTheme
import kotlinx.coroutines.launch
import android.os.Parcelable
import de.lxtools.noteshop.data.Note
import de.lxtools.noteshop.ui.notes.NoteInputDialog
import kotlinx.parcelize.Parcelize
@@ -136,7 +129,9 @@ fun AppShell(
// Collect state from ViewModel
val searchQuery by shoppingListsViewModel.searchQuery.collectAsState()
val notesSearchQuery by notesViewModel.searchQuery.collectAsState()
val isReorderMode by shoppingListsViewModel.isReorderMode.collectAsState()
val isNotesReorderMode by notesViewModel.isReorderMode.collectAsState()
val newItemName by shoppingListsViewModel.newItemName.collectAsState()
val showCompletedItems by shoppingListsViewModel.showCompletedItems.collectAsState()
val shoppingListWithItems by shoppingListsViewModel.getShoppingListWithItemsStream(selectedListId ?: 0)
@@ -197,11 +192,12 @@ fun AppShell(
)
}
val standardListName = stringResource(R.string.standard_list_name)
NavigationDrawerItem(
label = { Text(stringResource(id = R.string.load_standard_list)) },
selected = false,
onClick = {
shoppingListsViewModel.createStandardList()
shoppingListsViewModel.createStandardList(standardListName)
scope.launch { drawerState.close() }
}
)
@@ -221,8 +217,11 @@ fun AppShell(
TopAppBar(
title = { Text(if (currentScreen == Screen.ShoppingListDetail && isDetailSearchActive) stringResource(R.string.search) else topBarTitle) },
navigationIcon = {
if (isReorderMode) {
IconButton(onClick = { shoppingListsViewModel.disableReorderMode() }) {
if (isReorderMode || isNotesReorderMode) {
IconButton(onClick = {
if (isReorderMode) shoppingListsViewModel.disableReorderMode()
if (isNotesReorderMode) notesViewModel.disableReorderMode()
}) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
}
} else {
@@ -266,7 +265,7 @@ fun AppShell(
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.add_shopping_list))
}
}
if (currentScreen == Screen.Notes && !isReorderMode) {
if (currentScreen == Screen.Notes && !isNotesReorderMode) {
IconButton(onClick = {
notesViewModel.resetNoteDetails()
showNoteDialog = true
@@ -304,17 +303,32 @@ fun AppShell(
)
)
if (currentScreen == Screen.ShoppingLists) {
OutlinedTextField(
value = searchQuery,
onValueChange = shoppingListsViewModel::updateSearchQuery,
label = { Text(stringResource(R.string.search_list_hint)) },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
when (currentScreen) {
Screen.ShoppingLists -> {
OutlinedTextField(
value = searchQuery,
onValueChange = shoppingListsViewModel::updateSearchQuery,
label = { Text(stringResource(R.string.search_list_hint)) },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
}
Screen.Notes -> {
OutlinedTextField(
value = notesSearchQuery,
onValueChange = notesViewModel::updateSearchQuery,
label = { Text(stringResource(R.string.search_notes_hint)) },
leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
)
}
else -> {}
}
Spacer(modifier = Modifier.height(16.dp))
}
},
bottomBar = {

View File

@@ -13,7 +13,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable

View File

@@ -123,7 +123,7 @@ fun NoteCard(
) {
if (isReorderMode) {
IconButton(modifier = with(scope) { Modifier.draggableHandle() }, onClick = {}) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
Icon(Icons.Rounded.DragHandle, contentDescription = stringResource(R.string.reorder))
}
}
Text(
@@ -139,7 +139,7 @@ fun NoteCard(
}
if (isReorderMode) {
IconButton(modifier = with(scope) { Modifier.draggableHandle() }, onClick = {}) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
Icon(Icons.Rounded.DragHandle, contentDescription = stringResource(R.string.reorder))
}
}
}

View File

@@ -8,12 +8,15 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
class NotesViewModel(private val noteshopRepository: NoteshopRepository) : ViewModel() {
private val _searchQuery = MutableStateFlow("")
val searchQuery: StateFlow<String> = _searchQuery.asStateFlow()
private val _isReorderMode = MutableStateFlow(false)
val isReorderMode: StateFlow<Boolean> = _isReorderMode.asStateFlow()
@@ -25,13 +28,29 @@ class NotesViewModel(private val noteshopRepository: NoteshopRepository) : ViewM
_isReorderMode.value = false
}
val uiState: StateFlow<NotesUiState> = noteshopRepository.getAllNotesStream().map { notes ->
NotesUiState(notes.sortedBy { it.displayOrder })
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
initialValue = NotesUiState()
)
fun updateSearchQuery(query: String) {
_searchQuery.value = query
}
val uiState: StateFlow<NotesUiState> = combine(
noteshopRepository.getAllNotesStream(),
searchQuery
) { allNotes, query ->
val filteredNotes = if (query.isBlank()) {
allNotes
} else {
allNotes.filter {
it.title.contains(query, ignoreCase = true) ||
it.content.contains(query, ignoreCase = true)
}
}
NotesUiState(filteredNotes.sortedBy { it.displayOrder })
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
initialValue = NotesUiState()
)
private val _noteDetails = MutableStateFlow(NoteDetails())
val noteDetails: StateFlow<NoteDetails> = _noteDetails.asStateFlow()
@@ -106,4 +125,4 @@ data class NoteDetails(
fun isValid(): Boolean {
return title.isNotBlank()
}
}
}

View File

@@ -213,7 +213,7 @@ fun ShoppingListDetailScreen(
) {
if (isReorderMode) {
IconButton(modifier = Modifier.draggableHandle(), onClick = {}) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
Icon(Icons.Rounded.DragHandle, contentDescription = stringResource(R.string.reorder))
}
}
Text(
@@ -227,7 +227,7 @@ fun ShoppingListDetailScreen(
viewModel.onSelectItem(item)
showMenu = true
}) {
Icon(Icons.Default.MoreVert, contentDescription = "More options")
Icon(Icons.Default.MoreVert, contentDescription = stringResource(R.string.more_options))
DropdownMenu(expanded = showMenu, onDismissRequest = { showMenu = false }) {
DropdownMenuItem(
text = { Text(stringResource(R.string.rename_item_title)) },
@@ -247,7 +247,7 @@ fun ShoppingListDetailScreen(
}
if (isReorderMode) {
IconButton(modifier = Modifier.draggableHandle(), onClick = {}) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
Icon(Icons.Rounded.DragHandle, contentDescription = stringResource(R.string.reorder))
}
}
}

View File

@@ -131,7 +131,7 @@ fun ShoppingListCard(
) {
if (isReorderMode) {
IconButton(modifier = with(scope) { Modifier.draggableHandle() }, onClick = {}) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
Icon(Icons.Rounded.DragHandle, contentDescription = stringResource(R.string.reorder))
}
}
Text(
@@ -147,7 +147,7 @@ fun ShoppingListCard(
}
if (isReorderMode) {
IconButton(modifier = with(scope) { Modifier.draggableHandle() }, onClick = {}) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
Icon(Icons.Rounded.DragHandle, contentDescription = stringResource(R.string.reorder))
}
}
}

View File

@@ -290,10 +290,9 @@ class ShoppingListsViewModel(private val noteshopRepository: NoteshopRepository)
}
}
fun createStandardList() {
fun createStandardList(standardListName: String) {
viewModelScope.launch {
val allLists = uiState.value.shoppingLists
val standardListName = "Standard"
var newListName = standardListName
var counter = 1
while (allLists.any { it.shoppingList.name == newListName }) {

View File

@@ -32,6 +32,7 @@
<string name="no_items_in_list">Noch keine Artikel in dieser Liste.</string>
<string name="shopping_list_not_found">Einkaufsliste nicht gefunden.</string>
<string name="search_list_hint">Listen suchen...</string>
<string name="search_notes_hint">Notizen durchsuchen...</string>
<string name="add_item_simple_mode">Hinzufügen (Einfach)</string>
<string name="add_item_icon_desc">Artikel hinzufügen</string>
<string name="add_item_simple_icon_desc">Artikel hinzufügen (einfacher Modus)</string>
@@ -60,6 +61,8 @@
<string name="already_in_list">ist schon in der liste</string>
<string name="load_standard_list">Standardliste laden</string>
<string name="search">Suche</string>
<string name="reorder">Neu anordnen</string>
<string name="standard_list_name">Standard</string>
<string-array name="standard_list_items">
<item>Brötchen</item>
<item>Red Bull</item>

View File

@@ -32,6 +32,7 @@
<string name="no_items_in_list">No items in this list yet.</string>
<string name="shopping_list_not_found">Shopping list not found.</string>
<string name="search_list_hint">Search lists...</string>
<string name="search_notes_hint">Search notes...</string>
<string name="add_item_simple_mode">Add (Simple Mode)</string>
<string name="add_item_icon_desc">Add item(s)</string>
<string name="add_item_simple_icon_desc">Add item (simple mode)</string>
@@ -60,6 +61,8 @@
<string name="already_in_list">already in list</string>
<string name="load_standard_list">Load standard list</string>
<string name="search">Search</string>
<string name="reorder">Reorder</string>
<string name="standard_list_name">Standard</string>
<string-array name="standard_list_items">
<item>Bread rolls</item>
<item>Red Bull</item>