feat: Add displayOrder to notes and implement UI

This commit is contained in:
2025-10-13 18:13:00 +02:00
parent 13531415c5
commit af4cf289f9
11 changed files with 336 additions and 130 deletions

View File

@@ -4,6 +4,7 @@ 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
@@ -73,7 +74,10 @@ 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
import de.lxtools.noteshop.ui.notes.NotesViewModel
import de.lxtools.noteshop.ui.shopping.ShoppingListDetailScreen
@@ -82,6 +86,8 @@ 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
// Sealed class to represent the screens in the app
@@ -89,7 +95,8 @@ import kotlinx.parcelize.Parcelize
sealed class Screen(val route: String, val titleRes: Int) : Parcelable {
data object ShoppingLists : Screen("shopping_lists", R.string.menu_shopping_lists)
data object Notes : Screen("notes", R.string.menu_notes)
data object ShoppingListDetail : Screen("shopping_list_detail", R.string.menu_shopping_list_detail) // New screen
data object ShoppingListDetail : Screen("shopping_list_detail", R.string.menu_shopping_list_detail)
data object NoteDetail : Screen("note_detail", R.string.menu_note_detail)
}
class MainActivity : ComponentActivity() {
@@ -114,10 +121,14 @@ fun AppShell(
val scope = rememberCoroutineScope()
var currentScreen: Screen by rememberSaveable { mutableStateOf(Screen.ShoppingLists) }
var selectedListId: Int? by rememberSaveable { mutableStateOf(null) }
var selectedNoteId: Int? by rememberSaveable { mutableStateOf(null) }
var showListDialog by rememberSaveable { mutableStateOf(false) }
val listDetails by shoppingListsViewModel.listDetails.collectAsState()
var showNoteDialog by rememberSaveable { mutableStateOf(false) }
val noteDetails by notesViewModel.noteDetails.collectAsState()
val navigationItems = listOf(
Screen.ShoppingLists,
Screen.Notes
@@ -177,8 +188,10 @@ fun AppShell(
selected = screen == currentScreen,
onClick = {
shoppingListsViewModel.disableReorderMode()
notesViewModel.disableReorderMode()
currentScreen = screen
selectedListId = null
selectedNoteId = null
scope.launch { drawerState.close() }
}
)
@@ -200,6 +213,7 @@ fun AppShell(
topBar = {
val topBarTitle = when (currentScreen) {
is Screen.ShoppingListDetail -> shoppingListWithItems?.shoppingList?.name ?: ""
is Screen.NoteDetail -> notesViewModel.noteDetails.collectAsState().value.title
else -> stringResource(id = currentScreen.titleRes)
}
@@ -224,6 +238,17 @@ fun AppShell(
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
}
}
is Screen.NoteDetail -> {
IconButton(onClick = {
scope.launch {
notesViewModel.saveNote()
currentScreen = Screen.Notes
selectedNoteId = null
}
}) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = stringResource(R.string.back))
}
}
else -> {
IconButton(onClick = { scope.launch { drawerState.apply { if (isClosed) open() else close() } } }) {
Icon(imageVector = Icons.Default.Menu, contentDescription = stringResource(id = R.string.menu_open))
@@ -241,6 +266,14 @@ fun AppShell(
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.add_shopping_list))
}
}
if (currentScreen == Screen.Notes && !isReorderMode) {
IconButton(onClick = {
notesViewModel.resetNoteDetails()
showNoteDialog = true
}) {
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.add_note))
}
}
if (currentScreen == Screen.ShoppingListDetail) {
IconButton(onClick = { shoppingListsViewModel.toggleDetailSearch() }) {
Icon(Icons.Default.Search, contentDescription = "Search")
@@ -327,6 +360,22 @@ fun AppShell(
)
}
if (showNoteDialog) {
NoteInputDialog(
noteDetails = noteDetails,
onValueChange = notesViewModel::updateNoteDetails,
onSave = {
scope.launch {
notesViewModel.saveNote()
showNoteDialog = false
notesViewModel.resetNoteDetails()
}
},
onDismiss = { showNoteDialog = false },
isNewNote = noteDetails.id == 0
)
}
Column(
modifier = Modifier
.fillMaxSize()
@@ -348,7 +397,17 @@ fun AppShell(
)
}
is Screen.Notes -> {
NotesScreen(viewModel = notesViewModel)
NotesScreen(
viewModel = notesViewModel,
onNoteClick = { noteId ->
selectedNoteId = noteId
currentScreen = Screen.NoteDetail
},
onEditNote = { note ->
notesViewModel.updateNoteDetails(note)
showNoteDialog = true
}
)
}
is Screen.ShoppingListDetail -> {
ShoppingListDetailScreen(
@@ -356,6 +415,12 @@ fun AppShell(
viewModel = shoppingListsViewModel
)
}
is Screen.NoteDetail -> {
NoteDetailScreen(
noteId = selectedNoteId,
viewModel = notesViewModel
)
}
}
}
}
@@ -366,6 +431,6 @@ fun AppShell(
@Composable
fun DefaultPreview() {
NoteshopTheme {
AppShell()
// AppShell()
}
}
}

View File

@@ -10,7 +10,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
@Database(
entities = [Note::class, ShoppingList::class, ShoppingListItem::class],
version = 4, // Incremented version
version = 5, // Incremented version
exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
@@ -31,7 +31,7 @@ abstract class AppDatabase : RoomDatabase() {
AppDatabase::class.java,
"noteshop_database"
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5)
.build()
INSTANCE = instance
// return instance
@@ -56,5 +56,11 @@ abstract class AppDatabase : RoomDatabase() {
db.execSQL("ALTER TABLE shopping_list_items ADD COLUMN displayOrder INTEGER NOT NULL DEFAULT 0")
}
}
private val MIGRATION_4_5 = object : Migration(4, 5) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE notes ADD COLUMN displayOrder INTEGER NOT NULL DEFAULT 0")
}
}
}
}

View File

@@ -8,5 +8,6 @@ data class Note(
@PrimaryKey(autoGenerate = true)
val id: Int = 0,
val title: String,
val content: String
val content: String,
val displayOrder: Int = 0
)

View File

@@ -23,6 +23,12 @@ interface NoteDao {
@Query("SELECT * FROM notes WHERE id = :id")
fun getNote(id: Int): Flow<Note?>
@Query("SELECT * FROM notes ORDER BY id DESC")
@Query("SELECT * FROM notes ORDER BY displayOrder ASC")
fun getAllNotes(): Flow<List<Note>>
}
@Query("SELECT COUNT(*) FROM notes")
suspend fun getNotesCount(): Int
@Update
suspend fun updateNote(note: Note)
}

View File

@@ -31,6 +31,11 @@ interface NoteshopRepository {
*/
suspend fun deleteNote(note: Note)
/**
* Get the number of notes.
*/
suspend fun getNotesCount(): Int
/**
* Retrieve all the shopping lists with their items from the data source.
*/
@@ -100,10 +105,12 @@ class OfflineNoteshopRepository(private val noteDao: NoteDao, private val shoppi
override suspend fun insertNote(note: Note) = noteDao.insert(note)
override suspend fun updateNote(note: Note) = noteDao.update(note)
override suspend fun updateNote(note: Note) = noteDao.updateNote(note)
override suspend fun deleteNote(note: Note) = noteDao.delete(note)
override suspend fun getNotesCount(): Int = noteDao.getNotesCount()
override fun getAllShoppingListsWithItemsStream(): Flow<List<ShoppingListWithItems>> = shoppingListDao.getListsWithItems()
override fun getShoppingListWithItemsStream(listId: Int): Flow<ShoppingListWithItems?> = shoppingListDao.getListWithItems(listId)
@@ -130,4 +137,4 @@ class OfflineNoteshopRepository(private val noteDao: NoteDao, private val shoppi
return context.resources.getStringArray(de.lxtools.noteshop.R.array.standard_list_items).toList()
}
}
}

View File

@@ -0,0 +1,51 @@
package de.lxtools.noteshop.ui.notes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.LaunchedEffect
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
fun NoteDetailScreen(
noteId: Int?,
viewModel: NotesViewModel,
modifier: Modifier = Modifier
) {
val coroutineScope = rememberCoroutineScope()
val noteDetails by viewModel.noteDetails.collectAsState()
LaunchedEffect(key1 = noteId) {
if (noteId != null) {
viewModel.uiState.collect { uiState ->
uiState.noteList.find { it.id == noteId }?.let {
viewModel.updateNoteDetails(it)
}
}
}
}
Column(
modifier = modifier
.fillMaxSize()
.padding(16.dp)
) {
OutlinedTextField(
value = noteDetails.content,
onValueChange = { viewModel.updateNoteDetails(noteDetails.copy(content = it)) },
modifier = Modifier
.fillMaxWidth()
.weight(1f)
)
}
}

View File

@@ -0,0 +1,51 @@
package de.lxtools.noteshop.ui.notes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import de.lxtools.noteshop.R
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NoteInputDialog(
noteDetails: NoteDetails,
onValueChange: (NoteDetails) -> Unit,
onSave: () -> Unit,
onDismiss: () -> Unit,
isNewNote: Boolean,
modifier: Modifier = Modifier
) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(text = if (isNewNote) stringResource(R.string.add_note) else stringResource(R.string.edit_note)) },
text = {
Column {
OutlinedTextField(
value = noteDetails.title,
onValueChange = { onValueChange(noteDetails.copy(title = it)) },
label = { Text(stringResource(R.string.note_title)) },
modifier = Modifier.fillMaxWidth()
)
}
},
confirmButton = {
Button(onClick = onSave, enabled = noteDetails.isValid()) {
Text(stringResource(R.string.save))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel))
}
},
modifier = modifier
)
}

View File

@@ -1,6 +1,7 @@
package de.lxtools.noteshop.ui.notes
import androidx.compose.foundation.clickable
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -9,27 +10,25 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.rounded.DragHandle
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
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.Text
import androidx.compose.material3.TextButton
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.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@@ -37,130 +36,113 @@ import de.lxtools.noteshop.AppViewModelProvider
import de.lxtools.noteshop.R
import de.lxtools.noteshop.data.Note
import kotlinx.coroutines.launch
import sh.calvin.reorderable.ReorderableCollectionItemScope
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NotesScreen(viewModel: NotesViewModel = viewModel(factory = AppViewModelProvider.Factory), modifier: Modifier = Modifier) {
fun NotesScreen(
viewModel: NotesViewModel = viewModel(factory = AppViewModelProvider.Factory),
modifier: Modifier = Modifier,
onNoteClick: (Int) -> Unit,
onEditNote: (Note) -> Unit
) {
val uiState by viewModel.uiState.collectAsState()
val noteDetails by viewModel.noteDetails.collectAsState()
var showDialog by remember { mutableStateOf(false) }
val isReorderMode by viewModel.isReorderMode.collectAsState()
val coroutineScope = rememberCoroutineScope()
Scaffold(
floatingActionButton = {
FloatingActionButton(onClick = {
viewModel.resetNoteDetails()
showDialog = true
}) {
Icon(Icons.Default.Add, contentDescription = stringResource(R.string.add_note))
Column(
modifier = modifier.fillMaxSize()
) {
if (uiState.noteList.isEmpty()) {
Text(text = stringResource(R.string.no_notes_yet))
} else {
val lazyListState = rememberLazyListState()
val reorderableLazyListState = rememberReorderableLazyListState(lazyListState) { from, to ->
viewModel.moveNote(from.index, to.index)
}
}
) { innerPadding ->
Column(
modifier = modifier
.fillMaxSize()
.padding(innerPadding)
.padding(16.dp)
) {
if (uiState.noteList.isEmpty()) {
Text(text = stringResource(R.string.no_notes_yet))
} else {
LazyColumn {
items(uiState.noteList) { note ->
LazyColumn(
state = lazyListState,
) {
items(uiState.noteList, { it.id }) { note ->
ReorderableItem(reorderableLazyListState, key = note.id) { isDragging ->
NoteCard(
note = note,
onEdit = {
viewModel.updateNoteDetails(it)
showDialog = true
},
onDelete = {
onEditNote = onEditNote,
onDeleteNote = {
coroutineScope.launch {
viewModel.deleteNote(it)
}
}
},
modifier = Modifier.pointerInput(isReorderMode) {
if (!isReorderMode) {
detectTapGestures(
onLongPress = { viewModel.enableReorderMode() },
onTap = { onNoteClick(note.id) }
)
}
},
scope = this,
isDragging = isDragging,
isReorderMode = isReorderMode
)
}
}
}
}
if (showDialog) {
NoteInputDialog(
noteDetails = noteDetails,
onValueChange = viewModel::updateNoteDetails,
onSave = {
coroutineScope.launch {
viewModel.saveNote()
showDialog = false
viewModel.resetNoteDetails()
}
},
onDismiss = { showDialog = false },
isNewNote = noteDetails.id == 0
)
}
}
}
@Composable
fun NoteCard(note: Note, onEdit: (Note) -> Unit, onDelete: (Note) -> Unit, modifier: Modifier = Modifier) {
fun NoteCard(
note: Note,
onEditNote: (Note) -> Unit,
onDeleteNote: (Note) -> Unit,
modifier: Modifier = Modifier,
scope: ReorderableCollectionItemScope,
isDragging: Boolean,
isReorderMode: Boolean
) {
val scale = animateFloatAsState(if (isDragging) 1.05f else 1.0f, label = "scale")
Card(
modifier = modifier
.fillMaxWidth()
.padding(vertical = 4.dp)
.clickable { onEdit(note) }
.padding(vertical = 4.dp, horizontal = 16.dp)
.graphicsLayer {
scaleX = scale.value
scaleY = scale.value
}
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(text = note.title, style = MaterialTheme.typography.titleMedium)
Text(text = note.content, style = MaterialTheme.typography.bodyMedium)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
TextButton(onClick = { onDelete(note) }) {
Text(stringResource(R.string.delete))
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
if (isReorderMode) {
IconButton(modifier = with(scope) { Modifier.draggableHandle() }, onClick = {}) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
Text(
text = note.title,
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.weight(1f)
)
IconButton(onClick = { onEditNote(note) }, enabled = !isReorderMode) {
Icon(Icons.Default.Edit, contentDescription = stringResource(R.string.edit_note))
}
IconButton(onClick = { onDeleteNote(note) }, enabled = !isReorderMode) {
Icon(Icons.Default.Delete, contentDescription = stringResource(R.string.delete_note))
}
if (isReorderMode) {
IconButton(modifier = with(scope) { Modifier.draggableHandle() }, onClick = {}) {
Icon(Icons.Rounded.DragHandle, contentDescription = "Reorder")
}
}
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NoteInputDialog(
noteDetails: NoteDetails,
onValueChange: (NoteDetails) -> Unit,
onSave: () -> Unit,
onDismiss: () -> Unit,
isNewNote: Boolean,
modifier: Modifier = Modifier
) {
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(text = if (isNewNote) stringResource(R.string.add_note) else stringResource(R.string.edit_note)) },
text = {
Column {
OutlinedTextField(
value = noteDetails.title,
onValueChange = { onValueChange(noteDetails.copy(title = it)) },
label = { Text(stringResource(R.string.note_title)) },
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = noteDetails.content,
onValueChange = { onValueChange(noteDetails.copy(content = it)) },
label = { Text(stringResource(R.string.note_content)) },
modifier = Modifier.fillMaxWidth()
)
}
},
confirmButton = {
Button(onClick = onSave, enabled = noteDetails.isValid()) {
Text(stringResource(R.string.save))
}
},
dismissButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.cancel))
}
},
modifier = modifier
)
}

View File

@@ -14,12 +14,24 @@ import kotlinx.coroutines.launch
class NotesViewModel(private val noteshopRepository: NoteshopRepository) : ViewModel() {
val uiState: StateFlow<NotesUiState> = noteshopRepository.getAllNotesStream().map { NotesUiState(it) }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(TIMEOUT_MILLIS),
initialValue = NotesUiState()
)
private val _isReorderMode = MutableStateFlow(false)
val isReorderMode: StateFlow<Boolean> = _isReorderMode.asStateFlow()
fun enableReorderMode() {
_isReorderMode.value = true
}
fun disableReorderMode() {
_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()
)
private val _noteDetails = MutableStateFlow(NoteDetails())
val noteDetails: StateFlow<NoteDetails> = _noteDetails.asStateFlow()
@@ -28,7 +40,8 @@ class NotesViewModel(private val noteshopRepository: NoteshopRepository) : ViewM
_noteDetails.value = NoteDetails(
id = note.id,
title = note.title,
content = note.content
content = note.content,
displayOrder = note.displayOrder
)
}
@@ -38,7 +51,12 @@ class NotesViewModel(private val noteshopRepository: NoteshopRepository) : ViewM
suspend fun saveNote() {
if (noteDetails.value.isValid()) {
noteshopRepository.insertNote(noteDetails.value.toNote())
var currentNote = noteDetails.value.toNote()
if (currentNote.id == 0) {
val notesCount = noteshopRepository.getNotesCount()
currentNote = currentNote.copy(displayOrder = notesCount)
}
noteshopRepository.insertNote(currentNote)
}
}
@@ -50,6 +68,19 @@ class NotesViewModel(private val noteshopRepository: NoteshopRepository) : ViewM
_noteDetails.value = NoteDetails()
}
fun moveNote(from: Int, to: Int) {
viewModelScope.launch {
val list = uiState.value.noteList.toMutableList()
list.add(to, list.removeAt(from))
val updatedNotes = list.mapIndexed { index, item ->
item.copy(displayOrder = index)
}
updatedNotes.forEach { note ->
noteshopRepository.updateNote(note)
}
}
}
companion object {
private const val TIMEOUT_MILLIS = 5_000L
}
@@ -62,15 +93,17 @@ data class NotesUiState(
data class NoteDetails(
val id: Int = 0,
val title: String = "",
val content: String = ""
val content: String = "",
val displayOrder: Int = 0
) {
fun toNote(): Note = Note(
id = id,
title = title,
content = content
content = content,
displayOrder = displayOrder
)
fun isValid(): Boolean {
return title.isNotBlank() && content.isNotBlank()
return title.isNotBlank()
}
}
}

View File

@@ -7,6 +7,7 @@
<string name="add_note">Notiz hinzufügen</string>
<string name="edit_note">Notiz bearbeiten</string>
<string name="delete_note">Notiz löschen</string>
<string name="delete">Löschen</string>
<string name="no_notes_yet">Noch keine Notizen. Klicke auf + um eine hinzuzufügen!</string>
<string name="note_title">Titel</string>
@@ -27,6 +28,7 @@
<string name="edit_list">Liste bearbeiten</string>
<string name="delete_list">Liste löschen</string>
<string name="menu_shopping_list_detail">Einkaufslisten-Details</string>
<string name="menu_note_detail">Notizdetails</string>
<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>

View File

@@ -7,6 +7,7 @@
<string name="add_note">Add Note</string>
<string name="edit_note">Edit Note</string>
<string name="delete_note">Delete note</string>
<string name="delete">Delete</string>
<string name="no_notes_yet">No notes yet. Click + to add one!</string>
<string name="note_title">Title</string>
@@ -27,6 +28,7 @@
<string name="edit_list">Edit list</string>
<string name="delete_list">Delete list</string>
<string name="menu_shopping_list_detail">Shopping List Details</string>
<string name="menu_note_detail">Note Details</string>
<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>